diff --git a/contrib/curl/include/Makefile.am b/contrib/curl/include/Makefile.am index 3b24860299..d65bfeaa0f 100644 --- a/contrib/curl/include/Makefile.am +++ b/contrib/curl/include/Makefile.am @@ -1,5 +1,28 @@ +#*************************************************************************** +# _ _ ____ _ +# Project ___| | | | _ \| | +# / __| | | | |_) | | +# | (__| |_| | _ <| |___ +# \___|\___/|_| \_\_____| +# +# Copyright (C) Daniel Stenberg, , et al. +# +# This software is licensed as described in the file COPYING, which +# you should have received as part of this distribution. The terms +# are also available at https://curl.se/docs/copyright.html. +# +# You may opt to use, copy, modify, merge, publish, distribute and/or sell +# copies of the Software, and permit persons to whom the Software is +# furnished to do so, under the terms of the COPYING file. +# +# This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY +# KIND, either express or implied. +# +# SPDX-License-Identifier: curl +# +########################################################################### SUBDIRS = curl -EXTRA_DIST = README +EXTRA_DIST = README.md AUTOMAKE_OPTIONS = foreign no-dependencies diff --git a/contrib/curl/include/README b/contrib/curl/include/README deleted file mode 100644 index 6eb73b2b74..0000000000 --- a/contrib/curl/include/README +++ /dev/null @@ -1,55 +0,0 @@ - _ _ ____ _ - ___| | | | _ \| | - / __| | | | |_) | | - | (__| |_| | _ <| |___ - \___|\___/|_| \_\_____| - -Include files for libcurl, external users. - -They're all placed in the curl subdirectory here for better fit in any kind -of environment. You must include files from here using... - - #include - -... style and point the compiler's include path to the directory holding the -curl subdirectory. It makes it more likely to survive future modifications. - -NOTE FOR LIBCURL HACKERS - -The following notes apply to libcurl version 7.19.0 and later. - -* The distributed curl/curlbuild.h file is only intended to be used on systems - which can not run the also distributed configure script. - -* The distributed curlbuild.h file is generated as a copy of curlbuild.h.dist - when the libcurl source code distribution archive file is originally created. - -* If you check out from git on a non-configure platform, you must run the - appropriate buildconf* script to set up curlbuild.h and other local files - before being able of compiling the library. - -* On systems capable of running the configure script, the configure process - will overwrite the distributed include/curl/curlbuild.h file with one that - is suitable and specific to the library being configured and built, which - is generated from the include/curl/curlbuild.h.in template file. - -* If you intend to distribute an already compiled libcurl library you _MUST_ - also distribute along with it the generated curl/curlbuild.h which has been - used to compile it. Otherwise the library will be of no use for the users of - the library that you have built. It is _your_ responsibility to provide this - file. No one at the curl project can know how you have built the library. - -* File curl/curlbuild.h includes platform and configuration dependent info, - and must not be modified by anyone. Configure script generates it for you. - -* We cannot assume anything else but very basic compiler features being - present. While libcurl requires an ANSI C compiler to build, some of the - earlier ANSI compilers clearly can't deal with some preprocessor operators. - -* Newlines must remain unix-style for older compilers' sake. - -* Comments must be written in the old-style /* unnested C-fashion */ - -To figure out how to do good and portable checks for features, operating -systems or specific hardwarare, a very good resource is Bjorn Reese's -collection at http://predef.sf.net/ diff --git a/contrib/curl/include/README.md b/contrib/curl/include/README.md new file mode 100644 index 0000000000..b155d8c13b --- /dev/null +++ b/contrib/curl/include/README.md @@ -0,0 +1,20 @@ + + +# include + +Public include files for libcurl, external users. + +They are all placed in the curl subdirectory here for better fit in any kind of +environment. You must include files from here using... + + #include + +... style and point the compiler's include path to the directory holding the +curl subdirectory. It makes it more likely to survive future modifications. + +The public curl include files can be shared freely between different platforms +and different architectures. diff --git a/contrib/curl/include/curl/.gitignore b/contrib/curl/include/curl/.gitignore index 228a961fbb..2467938825 100644 --- a/contrib/curl/include/curl/.gitignore +++ b/contrib/curl/include/curl/.gitignore @@ -1,4 +1,7 @@ -curlbuild.h +# Copyright (C) Daniel Stenberg, , et al. +# +# SPDX-License-Identifier: curl + curlver.h.dist stamp-h2 stamp-h3 diff --git a/contrib/curl/include/curl/Makefile.am b/contrib/curl/include/curl/Makefile.am index 7c924fcb5a..8864d60e1f 100644 --- a/contrib/curl/include/curl/Makefile.am +++ b/contrib/curl/include/curl/Makefile.am @@ -5,11 +5,11 @@ # | (__| |_| | _ <| |___ # \___|\___/|_| \_\_____| # -# Copyright (C) 1998 - 2011, Daniel Stenberg, , et al. +# Copyright (C) Daniel Stenberg, , et al. # # This software is licensed as described in the file COPYING, which # you should have received as part of this distribution. The terms -# are also available at https://curl.haxx.se/docs/copyright.html. +# are also available at https://curl.se/docs/copyright.html. # # You may opt to use, copy, modify, merge, publish, distribute and/or sell # copies of the Software, and permit persons to whom the Software is @@ -18,36 +18,24 @@ # This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY # KIND, either express or implied. # +# SPDX-License-Identifier: curl +# ########################################################################### pkginclude_HEADERS = \ - curl.h curlver.h easy.h mprintf.h stdcheaders.h multi.h \ - typecheck-gcc.h curlbuild.h curlrules.h + curl.h curlver.h easy.h mprintf.h stdcheaders.h multi.h \ + typecheck-gcc.h system.h urlapi.h options.h header.h websockets.h pkgincludedir= $(includedir)/curl -# curlbuild.h does not exist in the git tree. When the original libcurl -# source code distribution archive file is created, curlbuild.h.dist is -# renamed to curlbuild.h and included in the tarball so that it can be -# used directly on non-configure systems. -# -# The distributed curlbuild.h will be overwritten on configure systems -# when the configure script runs, with one that is suitable and specific -# to the library being configured and built. -# -# curlbuild.h.in is the distributed template file from which the configure -# script creates curlbuild.h at library configuration time, overwiting the -# one included in the distribution archive. -# -# curlbuild.h.dist is not included in the source code distribution archive. - -EXTRA_DIST = curlbuild.h.in - -DISTCLEANFILES = curlbuild.h +CHECKSRC = $(CS_$(V)) +CS_0 = @echo " RUN " $@; +CS_1 = +CS_ = $(CS_0) checksrc: - @@PERL@ $(top_srcdir)/lib/checksrc.pl -Wcurlbuild.h -D$(top_srcdir)/include/curl $(pkginclude_HEADERS) $(EXTRA_DIST) + $(CHECKSRC)@PERL@ $(top_srcdir)/scripts/checksrc.pl -D$(top_srcdir)/include/curl $(pkginclude_HEADERS) -if CURLDEBUG +if DEBUGBUILD # for debug builds, we scan the sources on all regular make invokes all-local: checksrc endif diff --git a/contrib/curl/include/curl/curl.h b/contrib/curl/include/curl/curl.h index 467bb02409..043b7103e8 100644 --- a/contrib/curl/include/curl/curl.h +++ b/contrib/curl/include/curl/curl.h @@ -1,5 +1,5 @@ -#ifndef __CURL_CURL_H -#define __CURL_CURL_H +#ifndef CURLINC_CURL_H +#define CURLINC_CURL_H /*************************************************************************** * _ _ ____ _ * Project ___| | | | _ \| | @@ -7,11 +7,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2017, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -20,50 +20,65 @@ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY * KIND, either express or implied. * + * SPDX-License-Identifier: curl + * ***************************************************************************/ /* * If you have libcurl problems, all docs and details are found here: - * https://curl.haxx.se/libcurl/ - * - * curl-library mailing list subscription and unsubscription web interface: - * https://cool.haxx.se/mailman/listinfo/curl-library/ + * https://curl.se/libcurl/ */ #ifdef CURL_NO_OLDIES #define CURL_STRICTER #endif -#include "curlver.h" /* libcurl version defines */ -#include "curlbuild.h" /* libcurl build definitions */ -#include "curlrules.h" /* libcurl rules enforcement */ - -/* - * Define WIN32 when build target is Win32 API - */ - -#if (defined(_WIN32) || defined(__WIN32__)) && \ - !defined(WIN32) && !defined(__SYMBIAN32__) -#define WIN32 +/* Compile-time deprecation macros. */ +#if (defined(__GNUC__) && \ + ((__GNUC__ > 12) || ((__GNUC__ == 12) && (__GNUC_MINOR__ >= 1))) || \ + (defined(__clang__) && __clang_major__ >= 3) || \ + defined(__IAR_SYSTEMS_ICC__)) && \ + !defined(__INTEL_COMPILER) && \ + !defined(CURL_DISABLE_DEPRECATION) && !defined(BUILDING_LIBCURL) +#define CURL_DEPRECATED(version, message) \ + __attribute__((deprecated("since " # version ". " message))) +#if defined(__IAR_SYSTEMS_ICC__) +#define CURL_IGNORE_DEPRECATION(statements) \ + _Pragma("diag_suppress=Pe1444") \ + statements \ + _Pragma("diag_default=Pe1444") +#else +#define CURL_IGNORE_DEPRECATION(statements) \ + _Pragma("GCC diagnostic push") \ + _Pragma("GCC diagnostic ignored \"-Wdeprecated-declarations\"") \ + statements \ + _Pragma("GCC diagnostic pop") #endif +#else +#define CURL_DEPRECATED(version, message) +#define CURL_IGNORE_DEPRECATION(statements) statements +#endif + +#include "curlver.h" /* libcurl version defines */ +#include "system.h" /* determine things runtime */ #include #include -#if defined(__FreeBSD__) && (__FreeBSD__ >= 2) -/* Needed for __FreeBSD_version symbol definition */ -#include +#if defined(__FreeBSD__) || defined(__MidnightBSD__) +/* Needed for __FreeBSD_version or __MidnightBSD_version symbol definition */ +#include #endif /* The include stuff here below is mainly for time_t! */ #include #include -#if defined(WIN32) && !defined(_WIN32_WCE) && !defined(__CYGWIN__) +#if defined(_WIN32) && !defined(_WIN32_WCE) && !defined(__CYGWIN__) #if !(defined(_WINSOCKAPI_) || defined(_WINSOCK_H) || \ defined(__LWIP_OPT_H__) || defined(LWIP_HDR_OPT_H)) -/* The check above prevents the winsock2 inclusion if winsock.h already was - included, since they can't co-exist without problems */ +/* The check above prevents the winsock2.h inclusion if winsock.h already was + included, since they cannot co-exist without problems */ #include #include #endif @@ -73,22 +88,26 @@ libc5-based Linux systems. Only include it on systems that are known to require it! */ #if defined(_AIX) || defined(__NOVELL_LIBC__) || defined(__NetBSD__) || \ - defined(__minix) || defined(__SYMBIAN32__) || defined(__INTEGRITY) || \ + defined(__minix) || defined(__INTEGRITY) || \ defined(ANDROID) || defined(__ANDROID__) || defined(__OpenBSD__) || \ - (defined(__FreeBSD_version) && (__FreeBSD_version < 800000)) + defined(__CYGWIN__) || defined(AMIGA) || defined(__NuttX__) || \ + (defined(__FreeBSD_version) && (__FreeBSD_version < 800000)) || \ + (defined(__MidnightBSD_version) && (__MidnightBSD_version < 100000)) || \ + defined(__sun__) || defined(__serenity__) || defined(__vxworks__) #include #endif -#if !defined(WIN32) && !defined(_WIN32_WCE) +#if !defined(_WIN32) && !defined(_WIN32_WCE) #include #endif -#if !defined(WIN32) && !defined(__WATCOMC__) && !defined(__VXWORKS__) +#if !defined(_WIN32) #include #endif -#ifdef __BEOS__ -#include +/* Compatibility for non-Clang compilers */ +#ifndef __has_declspec_attribute +# define __has_declspec_attribute(x) 0 #endif #ifdef __cplusplus @@ -109,7 +128,9 @@ typedef void CURLSH; #ifdef CURL_STATICLIB # define CURL_EXTERN -#elif defined(WIN32) || defined(_WIN32) || defined(__SYMBIAN32__) +#elif defined(_WIN32) || \ + (__has_declspec_attribute(dllexport) && \ + __has_declspec_attribute(dllimport)) # if defined(BUILDING_LIBCURL) # define CURL_EXTERN __declspec(dllexport) # else @@ -123,7 +144,7 @@ typedef void CURLSH; #ifndef curl_socket_typedef /* socket typedef */ -#if defined(WIN32) && !defined(__LWIP_OPT_H__) && !defined(LWIP_HDR_OPT_H) +#if defined(_WIN32) && !defined(__LWIP_OPT_H__) && !defined(LWIP_HDR_OPT_H) typedef SOCKET curl_socket_t; #define CURL_SOCKET_BAD INVALID_SOCKET #else @@ -133,6 +154,34 @@ typedef int curl_socket_t; #define curl_socket_typedef #endif /* curl_socket_typedef */ +/* enum for the different supported SSL backends */ +typedef enum { + CURLSSLBACKEND_NONE = 0, + CURLSSLBACKEND_OPENSSL = 1, + CURLSSLBACKEND_GNUTLS = 2, + CURLSSLBACKEND_NSS CURL_DEPRECATED(8.3.0, "") = 3, + CURLSSLBACKEND_OBSOLETE4 = 4, /* Was QSOSSL. */ + CURLSSLBACKEND_GSKIT CURL_DEPRECATED(8.3.0, "") = 5, + CURLSSLBACKEND_POLARSSL CURL_DEPRECATED(7.69.0, "") = 6, + CURLSSLBACKEND_WOLFSSL = 7, + CURLSSLBACKEND_SCHANNEL = 8, + CURLSSLBACKEND_SECURETRANSPORT = 9, + CURLSSLBACKEND_AXTLS CURL_DEPRECATED(7.61.0, "") = 10, + CURLSSLBACKEND_MBEDTLS = 11, + CURLSSLBACKEND_MESALINK CURL_DEPRECATED(7.82.0, "") = 12, + CURLSSLBACKEND_BEARSSL = 13, + CURLSSLBACKEND_RUSTLS = 14 +} curl_sslbackend; + +/* aliases for library clones and renames */ +#define CURLSSLBACKEND_AWSLC CURLSSLBACKEND_OPENSSL +#define CURLSSLBACKEND_BORINGSSL CURLSSLBACKEND_OPENSSL +#define CURLSSLBACKEND_LIBRESSL CURLSSLBACKEND_OPENSSL + +/* deprecated names: */ +#define CURLSSLBACKEND_CYASSL CURLSSLBACKEND_WOLFSSL +#define CURLSSLBACKEND_DARWINSSL CURLSSLBACKEND_SECURETRANSPORT + struct curl_httppost { struct curl_httppost *next; /* next entry in the list */ char *name; /* pointer to allocated name */ @@ -149,9 +198,9 @@ struct curl_httppost { files */ long flags; /* as defined below */ -/* specified content is a file name */ +/* specified content is a filename */ #define CURL_HTTPPOST_FILENAME (1<<0) -/* specified content is a file name */ +/* specified content is a filename */ #define CURL_HTTPPOST_READFILE (1<<1) /* name is only stored pointer do not free in formfree */ #define CURL_HTTPPOST_PTRNAME (1<<2) @@ -167,8 +216,8 @@ struct curl_httppost { /* use size in 'contentlen', added in 7.46.0 */ #define CURL_HTTPPOST_LARGE (1<<7) - char *showfilename; /* The file name to show. If not set, the - actual file name will be used (if this + char *showfilename; /* The filename to show. If not set, the + actual filename will be used (if this is a file part) */ void *userp; /* custom pointer used for HTTPPOST_CALLBACK posts */ @@ -177,16 +226,22 @@ struct curl_httppost { set. Added in 7.46.0 */ }; -/* This is the CURLOPT_PROGRESSFUNCTION callback proto. It is now considered - deprecated but was the only choice up until 7.31.0 */ + +/* This is a return code for the progress callback that, when returned, will + signal libcurl to continue executing the default progress function */ +#define CURL_PROGRESSFUNC_CONTINUE 0x10000001 + +/* This is the CURLOPT_PROGRESSFUNCTION callback prototype. It is now + considered deprecated but was the only choice up until 7.31.0 */ typedef int (*curl_progress_callback)(void *clientp, double dltotal, double dlnow, double ultotal, double ulnow); -/* This is the CURLOPT_XFERINFOFUNCTION callback proto. It was introduced in - 7.32.0, it avoids floating point and provides more detailed information. */ +/* This is the CURLOPT_XFERINFOFUNCTION callback prototype. It was introduced + in 7.32.0, avoids the use of floating point numbers and provides more + detailed information. */ typedef int (*curl_xferinfo_callback)(void *clientp, curl_off_t dltotal, curl_off_t dlnow, @@ -195,7 +250,7 @@ typedef int (*curl_xferinfo_callback)(void *clientp, #ifndef CURL_MAX_READ_SIZE /* The maximum receive buffer size configurable via CURLOPT_BUFFERSIZE. */ -#define CURL_MAX_READ_SIZE 524288 +#define CURL_MAX_READ_SIZE (10*1024*1024) #endif #ifndef CURL_MAX_WRITE_SIZE @@ -219,12 +274,18 @@ typedef int (*curl_xferinfo_callback)(void *clientp, will signal libcurl to pause receiving on the current transfer. */ #define CURL_WRITEFUNC_PAUSE 0x10000001 +/* This is a magic return code for the write callback that, when returned, + will signal an error from the callback. */ +#define CURL_WRITEFUNC_ERROR 0xFFFFFFFF + typedef size_t (*curl_write_callback)(char *buffer, size_t size, size_t nitems, void *outstream); - +/* This callback will be called when a new resolver request is made */ +typedef int (*curl_resolver_start_callback)(void *resolver_state, + void *reserved, void *userdata); /* enumeration of file types */ typedef enum { @@ -249,14 +310,11 @@ typedef enum { #define CURLFINFOFLAG_KNOWN_SIZE (1<<6) #define CURLFINFOFLAG_KNOWN_HLINKCOUNT (1<<7) -/* Content of this structure depends on information which is known and is - achievable (e.g. by FTP LIST parsing). Please see the url_easy_setopt(3) man - page for callbacks returning this structure -- some fields are mandatory, - some others are optional. The FLAG field has special meaning. */ +/* Information about a single file, used when doing FTP wildcard matching */ struct curl_fileinfo { char *filename; curlfiletype filetype; - time_t time; + time_t time; /* always zero! */ unsigned int perm; int uid; int gid; @@ -274,7 +332,8 @@ struct curl_fileinfo { unsigned int flags; - /* used internally */ + /* These are libcurl private struct fields. Previously used by libcurl, so + they must never be interfered with. */ char *b_data; size_t b_size; size_t b_used; @@ -300,13 +359,13 @@ typedef long (*curl_chunk_bgn_callback)(const void *transfer_info, download of an individual chunk finished. Note! After this callback was set then it have to be called FOR ALL chunks. Even if downloading of this chunk was skipped in CHUNK_BGN_FUNC. - This is the reason why we don't need "transfer_info" parameter in this + This is the reason why we do not need "transfer_info" parameter in this callback and we are not interested in "remains" parameter too. */ typedef long (*curl_chunk_end_callback)(void *ptr); /* return codes for FNMATCHFUNCTION */ #define CURL_FNMATCHFUNC_MATCH 0 /* string corresponds to the pattern */ -#define CURL_FNMATCHFUNC_NOMATCH 1 /* pattern doesn't match the string */ +#define CURL_FNMATCHFUNC_NOMATCH 1 /* pattern does not match the string */ #define CURL_FNMATCHFUNC_FAIL 2 /* an error occurred */ /* callback type for wildcard downloading pattern matching. If the @@ -318,7 +377,7 @@ typedef int (*curl_fnmatch_callback)(void *ptr, /* These are the return codes for the seek callbacks */ #define CURL_SEEKFUNC_OK 0 #define CURL_SEEKFUNC_FAIL 1 /* fail the entire transfer */ -#define CURL_SEEKFUNC_CANTSEEK 2 /* tell libcurl seeking can't be done, so +#define CURL_SEEKFUNC_CANTSEEK 2 /* tell libcurl seeking cannot be done, so libcurl might try other means instead */ typedef int (*curl_seek_callback)(void *instream, curl_off_t offset, @@ -331,12 +390,22 @@ typedef int (*curl_seek_callback)(void *instream, signal libcurl to pause sending data on the current transfer. */ #define CURL_READFUNC_PAUSE 0x10000001 +/* Return code for when the trailing headers' callback has terminated + without any errors */ +#define CURL_TRAILERFUNC_OK 0 +/* Return code for when was an error in the trailing header's list and we + want to abort the request */ +#define CURL_TRAILERFUNC_ABORT 1 + typedef size_t (*curl_read_callback)(char *buffer, size_t size, size_t nitems, void *instream); -typedef enum { +typedef int (*curl_trailer_callback)(struct curl_slist **list, + void *userdata); + +typedef enum { CURLSOCKTYPE_IPCXN, /* socket created for a specific IP connection */ CURLSOCKTYPE_ACCEPT, /* socket created by accept() call */ CURLSOCKTYPE_LAST /* never use */ @@ -378,7 +447,7 @@ typedef enum { CURLIOE_LAST /* never use */ } curlioerr; -typedef enum { +typedef enum { CURLIOCMD_NOP, /* no operation */ CURLIOCMD_RESTARTREAD, /* restart the read stream from start */ CURLIOCMD_LAST /* never use */ @@ -391,7 +460,7 @@ typedef curlioerr (*curl_ioctl_callback)(CURL *handle, #ifndef CURL_DID_MEMORY_FUNC_TYPEDEFS /* * The following typedef's are signatures of malloc, free, realloc, strdup and - * calloc respectively. Function pointers of these types can be passed to the + * calloc respectively. Function pointers of these types can be passed to the * curl_global_init_mem() function to set user defined memory management * callback routines. */ @@ -404,7 +473,7 @@ typedef void *(*curl_calloc_callback)(size_t nmemb, size_t size); #define CURL_DID_MEMORY_FUNC_TYPEDEFS #endif -/* the kind of data that is passed to information_callback*/ +/* the kind of data that is passed to information_callback */ typedef enum { CURLINFO_TEXT = 0, CURLINFO_HEADER_IN, /* 1 */ @@ -423,6 +492,20 @@ typedef int (*curl_debug_callback) size_t size, /* size of the data pointed to */ void *userptr); /* whatever the user please */ +/* This is the CURLOPT_PREREQFUNCTION callback prototype. */ +typedef int (*curl_prereq_callback)(void *clientp, + char *conn_primary_ip, + char *conn_local_ip, + int conn_primary_port, + int conn_local_port); + +/* Return code for when the pre-request callback has terminated without + any errors */ +#define CURL_PREREQFUNC_OK 0 +/* Return code for when the pre-request callback wants to abort the + request */ +#define CURL_PREREQFUNC_ABORT 1 + /* All possible error codes from all sorts of curl functions. Future versions may return other values, stay prepared. @@ -465,21 +548,17 @@ typedef enum { CURLE_WRITE_ERROR, /* 23 */ CURLE_OBSOLETE24, /* 24 - NOT USED */ CURLE_UPLOAD_FAILED, /* 25 - failed upload "command" */ - CURLE_READ_ERROR, /* 26 - couldn't open/read from file */ + CURLE_READ_ERROR, /* 26 - could not open/read from file */ CURLE_OUT_OF_MEMORY, /* 27 */ - /* Note: CURLE_OUT_OF_MEMORY may sometimes indicate a conversion error - instead of a memory allocation error if CURL_DOES_CONVERSIONS - is defined - */ CURLE_OPERATION_TIMEDOUT, /* 28 - the timeout time was reached */ CURLE_OBSOLETE29, /* 29 - NOT USED */ CURLE_FTP_PORT_FAILED, /* 30 - FTP PORT operation failed */ CURLE_FTP_COULDNT_USE_REST, /* 31 - the REST command failed */ CURLE_OBSOLETE32, /* 32 - NOT USED */ - CURLE_RANGE_ERROR, /* 33 - RANGE "command" didn't work */ + CURLE_RANGE_ERROR, /* 33 - RANGE "command" did not work */ CURLE_HTTP_POST_ERROR, /* 34 */ CURLE_SSL_CONNECT_ERROR, /* 35 - wrong when connecting with SSL */ - CURLE_BAD_DOWNLOAD_RESUME, /* 36 - couldn't resume download */ + CURLE_BAD_DOWNLOAD_RESUME, /* 36 - could not resume download */ CURLE_FILE_COULDNT_READ_FILE, /* 37 */ CURLE_LDAP_CANNOT_BIND, /* 38 */ CURLE_LDAP_SEARCH_FAILED, /* 39 */ @@ -492,10 +571,9 @@ typedef enum { CURLE_OBSOLETE46, /* 46 - NOT USED */ CURLE_TOO_MANY_REDIRECTS, /* 47 - catch endless re-direct loops */ CURLE_UNKNOWN_OPTION, /* 48 - User specified an unknown option */ - CURLE_TELNET_OPTION_SYNTAX, /* 49 - Malformed telnet option */ + CURLE_SETOPT_OPTION_SYNTAX, /* 49 - Malformed setopt option */ CURLE_OBSOLETE50, /* 50 - NOT USED */ - CURLE_PEER_FAILED_VERIFICATION, /* 51 - peer's certificate or fingerprint - wasn't verified fine */ + CURLE_OBSOLETE51, /* 51 - NOT USED */ CURLE_GOT_NOTHING, /* 52 - when this is a specific error */ CURLE_SSL_ENGINE_NOTFOUND, /* 53 - SSL crypto engine not found */ CURLE_SSL_ENGINE_SETFAILED, /* 54 - can not set SSL crypto engine as @@ -504,10 +582,11 @@ typedef enum { CURLE_RECV_ERROR, /* 56 - failure in receiving network data */ CURLE_OBSOLETE57, /* 57 - NOT IN USE */ CURLE_SSL_CERTPROBLEM, /* 58 - problem with the local certificate */ - CURLE_SSL_CIPHER, /* 59 - couldn't use specified cipher */ - CURLE_SSL_CACERT, /* 60 - problem with the CA cert (path?) */ + CURLE_SSL_CIPHER, /* 59 - could not use specified cipher */ + CURLE_PEER_FAILED_VERIFICATION, /* 60 - peer's certificate or fingerprint + was not verified fine */ CURLE_BAD_CONTENT_ENCODING, /* 61 - Unrecognized/bad encoding */ - CURLE_LDAP_INVALID_URL, /* 62 - Invalid LDAP URL */ + CURLE_OBSOLETE62, /* 62 - NOT IN USE since 7.82.0 */ CURLE_FILESIZE_EXCEEDED, /* 63 - Maximum file size exceeded */ CURLE_USE_SSL_FAILED, /* 64 - Requested FTP SSL level failed */ CURLE_SEND_FAIL_REWIND, /* 65 - Sending the data requires a rewind @@ -522,12 +601,8 @@ typedef enum { CURLE_TFTP_UNKNOWNID, /* 72 - Unknown transfer ID */ CURLE_REMOTE_FILE_EXISTS, /* 73 - File already exists */ CURLE_TFTP_NOSUCHUSER, /* 74 - No such user */ - CURLE_CONV_FAILED, /* 75 - conversion failed */ - CURLE_CONV_REQD, /* 76 - caller must register conversion - callbacks using curl_easy_setopt options - CURLOPT_CONV_FROM_NETWORK_FUNCTION, - CURLOPT_CONV_TO_NETWORK_FUNCTION, and - CURLOPT_CONV_FROM_UTF8_FUNCTION */ + CURLE_OBSOLETE75, /* 75 - NOT IN USE since 7.82.0 */ + CURLE_OBSOLETE76, /* 76 - NOT IN USE since 7.82.0 */ CURLE_SSL_CACERT_BADFILE, /* 77 - could not load CACERT file, missing or wrong format */ CURLE_REMOTE_FILE_NOT_FOUND, /* 78 - remote file not found */ @@ -538,7 +613,7 @@ typedef enum { CURLE_SSL_SHUTDOWN_FAILED, /* 80 - Failed to shut down the SSL connection */ CURLE_AGAIN, /* 81 - socket is not ready for send/recv, - wait till it's ready and try again (Added + wait till it is ready and try again (Added in 7.18.2) */ CURLE_SSL_CRL_BADFILE, /* 82 - could not load CRL file, missing or wrong format (Added in 7.19.0) */ @@ -556,16 +631,27 @@ typedef enum { CURLE_SSL_INVALIDCERTSTATUS, /* 91 - invalid certificate status */ CURLE_HTTP2_STREAM, /* 92 - stream error in HTTP/2 framing layer */ + CURLE_RECURSIVE_API_CALL, /* 93 - an api function was called from + inside a callback */ + CURLE_AUTH_ERROR, /* 94 - an authentication function returned an + error */ + CURLE_HTTP3, /* 95 - An HTTP/3 layer problem */ + CURLE_QUIC_CONNECT_ERROR, /* 96 - QUIC connection error */ + CURLE_PROXY, /* 97 - proxy handshake error */ + CURLE_SSL_CLIENTCERT, /* 98 - client-side certificate required */ + CURLE_UNRECOVERABLE_POLL, /* 99 - poll/select returned fatal error */ + CURLE_TOO_LARGE, /* 100 - a value/data met its maximum */ + CURLE_ECH_REQUIRED, /* 101 - ECH tried but failed */ CURL_LAST /* never use! */ } CURLcode; #ifndef CURL_NO_OLDIES /* define this to test if your app builds with all the obsolete stuff removed! */ -/* Previously obsolete error code re-used in 7.38.0 */ +/* Previously obsolete error code reused in 7.38.0 */ #define CURLE_OBSOLETE16 CURLE_HTTP2 -/* Previously obsolete error codes re-used in 7.24.0 */ +/* Previously obsolete error codes reused in 7.24.0 */ #define CURLE_OBSOLETE10 CURLE_FTP_ACCEPT_FAILED #define CURLE_OBSOLETE12 CURLE_FTP_ACCEPT_TIMEOUT @@ -573,9 +659,15 @@ typedef enum { #define CURLOPT_ENCODING CURLOPT_ACCEPT_ENCODING #define CURLE_FTP_WEIRD_SERVER_REPLY CURLE_WEIRD_SERVER_REPLY +/* The following were added in 7.62.0 */ +#define CURLE_SSL_CACERT CURLE_PEER_FAILED_VERIFICATION + /* The following were added in 7.21.5, April 2011 */ #define CURLE_UNKNOWN_TELNET_OPTION CURLE_UNKNOWN_OPTION +/* Added for 7.78.0 */ +#define CURLE_TELNET_OPTION_SYNTAX CURLE_SETOPT_OPTION_SYNTAX + /* The following were added in 7.17.1 */ /* These are scheduled to disappear by 2009 */ #define CURLE_SSL_PEER_CERTIFICATE CURLE_PEER_FAILED_VERIFICATION @@ -607,13 +699,14 @@ typedef enum { /* The following were added earlier */ #define CURLE_OPERATION_TIMEOUTED CURLE_OPERATION_TIMEDOUT - #define CURLE_HTTP_NOT_FOUND CURLE_HTTP_RETURNED_ERROR #define CURLE_HTTP_PORT_FAILED CURLE_INTERFACE_FAILED #define CURLE_FTP_COULDNT_STOR_FILE CURLE_UPLOAD_FAILED - #define CURLE_FTP_PARTIAL_FILE CURLE_PARTIAL_FILE #define CURLE_FTP_BAD_DOWNLOAD_RESUME CURLE_BAD_DOWNLOAD_RESUME +#define CURLE_LDAP_INVALID_URL CURLE_OBSOLETE62 +#define CURLE_CONV_REQD CURLE_OBSOLETE76 +#define CURLE_CONV_FAILED CURLE_OBSOLETE75 /* This was the error code 50 in 7.7.3 and a few earlier versions, this is no longer used by libcurl but is instead #defined here only to not @@ -629,15 +722,61 @@ typedef enum { with them. */ #define CURLOPT_WRITEINFO CURLOPT_OBSOLETE40 #define CURLOPT_CLOSEPOLICY CURLOPT_OBSOLETE72 +#define CURLOPT_OBSOLETE72 9999 +#define CURLOPT_OBSOLETE40 9999 -#endif /*!CURL_NO_OLDIES*/ +#endif /* !CURL_NO_OLDIES */ + +/* + * Proxy error codes. Returned in CURLINFO_PROXY_ERROR if CURLE_PROXY was + * return for the transfers. + */ +typedef enum { + CURLPX_OK, + CURLPX_BAD_ADDRESS_TYPE, + CURLPX_BAD_VERSION, + CURLPX_CLOSED, + CURLPX_GSSAPI, + CURLPX_GSSAPI_PERMSG, + CURLPX_GSSAPI_PROTECTION, + CURLPX_IDENTD, + CURLPX_IDENTD_DIFFER, + CURLPX_LONG_HOSTNAME, + CURLPX_LONG_PASSWD, + CURLPX_LONG_USER, + CURLPX_NO_AUTH, + CURLPX_RECV_ADDRESS, + CURLPX_RECV_AUTH, + CURLPX_RECV_CONNECT, + CURLPX_RECV_REQACK, + CURLPX_REPLY_ADDRESS_TYPE_NOT_SUPPORTED, + CURLPX_REPLY_COMMAND_NOT_SUPPORTED, + CURLPX_REPLY_CONNECTION_REFUSED, + CURLPX_REPLY_GENERAL_SERVER_FAILURE, + CURLPX_REPLY_HOST_UNREACHABLE, + CURLPX_REPLY_NETWORK_UNREACHABLE, + CURLPX_REPLY_NOT_ALLOWED, + CURLPX_REPLY_TTL_EXPIRED, + CURLPX_REPLY_UNASSIGNED, + CURLPX_REQUEST_FAILED, + CURLPX_RESOLVE_HOST, + CURLPX_SEND_AUTH, + CURLPX_SEND_CONNECT, + CURLPX_SEND_REQUEST, + CURLPX_UNKNOWN_FAIL, + CURLPX_UNKNOWN_MODE, + CURLPX_USER_REJECTED, + CURLPX_LAST /* never use */ +} CURLproxycode; /* This prototype applies to all conversion callbacks */ typedef CURLcode (*curl_conv_callback)(char *buffer, size_t length); typedef CURLcode (*curl_ssl_ctx_callback)(CURL *curl, /* easy handle */ - void *ssl_ctx, /* actually an - OpenSSL SSL_CTX */ + void *ssl_ctx, /* actually an OpenSSL + or wolfSSL SSL_CTX, + or an mbedTLS + mbedtls_ssl_config */ void *userptr); typedef enum { @@ -645,13 +784,14 @@ typedef enum { CONNECT HTTP/1.1 */ CURLPROXY_HTTP_1_0 = 1, /* added in 7.19.4, force to use CONNECT HTTP/1.0 */ - CURLPROXY_HTTPS = 2, /* added in 7.52.0 */ + CURLPROXY_HTTPS = 2, /* HTTPS but stick to HTTP/1 added in 7.52.0 */ + CURLPROXY_HTTPS2 = 3, /* HTTPS and attempt HTTP/2 added in 8.2.0 */ CURLPROXY_SOCKS4 = 4, /* support added in 7.15.2, enum existed already in 7.10 */ CURLPROXY_SOCKS5 = 5, /* added in 7.10 */ CURLPROXY_SOCKS4A = 6, /* added in 7.18.0 */ CURLPROXY_SOCKS5_HOSTNAME = 7 /* Use the SOCKS5 protocol but pass along the - host name rather than the IP address. added + hostname rather than the IP address. added in 7.18.0 */ } curl_proxytype; /* this enum was added in 7.10 */ @@ -666,6 +806,7 @@ typedef enum { * CURLAUTH_NTLM - HTTP NTLM authentication * CURLAUTH_DIGEST_IE - HTTP Digest authentication with IE flavour * CURLAUTH_NTLM_WB - HTTP NTLM authentication delegated to winbind helper + * CURLAUTH_BEARER - HTTP Bearer token authentication * CURLAUTH_ONLY - Use together with a single other type to force no * authentication or just that single type * CURLAUTH_ANY - All fine types set @@ -678,9 +819,16 @@ typedef enum { #define CURLAUTH_NEGOTIATE (((unsigned long)1)<<2) /* Deprecated since the advent of CURLAUTH_NEGOTIATE */ #define CURLAUTH_GSSNEGOTIATE CURLAUTH_NEGOTIATE +/* Used for CURLOPT_SOCKS5_AUTH to stay terminologically correct */ +#define CURLAUTH_GSSAPI CURLAUTH_NEGOTIATE #define CURLAUTH_NTLM (((unsigned long)1)<<3) #define CURLAUTH_DIGEST_IE (((unsigned long)1)<<4) +#ifndef CURL_NO_OLDIES + /* functionality removed since 8.8.0 */ #define CURLAUTH_NTLM_WB (((unsigned long)1)<<5) +#endif +#define CURLAUTH_BEARER (((unsigned long)1)<<6) +#define CURLAUTH_AWS_SIGV4 (((unsigned long)1)<<7) #define CURLAUTH_ONLY (((unsigned long)1)<<31) #define CURLAUTH_ANY (~CURLAUTH_DIGEST_IE) #define CURLAUTH_ANYSAFE (~(CURLAUTH_BASIC|CURLAUTH_DIGEST_IE)) @@ -692,6 +840,7 @@ typedef enum { #define CURLSSH_AUTH_HOST (1<<2) /* host key files */ #define CURLSSH_AUTH_KEYBOARD (1<<3) /* keyboard interactive */ #define CURLSSH_AUTH_AGENT (1<<4) /* agent (ssh-agent, pageant...) */ +#define CURLSSH_AUTH_GSSAPI (1<<5) /* gssapi (kerberos, ...) */ #define CURLSSH_AUTH_DEFAULT CURLSSH_AUTH_ANY #define CURLGSSAPI_DELEGATION_NONE 0 /* no delegation (default) */ @@ -704,11 +853,13 @@ enum curl_khtype { CURLKHTYPE_UNKNOWN, CURLKHTYPE_RSA1, CURLKHTYPE_RSA, - CURLKHTYPE_DSS + CURLKHTYPE_DSS, + CURLKHTYPE_ECDSA, + CURLKHTYPE_ED25519 }; struct curl_khkey { - const char *key; /* points to a zero-terminated string encoded with base64 + const char *key; /* points to a null-terminated string encoded with base64 if len is zero, otherwise to the "raw" data */ size_t len; enum curl_khtype keytype; @@ -720,9 +871,10 @@ enum curl_khstat { CURLKHSTAT_FINE_ADD_TO_FILE, CURLKHSTAT_FINE, CURLKHSTAT_REJECT, /* reject the connection, return an error */ - CURLKHSTAT_DEFER, /* do not accept it, but we can't answer right now so - this causes a CURLE_DEFER error but otherwise the + CURLKHSTAT_DEFER, /* do not accept it, but we cannot answer right now. + Causes a CURLE_PEER_FAILED_VERIFICATION error but the connection will be left intact etc */ + CURLKHSTAT_FINE_REPLACE, /* accept and replace the wrong key */ CURLKHSTAT_LAST /* not for use, only a marker for last-in-list */ }; @@ -739,7 +891,18 @@ typedef int const struct curl_khkey *knownkey, /* known */ const struct curl_khkey *foundkey, /* found */ enum curl_khmatch, /* libcurl's view on the keys */ - void *clientp); /* custom pointer passed from app */ + void *clientp); /* custom pointer passed with */ + /* CURLOPT_SSH_KEYDATA */ + +typedef int + (*curl_sshhostkeycallback) (void *clientp,/* custom pointer passed */ + /* with CURLOPT_SSH_HOSTKEYDATA */ + int keytype, /* CURLKHTYPE */ + const char *key, /* hostkey to check */ + size_t keylen); /* length of the key */ + /* return CURLE_OK to accept */ + /* or something else to refuse */ + /* parameter for the CURLOPT_USE_SSL option */ typedef enum { @@ -763,6 +926,31 @@ typedef enum { SSL backends where such behavior is present. */ #define CURLSSLOPT_NO_REVOKE (1<<1) +/* - NO_PARTIALCHAIN tells libcurl to *NOT* accept a partial certificate chain + if possible. The OpenSSL backend has this ability. */ +#define CURLSSLOPT_NO_PARTIALCHAIN (1<<2) + +/* - REVOKE_BEST_EFFORT tells libcurl to ignore certificate revocation offline + checks and ignore missing revocation list for those SSL backends where such + behavior is present. */ +#define CURLSSLOPT_REVOKE_BEST_EFFORT (1<<3) + +/* - CURLSSLOPT_NATIVE_CA tells libcurl to use standard certificate store of + operating system. Currently implemented under MS-Windows. */ +#define CURLSSLOPT_NATIVE_CA (1<<4) + +/* - CURLSSLOPT_AUTO_CLIENT_CERT tells libcurl to automatically locate and use + a client certificate for authentication. (Schannel) */ +#define CURLSSLOPT_AUTO_CLIENT_CERT (1<<5) + +/* The default connection attempt delay in milliseconds for happy eyeballs. + CURLOPT_HAPPY_EYEBALLS_TIMEOUT_MS.3 and happy-eyeballs-timeout-ms.d document + this value, keep them in sync. */ +#define CURL_HET_DEFAULT 200L + +/* The default connection upkeep interval in milliseconds. */ +#define CURL_UPKEEP_INTERVAL_DEFAULT 60000L + #ifndef CURL_NO_OLDIES /* define this to test if your app builds with all the obsolete stuff removed! */ @@ -775,7 +963,7 @@ typedef enum { #define CURLFTPSSL_ALL CURLUSESSL_ALL #define CURLFTPSSL_LAST CURLUSESSL_LAST #define curl_ftpssl curl_usessl -#endif /*!CURL_NO_OLDIES*/ +#endif /* !CURL_NO_OLDIES */ /* parameter for the CURLOPT_FTP_SSL_CCC option */ typedef enum { @@ -817,7 +1005,45 @@ typedef enum { #define CURLHEADER_UNIFIED 0 #define CURLHEADER_SEPARATE (1<<0) -/* CURLPROTO_ defines are for the CURLOPT_*PROTOCOLS options */ +/* CURLALTSVC_* are bits for the CURLOPT_ALTSVC_CTRL option */ +#define CURLALTSVC_READONLYFILE (1<<2) +#define CURLALTSVC_H1 (1<<3) +#define CURLALTSVC_H2 (1<<4) +#define CURLALTSVC_H3 (1<<5) + + +struct curl_hstsentry { + char *name; + size_t namelen; + unsigned int includeSubDomains:1; + char expire[18]; /* YYYYMMDD HH:MM:SS [null-terminated] */ +}; + +struct curl_index { + size_t index; /* the provided entry's "index" or count */ + size_t total; /* total number of entries to save */ +}; + +typedef enum { + CURLSTS_OK, + CURLSTS_DONE, + CURLSTS_FAIL +} CURLSTScode; + +typedef CURLSTScode (*curl_hstsread_callback)(CURL *easy, + struct curl_hstsentry *e, + void *userp); +typedef CURLSTScode (*curl_hstswrite_callback)(CURL *easy, + struct curl_hstsentry *e, + struct curl_index *i, + void *userp); + +/* CURLHSTS_* are bits for the CURLOPT_HSTS option */ +#define CURLHSTS_ENABLE (long)(1<<0) +#define CURLHSTS_READONLYFILE (long)(1<<1) + +/* The CURLPROTO_ defines below are for the **deprecated** CURLOPT_*PROTOCOLS + options. Do not use. */ #define CURLPROTO_HTTP (1<<0) #define CURLPROTO_HTTPS (1<<1) #define CURLPROTO_FTP (1<<2) @@ -846,109 +1072,108 @@ typedef enum { #define CURLPROTO_GOPHER (1<<25) #define CURLPROTO_SMB (1<<26) #define CURLPROTO_SMBS (1<<27) +#define CURLPROTO_MQTT (1<<28) +#define CURLPROTO_GOPHERS (1<<29) #define CURLPROTO_ALL (~0) /* enable everything */ /* long may be 32 or 64 bits, but we should never depend on anything else but 32 */ #define CURLOPTTYPE_LONG 0 #define CURLOPTTYPE_OBJECTPOINT 10000 -#define CURLOPTTYPE_STRINGPOINT 10000 #define CURLOPTTYPE_FUNCTIONPOINT 20000 #define CURLOPTTYPE_OFF_T 30000 +#define CURLOPTTYPE_BLOB 40000 /* *STRINGPOINT is an alias for OBJECTPOINT to allow tools to extract the string options from the header file */ -/* name is uppercase CURLOPT_, - type is one of the defined CURLOPTTYPE_ - number is unique identifier */ -#ifdef CINIT -#undef CINIT -#endif -#ifdef CURL_ISOCPP -#define CINIT(na,t,nu) CURLOPT_ ## na = CURLOPTTYPE_ ## t + nu -#else -/* The macro "##" is ISO C, we assume pre-ISO C doesn't support it. */ -#define LONG CURLOPTTYPE_LONG -#define OBJECTPOINT CURLOPTTYPE_OBJECTPOINT -#define STRINGPOINT CURLOPTTYPE_OBJECTPOINT -#define FUNCTIONPOINT CURLOPTTYPE_FUNCTIONPOINT -#define OFF_T CURLOPTTYPE_OFF_T -#define CINIT(name,type,number) CURLOPT_/**/name = type + number -#endif +#define CURLOPT(na,t,nu) na = t + nu +#define CURLOPTDEPRECATED(na,t,nu,v,m) na CURL_DEPRECATED(v,m) = t + nu + +/* CURLOPT aliases that make no runtime difference */ + +/* 'char *' argument to a string with a trailing zero */ +#define CURLOPTTYPE_STRINGPOINT CURLOPTTYPE_OBJECTPOINT + +/* 'struct curl_slist *' argument */ +#define CURLOPTTYPE_SLISTPOINT CURLOPTTYPE_OBJECTPOINT + +/* 'void *' argument passed untouched to callback */ +#define CURLOPTTYPE_CBPOINT CURLOPTTYPE_OBJECTPOINT + +/* 'long' argument with a set of values/bitmask */ +#define CURLOPTTYPE_VALUES CURLOPTTYPE_LONG /* - * This macro-mania below setups the CURLOPT_[what] enum, to be used with - * curl_easy_setopt(). The first argument in the CINIT() macro is the [what] - * word. + * All CURLOPT_* values. */ typedef enum { /* This is the FILE * or void * the regular output should be written to. */ - CINIT(WRITEDATA, OBJECTPOINT, 1), + CURLOPT(CURLOPT_WRITEDATA, CURLOPTTYPE_CBPOINT, 1), /* The full URL to get/put */ - CINIT(URL, STRINGPOINT, 2), + CURLOPT(CURLOPT_URL, CURLOPTTYPE_STRINGPOINT, 2), /* Port number to connect to, if other than default. */ - CINIT(PORT, LONG, 3), + CURLOPT(CURLOPT_PORT, CURLOPTTYPE_LONG, 3), /* Name of proxy to use. */ - CINIT(PROXY, STRINGPOINT, 4), + CURLOPT(CURLOPT_PROXY, CURLOPTTYPE_STRINGPOINT, 4), /* "user:password;options" to use when fetching. */ - CINIT(USERPWD, STRINGPOINT, 5), + CURLOPT(CURLOPT_USERPWD, CURLOPTTYPE_STRINGPOINT, 5), /* "user:password" to use with proxy. */ - CINIT(PROXYUSERPWD, STRINGPOINT, 6), + CURLOPT(CURLOPT_PROXYUSERPWD, CURLOPTTYPE_STRINGPOINT, 6), /* Range to get, specified as an ASCII string. */ - CINIT(RANGE, STRINGPOINT, 7), + CURLOPT(CURLOPT_RANGE, CURLOPTTYPE_STRINGPOINT, 7), /* not used */ /* Specified file stream to upload from (use as input): */ - CINIT(READDATA, OBJECTPOINT, 9), + CURLOPT(CURLOPT_READDATA, CURLOPTTYPE_CBPOINT, 9), /* Buffer to receive error messages in, must be at least CURL_ERROR_SIZE - * bytes big. If this is not used, error messages go to stderr instead: */ - CINIT(ERRORBUFFER, OBJECTPOINT, 10), + * bytes big. */ + CURLOPT(CURLOPT_ERRORBUFFER, CURLOPTTYPE_OBJECTPOINT, 10), /* Function that will be called to store the output (instead of fwrite). The * parameters will use fwrite() syntax, make sure to follow them. */ - CINIT(WRITEFUNCTION, FUNCTIONPOINT, 11), + CURLOPT(CURLOPT_WRITEFUNCTION, CURLOPTTYPE_FUNCTIONPOINT, 11), /* Function that will be called to read the input (instead of fread). The * parameters will use fread() syntax, make sure to follow them. */ - CINIT(READFUNCTION, FUNCTIONPOINT, 12), + CURLOPT(CURLOPT_READFUNCTION, CURLOPTTYPE_FUNCTIONPOINT, 12), /* Time-out the read operation after this amount of seconds */ - CINIT(TIMEOUT, LONG, 13), + CURLOPT(CURLOPT_TIMEOUT, CURLOPTTYPE_LONG, 13), - /* If the CURLOPT_INFILE is used, this can be used to inform libcurl about + /* If CURLOPT_READDATA is used, this can be used to inform libcurl about * how large the file being sent really is. That allows better error * checking and better verifies that the upload was successful. -1 means * unknown size. * * For large file support, there is also a _LARGE version of the key * which takes an off_t type, allowing platforms with larger off_t - * sizes to handle larger files. See below for INFILESIZE_LARGE. + * sizes to handle larger files. See below for INFILESIZE_LARGE. */ - CINIT(INFILESIZE, LONG, 14), + CURLOPT(CURLOPT_INFILESIZE, CURLOPTTYPE_LONG, 14), /* POST static input fields. */ - CINIT(POSTFIELDS, OBJECTPOINT, 15), + CURLOPT(CURLOPT_POSTFIELDS, CURLOPTTYPE_OBJECTPOINT, 15), /* Set the referrer page (needed by some CGIs) */ - CINIT(REFERER, STRINGPOINT, 16), + CURLOPT(CURLOPT_REFERER, CURLOPTTYPE_STRINGPOINT, 16), /* Set the FTP PORT string (interface name, named or numerical IP address) Use i.e '-' to use default address. */ - CINIT(FTPPORT, STRINGPOINT, 17), + CURLOPT(CURLOPT_FTPPORT, CURLOPTTYPE_STRINGPOINT, 17), /* Set the User-Agent string (examined by some CGIs) */ - CINIT(USERAGENT, STRINGPOINT, 18), + CURLOPT(CURLOPT_USERAGENT, CURLOPTTYPE_STRINGPOINT, 18), /* If the download receives less than "low speed limit" bytes/second * during "low speed time" seconds, the operations is aborted. @@ -957,59 +1182,60 @@ typedef enum { */ /* Set the "low speed limit" */ - CINIT(LOW_SPEED_LIMIT, LONG, 19), + CURLOPT(CURLOPT_LOW_SPEED_LIMIT, CURLOPTTYPE_LONG, 19), /* Set the "low speed time" */ - CINIT(LOW_SPEED_TIME, LONG, 20), + CURLOPT(CURLOPT_LOW_SPEED_TIME, CURLOPTTYPE_LONG, 20), /* Set the continuation offset. * * Note there is also a _LARGE version of this key which uses * off_t types, allowing for large file offsets on platforms which - * use larger-than-32-bit off_t's. Look below for RESUME_FROM_LARGE. + * use larger-than-32-bit off_t's. Look below for RESUME_FROM_LARGE. */ - CINIT(RESUME_FROM, LONG, 21), + CURLOPT(CURLOPT_RESUME_FROM, CURLOPTTYPE_LONG, 21), /* Set cookie in request: */ - CINIT(COOKIE, STRINGPOINT, 22), + CURLOPT(CURLOPT_COOKIE, CURLOPTTYPE_STRINGPOINT, 22), /* This points to a linked list of headers, struct curl_slist kind. This list is also used for RTSP (in spite of its name) */ - CINIT(HTTPHEADER, OBJECTPOINT, 23), + CURLOPT(CURLOPT_HTTPHEADER, CURLOPTTYPE_SLISTPOINT, 23), /* This points to a linked list of post entries, struct curl_httppost */ - CINIT(HTTPPOST, OBJECTPOINT, 24), + CURLOPTDEPRECATED(CURLOPT_HTTPPOST, CURLOPTTYPE_OBJECTPOINT, 24, + 7.56.0, "Use CURLOPT_MIMEPOST"), /* name of the file keeping your private SSL-certificate */ - CINIT(SSLCERT, STRINGPOINT, 25), + CURLOPT(CURLOPT_SSLCERT, CURLOPTTYPE_STRINGPOINT, 25), /* password for the SSL or SSH private key */ - CINIT(KEYPASSWD, STRINGPOINT, 26), + CURLOPT(CURLOPT_KEYPASSWD, CURLOPTTYPE_STRINGPOINT, 26), /* send TYPE parameter? */ - CINIT(CRLF, LONG, 27), + CURLOPT(CURLOPT_CRLF, CURLOPTTYPE_LONG, 27), /* send linked-list of QUOTE commands */ - CINIT(QUOTE, OBJECTPOINT, 28), + CURLOPT(CURLOPT_QUOTE, CURLOPTTYPE_SLISTPOINT, 28), /* send FILE * or void * to store headers to, if you use a callback it is simply passed to the callback unmodified */ - CINIT(HEADERDATA, OBJECTPOINT, 29), + CURLOPT(CURLOPT_HEADERDATA, CURLOPTTYPE_CBPOINT, 29), /* point to a file to read the initial cookies from, also enables "cookie awareness" */ - CINIT(COOKIEFILE, STRINGPOINT, 31), + CURLOPT(CURLOPT_COOKIEFILE, CURLOPTTYPE_STRINGPOINT, 31), /* What version to specifically try to use. See CURL_SSLVERSION defines below. */ - CINIT(SSLVERSION, LONG, 32), + CURLOPT(CURLOPT_SSLVERSION, CURLOPTTYPE_VALUES, 32), /* What kind of HTTP time condition to use, see defines */ - CINIT(TIMECONDITION, LONG, 33), + CURLOPT(CURLOPT_TIMECONDITION, CURLOPTTYPE_VALUES, 33), /* Time to use with the above condition. Specified in number of seconds since 1 Jan 1970 */ - CINIT(TIMEVALUE, LONG, 34), + CURLOPT(CURLOPT_TIMEVALUE, CURLOPTTYPE_LONG, 34), /* 35 = OBSOLETE */ @@ -1017,37 +1243,58 @@ typedef enum { HTTP: DELETE, TRACE and others FTP: to use a different list command */ - CINIT(CUSTOMREQUEST, STRINGPOINT, 36), + CURLOPT(CURLOPT_CUSTOMREQUEST, CURLOPTTYPE_STRINGPOINT, 36), /* FILE handle to use instead of stderr */ - CINIT(STDERR, OBJECTPOINT, 37), + CURLOPT(CURLOPT_STDERR, CURLOPTTYPE_OBJECTPOINT, 37), /* 38 is not used */ /* send linked-list of post-transfer QUOTE commands */ - CINIT(POSTQUOTE, OBJECTPOINT, 39), + CURLOPT(CURLOPT_POSTQUOTE, CURLOPTTYPE_SLISTPOINT, 39), + + /* 40 is not used */ + + /* talk a lot */ + CURLOPT(CURLOPT_VERBOSE, CURLOPTTYPE_LONG, 41), + + /* throw the header out too */ + CURLOPT(CURLOPT_HEADER, CURLOPTTYPE_LONG, 42), + + /* shut off the progress meter */ + CURLOPT(CURLOPT_NOPROGRESS, CURLOPTTYPE_LONG, 43), + + /* use HEAD to get http document */ + CURLOPT(CURLOPT_NOBODY, CURLOPTTYPE_LONG, 44), + + /* no output on http error codes >= 400 */ + CURLOPT(CURLOPT_FAILONERROR, CURLOPTTYPE_LONG, 45), - CINIT(OBSOLETE40, OBJECTPOINT, 40), /* OBSOLETE, do not use! */ + /* this is an upload */ + CURLOPT(CURLOPT_UPLOAD, CURLOPTTYPE_LONG, 46), - CINIT(VERBOSE, LONG, 41), /* talk a lot */ - CINIT(HEADER, LONG, 42), /* throw the header out too */ - CINIT(NOPROGRESS, LONG, 43), /* shut off the progress meter */ - CINIT(NOBODY, LONG, 44), /* use HEAD to get http document */ - CINIT(FAILONERROR, LONG, 45), /* no output on http error codes >= 400 */ - CINIT(UPLOAD, LONG, 46), /* this is an upload */ - CINIT(POST, LONG, 47), /* HTTP POST method */ - CINIT(DIRLISTONLY, LONG, 48), /* bare names when listing directories */ + /* HTTP POST method */ + CURLOPT(CURLOPT_POST, CURLOPTTYPE_LONG, 47), - CINIT(APPEND, LONG, 50), /* Append instead of overwrite on upload! */ + /* bare names when listing directories */ + CURLOPT(CURLOPT_DIRLISTONLY, CURLOPTTYPE_LONG, 48), + + /* Append instead of overwrite on upload! */ + CURLOPT(CURLOPT_APPEND, CURLOPTTYPE_LONG, 50), /* Specify whether to read the user+password from the .netrc or the URL. * This must be one of the CURL_NETRC_* enums below. */ - CINIT(NETRC, LONG, 51), + CURLOPT(CURLOPT_NETRC, CURLOPTTYPE_VALUES, 51), + + /* use Location: Luke! */ + CURLOPT(CURLOPT_FOLLOWLOCATION, CURLOPTTYPE_LONG, 52), - CINIT(FOLLOWLOCATION, LONG, 52), /* use Location: Luke! */ + /* transfer data in text/ASCII format */ + CURLOPT(CURLOPT_TRANSFERTEXT, CURLOPTTYPE_LONG, 53), - CINIT(TRANSFERTEXT, LONG, 53), /* transfer data in text/ASCII format */ - CINIT(PUT, LONG, 54), /* HTTP PUT */ + /* HTTP PUT */ + CURLOPTDEPRECATED(CURLOPT_PUT, CURLOPTTYPE_LONG, 54, + 7.12.1, "Use CURLOPT_UPLOAD"), /* 55 = OBSOLETE */ @@ -1055,265 +1302,268 @@ typedef enum { * Function that will be called instead of the internal progress display * function. This function should be defined as the curl_progress_callback * prototype defines. */ - CINIT(PROGRESSFUNCTION, FUNCTIONPOINT, 56), + CURLOPTDEPRECATED(CURLOPT_PROGRESSFUNCTION, CURLOPTTYPE_FUNCTIONPOINT, 56, + 7.32.0, "Use CURLOPT_XFERINFOFUNCTION"), /* Data passed to the CURLOPT_PROGRESSFUNCTION and CURLOPT_XFERINFOFUNCTION callbacks */ - CINIT(PROGRESSDATA, OBJECTPOINT, 57), -#define CURLOPT_XFERINFODATA CURLOPT_PROGRESSDATA + CURLOPT(CURLOPT_XFERINFODATA, CURLOPTTYPE_CBPOINT, 57), +#define CURLOPT_PROGRESSDATA CURLOPT_XFERINFODATA /* We want the referrer field set automatically when following locations */ - CINIT(AUTOREFERER, LONG, 58), + CURLOPT(CURLOPT_AUTOREFERER, CURLOPTTYPE_LONG, 58), /* Port of the proxy, can be set in the proxy string as well with: "[host]:[port]" */ - CINIT(PROXYPORT, LONG, 59), + CURLOPT(CURLOPT_PROXYPORT, CURLOPTTYPE_LONG, 59), /* size of the POST input data, if strlen() is not good to use */ - CINIT(POSTFIELDSIZE, LONG, 60), + CURLOPT(CURLOPT_POSTFIELDSIZE, CURLOPTTYPE_LONG, 60), - /* tunnel non-http operations through a HTTP proxy */ - CINIT(HTTPPROXYTUNNEL, LONG, 61), + /* tunnel non-http operations through an HTTP proxy */ + CURLOPT(CURLOPT_HTTPPROXYTUNNEL, CURLOPTTYPE_LONG, 61), /* Set the interface string to use as outgoing network interface */ - CINIT(INTERFACE, STRINGPOINT, 62), + CURLOPT(CURLOPT_INTERFACE, CURLOPTTYPE_STRINGPOINT, 62), - /* Set the krb4/5 security level, this also enables krb4/5 awareness. This - * is a string, 'clear', 'safe', 'confidential' or 'private'. If the string - * is set but doesn't match one of these, 'private' will be used. */ - CINIT(KRBLEVEL, STRINGPOINT, 63), + /* Set the krb4/5 security level, this also enables krb4/5 awareness. This + * is a string, 'clear', 'safe', 'confidential' or 'private'. If the string + * is set but does not match one of these, 'private' will be used. */ + CURLOPT(CURLOPT_KRBLEVEL, CURLOPTTYPE_STRINGPOINT, 63), /* Set if we should verify the peer in ssl handshake, set 1 to verify. */ - CINIT(SSL_VERIFYPEER, LONG, 64), + CURLOPT(CURLOPT_SSL_VERIFYPEER, CURLOPTTYPE_LONG, 64), /* The CApath or CAfile used to validate the peer certificate this option is used only if SSL_VERIFYPEER is true */ - CINIT(CAINFO, STRINGPOINT, 65), + CURLOPT(CURLOPT_CAINFO, CURLOPTTYPE_STRINGPOINT, 65), /* 66 = OBSOLETE */ /* 67 = OBSOLETE */ /* Maximum number of http redirects to follow */ - CINIT(MAXREDIRS, LONG, 68), + CURLOPT(CURLOPT_MAXREDIRS, CURLOPTTYPE_LONG, 68), /* Pass a long set to 1 to get the date of the requested document (if possible)! Pass a zero to shut it off. */ - CINIT(FILETIME, LONG, 69), + CURLOPT(CURLOPT_FILETIME, CURLOPTTYPE_LONG, 69), /* This points to a linked list of telnet options */ - CINIT(TELNETOPTIONS, OBJECTPOINT, 70), + CURLOPT(CURLOPT_TELNETOPTIONS, CURLOPTTYPE_SLISTPOINT, 70), /* Max amount of cached alive connections */ - CINIT(MAXCONNECTS, LONG, 71), - - CINIT(OBSOLETE72, LONG, 72), /* OBSOLETE, do not use! */ + CURLOPT(CURLOPT_MAXCONNECTS, CURLOPTTYPE_LONG, 71), + /* 72 = OBSOLETE */ /* 73 = OBSOLETE */ /* Set to explicitly use a new connection for the upcoming transfer. - Do not use this unless you're absolutely sure of this, as it makes the + Do not use this unless you are absolutely sure of this, as it makes the operation slower and is less friendly for the network. */ - CINIT(FRESH_CONNECT, LONG, 74), + CURLOPT(CURLOPT_FRESH_CONNECT, CURLOPTTYPE_LONG, 74), - /* Set to explicitly forbid the upcoming transfer's connection to be re-used - when done. Do not use this unless you're absolutely sure of this, as it + /* Set to explicitly forbid the upcoming transfer's connection to be reused + when done. Do not use this unless you are absolutely sure of this, as it makes the operation slower and is less friendly for the network. */ - CINIT(FORBID_REUSE, LONG, 75), + CURLOPT(CURLOPT_FORBID_REUSE, CURLOPTTYPE_LONG, 75), - /* Set to a file name that contains random data for libcurl to use to + /* Set to a filename that contains random data for libcurl to use to seed the random engine when doing SSL connects. */ - CINIT(RANDOM_FILE, STRINGPOINT, 76), + CURLOPTDEPRECATED(CURLOPT_RANDOM_FILE, CURLOPTTYPE_STRINGPOINT, 76, + 7.84.0, "Serves no purpose anymore"), /* Set to the Entropy Gathering Daemon socket pathname */ - CINIT(EGDSOCKET, STRINGPOINT, 77), + CURLOPTDEPRECATED(CURLOPT_EGDSOCKET, CURLOPTTYPE_STRINGPOINT, 77, + 7.84.0, "Serves no purpose anymore"), /* Time-out connect operations after this amount of seconds, if connects are OK within this time, then fine... This only aborts the connect phase. */ - CINIT(CONNECTTIMEOUT, LONG, 78), + CURLOPT(CURLOPT_CONNECTTIMEOUT, CURLOPTTYPE_LONG, 78), /* Function that will be called to store headers (instead of fwrite). The * parameters will use fwrite() syntax, make sure to follow them. */ - CINIT(HEADERFUNCTION, FUNCTIONPOINT, 79), + CURLOPT(CURLOPT_HEADERFUNCTION, CURLOPTTYPE_FUNCTIONPOINT, 79), /* Set this to force the HTTP request to get back to GET. Only really usable if POST, PUT or a custom request have been used first. */ - CINIT(HTTPGET, LONG, 80), + CURLOPT(CURLOPT_HTTPGET, CURLOPTTYPE_LONG, 80), /* Set if we should verify the Common name from the peer certificate in ssl * handshake, set 1 to check existence, 2 to ensure that it matches the * provided hostname. */ - CINIT(SSL_VERIFYHOST, LONG, 81), + CURLOPT(CURLOPT_SSL_VERIFYHOST, CURLOPTTYPE_LONG, 81), - /* Specify which file name to write all known cookies in after completed - operation. Set file name to "-" (dash) to make it go to stdout. */ - CINIT(COOKIEJAR, STRINGPOINT, 82), + /* Specify which filename to write all known cookies in after completed + operation. Set filename to "-" (dash) to make it go to stdout. */ + CURLOPT(CURLOPT_COOKIEJAR, CURLOPTTYPE_STRINGPOINT, 82), - /* Specify which SSL ciphers to use */ - CINIT(SSL_CIPHER_LIST, STRINGPOINT, 83), + /* Specify which TLS 1.2 (1.1, 1.0) ciphers to use */ + CURLOPT(CURLOPT_SSL_CIPHER_LIST, CURLOPTTYPE_STRINGPOINT, 83), /* Specify which HTTP version to use! This must be set to one of the CURL_HTTP_VERSION* enums set below. */ - CINIT(HTTP_VERSION, LONG, 84), + CURLOPT(CURLOPT_HTTP_VERSION, CURLOPTTYPE_VALUES, 84), /* Specifically switch on or off the FTP engine's use of the EPSV command. By default, that one will always be attempted before the more traditional PASV command. */ - CINIT(FTP_USE_EPSV, LONG, 85), + CURLOPT(CURLOPT_FTP_USE_EPSV, CURLOPTTYPE_LONG, 85), /* type of the file keeping your SSL-certificate ("DER", "PEM", "ENG") */ - CINIT(SSLCERTTYPE, STRINGPOINT, 86), + CURLOPT(CURLOPT_SSLCERTTYPE, CURLOPTTYPE_STRINGPOINT, 86), /* name of the file keeping your private SSL-key */ - CINIT(SSLKEY, STRINGPOINT, 87), + CURLOPT(CURLOPT_SSLKEY, CURLOPTTYPE_STRINGPOINT, 87), /* type of the file keeping your private SSL-key ("DER", "PEM", "ENG") */ - CINIT(SSLKEYTYPE, STRINGPOINT, 88), + CURLOPT(CURLOPT_SSLKEYTYPE, CURLOPTTYPE_STRINGPOINT, 88), /* crypto engine for the SSL-sub system */ - CINIT(SSLENGINE, STRINGPOINT, 89), + CURLOPT(CURLOPT_SSLENGINE, CURLOPTTYPE_STRINGPOINT, 89), /* set the crypto engine for the SSL-sub system as default the param has no meaning... */ - CINIT(SSLENGINE_DEFAULT, LONG, 90), + CURLOPT(CURLOPT_SSLENGINE_DEFAULT, CURLOPTTYPE_LONG, 90), /* Non-zero value means to use the global dns cache */ - CINIT(DNS_USE_GLOBAL_CACHE, LONG, 91), /* DEPRECATED, do not use! */ + /* DEPRECATED, do not use! */ + CURLOPTDEPRECATED(CURLOPT_DNS_USE_GLOBAL_CACHE, CURLOPTTYPE_LONG, 91, + 7.11.1, "Use CURLOPT_SHARE"), /* DNS cache timeout */ - CINIT(DNS_CACHE_TIMEOUT, LONG, 92), + CURLOPT(CURLOPT_DNS_CACHE_TIMEOUT, CURLOPTTYPE_LONG, 92), /* send linked-list of pre-transfer QUOTE commands */ - CINIT(PREQUOTE, OBJECTPOINT, 93), + CURLOPT(CURLOPT_PREQUOTE, CURLOPTTYPE_SLISTPOINT, 93), /* set the debug function */ - CINIT(DEBUGFUNCTION, FUNCTIONPOINT, 94), + CURLOPT(CURLOPT_DEBUGFUNCTION, CURLOPTTYPE_FUNCTIONPOINT, 94), /* set the data for the debug function */ - CINIT(DEBUGDATA, OBJECTPOINT, 95), + CURLOPT(CURLOPT_DEBUGDATA, CURLOPTTYPE_CBPOINT, 95), /* mark this as start of a cookie session */ - CINIT(COOKIESESSION, LONG, 96), + CURLOPT(CURLOPT_COOKIESESSION, CURLOPTTYPE_LONG, 96), /* The CApath directory used to validate the peer certificate this option is used only if SSL_VERIFYPEER is true */ - CINIT(CAPATH, STRINGPOINT, 97), + CURLOPT(CURLOPT_CAPATH, CURLOPTTYPE_STRINGPOINT, 97), /* Instruct libcurl to use a smaller receive buffer */ - CINIT(BUFFERSIZE, LONG, 98), + CURLOPT(CURLOPT_BUFFERSIZE, CURLOPTTYPE_LONG, 98), /* Instruct libcurl to not use any signal/alarm handlers, even when using timeouts. This option is useful for multi-threaded applications. See libcurl-the-guide for more background information. */ - CINIT(NOSIGNAL, LONG, 99), + CURLOPT(CURLOPT_NOSIGNAL, CURLOPTTYPE_LONG, 99), /* Provide a CURLShare for mutexing non-ts data */ - CINIT(SHARE, OBJECTPOINT, 100), + CURLOPT(CURLOPT_SHARE, CURLOPTTYPE_OBJECTPOINT, 100), /* indicates type of proxy. accepted values are CURLPROXY_HTTP (default), CURLPROXY_HTTPS, CURLPROXY_SOCKS4, CURLPROXY_SOCKS4A and CURLPROXY_SOCKS5. */ - CINIT(PROXYTYPE, LONG, 101), + CURLOPT(CURLOPT_PROXYTYPE, CURLOPTTYPE_VALUES, 101), /* Set the Accept-Encoding string. Use this to tell a server you would like the response to be compressed. Before 7.21.6, this was known as CURLOPT_ENCODING */ - CINIT(ACCEPT_ENCODING, STRINGPOINT, 102), + CURLOPT(CURLOPT_ACCEPT_ENCODING, CURLOPTTYPE_STRINGPOINT, 102), /* Set pointer to private data */ - CINIT(PRIVATE, OBJECTPOINT, 103), + CURLOPT(CURLOPT_PRIVATE, CURLOPTTYPE_OBJECTPOINT, 103), /* Set aliases for HTTP 200 in the HTTP Response header */ - CINIT(HTTP200ALIASES, OBJECTPOINT, 104), + CURLOPT(CURLOPT_HTTP200ALIASES, CURLOPTTYPE_SLISTPOINT, 104), /* Continue to send authentication (user+password) when following locations, even when hostname changed. This can potentially send off the name and password to whatever host the server decides. */ - CINIT(UNRESTRICTED_AUTH, LONG, 105), + CURLOPT(CURLOPT_UNRESTRICTED_AUTH, CURLOPTTYPE_LONG, 105), /* Specifically switch on or off the FTP engine's use of the EPRT command ( it also disables the LPRT attempt). By default, those ones will always be attempted before the good old traditional PORT command. */ - CINIT(FTP_USE_EPRT, LONG, 106), + CURLOPT(CURLOPT_FTP_USE_EPRT, CURLOPTTYPE_LONG, 106), /* Set this to a bitmask value to enable the particular authentications methods you like. Use this in combination with CURLOPT_USERPWD. Note that setting multiple bits may cause extra network round-trips. */ - CINIT(HTTPAUTH, LONG, 107), + CURLOPT(CURLOPT_HTTPAUTH, CURLOPTTYPE_VALUES, 107), - /* Set the ssl context callback function, currently only for OpenSSL ssl_ctx - in second argument. The function must be matching the - curl_ssl_ctx_callback proto. */ - CINIT(SSL_CTX_FUNCTION, FUNCTIONPOINT, 108), + /* Set the ssl context callback function, currently only for OpenSSL or + wolfSSL ssl_ctx, or mbedTLS mbedtls_ssl_config in the second argument. + The function must match the curl_ssl_ctx_callback prototype. */ + CURLOPT(CURLOPT_SSL_CTX_FUNCTION, CURLOPTTYPE_FUNCTIONPOINT, 108), /* Set the userdata for the ssl context callback function's third argument */ - CINIT(SSL_CTX_DATA, OBJECTPOINT, 109), + CURLOPT(CURLOPT_SSL_CTX_DATA, CURLOPTTYPE_CBPOINT, 109), /* FTP Option that causes missing dirs to be created on the remote server. In 7.19.4 we introduced the convenience enums for this option using the CURLFTP_CREATE_DIR prefix. */ - CINIT(FTP_CREATE_MISSING_DIRS, LONG, 110), + CURLOPT(CURLOPT_FTP_CREATE_MISSING_DIRS, CURLOPTTYPE_LONG, 110), /* Set this to a bitmask value to enable the particular authentications methods you like. Use this in combination with CURLOPT_PROXYUSERPWD. Note that setting multiple bits may cause extra network round-trips. */ - CINIT(PROXYAUTH, LONG, 111), + CURLOPT(CURLOPT_PROXYAUTH, CURLOPTTYPE_VALUES, 111), - /* FTP option that changes the timeout, in seconds, associated with - getting a response. This is different from transfer timeout time and - essentially places a demand on the FTP server to acknowledge commands - in a timely manner. */ - CINIT(FTP_RESPONSE_TIMEOUT, LONG, 112), -#define CURLOPT_SERVER_RESPONSE_TIMEOUT CURLOPT_FTP_RESPONSE_TIMEOUT + /* Option that changes the timeout, in seconds, associated with getting a + response. This is different from transfer timeout time and essentially + places a demand on the server to acknowledge commands in a timely + manner. For FTP, SMTP, IMAP and POP3. */ + CURLOPT(CURLOPT_SERVER_RESPONSE_TIMEOUT, CURLOPTTYPE_LONG, 112), /* Set this option to one of the CURL_IPRESOLVE_* defines (see below) to - tell libcurl to resolve names to those IP versions only. This only has - affect on systems with support for more than one, i.e IPv4 _and_ IPv6. */ - CINIT(IPRESOLVE, LONG, 113), + tell libcurl to use those IP versions only. This only has effect on + systems with support for more than one, i.e IPv4 _and_ IPv6. */ + CURLOPT(CURLOPT_IPRESOLVE, CURLOPTTYPE_VALUES, 113), /* Set this option to limit the size of a file that will be downloaded from an HTTP or FTP server. Note there is also _LARGE version which adds large file support for - platforms which have larger off_t sizes. See MAXFILESIZE_LARGE below. */ - CINIT(MAXFILESIZE, LONG, 114), + platforms which have larger off_t sizes. See MAXFILESIZE_LARGE below. */ + CURLOPT(CURLOPT_MAXFILESIZE, CURLOPTTYPE_LONG, 114), /* See the comment for INFILESIZE above, but in short, specifies * the size of the file being uploaded. -1 means unknown. */ - CINIT(INFILESIZE_LARGE, OFF_T, 115), + CURLOPT(CURLOPT_INFILESIZE_LARGE, CURLOPTTYPE_OFF_T, 115), - /* Sets the continuation offset. There is also a LONG version of this; - * look above for RESUME_FROM. + /* Sets the continuation offset. There is also a CURLOPTTYPE_LONG version + * of this; look above for RESUME_FROM. */ - CINIT(RESUME_FROM_LARGE, OFF_T, 116), + CURLOPT(CURLOPT_RESUME_FROM_LARGE, CURLOPTTYPE_OFF_T, 116), /* Sets the maximum size of data that will be downloaded from - * an HTTP or FTP server. See MAXFILESIZE above for the LONG version. + * an HTTP or FTP server. See MAXFILESIZE above for the LONG version. */ - CINIT(MAXFILESIZE_LARGE, OFF_T, 117), + CURLOPT(CURLOPT_MAXFILESIZE_LARGE, CURLOPTTYPE_OFF_T, 117), - /* Set this option to the file name of your .netrc file you want libcurl + /* Set this option to the filename of your .netrc file you want libcurl to parse (using the CURLOPT_NETRC option). If not set, libcurl will do a poor attempt to find the user's home directory and check for a .netrc file in there. */ - CINIT(NETRC_FILE, STRINGPOINT, 118), + CURLOPT(CURLOPT_NETRC_FILE, CURLOPTTYPE_STRINGPOINT, 118), /* Enable SSL/TLS for FTP, pick one of: CURLUSESSL_TRY - try using SSL, proceed anyway otherwise CURLUSESSL_CONTROL - SSL for the control connection or fail CURLUSESSL_ALL - SSL for all communication or fail */ - CINIT(USE_SSL, LONG, 119), + CURLOPT(CURLOPT_USE_SSL, CURLOPTTYPE_VALUES, 119), /* The _LARGE version of the standard POSTFIELDSIZE option */ - CINIT(POSTFIELDSIZE_LARGE, OFF_T, 120), + CURLOPT(CURLOPT_POSTFIELDSIZE_LARGE, CURLOPTTYPE_OFF_T, 120), /* Enable/disable the TCP Nagle algorithm */ - CINIT(TCP_NODELAY, LONG, 121), + CURLOPT(CURLOPT_TCP_NODELAY, CURLOPTTYPE_LONG, 121), /* 122 OBSOLETE, used in 7.12.3. Gone in 7.13.0 */ /* 123 OBSOLETE. Gone in 7.16.0 */ @@ -1333,143 +1583,151 @@ typedef enum { CURLFTPAUTH_SSL - try "AUTH SSL" first, then TLS CURLFTPAUTH_TLS - try "AUTH TLS" first, then SSL */ - CINIT(FTPSSLAUTH, LONG, 129), + CURLOPT(CURLOPT_FTPSSLAUTH, CURLOPTTYPE_VALUES, 129), - CINIT(IOCTLFUNCTION, FUNCTIONPOINT, 130), - CINIT(IOCTLDATA, OBJECTPOINT, 131), + CURLOPTDEPRECATED(CURLOPT_IOCTLFUNCTION, CURLOPTTYPE_FUNCTIONPOINT, 130, + 7.18.0, "Use CURLOPT_SEEKFUNCTION"), + CURLOPTDEPRECATED(CURLOPT_IOCTLDATA, CURLOPTTYPE_CBPOINT, 131, + 7.18.0, "Use CURLOPT_SEEKDATA"), /* 132 OBSOLETE. Gone in 7.16.0 */ /* 133 OBSOLETE. Gone in 7.16.0 */ - /* zero terminated string for pass on to the FTP server when asked for + /* null-terminated string for pass on to the FTP server when asked for "account" info */ - CINIT(FTP_ACCOUNT, STRINGPOINT, 134), + CURLOPT(CURLOPT_FTP_ACCOUNT, CURLOPTTYPE_STRINGPOINT, 134), /* feed cookie into cookie engine */ - CINIT(COOKIELIST, STRINGPOINT, 135), + CURLOPT(CURLOPT_COOKIELIST, CURLOPTTYPE_STRINGPOINT, 135), /* ignore Content-Length */ - CINIT(IGNORE_CONTENT_LENGTH, LONG, 136), + CURLOPT(CURLOPT_IGNORE_CONTENT_LENGTH, CURLOPTTYPE_LONG, 136), /* Set to non-zero to skip the IP address received in a 227 PASV FTP server response. Typically used for FTP-SSL purposes but is not restricted to that. libcurl will then instead use the same IP address it used for the control connection. */ - CINIT(FTP_SKIP_PASV_IP, LONG, 137), + CURLOPT(CURLOPT_FTP_SKIP_PASV_IP, CURLOPTTYPE_LONG, 137), /* Select "file method" to use when doing FTP, see the curl_ftpmethod above. */ - CINIT(FTP_FILEMETHOD, LONG, 138), + CURLOPT(CURLOPT_FTP_FILEMETHOD, CURLOPTTYPE_VALUES, 138), /* Local port number to bind the socket to */ - CINIT(LOCALPORT, LONG, 139), + CURLOPT(CURLOPT_LOCALPORT, CURLOPTTYPE_LONG, 139), /* Number of ports to try, including the first one set with LOCALPORT. Thus, setting it to 1 will make no additional attempts but the first. */ - CINIT(LOCALPORTRANGE, LONG, 140), + CURLOPT(CURLOPT_LOCALPORTRANGE, CURLOPTTYPE_LONG, 140), /* no transfer, set up connection and let application use the socket by extracting it with CURLINFO_LASTSOCKET */ - CINIT(CONNECT_ONLY, LONG, 141), + CURLOPT(CURLOPT_CONNECT_ONLY, CURLOPTTYPE_LONG, 141), /* Function that will be called to convert from the network encoding (instead of using the iconv calls in libcurl) */ - CINIT(CONV_FROM_NETWORK_FUNCTION, FUNCTIONPOINT, 142), + CURLOPTDEPRECATED(CURLOPT_CONV_FROM_NETWORK_FUNCTION, + CURLOPTTYPE_FUNCTIONPOINT, 142, + 7.82.0, "Serves no purpose anymore"), /* Function that will be called to convert to the network encoding (instead of using the iconv calls in libcurl) */ - CINIT(CONV_TO_NETWORK_FUNCTION, FUNCTIONPOINT, 143), + CURLOPTDEPRECATED(CURLOPT_CONV_TO_NETWORK_FUNCTION, + CURLOPTTYPE_FUNCTIONPOINT, 143, + 7.82.0, "Serves no purpose anymore"), /* Function that will be called to convert from UTF8 (instead of using the iconv calls in libcurl) Note that this is used only for SSL certificate processing */ - CINIT(CONV_FROM_UTF8_FUNCTION, FUNCTIONPOINT, 144), + CURLOPTDEPRECATED(CURLOPT_CONV_FROM_UTF8_FUNCTION, + CURLOPTTYPE_FUNCTIONPOINT, 144, + 7.82.0, "Serves no purpose anymore"), /* if the connection proceeds too quickly then need to slow it down */ /* limit-rate: maximum number of bytes per second to send or receive */ - CINIT(MAX_SEND_SPEED_LARGE, OFF_T, 145), - CINIT(MAX_RECV_SPEED_LARGE, OFF_T, 146), + CURLOPT(CURLOPT_MAX_SEND_SPEED_LARGE, CURLOPTTYPE_OFF_T, 145), + CURLOPT(CURLOPT_MAX_RECV_SPEED_LARGE, CURLOPTTYPE_OFF_T, 146), /* Pointer to command string to send if USER/PASS fails. */ - CINIT(FTP_ALTERNATIVE_TO_USER, STRINGPOINT, 147), + CURLOPT(CURLOPT_FTP_ALTERNATIVE_TO_USER, CURLOPTTYPE_STRINGPOINT, 147), /* callback function for setting socket options */ - CINIT(SOCKOPTFUNCTION, FUNCTIONPOINT, 148), - CINIT(SOCKOPTDATA, OBJECTPOINT, 149), + CURLOPT(CURLOPT_SOCKOPTFUNCTION, CURLOPTTYPE_FUNCTIONPOINT, 148), + CURLOPT(CURLOPT_SOCKOPTDATA, CURLOPTTYPE_CBPOINT, 149), - /* set to 0 to disable session ID re-use for this transfer, default is + /* set to 0 to disable session ID reuse for this transfer, default is enabled (== 1) */ - CINIT(SSL_SESSIONID_CACHE, LONG, 150), + CURLOPT(CURLOPT_SSL_SESSIONID_CACHE, CURLOPTTYPE_LONG, 150), /* allowed SSH authentication methods */ - CINIT(SSH_AUTH_TYPES, LONG, 151), + CURLOPT(CURLOPT_SSH_AUTH_TYPES, CURLOPTTYPE_VALUES, 151), /* Used by scp/sftp to do public/private key authentication */ - CINIT(SSH_PUBLIC_KEYFILE, STRINGPOINT, 152), - CINIT(SSH_PRIVATE_KEYFILE, STRINGPOINT, 153), + CURLOPT(CURLOPT_SSH_PUBLIC_KEYFILE, CURLOPTTYPE_STRINGPOINT, 152), + CURLOPT(CURLOPT_SSH_PRIVATE_KEYFILE, CURLOPTTYPE_STRINGPOINT, 153), /* Send CCC (Clear Command Channel) after authentication */ - CINIT(FTP_SSL_CCC, LONG, 154), + CURLOPT(CURLOPT_FTP_SSL_CCC, CURLOPTTYPE_LONG, 154), /* Same as TIMEOUT and CONNECTTIMEOUT, but with ms resolution */ - CINIT(TIMEOUT_MS, LONG, 155), - CINIT(CONNECTTIMEOUT_MS, LONG, 156), + CURLOPT(CURLOPT_TIMEOUT_MS, CURLOPTTYPE_LONG, 155), + CURLOPT(CURLOPT_CONNECTTIMEOUT_MS, CURLOPTTYPE_LONG, 156), /* set to zero to disable the libcurl's decoding and thus pass the raw body data to the application even when it is encoded/compressed */ - CINIT(HTTP_TRANSFER_DECODING, LONG, 157), - CINIT(HTTP_CONTENT_DECODING, LONG, 158), + CURLOPT(CURLOPT_HTTP_TRANSFER_DECODING, CURLOPTTYPE_LONG, 157), + CURLOPT(CURLOPT_HTTP_CONTENT_DECODING, CURLOPTTYPE_LONG, 158), /* Permission used when creating new files and directories on the remote server for protocols that support it, SFTP/SCP/FILE */ - CINIT(NEW_FILE_PERMS, LONG, 159), - CINIT(NEW_DIRECTORY_PERMS, LONG, 160), + CURLOPT(CURLOPT_NEW_FILE_PERMS, CURLOPTTYPE_LONG, 159), + CURLOPT(CURLOPT_NEW_DIRECTORY_PERMS, CURLOPTTYPE_LONG, 160), - /* Set the behaviour of POST when redirecting. Values must be set to one + /* Set the behavior of POST when redirecting. Values must be set to one of CURL_REDIR* defines below. This used to be called CURLOPT_POST301 */ - CINIT(POSTREDIR, LONG, 161), + CURLOPT(CURLOPT_POSTREDIR, CURLOPTTYPE_VALUES, 161), /* used by scp/sftp to verify the host's public key */ - CINIT(SSH_HOST_PUBLIC_KEY_MD5, STRINGPOINT, 162), + CURLOPT(CURLOPT_SSH_HOST_PUBLIC_KEY_MD5, CURLOPTTYPE_STRINGPOINT, 162), /* Callback function for opening socket (instead of socket(2)). Optionally, callback is able change the address or refuse to connect returning - CURL_SOCKET_BAD. The callback should have type + CURL_SOCKET_BAD. The callback should have type curl_opensocket_callback */ - CINIT(OPENSOCKETFUNCTION, FUNCTIONPOINT, 163), - CINIT(OPENSOCKETDATA, OBJECTPOINT, 164), + CURLOPT(CURLOPT_OPENSOCKETFUNCTION, CURLOPTTYPE_FUNCTIONPOINT, 163), + CURLOPT(CURLOPT_OPENSOCKETDATA, CURLOPTTYPE_CBPOINT, 164), /* POST volatile input fields. */ - CINIT(COPYPOSTFIELDS, OBJECTPOINT, 165), + CURLOPT(CURLOPT_COPYPOSTFIELDS, CURLOPTTYPE_OBJECTPOINT, 165), /* set transfer mode (;type=) when doing FTP via an HTTP proxy */ - CINIT(PROXY_TRANSFER_MODE, LONG, 166), + CURLOPT(CURLOPT_PROXY_TRANSFER_MODE, CURLOPTTYPE_LONG, 166), /* Callback function for seeking in the input stream */ - CINIT(SEEKFUNCTION, FUNCTIONPOINT, 167), - CINIT(SEEKDATA, OBJECTPOINT, 168), + CURLOPT(CURLOPT_SEEKFUNCTION, CURLOPTTYPE_FUNCTIONPOINT, 167), + CURLOPT(CURLOPT_SEEKDATA, CURLOPTTYPE_CBPOINT, 168), /* CRL file */ - CINIT(CRLFILE, STRINGPOINT, 169), + CURLOPT(CURLOPT_CRLFILE, CURLOPTTYPE_STRINGPOINT, 169), /* Issuer certificate */ - CINIT(ISSUERCERT, STRINGPOINT, 170), + CURLOPT(CURLOPT_ISSUERCERT, CURLOPTTYPE_STRINGPOINT, 170), /* (IPv6) Address scope */ - CINIT(ADDRESS_SCOPE, LONG, 171), + CURLOPT(CURLOPT_ADDRESS_SCOPE, CURLOPTTYPE_LONG, 171), /* Collect certificate chain info and allow it to get retrievable with CURLINFO_CERTINFO after the transfer is complete. */ - CINIT(CERTINFO, LONG, 172), + CURLOPT(CURLOPT_CERTINFO, CURLOPTTYPE_LONG, 172), /* "name" and "pwd" to use when fetching. */ - CINIT(USERNAME, STRINGPOINT, 173), - CINIT(PASSWORD, STRINGPOINT, 174), + CURLOPT(CURLOPT_USERNAME, CURLOPTTYPE_STRINGPOINT, 173), + CURLOPT(CURLOPT_PASSWORD, CURLOPTTYPE_STRINGPOINT, 174), /* "name" and "pwd" to use with Proxy when fetching. */ - CINIT(PROXYUSERNAME, STRINGPOINT, 175), - CINIT(PROXYPASSWORD, STRINGPOINT, 176), + CURLOPT(CURLOPT_PROXYUSERNAME, CURLOPTTYPE_STRINGPOINT, 175), + CURLOPT(CURLOPT_PROXYPASSWORD, CURLOPTTYPE_STRINGPOINT, 176), /* Comma separated list of hostnames defining no-proxy zones. These should match both hostnames directly, and hostnames within a domain. For @@ -1478,103 +1736,107 @@ typedef enum { implementations of this, .local.com will be considered to be the same as local.com. A single * is the only valid wildcard, and effectively disables the use of proxy. */ - CINIT(NOPROXY, STRINGPOINT, 177), + CURLOPT(CURLOPT_NOPROXY, CURLOPTTYPE_STRINGPOINT, 177), /* block size for TFTP transfers */ - CINIT(TFTP_BLKSIZE, LONG, 178), + CURLOPT(CURLOPT_TFTP_BLKSIZE, CURLOPTTYPE_LONG, 178), /* Socks Service */ - CINIT(SOCKS5_GSSAPI_SERVICE, STRINGPOINT, 179), /* DEPRECATED, do not use! */ + /* DEPRECATED, do not use! */ + CURLOPTDEPRECATED(CURLOPT_SOCKS5_GSSAPI_SERVICE, + CURLOPTTYPE_STRINGPOINT, 179, + 7.49.0, "Use CURLOPT_PROXY_SERVICE_NAME"), /* Socks Service */ - CINIT(SOCKS5_GSSAPI_NEC, LONG, 180), + CURLOPT(CURLOPT_SOCKS5_GSSAPI_NEC, CURLOPTTYPE_LONG, 180), /* 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. */ - CINIT(PROTOCOLS, LONG, 181), + CURLOPTDEPRECATED(CURLOPT_PROTOCOLS, CURLOPTTYPE_LONG, 181, + 7.85.0, "Use CURLOPT_PROTOCOLS_STR"), /* 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. Defaults - to all protocols except FILE and SCP. */ - CINIT(REDIR_PROTOCOLS, LONG, 182), + to be set in both bitmasks to be allowed to get redirected to. */ + CURLOPTDEPRECATED(CURLOPT_REDIR_PROTOCOLS, CURLOPTTYPE_LONG, 182, + 7.85.0, "Use CURLOPT_REDIR_PROTOCOLS_STR"), - /* set the SSH knownhost file name to use */ - CINIT(SSH_KNOWNHOSTS, STRINGPOINT, 183), + /* set the SSH knownhost filename to use */ + CURLOPT(CURLOPT_SSH_KNOWNHOSTS, CURLOPTTYPE_STRINGPOINT, 183), /* set the SSH host key callback, must point to a curl_sshkeycallback function */ - CINIT(SSH_KEYFUNCTION, FUNCTIONPOINT, 184), + CURLOPT(CURLOPT_SSH_KEYFUNCTION, CURLOPTTYPE_FUNCTIONPOINT, 184), /* set the SSH host key callback custom pointer */ - CINIT(SSH_KEYDATA, OBJECTPOINT, 185), + CURLOPT(CURLOPT_SSH_KEYDATA, CURLOPTTYPE_CBPOINT, 185), /* set the SMTP mail originator */ - CINIT(MAIL_FROM, STRINGPOINT, 186), + CURLOPT(CURLOPT_MAIL_FROM, CURLOPTTYPE_STRINGPOINT, 186), /* set the list of SMTP mail receiver(s) */ - CINIT(MAIL_RCPT, OBJECTPOINT, 187), + CURLOPT(CURLOPT_MAIL_RCPT, CURLOPTTYPE_SLISTPOINT, 187), /* FTP: send PRET before PASV */ - CINIT(FTP_USE_PRET, LONG, 188), + CURLOPT(CURLOPT_FTP_USE_PRET, CURLOPTTYPE_LONG, 188), /* RTSP request method (OPTIONS, SETUP, PLAY, etc...) */ - CINIT(RTSP_REQUEST, LONG, 189), + CURLOPT(CURLOPT_RTSP_REQUEST, CURLOPTTYPE_VALUES, 189), /* The RTSP session identifier */ - CINIT(RTSP_SESSION_ID, STRINGPOINT, 190), + CURLOPT(CURLOPT_RTSP_SESSION_ID, CURLOPTTYPE_STRINGPOINT, 190), /* The RTSP stream URI */ - CINIT(RTSP_STREAM_URI, STRINGPOINT, 191), + CURLOPT(CURLOPT_RTSP_STREAM_URI, CURLOPTTYPE_STRINGPOINT, 191), /* The Transport: header to use in RTSP requests */ - CINIT(RTSP_TRANSPORT, STRINGPOINT, 192), + CURLOPT(CURLOPT_RTSP_TRANSPORT, CURLOPTTYPE_STRINGPOINT, 192), /* Manually initialize the client RTSP CSeq for this handle */ - CINIT(RTSP_CLIENT_CSEQ, LONG, 193), + CURLOPT(CURLOPT_RTSP_CLIENT_CSEQ, CURLOPTTYPE_LONG, 193), /* Manually initialize the server RTSP CSeq for this handle */ - CINIT(RTSP_SERVER_CSEQ, LONG, 194), + CURLOPT(CURLOPT_RTSP_SERVER_CSEQ, CURLOPTTYPE_LONG, 194), /* The stream to pass to INTERLEAVEFUNCTION. */ - CINIT(INTERLEAVEDATA, OBJECTPOINT, 195), + CURLOPT(CURLOPT_INTERLEAVEDATA, CURLOPTTYPE_CBPOINT, 195), /* Let the application define a custom write method for RTP data */ - CINIT(INTERLEAVEFUNCTION, FUNCTIONPOINT, 196), + CURLOPT(CURLOPT_INTERLEAVEFUNCTION, CURLOPTTYPE_FUNCTIONPOINT, 196), /* Turn on wildcard matching */ - CINIT(WILDCARDMATCH, LONG, 197), + CURLOPT(CURLOPT_WILDCARDMATCH, CURLOPTTYPE_LONG, 197), /* Directory matching callback called before downloading of an individual file (chunk) started */ - CINIT(CHUNK_BGN_FUNCTION, FUNCTIONPOINT, 198), + CURLOPT(CURLOPT_CHUNK_BGN_FUNCTION, CURLOPTTYPE_FUNCTIONPOINT, 198), /* Directory matching callback called after the file (chunk) was downloaded, or skipped */ - CINIT(CHUNK_END_FUNCTION, FUNCTIONPOINT, 199), + CURLOPT(CURLOPT_CHUNK_END_FUNCTION, CURLOPTTYPE_FUNCTIONPOINT, 199), /* Change match (fnmatch-like) callback for wildcard matching */ - CINIT(FNMATCH_FUNCTION, FUNCTIONPOINT, 200), + CURLOPT(CURLOPT_FNMATCH_FUNCTION, CURLOPTTYPE_FUNCTIONPOINT, 200), /* Let the application define custom chunk data pointer */ - CINIT(CHUNK_DATA, OBJECTPOINT, 201), + CURLOPT(CURLOPT_CHUNK_DATA, CURLOPTTYPE_CBPOINT, 201), /* FNMATCH_FUNCTION user pointer */ - CINIT(FNMATCH_DATA, OBJECTPOINT, 202), + CURLOPT(CURLOPT_FNMATCH_DATA, CURLOPTTYPE_CBPOINT, 202), /* send linked-list of name:port:address sets */ - CINIT(RESOLVE, OBJECTPOINT, 203), + CURLOPT(CURLOPT_RESOLVE, CURLOPTTYPE_SLISTPOINT, 203), /* Set a username for authenticated TLS */ - CINIT(TLSAUTH_USERNAME, STRINGPOINT, 204), + CURLOPT(CURLOPT_TLSAUTH_USERNAME, CURLOPTTYPE_STRINGPOINT, 204), /* Set a password for authenticated TLS */ - CINIT(TLSAUTH_PASSWORD, STRINGPOINT, 205), + CURLOPT(CURLOPT_TLSAUTH_PASSWORD, CURLOPTTYPE_STRINGPOINT, 205), /* Set authentication type for authenticated TLS */ - CINIT(TLSAUTH_TYPE, STRINGPOINT, 206), + CURLOPT(CURLOPT_TLSAUTH_TYPE, CURLOPTTYPE_STRINGPOINT, 206), /* Set to 1 to enable the "TE:" header in HTTP requests to ask for compressed transfer-encoded responses. Set to 0 to disable the use of TE: @@ -1582,201 +1844,385 @@ typedef enum { future libcurl release. libcurl will ask for the compressed methods it knows of, and if that - isn't any, it will not ask for transfer-encoding at all even if this + is not any, it will not ask for transfer-encoding at all even if this option is set to 1. */ - CINIT(TRANSFER_ENCODING, LONG, 207), + CURLOPT(CURLOPT_TRANSFER_ENCODING, CURLOPTTYPE_LONG, 207), /* Callback function for closing socket (instead of close(2)). The callback should have type curl_closesocket_callback */ - CINIT(CLOSESOCKETFUNCTION, FUNCTIONPOINT, 208), - CINIT(CLOSESOCKETDATA, OBJECTPOINT, 209), + CURLOPT(CURLOPT_CLOSESOCKETFUNCTION, CURLOPTTYPE_FUNCTIONPOINT, 208), + CURLOPT(CURLOPT_CLOSESOCKETDATA, CURLOPTTYPE_CBPOINT, 209), /* allow GSSAPI credential delegation */ - CINIT(GSSAPI_DELEGATION, LONG, 210), + CURLOPT(CURLOPT_GSSAPI_DELEGATION, CURLOPTTYPE_VALUES, 210), - /* Set the name servers to use for DNS resolution */ - CINIT(DNS_SERVERS, STRINGPOINT, 211), + /* Set the name servers to use for DNS resolution. + * Only supported by the c-ares DNS backend */ + CURLOPT(CURLOPT_DNS_SERVERS, CURLOPTTYPE_STRINGPOINT, 211), /* Time-out accept operations (currently for FTP only) after this amount - of miliseconds. */ - CINIT(ACCEPTTIMEOUT_MS, LONG, 212), + of milliseconds. */ + CURLOPT(CURLOPT_ACCEPTTIMEOUT_MS, CURLOPTTYPE_LONG, 212), /* Set TCP keepalive */ - CINIT(TCP_KEEPALIVE, LONG, 213), + CURLOPT(CURLOPT_TCP_KEEPALIVE, CURLOPTTYPE_LONG, 213), /* non-universal keepalive knobs (Linux, AIX, HP-UX, more) */ - CINIT(TCP_KEEPIDLE, LONG, 214), - CINIT(TCP_KEEPINTVL, LONG, 215), + CURLOPT(CURLOPT_TCP_KEEPIDLE, CURLOPTTYPE_LONG, 214), + CURLOPT(CURLOPT_TCP_KEEPINTVL, CURLOPTTYPE_LONG, 215), /* Enable/disable specific SSL features with a bitmask, see CURLSSLOPT_* */ - CINIT(SSL_OPTIONS, LONG, 216), + CURLOPT(CURLOPT_SSL_OPTIONS, CURLOPTTYPE_VALUES, 216), /* Set the SMTP auth originator */ - CINIT(MAIL_AUTH, STRINGPOINT, 217), + CURLOPT(CURLOPT_MAIL_AUTH, CURLOPTTYPE_STRINGPOINT, 217), /* Enable/disable SASL initial response */ - CINIT(SASL_IR, LONG, 218), + CURLOPT(CURLOPT_SASL_IR, CURLOPTTYPE_LONG, 218), /* Function that will be called instead of the internal progress display * function. This function should be defined as the curl_xferinfo_callback * prototype defines. (Deprecates CURLOPT_PROGRESSFUNCTION) */ - CINIT(XFERINFOFUNCTION, FUNCTIONPOINT, 219), + CURLOPT(CURLOPT_XFERINFOFUNCTION, CURLOPTTYPE_FUNCTIONPOINT, 219), /* The XOAUTH2 bearer token */ - CINIT(XOAUTH2_BEARER, STRINGPOINT, 220), + CURLOPT(CURLOPT_XOAUTH2_BEARER, CURLOPTTYPE_STRINGPOINT, 220), /* Set the interface string to use as outgoing network * interface for DNS requests. * Only supported by the c-ares DNS backend */ - CINIT(DNS_INTERFACE, STRINGPOINT, 221), + CURLOPT(CURLOPT_DNS_INTERFACE, CURLOPTTYPE_STRINGPOINT, 221), /* Set the local IPv4 address to use for outgoing DNS requests. * Only supported by the c-ares DNS backend */ - CINIT(DNS_LOCAL_IP4, STRINGPOINT, 222), + CURLOPT(CURLOPT_DNS_LOCAL_IP4, CURLOPTTYPE_STRINGPOINT, 222), - /* Set the local IPv4 address to use for outgoing DNS requests. + /* Set the local IPv6 address to use for outgoing DNS requests. * Only supported by the c-ares DNS backend */ - CINIT(DNS_LOCAL_IP6, STRINGPOINT, 223), + CURLOPT(CURLOPT_DNS_LOCAL_IP6, CURLOPTTYPE_STRINGPOINT, 223), /* Set authentication options directly */ - CINIT(LOGIN_OPTIONS, STRINGPOINT, 224), + CURLOPT(CURLOPT_LOGIN_OPTIONS, CURLOPTTYPE_STRINGPOINT, 224), /* Enable/disable TLS NPN extension (http2 over ssl might fail without) */ - CINIT(SSL_ENABLE_NPN, LONG, 225), + CURLOPTDEPRECATED(CURLOPT_SSL_ENABLE_NPN, CURLOPTTYPE_LONG, 225, + 7.86.0, "Has no function"), /* Enable/disable TLS ALPN extension (http2 over ssl might fail without) */ - CINIT(SSL_ENABLE_ALPN, LONG, 226), + CURLOPT(CURLOPT_SSL_ENABLE_ALPN, CURLOPTTYPE_LONG, 226), - /* Time to wait for a response to a HTTP request containing an + /* Time to wait for a response to an HTTP request containing an * Expect: 100-continue header before sending the data anyway. */ - CINIT(EXPECT_100_TIMEOUT_MS, LONG, 227), + CURLOPT(CURLOPT_EXPECT_100_TIMEOUT_MS, CURLOPTTYPE_LONG, 227), /* This points to a linked list of headers used for proxy requests only, struct curl_slist kind */ - CINIT(PROXYHEADER, OBJECTPOINT, 228), + CURLOPT(CURLOPT_PROXYHEADER, CURLOPTTYPE_SLISTPOINT, 228), /* Pass in a bitmask of "header options" */ - CINIT(HEADEROPT, LONG, 229), + CURLOPT(CURLOPT_HEADEROPT, CURLOPTTYPE_VALUES, 229), /* The public key in DER form used to validate the peer public key this option is used only if SSL_VERIFYPEER is true */ - CINIT(PINNEDPUBLICKEY, STRINGPOINT, 230), + CURLOPT(CURLOPT_PINNEDPUBLICKEY, CURLOPTTYPE_STRINGPOINT, 230), /* Path to Unix domain socket */ - CINIT(UNIX_SOCKET_PATH, STRINGPOINT, 231), + CURLOPT(CURLOPT_UNIX_SOCKET_PATH, CURLOPTTYPE_STRINGPOINT, 231), /* Set if we should verify the certificate status. */ - CINIT(SSL_VERIFYSTATUS, LONG, 232), + CURLOPT(CURLOPT_SSL_VERIFYSTATUS, CURLOPTTYPE_LONG, 232), /* Set if we should enable TLS false start. */ - CINIT(SSL_FALSESTART, LONG, 233), + CURLOPT(CURLOPT_SSL_FALSESTART, CURLOPTTYPE_LONG, 233), /* Do not squash dot-dot sequences */ - CINIT(PATH_AS_IS, LONG, 234), + CURLOPT(CURLOPT_PATH_AS_IS, CURLOPTTYPE_LONG, 234), /* Proxy Service Name */ - CINIT(PROXY_SERVICE_NAME, STRINGPOINT, 235), + CURLOPT(CURLOPT_PROXY_SERVICE_NAME, CURLOPTTYPE_STRINGPOINT, 235), /* Service Name */ - CINIT(SERVICE_NAME, STRINGPOINT, 236), + CURLOPT(CURLOPT_SERVICE_NAME, CURLOPTTYPE_STRINGPOINT, 236), - /* Wait/don't wait for pipe/mutex to clarify */ - CINIT(PIPEWAIT, LONG, 237), + /* Wait/do not wait for pipe/mutex to clarify */ + CURLOPT(CURLOPT_PIPEWAIT, CURLOPTTYPE_LONG, 237), /* Set the protocol used when curl is given a URL without a protocol */ - CINIT(DEFAULT_PROTOCOL, STRINGPOINT, 238), + CURLOPT(CURLOPT_DEFAULT_PROTOCOL, CURLOPTTYPE_STRINGPOINT, 238), /* Set stream weight, 1 - 256 (default is 16) */ - CINIT(STREAM_WEIGHT, LONG, 239), + CURLOPT(CURLOPT_STREAM_WEIGHT, CURLOPTTYPE_LONG, 239), /* Set stream dependency on another CURL handle */ - CINIT(STREAM_DEPENDS, OBJECTPOINT, 240), + CURLOPT(CURLOPT_STREAM_DEPENDS, CURLOPTTYPE_OBJECTPOINT, 240), /* Set E-xclusive stream dependency on another CURL handle */ - CINIT(STREAM_DEPENDS_E, OBJECTPOINT, 241), + CURLOPT(CURLOPT_STREAM_DEPENDS_E, CURLOPTTYPE_OBJECTPOINT, 241), /* Do not send any tftp option requests to the server */ - CINIT(TFTP_NO_OPTIONS, LONG, 242), + CURLOPT(CURLOPT_TFTP_NO_OPTIONS, CURLOPTTYPE_LONG, 242), /* Linked-list of host:port:connect-to-host:connect-to-port, overrides the URL's host:port (only for the network layer) */ - CINIT(CONNECT_TO, OBJECTPOINT, 243), + CURLOPT(CURLOPT_CONNECT_TO, CURLOPTTYPE_SLISTPOINT, 243), /* Set TCP Fast Open */ - CINIT(TCP_FASTOPEN, LONG, 244), + CURLOPT(CURLOPT_TCP_FASTOPEN, CURLOPTTYPE_LONG, 244), /* Continue to send data if the server responds early with an * HTTP status code >= 300 */ - CINIT(KEEP_SENDING_ON_ERROR, LONG, 245), + CURLOPT(CURLOPT_KEEP_SENDING_ON_ERROR, CURLOPTTYPE_LONG, 245), /* The CApath or CAfile used to validate the proxy certificate this option is used only if PROXY_SSL_VERIFYPEER is true */ - CINIT(PROXY_CAINFO, STRINGPOINT, 246), + CURLOPT(CURLOPT_PROXY_CAINFO, CURLOPTTYPE_STRINGPOINT, 246), /* The CApath directory used to validate the proxy certificate this option is used only if PROXY_SSL_VERIFYPEER is true */ - CINIT(PROXY_CAPATH, STRINGPOINT, 247), + CURLOPT(CURLOPT_PROXY_CAPATH, CURLOPTTYPE_STRINGPOINT, 247), /* Set if we should verify the proxy in ssl handshake, set 1 to verify. */ - CINIT(PROXY_SSL_VERIFYPEER, LONG, 248), + CURLOPT(CURLOPT_PROXY_SSL_VERIFYPEER, CURLOPTTYPE_LONG, 248), /* Set if we should verify the Common name from the proxy certificate in ssl * handshake, set 1 to check existence, 2 to ensure that it matches * the provided hostname. */ - CINIT(PROXY_SSL_VERIFYHOST, LONG, 249), + CURLOPT(CURLOPT_PROXY_SSL_VERIFYHOST, CURLOPTTYPE_LONG, 249), /* What version to specifically try to use for proxy. See CURL_SSLVERSION defines below. */ - CINIT(PROXY_SSLVERSION, LONG, 250), + CURLOPT(CURLOPT_PROXY_SSLVERSION, CURLOPTTYPE_VALUES, 250), /* Set a username for authenticated TLS for proxy */ - CINIT(PROXY_TLSAUTH_USERNAME, STRINGPOINT, 251), + CURLOPT(CURLOPT_PROXY_TLSAUTH_USERNAME, CURLOPTTYPE_STRINGPOINT, 251), /* Set a password for authenticated TLS for proxy */ - CINIT(PROXY_TLSAUTH_PASSWORD, STRINGPOINT, 252), + CURLOPT(CURLOPT_PROXY_TLSAUTH_PASSWORD, CURLOPTTYPE_STRINGPOINT, 252), /* Set authentication type for authenticated TLS for proxy */ - CINIT(PROXY_TLSAUTH_TYPE, STRINGPOINT, 253), + CURLOPT(CURLOPT_PROXY_TLSAUTH_TYPE, CURLOPTTYPE_STRINGPOINT, 253), /* name of the file keeping your private SSL-certificate for proxy */ - CINIT(PROXY_SSLCERT, STRINGPOINT, 254), + CURLOPT(CURLOPT_PROXY_SSLCERT, CURLOPTTYPE_STRINGPOINT, 254), /* type of the file keeping your SSL-certificate ("DER", "PEM", "ENG") for proxy */ - CINIT(PROXY_SSLCERTTYPE, STRINGPOINT, 255), + CURLOPT(CURLOPT_PROXY_SSLCERTTYPE, CURLOPTTYPE_STRINGPOINT, 255), /* name of the file keeping your private SSL-key for proxy */ - CINIT(PROXY_SSLKEY, STRINGPOINT, 256), + CURLOPT(CURLOPT_PROXY_SSLKEY, CURLOPTTYPE_STRINGPOINT, 256), /* type of the file keeping your private SSL-key ("DER", "PEM", "ENG") for proxy */ - CINIT(PROXY_SSLKEYTYPE, STRINGPOINT, 257), + CURLOPT(CURLOPT_PROXY_SSLKEYTYPE, CURLOPTTYPE_STRINGPOINT, 257), /* password for the SSL private key for proxy */ - CINIT(PROXY_KEYPASSWD, STRINGPOINT, 258), + CURLOPT(CURLOPT_PROXY_KEYPASSWD, CURLOPTTYPE_STRINGPOINT, 258), - /* Specify which SSL ciphers to use for proxy */ - CINIT(PROXY_SSL_CIPHER_LIST, STRINGPOINT, 259), + /* Specify which TLS 1.2 (1.1, 1.0) ciphers to use for proxy */ + CURLOPT(CURLOPT_PROXY_SSL_CIPHER_LIST, CURLOPTTYPE_STRINGPOINT, 259), /* CRL file for proxy */ - CINIT(PROXY_CRLFILE, STRINGPOINT, 260), + CURLOPT(CURLOPT_PROXY_CRLFILE, CURLOPTTYPE_STRINGPOINT, 260), /* Enable/disable specific SSL features with a bitmask for proxy, see CURLSSLOPT_* */ - CINIT(PROXY_SSL_OPTIONS, LONG, 261), + CURLOPT(CURLOPT_PROXY_SSL_OPTIONS, CURLOPTTYPE_LONG, 261), /* Name of pre proxy to use. */ - CINIT(PRE_PROXY, STRINGPOINT, 262), + CURLOPT(CURLOPT_PRE_PROXY, CURLOPTTYPE_STRINGPOINT, 262), /* The public key in DER form used to validate the proxy public key this option is used only if PROXY_SSL_VERIFYPEER is true */ - CINIT(PROXY_PINNEDPUBLICKEY, STRINGPOINT, 263), + CURLOPT(CURLOPT_PROXY_PINNEDPUBLICKEY, CURLOPTTYPE_STRINGPOINT, 263), /* Path to an abstract Unix domain socket */ - CINIT(ABSTRACT_UNIX_SOCKET, STRINGPOINT, 264), + CURLOPT(CURLOPT_ABSTRACT_UNIX_SOCKET, CURLOPTTYPE_STRINGPOINT, 264), + + /* Suppress proxy CONNECT response headers from user callbacks */ + CURLOPT(CURLOPT_SUPPRESS_CONNECT_HEADERS, CURLOPTTYPE_LONG, 265), + + /* The request target, instead of extracted from the URL */ + CURLOPT(CURLOPT_REQUEST_TARGET, CURLOPTTYPE_STRINGPOINT, 266), + + /* bitmask of allowed auth methods for connections to SOCKS5 proxies */ + CURLOPT(CURLOPT_SOCKS5_AUTH, CURLOPTTYPE_LONG, 267), + + /* Enable/disable SSH compression */ + CURLOPT(CURLOPT_SSH_COMPRESSION, CURLOPTTYPE_LONG, 268), + + /* Post MIME data. */ + CURLOPT(CURLOPT_MIMEPOST, CURLOPTTYPE_OBJECTPOINT, 269), + + /* Time to use with the CURLOPT_TIMECONDITION. Specified in number of + seconds since 1 Jan 1970. */ + CURLOPT(CURLOPT_TIMEVALUE_LARGE, CURLOPTTYPE_OFF_T, 270), + + /* Head start in milliseconds to give happy eyeballs. */ + CURLOPT(CURLOPT_HAPPY_EYEBALLS_TIMEOUT_MS, CURLOPTTYPE_LONG, 271), + + /* Function that will be called before a resolver request is made */ + CURLOPT(CURLOPT_RESOLVER_START_FUNCTION, CURLOPTTYPE_FUNCTIONPOINT, 272), + + /* User data to pass to the resolver start callback. */ + CURLOPT(CURLOPT_RESOLVER_START_DATA, CURLOPTTYPE_CBPOINT, 273), + + /* send HAProxy PROXY protocol header? */ + CURLOPT(CURLOPT_HAPROXYPROTOCOL, CURLOPTTYPE_LONG, 274), + + /* shuffle addresses before use when DNS returns multiple */ + CURLOPT(CURLOPT_DNS_SHUFFLE_ADDRESSES, CURLOPTTYPE_LONG, 275), + + /* Specify which TLS 1.3 ciphers suites to use */ + CURLOPT(CURLOPT_TLS13_CIPHERS, CURLOPTTYPE_STRINGPOINT, 276), + CURLOPT(CURLOPT_PROXY_TLS13_CIPHERS, CURLOPTTYPE_STRINGPOINT, 277), + + /* Disallow specifying username/login in URL. */ + CURLOPT(CURLOPT_DISALLOW_USERNAME_IN_URL, CURLOPTTYPE_LONG, 278), + + /* DNS-over-HTTPS URL */ + CURLOPT(CURLOPT_DOH_URL, CURLOPTTYPE_STRINGPOINT, 279), + + /* Preferred buffer size to use for uploads */ + CURLOPT(CURLOPT_UPLOAD_BUFFERSIZE, CURLOPTTYPE_LONG, 280), + + /* Time in ms between connection upkeep calls for long-lived connections. */ + CURLOPT(CURLOPT_UPKEEP_INTERVAL_MS, CURLOPTTYPE_LONG, 281), + + /* Specify URL using CURL URL API. */ + CURLOPT(CURLOPT_CURLU, CURLOPTTYPE_OBJECTPOINT, 282), + + /* add trailing data just after no more data is available */ + CURLOPT(CURLOPT_TRAILERFUNCTION, CURLOPTTYPE_FUNCTIONPOINT, 283), + + /* pointer to be passed to HTTP_TRAILER_FUNCTION */ + CURLOPT(CURLOPT_TRAILERDATA, CURLOPTTYPE_CBPOINT, 284), + + /* set this to 1L to allow HTTP/0.9 responses or 0L to disallow */ + CURLOPT(CURLOPT_HTTP09_ALLOWED, CURLOPTTYPE_LONG, 285), + + /* alt-svc control bitmask */ + CURLOPT(CURLOPT_ALTSVC_CTRL, CURLOPTTYPE_LONG, 286), + + /* alt-svc cache filename to possibly read from/write to */ + CURLOPT(CURLOPT_ALTSVC, CURLOPTTYPE_STRINGPOINT, 287), + + /* maximum age (idle time) of a connection to consider it for reuse + * (in seconds) */ + CURLOPT(CURLOPT_MAXAGE_CONN, CURLOPTTYPE_LONG, 288), + + /* SASL authorization identity */ + CURLOPT(CURLOPT_SASL_AUTHZID, CURLOPTTYPE_STRINGPOINT, 289), + + /* allow RCPT TO command to fail for some recipients */ + CURLOPT(CURLOPT_MAIL_RCPT_ALLOWFAILS, CURLOPTTYPE_LONG, 290), + + /* the private SSL-certificate as a "blob" */ + CURLOPT(CURLOPT_SSLCERT_BLOB, CURLOPTTYPE_BLOB, 291), + CURLOPT(CURLOPT_SSLKEY_BLOB, CURLOPTTYPE_BLOB, 292), + CURLOPT(CURLOPT_PROXY_SSLCERT_BLOB, CURLOPTTYPE_BLOB, 293), + CURLOPT(CURLOPT_PROXY_SSLKEY_BLOB, CURLOPTTYPE_BLOB, 294), + CURLOPT(CURLOPT_ISSUERCERT_BLOB, CURLOPTTYPE_BLOB, 295), + + /* Issuer certificate for proxy */ + CURLOPT(CURLOPT_PROXY_ISSUERCERT, CURLOPTTYPE_STRINGPOINT, 296), + CURLOPT(CURLOPT_PROXY_ISSUERCERT_BLOB, CURLOPTTYPE_BLOB, 297), + + /* the EC curves requested by the TLS client (RFC 8422, 5.1); + * OpenSSL support via 'set_groups'/'set_curves': + * https://docs.openssl.org/master/man3/SSL_CTX_set1_curves/ + */ + CURLOPT(CURLOPT_SSL_EC_CURVES, CURLOPTTYPE_STRINGPOINT, 298), + + /* HSTS bitmask */ + CURLOPT(CURLOPT_HSTS_CTRL, CURLOPTTYPE_LONG, 299), + /* HSTS filename */ + CURLOPT(CURLOPT_HSTS, CURLOPTTYPE_STRINGPOINT, 300), + + /* HSTS read callback */ + CURLOPT(CURLOPT_HSTSREADFUNCTION, CURLOPTTYPE_FUNCTIONPOINT, 301), + CURLOPT(CURLOPT_HSTSREADDATA, CURLOPTTYPE_CBPOINT, 302), + + /* HSTS write callback */ + CURLOPT(CURLOPT_HSTSWRITEFUNCTION, CURLOPTTYPE_FUNCTIONPOINT, 303), + CURLOPT(CURLOPT_HSTSWRITEDATA, CURLOPTTYPE_CBPOINT, 304), + + /* Parameters for V4 signature */ + CURLOPT(CURLOPT_AWS_SIGV4, CURLOPTTYPE_STRINGPOINT, 305), + + /* Same as CURLOPT_SSL_VERIFYPEER but for DoH (DNS-over-HTTPS) servers. */ + CURLOPT(CURLOPT_DOH_SSL_VERIFYPEER, CURLOPTTYPE_LONG, 306), + + /* Same as CURLOPT_SSL_VERIFYHOST but for DoH (DNS-over-HTTPS) servers. */ + CURLOPT(CURLOPT_DOH_SSL_VERIFYHOST, CURLOPTTYPE_LONG, 307), + + /* Same as CURLOPT_SSL_VERIFYSTATUS but for DoH (DNS-over-HTTPS) servers. */ + CURLOPT(CURLOPT_DOH_SSL_VERIFYSTATUS, CURLOPTTYPE_LONG, 308), + + /* The CA certificates as "blob" used to validate the peer certificate + this option is used only if SSL_VERIFYPEER is true */ + CURLOPT(CURLOPT_CAINFO_BLOB, CURLOPTTYPE_BLOB, 309), + + /* The CA certificates as "blob" used to validate the proxy certificate + this option is used only if PROXY_SSL_VERIFYPEER is true */ + CURLOPT(CURLOPT_PROXY_CAINFO_BLOB, CURLOPTTYPE_BLOB, 310), + + /* used by scp/sftp to verify the host's public key */ + CURLOPT(CURLOPT_SSH_HOST_PUBLIC_KEY_SHA256, CURLOPTTYPE_STRINGPOINT, 311), + + /* Function that will be called immediately before the initial request + is made on a connection (after any protocol negotiation step). */ + CURLOPT(CURLOPT_PREREQFUNCTION, CURLOPTTYPE_FUNCTIONPOINT, 312), + + /* Data passed to the CURLOPT_PREREQFUNCTION callback */ + CURLOPT(CURLOPT_PREREQDATA, CURLOPTTYPE_CBPOINT, 313), + + /* maximum age (since creation) of a connection to consider it for reuse + * (in seconds) */ + CURLOPT(CURLOPT_MAXLIFETIME_CONN, CURLOPTTYPE_LONG, 314), + + /* Set MIME option flags. */ + CURLOPT(CURLOPT_MIME_OPTIONS, CURLOPTTYPE_LONG, 315), + + /* set the SSH host key callback, must point to a curl_sshkeycallback + function */ + CURLOPT(CURLOPT_SSH_HOSTKEYFUNCTION, CURLOPTTYPE_FUNCTIONPOINT, 316), + + /* set the SSH host key callback custom pointer */ + CURLOPT(CURLOPT_SSH_HOSTKEYDATA, CURLOPTTYPE_CBPOINT, 317), + + /* specify which 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 + all built-in protocols. */ + CURLOPT(CURLOPT_PROTOCOLS_STR, CURLOPTTYPE_STRINGPOINT, 318), + + /* specify which protocols that libcurl is allowed to follow directs to */ + CURLOPT(CURLOPT_REDIR_PROTOCOLS_STR, CURLOPTTYPE_STRINGPOINT, 319), + + /* WebSockets options */ + CURLOPT(CURLOPT_WS_OPTIONS, CURLOPTTYPE_LONG, 320), + + /* CA cache timeout */ + CURLOPT(CURLOPT_CA_CACHE_TIMEOUT, CURLOPTTYPE_LONG, 321), + + /* Can leak things, gonna exit() soon */ + CURLOPT(CURLOPT_QUICK_EXIT, CURLOPTTYPE_LONG, 322), + + /* set a specific client IP for HAProxy PROXY protocol header? */ + CURLOPT(CURLOPT_HAPROXY_CLIENT_IP, CURLOPTTYPE_STRINGPOINT, 323), + + /* millisecond version */ + CURLOPT(CURLOPT_SERVER_RESPONSE_TIMEOUT_MS, CURLOPTTYPE_LONG, 324), + + /* set ECH configuration */ + CURLOPT(CURLOPT_ECH, CURLOPTTYPE_STRINGPOINT, 325), + + /* maximum number of keepalive probes (Linux, *BSD, macOS, etc.) */ + CURLOPT(CURLOPT_TCP_KEEPCNT, CURLOPTTYPE_LONG, 326), CURLOPT_LASTENTRY /* the last unused */ } CURLoption; @@ -1803,6 +2249,12 @@ typedef enum { #define CURLOPT_SSLCERTPASSWD CURLOPT_KEYPASSWD #define CURLOPT_KRB4LEVEL CURLOPT_KRBLEVEL +/* */ +#define CURLOPT_FTP_RESPONSE_TIMEOUT CURLOPT_SERVER_RESPONSE_TIMEOUT + +/* Added in 8.2.0 */ +#define CURLOPT_MAIL_RCPT_ALLLOWFAILS CURLOPT_MAIL_RCPT_ALLOWFAILS + #else /* This is set if CURL_NO_OLDIES is defined at compile-time */ #undef CURLOPT_DNS_USE_GLOBAL_CACHE /* soon obsolete */ @@ -1812,25 +2264,31 @@ typedef enum { /* Below here follows defines for the CURLOPT_IPRESOLVE option. If a host name resolves addresses using more than one IP protocol version, this option might be handy to force libcurl to use a specific IP version. */ -#define CURL_IPRESOLVE_WHATEVER 0 /* default, resolves addresses to all IP +#define CURL_IPRESOLVE_WHATEVER 0 /* default, uses addresses to all IP versions that your system allows */ -#define CURL_IPRESOLVE_V4 1 /* resolve to IPv4 addresses */ -#define CURL_IPRESOLVE_V6 2 /* resolve to IPv6 addresses */ +#define CURL_IPRESOLVE_V4 1 /* uses only IPv4 addresses/connections */ +#define CURL_IPRESOLVE_V6 2 /* uses only IPv6 addresses/connections */ - /* three convenient "aliases" that follow the name scheme better */ + /* Convenient "aliases" */ #define CURLOPT_RTSPHEADER CURLOPT_HTTPHEADER /* These enums are for use with the CURLOPT_HTTP_VERSION option. */ enum { - CURL_HTTP_VERSION_NONE, /* setting this means we don't care, and that we'd - like the library to choose the best possible - for us! */ + CURL_HTTP_VERSION_NONE, /* setting this means we do not care, and that we + would like the library to choose the best + possible for us! */ CURL_HTTP_VERSION_1_0, /* please use HTTP 1.0 in the request */ CURL_HTTP_VERSION_1_1, /* please use HTTP 1.1 in the request */ CURL_HTTP_VERSION_2_0, /* please use HTTP 2 in the request */ CURL_HTTP_VERSION_2TLS, /* use version 2 for HTTPS, version 1.1 for HTTP */ CURL_HTTP_VERSION_2_PRIOR_KNOWLEDGE, /* please use HTTP 2 without HTTP/1.1 Upgrade */ + CURL_HTTP_VERSION_3 = 30, /* Use HTTP/3, fallback to HTTP/2 or HTTP/1 if + needed. For HTTPS only. For HTTP, this option + makes libcurl return error. */ + CURL_HTTP_VERSION_3ONLY = 31, /* Use HTTP/3 without fallback. For HTTPS + only. For HTTP, this makes libcurl + return error. */ CURL_HTTP_VERSION_LAST /* *ILLEGAL* http version */ }; @@ -1871,18 +2329,26 @@ enum CURL_NETRC_OPTION { CURL_NETRC_LAST }; -enum { - CURL_SSLVERSION_DEFAULT, - CURL_SSLVERSION_TLSv1, /* TLS 1.x */ - CURL_SSLVERSION_SSLv2, - CURL_SSLVERSION_SSLv3, - CURL_SSLVERSION_TLSv1_0, - CURL_SSLVERSION_TLSv1_1, - CURL_SSLVERSION_TLSv1_2, - CURL_SSLVERSION_TLSv1_3, - - CURL_SSLVERSION_LAST /* never use, keep last */ -}; +#define CURL_SSLVERSION_DEFAULT 0 +#define CURL_SSLVERSION_TLSv1 1 /* TLS 1.x */ +#define CURL_SSLVERSION_SSLv2 2 +#define CURL_SSLVERSION_SSLv3 3 +#define CURL_SSLVERSION_TLSv1_0 4 +#define CURL_SSLVERSION_TLSv1_1 5 +#define CURL_SSLVERSION_TLSv1_2 6 +#define CURL_SSLVERSION_TLSv1_3 7 + +#define CURL_SSLVERSION_LAST 8 /* never use, keep last */ + +#define CURL_SSLVERSION_MAX_NONE 0 +#define CURL_SSLVERSION_MAX_DEFAULT (CURL_SSLVERSION_TLSv1 << 16) +#define CURL_SSLVERSION_MAX_TLSv1_0 (CURL_SSLVERSION_TLSv1_0 << 16) +#define CURL_SSLVERSION_MAX_TLSv1_1 (CURL_SSLVERSION_TLSv1_1 << 16) +#define CURL_SSLVERSION_MAX_TLSv1_2 (CURL_SSLVERSION_TLSv1_2 << 16) +#define CURL_SSLVERSION_MAX_TLSv1_3 (CURL_SSLVERSION_TLSv1_3 << 16) + + /* never use, keep last */ +#define CURL_SSLVERSION_MAX_LAST (CURL_SSLVERSION_LAST << 16) enum CURL_TLSAUTH { CURL_TLSAUTH_NONE, @@ -1912,60 +2378,173 @@ typedef enum { CURL_TIMECOND_LAST } curl_TimeCond; +/* Special size_t value signaling a null-terminated string. */ +#define CURL_ZERO_TERMINATED ((size_t) -1) /* curl_strequal() and curl_strnequal() are subject for removal in a future - libcurl, see lib/README.curlx for details + release */ +CURL_EXTERN int curl_strequal(const char *s1, const char *s2); +CURL_EXTERN int curl_strnequal(const char *s1, const char *s2, size_t n); - !checksrc! disable SPACEBEFOREPAREN 2 -*/ -CURL_EXTERN int (curl_strequal)(const char *s1, const char *s2); -CURL_EXTERN int (curl_strnequal)(const char *s1, const char *s2, size_t n); +/* Mime/form handling support. */ +typedef struct curl_mime curl_mime; /* Mime context. */ +typedef struct curl_mimepart curl_mimepart; /* Mime part context. */ -/* name is uppercase CURLFORM_ */ -#ifdef CFINIT -#undef CFINIT -#endif +/* CURLMIMEOPT_ defines are for the CURLOPT_MIME_OPTIONS option. */ +#define CURLMIMEOPT_FORMESCAPE (1<<0) /* Use backslash-escaping for forms. */ -#ifdef CURL_ISOCPP -#define CFINIT(name) CURLFORM_ ## name -#else -/* The macro "##" is ISO C, we assume pre-ISO C doesn't support it. */ -#define CFINIT(name) CURLFORM_/**/name -#endif +/* + * NAME curl_mime_init() + * + * DESCRIPTION + * + * Create a mime context and return its handle. The easy parameter is the + * target handle. + */ +CURL_EXTERN curl_mime *curl_mime_init(CURL *easy); + +/* + * NAME curl_mime_free() + * + * DESCRIPTION + * + * release a mime handle and its substructures. + */ +CURL_EXTERN void curl_mime_free(curl_mime *mime); + +/* + * NAME curl_mime_addpart() + * + * DESCRIPTION + * + * Append a new empty part to the given mime context and return a handle to + * the created part. + */ +CURL_EXTERN curl_mimepart *curl_mime_addpart(curl_mime *mime); + +/* + * NAME curl_mime_name() + * + * DESCRIPTION + * + * Set mime/form part name. + */ +CURL_EXTERN CURLcode curl_mime_name(curl_mimepart *part, const char *name); + +/* + * NAME curl_mime_filename() + * + * DESCRIPTION + * + * Set mime part remote filename. + */ +CURL_EXTERN CURLcode curl_mime_filename(curl_mimepart *part, + const char *filename); + +/* + * NAME curl_mime_type() + * + * DESCRIPTION + * + * Set mime part type. + */ +CURL_EXTERN CURLcode curl_mime_type(curl_mimepart *part, const char *mimetype); + +/* + * NAME curl_mime_encoder() + * + * DESCRIPTION + * + * Set mime data transfer encoder. + */ +CURL_EXTERN CURLcode curl_mime_encoder(curl_mimepart *part, + const char *encoding); + +/* + * NAME curl_mime_data() + * + * DESCRIPTION + * + * Set mime part data source from memory data, + */ +CURL_EXTERN CURLcode curl_mime_data(curl_mimepart *part, + const char *data, size_t datasize); + +/* + * NAME curl_mime_filedata() + * + * DESCRIPTION + * + * Set mime part data source from named file. + */ +CURL_EXTERN CURLcode curl_mime_filedata(curl_mimepart *part, + const char *filename); + +/* + * NAME curl_mime_data_cb() + * + * DESCRIPTION + * + * Set mime part data source from callback function. + */ +CURL_EXTERN 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); + +/* + * NAME curl_mime_subparts() + * + * DESCRIPTION + * + * Set mime part data source from subparts. + */ +CURL_EXTERN CURLcode curl_mime_subparts(curl_mimepart *part, + curl_mime *subparts); +/* + * NAME curl_mime_headers() + * + * DESCRIPTION + * + * Set mime part headers. + */ +CURL_EXTERN CURLcode curl_mime_headers(curl_mimepart *part, + struct curl_slist *headers, + int take_ownership); typedef enum { - CFINIT(NOTHING), /********* the first one is unused ************/ - - /* */ - CFINIT(COPYNAME), - CFINIT(PTRNAME), - CFINIT(NAMELENGTH), - CFINIT(COPYCONTENTS), - CFINIT(PTRCONTENTS), - CFINIT(CONTENTSLENGTH), - CFINIT(FILECONTENT), - CFINIT(ARRAY), - CFINIT(OBSOLETE), - CFINIT(FILE), - - CFINIT(BUFFER), - CFINIT(BUFFERPTR), - CFINIT(BUFFERLENGTH), - - CFINIT(CONTENTTYPE), - CFINIT(CONTENTHEADER), - CFINIT(FILENAME), - CFINIT(END), - CFINIT(OBSOLETE2), - - CFINIT(STREAM), - CFINIT(CONTENTLEN), /* added in 7.46.0, provide a curl_off_t length */ + /********* the first one is unused ************/ + CURLFORM_NOTHING CURL_DEPRECATED(7.56.0, ""), + CURLFORM_COPYNAME CURL_DEPRECATED(7.56.0, "Use curl_mime_name()"), + CURLFORM_PTRNAME CURL_DEPRECATED(7.56.0, "Use curl_mime_name()"), + CURLFORM_NAMELENGTH CURL_DEPRECATED(7.56.0, ""), + CURLFORM_COPYCONTENTS CURL_DEPRECATED(7.56.0, "Use curl_mime_data()"), + CURLFORM_PTRCONTENTS CURL_DEPRECATED(7.56.0, "Use curl_mime_data()"), + CURLFORM_CONTENTSLENGTH CURL_DEPRECATED(7.56.0, "Use curl_mime_data()"), + CURLFORM_FILECONTENT CURL_DEPRECATED(7.56.0, "Use curl_mime_data_cb()"), + CURLFORM_ARRAY CURL_DEPRECATED(7.56.0, ""), + CURLFORM_OBSOLETE, + CURLFORM_FILE CURL_DEPRECATED(7.56.0, "Use curl_mime_filedata()"), + + CURLFORM_BUFFER CURL_DEPRECATED(7.56.0, "Use curl_mime_filename()"), + CURLFORM_BUFFERPTR CURL_DEPRECATED(7.56.0, "Use curl_mime_data()"), + CURLFORM_BUFFERLENGTH CURL_DEPRECATED(7.56.0, "Use curl_mime_data()"), + + CURLFORM_CONTENTTYPE CURL_DEPRECATED(7.56.0, "Use curl_mime_type()"), + CURLFORM_CONTENTHEADER CURL_DEPRECATED(7.56.0, "Use curl_mime_headers()"), + CURLFORM_FILENAME CURL_DEPRECATED(7.56.0, "Use curl_mime_filename()"), + CURLFORM_END, + CURLFORM_OBSOLETE2, + + CURLFORM_STREAM CURL_DEPRECATED(7.56.0, "Use curl_mime_data_cb()"), + CURLFORM_CONTENTLEN /* added in 7.46.0, provide a curl_off_t length */ + CURL_DEPRECATED(7.56.0, "Use curl_mime_data()"), CURLFORM_LASTENTRY /* the last unused */ } CURLformoption; -#undef CFINIT /* done */ - /* structure to be used as parameter for CURLFORM_ARRAY */ struct curl_forms { CURLformoption option; @@ -1989,15 +2568,16 @@ struct curl_forms { * ***************************************************************************/ typedef enum { - CURL_FORMADD_OK, /* first, no error */ + CURL_FORMADD_OK CURL_DEPRECATED(7.56.0, ""), /* 1st, no error */ - CURL_FORMADD_MEMORY, - CURL_FORMADD_OPTION_TWICE, - CURL_FORMADD_NULL, - CURL_FORMADD_UNKNOWN_OPTION, - CURL_FORMADD_INCOMPLETE, - CURL_FORMADD_ILLEGAL_ARRAY, - CURL_FORMADD_DISABLED, /* libcurl was built with this disabled */ + CURL_FORMADD_MEMORY CURL_DEPRECATED(7.56.0, ""), + CURL_FORMADD_OPTION_TWICE CURL_DEPRECATED(7.56.0, ""), + CURL_FORMADD_NULL CURL_DEPRECATED(7.56.0, ""), + CURL_FORMADD_UNKNOWN_OPTION CURL_DEPRECATED(7.56.0, ""), + CURL_FORMADD_INCOMPLETE CURL_DEPRECATED(7.56.0, ""), + CURL_FORMADD_ILLEGAL_ARRAY CURL_DEPRECATED(7.56.0, ""), + /* libcurl was built with form api disabled */ + CURL_FORMADD_DISABLED CURL_DEPRECATED(7.56.0, ""), CURL_FORMADD_LAST /* last */ } CURLFORMcode; @@ -2011,9 +2591,10 @@ typedef enum { * adds one part that together construct a full post. Then use * CURLOPT_HTTPPOST to send it off to libcurl. */ -CURL_EXTERN CURLFORMcode curl_formadd(struct curl_httppost **httppost, - struct curl_httppost **last_post, - ...); +CURL_EXTERN CURLFORMcode CURL_DEPRECATED(7.56.0, "Use curl_mime_init()") +curl_formadd(struct curl_httppost **httppost, + struct curl_httppost **last_post, + ...); /* * callback function for curl_formget() @@ -2036,8 +2617,9 @@ typedef size_t (*curl_formget_callback)(void *arg, const char *buf, * the curl_formget_callback function. * Returns 0 on success. */ -CURL_EXTERN int curl_formget(struct curl_httppost *form, void *arg, - curl_formget_callback append); +CURL_EXTERN int CURL_DEPRECATED(7.56.0, "") +curl_formget(struct curl_httppost *form, void *arg, + curl_formget_callback append); /* * NAME curl_formfree() * @@ -2045,7 +2627,8 @@ CURL_EXTERN int curl_formget(struct curl_httppost *form, void *arg, * * Free a multipart formpost previously built with curl_formadd(). */ -CURL_EXTERN void curl_formfree(struct curl_httppost *form); +CURL_EXTERN void CURL_DEPRECATED(7.56.0, "Use curl_mime_free()") +curl_formfree(struct curl_httppost *form); /* * NAME curl_getenv() @@ -2062,7 +2645,7 @@ CURL_EXTERN char *curl_getenv(const char *variable); * * DESCRIPTION * - * Returns a static ascii string of the libcurl version. + * Returns a static ASCII string of the libcurl version. */ CURL_EXTERN char *curl_version(void); @@ -2121,8 +2704,10 @@ CURL_EXTERN void curl_free(void *p); * * curl_global_init() should be invoked exactly once for each application that * uses libcurl and before any call of other libcurl functions. - * - * This function is not thread-safe! + + * This function is thread-safe if CURL_VERSION_THREADSAFE is set in the + * curl_version_info_data.features flag (fetch by curl_version_info()). + */ CURL_EXTERN CURLcode curl_global_init(long flags); @@ -2132,11 +2717,11 @@ CURL_EXTERN CURLcode curl_global_init(long flags); * DESCRIPTION * * curl_global_init() or curl_global_init_mem() should be invoked exactly once - * for each application that uses libcurl. This function can be used to + * for each application that uses libcurl. This function can be used to * initialize libcurl and set user defined memory management callback - * functions. Users can implement memory management routines to check for - * memory leaks, check for mis-use of the curl library etc. User registered - * callback routines with be invoked by this library instead of the system + * functions. Users can implement memory management routines to check for + * memory leaks, check for mis-use of the curl library etc. User registered + * callback routines will be invoked by this library instead of the system * memory management routines like malloc, free etc. */ CURL_EXTERN CURLcode curl_global_init_mem(long flags, @@ -2156,12 +2741,68 @@ CURL_EXTERN CURLcode curl_global_init_mem(long flags, */ CURL_EXTERN void curl_global_cleanup(void); +/* + * NAME curl_global_trace() + * + * DESCRIPTION + * + * curl_global_trace() can be invoked at application start to + * configure which components in curl should participate in tracing. + + * This function is thread-safe if CURL_VERSION_THREADSAFE is set in the + * curl_version_info_data.features flag (fetch by curl_version_info()). + + */ +CURL_EXTERN CURLcode curl_global_trace(const char *config); + /* linked-list structure for the CURLOPT_QUOTE option (and other) */ struct curl_slist { char *data; struct curl_slist *next; }; +/* + * NAME curl_global_sslset() + * + * DESCRIPTION + * + * When built with multiple SSL backends, curl_global_sslset() allows to + * choose one. This function can only be called once, and it must be called + * *before* curl_global_init(). + * + * The backend can be identified by the id (e.g. CURLSSLBACKEND_OPENSSL). The + * backend can also be specified via the name parameter (passing -1 as id). + * If both id and name are specified, the name will be ignored. If neither id + * nor name are specified, the function will fail with + * CURLSSLSET_UNKNOWN_BACKEND and set the "avail" pointer to the + * NULL-terminated list of available backends. + * + * Upon success, the function returns CURLSSLSET_OK. + * + * If the specified SSL backend is not available, the function returns + * CURLSSLSET_UNKNOWN_BACKEND and sets the "avail" pointer to a NULL-terminated + * list of available SSL backends. + * + * The SSL backend can be set only once. If it has already been set, a + * subsequent attempt to change it will result in a CURLSSLSET_TOO_LATE. + */ + +struct curl_ssl_backend { + curl_sslbackend id; + const char *name; +}; +typedef struct curl_ssl_backend curl_ssl_backend; + +typedef enum { + CURLSSLSET_OK = 0, + CURLSSLSET_UNKNOWN_BACKEND, + CURLSSLSET_TOO_LATE, + CURLSSLSET_NO_BACKENDS /* libcurl was built without any SSL support */ +} CURLsslset; + +CURL_EXTERN CURLsslset curl_global_sslset(curl_sslbackend id, const char *name, + const curl_ssl_backend ***avail); + /* * NAME curl_slist_append() * @@ -2170,8 +2811,8 @@ struct curl_slist { * Appends a string to a linked list. If no list exists, it will be created * first. Returns the new list, after appending. */ -CURL_EXTERN struct curl_slist *curl_slist_append(struct curl_slist *, - const char *); +CURL_EXTERN struct curl_slist *curl_slist_append(struct curl_slist *list, + const char *data); /* * NAME curl_slist_free_all() @@ -2180,7 +2821,7 @@ CURL_EXTERN struct curl_slist *curl_slist_append(struct curl_slist *, * * free a previously built curl_slist. */ -CURL_EXTERN void curl_slist_free_all(struct curl_slist *); +CURL_EXTERN void curl_slist_free_all(struct curl_slist *list); /* * NAME curl_getdate() @@ -2193,36 +2834,16 @@ CURL_EXTERN void curl_slist_free_all(struct curl_slist *); */ CURL_EXTERN time_t curl_getdate(const char *p, const time_t *unused); -/* info about the certificate chain, only for OpenSSL builds. Asked +/* info about the certificate chain, for SSL backends that support it. Asked for with CURLOPT_CERTINFO / CURLINFO_CERTINFO */ struct curl_certinfo { int num_of_certs; /* number of certificates with information */ - struct curl_slist **certinfo; /* for each index in this array, there's a - linked list with textual information in the - format "name: value" */ + struct curl_slist **certinfo; /* for each index in this array, there is a + linked list with textual information for a + certificate in the format "name:content". + eg "Subject:foo", "Issuer:bar", etc. */ }; -/* enum for the different supported SSL backends */ -typedef enum { - CURLSSLBACKEND_NONE = 0, - CURLSSLBACKEND_OPENSSL = 1, - CURLSSLBACKEND_GNUTLS = 2, - CURLSSLBACKEND_NSS = 3, - CURLSSLBACKEND_OBSOLETE4 = 4, /* Was QSOSSL. */ - CURLSSLBACKEND_GSKIT = 5, - CURLSSLBACKEND_POLARSSL = 6, - CURLSSLBACKEND_CYASSL = 7, - CURLSSLBACKEND_SCHANNEL = 8, - CURLSSLBACKEND_DARWINSSL = 9, - CURLSSLBACKEND_AXTLS = 10, - CURLSSLBACKEND_MBEDTLS = 11 -} curl_sslbackend; - -/* aliases for library clones and renames */ -#define CURLSSLBACKEND_LIBRESSL 1 -#define CURLSSLBACKEND_BORINGSSL 1 -#define CURLSSLBACKEND_WOLFSSL 6 - /* Information about the SSL library used and the respective internal SSL handle, which can be used to obtain further information regarding the connection. Asked for with CURLINFO_TLS_SSL_PTR or CURLINFO_TLS_SESSION. */ @@ -2235,7 +2856,9 @@ struct curl_tlssessioninfo { #define CURLINFO_LONG 0x200000 #define CURLINFO_DOUBLE 0x300000 #define CURLINFO_SLIST 0x400000 +#define CURLINFO_PTR 0x400000 /* same as SLIST */ #define CURLINFO_SOCKET 0x500000 +#define CURLINFO_OFF_T 0x600000 #define CURLINFO_MASK 0x0fffff #define CURLINFO_TYPEMASK 0xf00000 @@ -2247,16 +2870,36 @@ typedef enum { CURLINFO_NAMELOOKUP_TIME = CURLINFO_DOUBLE + 4, CURLINFO_CONNECT_TIME = CURLINFO_DOUBLE + 5, CURLINFO_PRETRANSFER_TIME = CURLINFO_DOUBLE + 6, - CURLINFO_SIZE_UPLOAD = CURLINFO_DOUBLE + 7, - CURLINFO_SIZE_DOWNLOAD = CURLINFO_DOUBLE + 8, - CURLINFO_SPEED_DOWNLOAD = CURLINFO_DOUBLE + 9, - CURLINFO_SPEED_UPLOAD = CURLINFO_DOUBLE + 10, + CURLINFO_SIZE_UPLOAD CURL_DEPRECATED(7.55.0, "Use CURLINFO_SIZE_UPLOAD_T") + = CURLINFO_DOUBLE + 7, + CURLINFO_SIZE_UPLOAD_T = CURLINFO_OFF_T + 7, + CURLINFO_SIZE_DOWNLOAD + CURL_DEPRECATED(7.55.0, "Use CURLINFO_SIZE_DOWNLOAD_T") + = CURLINFO_DOUBLE + 8, + CURLINFO_SIZE_DOWNLOAD_T = CURLINFO_OFF_T + 8, + CURLINFO_SPEED_DOWNLOAD + CURL_DEPRECATED(7.55.0, "Use CURLINFO_SPEED_DOWNLOAD_T") + = CURLINFO_DOUBLE + 9, + CURLINFO_SPEED_DOWNLOAD_T = CURLINFO_OFF_T + 9, + CURLINFO_SPEED_UPLOAD + CURL_DEPRECATED(7.55.0, "Use CURLINFO_SPEED_UPLOAD_T") + = CURLINFO_DOUBLE + 10, + CURLINFO_SPEED_UPLOAD_T = CURLINFO_OFF_T + 10, CURLINFO_HEADER_SIZE = CURLINFO_LONG + 11, CURLINFO_REQUEST_SIZE = CURLINFO_LONG + 12, CURLINFO_SSL_VERIFYRESULT = CURLINFO_LONG + 13, CURLINFO_FILETIME = CURLINFO_LONG + 14, - CURLINFO_CONTENT_LENGTH_DOWNLOAD = CURLINFO_DOUBLE + 15, - CURLINFO_CONTENT_LENGTH_UPLOAD = CURLINFO_DOUBLE + 16, + CURLINFO_FILETIME_T = CURLINFO_OFF_T + 14, + CURLINFO_CONTENT_LENGTH_DOWNLOAD + CURL_DEPRECATED(7.55.0, + "Use CURLINFO_CONTENT_LENGTH_DOWNLOAD_T") + = CURLINFO_DOUBLE + 15, + CURLINFO_CONTENT_LENGTH_DOWNLOAD_T = CURLINFO_OFF_T + 15, + CURLINFO_CONTENT_LENGTH_UPLOAD + CURL_DEPRECATED(7.55.0, + "Use CURLINFO_CONTENT_LENGTH_UPLOAD_T") + = CURLINFO_DOUBLE + 16, + CURLINFO_CONTENT_LENGTH_UPLOAD_T = CURLINFO_OFF_T + 16, CURLINFO_STARTTRANSFER_TIME = CURLINFO_DOUBLE + 17, CURLINFO_CONTENT_TYPE = CURLINFO_STRING + 18, CURLINFO_REDIRECT_TIME = CURLINFO_DOUBLE + 19, @@ -2269,12 +2912,13 @@ typedef enum { CURLINFO_NUM_CONNECTS = CURLINFO_LONG + 26, CURLINFO_SSL_ENGINES = CURLINFO_SLIST + 27, CURLINFO_COOKIELIST = CURLINFO_SLIST + 28, - CURLINFO_LASTSOCKET = CURLINFO_LONG + 29, + CURLINFO_LASTSOCKET CURL_DEPRECATED(7.45.0, "Use CURLINFO_ACTIVESOCKET") + = CURLINFO_LONG + 29, CURLINFO_FTP_ENTRY_PATH = CURLINFO_STRING + 30, CURLINFO_REDIRECT_URL = CURLINFO_STRING + 31, CURLINFO_PRIMARY_IP = CURLINFO_STRING + 32, CURLINFO_APPCONNECT_TIME = CURLINFO_DOUBLE + 33, - CURLINFO_CERTINFO = CURLINFO_SLIST + 34, + CURLINFO_CERTINFO = CURLINFO_PTR + 34, CURLINFO_CONDITION_UNMET = CURLINFO_LONG + 35, CURLINFO_RTSP_SESSION_ID = CURLINFO_STRING + 36, CURLINFO_RTSP_CLIENT_CSEQ = CURLINFO_LONG + 37, @@ -2283,16 +2927,34 @@ typedef enum { CURLINFO_PRIMARY_PORT = CURLINFO_LONG + 40, CURLINFO_LOCAL_IP = CURLINFO_STRING + 41, CURLINFO_LOCAL_PORT = CURLINFO_LONG + 42, - CURLINFO_TLS_SESSION = CURLINFO_SLIST + 43, + CURLINFO_TLS_SESSION CURL_DEPRECATED(7.48.0, "Use CURLINFO_TLS_SSL_PTR") + = CURLINFO_PTR + 43, CURLINFO_ACTIVESOCKET = CURLINFO_SOCKET + 44, - CURLINFO_TLS_SSL_PTR = CURLINFO_SLIST + 45, + CURLINFO_TLS_SSL_PTR = CURLINFO_PTR + 45, CURLINFO_HTTP_VERSION = CURLINFO_LONG + 46, CURLINFO_PROXY_SSL_VERIFYRESULT = CURLINFO_LONG + 47, - CURLINFO_PROTOCOL = CURLINFO_LONG + 48, + CURLINFO_PROTOCOL CURL_DEPRECATED(7.85.0, "Use CURLINFO_SCHEME") + = CURLINFO_LONG + 48, CURLINFO_SCHEME = CURLINFO_STRING + 49, - /* Fill in new entries below here! */ - - CURLINFO_LASTONE = 49 + CURLINFO_TOTAL_TIME_T = CURLINFO_OFF_T + 50, + CURLINFO_NAMELOOKUP_TIME_T = CURLINFO_OFF_T + 51, + CURLINFO_CONNECT_TIME_T = CURLINFO_OFF_T + 52, + CURLINFO_PRETRANSFER_TIME_T = CURLINFO_OFF_T + 53, + CURLINFO_STARTTRANSFER_TIME_T = CURLINFO_OFF_T + 54, + CURLINFO_REDIRECT_TIME_T = CURLINFO_OFF_T + 55, + CURLINFO_APPCONNECT_TIME_T = CURLINFO_OFF_T + 56, + CURLINFO_RETRY_AFTER = CURLINFO_OFF_T + 57, + CURLINFO_EFFECTIVE_METHOD = CURLINFO_STRING + 58, + CURLINFO_PROXY_ERROR = CURLINFO_LONG + 59, + CURLINFO_REFERER = CURLINFO_STRING + 60, + CURLINFO_CAINFO = CURLINFO_STRING + 61, + CURLINFO_CAPATH = CURLINFO_STRING + 62, + CURLINFO_XFER_ID = CURLINFO_OFF_T + 63, + CURLINFO_CONN_ID = CURLINFO_OFF_T + 64, + CURLINFO_QUEUE_TIME_T = CURLINFO_OFF_T + 65, + CURLINFO_USED_PROXY = CURLINFO_LONG + 66, + CURLINFO_POSTTRANSFER_TIME_T = CURLINFO_OFF_T + 67, + CURLINFO_LASTONE = 67 } CURLINFO; /* CURLINFO_RESPONSE_CODE is the new name for the option previously known as @@ -2311,7 +2973,7 @@ typedef enum { CURLCLOSEPOLICY_LAST /* last, never use this */ } curl_closepolicy; -#define CURL_GLOBAL_SSL (1<<0) +#define CURL_GLOBAL_SSL (1<<0) /* no purpose since 7.57.0 */ #define CURL_GLOBAL_WIN32 (1<<1) #define CURL_GLOBAL_ALL (CURL_GLOBAL_SSL|CURL_GLOBAL_WIN32) #define CURL_GLOBAL_NOTHING 0 @@ -2335,6 +2997,8 @@ typedef enum { CURL_LOCK_DATA_DNS, CURL_LOCK_DATA_SSL_SESSION, CURL_LOCK_DATA_CONNECT, + CURL_LOCK_DATA_PSL, + CURL_LOCK_DATA_HSTS, CURL_LOCK_DATA_LAST } curl_lock_data; @@ -2366,7 +3030,7 @@ typedef enum { } CURLSHcode; typedef enum { - CURLSHOPT_NONE, /* don't use */ + CURLSHOPT_NONE, /* do not use */ CURLSHOPT_SHARE, /* specify a data type to share */ CURLSHOPT_UNSHARE, /* specify which data type to stop sharing */ CURLSHOPT_LOCKFUNC, /* pass in a 'curl_lock_function' pointer */ @@ -2377,18 +3041,27 @@ typedef enum { } CURLSHoption; CURL_EXTERN CURLSH *curl_share_init(void); -CURL_EXTERN CURLSHcode curl_share_setopt(CURLSH *, CURLSHoption option, ...); -CURL_EXTERN CURLSHcode curl_share_cleanup(CURLSH *); +CURL_EXTERN CURLSHcode curl_share_setopt(CURLSH *share, CURLSHoption option, + ...); +CURL_EXTERN CURLSHcode curl_share_cleanup(CURLSH *share); /**************************************************************************** * Structures for querying information about the curl library at runtime. */ typedef enum { - CURLVERSION_FIRST, - CURLVERSION_SECOND, - CURLVERSION_THIRD, - CURLVERSION_FOURTH, + CURLVERSION_FIRST, /* 7.10 */ + CURLVERSION_SECOND, /* 7.11.1 */ + CURLVERSION_THIRD, /* 7.12.0 */ + CURLVERSION_FOURTH, /* 7.16.1 */ + CURLVERSION_FIFTH, /* 7.57.0 */ + CURLVERSION_SIXTH, /* 7.66.0 */ + CURLVERSION_SEVENTH, /* 7.70.0 */ + CURLVERSION_EIGHTH, /* 7.72.0 */ + CURLVERSION_NINTH, /* 7.75.0 */ + CURLVERSION_TENTH, /* 7.77.0 */ + CURLVERSION_ELEVENTH, /* 7.87.0 */ + CURLVERSION_TWELFTH, /* 8.8.0 */ CURLVERSION_LAST /* never actually use this */ } CURLversion; @@ -2397,9 +3070,9 @@ typedef enum { meant to be a built-in version number for what kind of struct the caller expects. If the struct ever changes, we redefine the NOW to another enum from above. */ -#define CURLVERSION_NOW CURLVERSION_FOURTH +#define CURLVERSION_NOW CURLVERSION_TWELFTH -typedef struct { +struct curl_version_info_data { CURLversion age; /* age of the returned struct */ const char *version; /* LIBCURL_VERSION */ unsigned int version_num; /* LIBCURL_VERSION_NUM */ @@ -2425,7 +3098,43 @@ typedef struct { const char *libssh_version; /* human readable string */ -} curl_version_info_data; + /* These fields were added in CURLVERSION_FIFTH */ + unsigned int brotli_ver_num; /* Numeric Brotli version + (MAJOR << 24) | (MINOR << 12) | PATCH */ + const char *brotli_version; /* human readable string. */ + + /* These fields were added in CURLVERSION_SIXTH */ + unsigned int nghttp2_ver_num; /* Numeric nghttp2 version + (MAJOR << 16) | (MINOR << 8) | PATCH */ + const char *nghttp2_version; /* human readable string. */ + const char *quic_version; /* human readable quic (+ HTTP/3) library + + version or NULL */ + + /* These fields were added in CURLVERSION_SEVENTH */ + const char *cainfo; /* the built-in default CURLOPT_CAINFO, might + be NULL */ + const char *capath; /* the built-in default CURLOPT_CAPATH, might + be NULL */ + + /* These fields were added in CURLVERSION_EIGHTH */ + unsigned int zstd_ver_num; /* Numeric Zstd version + (MAJOR << 24) | (MINOR << 12) | PATCH */ + const char *zstd_version; /* human readable string. */ + + /* These fields were added in CURLVERSION_NINTH */ + const char *hyper_version; /* human readable string. */ + + /* These fields were added in CURLVERSION_TENTH */ + const char *gsasl_version; /* human readable string. */ + + /* These fields were added in CURLVERSION_ELEVENTH */ + /* feature_names is terminated by an entry with a NULL feature name */ + const char * const *feature_names; + + /* These fields were added in CURLVERSION_TWELFTH */ + const char *rtmp_version; /* human readable string. */ +}; +typedef struct curl_version_info_data curl_version_info_data; #define CURL_VERSION_IPV6 (1<<0) /* IPv6-enabled */ #define CURL_VERSION_KERBEROS4 (1<<1) /* Kerberos V4 auth is supported @@ -2446,7 +3155,7 @@ typedef struct { #define CURL_VERSION_CURLDEBUG (1<<13) /* Debug memory tracking supported */ #define CURL_VERSION_TLSAUTH_SRP (1<<14) /* TLS-SRP auth is supported */ #define CURL_VERSION_NTLM_WB (1<<15) /* NTLM delegation to winbind helper - is suported */ + is supported */ #define CURL_VERSION_HTTP2 (1<<16) /* HTTP2 support built-in */ #define CURL_VERSION_GSSAPI (1<<17) /* Built against a GSS-API library */ #define CURL_VERSION_KERBEROS5 (1<<18) /* Kerberos V5 auth is supported */ @@ -2454,8 +3163,17 @@ typedef struct { #define CURL_VERSION_PSL (1<<20) /* Mozilla's Public Suffix List, used for cookie domain verification */ #define CURL_VERSION_HTTPS_PROXY (1<<21) /* HTTPS-proxy support built-in */ +#define CURL_VERSION_MULTI_SSL (1<<22) /* Multiple SSL backends available */ +#define CURL_VERSION_BROTLI (1<<23) /* Brotli features are present. */ +#define CURL_VERSION_ALTSVC (1<<24) /* Alt-Svc handling built-in */ +#define CURL_VERSION_HTTP3 (1<<25) /* HTTP3 support built-in */ +#define CURL_VERSION_ZSTD (1<<26) /* zstd features are present */ +#define CURL_VERSION_UNICODE (1<<27) /* Unicode support on Windows */ +#define CURL_VERSION_HSTS (1<<28) /* HSTS is supported */ +#define CURL_VERSION_GSASL (1<<29) /* libgsasl is supported */ +#define CURL_VERSION_THREADSAFE (1<<30) /* libcurl API is thread-safe */ - /* +/* * NAME curl_version_info() * * DESCRIPTION @@ -2471,7 +3189,7 @@ CURL_EXTERN curl_version_info_data *curl_version_info(CURLversion); * DESCRIPTION * * The curl_easy_strerror function may be used to turn a CURLcode value - * into the equivalent human readable error string. This is useful + * into the equivalent human readable error string. This is useful * for printing meaningful error messages. */ CURL_EXTERN const char *curl_easy_strerror(CURLcode); @@ -2482,7 +3200,7 @@ CURL_EXTERN const char *curl_easy_strerror(CURLcode); * DESCRIPTION * * The curl_share_strerror function may be used to turn a CURLSHcode value - * into the equivalent human readable error string. This is useful + * into the equivalent human readable error string. This is useful * for printing meaningful error messages. */ CURL_EXTERN const char *curl_share_strerror(CURLSHcode); @@ -2508,15 +3226,22 @@ CURL_EXTERN CURLcode curl_easy_pause(CURL *handle, int bitmask); #define CURLPAUSE_CONT (CURLPAUSE_RECV_CONT|CURLPAUSE_SEND_CONT) #ifdef __cplusplus -} +} /* end of extern "C" */ #endif /* unfortunately, the easy.h and multi.h include files need options and info stuff before they can be included! */ #include "easy.h" /* nothing in curl is fun without the easy stuff */ #include "multi.h" +#include "urlapi.h" +#include "options.h" +#include "header.h" +#include "websockets.h" +#ifndef CURL_SKIP_INCLUDE_MPRINTF +#include "mprintf.h" +#endif -/* the typechecker doesn't work in C++ (yet) */ +/* the typechecker does not work in C++ (yet) */ #if defined(__GNUC__) && defined(__GNUC_MINOR__) && \ ((__GNUC__ > 4) || (__GNUC__ == 4 && __GNUC_MINOR__ >= 3)) && \ !defined(__cplusplus) && !defined(CURL_DISABLE_TYPECHECK) @@ -2531,6 +3256,6 @@ CURL_EXTERN CURLcode curl_easy_pause(CURL *handle, int bitmask); #define curl_share_setopt(share,opt,param) curl_share_setopt(share,opt,param) #define curl_multi_setopt(handle,opt,param) curl_multi_setopt(handle,opt,param) #endif /* __STDC__ >= 1 */ -#endif /* gcc >= 4.3 && !__cplusplus */ +#endif /* gcc >= 4.3 && !__cplusplus && !CURL_DISABLE_TYPECHECK */ -#endif /* __CURL_CURL_H */ +#endif /* CURLINC_CURL_H */ diff --git a/contrib/curl/include/curl/curlbuild.h b/contrib/curl/include/curl/curlbuild.h deleted file mode 100644 index ceae71adac..0000000000 --- a/contrib/curl/include/curl/curlbuild.h +++ /dev/null @@ -1,602 +0,0 @@ -#ifndef __CURL_CURLBUILD_H -#define __CURL_CURLBUILD_H -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) 1998 - 2016, Daniel Stenberg, , et al. - * - * This software is licensed as described in the file COPYING, which - * you should have received as part of this distribution. The terms - * are also available at https://curl.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. - * - ***************************************************************************/ - -/* ================================================================ */ -/* NOTES FOR CONFIGURE CAPABLE SYSTEMS */ -/* ================================================================ */ - -/* - * NOTE 1: - * ------- - * - * See file include/curl/curlbuild.h.in, run configure, and forget - * that this file exists it is only used for non-configure systems. - * But you can keep reading if you want ;-) - * - */ - -/* ================================================================ */ -/* NOTES FOR NON-CONFIGURE SYSTEMS */ -/* ================================================================ */ - -/* - * NOTE 1: - * ------- - * - * Nothing in this file is intended to be modified or adjusted by the - * curl library user nor by the curl library builder. - * - * If you think that something actually needs to be changed, adjusted - * or fixed in this file, then, report it on the libcurl development - * mailing list: https://cool.haxx.se/mailman/listinfo/curl-library/ - * - * Try to keep one section per platform, compiler and architecture, - * otherwise, if an existing section is reused for a different one and - * later on the original is adjusted, probably the piggybacking one can - * be adversely changed. - * - * In order to differentiate between platforms/compilers/architectures - * use only compiler built in predefined preprocessor symbols. - * - * This header file shall only export symbols which are 'curl' or 'CURL' - * prefixed, otherwise public name space would be polluted. - * - * NOTE 2: - * ------- - * - * For any given platform/compiler curl_off_t must be typedef'ed to a - * 64-bit wide signed integral data type. The width of this data type - * must remain constant and independent of any possible large file - * support settings. - * - * As an exception to the above, curl_off_t shall be typedef'ed to a - * 32-bit wide signed integral data type if there is no 64-bit type. - * - * As a general rule, curl_off_t shall not be mapped to off_t. This - * rule shall only be violated if off_t is the only 64-bit data type - * available and the size of off_t is independent of large file support - * settings. Keep your build on the safe side avoiding an off_t gating. - * If you have a 64-bit off_t then take for sure that another 64-bit - * data type exists, dig deeper and you will find it. - * - * NOTE 3: - * ------- - * - * Right now you might be staring at file include/curl/curlbuild.h.dist or - * at file include/curl/curlbuild.h, this is due to the following reason: - * file include/curl/curlbuild.h.dist is renamed to include/curl/curlbuild.h - * when the libcurl source code distribution archive file is created. - * - * File include/curl/curlbuild.h.dist is not included in the distribution - * archive. File include/curl/curlbuild.h is not present in the git tree. - * - * The distributed include/curl/curlbuild.h file is only intended to be used - * on systems which can not run the also distributed configure script. - * - * On systems capable of running the configure script, the configure process - * will overwrite the distributed include/curl/curlbuild.h file with one that - * is suitable and specific to the library being configured and built, which - * is generated from the include/curl/curlbuild.h.in template file. - * - * If you check out from git on a non-configure platform, you must run the - * appropriate buildconf* script to set up curlbuild.h and other local files. - * - */ - -/* ================================================================ */ -/* DEFINITION OF THESE SYMBOLS SHALL NOT TAKE PLACE ANYWHERE ELSE */ -/* ================================================================ */ - -#ifdef CURL_SIZEOF_LONG -# error "CURL_SIZEOF_LONG shall not be defined except in curlbuild.h" - Error Compilation_aborted_CURL_SIZEOF_LONG_already_defined -#endif - -#ifdef CURL_TYPEOF_CURL_SOCKLEN_T -# error "CURL_TYPEOF_CURL_SOCKLEN_T shall not be defined except in curlbuild.h" - Error Compilation_aborted_CURL_TYPEOF_CURL_SOCKLEN_T_already_defined -#endif - -#ifdef CURL_SIZEOF_CURL_SOCKLEN_T -# error "CURL_SIZEOF_CURL_SOCKLEN_T shall not be defined except in curlbuild.h" - Error Compilation_aborted_CURL_SIZEOF_CURL_SOCKLEN_T_already_defined -#endif - -#ifdef CURL_TYPEOF_CURL_OFF_T -# error "CURL_TYPEOF_CURL_OFF_T shall not be defined except in curlbuild.h" - Error Compilation_aborted_CURL_TYPEOF_CURL_OFF_T_already_defined -#endif - -#ifdef CURL_FORMAT_CURL_OFF_T -# error "CURL_FORMAT_CURL_OFF_T shall not be defined except in curlbuild.h" - Error Compilation_aborted_CURL_FORMAT_CURL_OFF_T_already_defined -#endif - -#ifdef CURL_FORMAT_CURL_OFF_TU -# error "CURL_FORMAT_CURL_OFF_TU shall not be defined except in curlbuild.h" - Error Compilation_aborted_CURL_FORMAT_CURL_OFF_TU_already_defined -#endif - -#ifdef CURL_FORMAT_OFF_T -# error "CURL_FORMAT_OFF_T shall not be defined except in curlbuild.h" - Error Compilation_aborted_CURL_FORMAT_OFF_T_already_defined -#endif - -#ifdef CURL_SIZEOF_CURL_OFF_T -# error "CURL_SIZEOF_CURL_OFF_T shall not be defined except in curlbuild.h" - Error Compilation_aborted_CURL_SIZEOF_CURL_OFF_T_already_defined -#endif - -#ifdef CURL_SUFFIX_CURL_OFF_T -# error "CURL_SUFFIX_CURL_OFF_T shall not be defined except in curlbuild.h" - Error Compilation_aborted_CURL_SUFFIX_CURL_OFF_T_already_defined -#endif - -#ifdef CURL_SUFFIX_CURL_OFF_TU -# error "CURL_SUFFIX_CURL_OFF_TU shall not be defined except in curlbuild.h" - Error Compilation_aborted_CURL_SUFFIX_CURL_OFF_TU_already_defined -#endif - -/* ================================================================ */ -/* EXTERNAL INTERFACE SETTINGS FOR NON-CONFIGURE SYSTEMS ONLY */ -/* ================================================================ */ - -#if defined(__DJGPP__) || defined(__GO32__) -# if defined(__DJGPP__) && (__DJGPP__ > 1) -# define CURL_SIZEOF_LONG 4 -# define CURL_TYPEOF_CURL_OFF_T long long -# define CURL_FORMAT_CURL_OFF_T "lld" -# define CURL_FORMAT_CURL_OFF_TU "llu" -# define CURL_FORMAT_OFF_T "%lld" -# define CURL_SIZEOF_CURL_OFF_T 8 -# define CURL_SUFFIX_CURL_OFF_T LL -# define CURL_SUFFIX_CURL_OFF_TU ULL -# else -# define CURL_SIZEOF_LONG 4 -# define CURL_TYPEOF_CURL_OFF_T long -# define CURL_FORMAT_CURL_OFF_T "ld" -# define CURL_FORMAT_CURL_OFF_TU "lu" -# define CURL_FORMAT_OFF_T "%ld" -# define CURL_SIZEOF_CURL_OFF_T 4 -# define CURL_SUFFIX_CURL_OFF_T L -# define CURL_SUFFIX_CURL_OFF_TU UL -# endif -# define CURL_TYPEOF_CURL_SOCKLEN_T int -# define CURL_SIZEOF_CURL_SOCKLEN_T 4 - -#elif defined(__SALFORDC__) -# define CURL_SIZEOF_LONG 4 -# define CURL_TYPEOF_CURL_OFF_T long -# define CURL_FORMAT_CURL_OFF_T "ld" -# define CURL_FORMAT_CURL_OFF_TU "lu" -# define CURL_FORMAT_OFF_T "%ld" -# define CURL_SIZEOF_CURL_OFF_T 4 -# define CURL_SUFFIX_CURL_OFF_T L -# define CURL_SUFFIX_CURL_OFF_TU UL -# define CURL_TYPEOF_CURL_SOCKLEN_T int -# define CURL_SIZEOF_CURL_SOCKLEN_T 4 - -#elif defined(__BORLANDC__) -# if (__BORLANDC__ < 0x520) -# define CURL_SIZEOF_LONG 4 -# define CURL_TYPEOF_CURL_OFF_T long -# define CURL_FORMAT_CURL_OFF_T "ld" -# define CURL_FORMAT_CURL_OFF_TU "lu" -# define CURL_FORMAT_OFF_T "%ld" -# define CURL_SIZEOF_CURL_OFF_T 4 -# define CURL_SUFFIX_CURL_OFF_T L -# define CURL_SUFFIX_CURL_OFF_TU UL -# else -# define CURL_SIZEOF_LONG 4 -# define CURL_TYPEOF_CURL_OFF_T __int64 -# define CURL_FORMAT_CURL_OFF_T "I64d" -# define CURL_FORMAT_CURL_OFF_TU "I64u" -# define CURL_FORMAT_OFF_T "%I64d" -# define CURL_SIZEOF_CURL_OFF_T 8 -# define CURL_SUFFIX_CURL_OFF_T i64 -# define CURL_SUFFIX_CURL_OFF_TU ui64 -# endif -# define CURL_TYPEOF_CURL_SOCKLEN_T int -# define CURL_SIZEOF_CURL_SOCKLEN_T 4 - -#elif defined(__TURBOC__) -# define CURL_SIZEOF_LONG 4 -# define CURL_TYPEOF_CURL_OFF_T long -# define CURL_FORMAT_CURL_OFF_T "ld" -# define CURL_FORMAT_CURL_OFF_TU "lu" -# define CURL_FORMAT_OFF_T "%ld" -# define CURL_SIZEOF_CURL_OFF_T 4 -# define CURL_SUFFIX_CURL_OFF_T L -# define CURL_SUFFIX_CURL_OFF_TU UL -# define CURL_TYPEOF_CURL_SOCKLEN_T int -# define CURL_SIZEOF_CURL_SOCKLEN_T 4 - -#elif defined(__WATCOMC__) -# if defined(__386__) -# define CURL_SIZEOF_LONG 4 -# define CURL_TYPEOF_CURL_OFF_T __int64 -# define CURL_FORMAT_CURL_OFF_T "I64d" -# define CURL_FORMAT_CURL_OFF_TU "I64u" -# define CURL_FORMAT_OFF_T "%I64d" -# define CURL_SIZEOF_CURL_OFF_T 8 -# define CURL_SUFFIX_CURL_OFF_T i64 -# define CURL_SUFFIX_CURL_OFF_TU ui64 -# else -# define CURL_SIZEOF_LONG 4 -# define CURL_TYPEOF_CURL_OFF_T long -# define CURL_FORMAT_CURL_OFF_T "ld" -# define CURL_FORMAT_CURL_OFF_TU "lu" -# define CURL_FORMAT_OFF_T "%ld" -# define CURL_SIZEOF_CURL_OFF_T 4 -# define CURL_SUFFIX_CURL_OFF_T L -# define CURL_SUFFIX_CURL_OFF_TU UL -# endif -# define CURL_TYPEOF_CURL_SOCKLEN_T int -# define CURL_SIZEOF_CURL_SOCKLEN_T 4 - -#elif defined(__POCC__) -# if (__POCC__ < 280) -# define CURL_SIZEOF_LONG 4 -# define CURL_TYPEOF_CURL_OFF_T long -# define CURL_FORMAT_CURL_OFF_T "ld" -# define CURL_FORMAT_CURL_OFF_TU "lu" -# define CURL_FORMAT_OFF_T "%ld" -# define CURL_SIZEOF_CURL_OFF_T 4 -# define CURL_SUFFIX_CURL_OFF_T L -# define CURL_SUFFIX_CURL_OFF_TU UL -# elif defined(_MSC_VER) -# define CURL_SIZEOF_LONG 4 -# define CURL_TYPEOF_CURL_OFF_T __int64 -# define CURL_FORMAT_CURL_OFF_T "I64d" -# define CURL_FORMAT_CURL_OFF_TU "I64u" -# define CURL_FORMAT_OFF_T "%I64d" -# define CURL_SIZEOF_CURL_OFF_T 8 -# define CURL_SUFFIX_CURL_OFF_T i64 -# define CURL_SUFFIX_CURL_OFF_TU ui64 -# else -# define CURL_SIZEOF_LONG 4 -# define CURL_TYPEOF_CURL_OFF_T long long -# define CURL_FORMAT_CURL_OFF_T "lld" -# define CURL_FORMAT_CURL_OFF_TU "llu" -# define CURL_FORMAT_OFF_T "%lld" -# define CURL_SIZEOF_CURL_OFF_T 8 -# define CURL_SUFFIX_CURL_OFF_T LL -# define CURL_SUFFIX_CURL_OFF_TU ULL -# endif -# define CURL_TYPEOF_CURL_SOCKLEN_T int -# define CURL_SIZEOF_CURL_SOCKLEN_T 4 - -#elif defined(__LCC__) -# if defined(__MCST__) /* MCST eLbrus Compiler Collection */ -# define CURL_SIZEOF_LONG 8 -# define CURL_TYPEOF_CURL_OFF_T long -# define CURL_FORMAT_CURL_OFF_T "ld" -# define CURL_FORMAT_CURL_OFF_TU "lu" -# define CURL_FORMAT_OFF_T "%ld" -# define CURL_SIZEOF_CURL_OFF_T 8 -# define CURL_SUFFIX_CURL_OFF_T L -# define CURL_SUFFIX_CURL_OFF_TU UL -# define CURL_TYPEOF_CURL_SOCKLEN_T socklen_t -# define CURL_SIZEOF_CURL_SOCKLEN_T 4 -# define CURL_PULL_SYS_TYPES_H 1 -# define CURL_PULL_SYS_SOCKET_H 1 -# else /* Local (or Little) C Compiler */ -# define CURL_SIZEOF_LONG 4 -# define CURL_TYPEOF_CURL_OFF_T long -# define CURL_FORMAT_CURL_OFF_T "ld" -# define CURL_FORMAT_CURL_OFF_TU "lu" -# define CURL_FORMAT_OFF_T "%ld" -# define CURL_SIZEOF_CURL_OFF_T 4 -# define CURL_SUFFIX_CURL_OFF_T L -# define CURL_SUFFIX_CURL_OFF_TU UL -# define CURL_TYPEOF_CURL_SOCKLEN_T int -# define CURL_SIZEOF_CURL_SOCKLEN_T 4 -# endif - -#elif defined(__SYMBIAN32__) -# if defined(__EABI__) /* Treat all ARM compilers equally */ -# define CURL_SIZEOF_LONG 4 -# define CURL_TYPEOF_CURL_OFF_T long long -# define CURL_FORMAT_CURL_OFF_T "lld" -# define CURL_FORMAT_CURL_OFF_TU "llu" -# define CURL_FORMAT_OFF_T "%lld" -# define CURL_SIZEOF_CURL_OFF_T 8 -# define CURL_SUFFIX_CURL_OFF_T LL -# define CURL_SUFFIX_CURL_OFF_TU ULL -# elif defined(__CW32__) -# pragma longlong on -# define CURL_SIZEOF_LONG 4 -# define CURL_TYPEOF_CURL_OFF_T long long -# define CURL_FORMAT_CURL_OFF_T "lld" -# define CURL_FORMAT_CURL_OFF_TU "llu" -# define CURL_FORMAT_OFF_T "%lld" -# define CURL_SIZEOF_CURL_OFF_T 8 -# define CURL_SUFFIX_CURL_OFF_T LL -# define CURL_SUFFIX_CURL_OFF_TU ULL -# elif defined(__VC32__) -# define CURL_SIZEOF_LONG 4 -# define CURL_TYPEOF_CURL_OFF_T __int64 -# define CURL_FORMAT_CURL_OFF_T "lld" -# define CURL_FORMAT_CURL_OFF_TU "llu" -# define CURL_FORMAT_OFF_T "%lld" -# define CURL_SIZEOF_CURL_OFF_T 8 -# define CURL_SUFFIX_CURL_OFF_T LL -# define CURL_SUFFIX_CURL_OFF_TU ULL -# endif -# define CURL_TYPEOF_CURL_SOCKLEN_T unsigned int -# define CURL_SIZEOF_CURL_SOCKLEN_T 4 - -#elif defined(__MWERKS__) -# define CURL_SIZEOF_LONG 4 -# define CURL_TYPEOF_CURL_OFF_T long long -# define CURL_FORMAT_CURL_OFF_T "lld" -# define CURL_FORMAT_CURL_OFF_TU "llu" -# define CURL_FORMAT_OFF_T "%lld" -# define CURL_SIZEOF_CURL_OFF_T 8 -# define CURL_SUFFIX_CURL_OFF_T LL -# define CURL_SUFFIX_CURL_OFF_TU ULL -# define CURL_TYPEOF_CURL_SOCKLEN_T int -# define CURL_SIZEOF_CURL_SOCKLEN_T 4 - -#elif defined(_WIN32_WCE) -# define CURL_SIZEOF_LONG 4 -# define CURL_TYPEOF_CURL_OFF_T __int64 -# define CURL_FORMAT_CURL_OFF_T "I64d" -# define CURL_FORMAT_CURL_OFF_TU "I64u" -# define CURL_FORMAT_OFF_T "%I64d" -# define CURL_SIZEOF_CURL_OFF_T 8 -# define CURL_SUFFIX_CURL_OFF_T i64 -# define CURL_SUFFIX_CURL_OFF_TU ui64 -# define CURL_TYPEOF_CURL_SOCKLEN_T int -# define CURL_SIZEOF_CURL_SOCKLEN_T 4 - -#elif defined(__MINGW32__) -# define CURL_SIZEOF_LONG 4 -# define CURL_TYPEOF_CURL_OFF_T long long -# define CURL_FORMAT_CURL_OFF_T "I64d" -# define CURL_FORMAT_CURL_OFF_TU "I64u" -# define CURL_FORMAT_OFF_T "%I64d" -# define CURL_SIZEOF_CURL_OFF_T 8 -# define CURL_SUFFIX_CURL_OFF_T LL -# define CURL_SUFFIX_CURL_OFF_TU ULL -# define CURL_TYPEOF_CURL_SOCKLEN_T int -# define CURL_SIZEOF_CURL_SOCKLEN_T 4 - -#elif defined(__VMS) -# if defined(__VAX) -# define CURL_SIZEOF_LONG 4 -# define CURL_TYPEOF_CURL_OFF_T long -# define CURL_FORMAT_CURL_OFF_T "ld" -# define CURL_FORMAT_CURL_OFF_TU "lu" -# define CURL_FORMAT_OFF_T "%ld" -# define CURL_SIZEOF_CURL_OFF_T 4 -# define CURL_SUFFIX_CURL_OFF_T L -# define CURL_SUFFIX_CURL_OFF_TU UL -# else -# define CURL_SIZEOF_LONG 4 -# define CURL_TYPEOF_CURL_OFF_T long long -# define CURL_FORMAT_CURL_OFF_T "lld" -# define CURL_FORMAT_CURL_OFF_TU "llu" -# define CURL_FORMAT_OFF_T "%lld" -# define CURL_SIZEOF_CURL_OFF_T 8 -# define CURL_SUFFIX_CURL_OFF_T LL -# define CURL_SUFFIX_CURL_OFF_TU ULL -# endif -# define CURL_TYPEOF_CURL_SOCKLEN_T unsigned int -# define CURL_SIZEOF_CURL_SOCKLEN_T 4 - -#elif defined(__OS400__) -# if defined(__ILEC400__) -# define CURL_SIZEOF_LONG 4 -# define CURL_TYPEOF_CURL_OFF_T long long -# define CURL_FORMAT_CURL_OFF_T "lld" -# define CURL_FORMAT_CURL_OFF_TU "llu" -# define CURL_FORMAT_OFF_T "%lld" -# define CURL_SIZEOF_CURL_OFF_T 8 -# define CURL_SUFFIX_CURL_OFF_T LL -# define CURL_SUFFIX_CURL_OFF_TU ULL -# define CURL_TYPEOF_CURL_SOCKLEN_T socklen_t -# define CURL_SIZEOF_CURL_SOCKLEN_T 4 -# define CURL_PULL_SYS_TYPES_H 1 -# define CURL_PULL_SYS_SOCKET_H 1 -# endif - -#elif defined(__MVS__) -# if defined(__IBMC__) || defined(__IBMCPP__) -# if defined(_ILP32) -# define CURL_SIZEOF_LONG 4 -# elif defined(_LP64) -# define CURL_SIZEOF_LONG 8 -# endif -# if defined(_LONG_LONG) -# define CURL_TYPEOF_CURL_OFF_T long long -# define CURL_FORMAT_CURL_OFF_T "lld" -# define CURL_FORMAT_CURL_OFF_TU "llu" -# define CURL_FORMAT_OFF_T "%lld" -# define CURL_SIZEOF_CURL_OFF_T 8 -# define CURL_SUFFIX_CURL_OFF_T LL -# define CURL_SUFFIX_CURL_OFF_TU ULL -# elif defined(_LP64) -# define CURL_TYPEOF_CURL_OFF_T long -# define CURL_FORMAT_CURL_OFF_T "ld" -# define CURL_FORMAT_CURL_OFF_TU "lu" -# define CURL_FORMAT_OFF_T "%ld" -# define CURL_SIZEOF_CURL_OFF_T 8 -# define CURL_SUFFIX_CURL_OFF_T L -# define CURL_SUFFIX_CURL_OFF_TU UL -# else -# define CURL_TYPEOF_CURL_OFF_T long -# define CURL_FORMAT_CURL_OFF_T "ld" -# define CURL_FORMAT_CURL_OFF_TU "lu" -# define CURL_FORMAT_OFF_T "%ld" -# define CURL_SIZEOF_CURL_OFF_T 4 -# define CURL_SUFFIX_CURL_OFF_T L -# define CURL_SUFFIX_CURL_OFF_TU UL -# endif -# define CURL_TYPEOF_CURL_SOCKLEN_T socklen_t -# define CURL_SIZEOF_CURL_SOCKLEN_T 4 -# define CURL_PULL_SYS_TYPES_H 1 -# define CURL_PULL_SYS_SOCKET_H 1 -# endif - -#elif defined(__370__) -# if defined(__IBMC__) || defined(__IBMCPP__) -# if defined(_ILP32) -# define CURL_SIZEOF_LONG 4 -# elif defined(_LP64) -# define CURL_SIZEOF_LONG 8 -# endif -# if defined(_LONG_LONG) -# define CURL_TYPEOF_CURL_OFF_T long long -# define CURL_FORMAT_CURL_OFF_T "lld" -# define CURL_FORMAT_CURL_OFF_TU "llu" -# define CURL_FORMAT_OFF_T "%lld" -# define CURL_SIZEOF_CURL_OFF_T 8 -# define CURL_SUFFIX_CURL_OFF_T LL -# define CURL_SUFFIX_CURL_OFF_TU ULL -# elif defined(_LP64) -# define CURL_TYPEOF_CURL_OFF_T long -# define CURL_FORMAT_CURL_OFF_T "ld" -# define CURL_FORMAT_CURL_OFF_TU "lu" -# define CURL_FORMAT_OFF_T "%ld" -# define CURL_SIZEOF_CURL_OFF_T 8 -# define CURL_SUFFIX_CURL_OFF_T L -# define CURL_SUFFIX_CURL_OFF_TU UL -# else -# define CURL_TYPEOF_CURL_OFF_T long -# define CURL_FORMAT_CURL_OFF_T "ld" -# define CURL_FORMAT_CURL_OFF_TU "lu" -# define CURL_FORMAT_OFF_T "%ld" -# define CURL_SIZEOF_CURL_OFF_T 4 -# define CURL_SUFFIX_CURL_OFF_T L -# define CURL_SUFFIX_CURL_OFF_TU UL -# endif -# define CURL_TYPEOF_CURL_SOCKLEN_T socklen_t -# define CURL_SIZEOF_CURL_SOCKLEN_T 4 -# define CURL_PULL_SYS_TYPES_H 1 -# define CURL_PULL_SYS_SOCKET_H 1 -# endif - -#elif defined(TPF) -# define CURL_SIZEOF_LONG 8 -# define CURL_TYPEOF_CURL_OFF_T long -# define CURL_FORMAT_CURL_OFF_T "ld" -# define CURL_FORMAT_CURL_OFF_TU "lu" -# define CURL_FORMAT_OFF_T "%ld" -# define CURL_SIZEOF_CURL_OFF_T 8 -# define CURL_SUFFIX_CURL_OFF_T L -# define CURL_SUFFIX_CURL_OFF_TU UL -# define CURL_TYPEOF_CURL_SOCKLEN_T int -# define CURL_SIZEOF_CURL_SOCKLEN_T 4 - -/* ===================================== */ -/* KEEP MSVC THE PENULTIMATE ENTRY */ -/* ===================================== */ - -#elif defined(_MSC_VER) -# if (_MSC_VER >= 900) && (_INTEGRAL_MAX_BITS >= 64) -# define CURL_SIZEOF_LONG 4 -# define CURL_TYPEOF_CURL_OFF_T __int64 -# define CURL_FORMAT_CURL_OFF_T "I64d" -# define CURL_FORMAT_CURL_OFF_TU "I64u" -# define CURL_FORMAT_OFF_T "%I64d" -# define CURL_SIZEOF_CURL_OFF_T 8 -# define CURL_SUFFIX_CURL_OFF_T i64 -# define CURL_SUFFIX_CURL_OFF_TU ui64 -# else -# define CURL_SIZEOF_LONG 4 -# define CURL_TYPEOF_CURL_OFF_T long -# define CURL_FORMAT_CURL_OFF_T "ld" -# define CURL_FORMAT_CURL_OFF_TU "lu" -# define CURL_FORMAT_OFF_T "%ld" -# define CURL_SIZEOF_CURL_OFF_T 4 -# define CURL_SUFFIX_CURL_OFF_T L -# define CURL_SUFFIX_CURL_OFF_TU UL -# endif -# define CURL_TYPEOF_CURL_SOCKLEN_T int -# define CURL_SIZEOF_CURL_SOCKLEN_T 4 - -/* ===================================== */ -/* KEEP GENERIC GCC THE LAST ENTRY */ -/* ===================================== */ - -#elif defined(__GNUC__) -# if !defined(__LP64__) && (defined(__ILP32__) || \ - defined(__i386__) || defined(__ppc__) || defined(__PPC__) || defined(__arm__) || \ - defined(__sparc__) || defined(__mips__) || defined(__sh__)) -# define CURL_SIZEOF_LONG 4 -# define CURL_TYPEOF_CURL_OFF_T long long -# define CURL_FORMAT_CURL_OFF_T "lld" -# define CURL_FORMAT_CURL_OFF_TU "llu" -# define CURL_FORMAT_OFF_T "%lld" -# define CURL_SIZEOF_CURL_OFF_T 8 -# define CURL_SUFFIX_CURL_OFF_T LL -# define CURL_SUFFIX_CURL_OFF_TU ULL -# elif defined(__LP64__) || \ - defined(__x86_64__) || defined(__ppc64__) || defined(__PPC64__) || defined(__sparc64__) || \ - defined(__e2k__) /* MCST Elbrus 2000 */ -# define CURL_SIZEOF_LONG 8 -# define CURL_TYPEOF_CURL_OFF_T long -# define CURL_FORMAT_CURL_OFF_T "ld" -# define CURL_FORMAT_CURL_OFF_TU "lu" -# define CURL_FORMAT_OFF_T "%ld" -# define CURL_SIZEOF_CURL_OFF_T 8 -# define CURL_SUFFIX_CURL_OFF_T L -# define CURL_SUFFIX_CURL_OFF_TU UL -# endif -# define CURL_TYPEOF_CURL_SOCKLEN_T socklen_t -# define CURL_SIZEOF_CURL_SOCKLEN_T 4 -# define CURL_PULL_SYS_TYPES_H 1 -# define CURL_PULL_SYS_SOCKET_H 1 - -#else -# error "Unknown non-configure build target!" - Error Compilation_aborted_Unknown_non_configure_build_target -#endif - -/* CURL_PULL_SYS_TYPES_H is defined above when inclusion of header file */ -/* sys/types.h is required here to properly make type definitions below. */ -#ifdef CURL_PULL_SYS_TYPES_H -# include -#endif - -/* CURL_PULL_SYS_SOCKET_H is defined above when inclusion of header file */ -/* sys/socket.h is required here to properly make type definitions below. */ -#ifdef CURL_PULL_SYS_SOCKET_H -# include -#endif - -/* Data type definition of curl_socklen_t. */ - -#ifdef CURL_TYPEOF_CURL_SOCKLEN_T - typedef CURL_TYPEOF_CURL_SOCKLEN_T curl_socklen_t; -#endif - -/* Data type definition of curl_off_t. */ - -#ifdef CURL_TYPEOF_CURL_OFF_T - typedef CURL_TYPEOF_CURL_OFF_T curl_off_t; -#endif - -#endif /* __CURL_CURLBUILD_H */ diff --git a/contrib/curl/include/curl/curlbuild.h.cmake b/contrib/curl/include/curl/curlbuild.h.cmake deleted file mode 100644 index bbb31a9408..0000000000 --- a/contrib/curl/include/curl/curlbuild.h.cmake +++ /dev/null @@ -1,197 +0,0 @@ -#ifndef __CURL_CURLBUILD_H -#define __CURL_CURLBUILD_H -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) 1998 - 2008, Daniel Stenberg, , et al. - * - * This software is licensed as described in the file COPYING, which - * you should have received as part of this distribution. The terms - * are also available at https://curl.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. - * - ***************************************************************************/ - -/* ================================================================ */ -/* NOTES FOR CONFIGURE CAPABLE SYSTEMS */ -/* ================================================================ */ - -/* - * NOTE 1: - * ------- - * - * Nothing in this file is intended to be modified or adjusted by the - * curl library user nor by the curl library builder. - * - * If you think that something actually needs to be changed, adjusted - * or fixed in this file, then, report it on the libcurl development - * mailing list: https://cool.haxx.se/mailman/listinfo/curl-library/ - * - * This header file shall only export symbols which are 'curl' or 'CURL' - * prefixed, otherwise public name space would be polluted. - * - * NOTE 2: - * ------- - * - * Right now you might be staring at file include/curl/curlbuild.h.in or - * at file include/curl/curlbuild.h, this is due to the following reason: - * - * On systems capable of running the configure script, the configure process - * will overwrite the distributed include/curl/curlbuild.h file with one that - * is suitable and specific to the library being configured and built, which - * is generated from the include/curl/curlbuild.h.in template file. - * - */ - -/* ================================================================ */ -/* DEFINITION OF THESE SYMBOLS SHALL NOT TAKE PLACE ANYWHERE ELSE */ -/* ================================================================ */ - -#ifdef CURL_SIZEOF_LONG -#error "CURL_SIZEOF_LONG shall not be defined except in curlbuild.h" - Error Compilation_aborted_CURL_SIZEOF_LONG_already_defined -#endif - -#ifdef CURL_TYPEOF_CURL_SOCKLEN_T -#error "CURL_TYPEOF_CURL_SOCKLEN_T shall not be defined except in curlbuild.h" - Error Compilation_aborted_CURL_TYPEOF_CURL_SOCKLEN_T_already_defined -#endif - -#ifdef CURL_SIZEOF_CURL_SOCKLEN_T -#error "CURL_SIZEOF_CURL_SOCKLEN_T shall not be defined except in curlbuild.h" - Error Compilation_aborted_CURL_SIZEOF_CURL_SOCKLEN_T_already_defined -#endif - -#ifdef CURL_TYPEOF_CURL_OFF_T -#error "CURL_TYPEOF_CURL_OFF_T shall not be defined except in curlbuild.h" - Error Compilation_aborted_CURL_TYPEOF_CURL_OFF_T_already_defined -#endif - -#ifdef CURL_FORMAT_CURL_OFF_T -#error "CURL_FORMAT_CURL_OFF_T shall not be defined except in curlbuild.h" - Error Compilation_aborted_CURL_FORMAT_CURL_OFF_T_already_defined -#endif - -#ifdef CURL_FORMAT_CURL_OFF_TU -#error "CURL_FORMAT_CURL_OFF_TU shall not be defined except in curlbuild.h" - Error Compilation_aborted_CURL_FORMAT_CURL_OFF_TU_already_defined -#endif - -#ifdef CURL_FORMAT_OFF_T -#error "CURL_FORMAT_OFF_T shall not be defined except in curlbuild.h" - Error Compilation_aborted_CURL_FORMAT_OFF_T_already_defined -#endif - -#ifdef CURL_SIZEOF_CURL_OFF_T -#error "CURL_SIZEOF_CURL_OFF_T shall not be defined except in curlbuild.h" - Error Compilation_aborted_CURL_SIZEOF_CURL_OFF_T_already_defined -#endif - -#ifdef CURL_SUFFIX_CURL_OFF_T -#error "CURL_SUFFIX_CURL_OFF_T shall not be defined except in curlbuild.h" - Error Compilation_aborted_CURL_SUFFIX_CURL_OFF_T_already_defined -#endif - -#ifdef CURL_SUFFIX_CURL_OFF_TU -#error "CURL_SUFFIX_CURL_OFF_TU shall not be defined except in curlbuild.h" - Error Compilation_aborted_CURL_SUFFIX_CURL_OFF_TU_already_defined -#endif - -/* ================================================================ */ -/* EXTERNAL INTERFACE SETTINGS FOR CONFIGURE CAPABLE SYSTEMS ONLY */ -/* ================================================================ */ - -/* Configure process defines this to 1 when it finds out that system */ -/* header file ws2tcpip.h must be included by the external interface. */ -#cmakedefine CURL_PULL_WS2TCPIP_H -#ifdef CURL_PULL_WS2TCPIP_H -# ifndef WIN32_LEAN_AND_MEAN -# define WIN32_LEAN_AND_MEAN -# endif -# include -# include -# include -#endif - -/* Configure process defines this to 1 when it finds out that system */ -/* header file sys/types.h must be included by the external interface. */ -#cmakedefine CURL_PULL_SYS_TYPES_H -#ifdef CURL_PULL_SYS_TYPES_H -# include -#endif - -/* Configure process defines this to 1 when it finds out that system */ -/* header file stdint.h must be included by the external interface. */ -#cmakedefine CURL_PULL_STDINT_H -#ifdef CURL_PULL_STDINT_H -# include -#endif - -/* Configure process defines this to 1 when it finds out that system */ -/* header file inttypes.h must be included by the external interface. */ -#cmakedefine CURL_PULL_INTTYPES_H -#ifdef CURL_PULL_INTTYPES_H -# include -#endif - -/* Configure process defines this to 1 when it finds out that system */ -/* header file sys/socket.h must be included by the external interface. */ -#cmakedefine CURL_PULL_SYS_SOCKET_H -#ifdef CURL_PULL_SYS_SOCKET_H -# include -#endif - -/* Configure process defines this to 1 when it finds out that system */ -/* header file sys/poll.h must be included by the external interface. */ -#cmakedefine CURL_PULL_SYS_POLL_H -#ifdef CURL_PULL_SYS_POLL_H -# include -#endif - -/* The size of `long', as computed by sizeof. */ -#define CURL_SIZEOF_LONG ${CURL_SIZEOF_LONG} - -/* Integral data type used for curl_socklen_t. */ -#define CURL_TYPEOF_CURL_SOCKLEN_T ${CURL_TYPEOF_CURL_SOCKLEN_T} - -/* The size of `curl_socklen_t', as computed by sizeof. */ -#define CURL_SIZEOF_CURL_SOCKLEN_T ${CURL_SIZEOF_CURL_SOCKLEN_T} - -/* Data type definition of curl_socklen_t. */ -typedef CURL_TYPEOF_CURL_SOCKLEN_T curl_socklen_t; - -/* Signed integral data type used for curl_off_t. */ -#define CURL_TYPEOF_CURL_OFF_T ${CURL_TYPEOF_CURL_OFF_T} - -/* Data type definition of curl_off_t. */ -typedef CURL_TYPEOF_CURL_OFF_T curl_off_t; - -/* curl_off_t formatting string directive without "%" conversion specifier. */ -#define CURL_FORMAT_CURL_OFF_T "${CURL_FORMAT_CURL_OFF_T}" - -/* unsigned curl_off_t formatting string without "%" conversion specifier. */ -#define CURL_FORMAT_CURL_OFF_TU "${CURL_FORMAT_CURL_OFF_TU}" - -/* curl_off_t formatting string directive with "%" conversion specifier. */ -#define CURL_FORMAT_OFF_T "${CURL_FORMAT_OFF_T}" - -/* The size of `curl_off_t', as computed by sizeof. */ -#define CURL_SIZEOF_CURL_OFF_T ${CURL_SIZEOF_CURL_OFF_T} - -/* curl_off_t constant suffix. */ -#define CURL_SUFFIX_CURL_OFF_T ${CURL_SUFFIX_CURL_OFF_T} - -/* unsigned curl_off_t constant suffix. */ -#define CURL_SUFFIX_CURL_OFF_TU ${CURL_SUFFIX_CURL_OFF_TU} - -#endif /* __CURL_CURLBUILD_H */ diff --git a/contrib/curl/include/curl/curlbuild.h.in b/contrib/curl/include/curl/curlbuild.h.in deleted file mode 100644 index ffab356709..0000000000 --- a/contrib/curl/include/curl/curlbuild.h.in +++ /dev/null @@ -1,197 +0,0 @@ -#ifndef __CURL_CURLBUILD_H -#define __CURL_CURLBUILD_H -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) 1998 - 2012, Daniel Stenberg, , et al. - * - * This software is licensed as described in the file COPYING, which - * you should have received as part of this distribution. The terms - * are also available at https://curl.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. - * - ***************************************************************************/ - -/* ================================================================ */ -/* NOTES FOR CONFIGURE CAPABLE SYSTEMS */ -/* ================================================================ */ - -/* - * NOTE 1: - * ------- - * - * Nothing in this file is intended to be modified or adjusted by the - * curl library user nor by the curl library builder. - * - * If you think that something actually needs to be changed, adjusted - * or fixed in this file, then, report it on the libcurl development - * mailing list: https://cool.haxx.se/mailman/listinfo/curl-library/ - * - * This header file shall only export symbols which are 'curl' or 'CURL' - * prefixed, otherwise public name space would be polluted. - * - * NOTE 2: - * ------- - * - * Right now you might be staring at file include/curl/curlbuild.h.in or - * at file include/curl/curlbuild.h, this is due to the following reason: - * - * On systems capable of running the configure script, the configure process - * will overwrite the distributed include/curl/curlbuild.h file with one that - * is suitable and specific to the library being configured and built, which - * is generated from the include/curl/curlbuild.h.in template file. - * - */ - -/* ================================================================ */ -/* DEFINITION OF THESE SYMBOLS SHALL NOT TAKE PLACE ANYWHERE ELSE */ -/* ================================================================ */ - -#ifdef CURL_SIZEOF_LONG -#error "CURL_SIZEOF_LONG shall not be defined except in curlbuild.h" - Error Compilation_aborted_CURL_SIZEOF_LONG_already_defined -#endif - -#ifdef CURL_TYPEOF_CURL_SOCKLEN_T -#error "CURL_TYPEOF_CURL_SOCKLEN_T shall not be defined except in curlbuild.h" - Error Compilation_aborted_CURL_TYPEOF_CURL_SOCKLEN_T_already_defined -#endif - -#ifdef CURL_SIZEOF_CURL_SOCKLEN_T -#error "CURL_SIZEOF_CURL_SOCKLEN_T shall not be defined except in curlbuild.h" - Error Compilation_aborted_CURL_SIZEOF_CURL_SOCKLEN_T_already_defined -#endif - -#ifdef CURL_TYPEOF_CURL_OFF_T -#error "CURL_TYPEOF_CURL_OFF_T shall not be defined except in curlbuild.h" - Error Compilation_aborted_CURL_TYPEOF_CURL_OFF_T_already_defined -#endif - -#ifdef CURL_FORMAT_CURL_OFF_T -#error "CURL_FORMAT_CURL_OFF_T shall not be defined except in curlbuild.h" - Error Compilation_aborted_CURL_FORMAT_CURL_OFF_T_already_defined -#endif - -#ifdef CURL_FORMAT_CURL_OFF_TU -#error "CURL_FORMAT_CURL_OFF_TU shall not be defined except in curlbuild.h" - Error Compilation_aborted_CURL_FORMAT_CURL_OFF_TU_already_defined -#endif - -#ifdef CURL_FORMAT_OFF_T -#error "CURL_FORMAT_OFF_T shall not be defined except in curlbuild.h" - Error Compilation_aborted_CURL_FORMAT_OFF_T_already_defined -#endif - -#ifdef CURL_SIZEOF_CURL_OFF_T -#error "CURL_SIZEOF_CURL_OFF_T shall not be defined except in curlbuild.h" - Error Compilation_aborted_CURL_SIZEOF_CURL_OFF_T_already_defined -#endif - -#ifdef CURL_SUFFIX_CURL_OFF_T -#error "CURL_SUFFIX_CURL_OFF_T shall not be defined except in curlbuild.h" - Error Compilation_aborted_CURL_SUFFIX_CURL_OFF_T_already_defined -#endif - -#ifdef CURL_SUFFIX_CURL_OFF_TU -#error "CURL_SUFFIX_CURL_OFF_TU shall not be defined except in curlbuild.h" - Error Compilation_aborted_CURL_SUFFIX_CURL_OFF_TU_already_defined -#endif - -/* ================================================================ */ -/* EXTERNAL INTERFACE SETTINGS FOR CONFIGURE CAPABLE SYSTEMS ONLY */ -/* ================================================================ */ - -/* Configure process defines this to 1 when it finds out that system */ -/* header file ws2tcpip.h must be included by the external interface. */ -#undef CURL_PULL_WS2TCPIP_H -#ifdef CURL_PULL_WS2TCPIP_H -# ifndef WIN32_LEAN_AND_MEAN -# define WIN32_LEAN_AND_MEAN -# endif -# include -# include -# include -#endif - -/* Configure process defines this to 1 when it finds out that system */ -/* header file sys/types.h must be included by the external interface. */ -#undef CURL_PULL_SYS_TYPES_H -#ifdef CURL_PULL_SYS_TYPES_H -# include -#endif - -/* Configure process defines this to 1 when it finds out that system */ -/* header file stdint.h must be included by the external interface. */ -#undef CURL_PULL_STDINT_H -#ifdef CURL_PULL_STDINT_H -# include -#endif - -/* Configure process defines this to 1 when it finds out that system */ -/* header file inttypes.h must be included by the external interface. */ -#undef CURL_PULL_INTTYPES_H -#ifdef CURL_PULL_INTTYPES_H -# include -#endif - -/* Configure process defines this to 1 when it finds out that system */ -/* header file sys/socket.h must be included by the external interface. */ -#undef CURL_PULL_SYS_SOCKET_H -#ifdef CURL_PULL_SYS_SOCKET_H -# include -#endif - -/* Configure process defines this to 1 when it finds out that system */ -/* header file sys/poll.h must be included by the external interface. */ -#undef CURL_PULL_SYS_POLL_H -#ifdef CURL_PULL_SYS_POLL_H -# include -#endif - -/* The size of `long', as computed by sizeof. */ -#undef CURL_SIZEOF_LONG - -/* Integral data type used for curl_socklen_t. */ -#undef CURL_TYPEOF_CURL_SOCKLEN_T - -/* The size of `curl_socklen_t', as computed by sizeof. */ -#undef CURL_SIZEOF_CURL_SOCKLEN_T - -/* Data type definition of curl_socklen_t. */ -typedef CURL_TYPEOF_CURL_SOCKLEN_T curl_socklen_t; - -/* Signed integral data type used for curl_off_t. */ -#undef CURL_TYPEOF_CURL_OFF_T - -/* Data type definition of curl_off_t. */ -typedef CURL_TYPEOF_CURL_OFF_T curl_off_t; - -/* curl_off_t formatting string directive without "%" conversion specifier. */ -#undef CURL_FORMAT_CURL_OFF_T - -/* unsigned curl_off_t formatting string without "%" conversion specifier. */ -#undef CURL_FORMAT_CURL_OFF_TU - -/* curl_off_t formatting string directive with "%" conversion specifier. */ -#undef CURL_FORMAT_OFF_T - -/* The size of `curl_off_t', as computed by sizeof. */ -#undef CURL_SIZEOF_CURL_OFF_T - -/* curl_off_t constant suffix. */ -#undef CURL_SUFFIX_CURL_OFF_T - -/* unsigned curl_off_t constant suffix. */ -#undef CURL_SUFFIX_CURL_OFF_TU - -#endif /* __CURL_CURLBUILD_H */ diff --git a/contrib/curl/include/curl/curlrules.h b/contrib/curl/include/curl/curlrules.h deleted file mode 100644 index 55d21f68f1..0000000000 --- a/contrib/curl/include/curl/curlrules.h +++ /dev/null @@ -1,262 +0,0 @@ -#ifndef __CURL_CURLRULES_H -#define __CURL_CURLRULES_H -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) 1998 - 2012, Daniel Stenberg, , et al. - * - * This software is licensed as described in the file COPYING, which - * you should have received as part of this distribution. The terms - * are also available at https://curl.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. - * - ***************************************************************************/ - -/* ================================================================ */ -/* COMPILE TIME SANITY CHECKS */ -/* ================================================================ */ - -/* - * NOTE 1: - * ------- - * - * All checks done in this file are intentionally placed in a public - * header file which is pulled by curl/curl.h when an application is - * being built using an already built libcurl library. Additionally - * this file is also included and used when building the library. - * - * If compilation fails on this file it is certainly sure that the - * problem is elsewhere. It could be a problem in the curlbuild.h - * header file, or simply that you are using different compilation - * settings than those used to build the library. - * - * Nothing in this file is intended to be modified or adjusted by the - * curl library user nor by the curl library builder. - * - * Do not deactivate any check, these are done to make sure that the - * library is properly built and used. - * - * You can find further help on the libcurl development mailing list: - * https://cool.haxx.se/mailman/listinfo/curl-library/ - * - * NOTE 2 - * ------ - * - * Some of the following compile time checks are based on the fact - * that the dimension of a constant array can not be a negative one. - * In this way if the compile time verification fails, the compilation - * will fail issuing an error. The error description wording is compiler - * dependent but it will be quite similar to one of the following: - * - * "negative subscript or subscript is too large" - * "array must have at least one element" - * "-1 is an illegal array size" - * "size of array is negative" - * - * If you are building an application which tries to use an already - * built libcurl library and you are getting this kind of errors on - * this file, it is a clear indication that there is a mismatch between - * how the library was built and how you are trying to use it for your - * application. Your already compiled or binary library provider is the - * only one who can give you the details you need to properly use it. - */ - -/* - * Verify that some macros are actually defined. - */ - -#ifndef CURL_SIZEOF_LONG -# error "CURL_SIZEOF_LONG definition is missing!" - Error Compilation_aborted_CURL_SIZEOF_LONG_is_missing -#endif - -#ifndef CURL_TYPEOF_CURL_SOCKLEN_T -# error "CURL_TYPEOF_CURL_SOCKLEN_T definition is missing!" - Error Compilation_aborted_CURL_TYPEOF_CURL_SOCKLEN_T_is_missing -#endif - -#ifndef CURL_SIZEOF_CURL_SOCKLEN_T -# error "CURL_SIZEOF_CURL_SOCKLEN_T definition is missing!" - Error Compilation_aborted_CURL_SIZEOF_CURL_SOCKLEN_T_is_missing -#endif - -#ifndef CURL_TYPEOF_CURL_OFF_T -# error "CURL_TYPEOF_CURL_OFF_T definition is missing!" - Error Compilation_aborted_CURL_TYPEOF_CURL_OFF_T_is_missing -#endif - -#ifndef CURL_FORMAT_CURL_OFF_T -# error "CURL_FORMAT_CURL_OFF_T definition is missing!" - Error Compilation_aborted_CURL_FORMAT_CURL_OFF_T_is_missing -#endif - -#ifndef CURL_FORMAT_CURL_OFF_TU -# error "CURL_FORMAT_CURL_OFF_TU definition is missing!" - Error Compilation_aborted_CURL_FORMAT_CURL_OFF_TU_is_missing -#endif - -#ifndef CURL_FORMAT_OFF_T -# error "CURL_FORMAT_OFF_T definition is missing!" - Error Compilation_aborted_CURL_FORMAT_OFF_T_is_missing -#endif - -#ifndef CURL_SIZEOF_CURL_OFF_T -# error "CURL_SIZEOF_CURL_OFF_T definition is missing!" - Error Compilation_aborted_CURL_SIZEOF_CURL_OFF_T_is_missing -#endif - -#ifndef CURL_SUFFIX_CURL_OFF_T -# error "CURL_SUFFIX_CURL_OFF_T definition is missing!" - Error Compilation_aborted_CURL_SUFFIX_CURL_OFF_T_is_missing -#endif - -#ifndef CURL_SUFFIX_CURL_OFF_TU -# error "CURL_SUFFIX_CURL_OFF_TU definition is missing!" - Error Compilation_aborted_CURL_SUFFIX_CURL_OFF_TU_is_missing -#endif - -/* - * Macros private to this header file. - */ - -#define CurlchkszEQ(t, s) sizeof(t) == s ? 1 : -1 - -#define CurlchkszGE(t1, t2) sizeof(t1) >= sizeof(t2) ? 1 : -1 - -/* - * Verify that the size previously defined and expected for long - * is the same as the one reported by sizeof() at compile time. - */ - -typedef char - __curl_rule_01__ - [CurlchkszEQ(long, CURL_SIZEOF_LONG)]; - -/* - * Verify that the size previously defined and expected for - * curl_off_t is actually the the same as the one reported - * by sizeof() at compile time. - */ - -typedef char - __curl_rule_02__ - [CurlchkszEQ(curl_off_t, CURL_SIZEOF_CURL_OFF_T)]; - -/* - * Verify at compile time that the size of curl_off_t as reported - * by sizeof() is greater or equal than the one reported for long - * for the current compilation. - */ - -typedef char - __curl_rule_03__ - [CurlchkszGE(curl_off_t, long)]; - -/* - * Verify that the size previously defined and expected for - * curl_socklen_t is actually the the same as the one reported - * by sizeof() at compile time. - */ - -typedef char - __curl_rule_04__ - [CurlchkszEQ(curl_socklen_t, CURL_SIZEOF_CURL_SOCKLEN_T)]; - -/* - * Verify at compile time that the size of curl_socklen_t as reported - * by sizeof() is greater or equal than the one reported for int for - * the current compilation. - */ - -typedef char - __curl_rule_05__ - [CurlchkszGE(curl_socklen_t, int)]; - -/* ================================================================ */ -/* EXTERNALLY AND INTERNALLY VISIBLE DEFINITIONS */ -/* ================================================================ */ - -/* - * CURL_ISOCPP and CURL_OFF_T_C definitions are done here in order to allow - * these to be visible and exported by the external libcurl interface API, - * while also making them visible to the library internals, simply including - * curl_setup.h, without actually needing to include curl.h internally. - * If some day this section would grow big enough, all this should be moved - * to its own header file. - */ - -/* - * Figure out if we can use the ## preprocessor operator, which is supported - * by ISO/ANSI C and C++. Some compilers support it without setting __STDC__ - * or __cplusplus so we need to carefully check for them too. - */ - -#if defined(__STDC__) || defined(_MSC_VER) || defined(__cplusplus) || \ - defined(__HP_aCC) || defined(__BORLANDC__) || defined(__LCC__) || \ - defined(__POCC__) || defined(__SALFORDC__) || defined(__HIGHC__) || \ - defined(__ILEC400__) - /* This compiler is believed to have an ISO compatible preprocessor */ -#define CURL_ISOCPP -#else - /* This compiler is believed NOT to have an ISO compatible preprocessor */ -#undef CURL_ISOCPP -#endif - -/* - * Macros for minimum-width signed and unsigned curl_off_t integer constants. - */ - -#if defined(__BORLANDC__) && (__BORLANDC__ == 0x0551) -# define __CURL_OFF_T_C_HLPR2(x) x -# define __CURL_OFF_T_C_HLPR1(x) __CURL_OFF_T_C_HLPR2(x) -# define CURL_OFF_T_C(Val) __CURL_OFF_T_C_HLPR1(Val) ## \ - __CURL_OFF_T_C_HLPR1(CURL_SUFFIX_CURL_OFF_T) -# define CURL_OFF_TU_C(Val) __CURL_OFF_T_C_HLPR1(Val) ## \ - __CURL_OFF_T_C_HLPR1(CURL_SUFFIX_CURL_OFF_TU) -#else -# ifdef CURL_ISOCPP -# define __CURL_OFF_T_C_HLPR2(Val,Suffix) Val ## Suffix -# else -# define __CURL_OFF_T_C_HLPR2(Val,Suffix) Val/**/Suffix -# endif -# define __CURL_OFF_T_C_HLPR1(Val,Suffix) __CURL_OFF_T_C_HLPR2(Val,Suffix) -# define CURL_OFF_T_C(Val) __CURL_OFF_T_C_HLPR1(Val,CURL_SUFFIX_CURL_OFF_T) -# define CURL_OFF_TU_C(Val) __CURL_OFF_T_C_HLPR1(Val,CURL_SUFFIX_CURL_OFF_TU) -#endif - -/* - * Get rid of macros private to this header file. - */ - -#undef CurlchkszEQ -#undef CurlchkszGE - -/* - * Get rid of macros not intended to exist beyond this point. - */ - -#undef CURL_PULL_WS2TCPIP_H -#undef CURL_PULL_SYS_TYPES_H -#undef CURL_PULL_SYS_SOCKET_H -#undef CURL_PULL_SYS_POLL_H -#undef CURL_PULL_STDINT_H -#undef CURL_PULL_INTTYPES_H - -#undef CURL_TYPEOF_CURL_SOCKLEN_T -#undef CURL_TYPEOF_CURL_OFF_T - -#ifdef CURL_NO_OLDIES -#undef CURL_FORMAT_OFF_T /* not required since 7.19.0 - obsoleted in 7.20.0 */ -#endif - -#endif /* __CURL_CURLRULES_H */ diff --git a/contrib/curl/include/curl/curlver.h b/contrib/curl/include/curl/curlver.h index 349e9fe888..e863815d41 100644 --- a/contrib/curl/include/curl/curlver.h +++ b/contrib/curl/include/curl/curlver.h @@ -1,5 +1,5 @@ -#ifndef __CURL_CURLVER_H -#define __CURL_CURLVER_H +#ifndef CURLINC_CURLVER_H +#define CURLINC_CURLVER_H /*************************************************************************** * _ _ ____ _ * Project ___| | | | _ \| | @@ -7,11 +7,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2017, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -20,33 +20,35 @@ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY * KIND, either express or implied. * + * SPDX-License-Identifier: curl + * ***************************************************************************/ /* This header file contains nothing but libcurl version info, generated by a script at release-time. This was made its own header file in 7.11.2 */ /* This is the global package copyright */ -#define LIBCURL_COPYRIGHT "1996 - 2017 Daniel Stenberg, ." +#define LIBCURL_COPYRIGHT "Daniel Stenberg, ." /* This is the version number of the libcurl package from which this header file origins: */ -#define LIBCURL_VERSION "7.53.1-DEV" +#define LIBCURL_VERSION "8.11.0-DEV" /* The numeric version number is also available "in parts" by using these defines: */ -#define LIBCURL_VERSION_MAJOR 7 -#define LIBCURL_VERSION_MINOR 53 -#define LIBCURL_VERSION_PATCH 1 +#define LIBCURL_VERSION_MAJOR 8 +#define LIBCURL_VERSION_MINOR 11 +#define LIBCURL_VERSION_PATCH 0 /* This is the numeric version of the libcurl version number, meant for easier - parsing and comparions by programs. The LIBCURL_VERSION_NUM define will + parsing and comparisons by programs. The LIBCURL_VERSION_NUM define will always follow this syntax: 0xXXYYZZ Where XX, YY and ZZ are the main version, release and patch numbers in hexadecimal (using 8 bits each). All three numbers are always represented - using two digits. 1.2 would appear as "0x010200" while version 9.11.7 + using two digits. 1.2 would appear as "0x010200" while version 9.11.7 appears as "0x090b07". This 6-digit (24 bits) hexadecimal number does not show pre-release number, @@ -57,21 +59,21 @@ CURL_VERSION_BITS() macro since curl's own configure script greps for it and needs it to contain the full number. */ -#define LIBCURL_VERSION_NUM 0x073501 +#define LIBCURL_VERSION_NUM 0x080b00 /* * This is the date and time when the full source package was created. The * timestamp is not stored in git, as the timestamp is properly set in the * tarballs by the maketgz script. * - * The format of the date should follow this template: + * The format of the date follows this template: * - * "Mon Feb 12 11:35:33 UTC 2007" + * "2007-11-23" */ -#define LIBCURL_TIMESTAMP "DEV" +#define LIBCURL_TIMESTAMP "[unreleased]" -#define CURL_VERSION_BITS(x,y,z) ((x)<<16|(y)<<8|z) +#define CURL_VERSION_BITS(x,y,z) ((x)<<16|(y)<<8|(z)) #define CURL_AT_LEAST_VERSION(x,y,z) \ (LIBCURL_VERSION_NUM >= CURL_VERSION_BITS(x, y, z)) -#endif /* __CURL_CURLVER_H */ +#endif /* CURLINC_CURLVER_H */ diff --git a/contrib/curl/include/curl/easy.h b/contrib/curl/include/curl/easy.h index 752c5049f8..71b8dd4674 100644 --- a/contrib/curl/include/curl/easy.h +++ b/contrib/curl/include/curl/easy.h @@ -1,5 +1,5 @@ -#ifndef __CURL_EASY_H -#define __CURL_EASY_H +#ifndef CURLINC_EASY_H +#define CURLINC_EASY_H /*************************************************************************** * _ _ ____ _ * Project ___| | | | _ \| | @@ -7,11 +7,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2016, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -20,11 +20,24 @@ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY * KIND, either express or implied. * + * SPDX-License-Identifier: curl + * ***************************************************************************/ #ifdef __cplusplus extern "C" { #endif +/* Flag bits in the curl_blob struct: */ +#define CURL_BLOB_COPY 1 /* tell libcurl to copy the data */ +#define CURL_BLOB_NOCOPY 0 /* tell libcurl to NOT copy the data */ + +struct curl_blob { + void *data; + size_t len; + unsigned int flags; /* bit 0 is defined, the rest are reserved and should be + left zeroes */ +}; + CURL_EXTERN CURL *curl_easy_init(void); CURL_EXTERN CURLcode curl_easy_setopt(CURL *curl, CURLoption option, ...); CURL_EXTERN CURLcode curl_easy_perform(CURL *curl); @@ -35,13 +48,13 @@ CURL_EXTERN void curl_easy_cleanup(CURL *curl); * * DESCRIPTION * - * Request internal information from the curl session with this function. The - * third argument MUST be a pointer to a long, a pointer to a char * or a - * pointer to a double (as the documentation describes elsewhere). The data - * pointed to will be filled in accordingly and can be relied upon only if the - * function returns CURLE_OK. This function is intended to get used *AFTER* a - * performed transfer, all results from this function are undefined until the - * transfer is completed. + * Request internal information from the curl session with this function. + * The third argument MUST be pointing to the specific type of the used option + * which is documented in each manpage of the option. The data pointed to + * will be filled in accordingly and can be relied upon only if the function + * returns CURLE_OK. This function is intended to get used *AFTER* a performed + * transfer, all results from this function are undefined until the transfer + * is completed. */ CURL_EXTERN CURLcode curl_easy_getinfo(CURL *curl, CURLINFO info, ...); @@ -95,8 +108,18 @@ CURL_EXTERN CURLcode curl_easy_recv(CURL *curl, void *buffer, size_t buflen, CURL_EXTERN CURLcode curl_easy_send(CURL *curl, const void *buffer, size_t buflen, size_t *n); + +/* + * NAME curl_easy_upkeep() + * + * DESCRIPTION + * + * Performs connection upkeep for the given session handle. + */ +CURL_EXTERN CURLcode curl_easy_upkeep(CURL *curl); + #ifdef __cplusplus -} +} /* end of extern "C" */ #endif #endif diff --git a/contrib/curl/include/curl/header.h b/contrib/curl/include/curl/header.h new file mode 100644 index 0000000000..8df11e1e42 --- /dev/null +++ b/contrib/curl/include/curl/header.h @@ -0,0 +1,74 @@ +#ifndef CURLINC_HEADER_H +#define CURLINC_HEADER_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#ifdef __cplusplus +extern "C" { +#endif + +struct curl_header { + char *name; /* this might not use the same case */ + char *value; + size_t amount; /* number of headers using this name */ + size_t index; /* ... of this instance, 0 or higher */ + unsigned int origin; /* see bits below */ + void *anchor; /* handle privately used by libcurl */ +}; + +/* 'origin' bits */ +#define CURLH_HEADER (1<<0) /* plain server header */ +#define CURLH_TRAILER (1<<1) /* trailers */ +#define CURLH_CONNECT (1<<2) /* CONNECT headers */ +#define CURLH_1XX (1<<3) /* 1xx headers */ +#define CURLH_PSEUDO (1<<4) /* pseudo headers */ + +typedef enum { + CURLHE_OK, + CURLHE_BADINDEX, /* header exists but not with this index */ + CURLHE_MISSING, /* no such header exists */ + CURLHE_NOHEADERS, /* no headers at all exist (yet) */ + CURLHE_NOREQUEST, /* no request with this number was used */ + CURLHE_OUT_OF_MEMORY, /* out of memory while processing */ + CURLHE_BAD_ARGUMENT, /* a function argument was not okay */ + CURLHE_NOT_BUILT_IN /* if API was disabled in the build */ +} CURLHcode; + +CURL_EXTERN CURLHcode curl_easy_header(CURL *easy, + const char *name, + size_t index, + unsigned int origin, + int request, + struct curl_header **hout); + +CURL_EXTERN struct curl_header *curl_easy_nextheader(CURL *easy, + unsigned int origin, + int request, + struct curl_header *prev); + +#ifdef __cplusplus +} /* end of extern "C" */ +#endif + +#endif /* CURLINC_HEADER_H */ diff --git a/contrib/curl/include/curl/mprintf.h b/contrib/curl/include/curl/mprintf.h index e20f546e19..88059c851f 100644 --- a/contrib/curl/include/curl/mprintf.h +++ b/contrib/curl/include/curl/mprintf.h @@ -1,5 +1,5 @@ -#ifndef __CURL_MPRINTF_H -#define __CURL_MPRINTF_H +#ifndef CURLINC_MPRINTF_H +#define CURLINC_MPRINTF_H /*************************************************************************** * _ _ ____ _ * Project ___| | | | _ \| | @@ -7,11 +7,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2016, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -20,6 +20,8 @@ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY * KIND, either express or implied. * + * SPDX-License-Identifier: curl + * ***************************************************************************/ #include @@ -30,21 +32,54 @@ extern "C" { #endif -CURL_EXTERN int curl_mprintf(const char *format, ...); -CURL_EXTERN int curl_mfprintf(FILE *fd, const char *format, ...); -CURL_EXTERN int curl_msprintf(char *buffer, const char *format, ...); +#ifndef CURL_TEMP_PRINTF +#if (defined(__GNUC__) || defined(__clang__) || \ + defined(__IAR_SYSTEMS_ICC__)) && \ + defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) && \ + !defined(CURL_NO_FMT_CHECKS) +#if defined(__MINGW32__) && !defined(__clang__) +#if defined(__MINGW_PRINTF_FORMAT) /* mingw-w64 3.0.0+. Needs stdio.h. */ +#define CURL_TEMP_PRINTF(fmt, arg) \ + __attribute__((format(__MINGW_PRINTF_FORMAT, fmt, arg))) +#else +#define CURL_TEMP_PRINTF(fmt, arg) +#endif +#else +#define CURL_TEMP_PRINTF(fmt, arg) \ + __attribute__((format(printf, fmt, arg))) +#endif +#else +#define CURL_TEMP_PRINTF(fmt, arg) +#endif +#endif + +CURL_EXTERN int curl_mprintf(const char *format, ...) + CURL_TEMP_PRINTF(1, 2); +CURL_EXTERN int curl_mfprintf(FILE *fd, const char *format, ...) + CURL_TEMP_PRINTF(2, 3); +CURL_EXTERN int curl_msprintf(char *buffer, const char *format, ...) + CURL_TEMP_PRINTF(2, 3); CURL_EXTERN int curl_msnprintf(char *buffer, size_t maxlength, - const char *format, ...); -CURL_EXTERN int curl_mvprintf(const char *format, va_list args); -CURL_EXTERN int curl_mvfprintf(FILE *fd, const char *format, va_list args); -CURL_EXTERN int curl_mvsprintf(char *buffer, const char *format, va_list args); + const char *format, ...) + CURL_TEMP_PRINTF(3, 4); +CURL_EXTERN int curl_mvprintf(const char *format, va_list args) + CURL_TEMP_PRINTF(1, 0); +CURL_EXTERN int curl_mvfprintf(FILE *fd, const char *format, va_list args) + CURL_TEMP_PRINTF(2, 0); +CURL_EXTERN int curl_mvsprintf(char *buffer, const char *format, va_list args) + CURL_TEMP_PRINTF(2, 0); CURL_EXTERN int curl_mvsnprintf(char *buffer, size_t maxlength, - const char *format, va_list args); -CURL_EXTERN char *curl_maprintf(const char *format, ...); -CURL_EXTERN char *curl_mvaprintf(const char *format, va_list args); + const char *format, va_list args) + CURL_TEMP_PRINTF(3, 0); +CURL_EXTERN char *curl_maprintf(const char *format, ...) + CURL_TEMP_PRINTF(1, 2); +CURL_EXTERN char *curl_mvaprintf(const char *format, va_list args) + CURL_TEMP_PRINTF(1, 0); + +#undef CURL_TEMP_PRINTF #ifdef __cplusplus -} +} /* end of extern "C" */ #endif -#endif /* __CURL_MPRINTF_H */ +#endif /* CURLINC_MPRINTF_H */ diff --git a/contrib/curl/include/curl/multi.h b/contrib/curl/include/curl/multi.h index d1e00cc5d3..7b6c351ada 100644 --- a/contrib/curl/include/curl/multi.h +++ b/contrib/curl/include/curl/multi.h @@ -1,5 +1,5 @@ -#ifndef __CURL_MULTI_H -#define __CURL_MULTI_H +#ifndef CURLINC_MULTI_H +#define CURLINC_MULTI_H /*************************************************************************** * _ _ ____ _ * Project ___| | | | _ \| | @@ -7,11 +7,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2016, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -20,9 +20,11 @@ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY * KIND, either express or implied. * + * SPDX-License-Identifier: curl + * ***************************************************************************/ /* - This is an "external" header file. Don't give away any internals here! + This is an "external" header file. Do not give away any internals here! GOALS @@ -64,12 +66,18 @@ typedef enum { CURLM_OK, CURLM_BAD_HANDLE, /* the passed-in handle is not a valid CURLM handle */ CURLM_BAD_EASY_HANDLE, /* an easy handle was not good/valid */ - CURLM_OUT_OF_MEMORY, /* if you ever get this, you're in deep sh*t */ + CURLM_OUT_OF_MEMORY, /* if you ever get this, you are in deep sh*t */ CURLM_INTERNAL_ERROR, /* this is a libcurl bug */ CURLM_BAD_SOCKET, /* the passed in socket argument did not match */ CURLM_UNKNOWN_OPTION, /* curl_multi_setopt() with unsupported option */ CURLM_ADDED_ALREADY, /* an easy handle already added to a multi handle was attempted to get added - again */ + CURLM_RECURSIVE_API_CALL, /* an api function was called from inside a + callback */ + CURLM_WAKEUP_FAILURE, /* wakeup is unavailable or failed */ + CURLM_BAD_FUNCTION_ARGUMENT, /* function called with a bad parameter */ + CURLM_ABORTED_BY_CALLBACK, + CURLM_UNRECOVERABLE_POLL, CURLM_LAST } CURLMcode; @@ -101,7 +109,7 @@ struct CURLMsg { typedef struct CURLMsg CURLMsg; /* Based on poll(2) structure and values. - * We don't use pollfd and POLL* constants explicitly + * We do not use pollfd and POLL* constants explicitly * to cover platforms without poll(). */ #define CURL_WAIT_POLLIN 0x0001 #define CURL_WAIT_POLLPRI 0x0002 @@ -110,13 +118,13 @@ typedef struct CURLMsg CURLMsg; struct curl_waitfd { curl_socket_t fd; short events; - short revents; /* not supported yet */ + short revents; }; /* * Name: curl_multi_init() * - * Desc: inititalize multi-style curl usage + * Desc: initialize multi-style curl usage * * Returns: a new CURLM handle to use in all 'curl_multi' functions. */ @@ -171,10 +179,33 @@ CURL_EXTERN CURLMcode curl_multi_wait(CURLM *multi_handle, int timeout_ms, int *ret); +/* + * Name: curl_multi_poll() + * + * Desc: Poll on all fds within a CURLM set as well as any + * additional fds passed to the function. + * + * Returns: CURLMcode type, general multi error code. + */ +CURL_EXTERN CURLMcode curl_multi_poll(CURLM *multi_handle, + struct curl_waitfd extra_fds[], + unsigned int extra_nfds, + int timeout_ms, + int *ret); + +/* + * Name: curl_multi_wakeup() + * + * Desc: wakes up a sleeping curl_multi_poll call. + * + * Returns: CURLMcode type, general multi error code. + */ +CURL_EXTERN CURLMcode curl_multi_wakeup(CURLM *multi_handle); + /* * Name: curl_multi_perform() * - * Desc: When the app thinks there's data available for curl it calls this + * Desc: When the app thinks there is data available for curl it calls this * function to read/write whatever there is right now. This returns * as soon as the reads and writes are done. This function does not * require that there actually is data available for reading or that @@ -184,8 +215,8 @@ CURL_EXTERN CURLMcode curl_multi_wait(CURLM *multi_handle, * * Returns: CURLMcode type, general multi error code. *NOTE* that this only * returns errors etc regarding the whole multi stack. There might - * still have occurred problems on invidual transfers even when this - * returns OK. + * still have occurred problems on individual transfers even when + * this returns OK. */ CURL_EXTERN CURLMcode curl_multi_perform(CURLM *multi_handle, int *running_handles); @@ -205,7 +236,7 @@ CURL_EXTERN CURLMcode curl_multi_cleanup(CURLM *multi_handle); /* * Name: curl_multi_info_read() * - * Desc: Ask the multi handle if there's any messages/informationals from + * Desc: Ask the multi handle if there is any messages/informationals from * the individual transfers. Messages include informationals such as * error code from the transfer or just the fact that a transfer is * completed. More details on these should be written down as well. @@ -218,11 +249,11 @@ CURL_EXTERN CURLMcode curl_multi_cleanup(CURLM *multi_handle); * curl_multi_cleanup(). * * The 'CURLMsg' struct is meant to be very simple and only contain - * very basic informations. If more involved information is wanted, + * very basic information. If more involved information is wanted, * we will provide the particular "transfer handle" in that struct * and that should/could/would be used in subsequent * curl_easy_getinfo() calls (or similar). The point being that we - * must never expose complex structs to applications, as then we'll + * must never expose complex structs to applications, as then we will * undoubtably get backwards compatibility problems in the future. * * Returns: A pointer to a filled-in struct, or NULL if it failed or ran out @@ -237,10 +268,10 @@ CURL_EXTERN CURLMsg *curl_multi_info_read(CURLM *multi_handle, * Name: curl_multi_strerror() * * Desc: The curl_multi_strerror function may be used to turn a CURLMcode - * value into the equivalent human readable error string. This is + * value into the equivalent human readable error string. This is * useful for printing meaningful error messages. * - * Returns: A pointer to a zero-terminated error message. + * Returns: A pointer to a null-terminated error message. */ CURL_EXTERN const char *curl_multi_strerror(CURLMcode); @@ -251,7 +282,7 @@ CURL_EXTERN const char *curl_multi_strerror(CURLMcode); * Desc: An alternative version of curl_multi_perform() that allows the * application to pass in one of the file descriptors that have been * detected to have "action" on them and let libcurl perform. - * See man page for details. + * See manpage for details. */ #define CURL_POLL_NONE 0 #define CURL_POLL_IN 1 @@ -287,16 +318,16 @@ typedef int (*curl_multi_timer_callback)(CURLM *multi, /* multi handle */ void *userp); /* private callback pointer */ -CURL_EXTERN CURLMcode curl_multi_socket(CURLM *multi_handle, curl_socket_t s, - int *running_handles); +CURL_EXTERN CURLMcode CURL_DEPRECATED(7.19.5, "Use curl_multi_socket_action()") +curl_multi_socket(CURLM *multi_handle, curl_socket_t s, int *running_handles); CURL_EXTERN CURLMcode curl_multi_socket_action(CURLM *multi_handle, curl_socket_t s, int ev_bitmask, int *running_handles); -CURL_EXTERN CURLMcode curl_multi_socket_all(CURLM *multi_handle, - int *running_handles); +CURL_EXTERN CURLMcode CURL_DEPRECATED(7.19.5, "Use curl_multi_socket_action()") +curl_multi_socket_all(CURLM *multi_handle, int *running_handles); #ifndef CURL_ALLOW_OLD_MULTI_SOCKET /* This macro below was added in 7.16.3 to push users who recompile to use @@ -317,68 +348,56 @@ CURL_EXTERN CURLMcode curl_multi_socket_all(CURLM *multi_handle, CURL_EXTERN CURLMcode curl_multi_timeout(CURLM *multi_handle, long *milliseconds); -#undef CINIT /* re-using the same name as in curl.h */ - -#ifdef CURL_ISOCPP -#define CINIT(name,type,num) CURLMOPT_ ## name = CURLOPTTYPE_ ## type + num -#else -/* The macro "##" is ISO C, we assume pre-ISO C doesn't support it. */ -#define LONG CURLOPTTYPE_LONG -#define OBJECTPOINT CURLOPTTYPE_OBJECTPOINT -#define FUNCTIONPOINT CURLOPTTYPE_FUNCTIONPOINT -#define OFF_T CURLOPTTYPE_OFF_T -#define CINIT(name,type,number) CURLMOPT_/**/name = type + number -#endif - typedef enum { /* This is the socket callback function pointer */ - CINIT(SOCKETFUNCTION, FUNCTIONPOINT, 1), + CURLOPT(CURLMOPT_SOCKETFUNCTION, CURLOPTTYPE_FUNCTIONPOINT, 1), /* This is the argument passed to the socket callback */ - CINIT(SOCKETDATA, OBJECTPOINT, 2), + CURLOPT(CURLMOPT_SOCKETDATA, CURLOPTTYPE_OBJECTPOINT, 2), /* set to 1 to enable pipelining for this multi handle */ - CINIT(PIPELINING, LONG, 3), + CURLOPT(CURLMOPT_PIPELINING, CURLOPTTYPE_LONG, 3), /* This is the timer callback function pointer */ - CINIT(TIMERFUNCTION, FUNCTIONPOINT, 4), + CURLOPT(CURLMOPT_TIMERFUNCTION, CURLOPTTYPE_FUNCTIONPOINT, 4), /* This is the argument passed to the timer callback */ - CINIT(TIMERDATA, OBJECTPOINT, 5), + CURLOPT(CURLMOPT_TIMERDATA, CURLOPTTYPE_OBJECTPOINT, 5), /* maximum number of entries in the connection cache */ - CINIT(MAXCONNECTS, LONG, 6), + CURLOPT(CURLMOPT_MAXCONNECTS, CURLOPTTYPE_LONG, 6), /* maximum number of (pipelining) connections to one host */ - CINIT(MAX_HOST_CONNECTIONS, LONG, 7), + CURLOPT(CURLMOPT_MAX_HOST_CONNECTIONS, CURLOPTTYPE_LONG, 7), /* maximum number of requests in a pipeline */ - CINIT(MAX_PIPELINE_LENGTH, LONG, 8), + CURLOPT(CURLMOPT_MAX_PIPELINE_LENGTH, CURLOPTTYPE_LONG, 8), /* a connection with a content-length longer than this will not be considered for pipelining */ - CINIT(CONTENT_LENGTH_PENALTY_SIZE, OFF_T, 9), + CURLOPT(CURLMOPT_CONTENT_LENGTH_PENALTY_SIZE, CURLOPTTYPE_OFF_T, 9), /* a connection with a chunk length longer than this will not be considered for pipelining */ - CINIT(CHUNK_LENGTH_PENALTY_SIZE, OFF_T, 10), + CURLOPT(CURLMOPT_CHUNK_LENGTH_PENALTY_SIZE, CURLOPTTYPE_OFF_T, 10), - /* a list of site names(+port) that are blacklisted from - pipelining */ - CINIT(PIPELINING_SITE_BL, OBJECTPOINT, 11), + /* a list of site names(+port) that are blocked from pipelining */ + CURLOPT(CURLMOPT_PIPELINING_SITE_BL, CURLOPTTYPE_OBJECTPOINT, 11), - /* a list of server types that are blacklisted from - pipelining */ - CINIT(PIPELINING_SERVER_BL, OBJECTPOINT, 12), + /* a list of server types that are blocked from pipelining */ + CURLOPT(CURLMOPT_PIPELINING_SERVER_BL, CURLOPTTYPE_OBJECTPOINT, 12), /* maximum number of open connections in total */ - CINIT(MAX_TOTAL_CONNECTIONS, LONG, 13), + CURLOPT(CURLMOPT_MAX_TOTAL_CONNECTIONS, CURLOPTTYPE_LONG, 13), /* This is the server push callback function pointer */ - CINIT(PUSHFUNCTION, FUNCTIONPOINT, 14), + CURLOPT(CURLMOPT_PUSHFUNCTION, CURLOPTTYPE_FUNCTIONPOINT, 14), /* This is the argument passed to the server push callback */ - CINIT(PUSHDATA, OBJECTPOINT, 15), + CURLOPT(CURLMOPT_PUSHDATA, CURLOPTTYPE_OBJECTPOINT, 15), + + /* maximum number of concurrent streams to support on a connection */ + CURLOPT(CURLMOPT_MAX_CONCURRENT_STREAMS, CURLOPTTYPE_LONG, 16), CURLMOPT_LASTENTRY /* the last unused */ } CURLMoption; @@ -407,17 +426,30 @@ CURL_EXTERN CURLMcode curl_multi_setopt(CURLM *multi_handle, CURL_EXTERN CURLMcode curl_multi_assign(CURLM *multi_handle, curl_socket_t sockfd, void *sockp); +/* + * Name: curl_multi_get_handles() + * + * Desc: Returns an allocated array holding all handles currently added to + * the multi handle. Marks the final entry with a NULL pointer. If + * there is no easy handle added to the multi handle, this function + * returns an array with the first entry as a NULL pointer. + * + * Returns: NULL on failure, otherwise a CURL **array pointer + */ +CURL_EXTERN CURL **curl_multi_get_handles(CURLM *multi_handle); /* * Name: curl_push_callback * * Desc: This callback gets called when a new stream is being pushed by the - * server. It approves or denies the new stream. + * server. It approves or denies the new stream. It can also decide + * to completely fail the connection. * - * Returns: CURL_PUSH_OK or CURL_PUSH_DENY. + * Returns: CURL_PUSH_OK, CURL_PUSH_DENY or CURL_PUSH_ERROROUT */ -#define CURL_PUSH_OK 0 -#define CURL_PUSH_DENY 1 +#define CURL_PUSH_OK 0 +#define CURL_PUSH_DENY 1 +#define CURL_PUSH_ERROROUT 2 /* added in 7.72.0 */ struct curl_pushheaders; /* forward declaration only */ @@ -432,6 +464,20 @@ typedef int (*curl_push_callback)(CURL *parent, struct curl_pushheaders *headers, void *userp); +/* + * Name: curl_multi_waitfds() + * + * Desc: Ask curl for fds for polling. The app can use these to poll on. + * We want curl_multi_perform() called as soon as one of them are + * ready. Passing zero size allows to get just a number of fds. + * + * Returns: CURLMcode type, general multi error code. + */ +CURL_EXTERN CURLMcode curl_multi_waitfds(CURLM *multi, + struct curl_waitfd *ufds, + unsigned int size, + unsigned int *fd_count); + #ifdef __cplusplus } /* end of extern "C" */ #endif diff --git a/contrib/curl/include/curl/options.h b/contrib/curl/include/curl/options.h new file mode 100644 index 0000000000..1ed76a95c6 --- /dev/null +++ b/contrib/curl/include/curl/options.h @@ -0,0 +1,70 @@ +#ifndef CURLINC_OPTIONS_H +#define CURLINC_OPTIONS_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + CURLOT_LONG, /* long (a range of values) */ + CURLOT_VALUES, /* (a defined set or bitmask) */ + CURLOT_OFF_T, /* curl_off_t (a range of values) */ + CURLOT_OBJECT, /* pointer (void *) */ + CURLOT_STRING, /* (char * to null-terminated buffer) */ + CURLOT_SLIST, /* (struct curl_slist *) */ + CURLOT_CBPTR, /* (void * passed as-is to a callback) */ + CURLOT_BLOB, /* blob (struct curl_blob *) */ + CURLOT_FUNCTION /* function pointer */ +} curl_easytype; + +/* Flag bits */ + +/* "alias" means it is provided for old programs to remain functional, + we prefer another name */ +#define CURLOT_FLAG_ALIAS (1<<0) + +/* The CURLOPTTYPE_* id ranges can still be used to figure out what type/size + to use for curl_easy_setopt() for the given id */ +struct curl_easyoption { + const char *name; + CURLoption id; + curl_easytype type; + unsigned int flags; +}; + +CURL_EXTERN const struct curl_easyoption * +curl_easy_option_by_name(const char *name); + +CURL_EXTERN const struct curl_easyoption * +curl_easy_option_by_id(CURLoption id); + +CURL_EXTERN const struct curl_easyoption * +curl_easy_option_next(const struct curl_easyoption *prev); + +#ifdef __cplusplus +} /* end of extern "C" */ +#endif +#endif /* CURLINC_OPTIONS_H */ diff --git a/contrib/curl/include/curl/stdcheaders.h b/contrib/curl/include/curl/stdcheaders.h index 027b6f4211..7451aa3052 100644 --- a/contrib/curl/include/curl/stdcheaders.h +++ b/contrib/curl/include/curl/stdcheaders.h @@ -1,5 +1,5 @@ -#ifndef __STDC_HEADERS_H -#define __STDC_HEADERS_H +#ifndef CURLINC_STDCHEADERS_H +#define CURLINC_STDCHEADERS_H /*************************************************************************** * _ _ ____ _ * Project ___| | | | _ \| | @@ -7,11 +7,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2016, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -20,6 +20,8 @@ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY * KIND, either express or implied. * + * SPDX-License-Identifier: curl + * ***************************************************************************/ #include @@ -30,4 +32,4 @@ size_t fwrite(const void *, size_t, size_t, FILE *); int strcasecmp(const char *, const char *); int strncasecmp(const char *, const char *, size_t); -#endif /* __STDC_HEADERS_H */ +#endif /* CURLINC_STDCHEADERS_H */ diff --git a/contrib/curl/include/curl/system.h b/contrib/curl/include/curl/system.h new file mode 100644 index 0000000000..e5be256845 --- /dev/null +++ b/contrib/curl/include/curl/system.h @@ -0,0 +1,496 @@ +#ifndef CURLINC_SYSTEM_H +#define CURLINC_SYSTEM_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +/* + * Try to keep one section per platform, compiler and architecture, otherwise, + * if an existing section is reused for a different one and later on the + * original is adjusted, probably the piggybacking one can be adversely + * changed. + * + * In order to differentiate between platforms/compilers/architectures use + * only compiler built-in predefined preprocessor symbols. + * + * curl_off_t + * ---------- + * + * For any given platform/compiler curl_off_t must be typedef'ed to a 64-bit + * wide signed integral data type. The width of this data type must remain + * constant and independent of any possible large file support settings. + * + * As an exception to the above, curl_off_t shall be typedef'ed to a 32-bit + * wide signed integral data type if there is no 64-bit type. + * + * As a general rule, curl_off_t shall not be mapped to off_t. This rule shall + * only be violated if off_t is the only 64-bit data type available and the + * size of off_t is independent of large file support settings. Keep your + * build on the safe side avoiding an off_t gating. If you have a 64-bit + * off_t then take for sure that another 64-bit data type exists, dig deeper + * and you will find it. + * + */ + +#if defined(__DJGPP__) || defined(__GO32__) +# if defined(__DJGPP__) && (__DJGPP__ > 1) +# define CURL_TYPEOF_CURL_OFF_T long long +# define CURL_FORMAT_CURL_OFF_T "lld" +# define CURL_FORMAT_CURL_OFF_TU "llu" +# define CURL_SUFFIX_CURL_OFF_T LL +# define CURL_SUFFIX_CURL_OFF_TU ULL +# else +# define CURL_TYPEOF_CURL_OFF_T long +# define CURL_FORMAT_CURL_OFF_T "ld" +# define CURL_FORMAT_CURL_OFF_TU "lu" +# define CURL_SUFFIX_CURL_OFF_T L +# define CURL_SUFFIX_CURL_OFF_TU UL +# endif +# define CURL_TYPEOF_CURL_SOCKLEN_T int + +#elif defined(__SALFORDC__) +# define CURL_TYPEOF_CURL_OFF_T long +# define CURL_FORMAT_CURL_OFF_T "ld" +# define CURL_FORMAT_CURL_OFF_TU "lu" +# define CURL_SUFFIX_CURL_OFF_T L +# define CURL_SUFFIX_CURL_OFF_TU UL +# define CURL_TYPEOF_CURL_SOCKLEN_T int + +#elif defined(__BORLANDC__) +# if (__BORLANDC__ < 0x520) +# define CURL_TYPEOF_CURL_OFF_T long +# define CURL_FORMAT_CURL_OFF_T "ld" +# define CURL_FORMAT_CURL_OFF_TU "lu" +# define CURL_SUFFIX_CURL_OFF_T L +# define CURL_SUFFIX_CURL_OFF_TU UL +# else +# define CURL_TYPEOF_CURL_OFF_T __int64 +# define CURL_FORMAT_CURL_OFF_T "I64d" +# define CURL_FORMAT_CURL_OFF_TU "I64u" +# define CURL_SUFFIX_CURL_OFF_T i64 +# define CURL_SUFFIX_CURL_OFF_TU ui64 +# endif +# define CURL_TYPEOF_CURL_SOCKLEN_T int + +#elif defined(__TURBOC__) +# define CURL_TYPEOF_CURL_OFF_T long +# define CURL_FORMAT_CURL_OFF_T "ld" +# define CURL_FORMAT_CURL_OFF_TU "lu" +# define CURL_SUFFIX_CURL_OFF_T L +# define CURL_SUFFIX_CURL_OFF_TU UL +# define CURL_TYPEOF_CURL_SOCKLEN_T int + +#elif defined(__POCC__) +# if (__POCC__ < 280) +# define CURL_TYPEOF_CURL_OFF_T long +# define CURL_FORMAT_CURL_OFF_T "ld" +# define CURL_FORMAT_CURL_OFF_TU "lu" +# define CURL_SUFFIX_CURL_OFF_T L +# define CURL_SUFFIX_CURL_OFF_TU UL +# elif defined(_MSC_VER) +# define CURL_TYPEOF_CURL_OFF_T __int64 +# define CURL_FORMAT_CURL_OFF_T "I64d" +# define CURL_FORMAT_CURL_OFF_TU "I64u" +# define CURL_SUFFIX_CURL_OFF_T i64 +# define CURL_SUFFIX_CURL_OFF_TU ui64 +# else +# define CURL_TYPEOF_CURL_OFF_T long long +# define CURL_FORMAT_CURL_OFF_T "lld" +# define CURL_FORMAT_CURL_OFF_TU "llu" +# define CURL_SUFFIX_CURL_OFF_T LL +# define CURL_SUFFIX_CURL_OFF_TU ULL +# endif +# define CURL_TYPEOF_CURL_SOCKLEN_T int + +#elif defined(__LCC__) +# if defined(__MCST__) /* MCST eLbrus Compiler Collection */ +# define CURL_TYPEOF_CURL_OFF_T long +# define CURL_FORMAT_CURL_OFF_T "ld" +# define CURL_FORMAT_CURL_OFF_TU "lu" +# define CURL_SUFFIX_CURL_OFF_T L +# define CURL_SUFFIX_CURL_OFF_TU UL +# define CURL_TYPEOF_CURL_SOCKLEN_T socklen_t +# define CURL_PULL_SYS_TYPES_H 1 +# define CURL_PULL_SYS_SOCKET_H 1 +# else /* Local (or Little) C Compiler */ +# define CURL_TYPEOF_CURL_OFF_T long +# define CURL_FORMAT_CURL_OFF_T "ld" +# define CURL_FORMAT_CURL_OFF_TU "lu" +# define CURL_SUFFIX_CURL_OFF_T L +# define CURL_SUFFIX_CURL_OFF_TU UL +# define CURL_TYPEOF_CURL_SOCKLEN_T int +# endif + +#elif defined(macintosh) +# include +# if TYPE_LONGLONG +# define CURL_TYPEOF_CURL_OFF_T long long +# define CURL_FORMAT_CURL_OFF_T "lld" +# define CURL_FORMAT_CURL_OFF_TU "llu" +# define CURL_SUFFIX_CURL_OFF_T LL +# define CURL_SUFFIX_CURL_OFF_TU ULL +# else +# define CURL_TYPEOF_CURL_OFF_T long +# define CURL_FORMAT_CURL_OFF_T "ld" +# define CURL_FORMAT_CURL_OFF_TU "lu" +# define CURL_SUFFIX_CURL_OFF_T L +# define CURL_SUFFIX_CURL_OFF_TU UL +# endif +# define CURL_TYPEOF_CURL_SOCKLEN_T unsigned int + +#elif defined(__TANDEM) +# if ! defined(__LP64) + /* Required for 32-bit NonStop builds only. */ +# define CURL_TYPEOF_CURL_OFF_T long long +# define CURL_FORMAT_CURL_OFF_T "lld" +# define CURL_FORMAT_CURL_OFF_TU "llu" +# define CURL_SUFFIX_CURL_OFF_T LL +# define CURL_SUFFIX_CURL_OFF_TU ULL +# define CURL_TYPEOF_CURL_SOCKLEN_T int +# endif + +#elif defined(_WIN32_WCE) +# define CURL_TYPEOF_CURL_OFF_T __int64 +# define CURL_FORMAT_CURL_OFF_T "I64d" +# define CURL_FORMAT_CURL_OFF_TU "I64u" +# define CURL_SUFFIX_CURL_OFF_T i64 +# define CURL_SUFFIX_CURL_OFF_TU ui64 +# define CURL_TYPEOF_CURL_SOCKLEN_T int + +#elif defined(__MINGW32__) +# include +# define CURL_TYPEOF_CURL_OFF_T long long +# define CURL_FORMAT_CURL_OFF_T PRId64 +# define CURL_FORMAT_CURL_OFF_TU PRIu64 +# define CURL_SUFFIX_CURL_OFF_T LL +# define CURL_SUFFIX_CURL_OFF_TU ULL +# define CURL_TYPEOF_CURL_SOCKLEN_T int +# define CURL_PULL_SYS_TYPES_H 1 + +#elif defined(__VMS) +# if defined(__VAX) +# define CURL_TYPEOF_CURL_OFF_T long +# define CURL_FORMAT_CURL_OFF_T "ld" +# define CURL_FORMAT_CURL_OFF_TU "lu" +# define CURL_SUFFIX_CURL_OFF_T L +# define CURL_SUFFIX_CURL_OFF_TU UL +# else +# define CURL_TYPEOF_CURL_OFF_T long long +# define CURL_FORMAT_CURL_OFF_T "lld" +# define CURL_FORMAT_CURL_OFF_TU "llu" +# define CURL_SUFFIX_CURL_OFF_T LL +# define CURL_SUFFIX_CURL_OFF_TU ULL +# endif +# define CURL_TYPEOF_CURL_SOCKLEN_T unsigned int + +#elif defined(__OS400__) +# define CURL_TYPEOF_CURL_OFF_T long long +# define CURL_FORMAT_CURL_OFF_T "lld" +# define CURL_FORMAT_CURL_OFF_TU "llu" +# define CURL_SUFFIX_CURL_OFF_T LL +# define CURL_SUFFIX_CURL_OFF_TU ULL +# define CURL_TYPEOF_CURL_SOCKLEN_T socklen_t +# define CURL_PULL_SYS_TYPES_H 1 +# define CURL_PULL_SYS_SOCKET_H 1 + +#elif defined(__MVS__) +# if defined(_LONG_LONG) +# define CURL_TYPEOF_CURL_OFF_T long long +# define CURL_FORMAT_CURL_OFF_T "lld" +# define CURL_FORMAT_CURL_OFF_TU "llu" +# define CURL_SUFFIX_CURL_OFF_T LL +# define CURL_SUFFIX_CURL_OFF_TU ULL +# elif defined(_LP64) +# define CURL_TYPEOF_CURL_OFF_T long +# define CURL_FORMAT_CURL_OFF_T "ld" +# define CURL_FORMAT_CURL_OFF_TU "lu" +# define CURL_SUFFIX_CURL_OFF_T L +# define CURL_SUFFIX_CURL_OFF_TU UL +# else +# define CURL_TYPEOF_CURL_OFF_T long +# define CURL_FORMAT_CURL_OFF_T "ld" +# define CURL_FORMAT_CURL_OFF_TU "lu" +# define CURL_SUFFIX_CURL_OFF_T L +# define CURL_SUFFIX_CURL_OFF_TU UL +# endif +# define CURL_TYPEOF_CURL_SOCKLEN_T socklen_t +# define CURL_PULL_SYS_TYPES_H 1 +# define CURL_PULL_SYS_SOCKET_H 1 + +#elif defined(__370__) +# if defined(__IBMC__) || defined(__IBMCPP__) +# if defined(_ILP32) +# elif defined(_LP64) +# endif +# if defined(_LONG_LONG) +# define CURL_TYPEOF_CURL_OFF_T long long +# define CURL_FORMAT_CURL_OFF_T "lld" +# define CURL_FORMAT_CURL_OFF_TU "llu" +# define CURL_SUFFIX_CURL_OFF_T LL +# define CURL_SUFFIX_CURL_OFF_TU ULL +# elif defined(_LP64) +# define CURL_TYPEOF_CURL_OFF_T long +# define CURL_FORMAT_CURL_OFF_T "ld" +# define CURL_FORMAT_CURL_OFF_TU "lu" +# define CURL_SUFFIX_CURL_OFF_T L +# define CURL_SUFFIX_CURL_OFF_TU UL +# else +# define CURL_TYPEOF_CURL_OFF_T long +# define CURL_FORMAT_CURL_OFF_T "ld" +# define CURL_FORMAT_CURL_OFF_TU "lu" +# define CURL_SUFFIX_CURL_OFF_T L +# define CURL_SUFFIX_CURL_OFF_TU UL +# endif +# define CURL_TYPEOF_CURL_SOCKLEN_T socklen_t +# define CURL_PULL_SYS_TYPES_H 1 +# define CURL_PULL_SYS_SOCKET_H 1 +# endif + +#elif defined(TPF) +# define CURL_TYPEOF_CURL_OFF_T long +# define CURL_FORMAT_CURL_OFF_T "ld" +# define CURL_FORMAT_CURL_OFF_TU "lu" +# define CURL_SUFFIX_CURL_OFF_T L +# define CURL_SUFFIX_CURL_OFF_TU UL +# define CURL_TYPEOF_CURL_SOCKLEN_T int + +#elif defined(__TINYC__) /* also known as tcc */ +# define CURL_TYPEOF_CURL_OFF_T long long +# define CURL_FORMAT_CURL_OFF_T "lld" +# define CURL_FORMAT_CURL_OFF_TU "llu" +# define CURL_SUFFIX_CURL_OFF_T LL +# define CURL_SUFFIX_CURL_OFF_TU ULL +# define CURL_TYPEOF_CURL_SOCKLEN_T socklen_t +# define CURL_PULL_SYS_TYPES_H 1 +# define CURL_PULL_SYS_SOCKET_H 1 + +#elif defined(__SUNPRO_C) || defined(__SUNPRO_CC) /* Oracle Solaris Studio */ +# if !defined(__LP64) && (defined(__ILP32) || \ + defined(__i386) || \ + defined(__sparcv8) || \ + defined(__sparcv8plus)) +# define CURL_TYPEOF_CURL_OFF_T long long +# define CURL_FORMAT_CURL_OFF_T "lld" +# define CURL_FORMAT_CURL_OFF_TU "llu" +# define CURL_SUFFIX_CURL_OFF_T LL +# define CURL_SUFFIX_CURL_OFF_TU ULL +# elif defined(__LP64) || \ + defined(__amd64) || defined(__sparcv9) +# define CURL_TYPEOF_CURL_OFF_T long +# define CURL_FORMAT_CURL_OFF_T "ld" +# define CURL_FORMAT_CURL_OFF_TU "lu" +# define CURL_SUFFIX_CURL_OFF_T L +# define CURL_SUFFIX_CURL_OFF_TU UL +# endif +# define CURL_TYPEOF_CURL_SOCKLEN_T socklen_t +# define CURL_PULL_SYS_TYPES_H 1 +# define CURL_PULL_SYS_SOCKET_H 1 + +#elif defined(__xlc__) /* IBM xlc compiler */ +# if !defined(_LP64) +# define CURL_TYPEOF_CURL_OFF_T long long +# define CURL_FORMAT_CURL_OFF_T "lld" +# define CURL_FORMAT_CURL_OFF_TU "llu" +# define CURL_SUFFIX_CURL_OFF_T LL +# define CURL_SUFFIX_CURL_OFF_TU ULL +# else +# define CURL_TYPEOF_CURL_OFF_T long +# define CURL_FORMAT_CURL_OFF_T "ld" +# define CURL_FORMAT_CURL_OFF_TU "lu" +# define CURL_SUFFIX_CURL_OFF_T L +# define CURL_SUFFIX_CURL_OFF_TU UL +# endif +# define CURL_TYPEOF_CURL_SOCKLEN_T socklen_t +# define CURL_PULL_SYS_TYPES_H 1 +# define CURL_PULL_SYS_SOCKET_H 1 + +#elif defined(__hpux) /* HP aCC compiler */ +# if !defined(_LP64) +# define CURL_TYPEOF_CURL_OFF_T long long +# define CURL_FORMAT_CURL_OFF_T "lld" +# define CURL_FORMAT_CURL_OFF_TU "llu" +# define CURL_SUFFIX_CURL_OFF_T LL +# define CURL_SUFFIX_CURL_OFF_TU ULL +# else +# define CURL_TYPEOF_CURL_OFF_T long +# define CURL_FORMAT_CURL_OFF_T "ld" +# define CURL_FORMAT_CURL_OFF_TU "lu" +# define CURL_SUFFIX_CURL_OFF_T L +# define CURL_SUFFIX_CURL_OFF_TU UL +# endif +# define CURL_TYPEOF_CURL_SOCKLEN_T socklen_t +# define CURL_PULL_SYS_TYPES_H 1 +# define CURL_PULL_SYS_SOCKET_H 1 + +/* ===================================== */ +/* KEEP MSVC THE PENULTIMATE ENTRY */ +/* ===================================== */ + +#elif defined(_MSC_VER) +# if (_MSC_VER >= 1800) +# include +# define CURL_TYPEOF_CURL_OFF_T __int64 +# define CURL_FORMAT_CURL_OFF_T PRId64 +# define CURL_FORMAT_CURL_OFF_TU PRIu64 +# define CURL_SUFFIX_CURL_OFF_T i64 +# define CURL_SUFFIX_CURL_OFF_TU ui64 +# elif (_MSC_VER >= 900) && (_INTEGRAL_MAX_BITS >= 64) +# define CURL_TYPEOF_CURL_OFF_T __int64 +# define CURL_FORMAT_CURL_OFF_T "I64d" +# define CURL_FORMAT_CURL_OFF_TU "I64u" +# define CURL_SUFFIX_CURL_OFF_T i64 +# define CURL_SUFFIX_CURL_OFF_TU ui64 +# else +# define CURL_TYPEOF_CURL_OFF_T long +# define CURL_FORMAT_CURL_OFF_T "ld" +# define CURL_FORMAT_CURL_OFF_TU "lu" +# define CURL_SUFFIX_CURL_OFF_T L +# define CURL_SUFFIX_CURL_OFF_TU UL +# endif +# define CURL_TYPEOF_CURL_SOCKLEN_T int + +/* ===================================== */ +/* KEEP GENERIC GCC THE LAST ENTRY */ +/* ===================================== */ + +#elif defined(__GNUC__) && !defined(_SCO_DS) +# if !defined(__LP64__) && \ + (defined(__ILP32__) || defined(__i386__) || defined(__hppa__) || \ + defined(__ppc__) || defined(__powerpc__) || defined(__arm__) || \ + defined(__sparc__) || defined(__mips__) || defined(__sh__) || \ + defined(__XTENSA__) || \ + (defined(__SIZEOF_LONG__) && __SIZEOF_LONG__ == 4) || \ + (defined(__LONG_MAX__) && __LONG_MAX__ == 2147483647L)) +# define CURL_TYPEOF_CURL_OFF_T long long +# define CURL_FORMAT_CURL_OFF_T "lld" +# define CURL_FORMAT_CURL_OFF_TU "llu" +# define CURL_SUFFIX_CURL_OFF_T LL +# define CURL_SUFFIX_CURL_OFF_TU ULL +# elif defined(__LP64__) || \ + defined(__x86_64__) || defined(__ppc64__) || defined(__sparc64__) || \ + defined(__e2k__) || \ + (defined(__SIZEOF_LONG__) && __SIZEOF_LONG__ == 8) || \ + (defined(__LONG_MAX__) && __LONG_MAX__ == 9223372036854775807L) +# define CURL_TYPEOF_CURL_OFF_T long +# define CURL_FORMAT_CURL_OFF_T "ld" +# define CURL_FORMAT_CURL_OFF_TU "lu" +# define CURL_SUFFIX_CURL_OFF_T L +# define CURL_SUFFIX_CURL_OFF_TU UL +# endif +# define CURL_TYPEOF_CURL_SOCKLEN_T socklen_t +# define CURL_PULL_SYS_TYPES_H 1 +# define CURL_PULL_SYS_SOCKET_H 1 + +#else +/* generic "safe guess" on old 32-bit style */ +# define CURL_TYPEOF_CURL_OFF_T long +# define CURL_FORMAT_CURL_OFF_T "ld" +# define CURL_FORMAT_CURL_OFF_TU "lu" +# define CURL_SUFFIX_CURL_OFF_T L +# define CURL_SUFFIX_CURL_OFF_TU UL +# define CURL_TYPEOF_CURL_SOCKLEN_T int +#endif + +#ifdef _AIX +/* AIX needs */ +#define CURL_PULL_SYS_POLL_H +#endif + +/* CURL_PULL_SYS_TYPES_H is defined above when inclusion of header file */ +/* sys/types.h is required here to properly make type definitions below. */ +#ifdef CURL_PULL_SYS_TYPES_H +# include +#endif + +/* CURL_PULL_SYS_SOCKET_H is defined above when inclusion of header file */ +/* sys/socket.h is required here to properly make type definitions below. */ +#ifdef CURL_PULL_SYS_SOCKET_H +# include +#endif + +/* CURL_PULL_SYS_POLL_H is defined above when inclusion of header file */ +/* sys/poll.h is required here to properly make type definitions below. */ +#ifdef CURL_PULL_SYS_POLL_H +# include +#endif + +/* Data type definition of curl_socklen_t. */ +#ifdef CURL_TYPEOF_CURL_SOCKLEN_T + typedef CURL_TYPEOF_CURL_SOCKLEN_T curl_socklen_t; +#endif + +/* Data type definition of curl_off_t. */ + +#ifdef CURL_TYPEOF_CURL_OFF_T + typedef CURL_TYPEOF_CURL_OFF_T curl_off_t; +#endif + +/* + * CURL_ISOCPP and CURL_OFF_T_C definitions are done here in order to allow + * these to be visible and exported by the external libcurl interface API, + * while also making them visible to the library internals, simply including + * curl_setup.h, without actually needing to include curl.h internally. + * If some day this section would grow big enough, all this should be moved + * to its own header file. + */ + +/* + * Figure out if we can use the ## preprocessor operator, which is supported + * by ISO/ANSI C and C++. Some compilers support it without setting __STDC__ + * or __cplusplus so we need to carefully check for them too. + */ + +#if defined(__STDC__) || defined(_MSC_VER) || defined(__cplusplus) || \ + defined(__HP_aCC) || defined(__BORLANDC__) || defined(__LCC__) || \ + defined(__POCC__) || defined(__SALFORDC__) || defined(__HIGHC__) || \ + defined(__ILEC400__) + /* This compiler is believed to have an ISO compatible preprocessor */ +#define CURL_ISOCPP +#else + /* This compiler is believed NOT to have an ISO compatible preprocessor */ +#undef CURL_ISOCPP +#endif + +/* + * Macros for minimum-width signed and unsigned curl_off_t integer constants. + */ + +#if defined(__BORLANDC__) && (__BORLANDC__ == 0x0551) +# define CURLINC_OFF_T_C_HLPR2(x) x +# define CURLINC_OFF_T_C_HLPR1(x) CURLINC_OFF_T_C_HLPR2(x) +# define CURL_OFF_T_C(Val) CURLINC_OFF_T_C_HLPR1(Val) ## \ + CURLINC_OFF_T_C_HLPR1(CURL_SUFFIX_CURL_OFF_T) +# define CURL_OFF_TU_C(Val) CURLINC_OFF_T_C_HLPR1(Val) ## \ + CURLINC_OFF_T_C_HLPR1(CURL_SUFFIX_CURL_OFF_TU) +#else +# ifdef CURL_ISOCPP +# define CURLINC_OFF_T_C_HLPR2(Val,Suffix) Val ## Suffix +# else +# define CURLINC_OFF_T_C_HLPR2(Val,Suffix) Val/**/Suffix +# endif +# define CURLINC_OFF_T_C_HLPR1(Val,Suffix) CURLINC_OFF_T_C_HLPR2(Val,Suffix) +# define CURL_OFF_T_C(Val) CURLINC_OFF_T_C_HLPR1(Val,CURL_SUFFIX_CURL_OFF_T) +# define CURL_OFF_TU_C(Val) CURLINC_OFF_T_C_HLPR1(Val,CURL_SUFFIX_CURL_OFF_TU) +#endif + +#endif /* CURLINC_SYSTEM_H */ diff --git a/contrib/curl/include/curl/typecheck-gcc.h b/contrib/curl/include/curl/typecheck-gcc.h index 3d683152b6..e532e6997d 100644 --- a/contrib/curl/include/curl/typecheck-gcc.h +++ b/contrib/curl/include/curl/typecheck-gcc.h @@ -1,5 +1,5 @@ -#ifndef __CURL_TYPECHECK_GCC_H -#define __CURL_TYPECHECK_GCC_H +#ifndef CURLINC_TYPECHECK_GCC_H +#define CURLINC_TYPECHECK_GCC_H /*************************************************************************** * _ _ ____ _ * Project ___| | | | _ \| | @@ -7,11 +7,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2016, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -20,117 +20,139 @@ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY * KIND, either express or implied. * + * SPDX-License-Identifier: curl + * ***************************************************************************/ /* wraps curl_easy_setopt() with typechecking */ /* To add a new kind of warning, add an - * if(_curl_is_sometype_option(_curl_opt)) - * if(!_curl_is_sometype(value)) + * if(curlcheck_sometype_option(_curl_opt)) + * if(!curlcheck_sometype(value)) * _curl_easy_setopt_err_sometype(); - * block and define _curl_is_sometype_option, _curl_is_sometype and + * block and define curlcheck_sometype_option, curlcheck_sometype and * _curl_easy_setopt_err_sometype below * * NOTE: We use two nested 'if' statements here instead of the && operator, in - * order to work around gcc bug #32061. It affects only gcc 4.3.x/4.4.x + * order to work around gcc bug #32061. It affects only gcc 4.3.x/4.4.x * when compiling with -Wlogical-op. * - * To add an option that uses the same type as an existing option, you'll just - * need to extend the appropriate _curl_*_option macro + * To add an option that uses the same type as an existing option, you will + * just need to extend the appropriate _curl_*_option macro */ -#define curl_easy_setopt(handle, option, value) \ -__extension__ ({ \ - __typeof__(option) _curl_opt = option; \ - if(__builtin_constant_p(_curl_opt)) { \ - if(_curl_is_long_option(_curl_opt)) \ - if(!_curl_is_long(value)) \ - _curl_easy_setopt_err_long(); \ - if(_curl_is_off_t_option(_curl_opt)) \ - if(!_curl_is_off_t(value)) \ - _curl_easy_setopt_err_curl_off_t(); \ - if(_curl_is_string_option(_curl_opt)) \ - if(!_curl_is_string(value)) \ - _curl_easy_setopt_err_string(); \ - if(_curl_is_write_cb_option(_curl_opt)) \ - if(!_curl_is_write_cb(value)) \ - _curl_easy_setopt_err_write_callback(); \ - if((_curl_opt) == CURLOPT_READFUNCTION) \ - if(!_curl_is_read_cb(value)) \ - _curl_easy_setopt_err_read_cb(); \ - if((_curl_opt) == CURLOPT_IOCTLFUNCTION) \ - if(!_curl_is_ioctl_cb(value)) \ - _curl_easy_setopt_err_ioctl_cb(); \ - if((_curl_opt) == CURLOPT_SOCKOPTFUNCTION) \ - if(!_curl_is_sockopt_cb(value)) \ - _curl_easy_setopt_err_sockopt_cb(); \ - if((_curl_opt) == CURLOPT_OPENSOCKETFUNCTION) \ - if(!_curl_is_opensocket_cb(value)) \ - _curl_easy_setopt_err_opensocket_cb(); \ - if((_curl_opt) == CURLOPT_PROGRESSFUNCTION) \ - if(!_curl_is_progress_cb(value)) \ - _curl_easy_setopt_err_progress_cb(); \ - if((_curl_opt) == CURLOPT_DEBUGFUNCTION) \ - if(!_curl_is_debug_cb(value)) \ - _curl_easy_setopt_err_debug_cb(); \ - if((_curl_opt) == CURLOPT_SSL_CTX_FUNCTION) \ - if(!_curl_is_ssl_ctx_cb(value)) \ - _curl_easy_setopt_err_ssl_ctx_cb(); \ - if(_curl_is_conv_cb_option(_curl_opt)) \ - if(!_curl_is_conv_cb(value)) \ - _curl_easy_setopt_err_conv_cb(); \ - if((_curl_opt) == CURLOPT_SEEKFUNCTION) \ - if(!_curl_is_seek_cb(value)) \ - _curl_easy_setopt_err_seek_cb(); \ - if(_curl_is_cb_data_option(_curl_opt)) \ - if(!_curl_is_cb_data(value)) \ - _curl_easy_setopt_err_cb_data(); \ - if((_curl_opt) == CURLOPT_ERRORBUFFER) \ - if(!_curl_is_error_buffer(value)) \ - _curl_easy_setopt_err_error_buffer(); \ - if((_curl_opt) == CURLOPT_STDERR) \ - if(!_curl_is_FILE(value)) \ - _curl_easy_setopt_err_FILE(); \ - if(_curl_is_postfields_option(_curl_opt)) \ - if(!_curl_is_postfields(value)) \ - _curl_easy_setopt_err_postfields(); \ - if((_curl_opt) == CURLOPT_HTTPPOST) \ - if(!_curl_is_arr((value), struct curl_httppost)) \ - _curl_easy_setopt_err_curl_httpost(); \ - if(_curl_is_slist_option(_curl_opt)) \ - if(!_curl_is_arr((value), struct curl_slist)) \ - _curl_easy_setopt_err_curl_slist(); \ - if((_curl_opt) == CURLOPT_SHARE) \ - if(!_curl_is_ptr((value), CURLSH)) \ - _curl_easy_setopt_err_CURLSH(); \ - } \ - curl_easy_setopt(handle, _curl_opt, value); \ -}) +#define curl_easy_setopt(handle, option, value) \ + __extension__({ \ + CURLoption _curl_opt = (option); \ + if(__builtin_constant_p(_curl_opt)) { \ + CURL_IGNORE_DEPRECATION( \ + if(curlcheck_long_option(_curl_opt)) \ + if(!curlcheck_long(value)) \ + _curl_easy_setopt_err_long(); \ + if(curlcheck_off_t_option(_curl_opt)) \ + if(!curlcheck_off_t(value)) \ + _curl_easy_setopt_err_curl_off_t(); \ + if(curlcheck_string_option(_curl_opt)) \ + if(!curlcheck_string(value)) \ + _curl_easy_setopt_err_string(); \ + if(curlcheck_write_cb_option(_curl_opt)) \ + if(!curlcheck_write_cb(value)) \ + _curl_easy_setopt_err_write_callback(); \ + if((_curl_opt) == CURLOPT_RESOLVER_START_FUNCTION) \ + if(!curlcheck_resolver_start_callback(value)) \ + _curl_easy_setopt_err_resolver_start_callback(); \ + if((_curl_opt) == CURLOPT_READFUNCTION) \ + if(!curlcheck_read_cb(value)) \ + _curl_easy_setopt_err_read_cb(); \ + if((_curl_opt) == CURLOPT_IOCTLFUNCTION) \ + if(!curlcheck_ioctl_cb(value)) \ + _curl_easy_setopt_err_ioctl_cb(); \ + if((_curl_opt) == CURLOPT_SOCKOPTFUNCTION) \ + if(!curlcheck_sockopt_cb(value)) \ + _curl_easy_setopt_err_sockopt_cb(); \ + if((_curl_opt) == CURLOPT_OPENSOCKETFUNCTION) \ + if(!curlcheck_opensocket_cb(value)) \ + _curl_easy_setopt_err_opensocket_cb(); \ + if((_curl_opt) == CURLOPT_PROGRESSFUNCTION) \ + if(!curlcheck_progress_cb(value)) \ + _curl_easy_setopt_err_progress_cb(); \ + if((_curl_opt) == CURLOPT_DEBUGFUNCTION) \ + if(!curlcheck_debug_cb(value)) \ + _curl_easy_setopt_err_debug_cb(); \ + if((_curl_opt) == CURLOPT_SSL_CTX_FUNCTION) \ + if(!curlcheck_ssl_ctx_cb(value)) \ + _curl_easy_setopt_err_ssl_ctx_cb(); \ + if(curlcheck_conv_cb_option(_curl_opt)) \ + if(!curlcheck_conv_cb(value)) \ + _curl_easy_setopt_err_conv_cb(); \ + if((_curl_opt) == CURLOPT_SEEKFUNCTION) \ + if(!curlcheck_seek_cb(value)) \ + _curl_easy_setopt_err_seek_cb(); \ + if(curlcheck_cb_data_option(_curl_opt)) \ + if(!curlcheck_cb_data(value)) \ + _curl_easy_setopt_err_cb_data(); \ + if((_curl_opt) == CURLOPT_ERRORBUFFER) \ + if(!curlcheck_error_buffer(value)) \ + _curl_easy_setopt_err_error_buffer(); \ + if((_curl_opt) == CURLOPT_STDERR) \ + if(!curlcheck_FILE(value)) \ + _curl_easy_setopt_err_FILE(); \ + if(curlcheck_postfields_option(_curl_opt)) \ + if(!curlcheck_postfields(value)) \ + _curl_easy_setopt_err_postfields(); \ + if((_curl_opt) == CURLOPT_HTTPPOST) \ + if(!curlcheck_arr((value), struct curl_httppost)) \ + _curl_easy_setopt_err_curl_httpost(); \ + if((_curl_opt) == CURLOPT_MIMEPOST) \ + if(!curlcheck_ptr((value), curl_mime)) \ + _curl_easy_setopt_err_curl_mimepost(); \ + if(curlcheck_slist_option(_curl_opt)) \ + if(!curlcheck_arr((value), struct curl_slist)) \ + _curl_easy_setopt_err_curl_slist(); \ + if((_curl_opt) == CURLOPT_SHARE) \ + if(!curlcheck_ptr((value), CURLSH)) \ + _curl_easy_setopt_err_CURLSH(); \ + ) \ + } \ + curl_easy_setopt(handle, _curl_opt, value); \ + }) /* wraps curl_easy_getinfo() with typechecking */ -/* FIXME: don't allow const pointers */ -#define curl_easy_getinfo(handle, info, arg) \ -__extension__ ({ \ - __typeof__(info) _curl_info = info; \ - if(__builtin_constant_p(_curl_info)) { \ - if(_curl_is_string_info(_curl_info)) \ - if(!_curl_is_arr((arg), char *)) \ - _curl_easy_getinfo_err_string(); \ - if(_curl_is_long_info(_curl_info)) \ - if(!_curl_is_arr((arg), long)) \ - _curl_easy_getinfo_err_long(); \ - if(_curl_is_double_info(_curl_info)) \ - if(!_curl_is_arr((arg), double)) \ - _curl_easy_getinfo_err_double(); \ - if(_curl_is_slist_info(_curl_info)) \ - if(!_curl_is_arr((arg), struct curl_slist *)) \ - _curl_easy_getinfo_err_curl_slist(); \ - } \ - curl_easy_getinfo(handle, _curl_info, arg); \ -}) - -/* TODO: typechecking for curl_share_setopt() and curl_multi_setopt(), - * for now just make sure that the functions are called with three - * arguments +#define curl_easy_getinfo(handle, info, arg) \ + __extension__({ \ + CURLINFO _curl_info = (info); \ + if(__builtin_constant_p(_curl_info)) { \ + CURL_IGNORE_DEPRECATION( \ + if(curlcheck_string_info(_curl_info)) \ + if(!curlcheck_arr((arg), char *)) \ + _curl_easy_getinfo_err_string(); \ + if(curlcheck_long_info(_curl_info)) \ + if(!curlcheck_arr((arg), long)) \ + _curl_easy_getinfo_err_long(); \ + if(curlcheck_double_info(_curl_info)) \ + if(!curlcheck_arr((arg), double)) \ + _curl_easy_getinfo_err_double(); \ + if(curlcheck_slist_info(_curl_info)) \ + if(!curlcheck_arr((arg), struct curl_slist *)) \ + _curl_easy_getinfo_err_curl_slist(); \ + if(curlcheck_tlssessioninfo_info(_curl_info)) \ + if(!curlcheck_arr((arg), struct curl_tlssessioninfo *)) \ + _curl_easy_getinfo_err_curl_tlssesssioninfo(); \ + if(curlcheck_certinfo_info(_curl_info)) \ + if(!curlcheck_arr((arg), struct curl_certinfo *)) \ + _curl_easy_getinfo_err_curl_certinfo(); \ + if(curlcheck_socket_info(_curl_info)) \ + if(!curlcheck_arr((arg), curl_socket_t)) \ + _curl_easy_getinfo_err_curl_socket(); \ + if(curlcheck_off_t_info(_curl_info)) \ + if(!curlcheck_arr((arg), curl_off_t)) \ + _curl_easy_getinfo_err_curl_off_t(); \ + ) \ + } \ + curl_easy_getinfo(handle, _curl_info, arg); \ + }) + +/* + * For now, just make sure that the functions are called with three arguments */ #define curl_share_setopt(share,opt,param) curl_share_setopt(share,opt,param) #define curl_multi_setopt(handle,opt,param) curl_multi_setopt(handle,opt,param) @@ -140,87 +162,105 @@ __extension__ ({ \ * functions */ /* To define a new warning, use _CURL_WARNING(identifier, "message") */ -#define _CURL_WARNING(id, message) \ - static void __attribute__((__warning__(message))) \ - __attribute__((__unused__)) __attribute__((__noinline__)) \ +#define CURLWARNING(id, message) \ + static void __attribute__((__warning__(message))) \ + __attribute__((__unused__)) __attribute__((__noinline__)) \ id(void) { __asm__(""); } -_CURL_WARNING(_curl_easy_setopt_err_long, +CURLWARNING(_curl_easy_setopt_err_long, "curl_easy_setopt expects a long argument for this option") -_CURL_WARNING(_curl_easy_setopt_err_curl_off_t, +CURLWARNING(_curl_easy_setopt_err_curl_off_t, "curl_easy_setopt expects a curl_off_t argument for this option") -_CURL_WARNING(_curl_easy_setopt_err_string, +CURLWARNING(_curl_easy_setopt_err_string, "curl_easy_setopt expects a " "string ('char *' or char[]) argument for this option" ) -_CURL_WARNING(_curl_easy_setopt_err_write_callback, +CURLWARNING(_curl_easy_setopt_err_write_callback, "curl_easy_setopt expects a curl_write_callback argument for this option") -_CURL_WARNING(_curl_easy_setopt_err_read_cb, +CURLWARNING(_curl_easy_setopt_err_resolver_start_callback, + "curl_easy_setopt expects a " + "curl_resolver_start_callback argument for this option" + ) +CURLWARNING(_curl_easy_setopt_err_read_cb, "curl_easy_setopt expects a curl_read_callback argument for this option") -_CURL_WARNING(_curl_easy_setopt_err_ioctl_cb, +CURLWARNING(_curl_easy_setopt_err_ioctl_cb, "curl_easy_setopt expects a curl_ioctl_callback argument for this option") -_CURL_WARNING(_curl_easy_setopt_err_sockopt_cb, +CURLWARNING(_curl_easy_setopt_err_sockopt_cb, "curl_easy_setopt expects a curl_sockopt_callback argument for this option") -_CURL_WARNING(_curl_easy_setopt_err_opensocket_cb, +CURLWARNING(_curl_easy_setopt_err_opensocket_cb, "curl_easy_setopt expects a " "curl_opensocket_callback argument for this option" ) -_CURL_WARNING(_curl_easy_setopt_err_progress_cb, +CURLWARNING(_curl_easy_setopt_err_progress_cb, "curl_easy_setopt expects a curl_progress_callback argument for this option") -_CURL_WARNING(_curl_easy_setopt_err_debug_cb, +CURLWARNING(_curl_easy_setopt_err_debug_cb, "curl_easy_setopt expects a curl_debug_callback argument for this option") -_CURL_WARNING(_curl_easy_setopt_err_ssl_ctx_cb, +CURLWARNING(_curl_easy_setopt_err_ssl_ctx_cb, "curl_easy_setopt expects a curl_ssl_ctx_callback argument for this option") -_CURL_WARNING(_curl_easy_setopt_err_conv_cb, +CURLWARNING(_curl_easy_setopt_err_conv_cb, "curl_easy_setopt expects a curl_conv_callback argument for this option") -_CURL_WARNING(_curl_easy_setopt_err_seek_cb, +CURLWARNING(_curl_easy_setopt_err_seek_cb, "curl_easy_setopt expects a curl_seek_callback argument for this option") -_CURL_WARNING(_curl_easy_setopt_err_cb_data, +CURLWARNING(_curl_easy_setopt_err_cb_data, "curl_easy_setopt expects a " "private data pointer as argument for this option") -_CURL_WARNING(_curl_easy_setopt_err_error_buffer, +CURLWARNING(_curl_easy_setopt_err_error_buffer, "curl_easy_setopt expects a " "char buffer of CURL_ERROR_SIZE as argument for this option") -_CURL_WARNING(_curl_easy_setopt_err_FILE, +CURLWARNING(_curl_easy_setopt_err_FILE, "curl_easy_setopt expects a 'FILE *' argument for this option") -_CURL_WARNING(_curl_easy_setopt_err_postfields, +CURLWARNING(_curl_easy_setopt_err_postfields, "curl_easy_setopt expects a 'void *' or 'char *' argument for this option") -_CURL_WARNING(_curl_easy_setopt_err_curl_httpost, +CURLWARNING(_curl_easy_setopt_err_curl_httpost, "curl_easy_setopt expects a 'struct curl_httppost *' " "argument for this option") -_CURL_WARNING(_curl_easy_setopt_err_curl_slist, +CURLWARNING(_curl_easy_setopt_err_curl_mimepost, + "curl_easy_setopt expects a 'curl_mime *' " + "argument for this option") +CURLWARNING(_curl_easy_setopt_err_curl_slist, "curl_easy_setopt expects a 'struct curl_slist *' argument for this option") -_CURL_WARNING(_curl_easy_setopt_err_CURLSH, +CURLWARNING(_curl_easy_setopt_err_CURLSH, "curl_easy_setopt expects a CURLSH* argument for this option") -_CURL_WARNING(_curl_easy_getinfo_err_string, +CURLWARNING(_curl_easy_getinfo_err_string, "curl_easy_getinfo expects a pointer to 'char *' for this info") -_CURL_WARNING(_curl_easy_getinfo_err_long, +CURLWARNING(_curl_easy_getinfo_err_long, "curl_easy_getinfo expects a pointer to long for this info") -_CURL_WARNING(_curl_easy_getinfo_err_double, +CURLWARNING(_curl_easy_getinfo_err_double, "curl_easy_getinfo expects a pointer to double for this info") -_CURL_WARNING(_curl_easy_getinfo_err_curl_slist, +CURLWARNING(_curl_easy_getinfo_err_curl_slist, "curl_easy_getinfo expects a pointer to 'struct curl_slist *' for this info") +CURLWARNING(_curl_easy_getinfo_err_curl_tlssesssioninfo, + "curl_easy_getinfo expects a pointer to " + "'struct curl_tlssessioninfo *' for this info") +CURLWARNING(_curl_easy_getinfo_err_curl_certinfo, + "curl_easy_getinfo expects a pointer to " + "'struct curl_certinfo *' for this info") +CURLWARNING(_curl_easy_getinfo_err_curl_socket, + "curl_easy_getinfo expects a pointer to curl_socket_t for this info") +CURLWARNING(_curl_easy_getinfo_err_curl_off_t, + "curl_easy_getinfo expects a pointer to curl_off_t for this info") /* groups of curl_easy_setops options that take the same type of argument */ /* To add a new option to one of the groups, just add * (option) == CURLOPT_SOMETHING - * to the or-expression. If the option takes a long or curl_off_t, you don't + * to the or-expression. If the option takes a long or curl_off_t, you do not * have to do anything */ /* evaluates to true if option takes a long argument */ -#define _curl_is_long_option(option) \ +#define curlcheck_long_option(option) \ (0 < (option) && (option) < CURLOPTTYPE_OBJECTPOINT) -#define _curl_is_off_t_option(option) \ - ((option) > CURLOPTTYPE_OFF_T) +#define curlcheck_off_t_option(option) \ + (((option) > CURLOPTTYPE_OFF_T) && ((option) < CURLOPTTYPE_BLOB)) /* evaluates to true if option takes a char* argument */ -#define _curl_is_string_option(option) \ +#define curlcheck_string_option(option) \ ((option) == CURLOPT_ABSTRACT_UNIX_SOCKET || \ (option) == CURLOPT_ACCEPT_ENCODING || \ + (option) == CURLOPT_ALTSVC || \ (option) == CURLOPT_CAINFO || \ (option) == CURLOPT_CAPATH || \ (option) == CURLOPT_COOKIE || \ @@ -234,10 +274,14 @@ _CURL_WARNING(_curl_easy_getinfo_err_curl_slist, (option) == CURLOPT_DNS_LOCAL_IP4 || \ (option) == CURLOPT_DNS_LOCAL_IP6 || \ (option) == CURLOPT_DNS_SERVERS || \ + (option) == CURLOPT_DOH_URL || \ + (option) == CURLOPT_ECH || \ (option) == CURLOPT_EGDSOCKET || \ - (option) == CURLOPT_FTPPORT || \ (option) == CURLOPT_FTP_ACCOUNT || \ (option) == CURLOPT_FTP_ALTERNATIVE_TO_USER || \ + (option) == CURLOPT_FTPPORT || \ + (option) == CURLOPT_HSTS || \ + (option) == CURLOPT_HAPROXY_CLIENT_IP || \ (option) == CURLOPT_INTERFACE || \ (option) == CURLOPT_ISSUERCERT || \ (option) == CURLOPT_KEYPASSWD || \ @@ -249,20 +293,41 @@ _CURL_WARNING(_curl_easy_getinfo_err_curl_slist, (option) == CURLOPT_NOPROXY || \ (option) == CURLOPT_PASSWORD || \ (option) == CURLOPT_PINNEDPUBLICKEY || \ + (option) == CURLOPT_PRE_PROXY || \ + (option) == CURLOPT_PROTOCOLS_STR || \ (option) == CURLOPT_PROXY || \ + (option) == CURLOPT_PROXY_CAINFO || \ + (option) == CURLOPT_PROXY_CAPATH || \ + (option) == CURLOPT_PROXY_CRLFILE || \ + (option) == CURLOPT_PROXY_ISSUERCERT || \ + (option) == CURLOPT_PROXY_KEYPASSWD || \ + (option) == CURLOPT_PROXY_PINNEDPUBLICKEY || \ + (option) == CURLOPT_PROXY_SERVICE_NAME || \ + (option) == CURLOPT_PROXY_SSL_CIPHER_LIST || \ + (option) == CURLOPT_PROXY_SSLCERT || \ + (option) == CURLOPT_PROXY_SSLCERTTYPE || \ + (option) == CURLOPT_PROXY_SSLKEY || \ + (option) == CURLOPT_PROXY_SSLKEYTYPE || \ + (option) == CURLOPT_PROXY_TLS13_CIPHERS || \ + (option) == CURLOPT_PROXY_TLSAUTH_PASSWORD || \ + (option) == CURLOPT_PROXY_TLSAUTH_TYPE || \ + (option) == CURLOPT_PROXY_TLSAUTH_USERNAME || \ (option) == CURLOPT_PROXYPASSWORD || \ (option) == CURLOPT_PROXYUSERNAME || \ (option) == CURLOPT_PROXYUSERPWD || \ - (option) == CURLOPT_PROXY_SERVICE_NAME || \ (option) == CURLOPT_RANDOM_FILE || \ (option) == CURLOPT_RANGE || \ + (option) == CURLOPT_REDIR_PROTOCOLS_STR || \ (option) == CURLOPT_REFERER || \ + (option) == CURLOPT_REQUEST_TARGET || \ (option) == CURLOPT_RTSP_SESSION_ID || \ (option) == CURLOPT_RTSP_STREAM_URI || \ (option) == CURLOPT_RTSP_TRANSPORT || \ + (option) == CURLOPT_SASL_AUTHZID || \ (option) == CURLOPT_SERVICE_NAME || \ (option) == CURLOPT_SOCKS5_GSSAPI_SERVICE || \ (option) == CURLOPT_SSH_HOST_PUBLIC_KEY_MD5 || \ + (option) == CURLOPT_SSH_HOST_PUBLIC_KEY_SHA256 || \ (option) == CURLOPT_SSH_KNOWNHOSTS || \ (option) == CURLOPT_SSH_PRIVATE_KEYFILE || \ (option) == CURLOPT_SSH_PUBLIC_KEYFILE || \ @@ -272,6 +337,7 @@ _CURL_WARNING(_curl_easy_getinfo_err_curl_slist, (option) == CURLOPT_SSLKEY || \ (option) == CURLOPT_SSLKEYTYPE || \ (option) == CURLOPT_SSL_CIPHER_LIST || \ + (option) == CURLOPT_TLS13_CIPHERS || \ (option) == CURLOPT_TLSAUTH_PASSWORD || \ (option) == CURLOPT_TLSAUTH_TYPE || \ (option) == CURLOPT_TLSAUTH_USERNAME || \ @@ -279,32 +345,36 @@ _CURL_WARNING(_curl_easy_getinfo_err_curl_slist, (option) == CURLOPT_URL || \ (option) == CURLOPT_USERAGENT || \ (option) == CURLOPT_USERNAME || \ + (option) == CURLOPT_AWS_SIGV4 || \ (option) == CURLOPT_USERPWD || \ (option) == CURLOPT_XOAUTH2_BEARER || \ + (option) == CURLOPT_SSL_EC_CURVES || \ 0) /* evaluates to true if option takes a curl_write_callback argument */ -#define _curl_is_write_cb_option(option) \ - ((option) == CURLOPT_HEADERFUNCTION || \ +#define curlcheck_write_cb_option(option) \ + ((option) == CURLOPT_HEADERFUNCTION || \ (option) == CURLOPT_WRITEFUNCTION) /* evaluates to true if option takes a curl_conv_callback argument */ -#define _curl_is_conv_cb_option(option) \ - ((option) == CURLOPT_CONV_TO_NETWORK_FUNCTION || \ - (option) == CURLOPT_CONV_FROM_NETWORK_FUNCTION || \ +#define curlcheck_conv_cb_option(option) \ + ((option) == CURLOPT_CONV_TO_NETWORK_FUNCTION || \ + (option) == CURLOPT_CONV_FROM_NETWORK_FUNCTION || \ (option) == CURLOPT_CONV_FROM_UTF8_FUNCTION) /* evaluates to true if option takes a data argument to pass to a callback */ -#define _curl_is_cb_data_option(option) \ +#define curlcheck_cb_data_option(option) \ ((option) == CURLOPT_CHUNK_DATA || \ (option) == CURLOPT_CLOSESOCKETDATA || \ (option) == CURLOPT_DEBUGDATA || \ (option) == CURLOPT_FNMATCH_DATA || \ (option) == CURLOPT_HEADERDATA || \ + (option) == CURLOPT_HSTSREADDATA || \ + (option) == CURLOPT_HSTSWRITEDATA || \ (option) == CURLOPT_INTERLEAVEDATA || \ (option) == CURLOPT_IOCTLDATA || \ (option) == CURLOPT_OPENSOCKETDATA || \ - (option) == CURLOPT_PRIVATE || \ + (option) == CURLOPT_PREREQDATA || \ (option) == CURLOPT_PROGRESSDATA || \ (option) == CURLOPT_READDATA || \ (option) == CURLOPT_SEEKDATA || \ @@ -312,16 +382,19 @@ _CURL_WARNING(_curl_easy_getinfo_err_curl_slist, (option) == CURLOPT_SSH_KEYDATA || \ (option) == CURLOPT_SSL_CTX_DATA || \ (option) == CURLOPT_WRITEDATA || \ + (option) == CURLOPT_RESOLVER_START_DATA || \ + (option) == CURLOPT_TRAILERDATA || \ + (option) == CURLOPT_SSH_HOSTKEYDATA || \ 0) /* evaluates to true if option takes a POST data argument (void* or char*) */ -#define _curl_is_postfields_option(option) \ +#define curlcheck_postfields_option(option) \ ((option) == CURLOPT_POSTFIELDS || \ (option) == CURLOPT_COPYPOSTFIELDS || \ 0) /* evaluates to true if option takes a struct curl_slist * argument */ -#define _curl_is_slist_option(option) \ +#define curlcheck_slist_option(option) \ ((option) == CURLOPT_HTTP200ALIASES || \ (option) == CURLOPT_HTTPHEADER || \ (option) == CURLOPT_MAIL_RCPT || \ @@ -331,30 +404,47 @@ _CURL_WARNING(_curl_easy_getinfo_err_curl_slist, (option) == CURLOPT_QUOTE || \ (option) == CURLOPT_RESOLVE || \ (option) == CURLOPT_TELNETOPTIONS || \ + (option) == CURLOPT_CONNECT_TO || \ 0) /* groups of curl_easy_getinfo infos that take the same type of argument */ /* evaluates to true if info expects a pointer to char * argument */ -#define _curl_is_string_info(info) \ - (CURLINFO_STRING < (info) && (info) < CURLINFO_LONG) +#define curlcheck_string_info(info) \ + (CURLINFO_STRING < (info) && (info) < CURLINFO_LONG && \ + (info) != CURLINFO_PRIVATE) /* evaluates to true if info expects a pointer to long argument */ -#define _curl_is_long_info(info) \ +#define curlcheck_long_info(info) \ (CURLINFO_LONG < (info) && (info) < CURLINFO_DOUBLE) /* evaluates to true if info expects a pointer to double argument */ -#define _curl_is_double_info(info) \ +#define curlcheck_double_info(info) \ (CURLINFO_DOUBLE < (info) && (info) < CURLINFO_SLIST) /* true if info expects a pointer to struct curl_slist * argument */ -#define _curl_is_slist_info(info) \ - (CURLINFO_SLIST < (info)) +#define curlcheck_slist_info(info) \ + (((info) == CURLINFO_SSL_ENGINES) || ((info) == CURLINFO_COOKIELIST)) + +/* true if info expects a pointer to struct curl_tlssessioninfo * argument */ +#define curlcheck_tlssessioninfo_info(info) \ + (((info) == CURLINFO_TLS_SSL_PTR) || ((info) == CURLINFO_TLS_SESSION)) + +/* true if info expects a pointer to struct curl_certinfo * argument */ +#define curlcheck_certinfo_info(info) ((info) == CURLINFO_CERTINFO) + +/* true if info expects a pointer to struct curl_socket_t argument */ +#define curlcheck_socket_info(info) \ + (CURLINFO_SOCKET < (info) && (info) < CURLINFO_OFF_T) +/* true if info expects a pointer to curl_off_t argument */ +#define curlcheck_off_t_info(info) \ + (CURLINFO_OFF_T < (info)) -/* typecheck helpers -- check whether given expression has requested type*/ -/* For pointers, you can use the _curl_is_ptr/_curl_is_arr macros, +/* typecheck helpers -- check whether given expression has requested type */ + +/* For pointers, you can use the curlcheck_ptr/curlcheck_arr macros, * otherwise define a new macro. Search for __builtin_types_compatible_p * in the GCC manual. * NOTE: these macros MUST NOT EVALUATE their arguments! The argument is @@ -363,36 +453,36 @@ _CURL_WARNING(_curl_easy_getinfo_err_curl_slist, * == or whatsoever. */ -/* XXX: should evaluate to true iff expr is a pointer */ -#define _curl_is_any_ptr(expr) \ +/* XXX: should evaluate to true if expr is a pointer */ +#define curlcheck_any_ptr(expr) \ (sizeof(expr) == sizeof(void *)) /* evaluates to true if expr is NULL */ /* XXX: must not evaluate expr, so this check is not accurate */ -#define _curl_is_NULL(expr) \ +#define curlcheck_NULL(expr) \ (__builtin_types_compatible_p(__typeof__(expr), __typeof__(NULL))) /* evaluates to true if expr is type*, const type* or NULL */ -#define _curl_is_ptr(expr, type) \ - (_curl_is_NULL(expr) || \ - __builtin_types_compatible_p(__typeof__(expr), type *) || \ +#define curlcheck_ptr(expr, type) \ + (curlcheck_NULL(expr) || \ + __builtin_types_compatible_p(__typeof__(expr), type *) || \ __builtin_types_compatible_p(__typeof__(expr), const type *)) /* evaluates to true if expr is one of type[], type*, NULL or const type* */ -#define _curl_is_arr(expr, type) \ - (_curl_is_ptr((expr), type) || \ +#define curlcheck_arr(expr, type) \ + (curlcheck_ptr((expr), type) || \ __builtin_types_compatible_p(__typeof__(expr), type [])) /* evaluates to true if expr is a string */ -#define _curl_is_string(expr) \ - (_curl_is_arr((expr), char) || \ - _curl_is_arr((expr), signed char) || \ - _curl_is_arr((expr), unsigned char)) +#define curlcheck_string(expr) \ + (curlcheck_arr((expr), char) || \ + curlcheck_arr((expr), signed char) || \ + curlcheck_arr((expr), unsigned char)) /* evaluates to true if expr is a long (no matter the signedness) * XXX: for now, int is also accepted (and therefore short and char, which * are promoted to int when passed to a variadic function) */ -#define _curl_is_long(expr) \ +#define curlcheck_long(expr) \ (__builtin_types_compatible_p(__typeof__(expr), long) || \ __builtin_types_compatible_p(__typeof__(expr), signed long) || \ __builtin_types_compatible_p(__typeof__(expr), unsigned long) || \ @@ -407,190 +497,194 @@ _CURL_WARNING(_curl_easy_getinfo_err_curl_slist, __builtin_types_compatible_p(__typeof__(expr), unsigned char)) /* evaluates to true if expr is of type curl_off_t */ -#define _curl_is_off_t(expr) \ +#define curlcheck_off_t(expr) \ (__builtin_types_compatible_p(__typeof__(expr), curl_off_t)) /* evaluates to true if expr is abuffer suitable for CURLOPT_ERRORBUFFER */ /* XXX: also check size of an char[] array? */ -#define _curl_is_error_buffer(expr) \ - (_curl_is_NULL(expr) || \ - __builtin_types_compatible_p(__typeof__(expr), char *) || \ +#define curlcheck_error_buffer(expr) \ + (curlcheck_NULL(expr) || \ + __builtin_types_compatible_p(__typeof__(expr), char *) || \ __builtin_types_compatible_p(__typeof__(expr), char[])) /* evaluates to true if expr is of type (const) void* or (const) FILE* */ #if 0 -#define _curl_is_cb_data(expr) \ - (_curl_is_ptr((expr), void) || \ - _curl_is_ptr((expr), FILE)) +#define curlcheck_cb_data(expr) \ + (curlcheck_ptr((expr), void) || \ + curlcheck_ptr((expr), FILE)) #else /* be less strict */ -#define _curl_is_cb_data(expr) \ - _curl_is_any_ptr(expr) +#define curlcheck_cb_data(expr) \ + curlcheck_any_ptr(expr) #endif /* evaluates to true if expr is of type FILE* */ -#define _curl_is_FILE(expr) \ - (__builtin_types_compatible_p(__typeof__(expr), FILE *)) +#define curlcheck_FILE(expr) \ + (curlcheck_NULL(expr) || \ + (__builtin_types_compatible_p(__typeof__(expr), FILE *))) /* evaluates to true if expr can be passed as POST data (void* or char*) */ -#define _curl_is_postfields(expr) \ - (_curl_is_ptr((expr), void) || \ - _curl_is_arr((expr), char)) +#define curlcheck_postfields(expr) \ + (curlcheck_ptr((expr), void) || \ + curlcheck_arr((expr), char) || \ + curlcheck_arr((expr), unsigned char)) -/* FIXME: the whole callback checking is messy... - * The idea is to tolerate char vs. void and const vs. not const - * pointers in arguments at least - */ /* helper: __builtin_types_compatible_p distinguishes between functions and * function pointers, hide it */ -#define _curl_callback_compatible(func, type) \ - (__builtin_types_compatible_p(__typeof__(func), type) || \ - __builtin_types_compatible_p(__typeof__(func), type*)) +#define curlcheck_cb_compatible(func, type) \ + (__builtin_types_compatible_p(__typeof__(func), type) || \ + __builtin_types_compatible_p(__typeof__(func) *, type)) + +/* evaluates to true if expr is of type curl_resolver_start_callback */ +#define curlcheck_resolver_start_callback(expr) \ + (curlcheck_NULL(expr) || \ + curlcheck_cb_compatible((expr), curl_resolver_start_callback)) /* evaluates to true if expr is of type curl_read_callback or "similar" */ -#define _curl_is_read_cb(expr) \ - (_curl_is_NULL(expr) || \ - __builtin_types_compatible_p(__typeof__(expr), __typeof__(fread)) || \ - __builtin_types_compatible_p(__typeof__(expr), curl_read_callback) || \ - _curl_callback_compatible((expr), _curl_read_callback1) || \ - _curl_callback_compatible((expr), _curl_read_callback2) || \ - _curl_callback_compatible((expr), _curl_read_callback3) || \ - _curl_callback_compatible((expr), _curl_read_callback4) || \ - _curl_callback_compatible((expr), _curl_read_callback5) || \ - _curl_callback_compatible((expr), _curl_read_callback6)) -typedef size_t (_curl_read_callback1)(char *, size_t, size_t, void *); -typedef size_t (_curl_read_callback2)(char *, size_t, size_t, const void *); -typedef size_t (_curl_read_callback3)(char *, size_t, size_t, FILE *); -typedef size_t (_curl_read_callback4)(void *, size_t, size_t, void *); -typedef size_t (_curl_read_callback5)(void *, size_t, size_t, const void *); -typedef size_t (_curl_read_callback6)(void *, size_t, size_t, FILE *); +#define curlcheck_read_cb(expr) \ + (curlcheck_NULL(expr) || \ + curlcheck_cb_compatible((expr), __typeof__(fread) *) || \ + curlcheck_cb_compatible((expr), curl_read_callback) || \ + curlcheck_cb_compatible((expr), _curl_read_callback1) || \ + curlcheck_cb_compatible((expr), _curl_read_callback2) || \ + curlcheck_cb_compatible((expr), _curl_read_callback3) || \ + curlcheck_cb_compatible((expr), _curl_read_callback4) || \ + curlcheck_cb_compatible((expr), _curl_read_callback5) || \ + curlcheck_cb_compatible((expr), _curl_read_callback6)) +typedef size_t (*_curl_read_callback1)(char *, size_t, size_t, void *); +typedef size_t (*_curl_read_callback2)(char *, size_t, size_t, const void *); +typedef size_t (*_curl_read_callback3)(char *, size_t, size_t, FILE *); +typedef size_t (*_curl_read_callback4)(void *, size_t, size_t, void *); +typedef size_t (*_curl_read_callback5)(void *, size_t, size_t, const void *); +typedef size_t (*_curl_read_callback6)(void *, size_t, size_t, FILE *); /* evaluates to true if expr is of type curl_write_callback or "similar" */ -#define _curl_is_write_cb(expr) \ - (_curl_is_read_cb(expr) || \ - __builtin_types_compatible_p(__typeof__(expr), __typeof__(fwrite)) || \ - __builtin_types_compatible_p(__typeof__(expr), curl_write_callback) || \ - _curl_callback_compatible((expr), _curl_write_callback1) || \ - _curl_callback_compatible((expr), _curl_write_callback2) || \ - _curl_callback_compatible((expr), _curl_write_callback3) || \ - _curl_callback_compatible((expr), _curl_write_callback4) || \ - _curl_callback_compatible((expr), _curl_write_callback5) || \ - _curl_callback_compatible((expr), _curl_write_callback6)) -typedef size_t (_curl_write_callback1)(const char *, size_t, size_t, void *); -typedef size_t (_curl_write_callback2)(const char *, size_t, size_t, +#define curlcheck_write_cb(expr) \ + (curlcheck_read_cb(expr) || \ + curlcheck_cb_compatible((expr), __typeof__(fwrite) *) || \ + curlcheck_cb_compatible((expr), curl_write_callback) || \ + curlcheck_cb_compatible((expr), _curl_write_callback1) || \ + curlcheck_cb_compatible((expr), _curl_write_callback2) || \ + curlcheck_cb_compatible((expr), _curl_write_callback3) || \ + curlcheck_cb_compatible((expr), _curl_write_callback4) || \ + curlcheck_cb_compatible((expr), _curl_write_callback5) || \ + curlcheck_cb_compatible((expr), _curl_write_callback6)) +typedef size_t (*_curl_write_callback1)(const char *, size_t, size_t, void *); +typedef size_t (*_curl_write_callback2)(const char *, size_t, size_t, const void *); -typedef size_t (_curl_write_callback3)(const char *, size_t, size_t, FILE *); -typedef size_t (_curl_write_callback4)(const void *, size_t, size_t, void *); -typedef size_t (_curl_write_callback5)(const void *, size_t, size_t, +typedef size_t (*_curl_write_callback3)(const char *, size_t, size_t, FILE *); +typedef size_t (*_curl_write_callback4)(const void *, size_t, size_t, void *); +typedef size_t (*_curl_write_callback5)(const void *, size_t, size_t, const void *); -typedef size_t (_curl_write_callback6)(const void *, size_t, size_t, FILE *); +typedef size_t (*_curl_write_callback6)(const void *, size_t, size_t, FILE *); /* evaluates to true if expr is of type curl_ioctl_callback or "similar" */ -#define _curl_is_ioctl_cb(expr) \ - (_curl_is_NULL(expr) || \ - __builtin_types_compatible_p(__typeof__(expr), curl_ioctl_callback) || \ - _curl_callback_compatible((expr), _curl_ioctl_callback1) || \ - _curl_callback_compatible((expr), _curl_ioctl_callback2) || \ - _curl_callback_compatible((expr), _curl_ioctl_callback3) || \ - _curl_callback_compatible((expr), _curl_ioctl_callback4)) -typedef curlioerr (_curl_ioctl_callback1)(CURL *, int, void *); -typedef curlioerr (_curl_ioctl_callback2)(CURL *, int, const void *); -typedef curlioerr (_curl_ioctl_callback3)(CURL *, curliocmd, void *); -typedef curlioerr (_curl_ioctl_callback4)(CURL *, curliocmd, const void *); +#define curlcheck_ioctl_cb(expr) \ + (curlcheck_NULL(expr) || \ + curlcheck_cb_compatible((expr), curl_ioctl_callback) || \ + curlcheck_cb_compatible((expr), _curl_ioctl_callback1) || \ + curlcheck_cb_compatible((expr), _curl_ioctl_callback2) || \ + curlcheck_cb_compatible((expr), _curl_ioctl_callback3) || \ + curlcheck_cb_compatible((expr), _curl_ioctl_callback4)) +typedef curlioerr (*_curl_ioctl_callback1)(CURL *, int, void *); +typedef curlioerr (*_curl_ioctl_callback2)(CURL *, int, const void *); +typedef curlioerr (*_curl_ioctl_callback3)(CURL *, curliocmd, void *); +typedef curlioerr (*_curl_ioctl_callback4)(CURL *, curliocmd, const void *); /* evaluates to true if expr is of type curl_sockopt_callback or "similar" */ -#define _curl_is_sockopt_cb(expr) \ - (_curl_is_NULL(expr) || \ - __builtin_types_compatible_p(__typeof__(expr), curl_sockopt_callback) || \ - _curl_callback_compatible((expr), _curl_sockopt_callback1) || \ - _curl_callback_compatible((expr), _curl_sockopt_callback2)) -typedef int (_curl_sockopt_callback1)(void *, curl_socket_t, curlsocktype); -typedef int (_curl_sockopt_callback2)(const void *, curl_socket_t, +#define curlcheck_sockopt_cb(expr) \ + (curlcheck_NULL(expr) || \ + curlcheck_cb_compatible((expr), curl_sockopt_callback) || \ + curlcheck_cb_compatible((expr), _curl_sockopt_callback1) || \ + curlcheck_cb_compatible((expr), _curl_sockopt_callback2)) +typedef int (*_curl_sockopt_callback1)(void *, curl_socket_t, curlsocktype); +typedef int (*_curl_sockopt_callback2)(const void *, curl_socket_t, curlsocktype); /* evaluates to true if expr is of type curl_opensocket_callback or "similar" */ -#define _curl_is_opensocket_cb(expr) \ - (_curl_is_NULL(expr) || \ - __builtin_types_compatible_p(__typeof__(expr), curl_opensocket_callback) ||\ - _curl_callback_compatible((expr), _curl_opensocket_callback1) || \ - _curl_callback_compatible((expr), _curl_opensocket_callback2) || \ - _curl_callback_compatible((expr), _curl_opensocket_callback3) || \ - _curl_callback_compatible((expr), _curl_opensocket_callback4)) -typedef curl_socket_t (_curl_opensocket_callback1) +#define curlcheck_opensocket_cb(expr) \ + (curlcheck_NULL(expr) || \ + curlcheck_cb_compatible((expr), curl_opensocket_callback) || \ + curlcheck_cb_compatible((expr), _curl_opensocket_callback1) || \ + curlcheck_cb_compatible((expr), _curl_opensocket_callback2) || \ + curlcheck_cb_compatible((expr), _curl_opensocket_callback3) || \ + curlcheck_cb_compatible((expr), _curl_opensocket_callback4)) +typedef curl_socket_t (*_curl_opensocket_callback1) (void *, curlsocktype, struct curl_sockaddr *); -typedef curl_socket_t (_curl_opensocket_callback2) +typedef curl_socket_t (*_curl_opensocket_callback2) (void *, curlsocktype, const struct curl_sockaddr *); -typedef curl_socket_t (_curl_opensocket_callback3) +typedef curl_socket_t (*_curl_opensocket_callback3) (const void *, curlsocktype, struct curl_sockaddr *); -typedef curl_socket_t (_curl_opensocket_callback4) +typedef curl_socket_t (*_curl_opensocket_callback4) (const void *, curlsocktype, const struct curl_sockaddr *); /* evaluates to true if expr is of type curl_progress_callback or "similar" */ -#define _curl_is_progress_cb(expr) \ - (_curl_is_NULL(expr) || \ - __builtin_types_compatible_p(__typeof__(expr), curl_progress_callback) || \ - _curl_callback_compatible((expr), _curl_progress_callback1) || \ - _curl_callback_compatible((expr), _curl_progress_callback2)) -typedef int (_curl_progress_callback1)(void *, +#define curlcheck_progress_cb(expr) \ + (curlcheck_NULL(expr) || \ + curlcheck_cb_compatible((expr), curl_progress_callback) || \ + curlcheck_cb_compatible((expr), _curl_progress_callback1) || \ + curlcheck_cb_compatible((expr), _curl_progress_callback2)) +typedef int (*_curl_progress_callback1)(void *, double, double, double, double); -typedef int (_curl_progress_callback2)(const void *, +typedef int (*_curl_progress_callback2)(const void *, double, double, double, double); /* evaluates to true if expr is of type curl_debug_callback or "similar" */ -#define _curl_is_debug_cb(expr) \ - (_curl_is_NULL(expr) || \ - __builtin_types_compatible_p(__typeof__(expr), curl_debug_callback) || \ - _curl_callback_compatible((expr), _curl_debug_callback1) || \ - _curl_callback_compatible((expr), _curl_debug_callback2) || \ - _curl_callback_compatible((expr), _curl_debug_callback3) || \ - _curl_callback_compatible((expr), _curl_debug_callback4) || \ - _curl_callback_compatible((expr), _curl_debug_callback5) || \ - _curl_callback_compatible((expr), _curl_debug_callback6) || \ - _curl_callback_compatible((expr), _curl_debug_callback7) || \ - _curl_callback_compatible((expr), _curl_debug_callback8)) -typedef int (_curl_debug_callback1) (CURL *, +#define curlcheck_debug_cb(expr) \ + (curlcheck_NULL(expr) || \ + curlcheck_cb_compatible((expr), curl_debug_callback) || \ + curlcheck_cb_compatible((expr), _curl_debug_callback1) || \ + curlcheck_cb_compatible((expr), _curl_debug_callback2) || \ + curlcheck_cb_compatible((expr), _curl_debug_callback3) || \ + curlcheck_cb_compatible((expr), _curl_debug_callback4) || \ + curlcheck_cb_compatible((expr), _curl_debug_callback5) || \ + curlcheck_cb_compatible((expr), _curl_debug_callback6) || \ + curlcheck_cb_compatible((expr), _curl_debug_callback7) || \ + curlcheck_cb_compatible((expr), _curl_debug_callback8)) +typedef int (*_curl_debug_callback1) (CURL *, curl_infotype, char *, size_t, void *); -typedef int (_curl_debug_callback2) (CURL *, +typedef int (*_curl_debug_callback2) (CURL *, curl_infotype, char *, size_t, const void *); -typedef int (_curl_debug_callback3) (CURL *, +typedef int (*_curl_debug_callback3) (CURL *, curl_infotype, const char *, size_t, void *); -typedef int (_curl_debug_callback4) (CURL *, +typedef int (*_curl_debug_callback4) (CURL *, curl_infotype, const char *, size_t, const void *); -typedef int (_curl_debug_callback5) (CURL *, +typedef int (*_curl_debug_callback5) (CURL *, curl_infotype, unsigned char *, size_t, void *); -typedef int (_curl_debug_callback6) (CURL *, +typedef int (*_curl_debug_callback6) (CURL *, curl_infotype, unsigned char *, size_t, const void *); -typedef int (_curl_debug_callback7) (CURL *, +typedef int (*_curl_debug_callback7) (CURL *, curl_infotype, const unsigned char *, size_t, void *); -typedef int (_curl_debug_callback8) (CURL *, +typedef int (*_curl_debug_callback8) (CURL *, curl_infotype, const unsigned char *, size_t, const void *); /* evaluates to true if expr is of type curl_ssl_ctx_callback or "similar" */ /* this is getting even messier... */ -#define _curl_is_ssl_ctx_cb(expr) \ - (_curl_is_NULL(expr) || \ - __builtin_types_compatible_p(__typeof__(expr), curl_ssl_ctx_callback) || \ - _curl_callback_compatible((expr), _curl_ssl_ctx_callback1) || \ - _curl_callback_compatible((expr), _curl_ssl_ctx_callback2) || \ - _curl_callback_compatible((expr), _curl_ssl_ctx_callback3) || \ - _curl_callback_compatible((expr), _curl_ssl_ctx_callback4) || \ - _curl_callback_compatible((expr), _curl_ssl_ctx_callback5) || \ - _curl_callback_compatible((expr), _curl_ssl_ctx_callback6) || \ - _curl_callback_compatible((expr), _curl_ssl_ctx_callback7) || \ - _curl_callback_compatible((expr), _curl_ssl_ctx_callback8)) -typedef CURLcode (_curl_ssl_ctx_callback1)(CURL *, void *, void *); -typedef CURLcode (_curl_ssl_ctx_callback2)(CURL *, void *, const void *); -typedef CURLcode (_curl_ssl_ctx_callback3)(CURL *, const void *, void *); -typedef CURLcode (_curl_ssl_ctx_callback4)(CURL *, const void *, const void *); +#define curlcheck_ssl_ctx_cb(expr) \ + (curlcheck_NULL(expr) || \ + curlcheck_cb_compatible((expr), curl_ssl_ctx_callback) || \ + curlcheck_cb_compatible((expr), _curl_ssl_ctx_callback1) || \ + curlcheck_cb_compatible((expr), _curl_ssl_ctx_callback2) || \ + curlcheck_cb_compatible((expr), _curl_ssl_ctx_callback3) || \ + curlcheck_cb_compatible((expr), _curl_ssl_ctx_callback4) || \ + curlcheck_cb_compatible((expr), _curl_ssl_ctx_callback5) || \ + curlcheck_cb_compatible((expr), _curl_ssl_ctx_callback6) || \ + curlcheck_cb_compatible((expr), _curl_ssl_ctx_callback7) || \ + curlcheck_cb_compatible((expr), _curl_ssl_ctx_callback8)) +typedef CURLcode (*_curl_ssl_ctx_callback1)(CURL *, void *, void *); +typedef CURLcode (*_curl_ssl_ctx_callback2)(CURL *, void *, const void *); +typedef CURLcode (*_curl_ssl_ctx_callback3)(CURL *, const void *, void *); +typedef CURLcode (*_curl_ssl_ctx_callback4)(CURL *, const void *, + const void *); #ifdef HEADER_SSL_H /* hack: if we included OpenSSL's ssl.h, we know about SSL_CTX - * this will of course break if we're included before OpenSSL headers... + * this will of course break if we are included before OpenSSL headers... */ -typedef CURLcode (_curl_ssl_ctx_callback5)(CURL *, SSL_CTX, void *); -typedef CURLcode (_curl_ssl_ctx_callback6)(CURL *, SSL_CTX, const void *); -typedef CURLcode (_curl_ssl_ctx_callback7)(CURL *, const SSL_CTX, void *); -typedef CURLcode (_curl_ssl_ctx_callback8)(CURL *, const SSL_CTX, - const void *); +typedef CURLcode (*_curl_ssl_ctx_callback5)(CURL *, SSL_CTX *, void *); +typedef CURLcode (*_curl_ssl_ctx_callback6)(CURL *, SSL_CTX *, const void *); +typedef CURLcode (*_curl_ssl_ctx_callback7)(CURL *, const SSL_CTX *, void *); +typedef CURLcode (*_curl_ssl_ctx_callback8)(CURL *, const SSL_CTX *, + const void *); #else typedef _curl_ssl_ctx_callback1 _curl_ssl_ctx_callback5; typedef _curl_ssl_ctx_callback1 _curl_ssl_ctx_callback6; @@ -599,26 +693,26 @@ typedef _curl_ssl_ctx_callback1 _curl_ssl_ctx_callback8; #endif /* evaluates to true if expr is of type curl_conv_callback or "similar" */ -#define _curl_is_conv_cb(expr) \ - (_curl_is_NULL(expr) || \ - __builtin_types_compatible_p(__typeof__(expr), curl_conv_callback) || \ - _curl_callback_compatible((expr), _curl_conv_callback1) || \ - _curl_callback_compatible((expr), _curl_conv_callback2) || \ - _curl_callback_compatible((expr), _curl_conv_callback3) || \ - _curl_callback_compatible((expr), _curl_conv_callback4)) +#define curlcheck_conv_cb(expr) \ + (curlcheck_NULL(expr) || \ + curlcheck_cb_compatible((expr), curl_conv_callback) || \ + curlcheck_cb_compatible((expr), _curl_conv_callback1) || \ + curlcheck_cb_compatible((expr), _curl_conv_callback2) || \ + curlcheck_cb_compatible((expr), _curl_conv_callback3) || \ + curlcheck_cb_compatible((expr), _curl_conv_callback4)) typedef CURLcode (*_curl_conv_callback1)(char *, size_t length); typedef CURLcode (*_curl_conv_callback2)(const char *, size_t length); typedef CURLcode (*_curl_conv_callback3)(void *, size_t length); typedef CURLcode (*_curl_conv_callback4)(const void *, size_t length); /* evaluates to true if expr is of type curl_seek_callback or "similar" */ -#define _curl_is_seek_cb(expr) \ - (_curl_is_NULL(expr) || \ - __builtin_types_compatible_p(__typeof__(expr), curl_seek_callback) || \ - _curl_callback_compatible((expr), _curl_seek_callback1) || \ - _curl_callback_compatible((expr), _curl_seek_callback2)) +#define curlcheck_seek_cb(expr) \ + (curlcheck_NULL(expr) || \ + curlcheck_cb_compatible((expr), curl_seek_callback) || \ + curlcheck_cb_compatible((expr), _curl_seek_callback1) || \ + curlcheck_cb_compatible((expr), _curl_seek_callback2)) typedef CURLcode (*_curl_seek_callback1)(void *, curl_off_t, int); typedef CURLcode (*_curl_seek_callback2)(const void *, curl_off_t, int); -#endif /* __CURL_TYPECHECK_GCC_H */ +#endif /* CURLINC_TYPECHECK_GCC_H */ diff --git a/contrib/curl/include/curl/urlapi.h b/contrib/curl/include/curl/urlapi.h new file mode 100644 index 0000000000..b4a6e5d567 --- /dev/null +++ b/contrib/curl/include/curl/urlapi.h @@ -0,0 +1,155 @@ +#ifndef CURLINC_URLAPI_H +#define CURLINC_URLAPI_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* the error codes for the URL API */ +typedef enum { + CURLUE_OK, + CURLUE_BAD_HANDLE, /* 1 */ + CURLUE_BAD_PARTPOINTER, /* 2 */ + CURLUE_MALFORMED_INPUT, /* 3 */ + CURLUE_BAD_PORT_NUMBER, /* 4 */ + CURLUE_UNSUPPORTED_SCHEME, /* 5 */ + CURLUE_URLDECODE, /* 6 */ + CURLUE_OUT_OF_MEMORY, /* 7 */ + CURLUE_USER_NOT_ALLOWED, /* 8 */ + CURLUE_UNKNOWN_PART, /* 9 */ + CURLUE_NO_SCHEME, /* 10 */ + CURLUE_NO_USER, /* 11 */ + CURLUE_NO_PASSWORD, /* 12 */ + CURLUE_NO_OPTIONS, /* 13 */ + CURLUE_NO_HOST, /* 14 */ + CURLUE_NO_PORT, /* 15 */ + CURLUE_NO_QUERY, /* 16 */ + CURLUE_NO_FRAGMENT, /* 17 */ + CURLUE_NO_ZONEID, /* 18 */ + CURLUE_BAD_FILE_URL, /* 19 */ + CURLUE_BAD_FRAGMENT, /* 20 */ + CURLUE_BAD_HOSTNAME, /* 21 */ + CURLUE_BAD_IPV6, /* 22 */ + CURLUE_BAD_LOGIN, /* 23 */ + CURLUE_BAD_PASSWORD, /* 24 */ + CURLUE_BAD_PATH, /* 25 */ + CURLUE_BAD_QUERY, /* 26 */ + CURLUE_BAD_SCHEME, /* 27 */ + CURLUE_BAD_SLASHES, /* 28 */ + CURLUE_BAD_USER, /* 29 */ + CURLUE_LACKS_IDN, /* 30 */ + CURLUE_TOO_LARGE, /* 31 */ + CURLUE_LAST +} CURLUcode; + +typedef enum { + CURLUPART_URL, + CURLUPART_SCHEME, + CURLUPART_USER, + CURLUPART_PASSWORD, + CURLUPART_OPTIONS, + CURLUPART_HOST, + CURLUPART_PORT, + CURLUPART_PATH, + CURLUPART_QUERY, + CURLUPART_FRAGMENT, + CURLUPART_ZONEID /* added in 7.65.0 */ +} CURLUPart; + +#define CURLU_DEFAULT_PORT (1<<0) /* return default port number */ +#define CURLU_NO_DEFAULT_PORT (1<<1) /* act as if no port number was set, + if the port number matches the + default for the scheme */ +#define CURLU_DEFAULT_SCHEME (1<<2) /* return default scheme if + missing */ +#define CURLU_NON_SUPPORT_SCHEME (1<<3) /* allow non-supported scheme */ +#define CURLU_PATH_AS_IS (1<<4) /* leave dot sequences */ +#define CURLU_DISALLOW_USER (1<<5) /* no user+password allowed */ +#define CURLU_URLDECODE (1<<6) /* URL decode on get */ +#define CURLU_URLENCODE (1<<7) /* URL encode on set */ +#define CURLU_APPENDQUERY (1<<8) /* append a form style part */ +#define CURLU_GUESS_SCHEME (1<<9) /* legacy curl-style guessing */ +#define CURLU_NO_AUTHORITY (1<<10) /* Allow empty authority when the + scheme is unknown. */ +#define CURLU_ALLOW_SPACE (1<<11) /* Allow spaces in the URL */ +#define CURLU_PUNYCODE (1<<12) /* get the hostname in punycode */ +#define CURLU_PUNY2IDN (1<<13) /* punycode => IDN conversion */ +#define CURLU_GET_EMPTY (1<<14) /* allow empty queries and fragments + when extracting the URL or the + components */ +#define CURLU_NO_GUESS_SCHEME (1<<15) /* for get, do not accept a guess */ + +typedef struct Curl_URL CURLU; + +/* + * curl_url() creates a new CURLU handle and returns a pointer to it. + * Must be freed with curl_url_cleanup(). + */ +CURL_EXTERN CURLU *curl_url(void); + +/* + * curl_url_cleanup() frees the CURLU handle and related resources used for + * the URL parsing. It will not free strings previously returned with the URL + * API. + */ +CURL_EXTERN void curl_url_cleanup(CURLU *handle); + +/* + * curl_url_dup() duplicates a CURLU handle and returns a new copy. The new + * handle must also be freed with curl_url_cleanup(). + */ +CURL_EXTERN CURLU *curl_url_dup(const CURLU *in); + +/* + * curl_url_get() extracts a specific part of the URL from a CURLU + * handle. Returns error code. The returned pointer MUST be freed with + * curl_free() afterwards. + */ +CURL_EXTERN CURLUcode curl_url_get(const CURLU *handle, CURLUPart what, + char **part, unsigned int flags); + +/* + * curl_url_set() sets a specific part of the URL in a CURLU handle. Returns + * error code. The passed in string will be copied. Passing a NULL instead of + * a part string, clears that part. + */ +CURL_EXTERN CURLUcode curl_url_set(CURLU *handle, CURLUPart what, + const char *part, unsigned int flags); + +/* + * curl_url_strerror() turns a CURLUcode value into the equivalent human + * readable error string. This is useful for printing meaningful error + * messages. + */ +CURL_EXTERN const char *curl_url_strerror(CURLUcode); + +#ifdef __cplusplus +} /* end of extern "C" */ +#endif + +#endif /* CURLINC_URLAPI_H */ diff --git a/contrib/curl/include/curl/websockets.h b/contrib/curl/include/curl/websockets.h new file mode 100644 index 0000000000..6ef6a2bc92 --- /dev/null +++ b/contrib/curl/include/curl/websockets.h @@ -0,0 +1,84 @@ +#ifndef CURLINC_WEBSOCKETS_H +#define CURLINC_WEBSOCKETS_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#ifdef __cplusplus +extern "C" { +#endif + +struct curl_ws_frame { + int age; /* zero */ + int flags; /* See the CURLWS_* defines */ + curl_off_t offset; /* the offset of this data into the frame */ + curl_off_t bytesleft; /* number of pending bytes left of the payload */ + size_t len; /* size of the current data chunk */ +}; + +/* flag bits */ +#define CURLWS_TEXT (1<<0) +#define CURLWS_BINARY (1<<1) +#define CURLWS_CONT (1<<2) +#define CURLWS_CLOSE (1<<3) +#define CURLWS_PING (1<<4) +#define CURLWS_OFFSET (1<<5) + +/* + * NAME curl_ws_recv() + * + * DESCRIPTION + * + * Receives data from the websocket connection. Use after successful + * curl_easy_perform() with CURLOPT_CONNECT_ONLY option. + */ +CURL_EXTERN CURLcode curl_ws_recv(CURL *curl, void *buffer, size_t buflen, + size_t *recv, + const struct curl_ws_frame **metap); + +/* flags for curl_ws_send() */ +#define CURLWS_PONG (1<<6) + +/* + * NAME curl_ws_send() + * + * DESCRIPTION + * + * Sends data over the websocket connection. Use after successful + * curl_easy_perform() with CURLOPT_CONNECT_ONLY option. + */ +CURL_EXTERN CURLcode curl_ws_send(CURL *curl, const void *buffer, + size_t buflen, size_t *sent, + curl_off_t fragsize, + unsigned int flags); + +/* bits for the CURLOPT_WS_OPTIONS bitmask: */ +#define CURLWS_RAW_MODE (1<<0) + +CURL_EXTERN const struct curl_ws_frame *curl_ws_meta(CURL *curl); + +#ifdef __cplusplus +} +#endif + +#endif /* CURLINC_WEBSOCKETS_H */ diff --git a/contrib/curl/lib/.gitignore b/contrib/curl/lib/.gitignore deleted file mode 100644 index 719fc977f3..0000000000 --- a/contrib/curl/lib/.gitignore +++ /dev/null @@ -1,12 +0,0 @@ -*.a -*.imp -*.nlm -*.orig -*.rej -*.res -TAGS -curl_config.h -curl_config.h.in -libcurl.plist.dist -libcurl.vers -stamp-h1 diff --git a/contrib/curl/lib/CMakeLists.txt b/contrib/curl/lib/CMakeLists.txt deleted file mode 100644 index eb2de6d87a..0000000000 --- a/contrib/curl/lib/CMakeLists.txt +++ /dev/null @@ -1,109 +0,0 @@ -set(LIB_NAME libcurl) - -configure_file(${CURL_SOURCE_DIR}/include/curl/curlbuild.h.cmake - ${CURL_BINARY_DIR}/include/curl/curlbuild.h) -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 - ${CURL_BINARY_DIR}/include/curl/curlbuild.h - ) - -if(MSVC) - list(APPEND CSOURCES libcurl.rc) -endif() - -# SET(CSOURCES -# # memdebug.c -not used -# # nwlib.c - Not used -# # strtok.c - specify later -# # strtoofft.c - specify later -# ) - -# # if we have Kerberos 4, right now this is never on -# #OPTION(CURL_KRB4 "Use Kerberos 4" OFF) -# IF(CURL_KRB4) -# SET(CSOURCES ${CSOURCES} -# krb4.c -# security.c -# ) -# ENDIF(CURL_KRB4) - -# #OPTION(CURL_MALLOC_DEBUG "Debug mallocs in Curl" OFF) -# MARK_AS_ADVANCED(CURL_MALLOC_DEBUG) -# IF(CURL_MALLOC_DEBUG) -# SET(CSOURCES ${CSOURCES} -# memdebug.c -# ) -# ENDIF(CURL_MALLOC_DEBUG) - -# # only build compat strtoofft if we need to -# IF(NOT HAVE_STRTOLL AND NOT HAVE__STRTOI64) -# SET(CSOURCES ${CSOURCES} -# strtoofft.c -# ) -# ENDIF(NOT HAVE_STRTOLL AND NOT HAVE__STRTOI64) - - -# 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() - -if(CURL_STATICLIB) - # Static lib - set(CURL_USER_DEFINED_DYNAMIC_OR_STATIC STATIC) -else() - # DLL / so dynamic lib - set(CURL_USER_DEFINED_DYNAMIC_OR_STATIC SHARED) -endif() - -add_library( - ${LIB_NAME} - ${CURL_USER_DEFINED_DYNAMIC_OR_STATIC} - ${HHEADERS} ${CSOURCES} - ) - -if(MSVC AND CURL_STATICLIB) - set_target_properties(${LIB_NAME} PROPERTIES STATIC_LIBRARY_FLAGS ${CMAKE_EXE_LINKER_FLAGS}) -endif() - -target_link_libraries(${LIB_NAME} ${CURL_LIBS}) - -if(WIN32) - add_definitions( -D_USRDLL ) -endif() - -set_target_properties(${LIB_NAME} PROPERTIES COMPILE_DEFINITIONS BUILDING_LIBCURL) - -if(HIDES_CURL_PRIVATE_SYMBOLS) - set_property(TARGET ${LIB_NAME} APPEND PROPERTY COMPILE_DEFINITIONS "CURL_HIDDEN_SYMBOLS") - set_property(TARGET ${LIB_NAME} APPEND PROPERTY COMPILE_FLAGS ${CURL_CFLAG_SYMBOLS_HIDE}) -endif() - -# Remove the "lib" prefix since the library is already named "libcurl". -set_target_properties(${LIB_NAME} PROPERTIES PREFIX "") -set_target_properties(${LIB_NAME} PROPERTIES IMPORT_PREFIX "") - -if(WIN32) - if(NOT CURL_STATICLIB) - # Add "_imp" as a suffix before the extension to avoid conflicting with the statically linked "libcurl.lib" - set_target_properties(${LIB_NAME} PROPERTIES IMPORT_SUFFIX "_imp.lib") - endif() -endif() - -install(TARGETS ${LIB_NAME} - ARCHIVE DESTINATION lib - LIBRARY DESTINATION lib - RUNTIME DESTINATION bin) diff --git a/contrib/curl/lib/Makefile.Watcom b/contrib/curl/lib/Makefile.Watcom deleted file mode 100644 index cbc54cfc76..0000000000 --- a/contrib/curl/lib/Makefile.Watcom +++ /dev/null @@ -1,279 +0,0 @@ -#*************************************************************************** -# _ _ ____ _ -# Project ___| | | | _ \| | -# / __| | | | |_) | | -# | (__| |_| | _ <| |___ -# \___|\___/|_| \_\_____| -# -# Copyright (C) 2005 - 2009, Gisle Vanem . -# Copyright (C) 2005 - 2015, Daniel Stenberg, , et al. -# -# This software is licensed as described in the file COPYING, which -# you should have received as part of this distribution. The terms -# are also available at https://curl.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. -# -#*************************************************************************** - -# -# Watcom / OpenWatcom / Win32 makefile for libcurl. -# - -.ERASE - -!if $(__VERSION__) < 1280 -!message !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -!message ! This Open Watcom version is too old and is no longer supported ! -!message ! Please download latest version from www.openwatcom.org ! -!message !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -!error Unsupported version of Open Watcom -!endif - -!ifndef %watcom -!error WATCOM environment variable not set! -!endif - -# In order to process Makefile.inc wmake must be called with -u switch! -!ifndef %MAKEFLAGS -!error You MUST call wmake with the -u switch! -!endif - -!ifdef %libname -LIBNAME = $(%libname) -!else -LIBNAME = libcurl -!endif -TARGETS = $(LIBNAME).dll $(LIBNAME).lib - -CC = wcc386 -LD = wlink -AR = wlib -RC = wrc - -!ifdef __LOADDLL__ -! loaddll wcc386 wccd386 -! loaddll wpp386 wppd386 -! loaddll wlib wlibd -! loaddll wlink wlinkd -!endif - -!ifdef __LINUX__ -CP = cp -MD = mkdir -p -!else -CP = copy 2>NUL -MD = mkdir -!endif -!if $(__VERSION__) > 1290 -RD = rm -rf -!else ifdef __UNIX__ -RD = rm -rf -!else -RD = rmdir /q /s 2>NUL -!endif - -SYS_INCL = -I"$(%watcom)/h/nt" -I"$(%watcom)/h" - -CFLAGS = -3r -mf -hc -zff -zgf -zq -zm -zc -s -fr=con -w2 -fpi -oilrtfm & - -wcd=201 -bt=nt -d+ -dWIN32 -dCURL_WANTS_CA_BUNDLE_ENV & - -dBUILDING_LIBCURL -I. -I"../include" $(SYS_INCL) - -!ifdef %debug -DEBUG = -dDEBUG=1 -dDEBUGBUILD -CFLAGS += -d3 $(DEBUG) -!else -CFLAGS += -d0 -!endif - -!ifdef %use_ipv6 -CFLAGS += -d_WIN32_WINNT=0x0501 -dENABLE_IPV6 -!endif - -!ifdef %use_sspi -CFLAGS += -dUSE_WINDOWS_SSPI -!endif - -!ifdef %use_winssl -CFLAGS += -dUSE_WINDOWS_SSPI -CFLAGS += -DUSE_SCHANNEL -!endif - -!ifdef %use_winidn -CFLAGS += -dWINVER=0x0600 -dUSE_WIN32_IDN -! if $(__VERSION__) <= 1290 -CFLAGS += -dWANT_IDN_PROTOTYPES -! endif -!endif - -# -# Change to suite. -# -!ifdef %zlib_root -ZLIB_ROOT = $(%zlib_root) -!else -ZLIB_ROOT = ../../zlib-1.2.8 -!endif - -!ifdef %libssh2_root -LIBSSH2_ROOT = $(%libssh2_root) -!else -LIBSSH2_ROOT = ../../libssh2-1.5.0 -!endif - -!ifdef %librtmp_root -LIBRTMP_ROOT = $(%librtmp_root) -!else -LIBRTMP_ROOT = ../../rtmpdump-2.3 -!endif - -!ifdef %openssl_root -OPENSSL_ROOT = $(%openssl_root) -!else -OPENSSL_ROOT = ../../openssl-1.0.2a -!endif - -!ifdef %ares_root -ARES_ROOT = $(%ares_root) -!else -ARES_ROOT = ../ares -!endif - -!ifdef %use_zlib -CFLAGS += -dHAVE_ZLIB_H -dHAVE_LIBZ -I"$(ZLIB_ROOT)" -!endif - -!ifdef %use_rtmp -CFLAGS += -dUSE_LIBRTMP -I"$(LIBRTMP_ROOT)" -!endif - -!ifdef %use_ssh2 -CFLAGS += -DUSE_LIBSSH2 -DHAVE_LIBSSH2_H -I"$(LIBSSH2_ROOT)/include" -I"$(LIBSSH2_ROOT)/win32" -!endif - -!ifdef %use_ssl -CFLAGS += -wcd=138 -dUSE_OPENSSL -dUSE_SSLEAY -I"$(OPENSSL_ROOT)/inc32" -!endif - -!ifdef %use_ares -CFLAGS += -dUSE_ARES -I"$(ARES_ROOT)" -!endif - -!ifdef %use_watt32 -CFLAGS += -dUSE_WATT32 -I"$(%watt_root)/inc" -!endif - -OBJ_BASE = WC_Win32.obj -!if $(__VERSION__) > 1290 -OBJ_STAT = $(OBJ_BASE)/stat -OBJ_DYN = $(OBJ_BASE)/dyn -!else ifdef __UNIX__ -OBJ_STAT = $(OBJ_BASE)/stat -OBJ_DYN = $(OBJ_BASE)/dyn -!else -OBJ_STAT = $(OBJ_BASE)\stat -OBJ_DYN = $(OBJ_BASE)\dyn -!endif - -LINK_ARG = $(OBJ_DYN)/wlink.arg -LIB_ARG = $(OBJ_STAT)/wlib.arg - -!include Makefile.inc - -OBJS1 = ./$(CSOURCES:.c=.obj) -OBJS2 = $(OBJS1:vtls/=) -OBJS3 = $(OBJS2:vauth/=) -OBJS4 = $(OBJS3: = ./) -OBJS_STAT = $(OBJS4:./=$(OBJ_STAT)/) -OBJS_DYN = $(OBJS4:./=$(OBJ_DYN)/) - -CURLBUILDH = ../include/curl/curlbuild.h -RESOURCE = $(OBJ_DYN)/libcurl.res - -DIRS = $(OBJ_BASE) $(OBJ_BASE)/stat $(OBJ_BASE)/dyn - -.c : vauth vtls - -all: $(CURLBUILDH) $(DIRS) $(TARGETS) .SYMBOLIC - @echo Welcome to libcurl - -clean: .SYMBOLIC - -rm -f $(OBJS_STAT) - -rm -f $(OBJS_DYN) - -rm -f $(RESOURCE) $(LINK_ARG) $(LIB_ARG) - -vclean distclean: clean .SYMBOLIC - -rm -f $(TARGETS) $(LIBNAME).map $(LIBNAME).sym - -$(RD) $(OBJ_STAT) - -$(RD) $(OBJ_DYN) - -$(RD) $(OBJ_BASE) - -$(DIRS): - -$(MD) $^@ - -$(CURLBUILDH): .EXISTSONLY - $(CP) $^@.dist $^@ - -$(LIBNAME).dll: $(OBJS_DYN) $(RESOURCE) $(__MAKEFILES__) - %create $(LINK_ARG) - @%append $(LINK_ARG) system nt dll -!ifdef %debug - @%append $(LINK_ARG) debug all - @%append $(LINK_ARG) option symfile -!endif - @%append $(LINK_ARG) option quiet, caseexact, eliminate - @%append $(LINK_ARG) option map=$(OBJ_DYN)/$(LIBNAME).map - @%append $(LINK_ARG) option implib=$(LIBNAME)_imp.lib - @%append $(LINK_ARG) option res=$(RESOURCE) - @for %f in ($(OBJS_DYN)) do @%append $(LINK_ARG) file %f - @%append $(LINK_ARG) library wldap32.lib -!ifdef %use_watt32 - @%append $(LINK_ARG) library '$(%watt_root)/lib/wattcpw_imp.lib' -!else - @%append $(LINK_ARG) library ws2_32.lib -!endif -!ifdef %use_zlib - @%append $(LINK_ARG) library '$(ZLIB_ROOT)/zlib.lib' -!endif -!ifdef %use_rtmp - @%append $(LINK_ARG) library '$(LIBRTMP_ROOT)/librtmp/librtmp.lib' -!endif -!ifdef %use_ssh2 - @%append $(LINK_ARG) library '$(LIBSSH2_ROOT)/win32/libssh2.lib' -!endif -!ifdef %use_ssl - @%append $(LINK_ARG) library '$(OPENSSL_ROOT)/out32/libeay32.lib' - @%append $(LINK_ARG) library '$(OPENSSL_ROOT)/out32/ssleay32.lib' -!endif -!ifdef %use_ares - @%append $(LINK_ARG) library '$(ARES_ROOT)/cares.lib' -!endif -!ifdef %use_winidn -! if $(__VERSION__) > 1290 - @%append $(LINK_ARG) library normaliz.lib -! else - @%append $(LINK_ARG) import '_IdnToAscii@20' 'NORMALIZ.DLL'.'IdnToAscii' - @%append $(LINK_ARG) import '_IdnToUnicode@20' 'NORMALIZ.DLL'.'IdnToUnicode' -! endif -!endif - $(LD) name $^@ @$(LINK_ARG) - -$(LIBNAME).lib: $(OBJS_STAT) - %create $(LIB_ARG) - @for %f in ($<) do @%append $(LIB_ARG) +- %f - $(AR) -q -b -c -pa $^@ @$(LIB_ARG) - -$(RESOURCE): libcurl.rc - $(RC) $(DEBUG) -q -r -zm -bt=nt -I"../include" $(SYS_INCL) $[@ -fo=$^@ - -.c{$(OBJ_DYN)}.obj: - $(CC) $(CFLAGS) -bd -br $[@ -fo=$^@ - -.c{$(OBJ_STAT)}.obj: - $(CC) $(CFLAGS) -DCURL_STATICLIB $[@ -fo=$^@ - diff --git a/contrib/curl/lib/Makefile.am b/contrib/curl/lib/Makefile.am deleted file mode 100644 index cc7ee9031e..0000000000 --- a/contrib/curl/lib/Makefile.am +++ /dev/null @@ -1,155 +0,0 @@ -#*************************************************************************** -# _ _ ____ _ -# Project ___| | | | _ \| | -# / __| | | | |_) | | -# | (__| |_| | _ <| |___ -# \___|\___/|_| \_\_____| -# -# Copyright (C) 1998 - 2017, Daniel Stenberg, , et al. -# -# This software is licensed as described in the file COPYING, which -# you should have received as part of this distribution. The terms -# are also available at https://curl.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. -# -########################################################################### -AUTOMAKE_OPTIONS = foreign nostdinc - -CMAKE_DIST = CMakeLists.txt curl_config.h.cmake - -EXTRA_DIST = Makefile.b32 Makefile.m32 config-win32.h \ - config-win32ce.h config-riscos.h config-mac.h curl_config.h.in \ - makefile.dj config-dos.h libcurl.plist libcurl.rc config-amigaos.h \ - makefile.amiga Makefile.netware nwlib.c nwos.c config-win32ce.h \ - config-os400.h setup-os400.h config-symbian.h Makefile.Watcom \ - config-tpf.h mk-ca-bundle.pl mk-ca-bundle.vbs $(CMAKE_DIST) \ - firefox-db2pem.sh config-vxworks.h Makefile.vxworks checksrc.pl \ - objnames-test08.sh objnames-test10.sh objnames.inc - -lib_LTLIBRARIES = libcurl.la - -if BUILD_UNITTESTS -noinst_LTLIBRARIES = libcurlu.la -else -noinst_LTLIBRARIES = -endif - -# This might hold -Werror -CFLAGS += @CURL_CFLAG_EXTRAS@ - -# Specify our include paths here, and do it relative to $(top_srcdir) and -# $(top_builddir), to ensure that these paths which belong to the library -# being currently built and tested are searched before the library which -# might possibly already be installed in the system. -# -# $(top_builddir)/include/curl for generated curlbuild.h included from curl.h -# $(top_builddir)/include for generated curlbuild.h inc. from lib/curl_setup.h -# $(top_srcdir)/include is for libcurl's external include files -# $(top_builddir)/lib is for libcurl's generated lib/curl_config.h file -# $(top_srcdir)/lib for libcurl's lib/curl_setup.h and other "private" files -# $(top_builddir)/ares is for in-tree c-ares's generated ares_build.h file -# $(top_srcdir)/ares is for in-tree c-ares's external include files - -AM_CPPFLAGS = -I$(top_builddir)/include/curl \ - -I$(top_builddir)/include \ - -I$(top_srcdir)/include \ - -I$(top_builddir)/lib \ - -I$(top_srcdir)/lib - -if USE_EMBEDDED_ARES -AM_CPPFLAGS += -I$(top_builddir)/ares \ - -I$(top_srcdir)/ares -endif - -# Prevent LIBS from being used for all link targets -LIBS = $(BLANK_AT_MAKETIME) - -if SONAME_BUMP -# -# Bumping of SONAME conditionally may seem like a weird thing to do, and yeah -# it is. The problem is that we try to avoid the bump as hard as possible, but -# yet it is still necessary for a few rare situations. The configure script will -# attempt to figure out these situations, and it can be forced to consider this -# to be such a case! See README.curl_off_t for further details. -# -# This conditional soname bump SHOULD be removed at next "proper" bump. -# -VERSIONINFO=-version-info 9:0:4 -else -VERSIONINFO=-version-info 8:0:4 -endif - -# This flag accepts an argument of the form current[:revision[:age]]. So, -# passing -version-info 3:12:1 sets current to 3, revision to 12, and age to -# 1. -# -# Here's the simplified rule guide on how to change -version-info: -# (current version is C:R:A) -# -# 1. if there are only source changes, use C:R+1:A -# 2. if interfaces were added use C+1:0:A+1 -# 3. if interfaces were removed, then use C+1:0:0 -# -# For the full guide on libcurl ABI rules, see docs/libcurl/ABI - -AM_CPPFLAGS += -DBUILDING_LIBCURL -AM_LDFLAGS = -AM_CFLAGS = - -libcurl_la_CPPFLAGS_EXTRA = -libcurl_la_LDFLAGS_EXTRA = -libcurl_la_CFLAGS_EXTRA = - -if CURL_LT_SHLIB_USE_VERSION_INFO -libcurl_la_LDFLAGS_EXTRA += $(VERSIONINFO) -endif - -if CURL_LT_SHLIB_USE_NO_UNDEFINED -libcurl_la_LDFLAGS_EXTRA += -no-undefined -endif - -if CURL_LT_SHLIB_USE_MIMPURE_TEXT -libcurl_la_LDFLAGS_EXTRA += -mimpure-text -endif - -if CURL_LT_SHLIB_USE_VERSIONED_SYMBOLS -libcurl_la_LDFLAGS_EXTRA += -Wl,--version-script=libcurl.vers -endif - -if USE_CPPFLAG_CURL_STATICLIB -libcurl_la_CPPFLAGS_EXTRA += -DCURL_STATICLIB -endif - -if DOING_CURL_SYMBOL_HIDING -libcurl_la_CPPFLAGS_EXTRA += -DCURL_HIDDEN_SYMBOLS -libcurl_la_CFLAGS_EXTRA += $(CFLAG_CURL_SYMBOL_HIDING) -endif - -libcurl_la_CPPFLAGS = $(AM_CPPFLAGS) $(libcurl_la_CPPFLAGS_EXTRA) -libcurl_la_LDFLAGS = $(AM_LDFLAGS) $(libcurl_la_LDFLAGS_EXTRA) $(LDFLAGS) $(LIBCURL_LIBS) -libcurl_la_CFLAGS = $(AM_CFLAGS) $(libcurl_la_CFLAGS_EXTRA) - -libcurlu_la_CPPFLAGS = $(AM_CPPFLAGS) -DCURL_STATICLIB -DUNITTESTS -libcurlu_la_LDFLAGS = $(AM_LDFLAGS) -static $(LIBCURL_LIBS) -libcurlu_la_CFLAGS = $(AM_CFLAGS) - -# Makefile.inc provides the CSOURCES and HHEADERS defines -include Makefile.inc - -libcurl_la_SOURCES = $(CSOURCES) $(HHEADERS) -libcurlu_la_SOURCES = $(CSOURCES) $(HHEADERS) - -checksrc: - @PERL@ $(srcdir)/checksrc.pl -D$(srcdir) -W$(srcdir)/curl_config.h \ - $(srcdir)/*.[ch] $(srcdir)/vauth/*.[ch] $(srcdir)/vtls/*.[ch] - -if CURLDEBUG -# for debug builds, we scan the sources on all regular make invokes -all-local: checksrc -endif diff --git a/contrib/curl/lib/Makefile.b32 b/contrib/curl/lib/Makefile.b32 deleted file mode 100644 index 481386157d..0000000000 --- a/contrib/curl/lib/Makefile.b32 +++ /dev/null @@ -1,185 +0,0 @@ -#*************************************************************************** -# _ _ ____ _ -# Project ___| | | | _ \| | -# / __| | | | |_) | | -# | (__| |_| | _ <| |___ -# \___|\___/|_| \_\_____| -# -# Copyright (C) 2000, Jaepil Kim, . -# Copyright (C) 2001 - 2015, Daniel Stenberg, , et al. -# -# This software is licensed as described in the file COPYING, which -# you should have received as part of this distribution. The terms -# are also available at https://curl.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. -# -#*************************************************************************** - -############################################################ -# -# Makefile.b32 - Borland's C++ Compiler 5.X -# -# 'BCCDIR' has to be set up to point to the base directory -# of the compiler, i.e. SET BCCDIR = c:\Borland\BCC55 -# -############################################################ - -!if "$(__MAKE__)" == "" -!error __MAKE__ not defined. Use Borlands's MAKE to process this makefile. -!endif - -# Borland's $(MAKEDIR) expands to the path where make.exe is located, -# use this feature to define BCCDIR when user has not defined BCCDIR. -!ifndef BCCDIR -BCCDIR = $(MAKEDIR)\.. -!endif - -# Edit the path below to point to the base of your Zlib sources. -!ifndef ZLIB_PATH -ZLIB_PATH = ..\..\zlib-1.2.8 -!endif - -# Edit the path below to point to the base of your OpenSSL package. -!ifndef OPENSSL_PATH -OPENSSL_PATH = ..\..\openssl-1.0.2a -!endif - -# Set libcurl static lib, dll and import lib -LIBCURL_LIB = libcurl.lib -LIBCURL_DLL = libcurl.dll -LIBCURL_IMPLIB = libcurl_imp.lib - -# Setup environment -PP_CMD = cpp32 -q -P- -CC_CMD = bcc32 -q -c -LD = bcc32 -RM = del 2>NUL -MKDIR = md -RMDIR = rd /q -LIB = tlib -IMPLIB = implib - -CC_FLAGS = -5 -O2 -tWM -w -w-aus -w-ccc -w-dup -w-prc -w-pro -w-rch -w-sig -w-spa -w-inl -w-pia -w-pin -Dinline=__inline -LIBFLAGS = /C /P32 -LDFLAGS = -q -lq -laa -tWD - -SRCDIR = .;.\vauth;.\vtls -OBJDIR = .\BCC_objs -INCDIRS = -I.;.\lib;..\include -LINKLIB = $(BCCDIR)\lib\cw32mt.lib $(BCCDIR)\lib\ws2_32.lib -DEFINES = -DNDEBUG -DWIN32 -DBUILDING_LIBCURL - -# By default SSPI support is enabled for BCC -!ifndef DISABLE_SSPI -DEFINES = $(DEFINES) -DUSE_WINDOWS_SSPI -!endif - -# By default LDAP support is disabled for BCC -!ifndef WITH_LDAP -DEFINES = $(DEFINES) -DCURL_DISABLE_LDAP -!endif - -# ZLIB support is enabled setting WITH_ZLIB=1 -!ifdef WITH_ZLIB -DEFINES = $(DEFINES) -DHAVE_LIBZ -DHAVE_ZLIB_H -INCDIRS = $(INCDIRS);$(ZLIB_PATH) -LINKLIB = $(LINKLIB) $(ZLIB_PATH)\zlib.lib -!endif - -# SSL support is enabled setting WITH_SSL=1 -!ifdef WITH_SSL -DEFINES = $(DEFINES) -DUSE_OPENSSL -INCDIRS = $(INCDIRS);$(OPENSSL_PATH)\inc32;$(OPENSSL_PATH)\inc32\openssl -LINKLIB = $(LINKLIB) $(OPENSSL_PATH)\out32\ssleay32.lib $(OPENSSL_PATH)\out32\libeay32.lib -!endif - -.autodepend - -.path.c = $(SRCDIR) -.path.obj = $(OBJDIR) -.path.int = $(OBJDIR) - -# Makefile.inc provides the CSOURCES and HHEADERS defines -!include Makefile.inc - -# Borland's command line librarian program TLIB version 4.5 is not capable -# of building a library when any of its objects contains an hypen in its -# name, due to a command line parsing bug. In order to workaround this, we -# build source files with hyphens in their name as objects with underscores -# using explicit compilation build rules instead of implicit ones. - -NOHYPHEN1 = $(CSOURCES:-=_) -NOHYPHEN2 = $(NOHYPHEN1:vauth/=) -NOHYPHEN3 = $(NOHYPHEN2:vtls/=) - -OBJECTS = $(NOHYPHEN3:.c=.obj) -PREPROCESSED = $(NOHYPHEN3:.c=.int) - -# Borland's command line compiler (BCC32) version 5.5.1 integrated -# preprocessor has a bug which results in silently generating wrong -# definitions for libcurl macros such as CURL_OFF_T_C, on the other -# hand Borland's command line preprocessor (CPP32) version 5.5.1 does -# not have the bug and achieves proper results. In order to avoid the -# silent bug we first preprocess source files and later compile the -# preprocessed result. - -.c.obj: - @-$(RM) $(@R).int - $(PP_CMD) $(CC_FLAGS) $(INCDIRS) $(DEFINES) -o$(@R).int $(<) - $(CC_CMD) $(CC_FLAGS) -o$(@) $(@R).int - -all: $(OBJDIR) $(LIBCURL_LIB) $(LIBCURL_DLL) - -asyn_ares.obj: asyn-ares.c - @-$(RM) $(@R).int - $(PP_CMD) $(CC_FLAGS) $(INCDIRS) $(DEFINES) -o$(@R).int $(?) - $(CC_CMD) $(CC_FLAGS) -o$(@) $(@R).int - -asyn_thread.obj: asyn-thread.c - @-$(RM) $(@R).int - $(PP_CMD) $(CC_FLAGS) $(INCDIRS) $(DEFINES) -o$(@R).int $(?) - $(CC_CMD) $(CC_FLAGS) -o$(@) $(@R).int - -non_ascii.obj: non-ascii.c - @-$(RM) $(@R).int - $(PP_CMD) $(CC_FLAGS) $(INCDIRS) $(DEFINES) -o$(@R).int $(?) - $(CC_CMD) $(CC_FLAGS) -o$(@) $(@R).int - -clean: - cd $(OBJDIR) - @-$(RM) $(OBJECTS) - @-$(RM) $(PREPROCESSED) - cd .. - @-$(RMDIR) $(OBJDIR) - @-$(RM) $(LIBCURL_LIB) - @-$(RM) $(LIBCURL_IMPLIB) - @-$(RM) libcurl.tds - -$(OBJDIR): - @-$(RMDIR) $(OBJDIR) - @-$(MKDIR) $(OBJDIR) - -$(LIBCURL_LIB): $(OBJECTS) - @-$(RM) $(LIBCURL_LIB) - $(LIB) $(LIBFLAGS) $@ @&&! -+$(**: = &^ -+) -! - -$(LIBCURL_DLL) $(LIBCURL_IMPLIB): $(OBJECTS) $(LINKLIB) - @-$(RM) $(LIBCURL_DLL) - @-$(RM) $(LIBCURL_IMPLIB) - $(LD) $(LDFLAGS) -e$(LIBCURL_DLL) @&&! -$(**: = ^ -) -! - $(IMPLIB) $(LIBCURL_IMPLIB) $(LIBCURL_DLL) - - -# End of Makefile.b32 diff --git a/contrib/curl/lib/Makefile.inc b/contrib/curl/lib/Makefile.inc deleted file mode 100644 index 19f58000a1..0000000000 --- a/contrib/curl/lib/Makefile.inc +++ /dev/null @@ -1,80 +0,0 @@ -#*************************************************************************** -# _ _ ____ _ -# Project ___| | | | _ \| | -# / __| | | | |_) | | -# | (__| |_| | _ <| |___ -# \___|\___/|_| \_\_____| -# -# Copyright (C) 1998 - 2016, Daniel Stenberg, , et al. -# -# This software is licensed as described in the file COPYING, which -# you should have received as part of this distribution. The terms -# are also available at https://curl.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. -# -########################################################################### - -LIB_VAUTH_CFILES = vauth/vauth.c vauth/cleartext.c vauth/cram.c \ - vauth/digest.c vauth/digest_sspi.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 - -LIB_VAUTH_HFILES = vauth/vauth.h vauth/digest.h vauth/ntlm.h - -LIB_VTLS_CFILES = vtls/openssl.c vtls/gtls.c vtls/vtls.c vtls/nss.c \ - vtls/polarssl.c vtls/polarssl_threadlock.c vtls/axtls.c \ - vtls/cyassl.c vtls/schannel.c vtls/darwinssl.c vtls/gskit.c \ - vtls/mbedtls.c - -LIB_VTLS_HFILES = vtls/openssl.h vtls/vtls.h vtls/gtls.h \ - vtls/nssg.h vtls/polarssl.h vtls/polarssl_threadlock.h vtls/axtls.h \ - vtls/cyassl.h vtls/schannel.h vtls/darwinssl.h vtls/gskit.h \ - vtls/mbedtls.h - -LIB_CFILES = file.c timeval.c base64.c hostip.c progress.c formdata.c \ - cookie.c http.c sendf.c ftp.c url.c dict.c if2ip.c speedcheck.c \ - ldap.c version.c getenv.c escape.c mprintf.c telnet.c netrc.c \ - getinfo.c transfer.c strcase.c easy.c security.c curl_fnmatch.c \ - fileinfo.c ftplistparser.c wildcard.c krb5.c memdebug.c http_chunks.c \ - strtok.c connect.c llist.c hash.c multi.c content_encoding.c share.c \ - http_digest.c md4.c md5.c http_negotiate.c inet_pton.c strtoofft.c \ - strerror.c amigaos.c hostasyn.c hostip4.c hostip6.c hostsyn.c \ - inet_ntop.c parsedate.c select.c tftp.c splay.c strdup.c socks.c \ - ssh.c curl_addrinfo.c socks_gssapi.c socks_sspi.c \ - curl_sspi.c slist.c nonblock.c curl_memrchr.c imap.c pop3.c smtp.c \ - pingpong.c rtsp.c curl_threads.c warnless.c hmac.c curl_rtmp.c \ - openldap.c curl_gethostname.c gopher.c idn_win32.c \ - http_proxy.c non-ascii.c asyn-ares.c asyn-thread.c curl_gssapi.c \ - http_ntlm.c curl_ntlm_wb.c curl_ntlm_core.c curl_sasl.c rand.c \ - curl_multibyte.c hostcheck.c conncache.c pipeline.c dotdot.c \ - x509asn1.c http2.c smb.c curl_endian.c curl_des.c system_win32.c - -LIB_HFILES = arpa_telnet.h netrc.h file.h timeval.h hostip.h progress.h \ - formdata.h cookie.h http.h sendf.h ftp.h url.h dict.h if2ip.h \ - speedcheck.h urldata.h curl_ldap.h escape.h telnet.h getinfo.h \ - strcase.h curl_sec.h memdebug.h http_chunks.h curl_fnmatch.h \ - wildcard.h fileinfo.h ftplistparser.h strtok.h connect.h llist.h \ - hash.h content_encoding.h share.h curl_md4.h curl_md5.h http_digest.h \ - http_negotiate.h inet_pton.h amigaos.h strtoofft.h strerror.h \ - inet_ntop.h curlx.h curl_memory.h curl_setup.h transfer.h select.h \ - easyif.h multiif.h parsedate.h tftp.h sockaddr.h splay.h strdup.h \ - socks.h ssh.h curl_base64.h curl_addrinfo.h curl_sspi.h \ - slist.h nonblock.h curl_memrchr.h imap.h pop3.h smtp.h pingpong.h \ - rtsp.h curl_threads.h warnless.h curl_hmac.h curl_rtmp.h \ - curl_gethostname.h gopher.h http_proxy.h non-ascii.h asyn.h \ - http_ntlm.h curl_gssapi.h curl_ntlm_wb.h curl_ntlm_core.h \ - curl_sasl.h curl_multibyte.h hostcheck.h conncache.h \ - curl_setup_once.h multihandle.h setup-vms.h pipeline.h dotdot.h \ - x509asn1.h http2.h sigpipe.h smb.h curl_endian.h curl_des.h \ - curl_printf.h system_win32.h rand.h - -LIB_RCFILES = libcurl.rc - -CSOURCES = $(LIB_CFILES) $(LIB_VAUTH_CFILES) $(LIB_VTLS_CFILES) -HHEADERS = $(LIB_HFILES) $(LIB_VAUTH_HFILES) $(LIB_VTLS_HFILES) diff --git a/contrib/curl/lib/Makefile.m32 b/contrib/curl/lib/Makefile.m32 deleted file mode 100644 index 522dbd1df7..0000000000 --- a/contrib/curl/lib/Makefile.m32 +++ /dev/null @@ -1,363 +0,0 @@ -#*************************************************************************** -# _ _ ____ _ -# Project ___| | | | _ \| | -# / __| | | | |_) | | -# | (__| |_| | _ <| |___ -# \___|\___/|_| \_\_____| -# -# Copyright (C) 1999 - 2015, Daniel Stenberg, , et al. -# -# This software is licensed as described in the file COPYING, which -# you should have received as part of this distribution. The terms -# are also available at https://curl.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. -# -#*************************************************************************** - -########################################################################### -# -## Makefile for building libcurl.a with MingW (GCC-3.2 or later) -## and optionally OpenSSL (1.0.2a), libssh2 (1.5), zlib (1.2.8), librtmp (2.4) -## -## Usage: mingw32-make -f Makefile.m32 CFG=-feature1[-feature2][-feature3][...] -## Example: mingw32-make -f Makefile.m32 CFG=-zlib-ssl-sspi-winidn -## -## Hint: you can also set environment vars to control the build, f.e.: -## set ZLIB_PATH=c:/zlib-1.2.8 -## set ZLIB=1 -# -########################################################################### - -# Edit the path below to point to the base of your Zlib sources. -ifndef ZLIB_PATH -ZLIB_PATH = ../../zlib-1.2.8 -endif -# Edit the path below to point to the base of your OpenSSL package. -ifndef OPENSSL_PATH -OPENSSL_PATH = ../../openssl-1.0.2a -endif -# Edit the path below to point to the base of your LibSSH2 package. -ifndef LIBSSH2_PATH -LIBSSH2_PATH = ../../libssh2-1.5.0 -endif -# Edit the path below to point to the base of your librtmp package. -ifndef LIBRTMP_PATH -LIBRTMP_PATH = ../../librtmp-2.4 -endif -# Edit the path below to point to the base of your libidn package. -ifndef LIBIDN_PATH -LIBIDN_PATH = ../../libidn-1.32 -endif -# Edit the path below to point to the base of your MS IDN package. -# Microsoft Internationalized Domain Names (IDN) Mitigation APIs 1.1 -# https://www.microsoft.com/en-us/download/details.aspx?id=734 -ifndef WINIDN_PATH -WINIDN_PATH = ../../Microsoft IDN Mitigation APIs -endif -# Edit the path below to point to the base of your Novell LDAP NDK. -ifndef LDAP_SDK -LDAP_SDK = c:/novell/ndk/cldapsdk/win32 -endif -# Edit the path below to point to the base of your nghttp2 package. -ifndef NGHTTP2_PATH -NGHTTP2_PATH = ../../nghttp2-1.0.0 -endif - -PROOT = .. - -# Edit the path below to point to the base of your c-ares package. -ifndef LIBCARES_PATH -LIBCARES_PATH = $(PROOT)/ares -endif - -CC = $(CROSSPREFIX)gcc -CFLAGS = $(CURL_CFLAG_EXTRAS) -g -O2 -Wall -CFLAGS += -fno-strict-aliasing -# comment LDFLAGS below to keep debug info -LDFLAGS = $(CURL_LDFLAG_EXTRAS) $(CURL_LDFLAG_EXTRAS_DLL) -s -AR = $(CROSSPREFIX)ar -RANLIB = $(CROSSPREFIX)ranlib -RC = $(CROSSPREFIX)windres -RCFLAGS = --include-dir=$(PROOT)/include -DDEBUGBUILD=0 -O COFF -STRIP = $(CROSSPREFIX)strip -g - -# Set environment var ARCH to your architecture to override autodetection. -ifndef ARCH -ifeq ($(findstring x86_64,$(shell $(CC) -dumpmachine)),x86_64) -ARCH = w64 -else -ARCH = w32 -endif -endif - -ifeq ($(ARCH),w64) -CFLAGS += -m64 -D_AMD64_ -LDFLAGS += -m64 -RCFLAGS += -F pe-x86-64 -else -CFLAGS += -m32 -LDFLAGS += -m32 -RCFLAGS += -F pe-i386 -endif - -# Platform-dependent helper tool macros -ifeq ($(findstring /sh,$(SHELL)),/sh) -DEL = rm -f $1 -RMDIR = rm -fr $1 -MKDIR = mkdir -p $1 -COPY = -cp -afv $1 $2 -#COPYR = -cp -afr $1/* $2 -COPYR = -rsync -aC $1/* $2 -TOUCH = touch $1 -CAT = cat -ECHONL = echo "" -DL = ' -else -ifeq "$(OS)" "Windows_NT" -DEL = -del 2>NUL /q /f $(subst /,\,$1) -RMDIR = -rd 2>NUL /q /s $(subst /,\,$1) -else -DEL = -del 2>NUL $(subst /,\,$1) -RMDIR = -deltree 2>NUL /y $(subst /,\,$1) -endif -MKDIR = -md 2>NUL $(subst /,\,$1) -COPY = -copy 2>NUL /y $(subst /,\,$1) $(subst /,\,$2) -COPYR = -xcopy 2>NUL /q /y /e $(subst /,\,$1) $(subst /,\,$2) -TOUCH = copy 2>&1>NUL /b $(subst /,\,$1) +,, -CAT = type -ECHONL = $(ComSpec) /c echo. -endif - -######################################################## -## Nothing more to do below this line! - -ifeq ($(findstring -dyn,$(CFG)),-dyn) -DYN = 1 -endif -ifeq ($(findstring -ares,$(CFG)),-ares) -ARES = 1 -endif -ifeq ($(findstring -sync,$(CFG)),-sync) -SYNC = 1 -endif -ifeq ($(findstring -rtmp,$(CFG)),-rtmp) -RTMP = 1 -SSL = 1 -ZLIB = 1 -endif -ifeq ($(findstring -ssh2,$(CFG)),-ssh2) -SSH2 = 1 -ifneq ($(findstring -winssl,$(CFG)),-winssl) -SSL = 1 -endif -ZLIB = 1 -endif -ifeq ($(findstring -ssl,$(CFG)),-ssl) -SSL = 1 -endif -ifeq ($(findstring -srp,$(CFG)),-srp) -SRP = 1 -endif -ifeq ($(findstring -zlib,$(CFG)),-zlib) -ZLIB = 1 -endif -ifeq ($(findstring -idn,$(CFG)),-idn) -IDN = 1 -endif -ifeq ($(findstring -winidn,$(CFG)),-winidn) -WINIDN = 1 -endif -ifeq ($(findstring -sspi,$(CFG)),-sspi) -SSPI = 1 -endif -ifeq ($(findstring -ldaps,$(CFG)),-ldaps) -LDAPS = 1 -endif -ifeq ($(findstring -ipv6,$(CFG)),-ipv6) -IPV6 = 1 -endif -ifeq ($(findstring -winssl,$(CFG)),-winssl) -WINSSL = 1 -SSPI = 1 -endif -ifeq ($(findstring -nghttp2,$(CFG)),-nghttp2) -NGHTTP2 = 1 -endif - -INCLUDES = -I. -I../include -CFLAGS += -DBUILDING_LIBCURL - -ifdef SYNC - CFLAGS += -DUSE_SYNC_DNS -else - ifdef ARES - INCLUDES += -I"$(LIBCARES_PATH)" - CFLAGS += -DUSE_ARES -DCARES_STATICLIB - DLL_LIBS += -L"$(LIBCARES_PATH)" -lcares - libcurl_dll_DEPENDENCIES = $(LIBCARES_PATH)/libcares.a - endif -endif -ifdef RTMP - INCLUDES += -I"$(LIBRTMP_PATH)" - CFLAGS += -DUSE_LIBRTMP - DLL_LIBS += -L"$(LIBRTMP_PATH)/librtmp" -lrtmp -lwinmm -endif -ifdef NGHTTP2 - INCLUDES += -I"$(NGHTTP2_PATH)/include" - CFLAGS += -DUSE_NGHTTP2 - DLL_LIBS += -L"$(NGHTTP2_PATH)/lib" -lnghttp2 -endif -ifdef SSH2 - INCLUDES += -I"$(LIBSSH2_PATH)/include" -I"$(LIBSSH2_PATH)/win32" - CFLAGS += -DUSE_LIBSSH2 -DHAVE_LIBSSH2_H - DLL_LIBS += -L"$(LIBSSH2_PATH)/win32" -lssh2 - ifdef WINSSL - ifndef DYN - DLL_LIBS += -lbcrypt -lcrypt32 - endif - endif -endif -ifdef SSL - ifndef OPENSSL_INCLUDE - ifeq "$(wildcard $(OPENSSL_PATH)/outinc)" "$(OPENSSL_PATH)/outinc" - OPENSSL_INCLUDE = $(OPENSSL_PATH)/outinc - endif - ifeq "$(wildcard $(OPENSSL_PATH)/include)" "$(OPENSSL_PATH)/include" - OPENSSL_INCLUDE = $(OPENSSL_PATH)/include - endif - endif - ifneq "$(wildcard $(OPENSSL_INCLUDE)/openssl/opensslv.h)" "$(OPENSSL_INCLUDE)/openssl/opensslv.h" - $(error Invalid path to OpenSSL package: $(OPENSSL_PATH)) - endif - ifndef OPENSSL_LIBPATH - ifeq "$(wildcard $(OPENSSL_PATH)/out)" "$(OPENSSL_PATH)/out" - OPENSSL_LIBPATH = $(OPENSSL_PATH)/out - OPENSSL_LIBS = -leay32 -lssl32 - endif - ifeq "$(wildcard $(OPENSSL_PATH)/lib)" "$(OPENSSL_PATH)/lib" - OPENSSL_LIBPATH = $(OPENSSL_PATH)/lib - OPENSSL_LIBS = -lcrypto -lssl - endif - endif - ifndef DYN - OPENSSL_LIBS += -lgdi32 -lcrypt32 - endif - INCLUDES += -I"$(OPENSSL_INCLUDE)" - CFLAGS += -DUSE_OPENSSL -DHAVE_OPENSSL_ENGINE_H -DHAVE_OPENSSL_PKCS12_H \ - -DHAVE_ENGINE_LOAD_BUILTIN_ENGINES -DOPENSSL_NO_KRB5 \ - -DCURL_WANTS_CA_BUNDLE_ENV - DLL_LIBS += -L"$(OPENSSL_LIBPATH)" $(OPENSSL_LIBS) - ifdef SRP - ifeq "$(wildcard $(OPENSSL_INCLUDE)/openssl/srp.h)" "$(OPENSSL_INCLUDE)/openssl/srp.h" - CFLAGS += -DHAVE_OPENSSL_SRP -DUSE_TLS_SRP - endif - endif -else -ifdef WINSSL - DLL_LIBS += -lcrypt32 -endif -endif -ifdef ZLIB - INCLUDES += -I"$(ZLIB_PATH)" - CFLAGS += -DHAVE_LIBZ -DHAVE_ZLIB_H - DLL_LIBS += -L"$(ZLIB_PATH)" -lz -endif -ifdef IDN - INCLUDES += -I"$(LIBIDN_PATH)/include" - CFLAGS += -DUSE_LIBIDN - DLL_LIBS += -L"$(LIBIDN_PATH)/lib" -lidn -else -ifdef WINIDN - CFLAGS += -DUSE_WIN32_IDN - CFLAGS += -DWANT_IDN_PROTOTYPES - DLL_LIBS += -L"$(WINIDN_PATH)" -lnormaliz -endif -endif -ifdef SSPI - CFLAGS += -DUSE_WINDOWS_SSPI - ifdef WINSSL - CFLAGS += -DUSE_SCHANNEL - endif -endif -ifdef SPNEGO - CFLAGS += -DHAVE_SPNEGO -endif -ifdef IPV6 - CFLAGS += -DENABLE_IPV6 -D_WIN32_WINNT=0x0501 -endif -ifdef LDAPS - CFLAGS += -DHAVE_LDAP_SSL -endif -ifdef USE_LDAP_NOVELL - INCLUDES += -I"$(LDAP_SDK)/inc" - CFLAGS += -DCURL_HAS_NOVELL_LDAPSDK - DLL_LIBS += -L"$(LDAP_SDK)/lib/mscvc" -lldapsdk -lldapssl -lldapx -endif -ifdef USE_LDAP_OPENLDAP - INCLUDES += -I"$(LDAP_SDK)/include" - CFLAGS += -DCURL_HAS_OPENLDAP_LDAPSDK - DLL_LIBS += -L"$(LDAP_SDK)/lib" -lldap -llber -endif -ifndef USE_LDAP_NOVELL -ifndef USE_LDAP_OPENLDAP - DLL_LIBS += -lwldap32 -endif -endif -DLL_LIBS += -lws2_32 - -# Makefile.inc provides the CSOURCES and HHEADERS defines -include Makefile.inc - -libcurl_dll_LIBRARY = libcurl.dll -libcurl_dll_a_LIBRARY = libcurldll.a -libcurl_a_LIBRARY = libcurl.a - -libcurl_a_OBJECTS := $(patsubst %.c,%.o,$(strip $(CSOURCES))) -libcurl_a_DEPENDENCIES := $(strip $(CSOURCES) $(HHEADERS)) - -RESOURCE = libcurl.res - - -all: $(libcurl_a_LIBRARY) $(libcurl_dll_LIBRARY) - -$(libcurl_a_LIBRARY): $(libcurl_a_OBJECTS) $(libcurl_a_DEPENDENCIES) - @$(call DEL, $@) - $(AR) cru $@ $(libcurl_a_OBJECTS) - $(RANLIB) $@ - $(STRIP) $@ - -# remove the last line above to keep debug info - -$(libcurl_dll_LIBRARY): $(libcurl_a_OBJECTS) $(RESOURCE) $(libcurl_dll_DEPENDENCIES) - @$(call DEL, $@) - $(CC) $(LDFLAGS) -shared -o $@ \ - -Wl,--output-def,$(@:.dll=.def),--out-implib,$(libcurl_dll_a_LIBRARY) \ - $(libcurl_a_OBJECTS) $(RESOURCE) $(DLL_LIBS) - -%.o: %.c $(PROOT)/include/curl/curlbuild.h - $(CC) $(INCLUDES) $(CFLAGS) -c $< -o $@ - -%.res: %.rc - $(RC) $(RCFLAGS) -i $< -o $@ - -clean: -ifeq "$(wildcard $(PROOT)/include/curl/curlbuild.h.dist)" "$(PROOT)/include/curl/curlbuild.h.dist" - @$(call DEL, $(PROOT)/include/curl/curlbuild.h) -endif - @$(call DEL, $(libcurl_a_OBJECTS) $(RESOURCE)) - -distclean vclean: clean - @$(call DEL, $(libcurl_a_LIBRARY) $(libcurl_dll_LIBRARY) $(libcurl_dll_LIBRARY:.dll=.def) $(libcurl_dll_a_LIBRARY)) - -$(PROOT)/include/curl/curlbuild.h: - @echo Creating $@ - @$(call COPY, $@.dist, $@) - -$(LIBCARES_PATH)/libcares.a: - $(MAKE) -C $(LIBCARES_PATH) -f Makefile.m32 diff --git a/contrib/curl/lib/Makefile.netware b/contrib/curl/lib/Makefile.netware deleted file mode 100644 index f689a36f80..0000000000 --- a/contrib/curl/lib/Makefile.netware +++ /dev/null @@ -1,799 +0,0 @@ -#*************************************************************************** -# _ _ ____ _ -# Project ___| | | | _ \| | -# / __| | | | |_) | | -# | (__| |_| | _ <| |___ -# \___|\___/|_| \_\_____| -# -# Copyright (C) 2004 - 2015, Guenter Knauf, . -# Copyright (C) 2001 - 2015, Daniel Stenberg, , et al. -# -# This software is licensed as described in the file COPYING, which -# you should have received as part of this distribution. The terms -# are also available at https://curl.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. -# -#*************************************************************************** - -################################################################# -# -## Makefile for building libcurl.nlm (NetWare version - gnu make) -## -## Use: make -f Makefile.netware -# -################################################################# - -# Edit the path below to point to the base of your Novell NDK. -ifndef NDKBASE -NDKBASE = c:/novell -endif - -# Edit the path below to point to the base of your Zlib sources. -ifndef ZLIB_PATH -ZLIB_PATH = ../../zlib-1.2.8 -endif - -# Edit the path below to point to the base of your OpenSSL package. -ifndef OPENSSL_PATH -OPENSSL_PATH = ../../openssl-1.0.2a -endif - -# Edit the path below to point to the base of your LibSSH2 package. -ifndef LIBSSH2_PATH -LIBSSH2_PATH = ../../libssh2-1.5.0 -endif - -# Edit the path below to point to the base of your axTLS package. -ifndef AXTLS_PATH -AXTLS_PATH = ../../axTLS-1.2.7 -endif - -# Edit the path below to point to the base of your libidn package. -ifndef LIBIDN_PATH -LIBIDN_PATH = ../../libidn-1.18 -endif - -# Edit the path below to point to the base of your librtmp package. -ifndef LIBRTMP_PATH -LIBRTMP_PATH = ../../librtmp-2.3 -endif - -# Edit the path below to point to the base of your nghttp2 package. -ifndef NGHTTP2_PATH -NGHTTP2_PATH = ../../nghttp2-0.6.7 -endif - -# Edit the path below to point to the base of your fbopenssl package. -ifndef FBOPENSSL_PATH -FBOPENSSL_PATH = ../../fbopenssl-0.4 -endif - -# Edit the path below to point to the base of your c-ares package. -ifndef LIBCARES_PATH -LIBCARES_PATH = ../ares -endif - -ifndef INSTDIR -INSTDIR = ..$(DS)curl-$(LIBCURL_VERSION_STR)-bin-nw -endif - -# Edit the vars below to change NLM target settings. -TARGET = libcurl -VERSION = $(LIBCURL_VERSION) -COPYR = Copyright (C) $(LIBCURL_COPYRIGHT_STR) -DESCR = curl libcurl $(LIBCURL_VERSION_STR) ($(LIBARCH)) - https://curl.haxx.se -MTSAFE = YES -STACK = 64000 -SCREEN = none -EXPORTF = $(TARGET).imp -EXPORTS = @$(EXPORTF) - -# Uncomment the next line to enable linking with POSIX semantics. -# POSIXFL = 1 - -# Edit the var below to point to your lib architecture. -ifndef LIBARCH -LIBARCH = LIBC -endif - -# must be equal to NDEBUG or DEBUG, CURLDEBUG -ifndef DB -DB = NDEBUG -endif -# Optimization: -O or debugging: -g -ifeq ($(DB),NDEBUG) - OPT = -O2 - OBJDIR = release -else - OPT = -g - OBJDIR = debug -endif - -# The following lines defines your compiler. -ifdef CWFolder - METROWERKS = $(CWFolder) -endif -ifdef METROWERKS - # MWCW_PATH = $(subst \,/,$(METROWERKS))/Novell Support - MWCW_PATH = $(subst \,/,$(METROWERKS))/Novell Support/Metrowerks Support - CC = mwccnlm -else - CC = gcc -endif -PERL = perl -# Here you can find a native Win32 binary of the original awk: -# http://www.gknw.net/development/prgtools/awk-20100523.zip -AWK = awk -CP = cp -afv -MKDIR = mkdir -# RM = rm -f -# If you want to mark the target as MTSAFE you will need a tool for -# generating the xdc data for the linker; here's a minimal tool: -# http://www.gknw.net/development/prgtools/mkxdc.zip -MPKXDC = mkxdc - -# LIBARCH_U = $(shell $(AWK) 'BEGIN {print toupper(ARGV[1])}' $(LIBARCH)) -LIBARCH_L = $(shell $(AWK) 'BEGIN {print tolower(ARGV[1])}' $(LIBARCH)) - -# Include the version info retrieved from curlver.h --include $(OBJDIR)/version.inc - -# Global flags for all compilers -CFLAGS += $(OPT) -D$(DB) -DNETWARE -DHAVE_CONFIG_H -nostdinc - -ifeq ($(CC),mwccnlm) -LD = mwldnlm -LDFLAGS = -nostdlib $(PRELUDE) $(OBJL) -o $@ -commandfile -AR = mwldnlm -ARFLAGS = -nostdlib -type library -o -LIBEXT = lib -#RANLIB = -CFLAGS += -msgstyle gcc -gccinc -inline off -opt nointrinsics -proc 586 -CFLAGS += -relax_pointers -#CFLAGS += -w on -ifeq ($(LIBARCH),LIBC) -ifeq ($(POSIXFL),1) - PRELUDE = $(NDK_LIBC)/imports/posixpre.o -else - PRELUDE = $(NDK_LIBC)/imports/libcpre.o -endif - CFLAGS += -align 4 -else - # PRELUDE = $(NDK_CLIB)/imports/clibpre.o - # to avoid the __init_* / __deinit_* whoes dont use prelude from NDK - PRELUDE = "$(MWCW_PATH)/libraries/runtime/prelude.obj" - # CFLAGS += -include "$(MWCW_PATH)/headers/nlm_clib_prefix.h" - CFLAGS += -align 1 -endif -else -LD = nlmconv -LDFLAGS = -T -AR = ar -ARFLAGS = -cq -LIBEXT = a -RANLIB = ranlib -CFLAGS += -m32 -CFLAGS += -fno-builtin -fno-strict-aliasing -ifeq ($(findstring gcc,$(CC)),gcc) -CFLAGS += -fpcc-struct-return -endif -CFLAGS += -Wall # -pedantic -ifeq ($(LIBARCH),LIBC) -ifeq ($(POSIXFL),1) - PRELUDE = $(NDK_LIBC)/imports/posixpre.gcc.o -else - PRELUDE = $(NDK_LIBC)/imports/libcpre.gcc.o -endif -else - PRELUDE = $(NDK_CLIB)/imports/clibpre.gcc.o - # to avoid the __init_* / __deinit_* whoes dont use prelude from NDK - # http://www.gknw.net/development/mk_nlm/gcc_pre.zip - # PRELUDE = $(NDK_ROOT)/pre/prelude.o - CFLAGS += -include $(NDKBASE)/nlmconv/genlm.h -endif -endif - -NDK_ROOT = $(NDKBASE)/ndk -ifndef NDK_CLIB -NDK_CLIB = $(NDK_ROOT)/nwsdk -endif -ifndef NDK_LIBC -NDK_LIBC = $(NDK_ROOT)/libc -endif -ifndef NDK_LDAP -NDK_LDAP = $(NDK_ROOT)/cldapsdk/netware -endif -CURL_INC = ../include -CURL_LIB = ../lib - -INCLUDES = -I$(CURL_INC) -I$(CURL_LIB) - -ifeq ($(findstring -static,$(CFG)),-static) -LINK_STATIC = 1 -endif -ifeq ($(findstring -ares,$(CFG)),-ares) -WITH_ARES = 1 -endif -ifeq ($(findstring -rtmp,$(CFG)),-rtmp) -WITH_RTMP = 1 -WITH_SSL = 1 -WITH_ZLIB = 1 -endif -ifeq ($(findstring -ssh2,$(CFG)),-ssh2) -WITH_SSH2 = 1 -WITH_SSL = 1 -WITH_ZLIB = 1 -endif -ifeq ($(findstring -axtls,$(CFG)),-axtls) -WITH_AXTLS = 1 -WITH_SSL = -else -ifeq ($(findstring -ssl,$(CFG)),-ssl) -WITH_SSL = 1 -ifeq ($(findstring -srp,$(CFG)),-srp) -ifeq "$(wildcard $(OPENSSL_PATH)/outinc_nw_$(LIBARCH_L)/openssl/srp.h)" "$(OPENSSL_PATH)/outinc_nw_$(LIBARCH_L)/openssl/srp.h" -WITH_SRP = 1 -endif -endif -endif -endif -ifeq ($(findstring -zlib,$(CFG)),-zlib) -WITH_ZLIB = 1 -endif -ifeq ($(findstring -idn,$(CFG)),-idn) -WITH_IDN = 1 -endif -ifeq ($(findstring -nghttp2,$(CFG)),-nghttp2) -WITH_NGHTTP2 = 1 -endif -ifeq ($(findstring -ipv6,$(CFG)),-ipv6) -ENABLE_IPV6 = 1 -endif - -ifdef WITH_ARES - INCLUDES += -I$(LIBCARES_PATH) - LDLIBS += $(LIBCARES_PATH)/libcares.$(LIBEXT) -endif -ifdef WITH_SSH2 - INCLUDES += -I$(LIBSSH2_PATH)/include -ifdef LINK_STATIC - LDLIBS += $(LIBSSH2_PATH)/nw/libssh2.$(LIBEXT) -else - MODULES += libssh2.nlm - IMPORTS += @$(LIBSSH2_PATH)/nw/libssh2.imp -endif -endif -ifdef WITH_RTMP - INCLUDES += -I$(LIBRTMP_PATH) - LDLIBS += $(LIBRTMP_PATH)/librtmp/librtmp.$(LIBEXT) -endif -ifdef WITH_SSL - INCLUDES += -I$(OPENSSL_PATH)/outinc_nw_$(LIBARCH_L) - LDLIBS += $(OPENSSL_PATH)/out_nw_$(LIBARCH_L)/ssl.$(LIBEXT) - LDLIBS += $(OPENSSL_PATH)/out_nw_$(LIBARCH_L)/crypto.$(LIBEXT) - IMPORTS += GetProcessSwitchCount RunningProcess - INSTDEP += ca-bundle.crt -else -ifdef WITH_AXTLS - INCLUDES += -I$(AXTLS_PATH)/inc -ifdef LINK_STATIC - LDLIBS += $(AXTLS_PATH)/lib/libaxtls.$(LIBEXT) -else - MODULES += libaxtls.nlm - IMPORTS += $(AXTLS_PATH)/lib/libaxtls.imp -endif - INSTDEP += ca-bundle.crt -endif -endif -ifdef WITH_ZLIB - INCLUDES += -I$(ZLIB_PATH) -ifdef LINK_STATIC - LDLIBS += $(ZLIB_PATH)/nw/$(LIBARCH)/libz.$(LIBEXT) -else - MODULES += libz.nlm - IMPORTS += @$(ZLIB_PATH)/nw/$(LIBARCH)/libz.imp -endif -endif -ifdef WITH_IDN - INCLUDES += -I$(LIBIDN_PATH)/include - LDLIBS += $(LIBIDN_PATH)/lib/libidn.$(LIBEXT) -endif -ifdef WITH_NGHTTP2 - INCLUDES += -I$(NGHTTP2_PATH)/include - LDLIBS += $(NGHTTP2_PATH)/lib/libnghttp2.$(LIBEXT) -endif - -ifeq ($(LIBARCH),LIBC) - INCLUDES += -I$(NDK_LIBC)/include - # INCLUDES += -I$(NDK_LIBC)/include/nks - # INCLUDES += -I$(NDK_LIBC)/include/winsock - CFLAGS += -D_POSIX_SOURCE -else - INCLUDES += -I$(NDK_CLIB)/include/nlm - # INCLUDES += -I$(NDK_CLIB)/include/nlm/obsolete - # INCLUDES += -I$(NDK_CLIB)/include -endif -ifndef DISABLE_LDAP - INCLUDES += -I$(NDK_LDAP)/$(LIBARCH_L)/inc -endif -CFLAGS += $(INCLUDES) - -ifeq ($(MTSAFE),YES) - XDCOPT = -n -endif -ifeq ($(MTSAFE),NO) - XDCOPT = -u -endif -ifdef XDCOPT - XDCDATA = $(OBJDIR)/$(TARGET).xdc -endif - -ifeq ($(findstring /sh,$(SHELL)),/sh) -DL = ' -DS = / -PCT = % -#-include $(NDKBASE)/nlmconv/ncpfs.inc -else -DS = \\ -PCT = %% -endif - -# Makefile.inc provides the CSOURCES and HHEADERS defines -include Makefile.inc - -OBJS := $(patsubst %.c,$(OBJDIR)/%.o,$(strip $(notdir $(CSOURCES)))) $(OBJDIR)/nwos.o - -OBJL = $(OBJS) $(OBJDIR)/nwlib.o $(LDLIBS) - -vpath %.c . vauth vtls - -all: lib nlm - -nlm: prebuild $(TARGET).nlm - -lib: prebuild $(TARGET).$(LIBEXT) - -prebuild: $(OBJDIR) $(CURL_INC)/curl/curlbuild.h $(OBJDIR)/version.inc curl_config.h - -$(OBJDIR)/%.o: %.c -# @echo Compiling $< - $(CC) $(CFLAGS) -c $< -o $@ - -$(OBJDIR)/version.inc: $(CURL_INC)/curl/curlver.h $(OBJDIR) - @echo Creating $@ - @$(AWK) -f ../packages/NetWare/get_ver.awk $< > $@ - -install: $(INSTDIR) all $(INSTDEP) - @$(CP) $(TARGET).nlm $(INSTDIR) - @$(CP) $(TARGET).$(LIBEXT) $(INSTDIR) - @$(CP) ../CHANGES $(INSTDIR) - @$(CP) ../COPYING $(INSTDIR) - @$(CP) ../README $(INSTDIR) - @$(CP) ../RELEASE-NOTES $(INSTDIR) -ifdef WITH_SSL - @-$(CP) ca-bundle.crt $(INSTDIR)/ca-bundle.crt -endif - -clean: - -$(RM) curl_config.h - -$(RM) -r $(OBJDIR) - -distclean vclean: clean - -$(RM) $(TARGET).$(LIBEXT) $(TARGET).nlm $(TARGET).imp - -$(RM) certdata.txt ca-bundle.crt - -$(OBJDIR) $(INSTDIR): - @$(MKDIR) $@ - -$(TARGET).$(LIBEXT): $(OBJS) - @echo Creating $@ - @-$(RM) $@ - @$(AR) $(ARFLAGS) $@ $^ -ifdef RANLIB - @$(RANLIB) $@ -endif - -$(TARGET).nlm: $(OBJDIR)/$(TARGET).def $(OBJL) $(EXPORTF) $(XDCDATA) - @echo Linking $@ - @-$(RM) $@ - @$(LD) $(LDFLAGS) $< - -$(OBJDIR)/%.xdc: Makefile.netware - @echo Creating $@ - @$(MPKXDC) $(XDCOPT) $@ - -$(OBJDIR)/%.def: Makefile.netware - @echo $(DL)# DEF file for linking with $(LD)$(DL) > $@ - @echo $(DL)# Do not edit this file - it is created by make!$(DL) >> $@ - @echo $(DL)# All your changes will be lost!!$(DL) >> $@ - @echo $(DL)#$(DL) >> $@ - @echo $(DL)copyright "$(COPYR)"$(DL) >> $@ - @echo $(DL)description "$(DESCR)"$(DL) >> $@ - @echo $(DL)version $(VERSION)$(DL) >> $@ -ifdef NLMTYPE - @echo $(DL)type $(NLMTYPE)$(DL) >> $@ -endif -ifdef STACK - @echo $(DL)stack $(STACK)$(DL) >> $@ -endif -ifdef SCREEN - @echo $(DL)screenname "$(SCREEN)"$(DL) >> $@ -else - @echo $(DL)screenname "DEFAULT"$(DL) >> $@ -endif -ifneq ($(DB),NDEBUG) - @echo $(DL)debug$(DL) >> $@ -endif - @echo $(DL)threadname "$(TARGET)"$(DL) >> $@ -ifdef XDCDATA - @echo $(DL)xdcdata $(XDCDATA)$(DL) >> $@ -endif - @echo $(DL)flag_on 64$(DL) >> $@ -ifeq ($(LIBARCH),CLIB) - @echo $(DL)start _Prelude$(DL) >> $@ - @echo $(DL)exit _Stop$(DL) >> $@ - @echo $(DL)import @$(NDK_CLIB)/imports/clib.imp$(DL) >> $@ - @echo $(DL)import @$(NDK_CLIB)/imports/threads.imp$(DL) >> $@ - @echo $(DL)import @$(NDK_CLIB)/imports/nlmlib.imp$(DL) >> $@ - @echo $(DL)import @$(NDK_CLIB)/imports/socklib.imp$(DL) >> $@ - @echo $(DL)module clib$(DL) >> $@ -ifndef DISABLE_LDAP - @echo $(DL)import @$(NDK_LDAP)/clib/imports/ldapsdk.imp$(DL) >> $@ - @echo $(DL)import @$(NDK_LDAP)/clib/imports/ldapssl.imp$(DL) >> $@ -# @echo $(DL)import @$(NDK_LDAP)/clib/imports/ldapx.imp$(DL) >> $@ - @echo $(DL)module ldapsdk ldapssl$(DL) >> $@ -endif -else -ifeq ($(POSIXFL),1) - @echo $(DL)flag_on 4194304$(DL) >> $@ -endif - @echo $(DL)pseudopreemption$(DL) >> $@ -ifeq ($(findstring posixpre,$(PRELUDE)),posixpre) - @echo $(DL)start POSIX_Start$(DL) >> $@ - @echo $(DL)exit POSIX_Stop$(DL) >> $@ - @echo $(DL)check POSIX_CheckUnload$(DL) >> $@ -else - @echo $(DL)start _LibCPrelude$(DL) >> $@ - @echo $(DL)exit _LibCPostlude$(DL) >> $@ - @echo $(DL)check _LibCCheckUnload$(DL) >> $@ -endif - @echo $(DL)import @$(NDK_LIBC)/imports/libc.imp$(DL) >> $@ - @echo $(DL)import @$(NDK_LIBC)/imports/netware.imp$(DL) >> $@ - @echo $(DL)module libc$(DL) >> $@ -ifndef DISABLE_LDAP - @echo $(DL)import @$(NDK_LDAP)/libc/imports/lldapsdk.imp$(DL) >> $@ - @echo $(DL)import @$(NDK_LDAP)/libc/imports/lldapssl.imp$(DL) >> $@ -# @echo $(DL)import @$(NDK_LDAP)/libc/imports/lldapx.imp$(DL) >> $@ - @echo $(DL)module lldapsdk lldapssl$(DL) >> $@ -endif -endif -ifdef MODULES - @echo $(DL)module $(MODULES)$(DL) >> $@ -endif -ifdef EXPORTS - @echo $(DL)export $(EXPORTS)$(DL) >> $@ -endif -ifdef IMPORTS - @echo $(DL)import $(IMPORTS)$(DL) >> $@ -endif -ifeq ($(findstring nlmconv,$(LD)),nlmconv) - @echo $(DL)input $(PRELUDE)$(DL) >> $@ - @echo $(DL)input $(OBJL)$(DL) >> $@ -#ifdef LDLIBS -# @echo $(DL)input $(LDLIBS)$(DL) >> $@ -#endif - @echo $(DL)output $(TARGET).nlm$(DL) >> $@ -endif - -curl_config.h: Makefile.netware - @echo Creating $@ - @echo $(DL)/* $@ for NetWare target.$(DL) > $@ - @echo $(DL)** Do not edit this file - it is created by make!$(DL) >> $@ - @echo $(DL)** All your changes will be lost!!$(DL) >> $@ - @echo $(DL)*/$(DL) >> $@ - @echo $(DL)#ifndef NETWARE$(DL) >> $@ - @echo $(DL)#error This $(notdir $@) is created for NetWare platform!$(DL) >> $@ - @echo $(DL)#endif$(DL) >> $@ - @echo $(DL)#define VERSION "$(LIBCURL_VERSION_STR)"$(DL) >> $@ - @echo $(DL)#define PACKAGE_BUGREPORT "a suitable curl mailing list => https://curl.haxx.se/mail/"$(DL) >> $@ -ifeq ($(LIBARCH),CLIB) - @echo $(DL)#define OS "i586-pc-clib-NetWare"$(DL) >> $@ - @echo $(DL)#define NETDB_USE_INTERNET 1$(DL) >> $@ - @echo $(DL)#define HAVE_STRICMP 1$(DL) >> $@ - @echo $(DL)#define HAVE_STRNICMP 1$(DL) >> $@ - @echo $(DL)#define RECV_TYPE_ARG1 int$(DL) >> $@ - @echo $(DL)#define RECV_TYPE_ARG2 char *$(DL) >> $@ - @echo $(DL)#define RECV_TYPE_ARG3 int$(DL) >> $@ - @echo $(DL)#define RECV_TYPE_ARG4 int$(DL) >> $@ - @echo $(DL)#define RECV_TYPE_RETV int$(DL) >> $@ - @echo $(DL)#define RECVFROM_TYPE_ARG1 int$(DL) >> $@ - @echo $(DL)#define RECVFROM_TYPE_ARG2 char$(DL) >> $@ - @echo $(DL)#define RECVFROM_TYPE_ARG3 int$(DL) >> $@ - @echo $(DL)#define RECVFROM_TYPE_ARG4 int$(DL) >> $@ - @echo $(DL)#define RECVFROM_TYPE_ARG5 struct sockaddr$(DL) >> $@ - @echo $(DL)#define RECVFROM_TYPE_ARG6 int$(DL) >> $@ - @echo $(DL)#define RECVFROM_TYPE_RETV int$(DL) >> $@ - @echo $(DL)#define SEND_QUAL_ARG2$(DL) >> $@ - @echo $(DL)#define SEND_TYPE_ARG1 int$(DL) >> $@ - @echo $(DL)#define SEND_TYPE_ARG2 char *$(DL) >> $@ - @echo $(DL)#define SEND_TYPE_ARG3 int$(DL) >> $@ - @echo $(DL)#define SEND_TYPE_ARG4 int$(DL) >> $@ - @echo $(DL)#define SEND_TYPE_RETV int$(DL) >> $@ - @echo $(DL)#define SIZEOF_SIZE_T 4$(DL) >> $@ - @echo $(DL)#define pressanykey PressAnyKeyToContinue$(DL) >> $@ -else - @echo $(DL)#define OS "i586-pc-libc-NetWare"$(DL) >> $@ - @echo $(DL)#define HAVE_FTRUNCATE 1$(DL) >> $@ - @echo $(DL)#define HAVE_GETTIMEOFDAY 1$(DL) >> $@ - @echo $(DL)#define HAVE_INTTYPES_H 1$(DL) >> $@ - @echo $(DL)#define HAVE_LONGLONG 1$(DL) >> $@ - @echo $(DL)#define HAVE_STDINT_H 1$(DL) >> $@ - @echo $(DL)#define HAVE_STRCASECMP 1$(DL) >> $@ - @echo $(DL)#define HAVE_STRLCAT 1$(DL) >> $@ - @echo $(DL)#define HAVE_STRLCPY 1$(DL) >> $@ - @echo $(DL)#define HAVE_STRTOLL 1$(DL) >> $@ - @echo $(DL)#define HAVE_SYS_PARAM_H 1$(DL) >> $@ - @echo $(DL)#define HAVE_SYS_SELECT_H 1$(DL) >> $@ - @echo $(DL)#define HAVE_TERMIOS_H 1$(DL) >> $@ - @echo $(DL)#define RECV_TYPE_ARG1 int$(DL) >> $@ - @echo $(DL)#define RECV_TYPE_ARG2 void *$(DL) >> $@ - @echo $(DL)#define RECV_TYPE_ARG3 size_t$(DL) >> $@ - @echo $(DL)#define RECV_TYPE_ARG4 int$(DL) >> $@ - @echo $(DL)#define RECV_TYPE_RETV ssize_t$(DL) >> $@ - @echo $(DL)#define RECVFROM_TYPE_ARG1 int$(DL) >> $@ - @echo $(DL)#define RECVFROM_TYPE_ARG2 void$(DL) >> $@ - @echo $(DL)#define RECVFROM_TYPE_ARG3 size_t$(DL) >> $@ - @echo $(DL)#define RECVFROM_TYPE_ARG4 int$(DL) >> $@ - @echo $(DL)#define RECVFROM_TYPE_ARG5 struct sockaddr$(DL) >> $@ - @echo $(DL)#define RECVFROM_TYPE_ARG6 size_t$(DL) >> $@ - @echo $(DL)#define RECVFROM_TYPE_RETV ssize_t$(DL) >> $@ - @echo $(DL)#define RECVFROM_TYPE_ARG2_IS_VOID 1$(DL) >> $@ - @echo $(DL)#define SEND_QUAL_ARG2$(DL) >> $@ - @echo $(DL)#define SEND_TYPE_ARG1 int$(DL) >> $@ - @echo $(DL)#define SEND_TYPE_ARG2 void *$(DL) >> $@ - @echo $(DL)#define SEND_TYPE_ARG3 size_t$(DL) >> $@ - @echo $(DL)#define SEND_TYPE_ARG4 int$(DL) >> $@ - @echo $(DL)#define SEND_TYPE_RETV ssize_t$(DL) >> $@ - @echo $(DL)#define SIZEOF_OFF_T 8$(DL) >> $@ - @echo $(DL)#define SIZEOF_SIZE_T 8$(DL) >> $@ - @echo $(DL)#define _LARGEFILE 1$(DL) >> $@ -ifdef ENABLE_IPV6 - @echo $(DL)#define ENABLE_IPV6 1$(DL) >> $@ - @echo $(DL)#define HAVE_AF_INET6 1$(DL) >> $@ - @echo $(DL)#define HAVE_PF_INET6 1$(DL) >> $@ - @echo $(DL)#define HAVE_FREEADDRINFO 1$(DL) >> $@ - @echo $(DL)#define HAVE_GETADDRINFO 1$(DL) >> $@ - @echo $(DL)#define HAVE_SOCKADDR_IN6_SIN6_SCOPE_ID 1$(DL) >> $@ - @echo $(DL)#define HAVE_STRUCT_ADDRINFO 1$(DL) >> $@ - @echo $(DL)#define HAVE_STRUCT_IN6_ADDR 1$(DL) >> $@ - @echo $(DL)#define HAVE_STRUCT_SOCKADDR_IN6 1$(DL) >> $@ - @echo $(DL)#define SIZEOF_STRUCT_IN6_ADDR 16$(DL) >> $@ -endif -endif - @echo $(DL)#define USE_MANUAL 1$(DL) >> $@ - @echo $(DL)#define HAVE_ARPA_INET_H 1$(DL) >> $@ - @echo $(DL)#define HAVE_ASSERT_H 1$(DL) >> $@ - @echo $(DL)#define HAVE_ERRNO_H 1$(DL) >> $@ - @echo $(DL)#define HAVE_ERR_H 1$(DL) >> $@ - @echo $(DL)#define HAVE_FCNTL_H 1$(DL) >> $@ - @echo $(DL)#define HAVE_GETHOSTBYADDR 1$(DL) >> $@ - @echo $(DL)#define HAVE_GETHOSTBYNAME 1$(DL) >> $@ - @echo $(DL)#define HAVE_GETPROTOBYNAME 1$(DL) >> $@ - @echo $(DL)#define HAVE_GMTIME_R 1$(DL) >> $@ - @echo $(DL)#define HAVE_INET_ADDR 1$(DL) >> $@ - @echo $(DL)#define HAVE_IOCTL 1$(DL) >> $@ - @echo $(DL)#define HAVE_IOCTL_FIONBIO 1$(DL) >> $@ - @echo $(DL)#define HAVE_LIMITS_H 1$(DL) >> $@ - @echo $(DL)#define HAVE_LL 1$(DL) >> $@ - @echo $(DL)#define HAVE_LOCALE_H 1$(DL) >> $@ - @echo $(DL)#define HAVE_LOCALTIME_R 1$(DL) >> $@ - @echo $(DL)#define HAVE_MALLOC_H 1$(DL) >> $@ - @echo $(DL)#define HAVE_NETINET_IN_H 1$(DL) >> $@ - @echo $(DL)#define HAVE_RECV 1$(DL) >> $@ - @echo $(DL)#define HAVE_RECVFROM 1$(DL) >> $@ - @echo $(DL)#define HAVE_SELECT 1$(DL) >> $@ - @echo $(DL)#define HAVE_SEND 1$(DL) >> $@ - @echo $(DL)#define HAVE_SETJMP_H 1$(DL) >> $@ - @echo $(DL)#define HAVE_SETLOCALE 1$(DL) >> $@ - @echo $(DL)#define HAVE_SIGNAL 1$(DL) >> $@ - @echo $(DL)#define HAVE_SIGNAL_H 1$(DL) >> $@ - @echo $(DL)#define HAVE_SIG_ATOMIC_T 1$(DL) >> $@ - @echo $(DL)#define HAVE_SOCKET 1$(DL) >> $@ - @echo $(DL)#define HAVE_STDLIB_H 1$(DL) >> $@ - @echo $(DL)#define HAVE_STRDUP 1$(DL) >> $@ - @echo $(DL)#define HAVE_STRFTIME 1$(DL) >> $@ - @echo $(DL)#define HAVE_STRING_H 1$(DL) >> $@ - @echo $(DL)#define HAVE_STRSTR 1$(DL) >> $@ - @echo $(DL)#define HAVE_STRUCT_TIMEVAL 1$(DL) >> $@ - @echo $(DL)#define HAVE_SYS_IOCTL_H 1$(DL) >> $@ - @echo $(DL)#define HAVE_SYS_STAT_H 1$(DL) >> $@ - @echo $(DL)#define HAVE_SYS_TIME_H 1$(DL) >> $@ - @echo $(DL)#define HAVE_TIME_H 1$(DL) >> $@ - @echo $(DL)#define HAVE_UNAME 1$(DL) >> $@ - @echo $(DL)#define HAVE_UNISTD_H 1$(DL) >> $@ - @echo $(DL)#define HAVE_UTIME 1$(DL) >> $@ - @echo $(DL)#define HAVE_UTIME_H 1$(DL) >> $@ - @echo $(DL)#define HAVE_WRITEV 1$(DL) >> $@ - @echo $(DL)#define RETSIGTYPE void$(DL) >> $@ - @echo $(DL)#define SIZEOF_INT 4$(DL) >> $@ - @echo $(DL)#define SIZEOF_SHORT 2$(DL) >> $@ - @echo $(DL)#define SIZEOF_STRUCT_IN_ADDR 4$(DL) >> $@ - @echo $(DL)#define STDC_HEADERS 1$(DL) >> $@ - @echo $(DL)#define TIME_WITH_SYS_TIME 1$(DL) >> $@ -ifdef DISABLE_LDAP - @echo $(DL)#define CURL_DISABLE_LDAP 1$(DL) >> $@ -else - @echo $(DL)#define CURL_HAS_NOVELL_LDAPSDK 1$(DL) >> $@ -ifndef DISABLE_LDAPS - @echo $(DL)#define HAVE_LDAP_SSL 1$(DL) >> $@ -endif - @echo $(DL)#define HAVE_LDAP_SSL_H 1$(DL) >> $@ - @echo $(DL)#define HAVE_LDAP_URL_PARSE 1$(DL) >> $@ -endif -ifdef NW_WINSOCK - @echo $(DL)#define HAVE_CLOSESOCKET 1$(DL) >> $@ -else - @echo $(DL)#define USE_BSD_SOCKETS 1$(DL) >> $@ - @echo $(DL)#define HAVE_SYS_TYPES_H 1$(DL) >> $@ - @echo $(DL)#define HAVE_SYS_SOCKET_H 1$(DL) >> $@ - @echo $(DL)#define HAVE_SYS_SOCKIO_H 1$(DL) >> $@ - @echo $(DL)#define HAVE_NETDB_H 1$(DL) >> $@ -endif -ifdef WITH_ARES - @echo $(DL)#define USE_ARES 1$(DL) >> $@ -endif -ifdef WITH_ZLIB - @echo $(DL)#define HAVE_ZLIB_H 1$(DL) >> $@ - @echo $(DL)#define HAVE_LIBZ 1$(DL) >> $@ -endif -ifdef WITH_SSL - @echo $(DL)#define USE_SSLEAY 1$(DL) >> $@ - @echo $(DL)#define USE_OPENSSL 1$(DL) >> $@ - @echo $(DL)#define HAVE_OPENSSL_X509_H 1$(DL) >> $@ - @echo $(DL)#define HAVE_OPENSSL_SSL_H 1$(DL) >> $@ - @echo $(DL)#define HAVE_OPENSSL_RSA_H 1$(DL) >> $@ - @echo $(DL)#define HAVE_OPENSSL_PEM_H 1$(DL) >> $@ - @echo $(DL)#define HAVE_OPENSSL_ERR_H 1$(DL) >> $@ - @echo $(DL)#define HAVE_OPENSSL_CRYPTO_H 1$(DL) >> $@ - @echo $(DL)#define HAVE_OPENSSL_ENGINE_H 1$(DL) >> $@ - @echo $(DL)#define HAVE_LIBSSL 1$(DL) >> $@ - @echo $(DL)#define HAVE_LIBCRYPTO 1$(DL) >> $@ - @echo $(DL)#define OPENSSL_NO_KRB5 1$(DL) >> $@ -ifdef WITH_SRP - @echo $(DL)#define HAVE_SSLEAY_SRP 1$(DL) >> $@ - @echo $(DL)#define USE_TLS_SRP 1$(DL) >> $@ -endif -ifdef WITH_SPNEGO - @echo $(DL)#define HAVE_SPNEGO 1$(DL) >> $@ -endif -else -ifdef WITH_AXTLS - @echo $(DL)#define USE_AXTLS 1$(DL) >> $@ -endif -endif -ifdef WITH_SSH2 - @echo $(DL)#define USE_LIBSSH2 1$(DL) >> $@ - @echo $(DL)#define HAVE_LIBSSH2_H 1$(DL) >> $@ -endif -ifdef WITH_IDN - @echo $(DL)#define HAVE_LIBIDN 1$(DL) >> $@ - @echo $(DL)#define HAVE_TLD_H 1$(DL) >> $@ -endif -ifdef WITH_RTMP - @echo $(DL)#define USE_LIBRTMP 1$(DL) >> $@ -endif -ifdef WITH_NGHTTP2 - @echo $(DL)#define USE_NGHTTP2 1$(DL) >> $@ -endif - @echo $(DL)#ifdef __GNUC__$(DL) >> $@ - @echo $(DL)#define HAVE_VARIADIC_MACROS_GCC 1$(DL) >> $@ - @echo $(DL)#else$(DL) >> $@ - @echo $(DL)#define HAVE_VARIADIC_MACROS_C99 1$(DL) >> $@ - @echo $(DL)#endif$(DL) >> $@ -ifdef CABUNDLE - @echo $(DL)#define CURL_CA_BUNDLE "$(CABUNDLE)"$(DL) >> $@ -else - @echo $(DL)#define CURL_CA_BUNDLE getenv("CURL_CA_BUNDLE")$(DL) >> $@ -endif - -$(EXPORTF): $(CURL_INC)/curl/curl.h $(CURL_INC)/curl/easy.h $(CURL_INC)/curl/multi.h $(CURL_INC)/curl/mprintf.h - @echo Creating $@ - @$(AWK) -f ../packages/NetWare/get_exp.awk $^ > $@ - -FORCE: ; - -info: $(OBJDIR)/version.inc - @echo Configured to build $(TARGET) with these options: - @echo libarchitecture: $(LIBARCH) - @echo curl version: $(LIBCURL_VERSION_STR) - @echo compiler/linker: $(CC) / $(LD) -ifdef CABUNDLE - @echo ca-bundle path: $(CABUNDLE) -endif -ifdef WITH_SSL - @echo SSL support: enabled (OpenSSL) -else - @echo SSL support: no -endif -ifdef WITH_SRP - @echo SRP support: enabled -else - @echo SRP support: no -endif -ifdef WITH_SSH2 - @echo SSH2 support: enabled (libssh2) -else - @echo SSH2 support: no -endif -ifdef WITH_ZLIB - @echo zlib support: enabled -else - @echo zlib support: no -endif -ifdef WITH_NGHTTP2 - @echo http2 support: enabled -else - @echo http2 support: no -endif -ifdef WITH_ARES - @echo c-ares support: enabled -else - @echo c-ares support: no -endif -ifdef ENABLE_IPV6 - @echo IPv6 support: enabled -else - @echo IPv6 support: no -endif - -$(CURL_INC)/curl/curlbuild.h: Makefile.netware FORCE - @echo Creating $@ - @echo $(DL)/* $@ intended for NetWare target.$(DL) > $@ - @echo $(DL)** Do not edit this file - it is created by make!$(DL) >> $@ - @echo $(DL)** All your changes will be lost!!$(DL) >> $@ - @echo $(DL)*/$(DL) >> $@ - @echo $(DL)#ifndef NETWARE$(DL) >> $@ - @echo $(DL)#error This $(notdir $@) is created for NetWare platform!$(DL) >> $@ - @echo $(DL)#endif$(DL) >> $@ - @echo $(DL)#ifndef __CURL_CURLBUILD_H$(DL) >> $@ - @echo $(DL)#define __CURL_CURLBUILD_H$(DL) >> $@ -ifeq ($(LIBARCH),LIBC) - @echo $(DL)#define CURL_SIZEOF_LONG 4$(DL) >> $@ - @echo $(DL)#define CURL_TYPEOF_CURL_SOCKLEN_T unsigned int$(DL) >> $@ - @echo $(DL)#define CURL_SIZEOF_CURL_SOCKLEN_T 4$(DL) >> $@ - @echo $(DL)#define CURL_TYPEOF_CURL_OFF_T long long$(DL) >> $@ - @echo $(DL)#define CURL_FORMAT_CURL_OFF_T "lld"$(DL) >> $@ - @echo $(DL)#define CURL_FORMAT_CURL_OFF_TU "llu"$(DL) >> $@ - @echo $(DL)#define CURL_FORMAT_OFF_T "$(PCT)lld"$(DL) >> $@ - @echo $(DL)#define CURL_SIZEOF_CURL_OFF_T 8$(DL) >> $@ - @echo $(DL)#define CURL_SUFFIX_CURL_OFF_T LL$(DL) >> $@ - @echo $(DL)#define CURL_SUFFIX_CURL_OFF_TU ULL$(DL) >> $@ -else - @echo $(DL)#define CURL_SIZEOF_LONG 4$(DL) >> $@ - @echo $(DL)#define CURL_TYPEOF_CURL_SOCKLEN_T int$(DL) >> $@ - @echo $(DL)#define CURL_SIZEOF_CURL_SOCKLEN_T 4$(DL) >> $@ - @echo $(DL)#define CURL_TYPEOF_CURL_OFF_T long$(DL) >> $@ - @echo $(DL)#define CURL_FORMAT_CURL_OFF_T "ld"$(DL) >> $@ - @echo $(DL)#define CURL_FORMAT_CURL_OFF_TU "lu"$(DL) >> $@ - @echo $(DL)#define CURL_FORMAT_OFF_T "$(PCT)ld"$(DL) >> $@ - @echo $(DL)#define CURL_SIZEOF_CURL_OFF_T 4$(DL) >> $@ - @echo $(DL)#define CURL_SUFFIX_CURL_OFF_T L$(DL) >> $@ - @echo $(DL)#define CURL_SUFFIX_CURL_OFF_TU UL$(DL) >> $@ -endif - @echo $(DL)typedef CURL_TYPEOF_CURL_SOCKLEN_T curl_socklen_t;$(DL) >> $@ - @echo $(DL)typedef CURL_TYPEOF_CURL_OFF_T curl_off_t;$(DL) >> $@ - @echo $(DL)#endif /* __CURL_CURLBUILD_H */$(DL) >> $@ - -$(LIBCARES_PATH)/libcares.$(LIBEXT): - $(MAKE) -C $(LIBCARES_PATH) -f Makefile.netware lib - -ca-bundle.crt: mk-ca-bundle.pl - @echo Creating $@ - @-$(PERL) $< -b -n $@ - diff --git a/contrib/curl/lib/Makefile.vxworks b/contrib/curl/lib/Makefile.vxworks deleted file mode 100644 index 7ff197f033..0000000000 --- a/contrib/curl/lib/Makefile.vxworks +++ /dev/null @@ -1,177 +0,0 @@ -#***************************************************************************** -# -# -#Filename : Makefile.vxworks -#Description: makefile to be used in order to compile libcurl for VxWoorks 6.3. -# -#How to use: -# 1. Adjust environment variables at the file beginning -# 2. Open the Command Prompt window and change directory ('cd') -# into the 'lib' folder -# 3. Add /bin folder to the PATH environment variable -# For example type 'set PATH=C:/embedded/cygwin/bin;%PATH%' -# 4. Build the library by typing 'make -f ./Makefile.vxworks' -# As a result the libcurl.a should be created in the 'lib' folder. -# To clean package use 'make -f ./Makefile.vxworks clean' -#Requirements: -# 1. WinXP machine -# 2. Full CYGWIN installation (open source) with GNU make version -# v3.78 or higher -# 3. WindRiver Workbench with vxWorks 6.3 (commercial) -#***************************************************************************** - -# ---------------------------------------------------------------------- -# Environment -# ---------------------------------------------------------------------- - -export WIND_HOME := C:/embedded/Workbench2.5.0.1 -export WIND_BASE := $(WIND_HOME)/vxworks-6.3 -export WIND_HOST_TYPE := x86-win32 - -# BUILD_TYE:= | (build with debugging info or optimized) -BUILD_TYPE := debug -USER_CFLAGS:= - -# directories where to seek for includes and libraries -OPENSSL_INC := D:/libraries/openssl/openssl-0.9.8zc-vxWorks6.3/include -OPENSSL_LIB := D:/libraries/openssl/openssl-0.9.8zc-vxWorks6.3 -ZLIB_INC := D:/libraries/zlib/zlib-1.2.8-VxWorks6.3/zlib-1.2.8 -ZLIB_LIB := D:/libraries/zlib/zlib-1.2.8-VxWorks6.3/binaries/vxworks_3.1_gnu/Debug/lib -ARES_INC := -ARES_LIB := - - -# ---------------------------------------------------------------------- -# Compiler -# ---------------------------------------------------------------------- - -CC := ccppc -AR := arppc -LINK := ccppc -CFLAGS := -D__GNUC__ -D__ppc__ -msoft-float -fno-builtin -mcpu=604 -mlongcall -DCPU=PPC604 -D_GNU_TOOL -Wall -W -Winline $(USER_CFLAGS) -LDFLAGS := -nostdlib -Wl,-i -Wl,-X -INCLUDE_FLAG := -I -C_DEBUGFLAG := -g -C_OPTFLAG := -O2 -COMPILE_ONLY_FLAG := -c -OBJ_EXTENSION := .o -CC_OBJ_OUTPUT = -o $@ -ARFLAGS := -rc -LIBS_FLAG := -l -LIBS_DIRFLAG:= -L -LD_DEBUGFLAG := $(C_DEBUGFLAG) -EXECUTE_EXTENSION := .out -TOOL_CHAIN_BIN := $(WIND_HOME)/gnu/3.4.4-vxworks-6.3/$(WIND_HOST_TYPE)/bin/ - -# ---------------------------------------------------------------------- - -# Add -DINET6 if the OS kernel image was built with IPv6 support -# CFLAGS += -DINET6 - -# Set up compiler and linker flags for debug or optimization -ifeq ($(BUILD_TYPE), debug) -CFLAGS += $(C_DEBUGFLAG) -LDFLAGS += $(LD_DEBUGFLAG) -else -CFLAGS += $(C_OPTFLAG) -endif - -# ---------------------------------------------------------------------- - -# Main Makefile and possible sub-make files -MAKEFILES := Makefile.vxworks - -# List of external include directories -#----- -# IMPORTANT: include OPENSSL directories before system -# in order to prevent WindRiver OpenSSL to be used. -#----- -INCLUDE_DIRS := ../include $(OPENSSL_INC) $(ZLIB_INC) $(ARES_INC) $(WIND_BASE)/target/h $(WIND_BASE)/target/h/wrn/coreip - -# List of external libraries and their directories -LIBS_LIST := . -LIB_DIRS := . -ifneq ($(OPENSSL_LIB), ) -LIBS_LIST += crypto ssl -LIB_DIRS += $(OPENSSL_LIB) -endif -ifneq ($(ZLIB_LIB), ) -LIBS_LIST += z -LIB_DIRS += $(ZLIB_LIB) -endif -ifneq ($(ARES_LIB), ) -LIBS_LIST += ares -LIB_DIRS += $(ARES_LIB) -endif - -# Add include and library directories and libraries -CFLAGS += $(INCLUDE_DIRS:%=$(INCLUDE_FLAG)%) -LDFLAGS += $(LIB_DIRS:%=$(LIBS_DIRFLAG)%) - -# List of targets to make for libs target -LIBS_TARGET_LIST := libcurl.a - -# List of execuatble applications to make in addition to libs for all target -EXE_TARGET_LIST := - -# Support for echoing rules -# If ECHORULES variable was set (for example, using 'make' command line) -# some shell commands in the rules will be echoed -ifneq ($(strip $(findstring $(ECHORULES), yes YES 1 true TRUE)),) -_@_ := -else -_@_ := @ -endif - -# Directory to hold compilation intermediate files -TMP_DIR := tmp - -# Get sources and headers to be compiled -include Makefile.inc - -# List of headers -INCLUDE_FILES := $(HHEADERS) -INCLUDE_FILES += $(shell find ../include -name \*.h) - -# List of sources -OBJLIST := $(CSOURCES:%.c=$(TMP_DIR)/%$(OBJ_EXTENSION)) - - -# ---------------------------------------------------------------------- - -#### default rule -# It should be first rule in this file -.PHONY: default -default: libcurl.a - -#### Compiling C files -$(TMP_DIR)/%$(OBJ_EXTENSION): %.c $(MAKEFILES) - @echo Compiling C file $< $(ECHO_STDOUT) - @[ -d $(@D) ] || mkdir -p $(@D) - $(_@_) $(TOOL_CHAIN_BIN)$(CC) $(COMPILE_ONLY_FLAG) $(CFLAGS) $< $(CC_OBJ_OUTPUT) - -#### Creating library -$(LIBS_TARGET_LIST): $(INCLUDE_FILES) $(MAKEFILES) $(OBJLIST) - @echo Creating library $@ $(ECHO_STDOUT) - $(_@_) [ -d $(@D) ] || mkdir -p $(@D) - $(_@_) rm -f $@ - $(_@_) $(TOOL_CHAIN_BIN)$(AR) $(ARFLAGS) $@ $(filter %$(OBJ_EXTENSION), $^) - -#### Creating application -$(EXE_TARGET_LIST): $(INCLUDE_FILES) $(MAKEFILES) $(LIBS_TARGET_LIST) - @echo Creating application $@ - @[ -d $(@D) ] || mkdir -p $(@D) - $(_@_) $(TOOL_CHAIN_BIN)$(LINK) $(CC_OBJ_OUTPUT) $($(@)_EXE_OBJ_LIST) $(LDFLAGS) $($(@)_EXE_LIBS_NEEDED:%=$(LIBS_FLAG)%) $(LIBS_LIST:%=$(LIBS_FLAG)%) $(USER_LIBS_LIST) $(USER_LIBS_LIST) - -#### Master Targets -libs: $(LIBS_TARGET_LIST) - @echo All libs made. - -all: $(LIBS_TARGET_LIST) $(EXE_TARGET_LIST) $(INCLUDE_TARGET_LIST) - @echo All targets made. - -# Clean up -.PHONY: clean -clean: - $(_@_) rm -rf $(TMP_DIR) - @echo libcurl was cleaned. diff --git a/contrib/curl/lib/altsvc.c b/contrib/curl/lib/altsvc.c new file mode 100644 index 0000000000..6c040fbd4c --- /dev/null +++ b/contrib/curl/lib/altsvc.c @@ -0,0 +1,708 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ +/* + * 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 +#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; + if(strcasecompare(name, "http/1.1")) + return ALPN_h1; + 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 */ + free(as); + 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_memdup0(srchost, hlen); + if(!as->src.host) + goto error; + + as->dst.host = Curl_memdup0(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_append(&asi->list, 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; + FILE *fp; + + /* we need a private copy of the filename 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) { + struct dynbuf buf; + Curl_dyn_init(&buf, MAX_ALTSVC_LINE); + while(Curl_get_line(&buf, fp)) { + char *lineptr = Curl_dyn_ptr(&buf); + while(*lineptr && ISBLANK(*lineptr)) + lineptr++; + if(*lineptr == '#') + /* skip commented lines */ + continue; + + altsvc_add(asi, lineptr); + } + Curl_dyn_free(&buf); /* free the line buffer */ + fclose(fp); + } + return result; +} + +/* + * 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 USE_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 %u\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 USE_HTTP3 + | CURLALTSVC_H3 +#endif + ; + return asi; +} + +/* + * Curl_altsvc_load() loads alt-svc from file. + */ +CURLcode Curl_altsvc_load(struct altsvcinfo *asi, const char *file) +{ + DEBUGASSERT(asi); + return altsvc_load(asi, file); +} + +/* + * Curl_altsvc_ctrl() passes on the external bitmask. + */ +CURLcode Curl_altsvc_ctrl(struct altsvcinfo *asi, const long ctrl) +{ + DEBUGASSERT(asi); + 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) +{ + if(*altsvcp) { + struct Curl_llist_node *e; + struct Curl_llist_node *n; + struct altsvcinfo *altsvc = *altsvcp; + for(e = Curl_llist_head(&altsvc->list); e; e = n) { + struct altsvc *as = Curl_node_elem(e); + n = Curl_node_next(e); + 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) +{ + 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 filename */ + return CURLE_OK; + + result = Curl_fopen(data, file, &out, &tempstore); + if(!result) { + struct Curl_llist_node *e; + struct Curl_llist_node *n; + 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 = Curl_llist_head(&altsvc->list); e; e = n) { + struct altsvc *as = Curl_node_elem(e); + n = Curl_node_next(e); + 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 cannot 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_node *e; + struct Curl_llist_node *n; + for(e = Curl_llist_head(&asi->list); e; e = n) { + struct altsvc *as = Curl_node_elem(e); + n = Curl_node_next(e); + if((srcalpnid == as->src.alpnid) && + (srcport == as->src.port) && + hostcompare(srchost, as->src.host)) { + Curl_node_remove(e); + 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) { + 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 is contents to the right of the + * header name. + * + * Currently this function rejects invalid data without returning an error. + * Invalid hostname, 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 != ':') { + /* hostname 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 hostname, 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 = (time_t)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_append(&asi->list, 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 is 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_node *e; + struct Curl_llist_node *n; + time_t now = time(NULL); + DEBUGASSERT(asi); + DEBUGASSERT(srchost); + DEBUGASSERT(dstentry); + + for(e = Curl_llist_head(&asi->list); e; e = n) { + struct altsvc *as = Curl_node_elem(e); + n = Curl_node_next(e); + if(as->expires < now) { + /* an expired entry, remove */ + Curl_node_remove(e); + altsvc_free(as); + continue; + } + if((as->src.alpnid == srcalpnid) && + hostcompare(srchost, as->src.host) && + (as->src.port == srcport) && + (versions & (int)as->dst.alpnid)) { + /* match */ + *dstentry = as; + return TRUE; + } + } + return FALSE; +} + +#endif /* !CURL_DISABLE_HTTP && !CURL_DISABLE_ALTSVC */ diff --git a/contrib/curl/lib/altsvc.h b/contrib/curl/lib/altsvc.h new file mode 100644 index 0000000000..48999efb31 --- /dev/null +++ b/contrib/curl/lib/altsvc.h @@ -0,0 +1,81 @@ +#ifndef HEADER_CURL_ALTSVC_H +#define HEADER_CURL_ALTSVC_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ +#include "curl_setup.h" + +#if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_ALTSVC) +#include +#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; + unsigned int prio; + struct Curl_llist_node 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/contrib/curl/lib/amigaos.c b/contrib/curl/lib/amigaos.c index 4f55b30e76..1321c53c48 100644 --- a/contrib/curl/lib/amigaos.c +++ b/contrib/curl/lib/amigaos.c @@ -5,11 +5,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2016, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -18,27 +18,193 @@ * 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(__ixemul__) +#ifdef __AMIGA__ -#include +#include +#include "hostip.h" #include "amigaos.h" +#ifdef HAVE_PROTO_BSDSOCKET_H +# if defined(__amigaos4__) +# include +# elif !defined(USE_AMISSL) +# include +# endif +# ifdef __libnix__ +# include +# 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 + +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 runtime, + * 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 +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__ -#include void __request(const char *msg); #else # define __request(msg) Printf(msg "\n\a") #endif -void Curl_amiga_cleanup() +void Curl_amiga_cleanup(void) { if(SocketBase) { CloseLibrary(SocketBase); @@ -46,32 +212,36 @@ void Curl_amiga_cleanup() } } -bool Curl_amiga_init() +CURLcode Curl_amiga_init(void) { if(!SocketBase) SocketBase = OpenLibrary("bsdsocket.library", 4); if(!SocketBase) { __request("No TCP/IP Stack running!"); - return FALSE; + 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 FALSE; + return CURLE_FAILED_INIT; } #ifndef __libnix__ atexit(Curl_amiga_cleanup); #endif - return TRUE; + return CURLE_OK; } #ifdef __libnix__ ADD2EXIT(Curl_amiga_cleanup, -50); #endif -#endif /* __AMIGA__ && ! __ixemul__ */ +#endif /* !USE_AMISSL */ + +#endif /* HAVE_PROTO_BSDSOCKET_H */ + +#endif /* __AMIGA__ */ diff --git a/contrib/curl/lib/amigaos.h b/contrib/curl/lib/amigaos.h index 02bee16107..c99d963ece 100644 --- a/contrib/curl/lib/amigaos.h +++ b/contrib/curl/lib/amigaos.h @@ -7,11 +7,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2012, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -20,20 +20,22 @@ * 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(__ixemul__) +#if defined(__AMIGA__) && defined(HAVE_PROTO_BSDSOCKET_H) && \ + (!defined(USE_AMISSL) || defined(__amigaos4__)) -bool Curl_amiga_init(); -void Curl_amiga_cleanup(); +CURLcode Curl_amiga_init(void); +void Curl_amiga_cleanup(void); #else -#define Curl_amiga_init() 1 +#define Curl_amiga_init() CURLE_OK #define Curl_amiga_cleanup() Curl_nop_stmt #endif #endif /* HEADER_CURL_AMIGAOS_H */ - diff --git a/contrib/curl/lib/arpa_telnet.h b/contrib/curl/lib/arpa_telnet.h index ec238729dd..d641a01da8 100644 --- a/contrib/curl/lib/arpa_telnet.h +++ b/contrib/curl/lib/arpa_telnet.h @@ -7,11 +7,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2011, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -20,6 +20,8 @@ * 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 /* @@ -37,6 +39,7 @@ #define CURL_NEW_ENV_VAR 0 #define CURL_NEW_ENV_VALUE 1 +#ifndef CURL_DISABLE_VERBOSE_STRINGS /* * The telnet options represented as strings */ @@ -53,11 +56,14 @@ static const char * const telnetoptions[]= "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_TELOPT(x) telnetoptions[x] #define CURL_NTELOPTS 40 @@ -71,11 +77,12 @@ static const char * const telnetoptions[]= #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_WONT 252 /* Our side will not 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: */ @@ -86,6 +93,7 @@ static const char * const telnetcmds[]= "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! ;-) */ @@ -97,7 +105,12 @@ static const char * const telnetcmds[]= #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 */ diff --git a/contrib/curl/lib/asyn-ares.c b/contrib/curl/lib/asyn-ares.c index c038c2a3f1..f6ad32ef36 100644 --- a/contrib/curl/lib/asyn-ares.c +++ b/contrib/curl/lib/asyn-ares.c @@ -5,11 +5,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2016, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -18,13 +18,21 @@ * 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_LIMITS_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 -#endif #ifdef HAVE_NETINET_IN_H #include #endif @@ -39,64 +47,87 @@ #include #endif -#ifdef HAVE_PROCESS_H -#include -#endif - -#if (defined(NETWARE) && defined(__NOVELL_LIBC__)) -#undef in_addr_t -#define in_addr_t unsigned long -#endif - -/*********************************************************************** - * 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 "urldata.h" #include "sendf.h" #include "hostip.h" #include "hash.h" #include "share.h" -#include "strerror.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) || defined(_WIN32) || defined(__SYMBIAN32__)) -# define CARES_STATICLIB -# endif -# include -# include /* really old c-ares didn't include this by - itself */ +#if defined(CURL_STATICLIB) && !defined(CARES_STATICLIB) && \ + defined(_WIN32) +# define CARES_STATICLIB +#endif +#include +#include /* really old c-ares did not 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 ResolverResults { - int num_pending; /* number of ares_gethostbyname() requests */ - Curl_addrinfo *temp_ai; /* intermediary result while fetching c-ares parts */ +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 does not 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 + +static int ares_ver = 0; + /* * 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. + * resolve API. Called from curl_global_init() to initialize global resolver + * environment. Initializes ares library. */ int Curl_resolver_global_init(void) { @@ -105,6 +136,7 @@ int Curl_resolver_global_init(void) return CURLE_FAILED_INIT; } #endif + ares_version(&ares_ver); return CURLE_OK; } @@ -121,16 +153,48 @@ void Curl_resolver_global_cleanup(void) #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. + * structure). Fills the passed pointer by the initialized ares_channel. */ -CURLcode Curl_resolver_init(void **resolver) +CURLcode Curl_resolver_init(struct Curl_easy *easy, void **resolver) { - int status = ares_init((ares_channel*)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; + + /* + if c ares < 1.20.0: curl set timeout to CARES_TIMEOUT_PER_ATTEMPT (2s) + + if c-ares >= 1.20.0 it already has the timeout to 2s, curl does not need + to set the timeout value; + + if c-ares >= 1.24.0, user can set the timeout via /etc/resolv.conf to + overwrite c-ares' timeout. + */ + DEBUGASSERT(ares_ver); + if(ares_ver < 0x011400) { + options.timeout = CARES_TIMEOUT_PER_ATTEMPT; + 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; @@ -147,7 +211,7 @@ CURLcode Curl_resolver_init(void **resolver) * * Called from curl_easy_cleanup() -> Curl_close() to cleanup resolver * URL-state specific environment ('resolver' member of the UrlState - * structure). Destroys the ares channel. + * structure). Destroys the ares channel. */ void Curl_resolver_cleanup(void *resolver) { @@ -158,15 +222,18 @@ 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). Duplicates the + * environment ('resolver' member of the UrlState structure). Duplicates the * 'from' ares channel and passes the resulting channel to the 'to' pointer. */ -int Curl_resolver_duphandle(void **to, void *from) +CURLcode Curl_resolver_duphandle(struct Curl_easy *easy, void **to, void *from) { - /* Clone the ares channel for the new handle */ - if(ARES_SUCCESS != ares_dup((ares_channel*)to, (ares_channel)from)) - return CURLE_FAILED_INIT; - return CURLE_OK; + (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); @@ -174,11 +241,23 @@ static void destroy_async_data(struct Curl_async *async); /* * Cancel all possibly still on-going resolves for this connection. */ -void Curl_resolver_cancel(struct connectdata *conn) +void Curl_resolver_cancel(struct Curl_easy *data) +{ + DEBUGASSERT(data); + if(data->state.async.resolver) + ares_cancel((ares_channel)data->state.async.resolver); + destroy_async_data(&data->state.async); +} + +/* + * We are equivalent to Curl_resolver_cancel() for the c-ares resolver. We + * never block. + */ +void Curl_resolver_kill(struct Curl_easy *data) { - if(conn->data && conn->data->state.resolver) - ares_cancel((ares_channel)conn->data->state.resolver); - destroy_async_data(&conn->async); + /* We do not 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); } /* @@ -186,10 +265,8 @@ void Curl_resolver_cancel(struct connectdata *conn) */ static void destroy_async_data(struct Curl_async *async) { - free(async->hostname); - - if(async->os_specific) { - struct ResolverResults *res = (struct ResolverResults *)async->os_specific; + if(async->tdata) { + struct thread_data *res = async->tdata; if(res) { if(res->temp_ai) { Curl_freeaddrinfo(res->temp_ai); @@ -197,42 +274,38 @@ static void destroy_async_data(struct Curl_async *async) } free(res); } - async->os_specific = NULL; + async->tdata = NULL; } - - async->hostname = 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 + * (using curl_multi_fdset()) wants to get our fd_set setup and we are 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 connectdata *conn, - curl_socket_t *socks, - int numsocks) - +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)conn->data->state.resolver, - (ares_socket_t *)socks, numsocks); + int max = ares_getsock((ares_channel)data->state.async.resolver, + (ares_socket_t *)socks, MAX_SOCKSPEREASYHANDLE); maxtime.tv_sec = CURL_TIMEOUT_RESOLVE; maxtime.tv_usec = 0; - timeout = ares_timeout((ares_channel)conn->data->state.resolver, &maxtime, + timeout = ares_timeout((ares_channel)data->state.async.resolver, &maxtime, &timebuf); - milli = (timeout->tv_sec * 1000) + (timeout->tv_usec/1000); + milli = (long)curlx_tvtoms(timeout); if(milli == 0) milli += 10; - Curl_expire_latest(conn->data, milli); + Curl_expire(data, milli, EXPIRE_ASYNC_NAME); return max; } @@ -244,12 +317,11 @@ int Curl_resolver_getsock(struct connectdata *conn, * 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 + * return number of sockets it worked on, or -1 on error */ -static int waitperform(struct connectdata *conn, int timeout_ms) +static int waitperform(struct Curl_easy *data, timediff_t timeout_ms) { - struct Curl_easy *data = conn->data; int nfds; int bitmask; ares_socket_t socks[ARES_GETSOCK_MAXNUM]; @@ -257,10 +329,10 @@ static int waitperform(struct connectdata *conn, int timeout_ms) int i; int num = 0; - bitmask = ares_getsock((ares_channel)data->state.resolver, socks, + bitmask = ares_getsock((ares_channel)data->state.async.resolver, socks, ARES_GETSOCK_MAXNUM); - for(i=0; i < ARES_GETSOCK_MAXNUM; i++) { + for(i = 0; i < ARES_GETSOCK_MAXNUM; i++) { pfd[i].events = 0; pfd[i].revents = 0; if(ARES_GETSOCK_READABLE(bitmask, i)) { @@ -271,30 +343,33 @@ static int waitperform(struct connectdata *conn, int timeout_ms) pfd[i].fd = socks[i]; pfd[i].events |= POLLWRNORM|POLLOUT; } - if(pfd[i].events != 0) + if(pfd[i].events) num++; else break; } - if(num) - nfds = Curl_poll(pfd, num, timeout_ms); + if(num) { + nfds = Curl_poll(pfd, (unsigned int)num, timeout_ms); + if(nfds < 0) + return -1; + } else nfds = 0; if(!nfds) - /* Call ares_process() unconditonally here, even if we simply timed out - above, as otherwise the ares name resolve won't timeout! */ - ares_process_fd((ares_channel)data->state.resolver, ARES_SOCKET_BAD, + /* Call ares_process() unconditionally here, even if we simply timed out + above, as otherwise the ares name resolve will not timeout! */ + ares_process_fd((ares_channel)data->state.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->state.resolver, - pfd[i].revents & (POLLRDNORM|POLLIN)? - pfd[i].fd:ARES_SOCKET_BAD, - pfd[i].revents & (POLLWRNORM|POLLOUT)? - pfd[i].fd:ARES_SOCKET_BAD); + for(i = 0; i < num; i++) + ares_process_fd((ares_channel)data->state.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; } @@ -306,33 +381,55 @@ static int waitperform(struct connectdata *conn, int timeout_ms) * * Returns normal CURLcode errors. */ -CURLcode Curl_resolver_is_resolved(struct connectdata *conn, +CURLcode Curl_resolver_is_resolved(struct Curl_easy *data, struct Curl_dns_entry **dns) { - struct Curl_easy *data = conn->data; - struct ResolverResults *res = (struct ResolverResults *) - conn->async.os_specific; + struct thread_data *res = data->state.async.tdata; CURLcode result = CURLE_OK; + DEBUGASSERT(dns); *dns = NULL; - waitperform(conn, 0); + if(waitperform(data, 0) < 0) + return CURLE_UNRECOVERABLE_POLL; + +#ifndef HAVE_CARES_GETADDRINFO + /* Now that we have 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->state.async.resolver); + DEBUGASSERT(res->num_pending == 0); + } +#endif if(res && !res->num_pending) { - (void)Curl_addrinfo_callback(conn, res->last_status, res->temp_ai); + (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(!conn->async.dns) { - failf(data, "Could not resolve: %s (%s)", - conn->async.hostname, ares_strerror(conn->async.status)); - result = conn->bits.proxy?CURLE_COULDNT_RESOLVE_PROXY: - CURLE_COULDNT_RESOLVE_HOST; - } + + if(!data->state.async.dns) + result = Curl_resolver_error(data); else - *dns = conn->async.dns; + *dns = data->state.async.dns; - destroy_async_data(&conn->async); + destroy_async_data(&data->state.async); } return result; @@ -341,103 +438,129 @@ CURLcode Curl_resolver_is_resolved(struct connectdata *conn, /* * Curl_resolver_wait_resolv() * - * waits for a resolve to finish. This function should be avoided since using + * 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 + * 'entry' MUST be non-NULL. * - * Returns CURLE_COULDNT_RESOLVE_HOST if the host was not resolved, and - * CURLE_OPERATION_TIMEDOUT if a time-out occurred. + * 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 connectdata *conn, +CURLcode Curl_resolver_wait_resolv(struct Curl_easy *data, struct Curl_dns_entry **entry) { CURLcode result = CURLE_OK; - struct Curl_easy *data = conn->data; - long timeout; - struct timeval now = Curl_tvnow(); - struct Curl_dns_entry *temp_entry; + 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. */ - for(;;) { + while(!result) { struct timeval *tvp, tv, store; - long timediff; int itimeout; - int timeout_ms; + timediff_t timeout_ms; - itimeout = (timeout > (long)INT_MAX) ? INT_MAX : (int)timeout; +#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->state.resolver, &store, &tv); + tvp = ares_timeout((ares_channel)data->state.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 = (int)(tvp->tv_usec/1000); + timeout_ms = (timediff_t)(tvp->tv_usec/1000); else timeout_ms = 1000; - waitperform(conn, timeout_ms); - Curl_resolver_is_resolved(conn, &temp_entry); + if(waitperform(data, timeout_ms) < 0) + return CURLE_UNRECOVERABLE_POLL; + result = Curl_resolver_is_resolved(data, entry); - if(conn->async.done) + if(result || data->state.async.done) break; - if(Curl_pgrsUpdate(conn)) { + if(Curl_pgrsUpdate(data)) result = CURLE_ABORTED_BY_CALLBACK; - timeout = -1; /* trigger the cancel below */ - } else { - struct timeval now2 = Curl_tvnow(); - timediff = Curl_tvdiff(now2, now); /* spent time */ - timeout -= timediff?timediff:1; /* always deduct at least 1 */ + 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) { - /* our timeout, so we cancel the ares operation */ - ares_cancel((ares_channel)data->state.resolver); - break; - } + if(timeout < 0) + result = CURLE_OPERATION_TIMEDOUT; } + if(result) + /* failure, so we cancel the ares operation */ + ares_cancel((ares_channel)data->state.async.resolver); /* Operation complete, if the lookup was successful we now have the entry in the cache. */ if(entry) - *entry = conn->async.dns; + *entry = data->state.async.dns; if(result) - /* close the connection, since we can't return failure here without - cleaning up this connection properly. - TODO: remove this action from here, it is not a name resolver decision. - */ - connclose(conn, "c-ares resolve failed"); + /* close the connection, since we cannot 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 ResolverResults *res, - Curl_addrinfo *ai) +static void compound_results(struct thread_data *res, + struct Curl_addrinfo *ai) { - Curl_addrinfo *ai_tail; if(!ai) return; - ai_tail = ai; - while(ai_tail->ai_next) - ai_tail = ai_tail->ai_next; +#ifdef USE_IPV6 /* CURLRES_IPV6 */ + if(res->temp_ai && res->temp_ai->ai_family == PF_INET6) { + /* We have results already, put the new IPv6 entries at the head of the + list. */ + struct Curl_addrinfo *temp_ai_tail = res->temp_ai; + + while(temp_ai_tail->ai_next) + temp_ai_tail = temp_ai_tail->ai_next; - /* Add the new results to the list of old results. */ - ai_tail->ai_next = res->temp_ai; - res->temp_ai = ai; + 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; + } } /* @@ -452,8 +575,8 @@ static void query_completed_cb(void *arg, /* (struct connectdata *) */ #endif struct hostent *hostent) { - struct connectdata *conn = (struct connectdata *)arg; - struct ResolverResults *res; + struct Curl_easy *data = (struct Curl_easy *)arg; + struct thread_data *res; #ifdef HAVE_CARES_CALLBACK_TIMEOUTS (void)timeouts; /* ignored */ @@ -464,120 +587,255 @@ static void query_completed_cb(void *arg, /* (struct connectdata *) */ be valid so only defer it when we know the 'status' says its fine! */ return; - res = (struct ResolverResults *)conn->async.os_specific; - res->num_pending--; + res = data->state.async.tdata; + if(res) { + res->num_pending--; + + if(CURL_ASYNC_SUCCESS == status) { + struct Curl_addrinfo *ai = Curl_he2ai(hostent, data->state.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 have got a + "definitive" response from one of a set of parallel queries, we need to + think about how long we are 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 is 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 do not + bother handling any other cases. */ + DEBUGASSERT(res->num_pending == 1); + + /* it is possible that one of these parallel queries could succeed + quickly, but the other could always fail or timeout (when we are + talking to a pool of DNS servers that can only successfully resolve + IPv4 address, for example). + + it is 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 do not want to wait _too_ long or users will think + requests are slow when IPv6 lookups do not 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 did not 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 cannot know exactly + where in its retry cycle we are. We could guess based on how much + time has gone by, but it does not really matter. Happy Eyeballs tells + us that, given usable information in hand, we simply do not 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 do not 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 USE_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; - if(CURL_ASYNC_SUCCESS == status) { - Curl_addrinfo *ai = Curl_he2ai(hostent, conn->async.port); - if(ai) { - compound_results(res, ai); + /* 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; + struct thread_data *res = data->state.async.tdata; + (void)timeouts; + if(ARES_SUCCESS == status) { + res->temp_ai = ares2addr(result->nodes); + res->last_status = CURL_ASYNC_SUCCESS; + ares_freeaddrinfo(result); } - /* A successful result overwrites any previous error */ - if(res->last_status != ARES_SUCCESS) - res->last_status = status; + 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 forth argument will point to + * 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. */ -Curl_addrinfo *Curl_resolver_getaddrinfo(struct connectdata *conn, - const char *hostname, - int port, - int *waitp) +struct Curl_addrinfo *Curl_resolver_getaddrinfo(struct Curl_easy *data, + const char *hostname, + int port, + int *waitp) { - char *bufp; - struct Curl_easy *data = conn->data; - struct in_addr in; - int family = PF_INET; -#ifdef ENABLE_IPV6 /* CURLRES_IPV6 */ - struct in6_addr in6; -#endif /* CURLRES_IPV6 */ - + struct thread_data *res = NULL; + size_t namelen = strlen(hostname); *waitp = 0; /* default to synchronous response */ - /* 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 */ - return Curl_ip2addr(AF_INET, &in, hostname, port); - } + res = calloc(1, sizeof(struct thread_data) + namelen); + if(res) { + strcpy(res->hostname, hostname); + data->state.async.hostname = res->hostname; + data->state.async.port = port; + data->state.async.done = FALSE; /* not done */ + data->state.async.status = 0; /* clear */ + data->state.async.dns = NULL; /* clear */ + data->state.async.tdata = res; -#ifdef ENABLE_IPV6 /* CURLRES_IPV6 */ - /* Otherwise, check if this is an IPv6 address string */ - if(Curl_inet_pton (AF_INET6, hostname, &in6) > 0) - /* This must be an IPv6 address literal. */ - return Curl_ip2addr(AF_INET6, &in6, hostname, port); + /* initial status - failed */ + res->last_status = ARES_ENOTFOUND; - switch(conn->ip_version) { - default: -#if ARES_VERSION >= 0x010601 - family = PF_UNSPEC; /* supported by c-ares since 1.6.1, so for older - c-ares versions this just falls through and defaults - to PF_INET */ - break; -#endif - case CURL_IPRESOLVE_V4: - family = PF_INET; - break; - case CURL_IPRESOLVE_V6: - family = PF_INET6; - break; - } +#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 */ - - bufp = strdup(hostname); - if(bufp) { - struct ResolverResults *res = NULL; - free(conn->async.hostname); - conn->async.hostname = bufp; - conn->async.port = port; - conn->async.done = FALSE; /* not done */ - conn->async.status = 0; /* clear */ - conn->async.dns = NULL; /* clear */ - res = calloc(sizeof(struct ResolverResults), 1); - if(!res) { - free(conn->async.hostname); - conn->async.hostname = NULL; - return NULL; + 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->state.async.resolver, hostname, + service, &hints, addrinfo_cb, data); } - conn->async.os_specific = res; +#else - /* initial status - failed */ - res->last_status = ARES_ENOTFOUND; -#ifdef ENABLE_IPV6 /* CURLRES_IPV6 */ - if(family == PF_UNSPEC) { - if(Curl_ipv6works()) { - res->num_pending = 2; - - /* areschannel is already setup in the Curl_open() function */ - ares_gethostbyname((ares_channel)data->state.resolver, hostname, - PF_INET, query_completed_cb, conn); - ares_gethostbyname((ares_channel)data->state.resolver, hostname, - PF_INET6, query_completed_cb, conn); - } - else { - res->num_pending = 1; +#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->state.resolver, hostname, - PF_INET, query_completed_cb, conn); - } + /* areschannel is already setup in the Curl_open() function */ + ares_gethostbyname((ares_channel)data->state.async.resolver, hostname, + PF_INET, query_completed_cb, data); + ares_gethostbyname((ares_channel)data->state.async.resolver, hostname, + PF_INET6, query_completed_cb, data); } else -#endif /* CURLRES_IPV6 */ +#endif { res->num_pending = 1; /* areschannel is already setup in the Curl_open() function */ - ares_gethostbyname((ares_channel)data->state.resolver, hostname, family, - query_completed_cb, conn); + ares_gethostbyname((ares_channel)data->state.async.resolver, + hostname, PF_INET, + query_completed_cb, data); } - +#endif *waitp = 1; /* expect asynchronous response */ } return NULL; /* no struct yet */ @@ -591,15 +849,20 @@ CURLcode Curl_set_dns_servers(struct Curl_easy *data, /* 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 + * So, just return OK if none are configured and do not actually make + * any changes to c-ares. This lets c-ares use its defaults, which * it gets from the OS (for instance from /etc/resolv.conf on Linux). */ if(!(servers && servers[0])) return CURLE_OK; -#if (ARES_VERSION >= 0x010704) - ares_result = ares_set_servers_csv(data->state.resolver, servers); +#ifdef HAVE_CARES_SERVERS_CSV +#ifdef HAVE_CARES_PORTS_CSV + ares_result = ares_set_servers_ports_csv(data->state.async.resolver, + servers); +#else + ares_result = ares_set_servers_csv(data->state.async.resolver, servers); +#endif switch(ares_result) { case ARES_SUCCESS: result = CURLE_OK; @@ -611,6 +874,7 @@ CURLcode Curl_set_dns_servers(struct Curl_easy *data, case ARES_ENODATA: case ARES_EBADSTR: default: + DEBUGF(infof(data, "bad servers set")); result = CURLE_BAD_FUNCTION_ARGUMENT; break; } @@ -624,11 +888,11 @@ CURLcode Curl_set_dns_servers(struct Curl_easy *data, CURLcode Curl_set_dns_interface(struct Curl_easy *data, const char *interf) { -#if (ARES_VERSION >= 0x010704) +#ifdef HAVE_CARES_LOCAL_DEV if(!interf) interf = ""; - ares_set_local_dev((ares_channel)data->state.resolver, interf); + ares_set_local_dev((ares_channel)data->state.async.resolver, interf); return CURLE_OK; #else /* c-ares version too old! */ @@ -641,7 +905,7 @@ CURLcode Curl_set_dns_interface(struct Curl_easy *data, CURLcode Curl_set_dns_local_ip4(struct Curl_easy *data, const char *local_ip4) { -#if (ARES_VERSION >= 0x010704) +#ifdef HAVE_CARES_SET_LOCAL struct in_addr a4; if((!local_ip4) || (local_ip4[0] == 0)) { @@ -649,11 +913,13 @@ CURLcode Curl_set_dns_local_ip4(struct Curl_easy *data, } else { if(Curl_inet_pton(AF_INET, local_ip4, &a4) != 1) { + DEBUGF(infof(data, "bad DNS IPv4 address")); return CURLE_BAD_FUNCTION_ARGUMENT; } } - ares_set_local_ip4((ares_channel)data->state.resolver, ntohl(a4.s_addr)); + ares_set_local_ip4((ares_channel)data->state.async.resolver, + ntohl(a4.s_addr)); return CURLE_OK; #else /* c-ares version too old! */ @@ -666,7 +932,7 @@ CURLcode Curl_set_dns_local_ip4(struct Curl_easy *data, CURLcode Curl_set_dns_local_ip6(struct Curl_easy *data, const char *local_ip6) { -#if (ARES_VERSION >= 0x010704) && defined(ENABLE_IPV6) +#if defined(HAVE_CARES_SET_LOCAL) && defined(USE_IPV6) unsigned char a6[INET6_ADDRSTRLEN]; if((!local_ip6) || (local_ip6[0] == 0)) { @@ -675,11 +941,12 @@ CURLcode Curl_set_dns_local_ip6(struct Curl_easy *data, } else { if(Curl_inet_pton(AF_INET6, local_ip6, a6) != 1) { + DEBUGF(infof(data, "bad DNS IPv6 address")); return CURLE_BAD_FUNCTION_ARGUMENT; } } - ares_set_local_ip6((ares_channel)data->state.resolver, a6); + ares_set_local_ip6((ares_channel)data->state.async.resolver, a6); return CURLE_OK; #else /* c-ares version too old! */ diff --git a/contrib/curl/lib/asyn-thread.c b/contrib/curl/lib/asyn-thread.c index 26a15b110c..a58e4b7904 100644 --- a/contrib/curl/lib/asyn-thread.c +++ b/contrib/curl/lib/asyn-thread.c @@ -5,11 +5,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2016, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -18,9 +18,17 @@ * 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 @@ -36,19 +44,8 @@ #include #endif -#if defined(USE_THREADS_POSIX) -# ifdef HAVE_PTHREAD_H -# include -# endif -#elif defined(USE_THREADS_WIN32) -# ifdef HAVE_PROCESS_H -# include -# endif -#endif - -#if (defined(NETWARE) && defined(__NOVELL_LIBC__)) -#undef in_addr_t -#define in_addr_t unsigned long +#if defined(USE_THREADS_POSIX) && defined(HAVE_PTHREAD_H) +# include #endif #ifdef HAVE_GETADDRINFO @@ -62,10 +59,8 @@ #include "hostip.h" #include "hash.h" #include "share.h" -#include "strerror.h" #include "url.h" #include "multiif.h" -#include "inet_pton.h" #include "inet_ntop.h" #include "curl_threads.h" #include "connect.h" @@ -74,10 +69,9 @@ #include "curl_memory.h" #include "memdebug.h" -/*********************************************************************** - * Only for threaded name resolves builds - **********************************************************************/ -#ifdef CURLRES_THREADED +struct resdata { + struct curltime start; +}; /* * Curl_resolver_global_init() @@ -102,11 +96,14 @@ 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). Does nothing here. + * structure). */ -CURLcode Curl_resolver_init(void **resolver) +CURLcode Curl_resolver_init(struct Curl_easy *easy, void **resolver) { - (void)resolver; + (void)easy; + *resolver = calloc(1, sizeof(struct resdata)); + if(!*resolver) + return CURLE_OUT_OF_MEMORY; return CURLE_OK; } @@ -114,24 +111,22 @@ CURLcode Curl_resolver_init(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). Does nothing here. + * structure). */ void Curl_resolver_cleanup(void *resolver) { - (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). Does nothing - * here. + * environment ('resolver' member of the UrlState structure). */ -int Curl_resolver_duphandle(void **to, void *from) +CURLcode Curl_resolver_duphandle(struct Curl_easy *easy, void **to, void *from) { - (void)to; (void)from; - return CURLE_OK; + return Curl_resolver_init(easy, to); } static void destroy_async_data(struct Curl_async *); @@ -139,27 +134,30 @@ static void destroy_async_data(struct Curl_async *); /* * Cancel all possibly still on-going resolves for this connection. */ -void Curl_resolver_cancel(struct connectdata *conn) +void Curl_resolver_cancel(struct Curl_easy *data) { - destroy_async_data(&conn->async); + destroy_async_data(&data->state.async); } /* This function is used to init a threaded resolve */ -static bool init_resolve_thread(struct connectdata *conn, +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; + curl_mutex_t *mtx; int done; - + int port; char *hostname; /* hostname to resolve, Curl_async.hostname duplicate */ - int port; +#ifndef CURL_DISABLE_SOCKETPAIR + struct Curl_easy *data; + curl_socket_t sock_pair[2]; /* eventfd/pipes/socket pair */ +#endif int sock_error; - Curl_addrinfo *res; + struct Curl_addrinfo *res; #ifdef HAVE_GETADDRINFO struct addrinfo hints; #endif @@ -169,20 +167,18 @@ struct thread_sync_data { struct thread_data { curl_thread_t thread_hnd; unsigned int poll_interval; - time_t interval_end; + timediff_t interval_end; struct thread_sync_data tsd; }; -static struct thread_sync_data *conn_thread_sync_data(struct connectdata *conn) +static struct thread_sync_data *conn_thread_sync_data(struct Curl_easy *data) { - return &(((struct thread_data *)conn->async.os_specific)->tsd); + return &(data->state.async.tdata->tsd); } -#define CONN_THREAD_SYNC_DATA(conn) &(((conn)->async.os_specific)->tsd); - /* Destroy resolver thread synchronization data */ static -void destroy_thread_sync_data(struct thread_sync_data * tsd) +void destroy_thread_sync_data(struct thread_sync_data *tsd) { if(tsd->mtx) { Curl_mutex_destroy(tsd->mtx); @@ -194,12 +190,21 @@ void destroy_thread_sync_data(struct thread_sync_data * tsd) 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, +int init_thread_sync_data(struct thread_data *td, const char *hostname, int port, const struct addrinfo *hints) @@ -210,6 +215,10 @@ int init_thread_sync_data(struct thread_data * td, 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; @@ -218,11 +227,19 @@ int init_thread_sync_data(struct thread_data * td, #endif tsd->mtx = malloc(sizeof(curl_mutex_t)); - if(tsd->mtx == NULL) + 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, FALSE) < 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 @@ -234,24 +251,29 @@ int init_thread_sync_data(struct thread_data * td, return 1; - err_exit: - /* Memory allocation failed */ +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 int getaddrinfo_complete(struct connectdata *conn) +static CURLcode getaddrinfo_complete(struct Curl_easy *data) { - struct thread_sync_data *tsd = conn_thread_sync_data(conn); - int rc; + struct thread_sync_data *tsd = conn_thread_sync_data(data); + CURLcode result; - rc = Curl_addrinfo_callback(conn, tsd->sock_error, tsd->res); + 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. + cache. Set our copy to NULL so destroy_thread_sync_data does not free it. */ tsd->res = NULL; - return rc; + return result; } @@ -260,22 +282,36 @@ static int getaddrinfo_complete(struct connectdata *conn) /* * getaddrinfo_thread() resolves a name and then exits. * - * For builds without ARES, but with ENABLE_IPV6, create a resolver thread + * For builds without ARES, but with USE_IPV6, create a resolver thread * and wait on it. */ -static unsigned int CURL_STDCALL getaddrinfo_thread(void *arg) +static +#if defined(_WIN32_WCE) || defined(CURL_WINDOWS_UWP) +DWORD +#else +unsigned int +#endif +CURL_STDCALL getaddrinfo_thread(void *arg) { - struct thread_sync_data *tsd = (struct thread_sync_data*)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 +#ifdef USE_EVENTFD + const void *buf; + const uint64_t val = 1; +#else + char buf[1]; +#endif +#endif - snprintf(service, sizeof(service), "%d", tsd->port); + msnprintf(service, sizeof(service), "%d", tsd->port); rc = Curl_getaddrinfo_ex(tsd->hostname, service, &tsd->hints, &tsd->res); - if(rc != 0) { - tsd->sock_error = SOCKERRNO?SOCKERRNO:rc; + if(rc) { + tsd->sock_error = SOCKERRNO ? SOCKERRNO : rc; if(tsd->sock_error == 0) tsd->sock_error = RESOLVER_ENOMEM; } @@ -291,6 +327,20 @@ static unsigned int CURL_STDCALL getaddrinfo_thread(void *arg) free(td); } else { +#ifndef CURL_DISABLE_SOCKETPAIR + if(tsd->sock_pair[1] != CURL_SOCKET_BAD) { +#ifdef USE_EVENTFD + buf = &val; +#else + buf[0] = 1; +#endif + /* DNS has been resolved, signal client task */ + 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); } @@ -303,7 +353,13 @@ static unsigned int CURL_STDCALL getaddrinfo_thread(void *arg) /* * gethostbyname_thread() resolves a name and then exits. */ -static unsigned int CURL_STDCALL gethostbyname_thread(void *arg) +static +#if defined(_WIN32_WCE) || defined(CURL_WINDOWS_UWP) +DWORD +#else +unsigned int +#endif +CURL_STDCALL gethostbyname_thread(void *arg) { struct thread_sync_data *tsd = (struct thread_sync_data *)arg; struct thread_data *td = tsd->td; @@ -338,9 +394,13 @@ static unsigned int CURL_STDCALL gethostbyname_thread(void *arg) */ static void destroy_async_data(struct Curl_async *async) { - if(async->os_specific) { - struct thread_data *td = (struct thread_data*) async->os_specific; + 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 @@ -360,10 +420,18 @@ static void destroy_async_data(struct Curl_async *async) destroy_thread_sync_data(&td->tsd); - free(async->os_specific); + 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); + wakeup_close(sock_rd); +#endif } - async->os_specific = NULL; + async->tdata = NULL; free(async->hostname); async->hostname = NULL; @@ -375,118 +443,137 @@ static void destroy_async_data(struct Curl_async *async) * * Returns FALSE in case of failure, otherwise TRUE. */ -static bool init_resolve_thread(struct connectdata *conn, +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 = RESOLVER_ENOMEM; + int err = ENOMEM; + struct Curl_async *asp = &data->state.async; - conn->async.os_specific = (void *)td; + data->state.async.tdata = td; if(!td) - goto err_exit; + goto errno_exit; - conn->async.port = port; - conn->async.done = FALSE; - conn->async.status = 0; - conn->async.dns = NULL; + 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)) - goto err_exit; + if(!init_thread_sync_data(td, hostname, port, hints)) { + asp->tdata = NULL; + free(td); + goto errno_exit; + } - free(conn->async.hostname); - conn->async.hostname = strdup(hostname); - if(!conn->async.hostname) + 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) { -#ifndef _WIN32_WCE + if(td->thread_hnd == curl_thread_t_null) { + /* The thread never started, so mark it as done here for proper cleanup. */ + td->tsd.done = 1; err = errno; -#endif goto err_exit; } return TRUE; - err_exit: - destroy_async_data(&conn->async); - - SET_ERRNO(err); +err_exit: + destroy_async_data(asp); +errno_exit: + errno = err; return FALSE; } /* - * resolver_error() calls failf() with the appropriate message after a resolve - * error + * 'entry' may be NULL and then no data is returned */ - -static CURLcode resolver_error(struct connectdata *conn) +static CURLcode thread_wait_resolv(struct Curl_easy *data, + struct Curl_dns_entry **entry, + bool report) { - const char *host_or_proxy; - CURLcode result; + struct thread_data *td; + CURLcode result = CURLE_OK; - if(conn->bits.httpproxy) { - host_or_proxy = "proxy"; - result = CURLE_COULDNT_RESOLVE_PROXY; - } - else { - host_or_proxy = "host"; - result = CURLE_COULDNT_RESOLVE_HOST; + DEBUGASSERT(data); + td = data->state.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->state.async.done = TRUE; + + if(entry) + *entry = data->state.async.dns; - failf(conn->data, "Could not resolve %s: %s", host_or_proxy, - conn->async.hostname); + if(!data->state.async.dns && report) + /* a name was not resolved, report error */ + result = Curl_resolver_error(data); + + destroy_async_data(&data->state.async); + + if(!data->state.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->state.async.tdata; + + /* If we are 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 + * 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 connectdata *conn, +CURLcode Curl_resolver_wait_resolv(struct Curl_easy *data, struct Curl_dns_entry **entry) { - struct thread_data *td = (struct thread_data*) conn->async.os_specific; - CURLcode result = CURLE_OK; - - DEBUGASSERT(conn && td); - - /* wait for the thread to resolve the name */ - if(Curl_thread_join(&td->thread_hnd)) - result = getaddrinfo_complete(conn); - else - DEBUGASSERT(0); - - conn->async.done = TRUE; - - if(entry) - *entry = conn->async.dns; - - if(!conn->async.dns) - /* a name was not resolved, report error */ - result = resolver_error(conn); - - destroy_async_data(&conn->async); - - if(!conn->async.dns) - connclose(conn, "asynch resolve failed"); - - return result; + return thread_wait_resolv(data, entry, TRUE); } /* @@ -494,13 +581,13 @@ CURLcode Curl_resolver_wait_resolv(struct connectdata *conn, * 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 connectdata *conn, +CURLcode Curl_resolver_is_resolved(struct Curl_easy *data, struct Curl_dns_entry **entry) { - struct Curl_easy *data = conn->data; - struct thread_data *td = (struct thread_data*) conn->async.os_specific; + struct thread_data *td = data->state.async.tdata; int done = 0; + DEBUGASSERT(entry); *entry = NULL; if(!td) { @@ -513,19 +600,21 @@ CURLcode Curl_resolver_is_resolved(struct connectdata *conn, Curl_mutex_release(td->tsd.mtx); if(done) { - getaddrinfo_complete(conn); + getaddrinfo_complete(data); - if(!conn->async.dns) { - CURLcode result = resolver_error(conn); - destroy_async_data(&conn->async); + if(!data->state.async.dns) { + CURLcode result = Curl_resolver_error(data); + destroy_async_data(&data->state.async); return result; } - destroy_async_data(&conn->async); - *entry = conn->async.dns; + destroy_async_data(&data->state.async); + *entry = data->state.async.dns; } else { /* poll for name lookup done with exponential backoff up to 250ms */ - time_t elapsed = Curl_tvdiff(Curl_tvnow(), data->progress.t_startsingle); + /* 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; @@ -540,47 +629,75 @@ CURLcode Curl_resolver_is_resolved(struct connectdata *conn, td->poll_interval = 250; td->interval_end = elapsed + td->poll_interval; - Curl_expire(conn->data, td->poll_interval); + Curl_expire(data, td->poll_interval, EXPIRE_ASYNC_NAME); } return CURLE_OK; } -int Curl_resolver_getsock(struct connectdata *conn, - curl_socket_t *socks, - int numsocks) +int Curl_resolver_getsock(struct Curl_easy *data, curl_socket_t *socks) { - (void)conn; + int ret_val = 0; + timediff_t milli; + timediff_t ms; + struct resdata *reslv = (struct resdata *)data->state.async.resolver; +#ifndef CURL_DISABLE_SOCKETPAIR + struct thread_data *td = data->state.async.tdata; +#else (void)socks; - (void)numsocks; - return 0; +#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 */ -Curl_addrinfo *Curl_resolver_getaddrinfo(struct connectdata *conn, - const char *hostname, - int port, - int *waitp) +struct Curl_addrinfo *Curl_resolver_getaddrinfo(struct Curl_easy *data, + const char *hostname, + int port, + int *waitp) { - struct in_addr in; + struct resdata *reslv = (struct resdata *)data->state.async.resolver; *waitp = 0; /* default to synchronous response */ - if(Curl_inet_pton(AF_INET, hostname, &in) > 0) - /* This is a dotted IP address 123.123.123.123-style */ - return Curl_ip2addr(AF_INET, &in, hostname, port); + reslv->start = Curl_now(); /* fire up a new resolver thread! */ - if(init_resolve_thread(conn, hostname, port, NULL)) { + if(init_resolve_thread(data, hostname, port, NULL)) { *waitp = 1; /* expect asynchronous response */ return NULL; } - /* fall-back to blocking version */ - return Curl_ipv4_resolve_r(hostname, port); + failf(data, "getaddrinfo() thread failed"); + + return NULL; } #else /* !HAVE_GETADDRINFO */ @@ -588,85 +705,42 @@ Curl_addrinfo *Curl_resolver_getaddrinfo(struct connectdata *conn, /* * Curl_resolver_getaddrinfo() - for getaddrinfo */ -Curl_addrinfo *Curl_resolver_getaddrinfo(struct connectdata *conn, - const char *hostname, - int port, - int *waitp) +struct Curl_addrinfo *Curl_resolver_getaddrinfo(struct Curl_easy *data, + const char *hostname, + int port, + int *waitp) { struct addrinfo hints; - struct in_addr in; - Curl_addrinfo *res; - int error; - char sbuf[12]; int pf = PF_INET; -#ifdef CURLRES_IPV6 - struct in6_addr in6; -#endif /* CURLRES_IPV6 */ + struct resdata *reslv = (struct resdata *)data->state.async.resolver; *waitp = 0; /* default to synchronous response */ -#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 */ - return Curl_ip2addr(AF_INET, &in, hostname, port); - -#ifdef CURLRES_IPV6 - /* check if this is an IPv6 address string */ - if(Curl_inet_pton(AF_INET6, hostname, &in6) > 0) - /* This is an IPv6 address literal */ - return Curl_ip2addr(AF_INET6, &in6, hostname, port); -#endif /* CURLRES_IPV6 */ -#endif /* !USE_RESOLVE_ON_IPS */ - #ifdef CURLRES_IPV6 - /* - * Check if a limited name resolve has been requested. - */ - switch(conn->ip_version) { - case CURL_IPRESOLVE_V4: - pf = PF_INET; - break; - case CURL_IPRESOLVE_V6: - pf = PF_INET6; - break; - default: - pf = PF_UNSPEC; - break; + 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; } - - if((pf != PF_INET) && !Curl_ipv6works()) - /* The stack seems to be a non-IPv6 one */ - pf = PF_INET; #endif /* CURLRES_IPV6 */ memset(&hints, 0, sizeof(hints)); hints.ai_family = pf; - hints.ai_socktype = conn->socktype; - - snprintf(sbuf, sizeof(sbuf), "%d", port); + 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(conn, hostname, port, &hints)) { + if(init_resolve_thread(data, hostname, port, &hints)) { *waitp = 1; /* expect asynchronous response */ return NULL; } - /* fall-back to blocking version */ - infof(conn->data, "init_resolve_thread() failed for %s; %s\n", - hostname, Curl_strerror(conn, ERRNO)); - - error = Curl_getaddrinfo_ex(hostname, sbuf, &hints, &res); - if(error) { - infof(conn->data, "getaddrinfo() failed for %s:%d; %s\n", - hostname, port, Curl_strerror(conn, SOCKERRNO)); - return NULL; - } - else { - Curl_addrinfo_set_port(res, port); - } + failf(data, "getaddrinfo() thread failed to start"); + return NULL; - return res; } #endif /* !HAVE_GETADDRINFO */ diff --git a/contrib/curl/lib/asyn.h b/contrib/curl/lib/asyn.h index 3adc3664a2..0ff2048845 100644 --- a/contrib/curl/lib/asyn.h +++ b/contrib/curl/lib/asyn.h @@ -7,11 +7,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2011, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -20,6 +20,8 @@ * 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" @@ -56,17 +58,17 @@ 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. + * 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(void **resolver); +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 + * structure). Should destroy the handler and free all resources connected to * it. */ void Curl_resolver_cleanup(void *resolver); @@ -74,21 +76,37 @@ 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 + * 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 + * pointer. Returning anything else than CURLE_OK causes failed * curl_easy_duphandle() call. */ -int Curl_resolver_duphandle(void **to, void *from); +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. + * 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_cancel(struct connectdata *conn); +void Curl_resolver_kill(struct Curl_easy *data); /* Curl_resolver_getsock() * @@ -98,8 +116,7 @@ void Curl_resolver_cancel(struct connectdata *conn); * return bitmask indicating what file descriptors (referring to array indexes * in the 'sock' array) to wait for, read/write. */ -int Curl_resolver_getsock(struct connectdata *conn, curl_socket_t *sock, - int numsocks); +int Curl_resolver_getsock(struct Curl_easy *data, curl_socket_t *sock); /* * Curl_resolver_is_resolved() @@ -110,48 +127,47 @@ int Curl_resolver_getsock(struct connectdata *conn, curl_socket_t *sock, * * Returns normal CURLcode errors. */ -CURLcode Curl_resolver_is_resolved(struct connectdata *conn, +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 + * 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, and - * CURLE_OPERATION_TIMEDOUT if a time-out occurred. - + * 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 connectdata *conn, +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 forth argument will point to + * 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. */ -Curl_addrinfo *Curl_resolver_getaddrinfo(struct connectdata *conn, - const char *hostname, - int port, - int *waitp); +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 */ +/* convert these functions if an asynch resolver is not 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_getsock(x,y,z) 0 -#define Curl_resolver_duphandle(x,y) CURLE_OK -#define Curl_resolver_init(x) CURLE_OK +#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 diff --git a/contrib/curl/lib/base64.c b/contrib/curl/lib/base64.c index 204a2273d1..8373115d20 100644 --- a/contrib/curl/lib/base64.c +++ b/contrib/curl/lib/base64.c @@ -5,11 +5,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2016, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -18,71 +18,47 @@ * 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" -#include "urldata.h" /* for the Curl_easy definition */ + +#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" -#include "non-ascii.h" -/* The last 3 #include files should be in this order */ -#include "curl_printf.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 --- */ -static const char base64[]= - "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; +/* Padding character string starts at offset 64. */ +static const char base64encdec[]= + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; -/* The Base 64 encoding with an URL and filename safe alphabet, RFC 4648 +/* The Base 64 encoding with a URL and filename safe alphabet, RFC 4648 section 5 */ static const char base64url[]= "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"; -static size_t decodeQuantum(unsigned char *dest, const char *src) -{ - size_t padding = 0; - const char *s, *p; - unsigned long i, x = 0; - - for(i = 0, s = src; i < 4; i++, s++) { - unsigned long v = 0; - - if(*s == '=') { - x = (x << 6); - padding++; - } - else { - p = base64; - - while(*p && (*p != *s)) { - v++; - p++; - } - - if(*p == *s) - x = (x << 6) + v; - else - return 0; - } - } - - if(padding < 1) - dest[2] = curlx_ultouc(x & 0xFFUL); - - x >>= 8; - if(padding < 2) - dest[1] = curlx_ultouc(x & 0xFFUL); - - x >>= 8; - dest[0] = curlx_ultouc(x & 0xFFUL); - - return 3 - padding; -} - +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() * @@ -101,13 +77,14 @@ CURLcode Curl_base64_decode(const char *src, unsigned char **outptr, size_t *outlen) { size_t srclen = 0; - size_t length = 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; @@ -117,45 +94,82 @@ CURLcode Curl_base64_decode(const char *src, if(!srclen || srclen % 4) return CURLE_BAD_CONTENT_ENCODING; - /* Find the position of any = padding characters */ - while((src[length] != '=') && src[length]) - length++; - - /* A maximum of two = padding characters is allowed */ - if(src[length] == '=') { + /* srclen is at least 4 here */ + while(src[srclen - 1 - padding] == '=') { + /* count padding characters */ padding++; - if(src[length + 1] == '=') - padding++; + /* A maximum of two = padding characters is allowed */ + if(padding > 2) + return CURLE_BAD_CONTENT_ENCODING; } - /* Check the = padding characters weren't part way through the input */ - if(length + padding != srclen) - 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 zero terminator */ + /* Allocate our buffer including room for a null-terminator */ newstr = malloc(rawlen + 1); if(!newstr) return CURLE_OUT_OF_MEMORY; pos = newstr; - /* Decode the quantums */ - for(i = 0; i < numQuantums; i++) { - size_t result = decodeQuantum(pos, src); - if(!result) { - free(newstr); - - return CURLE_BAD_CONTENT_ENCODING; + 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 += result; - src += 4; + 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 */ @@ -166,95 +180,60 @@ CURLcode Curl_base64_decode(const char *src, *outlen = rawlen; return CURLE_OK; +bad: + free(newstr); + return CURLE_BAD_CONTENT_ENCODING; } static CURLcode base64_encode(const char *table64, - struct Curl_easy *data, const char *inputbuff, size_t insize, char **outptr, size_t *outlen) { - CURLcode result; - unsigned char ibuf[3]; - unsigned char obuf[4]; - int i; - int inputparts; char *output; char *base64data; - char *convbuf = NULL; - - const char *indata = inputbuff; + const unsigned char *in = (unsigned char *)inputbuff; + const char *padstr = &table64[64]; /* Point to padding string. */ *outptr = NULL; *outlen = 0; if(!insize) - insize = strlen(indata); + insize = strlen(inputbuff); #if SIZEOF_SIZE_T == 4 if(insize > UINT_MAX/4) return CURLE_OUT_OF_MEMORY; #endif - base64data = output = malloc(insize * 4 / 3 + 4); + base64data = output = malloc((insize + 2) / 3 * 4 + 1); if(!output) return CURLE_OUT_OF_MEMORY; - /* - * The base64 data needs to be created using the network encoding - * not the host encoding. And we can't change the actual input - * so we copy it to a buffer, translate it, and use that instead. - */ - result = Curl_convert_clone(data, indata, insize, &convbuf); - if(result) { - free(output); - return result; + 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(convbuf) - indata = (char *)convbuf; - - while(insize > 0) { - for(i = inputparts = 0; i < 3; i++) { - if(insize > 0) { - inputparts++; - ibuf[i] = (unsigned char) *indata; - indata++; - insize--; + 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 - ibuf[i] = 0; } - - obuf[0] = (unsigned char) ((ibuf[0] & 0xFC) >> 2); - obuf[1] = (unsigned char) (((ibuf[0] & 0x03) << 4) | \ - ((ibuf[1] & 0xF0) >> 4)); - obuf[2] = (unsigned char) (((ibuf[1] & 0x0F) << 2) | \ - ((ibuf[2] & 0xC0) >> 6)); - obuf[3] = (unsigned char) (ibuf[2] & 0x3F); - - switch(inputparts) { - case 1: /* only one byte read */ - snprintf(output, 5, "%c%c==", - table64[obuf[0]], - table64[obuf[1]]); - break; - - case 2: /* two bytes read */ - snprintf(output, 5, "%c%c%c=", - table64[obuf[0]], - table64[obuf[1]], - table64[obuf[2]]); - break; - - default: - snprintf(output, 5, "%c%c%c%c", - table64[obuf[0]], - table64[obuf[1]], - table64[obuf[2]], - table64[obuf[3]]); - break; + else { + /* insize == 2 */ + *output++ = table64[ ((in[0] & 0x03) << 4) | ((in[1] & 0xF0) >> 4) ]; + *output++ = table64[ ((in[1] & 0x0F) << 2) ]; + if(*padstr) + *output++ = *padstr; } - output += 4; } /* Zero terminate */ @@ -263,10 +242,8 @@ static CURLcode base64_encode(const char *table64, /* Return the pointer to the new data (allocated memory) */ *outptr = base64data; - free(convbuf); - /* Return the length of the new data */ - *outlen = strlen(base64data); + *outlen = (size_t)(output - base64data); return CURLE_OK; } @@ -284,15 +261,12 @@ static CURLcode base64_encode(const char *table64, * Returns CURLE_OK on success, otherwise specific error code. Function * output shall not be considered valid unless CURLE_OK is returned. * - * When encoded data length is 0, returns NULL in *outptr. - * * @unittest: 1302 */ -CURLcode Curl_base64_encode(struct Curl_easy *data, - const char *inputbuff, size_t insize, +CURLcode Curl_base64_encode(const char *inputbuff, size_t insize, char **outptr, size_t *outlen) { - return base64_encode(base64, data, inputbuff, insize, outptr, outlen); + return base64_encode(base64encdec, inputbuff, insize, outptr, outlen); } /* @@ -308,13 +282,12 @@ CURLcode Curl_base64_encode(struct Curl_easy *data, * Returns CURLE_OK on success, otherwise specific error code. Function * output shall not be considered valid unless CURLE_OK is returned. * - * When encoded data length is 0, returns NULL in *outptr. - * * @unittest: 1302 */ -CURLcode Curl_base64url_encode(struct Curl_easy *data, - const char *inputbuff, size_t insize, +CURLcode Curl_base64url_encode(const char *inputbuff, size_t insize, char **outptr, size_t *outlen) { - return base64_encode(base64url, data, inputbuff, insize, outptr, outlen); + return base64_encode(base64url, inputbuff, insize, outptr, outlen); } + +#endif /* no users so disabled */ diff --git a/contrib/curl/lib/bufq.c b/contrib/curl/lib/bufq.c new file mode 100644 index 0000000000..4e67c3a3e8 --- /dev/null +++ b/contrib/curl/lib/bufq.c @@ -0,0 +1,746 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" +#include "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 size_t chunk_unwrite(struct buf_chunk *chunk, size_t len) +{ + size_t n = chunk->w_offset - chunk->r_offset; + DEBUGASSERT(chunk->w_offset >= chunk->r_offset); + if(!n) { + return 0; + } + else if(n <= len) { + chunk->r_offset = chunk->w_offset = 0; + return n; + } + else { + chunk->w_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 *chunk_prev(struct buf_chunk *head, + struct buf_chunk *chunk) +{ + while(head) { + if(head == chunk) + return NULL; + if(head->next == chunk) + return head; + head = head->next; + } + return NULL; +} + +static void prune_tail(struct bufq *q) +{ + struct buf_chunk *chunk; + + while(q->tail && chunk_is_empty(q->tail)) { + chunk = q->tail; + q->tail = chunk_prev(q->head, chunk); + if(q->tail) + q->tail->next = NULL; + if(q->head == chunk) + q->head = q->tail; + 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) || (q->opts & BUFQ_OPT_SOFT_LIMIT)) { + *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; +} + +CURLcode Curl_bufq_cwrite(struct bufq *q, + const char *buf, size_t len, + size_t *pnwritten) +{ + ssize_t n; + CURLcode result; + n = Curl_bufq_write(q, (const unsigned char *)buf, len, &result); + *pnwritten = (n < 0) ? 0 : (size_t)n; + return result; +} + +CURLcode Curl_bufq_unwrite(struct bufq *q, size_t len) +{ + while(len && q->tail) { + len -= chunk_unwrite(q->head, len); + prune_tail(q); + } + return len ? CURLE_AGAIN : CURLE_OK; +} + +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; +} + +CURLcode Curl_bufq_cread(struct bufq *q, char *buf, size_t len, + size_t *pnread) +{ + ssize_t n; + CURLcode result; + n = Curl_bufq_read(q, (unsigned char *)buf, len, &result); + *pnread = (n < 0) ? 0 : (size_t)n; + return result; +} + +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/contrib/curl/lib/bufq.h b/contrib/curl/lib/bufq.h new file mode 100644 index 0000000000..ec415648fd --- /dev/null +++ b/contrib/curl/lib/bufq.h @@ -0,0 +1,278 @@ +#ifndef HEADER_CURL_BUFQ_H +#define HEADER_CURL_BUFQ_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ +#include "curl_setup.h" + +#include + +/** + * 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 + * 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); + +CURLcode Curl_bufq_cwrite(struct bufq *q, + const char *buf, size_t len, + size_t *pnwritten); + +/** + * Remove `len` bytes from the end of the buffer queue again. + * Returns CURLE_AGAIN if less than `len` bytes were in the queue. + */ +CURLcode Curl_bufq_unwrite(struct bufq *q, size_t len); + +/** + * 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); + +CURLcode Curl_bufq_cread(struct bufq *q, char *buf, size_t len, + size_t *pnread); + +/** + * 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/contrib/curl/lib/bufref.c b/contrib/curl/lib/bufref.c new file mode 100644 index 0000000000..f048b57011 --- /dev/null +++ b/contrib/curl/lib/bufref.c @@ -0,0 +1,127 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" +#include "urldata.h" +#include "bufref.h" +#include "strdup.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 does not 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 = Curl_memdup0(ptr, len); + if(!cpy) + return CURLE_OUT_OF_MEMORY; + } + + Curl_bufref_set(br, cpy, len, curl_free); + return CURLE_OK; +} diff --git a/contrib/curl/lib/bufref.h b/contrib/curl/lib/bufref.h new file mode 100644 index 0000000000..dd424f18f5 --- /dev/null +++ b/contrib/curl/lib/bufref.h @@ -0,0 +1,48 @@ +#ifndef HEADER_CURL_BUFREF_H +#define HEADER_CURL_BUFREF_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +/* + * 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/contrib/curl/lib/c-hyper.c b/contrib/curl/lib/c-hyper.c new file mode 100644 index 0000000000..2b8eb95707 --- /dev/null +++ b/contrib/curl/lib/c-hyper.c @@ -0,0 +1,1254 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.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 +#endif + +#ifdef HAVE_NETDB_H +#include +#endif +#ifdef HAVE_ARPA_INET_H +#include +#endif +#ifdef HAVE_NET_IF_H +#include +#endif +#ifdef HAVE_SYS_IOCTL_H +#include +#endif + +#ifdef HAVE_SYS_PARAM_H +#include +#endif + +#include +#include "urldata.h" +#include "cfilters.h" +#include "sendf.h" +#include "headers.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" + + +static CURLcode cr_hyper_add(struct Curl_easy *data); + +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 hyp_io_ctx *io_ctx = userp; + struct Curl_easy *data = io_ctx->data; + struct connectdata *conn = data->conn; + CURLcode result; + ssize_t nread; + DEBUGASSERT(conn); + (void)ctx; + + DEBUGF(infof(data, "Curl_hyper_recv(%zu)", buflen)); + result = Curl_conn_recv(data, io_ctx->sockindex, + (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 hyp_io_ctx *io_ctx = userp; + struct Curl_easy *data = io_ctx->data; + CURLcode result; + size_t nwrote; + + DEBUGF(infof(data, "Curl_hyper_send(%zu)", buflen)); + result = Curl_conn_send(data, io_ctx->sockindex, + (void *)buf, buflen, FALSE, &nwrote); + 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_TOO_LARGE; + return HYPER_ITER_BREAK; + } + + 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, headp, len); + 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(!k->bodywritten) { +#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(Curl_http_exp100_is_selected(data)) { + if(data->req.httpcode < 400) { + Curl_http_exp100_got100(data); + if(data->hyp.send_body_waker) { + hyper_waker_wake(data->hyp.send_body_waker); + data->hyp.send_body_waker = NULL; + } + } + else { /* >= 4xx */ + Curl_req_abort_sending(data); + } + } + if(data->state.hconnect && (data->req.httpcode/100 != 2) && + data->state.authproxy.done) { + data->req.done = TRUE; + result = CURLE_OK; + } + else + result = Curl_http_firstwrite(data); + if(result || data->req.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; + data->req.httpversion = http_version == HYPER_HTTP_VERSION_1_1 ? 11 : + (http_version == HYPER_HTTP_VERSION_2 ? 20 : 10); + if(data->state.hconnect) + /* CONNECT */ + data->info.httpproxycode = http_status; + else { + conn->httpversion = (unsigned char)data->req.httpversion; + 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: could not pass blank header"); + /* Hyper does chunked decoding itself. If it was added during + * response header processing, remove it again. */ + Curl_cwriter_remove_by_name(data, "chunked"); + } + return result; +} + +CURLcode Curl_hyper_stream(struct Curl_easy *data, + struct connectdata *conn, + int *didwhat, + 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(data->hyp.send_body_waker) { + /* If there is still something to upload, wake it to give it + * another try. */ + hyper_waker_wake(data->hyp.send_body_waker); + data->hyp.send_body_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; + } + + while(1) { + 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; + goto out; + case HYPERE_UNEXPECTED_EOF: + if(!data->req.bytecount) + result = CURLE_GOT_NOTHING; + else + result = CURLE_RECV_ERROR; + goto out; + 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 */ + goto out; + default: + result = CURLE_RECV_ERROR; + goto out; + } + } + data->req.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 == (void *)USERDATA_RESP_BODY) { + /* end of transfer */ + data->req.done = TRUE; + infof(data, "hyperstream is done"); + if(!k->bodywritten) { + /* hyper does not always call the body write callback */ + result = Curl_http_firstwrite(data); + } + break; + } + else { + /* A background task for hyper; ignore */ + DEBUGF(infof(data, "hyper: some background task done")); + continue; + } + } + else if(t == HYPER_TASK_RESPONSE) { + resp = hyper_task_value(task); + hyper_task_free(task); + + *didwhat = KEEP_RECV; + if(!resp) { + failf(data, "hyperstream: could not get response"); + result = CURLE_RECV_ERROR; + goto out; + } + + 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 && Curl_http_exp100_is_selected(data)) { + infof(data, "Got 417 while waiting for a 100"); + data->state.disableexpect = TRUE; + data->req.newurl = strdup(data->state.url); + Curl_req_abort_sending(data); + } + + result = status_line(data, conn, + http_status, http_version, reasonp, reason_len); + if(result) + goto out; + + headers = hyper_response_headers(resp); + if(!headers) { + failf(data, "hyperstream: could not get response headers"); + result = CURLE_RECV_ERROR; + goto out; + } + + /* the headers are already received */ + hyper_headers_foreach(headers, hyper_each_header, data); + if(data->state.hresult) { + result = data->state.hresult; + goto out; + } + + result = empty_header(data); + if(result) + goto out; + + k->deductheadercount = + (100 <= http_status && 199 >= http_status) ? k->headerbytecount : 0; +#ifndef CURL_DISABLE_WEBSOCKETS + if(k->upgr101 == UPGR101_WS) { + if(http_status == 101) { + /* verify the response */ + result = Curl_ws_accept(data, NULL, 0); + if(result) + goto out; + } + else { + failf(data, "Expected 101, got %u", k->httpcode); + result = CURLE_HTTP_RETURNED_ERROR; + goto out; + } + } +#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) + goto out; + + resp_body = hyper_response_body(resp); + if(!resp_body) { + failf(data, "hyperstream: could not get response body"); + result = CURLE_RECV_ERROR; + goto out; + } + foreach = hyper_body_foreach(resp_body, hyper_body_chunk, data); + if(!foreach) { + failf(data, "hyperstream: body foreach failed"); + result = CURLE_OUT_OF_MEMORY; + goto out; + } + 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; + goto out; + } + + hyper_response_free(resp); + resp = NULL; + } + else { + DEBUGF(infof(data, "hyper: unhandled tasktype %x", t)); + } + } /* while(1) */ + + if(!result && Curl_xfer_needs_flush(data)) { + DEBUGF(infof(data, "Curl_hyper_stream(), connection needs flush")); + result = Curl_xfer_flush(data); + } + +out: + DEBUGF(infof(data, "Curl_hyper_stream() -> %d", result)); + 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 uploadstreamed(void *userdata, hyper_context *ctx, + hyper_buf **chunk) +{ + size_t fillcount; + struct Curl_easy *data = (struct Curl_easy *)userdata; + CURLcode result; + char *xfer_ulbuf; + size_t xfer_ulblen; + bool eos; + int rc = HYPER_POLL_ERROR; + (void)ctx; + + result = Curl_multi_xfer_ulbuf_borrow(data, &xfer_ulbuf, &xfer_ulblen); + if(result) + goto out; + + result = Curl_client_read(data, xfer_ulbuf, xfer_ulblen, &fillcount, &eos); + if(result) + goto out; + + if(fillcount) { + hyper_buf *copy = hyper_buf_copy((uint8_t *)xfer_ulbuf, fillcount); + if(copy) + *chunk = copy; + else { + result = CURLE_OUT_OF_MEMORY; + goto out; + } + /* increasing the writebytecount here is a little premature but we + do not know exactly when the body is sent */ + data->req.writebytecount += fillcount; + if(eos) + data->req.eos_read = TRUE; + Curl_pgrsSetUploadCounter(data, data->req.writebytecount); + rc = HYPER_POLL_READY; + } + else if(eos) { + data->req.eos_read = TRUE; + *chunk = NULL; + rc = HYPER_POLL_READY; + } + 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); + rc = HYPER_POLL_PENDING; + } + + if(!data->req.upload_done && data->req.eos_read) { + DEBUGF(infof(data, "hyper: uploadstreamed(), upload is done")); + result = Curl_req_set_upload_done(data); + } + +out: + Curl_multi_xfer_ulbuf_release(data, xfer_ulbuf); + data->state.hresult = result; + DEBUGF(infof(data, "hyper: uploadstreamed() -> %d", result)); + return rc; +} + +/* + * finalize_request() sets up last headers and optional body settings + */ +static CURLcode finalize_request(struct Curl_easy *data, + hyper_headers *headers, + hyper_request *hyperreq, + Curl_HttpReq httpreq) +{ + 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_req_complete(data, &req, httpreq); + if(result) + return result; + + /* if the "complete" above did produce more than the closing line, + parse the added headers */ + if(Curl_dyn_len(&req) != 2 || strcmp(Curl_dyn_ptr(&req), "\r\n")) { + result = Curl_hyper_header(data, headers, Curl_dyn_ptr(&req)); + if(result) + return result; + } + + Curl_dyn_free(&req); + + body = hyper_body_new(); + hyper_body_set_userdata(body, data); + hyper_body_set_data_func(body, uploadstreamed); + + if(HYPERE_OK != hyper_request_set_body(hyperreq, body)) { + /* fail */ + result = CURLE_OUT_OF_MEMORY; + } + } + + return cr_hyper_add(data); +} + +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: could not 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; + result = Curl_client_start(data); + if(result) + goto out; + + /* Add collecting of headers written to client. For a new connection, + * we might have done that already, but reuse + * or multiplex needs it here as well. */ + result = Curl_headers_init(data); + if(result) + goto out; + + infof(data, "Time for the Hyper dance"); + memset(h, 0, sizeof(struct hyptransfer)); + + result = Curl_http_host(data, conn); + if(result) + goto out; + + 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) { + result = CURLE_OUT_OF_MEMORY; + goto out; + } + } + result = Curl_http_output_auth(data, conn, method, httpreq, + (pq ? pq : data->state.up.path), FALSE); + free(pq); + if(result) + goto out; + } + + result = Curl_http_req_set_reader(data, httpreq, &te); + if(result) + goto out; + + result = Curl_http_range(data, httpreq); + if(result) + goto out; + + result = Curl_http_useragent(data); + if(result) + goto out; + + io = hyper_io_new(); + if(!io) { + failf(data, "Couldn't create hyper IO"); + result = CURLE_OUT_OF_MEMORY; + goto out; + } + /* tell Hyper how to read/write network data */ + h->io_ctx.data = data; + h->io_ctx.sockindex = FIRSTSOCKET; + hyper_io_set_userdata(io, &h->io_ctx); + 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 out; + } + } + + options = hyper_clientconn_options_new(); + if(!options) { + failf(data, "Couldn't create hyper client options"); + result = CURLE_OUT_OF_MEMORY; + goto out; + } + if(conn->alpn == CURL_HTTP_VERSION_2) { + failf(data, "ALPN protocol h2 not supported with Hyper"); + result = CURLE_UNSUPPORTED_PROTOCOL; + goto out; + } + 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 out; + } + 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 out; + } + 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 out; + } + + 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 out; + } + + 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 out; + } + } + + if(hyper_request_set_method(req, (uint8_t *)method, strlen(method))) { + failf(data, "error setting method"); + result = CURLE_OUT_OF_MEMORY; + goto out; + } + + result = request_target(data, conn, method, req); + if(result) + goto out; + + headers = hyper_request_headers(req); + if(!headers) { + failf(data, "hyper_request_headers"); + result = CURLE_OUT_OF_MEMORY; + goto out; + } + + rc = hyper_request_on_informational(req, http1xx_cb, data); + if(rc) { + result = CURLE_OUT_OF_MEMORY; + goto out; + } + + if(data->state.aptr.host) { + result = Curl_hyper_header(data, headers, data->state.aptr.host); + if(result) + goto out; + } + +#ifndef CURL_DISABLE_PROXY + if(data->state.aptr.proxyuserpwd) { + result = Curl_hyper_header(data, headers, data->state.aptr.proxyuserpwd); + if(result) + goto out; + } +#endif + + if(data->state.aptr.userpwd) { + result = Curl_hyper_header(data, headers, data->state.aptr.userpwd); + if(result) + goto out; + } + + if((data->state.use_range && data->state.aptr.rangeline)) { + result = Curl_hyper_header(data, headers, data->state.aptr.rangeline); + if(result) + goto out; + } + + 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 out; + } + + 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 out; + } + if(te) { + result = Curl_hyper_header(data, headers, te); + if(result) + goto out; + } + +#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 out; + } + result = Curl_hyper_header(data, headers, altused); + if(result) + goto out; + 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 out; + } +#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 out; + } + +#ifdef HAVE_LIBZ + /* we only consider transfer-encoding magic if libz support is built-in */ + result = Curl_transferencode(data); + if(result) + goto out; + result = Curl_hyper_header(data, headers, data->state.aptr.te); + if(result) + goto out; +#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 out; + } + else + Curl_safefree(data->state.aptr.accept_encoding); + + result = cookies(data, conn, headers); + if(result) + goto out; + + if(!result && conn->handler->protocol&(CURLPROTO_WS|CURLPROTO_WSS)) + result = Curl_ws_request(data, headers); + + result = Curl_add_timecondition(data, headers); + if(result) + goto out; + + result = Curl_add_custom_headers(data, FALSE, headers); + if(result) + goto out; + + result = finalize_request(data, headers, req, httpreq); + if(result) + goto out; + + Curl_debug(data, CURLINFO_HEADER_OUT, (char *)"\r\n", 2); + + if(data->req.upload_chunky && data->req.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 out; + } + 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 out; + } + 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 */ + result = Curl_req_set_upload_done(data); + if(result) + goto out; + } + + Curl_xfer_setup1(data, CURL_XFER_SENDRECV, -1, TRUE); + conn->datastream = Curl_hyper_stream; + + /* clear userpwd and proxyuserpwd to avoid reusing old credentials + * from reused connections */ + Curl_safefree(data->state.aptr.userpwd); +#ifndef CURL_DISABLE_PROXY + Curl_safefree(data->state.aptr.proxyuserpwd); +#endif + +out: + if(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->send_body_waker) { + hyper_waker_free(h->send_body_waker); + h->send_body_waker = NULL; + } +} + +static CURLcode cr_hyper_unpause(struct Curl_easy *data, + struct Curl_creader *reader) +{ + (void)reader; + if(data->hyp.send_body_waker) { + hyper_waker_wake(data->hyp.send_body_waker); + data->hyp.send_body_waker = NULL; + } + return CURLE_OK; +} + +/* Hyper client reader, handling unpausing */ +static const struct Curl_crtype cr_hyper_protocol = { + "cr-hyper", + Curl_creader_def_init, + Curl_creader_def_read, + Curl_creader_def_close, + Curl_creader_def_needs_rewind, + Curl_creader_def_total_length, + Curl_creader_def_resume_from, + Curl_creader_def_rewind, + cr_hyper_unpause, + Curl_creader_def_is_paused, + Curl_creader_def_done, + sizeof(struct Curl_creader) +}; + +static CURLcode cr_hyper_add(struct Curl_easy *data) +{ + struct Curl_creader *reader = NULL; + CURLcode result; + + result = Curl_creader_create(&reader, data, &cr_hyper_protocol, + CURL_CR_PROTOCOL); + if(!result) + result = Curl_creader_add(data, reader); + + if(result && reader) + Curl_creader_free(data, reader); + return result; +} + +#endif /* !defined(CURL_DISABLE_HTTP) && defined(USE_HYPER) */ diff --git a/contrib/curl/lib/c-hyper.h b/contrib/curl/lib/c-hyper.h new file mode 100644 index 0000000000..89dd53b8fd --- /dev/null +++ b/contrib/curl/lib/c-hyper.h @@ -0,0 +1,63 @@ +#ifndef HEADER_CURL_HYPER_H +#define HEADER_CURL_HYPER_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.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 + +struct hyp_io_ctx { + struct Curl_easy *data; + int sockindex; +}; + +/* per-transfer data for the Hyper backend */ +struct hyptransfer { + hyper_waker *write_waker; + hyper_waker *read_waker; + const hyper_executor *exec; + hyper_waker *send_body_waker; + struct hyp_io_ctx io_ctx; +}; + +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, + 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/contrib/curl/lib/cf-h1-proxy.c b/contrib/curl/lib/cf-h1-proxy.c new file mode 100644 index 0000000000..a9fd21fa4d --- /dev/null +++ b/contrib/curl/lib/cf-h1-proxy.c @@ -0,0 +1,1103 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#if !defined(CURL_DISABLE_PROXY) && !defined(CURL_DISABLE_HTTP) + +#include +#ifdef USE_HYPER +#include +#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 dynbuf rcvbuf; + struct dynbuf request_data; + size_t nsent; + size_t headerlines; + struct Curl_chunker ch; + 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; + + if(cf->conn->handler->flags & PROTOPT_NOTCPPROXY) { + failf(data, "%s cannot be done over CONNECT", cf->conn->handler->scheme); + return CURLE_UNSUPPORTED_PROTOCOL; + } + + 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); + Curl_httpchunk_init(data, &ts->ch, TRUE); + + *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; + /* 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 is not accidentally used for the document request + after we have 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) +{ + if(cf) { + struct h1_tunnel_state *ts = cf->ctx; + if(ts) { + h1_tunnel_go_state(cf, ts, H1_TUNNEL_FAILED, data); + Curl_dyn_free(&ts->rcvbuf); + Curl_dyn_free(&ts->request_data); + Curl_httpchunk_free(data, &ts->ch); + free(ts); + cf->ctx = NULL; + } + } +} + +static bool tunnel_want_send(struct h1_tunnel_state *ts) +{ + return (ts->tunnel_state == H1_TUNNEL_CONNECT); +} + +#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 have looped here due to authentication + reasons, and we do not 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); + if(!result) + result = Curl_creader_set_null(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, FALSE, &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; + /* reset our chunky engine */ + Curl_httpchunk_reset(data, &ts->ch, TRUE); + } + } + 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; + char *linep; + size_t line_len; + 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_conn_recv(data, cf->sockindex, &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 are done! */ + ts->cl--; + if(ts->cl <= 0) { + ts->keepon = KEEPON_DONE; + break; + } + } + else if(ts->chunked_encoding) { + /* chunked-encoded body, so we need to do the chunked dance + properly to know when the end of the body is reached */ + size_t consumed = 0; + + /* now parse the chunked piece of data so that we can + properly tell when the stream ends */ + result = Curl_httpchunk_read(data, &ts->ch, &byte, 1, &consumed); + if(result) + return result; + if(Curl_httpchunk_is_done(data, &ts->ch)) { + /* we are 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); + line_len = Curl_dyn_len(&ts->rcvbuf); /* amount of bytes in this line */ + + /* output debug if that is requested */ + Curl_debug(data, CURLINFO_HEADER_IN, linep, line_len); + + /* send the header to the callback */ + writetype = CLIENTWRITE_HEADER | CLIENTWRITE_CONNECT | + (ts->headerlines == 1 ? CLIENTWRITE_STATUS : 0); + result = Curl_client_write(data, writetype, linep, line_len); + if(result) + return result; + + result = Curl_bump_headersize(data, line_len, TRUE); + if(result) + return result; + + /* Newlines are CRLF, so the CR is ignored as the line is not + 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 %" FMT_OFF_T " bytes of response-body", ts->cl); + } + else if(ts->chunked_encoding) { + infof(data, "Ignore chunked response-body"); + } + else { + /* without content-length or chunked encoding, we + cannot 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 is 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 */ + h->io_ctx.data = data; + h->io_ctx.sockindex = cf->sockindex; + hyper_io_set_userdata(io, &h->io_ctx); + 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 have looped here due to authentication + reasons, and we do not 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] && *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; + + result = Curl_creader_set_null(data); + 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; + result = Curl_hyper_stream(data, cf->conn, &didwhat, + CURL_CSELECT_IN | CURL_CSELECT_OUT); + *done = data->req.done; + 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. + */ + Curl_req_soft_reset(&data->req, data); + 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; + /* The real request will follow the CONNECT, reset request partially */ + Curl_req_soft_reset(&data->req, data); + Curl_client_reset(data); + Curl_pgrsSetUploadCounter(data, 0); + Curl_pgrsSetDownloadCounter(data, 0); + + 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 have 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 are still sending the request, wait + for write. */ + if(tunnel_want_send(ts)) + 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"); + if(cf) { + cf->connected = FALSE; + if(cf->ctx) { + h1_tunnel_go_state(cf, cf->ctx, H1_TUNNEL_INIT, data); + } + if(cf->next) + cf->next->cft->do_close(cf->next, data); + } +} + + +struct Curl_cftype Curl_cft_h1_proxy = { + "H1-PROXY", + CF_TYPE_IP_CONNECT|CF_TYPE_PROXY, + 0, + cf_h1_proxy_destroy, + cf_h1_proxy_connect, + cf_h1_proxy_close, + Curl_cf_def_shutdown, + Curl_cf_http_proxy_get_host, + cf_h1_proxy_adjust_pollset, + Curl_cf_def_data_pending, + 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/contrib/curl/lib/cf-h1-proxy.h b/contrib/curl/lib/cf-h1-proxy.h new file mode 100644 index 0000000000..ac5bed0b2b --- /dev/null +++ b/contrib/curl/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, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the 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/contrib/curl/lib/cf-h2-proxy.c b/contrib/curl/lib/cf-h2-proxy.c new file mode 100644 index 0000000000..038952d641 --- /dev/null +++ b/contrib/curl/lib/cf-h2-proxy.c @@ -0,0 +1,1659 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#if defined(USE_NGHTTP2) && !defined(CURL_DISABLE_PROXY) + +#include +#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 "sendf.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; + 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 is not accidentally used for the document request + after we have 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(rcvd_goaway); + BIT(sent_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) +{ + struct cf_h2_proxy_ctx *ctx = cf->ctx; + unsigned char bits; + + (void)cf; + bits = CURL_CSELECT_IN; + if(!tunnel->closed && !tunnel->reset && + !Curl_bufq_is_empty(&ctx->tunnel.sendbuf)) + bits |= CURL_CSELECT_OUT; + if(data->state.select_bits != bits) { + CURL_TRC_CF(data, cf, "[%d] DRAIN select_bits=%x", + tunnel->stream_id, bits); + data->state.select_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, + FALSE, 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->rcvd_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; + result = Curl_creader_set_null(data); + 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 && (result != CURLE_AGAIN)) || 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); + if(*done) { + cf->connected = TRUE; + /* The real request will follow the CONNECT, reset request partially */ + Curl_req_soft_reset(&data->req, data); + Curl_client_reset(data); + } + 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 CURLcode cf_h2_proxy_shutdown(struct Curl_cfilter *cf, + struct Curl_easy *data, bool *done) +{ + struct cf_h2_proxy_ctx *ctx = cf->ctx; + struct cf_call_data save; + CURLcode result; + int rv; + + if(!cf->connected || !ctx->h2 || cf->shutdown || ctx->conn_closed) { + *done = TRUE; + return CURLE_OK; + } + + CF_DATA_SAVE(save, cf, data); + + if(!ctx->sent_goaway) { + rv = nghttp2_submit_goaway(ctx->h2, NGHTTP2_FLAG_NONE, + 0, 0, + (const uint8_t *)"shutdown", + sizeof("shutdown")); + if(rv) { + failf(data, "nghttp2_submit_goaway() failed: %s(%d)", + nghttp2_strerror(rv), rv); + result = CURLE_SEND_ERROR; + goto out; + } + ctx->sent_goaway = TRUE; + } + /* GOAWAY submitted, process egress and ingress until nghttp2 is done. */ + result = CURLE_OK; + if(nghttp2_session_want_write(ctx->h2)) + result = proxy_h2_progress_egress(cf, data); + if(!result && nghttp2_session_want_read(ctx->h2)) + result = proxy_h2_progress_ingress(cf, data); + + *done = (ctx->conn_closed || + (!result && !nghttp2_session_want_write(ctx->h2) && + !nghttp2_session_want_read(ctx->h2))); +out: + CF_DATA_RESTORE(cf, save); + cf->shutdown = (result || *done); + return result; +} + +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; + struct cf_call_data save; + curl_socket_t sock = Curl_conn_cf_get_socket(cf, data); + bool want_recv, want_send; + + if(!cf->connected && ctx->h2) { + want_send = nghttp2_session_want_write(ctx->h2) || + !Curl_bufq_is_empty(&ctx->outbufq) || + !Curl_bufq_is_empty(&ctx->tunnel.sendbuf); + want_recv = nghttp2_session_want_read(ctx->h2); + } + else + Curl_pollset_check(data, ps, sock, &want_recv, &want_send); + + if(ctx->h2 && (want_recv || want_send)) { + 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_bufq_is_empty(&ctx->outbufq) || + !Curl_bufq_is_empty(&ctx->tunnel.sendbuf); + + Curl_pollset_set(data, ps, sock, want_recv, want_send); + CURL_TRC_CF(data, cf, "adjust_pollset, want_recv=%d want_send=%d", + want_recv, want_send); + CF_DATA_RESTORE(cf, save); + } + else if(ctx->sent_goaway && !cf->shutdown) { + /* shutdown in progress */ + CF_DATA_SAVE(save, cf, data); + want_send = nghttp2_session_want_write(ctx->h2) || + !Curl_bufq_is_empty(&ctx->outbufq) || + !Curl_bufq_is_empty(&ctx->tunnel.sendbuf); + want_recv = nghttp2_session_want_read(ctx->h2); + Curl_pollset_set(data, ps, sock, want_recv, want_send); + CURL_TRC_CF(data, cf, "adjust_pollset, want_recv=%d want_send=%d", + 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"); /* do not 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->rcvd_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 && (result != CURLE_AGAIN)) { + *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, bool eos, + CURLcode *err) +{ + struct cf_h2_proxy_ctx *ctx = cf->ctx; + struct cf_call_data save; + int rv; + ssize_t nwritten; + CURLcode result; + + (void)eos; /* TODO, maybe useful for blocks? */ + 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 { + nwritten = Curl_bufq_write(&ctx->tunnel.sendbuf, buf, len, err); + if(nwritten < 0 && (*err != CURLE_AGAIN)) + goto out; + } + + 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 && (result != CURLE_AGAIN)) { + *err = result; + nwritten = -1; + goto out; + } + + 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 CURLcode cf_h2_proxy_flush(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct cf_h2_proxy_ctx *ctx = cf->ctx; + struct cf_call_data save; + CURLcode result = CURLE_OK; + + CF_DATA_SAVE(save, cf, data); + if(!Curl_bufq_is_empty(&ctx->tunnel.sendbuf)) { + /* resume the potentially suspended tunnel */ + int rv = nghttp2_session_resume_data(ctx->h2, ctx->tunnel.stream_id); + if(nghttp2_is_fatal(rv)) { + result = CURLE_SEND_ERROR; + goto out; + } + } + + result = proxy_h2_progress_egress(cf, data); + +out: + CURL_TRC_CF(data, cf, "[%d] flush -> %d, " + "h2 windows %d-%d (stream-conn), buffers %zu-%zu (stream-conn)", + ctx->tunnel.stream_id, result, + 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 result; +} + +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 have sent off a request and the connection is + not in use by any other transfer, there should not 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; +} + +static CURLcode cf_h2_proxy_query(struct Curl_cfilter *cf, + struct Curl_easy *data, + int query, int *pres1, void *pres2) +{ + struct cf_h2_proxy_ctx *ctx = cf->ctx; + + switch(query) { + case CF_QUERY_NEED_FLUSH: { + if(!Curl_bufq_is_empty(&ctx->outbufq) || + !Curl_bufq_is_empty(&ctx->tunnel.sendbuf)) { + CURL_TRC_CF(data, cf, "needs flush"); + *pres1 = TRUE; + return CURLE_OK; + } + break; + } + default: + break; + } + return cf->next ? + cf->next->cft->query(cf->next, data, query, pres1, pres2) : + CURLE_UNKNOWN_OPTION; +} + +static CURLcode cf_h2_proxy_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)arg1; + (void)arg2; + + switch(event) { + case CF_CTRL_FLUSH: + CF_DATA_SAVE(save, cf, data); + result = cf_h2_proxy_flush(cf, data); + CF_DATA_RESTORE(cf, save); + break; + default: + break; + } + return result; +} + +struct Curl_cftype Curl_cft_h2_proxy = { + "H2-PROXY", + CF_TYPE_IP_CONNECT|CF_TYPE_PROXY, + CURL_LOG_LVL_NONE, + cf_h2_proxy_destroy, + cf_h2_proxy_connect, + cf_h2_proxy_close, + cf_h2_proxy_shutdown, + Curl_cf_http_proxy_get_host, + cf_h2_proxy_adjust_pollset, + cf_h2_proxy_data_pending, + cf_h2_proxy_send, + cf_h2_proxy_recv, + cf_h2_proxy_cntrl, + cf_h2_proxy_is_alive, + Curl_cf_def_conn_keep_alive, + cf_h2_proxy_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/contrib/curl/lib/cf-h2-proxy.h b/contrib/curl/lib/cf-h2-proxy.h new file mode 100644 index 0000000000..c01bf62133 --- /dev/null +++ b/contrib/curl/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, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the 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/contrib/curl/lib/cf-haproxy.c b/contrib/curl/lib/cf-haproxy.c new file mode 100644 index 0000000000..ae2402f224 --- /dev/null +++ b/contrib/curl/lib/cf-haproxy.c @@ -0,0 +1,253 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#if !defined(CURL_DISABLE_PROXY) + +#include +#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 *client_ip; + struct ip_quadruple ipquad; + int is_ipv6; + + 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 */ + result = Curl_conn_cf_get_ip_info(cf->next, data, &is_ipv6, &ipquad); + if(result) + return result; + + /* Emit the correct prefix for IPv6 */ + if(data->set.str[STRING_HAPROXY_CLIENT_IP]) + client_ip = data->set.str[STRING_HAPROXY_CLIENT_IP]; + else + client_ip = ipquad.local_ip; + + result = Curl_dyn_addf(&ctx->data_out, "PROXY %s %s %s %i %i\r\n", + is_ipv6 ? "TCP6" : "TCP4", + client_ip, ipquad.remote_ip, + ipquad.local_port, ipquad.remote_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 nwritten; + nwritten = Curl_conn_cf_send(cf->next, data, + Curl_dyn_ptr(&ctx->data_out), len, FALSE, + &result); + if(nwritten < 0) { + if(result != CURLE_AGAIN) + goto out; + result = CURLE_OK; + nwritten = 0; + } + Curl_dyn_tail(&ctx->data_out, len - (size_t)nwritten); + 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", + CF_TYPE_PROXY, + 0, + cf_haproxy_destroy, + cf_haproxy_connect, + cf_haproxy_close, + Curl_cf_def_shutdown, + Curl_cf_def_get_host, + cf_haproxy_adjust_pollset, + Curl_cf_def_data_pending, + 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/contrib/curl/lib/cf-haproxy.h b/contrib/curl/lib/cf-haproxy.h new file mode 100644 index 0000000000..d02c323e7b --- /dev/null +++ b/contrib/curl/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, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the 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/contrib/curl/lib/cf-https-connect.c b/contrib/curl/lib/cf-https-connect.c new file mode 100644 index 0000000000..95c105e338 --- /dev/null +++ b/contrib/curl/lib/cf-https-connect.c @@ -0,0 +1,613 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#if !defined(CURL_DISABLE_HTTP) && !defined(USE_HYPER) + +#include "urldata.h" +#include +#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; + BIT(enabled); + BIT(shutdown); +}; + +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); +} + +static bool cf_hc_baller_needs_flush(struct cf_hc_baller *b, + struct Curl_easy *data) +{ + return b->cf && !b->result && Curl_conn_cf_needs_flush(b->cf, data); +} + +static CURLcode cf_hc_baller_cntrl(struct cf_hc_baller *b, + struct Curl_easy *data, + int event, int arg1, void *arg2) +{ + if(b->cf && !b->result) + return Curl_conn_cf_cntrl(b->cf, data, FALSE, event, arg1, arg2); + return CURLE_OK; +} + +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; + unsigned int soft_eyeballs_timeout_ms; + unsigned 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: + 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 + break; + default: + break; + } + ctx->state = CF_HC_SUCCESS; + cf->connected = TRUE; + 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 CURLcode cf_hc_shutdown(struct Curl_cfilter *cf, + struct Curl_easy *data, bool *done) +{ + struct cf_hc_ctx *ctx = cf->ctx; + struct cf_hc_baller *ballers[2]; + size_t i; + CURLcode result = CURLE_OK; + + DEBUGASSERT(data); + if(cf->connected) { + *done = TRUE; + return CURLE_OK; + } + + /* shutdown all ballers that have not done so already. If one fails, + * continue shutting down others until all are shutdown. */ + ballers[0] = &ctx->h3_baller; + ballers[1] = &ctx->h21_baller; + for(i = 0; i < sizeof(ballers)/sizeof(ballers[0]); i++) { + struct cf_hc_baller *b = ballers[i]; + bool bdone = FALSE; + if(!cf_hc_baller_is_active(b) || b->shutdown) + continue; + b->result = b->cf->cft->do_shutdown(b->cf, data, &bdone); + if(b->result || bdone) + b->shutdown = TRUE; /* treat a failed shutdown as done */ + } + + *done = TRUE; + for(i = 0; i < sizeof(ballers)/sizeof(ballers[0]); i++) { + if(ballers[i] && !ballers[i]->shutdown) + *done = FALSE; + } + if(*done) { + for(i = 0; i < sizeof(ballers)/sizeof(ballers[0]); i++) { + if(ballers[i] && ballers[i]->result) + result = ballers[i]->result; + } + } + CURL_TRC_CF(data, cf, "shutdown -> %d, done=%d", result, *done); + return result; +} + +static void cf_hc_adjust_pollset(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct easy_pollset *ps) +{ + 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) +{ + struct cf_hc_ctx *ctx = cf->ctx; + + if(!cf->connected) { + switch(query) { + case CF_QUERY_TIMER_CONNECT: { + struct curltime *when = pres2; + *when = cf_get_max_baller_time(cf, data, CF_QUERY_TIMER_CONNECT); + return CURLE_OK; + } + case CF_QUERY_TIMER_APPCONNECT: { + struct curltime *when = pres2; + *when = cf_get_max_baller_time(cf, data, CF_QUERY_TIMER_APPCONNECT); + return CURLE_OK; + } + case CF_QUERY_NEED_FLUSH: { + if(cf_hc_baller_needs_flush(&ctx->h3_baller, data) + || cf_hc_baller_needs_flush(&ctx->h21_baller, data)) { + *pres1 = TRUE; + return CURLE_OK; + } + break; + } + default: + break; + } + } + return cf->next ? + cf->next->cft->query(cf->next, data, query, pres1, pres2) : + CURLE_UNKNOWN_OPTION; +} + +static CURLcode cf_hc_cntrl(struct Curl_cfilter *cf, + struct Curl_easy *data, + int event, int arg1, void *arg2) +{ + struct cf_hc_ctx *ctx = cf->ctx; + CURLcode result = CURLE_OK; + + if(!cf->connected) { + result = cf_hc_baller_cntrl(&ctx->h3_baller, data, event, arg1, arg2); + if(!result || (result == CURLE_AGAIN)) + result = cf_hc_baller_cntrl(&ctx->h21_baller, data, event, arg1, arg2); + if(result == CURLE_AGAIN) + result = CURLE_OK; + } + return result; +} + +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, + cf_hc_shutdown, + Curl_cf_def_get_host, + cf_hc_adjust_pollset, + cf_hc_data_pending, + Curl_cf_def_send, + Curl_cf_def_recv, + cf_hc_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) /* cannot 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/contrib/curl/lib/cf-https-connect.h b/contrib/curl/lib/cf-https-connect.h new file mode 100644 index 0000000000..6a39527317 --- /dev/null +++ b/contrib/curl/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, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the 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/contrib/curl/lib/cf-socket.c b/contrib/curl/lib/cf-socket.c new file mode 100644 index 0000000000..fc57c39bc9 --- /dev/null +++ b/contrib/curl/lib/cf-socket.c @@ -0,0 +1,2188 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#ifdef HAVE_NETINET_IN_H +#include /* may need it */ +#endif +#ifdef HAVE_SYS_UN_H +#include /* for sockaddr_un */ +#endif +#ifdef HAVE_LINUX_TCP_H +#include +#elif defined(HAVE_NETINET_TCP_H) +#include +#endif +#ifdef HAVE_NETINET_UDP_H +#include +#endif +#ifdef HAVE_SYS_IOCTL_H +#include +#endif +#ifdef HAVE_NETDB_H +#include +#endif +#ifdef HAVE_FCNTL_H +#include +#endif +#ifdef HAVE_ARPA_INET_H +#include +#endif + +#ifdef __VMS +#include +#include +#endif + +#ifdef __DragonFly__ +/* Required for __DragonFly_version */ +#include +#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 "strdup.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(USE_IPV6) && defined(IPV6_V6ONLY) && defined(_WIN32) +/* It makes support for IPv4-mapped IPv6 addresses. + * Linux kernel, NetBSD, FreeBSD and Darwin: default is off; + * Windows Vista and later: default is on; + * 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 macOS (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(USE_WINSOCK) && \ + defined(TCP_KEEPIDLE) && defined(TCP_KEEPINTVL) && defined(TCP_KEEPCNT) +/* Win 10, v 1709 (10.0.16299) and later can use SetSockOpt TCP_KEEP____ + * so should use seconds */ +#define CURL_WINSOCK_KEEP_SSO +#define KEEPALIVE_FACTOR(x) +#elif defined(USE_WINSOCK) || \ + (defined(__sun) && !defined(TCP_KEEPIDLE)) || \ + (defined(__DragonFly__) && __DragonFly_version < 500702) || \ + (defined(_WIN32) && !defined(TCP_KEEPIDLE)) +/* Solaris < 11.4, DragonFlyBSD < 500702 and Windows < 10.0.16299 + * use millisecond units. */ +#define KEEPALIVE_FACTOR(x) (x *= 1000) +#else +#define KEEPALIVE_FACTOR(x) +#endif + +#if defined(USE_WINSOCK) && !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 " + "%" FMT_SOCKET_T ": errno %d", + sockfd, SOCKERRNO); + } + else { +#if defined(SIO_KEEPALIVE_VALS) /* Windows */ +/* Windows 10, version 1709 (10.0.16299) and later versions */ +#if defined(CURL_WINSOCK_KEEP_SSO) + optval = curlx_sltosi(data->set.tcp_keepidle); + KEEPALIVE_FACTOR(optval); + if(setsockopt(sockfd, IPPROTO_TCP, TCP_KEEPIDLE, + (const char *)&optval, sizeof(optval)) < 0) { + infof(data, "Failed to set TCP_KEEPIDLE on fd " + "%" FMT_SOCKET_T ": errno %d", + sockfd, SOCKERRNO); + } + optval = curlx_sltosi(data->set.tcp_keepintvl); + KEEPALIVE_FACTOR(optval); + if(setsockopt(sockfd, IPPROTO_TCP, TCP_KEEPINTVL, + (const char *)&optval, sizeof(optval)) < 0) { + infof(data, "Failed to set TCP_KEEPINTVL on fd " + "%" FMT_SOCKET_T ": errno %d", + sockfd, SOCKERRNO); + } + optval = curlx_sltosi(data->set.tcp_keepcnt); + if(setsockopt(sockfd, IPPROTO_TCP, TCP_KEEPCNT, + (const char *)&optval, sizeof(optval)) < 0) { + infof(data, "Failed to set TCP_KEEPCNT on fd " + "%" FMT_SOCKET_T ": errno %d", + sockfd, SOCKERRNO); + } +#else /* Windows < 10.0.16299 */ + struct tcp_keepalive vals; + DWORD dummy; + vals.onoff = 1; + optval = curlx_sltosi(data->set.tcp_keepidle); + KEEPALIVE_FACTOR(optval); + vals.keepalivetime = (u_long)optval; + optval = curlx_sltosi(data->set.tcp_keepintvl); + KEEPALIVE_FACTOR(optval); + vals.keepaliveinterval = (u_long)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 " + "%" FMT_SOCKET_T ": errno %d", sockfd, SOCKERRNO); + } +#endif +#else /* !Windows */ +#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 " + "%" FMT_SOCKET_T ": errno %d", + sockfd, SOCKERRNO); + } +#elif defined(TCP_KEEPALIVE) + /* macOS 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 " + "%" FMT_SOCKET_T ": errno %d", + sockfd, SOCKERRNO); + } +#elif defined(TCP_KEEPALIVE_THRESHOLD) + /* Solaris <11.4 style */ + optval = curlx_sltosi(data->set.tcp_keepidle); + KEEPALIVE_FACTOR(optval); + if(setsockopt(sockfd, IPPROTO_TCP, TCP_KEEPALIVE_THRESHOLD, + (void *)&optval, sizeof(optval)) < 0) { + infof(data, "Failed to set TCP_KEEPALIVE_THRESHOLD on fd " + "%" FMT_SOCKET_T ": errno %d", + sockfd, SOCKERRNO); + } +#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 " + "%" FMT_SOCKET_T ": errno %d", + sockfd, SOCKERRNO); + } +#elif defined(TCP_KEEPALIVE_ABORT_THRESHOLD) + /* Solaris <11.4 style */ + /* TCP_KEEPALIVE_ABORT_THRESHOLD should equal to + * TCP_KEEPCNT * TCP_KEEPINTVL on other platforms. + * The default value of TCP_KEEPCNT is 9 on Linux, + * 8 on *BSD/macOS, 5 or 10 on Windows. We use the + * default config for Solaris <11.4 because there is + * no default value for TCP_KEEPCNT on Solaris 11.4. + * + * Note that the consequent probes will not be sent + * at equal intervals on Solaris, but will be sent + * using the exponential backoff algorithm. */ + optval = curlx_sltosi(data->set.tcp_keepcnt) * + curlx_sltosi(data->set.tcp_keepintvl); + KEEPALIVE_FACTOR(optval); + if(setsockopt(sockfd, IPPROTO_TCP, TCP_KEEPALIVE_ABORT_THRESHOLD, + (void *)&optval, sizeof(optval)) < 0) { + infof(data, "Failed to set TCP_KEEPALIVE_ABORT_THRESHOLD on fd " + "%" FMT_SOCKET_T ": errno %d", sockfd, SOCKERRNO); + } +#endif +#ifdef TCP_KEEPCNT + optval = curlx_sltosi(data->set.tcp_keepcnt); + if(setsockopt(sockfd, IPPROTO_TCP, TCP_KEEPCNT, + (void *)&optval, sizeof(optval)) < 0) { + infof(data, "Failed to set TCP_KEEPCNT on fd " + "%" FMT_SOCKET_T ": errno %d", sockfd, SOCKERRNO); + } +#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 = (unsigned int)ai->ai_addrlen; + + if(dest->addrlen > sizeof(struct Curl_sockaddr_storage)) + dest->addrlen = sizeof(struct Curl_sockaddr_storage); + memcpy(&dest->curl_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(USE_IPV6) && defined(HAVE_SOCKADDR_IN6_SIN6_SCOPE_ID) + if(data->conn->scope_id && (addr->family == AF_INET6)) { + struct sockaddr_in6 * const sa6 = (void *)&addr->curl_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 does not 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(CURL_SOCKET_BAD == sock) + return 0; + + 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_sndbuf_init(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 /* USE_WINSOCK */ + +/* + * Curl_parse_interface() + * + * This is used to parse interface argument in the following formats. + * In all the examples, `host` can be an IP address or a hostname. + * + * - can be either an interface name or a host. + * if! - interface name. + * host! - hostname. + * ifhost!! - interface name and hostname. + * + * Parameters: + * + * input [in] - input string. + * len [in] - length of the input string. + * dev [in/out] - address where a pointer to newly allocated memory + * holding the interface-or-host will be stored upon + * completion. + * iface [in/out] - address where a pointer to newly allocated memory + * holding the interface will be stored upon completion. + * host [in/out] - address where a pointer to newly allocated memory + * holding the host will be stored upon completion. + * + * Returns CURLE_OK on success. + */ +CURLcode Curl_parse_interface(const char *input, + char **dev, char **iface, char **host) +{ + static const char if_prefix[] = "if!"; + static const char host_prefix[] = "host!"; + static const char if_host_prefix[] = "ifhost!"; + size_t len; + + DEBUGASSERT(dev); + DEBUGASSERT(iface); + DEBUGASSERT(host); + + len = strlen(input); + if(len > 512) + return CURLE_BAD_FUNCTION_ARGUMENT; + + if(!strncmp(if_prefix, input, strlen(if_prefix))) { + input += strlen(if_prefix); + if(!*input) + return CURLE_BAD_FUNCTION_ARGUMENT; + *iface = Curl_memdup0(input, len - strlen(if_prefix)); + return *iface ? CURLE_OK : CURLE_OUT_OF_MEMORY; + } + else if(!strncmp(host_prefix, input, strlen(host_prefix))) { + input += strlen(host_prefix); + if(!*input) + return CURLE_BAD_FUNCTION_ARGUMENT; + *host = Curl_memdup0(input, len - strlen(host_prefix)); + return *host ? CURLE_OK : CURLE_OUT_OF_MEMORY; + } + else if(!strncmp(if_host_prefix, input, strlen(if_host_prefix))) { + const char *host_part; + input += strlen(if_host_prefix); + len -= strlen(if_host_prefix); + host_part = memchr(input, '!', len); + if(!host_part || !*(host_part + 1)) + return CURLE_BAD_FUNCTION_ARGUMENT; + *iface = Curl_memdup0(input, host_part - input); + if(!*iface) + return CURLE_OUT_OF_MEMORY; + ++host_part; + *host = Curl_memdup0(host_part, len - (host_part - input)); + if(!*host) { + free(*iface); + *iface = NULL; + return CURLE_OUT_OF_MEMORY; + } + return CURLE_OK; + } + + if(!*input) + return CURLE_BAD_FUNCTION_ARGUMENT; + *dev = Curl_memdup0(input, len); + return *dev ? CURLE_OK : CURLE_OUT_OF_MEMORY; +} + +#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 USE_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]; + const char *iface_input = data->set.str[STRING_INTERFACE]; + const char *host_input = data->set.str[STRING_BINDHOST]; + const char *iface = iface_input ? iface_input : dev; + const char *host = host_input ? host_input : dev; + int error; +#ifdef IP_BIND_ADDRESS_NO_PORT + int on = 1; +#endif +#ifndef USE_IPV6 + (void)scope; +#endif + + /************************************************************* + * Select device to bind socket to + *************************************************************/ + if(!iface && !host && !port) + /* no local kind of binding was requested */ + return CURLE_OK; + + memset(&sa, 0, sizeof(struct Curl_sockaddr_storage)); + + if(iface && (strlen(iface) < 255) ) { + char myhost[256] = ""; + int done = 0; /* -1 for error, 1 for address found */ + if2ip_result_t if2ip_result = IF2IP_NOT_FOUND; + + /* interface */ +#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, + iface, (curl_socklen_t)strlen(iface) + 1) == 0) { + /* This is often "errno 1, error: Operation not permitted" if you are + * 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. + */ + if(!host_input) { + infof(data, "socket successfully bound to interface '%s'", iface); + return CURLE_OK; + } + } +#endif + if(!host_input) { + /* Discover IP from input device, then bind to it */ + if2ip_result = Curl_if2ip(af, +#ifdef USE_IPV6 + scope, conn->scope_id, +#endif + iface, myhost, sizeof(myhost)); + } + switch(if2ip_result) { + case IF2IP_NOT_FOUND: + if(iface_input && !host_input) { + /* Do not fall back to treating it as a hostname */ + char buffer[STRERROR_LEN]; + data->state.os_errno = error = SOCKERRNO; + failf(data, "Couldn't bind to interface '%s' with errno %d: %s", + iface, error, Curl_strerror(error, buffer, sizeof(buffer))); + 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: + /* + * We now have the numerical IP address in the 'myhost' buffer + */ + host = myhost; + infof(data, "Local Interface %s is ip %s using address family %i", + iface, host, af); + done = 1; + break; + } + if(!iface_input || host_input) { + /* + * This was not an interface, resolve the name as a hostname + * 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 USE_IPV6 + else if(af == AF_INET6) + conn->ip_version = CURL_IPRESOLVE_V6; +#endif + + rc = Curl_resolv(data, host, 80, FALSE, &h); + if(rc == CURLRESOLV_PENDING) + (void)Curl_resolver_wait_resolv(data, &h); + conn->ip_version = ipver; + + if(h) { + int h_af = h->addr->ai_family; + /* 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", + host, af, myhost, h_af); + Curl_resolv_unlink(data, &h); /* this will NULL, potential free h */ + if(af != h_af) { + /* 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 USE_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. */ + char buffer[STRERROR_LEN]; + data->state.errorbuf = FALSE; + data->state.os_errno = error = SOCKERRNO; + failf(data, "Couldn't bind to '%s' with errno %d: %s", + host, error, Curl_strerror(error, buffer, sizeof(buffer))); + return CURLE_INTERFACE_FAILED; + } + } + else { + /* no device was given, prepare sa to match af's needs */ +#ifdef USE_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 */ + 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 USE_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 do not have Rational Quantify, but the hint from his post was + * ntdll::NtRemoveIoCompletion(). I would 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 Windows CE versions do not support SO_ERROR */ + if(WSAENOPROTOOPT == err) { + SET_SOCKERRNO(0); + err = 0; + } +#endif +#if defined(EBADIOCTL) && defined(__minix) + /* Minix 3.1.x does not 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 was not 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 ip_quadruple ip; /* The IP quadruple 2x(addr+port) */ + 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 */ +#ifdef USE_WINSOCK + struct curltime last_sndbuf_query_at; /* when SO_SNDBUF last queried */ + ULONG sndbuf_size; /* the last set SO_SNDBUF size */ +#endif + 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(listening); /* socket is listening */ + BIT(accepted); /* socket was accepted, not connected */ + BIT(sock_connected); /* socket is "connected", e.g. in UDP */ + BIT(active); +}; + +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); +#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 +} + +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(%" FMT_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; + ctx->active = FALSE; + memset(&ctx->started_at, 0, sizeof(ctx->started_at)); + memset(&ctx->connected_at, 0, sizeof(ctx->connected_at)); + } + + cf->connected = FALSE; +} + +static CURLcode cf_socket_shutdown(struct Curl_cfilter *cf, + struct Curl_easy *data, + bool *done) +{ + if(cf->connected) { + struct cf_socket_ctx *ctx = cf->ctx; + + CURL_TRC_CF(data, cf, "cf_socket_shutdown(%" FMT_SOCKET_T ")", ctx->sock); + /* On TCP, and when the socket looks well and non-blocking mode + * can be enabled, receive dangling bytes before close to avoid + * entering RST states unnecessarily. */ + if(ctx->sock != CURL_SOCKET_BAD && + ctx->transport == TRNSPRT_TCP && + (curlx_nonblock(ctx->sock, TRUE) >= 0)) { + unsigned char buf[1024]; + (void)sread(ctx->sock, buf, sizeof(buf)); + } + } + *done = TRUE; + return CURLE_OK; +} + +static void cf_socket_destroy(struct Curl_cfilter *cf, struct Curl_easy *data) +{ + struct cf_socket_ctx *ctx = cf->ctx; + + cf_socket_close(cf, data); + CURL_TRC_CF(data, cf, "destroy"); + 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((ctx->sock != CURL_SOCKET_BAD) && + !(data->conn->handler->protocol & CURLPROTO_TFTP)) { + /* TFTP does not connect, so it cannot get the IP like this */ + + char buffer[STRERROR_LEN]; + 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->ip.local_ip, &ctx->ip.local_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->ip.local_ip[0] = 0; + ctx->ip.local_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.curl_sa_addr, + (curl_socklen_t)ctx->addr.addrlen, + ctx->ip.remote_ip, &ctx->ip.remote_port)) { + char buffer[STRERROR_LEN]; + + ctx->error = errno; + /* malformed address or bug in inet_ntop, try next address */ + failf(data, "curl_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(); +#ifdef SOCK_NONBLOCK + /* Do not tuck SOCK_NONBLOCK into socktype when opensocket callback is set + * because we would not know how socketype is about to be used in the + * callback, SOCK_NONBLOCK might get factored out before calling socket(). + */ + if(!data->set.fopensocket) + ctx->addr.socktype |= SOCK_NONBLOCK; +#endif + result = socket_open(data, &ctx->addr, &ctx->sock); +#ifdef SOCK_NONBLOCK + /* Restore the socktype after the socket is created. */ + if(!data->set.fopensocket) + ctx->addr.socktype &= ~SOCK_NONBLOCK; +#endif + if(result) + goto out; + + result = set_remote_ip(cf, data); + if(result) + goto out; + +#ifdef USE_IPV6 + if(ctx->addr.family == AF_INET6) { + set_ipv6_v6only(ctx->sock, 0); + infof(data, " Trying [%s]:%d...", ctx->ip.remote_ip, ctx->ip.remote_port); + } + else +#endif + infof(data, " Trying %s:%d...", ctx->ip.remote_ip, ctx->ip.remote_port); + +#ifdef USE_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_sndbuf_init(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 USE_IPV6 + || ctx->addr.family == AF_INET6 +#endif + ) { + result = bindlocal(data, cf->conn, ctx->sock, ctx->addr.family, + Curl_ipv6_scope(&ctx->addr.curl_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 + +#ifndef SOCK_NONBLOCK + /* Set socket non-blocking, must be a non-blocking socket for + * a non-blocking connect. */ + error = curlx_nonblock(ctx->sock, TRUE); + if(error < 0) { + result = CURLE_UNSUPPORTED_PROTOCOL; + ctx->error = SOCKERRNO; + goto out; + } +#else + if(data->set.fopensocket) { + /* Set socket non-blocking, must be a non-blocking socket for + * a non-blocking connect. */ + error = curlx_nonblock(ctx->sock, TRUE); + if(error < 0) { + result = CURLE_UNSUPPORTED_PROTOCOL; + ctx->error = SOCKERRNO; + goto out; + } + } +#endif + ctx->sock_connected = (ctx->addr.socktype != SOCK_DGRAM); +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=%" FMT_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.curl_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.curl_sa_addr, ctx->addr.addrlen); + } +# else + rc = connect(ctx->sock, &ctx->addr.curl_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 %" FMT_SOCKET_T, + ctx->sock); + + rc = connect(ctx->sock, &ctx->addr.curl_sa_addr, ctx->addr.addrlen); +#elif defined(MSG_FASTOPEN) /* old Linux */ + if(cf->conn->given->flags & PROTOPT_SSL) + rc = connect(ctx->sock, &ctx->addr.curl_sa_addr, ctx->addr.addrlen); + else + rc = 0; /* Do nothing */ +#endif + } + else { + rc = connect(ctx->sock, &ctx->addr.curl_sa_addr, + (curl_socklen_t)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->ip.local_ip, ctx->ip.local_port); + if(-1 == rc) { + result = socket_connect_result(data, ctx->ip.remote_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->ip.remote_ip, ctx->ip.remote_port, + ctx->ip.local_ip, ctx->ip.local_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) +{ + struct cf_socket_ctx *ctx = cf->ctx; + (void)data; + *phost = cf->conn->host.name; + *pdisplay_host = cf->conn->host.dispname; + *pport = ctx->ip.remote_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) { + /* A listening socket filter needs to be connected before the accept + * for some weird FTP interaction. This should be rewritten, so that + * FTP no longer does the socket checks and accept calls and delegates + * all that to the filter. TODO. */ + if(ctx->listening) { + Curl_pollset_set_in_only(data, ps, ctx->sock); + CURL_TRC_CF(data, cf, "adjust_pollset, listening, POLLIN fd=%" + FMT_SOCKET_T, ctx->sock); + } + else if(!cf->connected) { + Curl_pollset_set_out_only(data, ps, ctx->sock); + CURL_TRC_CF(data, cf, "adjust_pollset, !connected, POLLOUT fd=%" + FMT_SOCKET_T, ctx->sock); + } + else if(!ctx->active) { + Curl_pollset_add_in(data, ps, ctx->sock); + CURL_TRC_CF(data, cf, "adjust_pollset, !active, POLLIN fd=%" + FMT_SOCKET_T, ctx->sock); + } + } +} + +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; + readable = SOCKET_READABLE(ctx->sock, 0); + return (readable > 0 && (readable & CURL_CSELECT_IN)); +} + +#ifdef USE_WINSOCK + +#ifndef SIO_IDEAL_SEND_BACKLOG_QUERY +#define SIO_IDEAL_SEND_BACKLOG_QUERY 0x4004747B +#endif + +static void win_update_sndbuf_size(struct cf_socket_ctx *ctx) +{ + ULONG ideal; + DWORD ideallen; + struct curltime n = Curl_now(); + + if(Curl_timediff(n, ctx->last_sndbuf_query_at) > 1000) { + if(!WSAIoctl(ctx->sock, SIO_IDEAL_SEND_BACKLOG_QUERY, 0, 0, + &ideal, sizeof(ideal), &ideallen, 0, 0) && + ideal != ctx->sndbuf_size && + !setsockopt(ctx->sock, SOL_SOCKET, SO_SNDBUF, + (const char *)&ideal, sizeof(ideal))) { + ctx->sndbuf_size = ideal; + } + ctx->last_sndbuf_query_at = n; + } +} + +#endif /* USE_WINSOCK */ + +static ssize_t cf_socket_send(struct Curl_cfilter *cf, struct Curl_easy *data, + const void *buf, size_t len, bool eos, + CURLcode *err) +{ + struct cf_socket_ctx *ctx = cf->ctx; + curl_socket_t fdsave; + ssize_t nwritten; + size_t orig_len = len; + + (void)eos; /* unused */ + *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 = 0; + Curl_rand_bytes(data, FALSE, &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->curl_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; + } + } + +#if defined(USE_WINSOCK) + if(!*err) + win_update_sndbuf_size(ctx); +#endif + + 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; + ssize_t nread; + + *err = CURLE_OK; + +#ifdef DEBUGBUILD + /* simulate network blocking/partial reads */ + if(cf->cft != &Curl_cft_udp && ctx->rblock_percent > 0) { + unsigned char c = 0; + Curl_rand(data, &c, 1); + if(c >= ((100-ctx->rblock_percent)*256/100)) { + CURL_TRC_CF(data, cf, "recv(len=%zu) SIMULATE EWOULDBLOCK", len); + *err = CURLE_AGAIN; + return -1; + } + } + 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 + + *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; + } + else { + char buffer[STRERROR_LEN]; + + failf(data, "Recv failure: %s", + Curl_strerror(sockerr, buffer, sizeof(buffer))); + data->state.os_errno = sockerr; + *err = CURLE_RECV_ERROR; + } + } + + 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; + } + return nread; +} + +static void cf_socket_update_data(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + /* Update the IP info held in the transfer, if we have that. */ + if(cf->connected && (cf->sockindex == FIRSTSOCKET)) { + struct cf_socket_ctx *ctx = cf->ctx; + data->info.primary = ctx->ip; + /* not sure if this is redundant... */ + data->info.conn_remote_port = cf->conn->remote_port; + } +} + +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; + set_local_ip(cf, data); + if(cf->sockindex == FIRSTSOCKET) { + cf->conn->primary = ctx->ip; + cf->conn->remote_addr = &ctx->addr; + #ifdef USE_IPV6 + cf->conn->bits.ipv6 = (ctx->addr.family == AF_INET6) ? TRUE : FALSE; + #endif + } + else { + cf->conn->secondary = ctx->ip; + } + 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); + cf_socket_update_data(cf, data); + break; + case CF_CTRL_DATA_SETUP: + cf_socket_update_data(cf, data); + 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; + } + case CF_QUERY_IP_INFO: +#ifdef USE_IPV6 + *pres1 = (ctx->addr.family == AF_INET6) ? TRUE : FALSE; +#else + *pres1 = FALSE; +#endif + *(struct ip_quadruple *)pres2 = ctx->ip; + 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_shutdown, + 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; + int one = 1; + + (void)one; + + /* QUIC needs a connected socket, nonblocking */ + DEBUGASSERT(ctx->sock != CURL_SOCKET_BAD); + + rc = connect(ctx->sock, &ctx->addr.curl_sa_addr, + (curl_socklen_t)ctx->addr.addrlen); + if(-1 == rc) { + return socket_connect_result(data, ctx->ip.remote_ip, SOCKERRNO); + } + ctx->sock_connected = TRUE; + set_local_ip(cf, data); + CURL_TRC_CF(data, cf, "%s socket %" FMT_SOCKET_T + " connected: [%s:%d] -> [%s:%d]", + (ctx->transport == TRNSPRT_QUIC) ? "QUIC" : "UDP", + ctx->sock, ctx->ip.local_ip, ctx->ip.local_port, + ctx->ip.remote_ip, ctx->ip.remote_port); + + /* Currently, cf->ctx->sock is always non-blocking because the only + * caller to cf_udp_setup_quic() is cf_udp_connect() that passes the + * non-blocking socket created by cf_socket_open() to it. Thus, we + * do not need to call curlx_nonblock() in cf_udp_setup_quic() anymore. + */ + 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 + } + +#if defined(__linux__) && defined(UDP_GRO) && \ + (defined(HAVE_SENDMMSG) || defined(HAVE_SENDMSG)) && \ + ((defined(USE_NGTCP2) && defined(USE_NGHTTP3)) || defined(USE_QUICHE)) + (void)setsockopt(ctx->sock, IPPROTO_UDP, UDP_GRO, &one, + (socklen_t)sizeof(one)); +#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=%" + FMT_SOCKET_T " (%s:%d)", + ctx->sock, ctx->ip.local_ip, ctx->ip.local_port); + } + else { + CURL_TRC_CF(data, cf, "cf_udp_connect(), opened socket=%" + FMT_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_shutdown, + 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_shutdown, + 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_shutdown, + 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->listening = TRUE; + 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(%" FMT_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->ip.remote_ip[0] = 0; + ctx->ip.remote_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->ip.remote_ip, &ctx->ip.remote_port)) { + failf(data, "ssrem inet_ntop() failed with errno %d: %s", + errno, Curl_strerror(errno, buffer, sizeof(buffer))); + return; + } +#else + ctx->ip.remote_ip[0] = 0; + ctx->ip.remote_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; + DEBUGASSERT(ctx->listening); + /* discard the listen socket */ + socket_close(data, conn, TRUE, ctx->sock); + ctx->listening = FALSE; + 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=%" FMT_SOCKET_T + ", remote=%s port=%d)", + ctx->sock, ctx->ip.remote_ip, ctx->ip.remote_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, + struct ip_quadruple *pip) +{ + (void)data; + 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(pip) + *pip = ctx->ip; + return CURLE_OK; + } + return CURLE_FAILED_INIT; +} diff --git a/contrib/curl/lib/cf-socket.h b/contrib/curl/lib/cf-socket.h new file mode 100644 index 0000000000..6374e7c92b --- /dev/null +++ b/contrib/curl/lib/cf-socket.h @@ -0,0 +1,176 @@ +#ifndef HEADER_CURL_CF_SOCKET_H +#define HEADER_CURL_CF_SOCKET_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ +#include "curl_setup.h" + +#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; +struct ip_quadruple; + +/* + * 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 curl_sa_addr _sa_ex_u.addr + +/* + * Parse interface option, and return the interface name and the host part. +*/ +CURLcode Curl_parse_interface(const char *input, + char **dev, char **iface, char **host); + +/* + * 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_sndbuf_init(curl_socket_t sockfd); +#else +#define Curl_sndbuf_init(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 pip pointer to get IP quadruple 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, + struct ip_quadruple *pip); + +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/contrib/curl/lib/cfilters.c b/contrib/curl/lib/cfilters.c new file mode 100644 index 0000000000..daa1877ab2 --- /dev/null +++ b/contrib/curl/lib/cfilters.c @@ -0,0 +1,1021 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#include "urldata.h" +#include "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 + +static void cf_cntrl_update_info(struct Curl_easy *data, + struct connectdata *conn); + +#ifdef UNITTESTS +/* 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 + +CURLcode Curl_cf_def_shutdown(struct Curl_cfilter *cf, + struct Curl_easy *data, bool *done) +{ + (void)cf; + (void)data; + *done = TRUE; + return CURLE_OK; +} + +static void conn_report_connect_stats(struct Curl_easy *data, + struct connectdata *conn); + +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->primary.remote_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, bool eos, + CURLcode *err) +{ + return cf->next ? + cf->next->cft->do_send(cf->next, data, buf, len, eos, 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); + } + Curl_shutdown_clear(data, index); +} + +CURLcode Curl_conn_shutdown(struct Curl_easy *data, int sockindex, bool *done) +{ + struct Curl_cfilter *cf; + CURLcode result = CURLE_OK; + timediff_t timeout_ms; + struct curltime now; + + DEBUGASSERT(data->conn); + /* Get the first connected filter that is not shut down already. */ + cf = data->conn->cfilter[sockindex]; + while(cf && (!cf->connected || cf->shutdown)) + cf = cf->next; + + if(!cf) { + *done = TRUE; + return CURLE_OK; + } + + *done = FALSE; + now = Curl_now(); + if(!Curl_shutdown_started(data, sockindex)) { + DEBUGF(infof(data, "shutdown start on%s connection", + sockindex ? " secondary" : "")); + Curl_shutdown_start(data, sockindex, &now); + } + else { + timeout_ms = Curl_shutdown_timeleft(data->conn, sockindex, &now); + if(timeout_ms < 0) { + /* info message, since this might be regarded as acceptable */ + infof(data, "shutdown timeout"); + return CURLE_OPERATION_TIMEDOUT; + } + } + + while(cf) { + if(!cf->shutdown) { + bool cfdone = FALSE; + result = cf->cft->do_shutdown(cf, data, &cfdone); + if(result) { + CURL_TRC_CF(data, cf, "shut down failed with %d", result); + return result; + } + else if(!cfdone) { + CURL_TRC_CF(data, cf, "shut down not done yet"); + return CURLE_OK; + } + CURL_TRC_CF(data, cf, "shut down successfully"); + cf->shutdown = TRUE; + } + cf = cf->next; + } + *done = (!result); + return result; +} + +ssize_t Curl_cf_recv(struct Curl_easy *data, int num, char *buf, + size_t len, CURLcode *code) +{ + struct Curl_cfilter *cf; + + DEBUGASSERT(data); + DEBUGASSERT(data->conn); + *code = CURLE_OK; + cf = data->conn->cfilter[num]; + while(cf && !cf->connected) { + cf = cf->next; + } + if(cf) { + ssize_t nread = cf->cft->do_recv(cf, data, buf, len, code); + DEBUGASSERT(nread >= 0 || *code); + DEBUGASSERT(nread < 0 || !*code); + return nread; + } + failf(data, "recv: no filter connected"); + *code = CURLE_FAILED_INIT; + return -1; +} + +ssize_t Curl_cf_send(struct Curl_easy *data, int num, + const void *mem, size_t len, bool eos, + CURLcode *code) +{ + struct Curl_cfilter *cf; + + DEBUGASSERT(data); + DEBUGASSERT(data->conn); + *code = CURLE_OK; + cf = data->conn->cfilter[num]; + while(cf && !cf->connected) { + cf = cf->next; + } + if(cf) { + ssize_t nwritten = cf->cft->do_send(cf, data, mem, len, eos, code); + DEBUGASSERT(nwritten >= 0 || *code); + DEBUGASSERT(nwritten < 0 || !*code || !len); + return nwritten; + } + 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, bool eos, + CURLcode *err) +{ + if(cf) + return cf->cft->do_send(cf, data, buf, len, eos, 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) { + *done = FALSE; + return CURLE_FAILED_INIT; + } + + *done = cf->connected; + if(!*done) { + if(Curl_conn_needs_flush(data, sockindex)) { + DEBUGF(infof(data, "Curl_conn_connect(index=%d), flush", sockindex)); + result = Curl_conn_flush(data, sockindex); + if(result && (result != CURLE_AGAIN)) + return result; + } + + result = cf->cft->do_connect(cf, data, blocking, done); + if(!result && *done) { + /* Now that the complete filter chain is connected, let all filters + * persist information at the connection. E.g. cf-socket sets the + * socket and ip related information. */ + cf_cntrl_update_info(data, data->conn); + conn_report_connect_stats(data, data->conn); + data->conn->keepalive = Curl_now(); + Curl_verboseconnect(data, data->conn, sockindex); + } + 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; +} + +bool Curl_conn_cf_needs_flush(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + CURLcode result; + int pending = FALSE; + result = cf ? cf->cft->query(cf, data, CF_QUERY_NEED_FLUSH, + &pending, NULL) : CURLE_UNKNOWN_OPTION; + return (result || pending == FALSE) ? FALSE : TRUE; +} + +bool Curl_conn_needs_flush(struct Curl_easy *data, int sockindex) +{ + return Curl_conn_cf_needs_flush(data->conn->cfilter[sockindex], data); +} + +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; + /* Skip all filters that have already shut down */ + while(cf && cf->shutdown) + cf = cf->next; + /* From there on, give all filters a chance to adjust the pollset. + * Lower filters are called later, so they may override */ + while(cf) { + 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); + } +} + +int Curl_conn_cf_poll(struct Curl_cfilter *cf, + struct Curl_easy *data, + timediff_t timeout_ms) +{ + struct easy_pollset ps; + struct pollfd pfds[MAX_SOCKSPEREASYHANDLE]; + unsigned int i, npfds = 0; + + DEBUGASSERT(cf); + DEBUGASSERT(data); + DEBUGASSERT(data->conn); + memset(&ps, 0, sizeof(ps)); + memset(pfds, 0, sizeof(pfds)); + + Curl_conn_cf_adjust_pollset(cf, data, &ps); + DEBUGASSERT(ps.num <= MAX_SOCKSPEREASYHANDLE); + for(i = 0; i < ps.num; ++i) { + short events = 0; + if(ps.actions[i] & CURL_POLL_IN) { + events |= POLLIN; + } + if(ps.actions[i] & CURL_POLL_OUT) { + events |= POLLOUT; + } + if(events) { + pfds[npfds].fd = ps.sockets[i]; + pfds[npfds].events = events; + ++npfds; + } + } + + if(!npfds) + DEBUGF(infof(data, "no sockets to poll!")); + return Curl_poll(pfds, npfds, timeout_ms); +} + +void Curl_conn_get_host(struct Curl_easy *data, int sockindex, + const char **phost, const char **pdisplay_host, + int *pport) +{ + 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; +} + +CURLcode Curl_conn_cf_get_ip_info(struct Curl_cfilter *cf, + struct Curl_easy *data, + int *is_ipv6, struct ip_quadruple *ipquad) +{ + if(cf) + return cf->cft->query(cf, data, CF_QUERY_IP_INFO, is_ipv6, ipquad); + return CURLE_UNKNOWN_OPTION; +} + +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); +} + + +CURLcode Curl_conn_flush(struct Curl_easy *data, int sockindex) +{ + return Curl_conn_cf_cntrl(data->conn->cfilter[sockindex], data, FALSE, + CF_CTRL_FLUSH, 0, NULL); +} + +/** + * Notify connection filters that the transfer represented by `data` + * is done 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); +} + +static void cf_cntrl_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; +} + +int Curl_conn_get_stream_error(struct Curl_easy *data, + struct connectdata *conn, + int sockindex) +{ + CURLcode result; + int n = 0; + + struct Curl_cfilter *cf = conn->cfilter[sockindex]; + result = cf ? cf->cft->query(cf, data, CF_QUERY_STREAM_ERROR, + &n, NULL) : CURLE_UNKNOWN_OPTION; + return (result || n < 0) ? 0 : n; +} + +int Curl_conn_sockindex(struct Curl_easy *data, curl_socket_t sockfd) +{ + if(data && data->conn && + sockfd != CURL_SOCKET_BAD && sockfd == data->conn->sock[SECONDARYSOCKET]) + return SECONDARYSOCKET; + return FIRSTSOCKET; +} + +CURLcode Curl_conn_recv(struct Curl_easy *data, int sockindex, + char *buf, size_t blen, ssize_t *n) +{ + CURLcode result = CURLE_OK; + ssize_t nread; + + DEBUGASSERT(data->conn); + nread = data->conn->recv[sockindex](data, sockindex, buf, blen, &result); + DEBUGASSERT(nread >= 0 || result); + DEBUGASSERT(nread < 0 || !result); + *n = (nread >= 0) ? (size_t)nread : 0; + return result; +} + +CURLcode Curl_conn_send(struct Curl_easy *data, int sockindex, + const void *buf, size_t blen, bool eos, + size_t *pnwritten) +{ + size_t write_len = blen; + 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 DEBUGBUILD + { + /* 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) + write_len = CURLMIN(write_len, altsize); + } + } +#endif + if(write_len != blen) + eos = FALSE; + nwritten = conn->send[sockindex](data, sockindex, buf, write_len, eos, + &result); + DEBUGASSERT((nwritten >= 0) || result); + *pnwritten = (nwritten < 0) ? 0 : (size_t)nwritten; + return result; +} + +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, + 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/contrib/curl/lib/cfilters.h b/contrib/curl/lib/cfilters.h new file mode 100644 index 0000000000..af696f52a5 --- /dev/null +++ b/contrib/curl/lib/cfilters.h @@ -0,0 +1,696 @@ +#ifndef HEADER_CURL_CFILTERS_H +#define HEADER_CURL_CFILTERS_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "timediff.h" + +struct Curl_cfilter; +struct Curl_easy; +struct Curl_dns_entry; +struct connectdata; +struct ip_quadruple; + +/* 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); + +/* Callback to close the connection immediately. */ +typedef void Curl_cft_close(struct Curl_cfilter *cf, + struct Curl_easy *data); + +/* Callback to close the connection filter gracefully, non-blocking. + * Implementations MUST NOT chain calls to cf->next. + */ +typedef CURLcode Curl_cft_shutdown(struct Curl_cfilter *cf, + struct Curl_easy *data, + bool *done); + +typedef CURLcode Curl_cft_connect(struct Curl_cfilter *cf, + struct Curl_easy *data, + bool blocking, bool *done); + +/* 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. An 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 */ + bool eos, /* last chunk */ + 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 */ +#define CF_CTRL_FLUSH (256+2) /* 0 NULL first fail */ + +/** + * 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 + * - CF_QUERY_NEED_FLUSH: TRUE iff any of the filters have unsent data + * - CF_QUERY_IP_INFO: res1 says if connection used IPv6, res2 is the + * ip quadruple + */ +/* 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 */ +#define CF_QUERY_STREAM_ERROR 6 /* error code - */ +#define CF_QUERY_NEED_FLUSH 7 /* TRUE/FALSE - */ +#define CF_QUERY_IP_INFO 8 /* TRUE/FALSE struct ip_quadruple */ + +/** + * 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 + * CF_TYPE_PROXY provides proxying + */ +#define CF_TYPE_IP_CONNECT (1 << 0) +#define CF_TYPE_SSL (1 << 1) +#define CF_TYPE_MULTIPLEX (1 << 2) +#define CF_TYPE_PROXY (1 << 3) + +/* A connection filter type, e.g. specific implementation. */ +struct Curl_cftype { + 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_shutdown *do_shutdown; /* shutdown conn */ + Curl_cft_get_host *get_host; /* host filter talks to */ + Curl_cft_adjust_pollset *adjust_pollset; /* adjust transfer poll set */ + Curl_cft_data_pending *has_data_pending;/* conn has data pending */ + 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 */ + BIT(shutdown); /* != 0 iff this filter has shut down */ +}; + +/* 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, bool eos, + 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); +CURLcode Curl_cf_def_shutdown(struct Curl_cfilter *cf, + struct Curl_easy *data, bool *done); + +/** + * Create a new filter instance, unattached to the filter chain. + * 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, bool eos, + 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); + +CURLcode Curl_conn_cf_get_ip_info(struct Curl_cfilter *cf, + struct Curl_easy *data, + int *is_ipv6, struct ip_quadruple *ipquad); + +bool Curl_conn_cf_needs_flush(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); + +/** + * Shutdown the connection at `sockindex` non-blocking, using timeout + * from `data->set.shutdowntimeout`, default DEFAULT_SHUTDOWN_TIMEOUT_MS. + * Will return CURLE_OK and *done == FALSE if not finished. + */ +CURLcode Curl_conn_shutdown(struct Curl_easy *data, int sockindex, bool *done); + +/** + * Return if data is pending in some connection filter at chain + * `sockindex` for connection `data->conn`. + */ +bool Curl_conn_data_pending(struct Curl_easy *data, + int sockindex); + +/** + * Return TRUE if any of the connection filters at chain `sockindex` + * have data still to send. + */ +bool Curl_conn_needs_flush(struct Curl_easy *data, int sockindex); + +/** + * Flush any pending data on the connection filters at chain `sockindex`. + */ +CURLcode Curl_conn_flush(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); + +/** + * Curl_poll() the filter chain at `cf` with timeout `timeout_ms`. + * Returns 0 on timeout, negative on error or number of sockets + * with requested poll events. + */ +int Curl_conn_cf_poll(struct Curl_cfilter *cf, + struct Curl_easy *data, + timediff_t timeout_ms); + +/** + * Receive data through the filter chain at `sockindex` for connection + * `data->conn`. Copy at most `len` bytes into `buf`. Return the + * actual number of bytes copied or a negative value on error. + * The error code is placed into `*code`. + */ +ssize_t Curl_cf_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_cf_send(struct Curl_easy *data, int sockindex, + const void *buf, size_t len, bool eos, 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 done 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); + +/** + * 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); + +#ifdef UNITTESTS +void Curl_cf_def_close(struct Curl_cfilter *cf, struct Curl_easy *data); +#endif +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); + +/** + * Get the underlying error code for a transfer stream or 0 if not known. + */ +int Curl_conn_get_stream_error(struct Curl_easy *data, + struct connectdata *conn, + int sockindex); + +/** + * Get the index of the given socket in the connection's sockets. + * Useful in calling `Curl_conn_send()/Curl_conn_recv()` with the + * correct socket index. + */ +int Curl_conn_sockindex(struct Curl_easy *data, curl_socket_t sockfd); + +/* + * Receive data on the connection, using FIRSTSOCKET/SECONDARYSOCKET. + * Will return CURLE_AGAIN iff blocked on receiving. + */ +CURLcode Curl_conn_recv(struct Curl_easy *data, int sockindex, + char *buf, size_t buffersize, + ssize_t *pnread); + +/* + * Send data on the connection, using FIRSTSOCKET/SECONDARYSOCKET. + * Will return CURLE_AGAIN iff blocked on sending. + */ +CURLcode Curl_conn_send(struct Curl_easy *data, int sockindex, + const void *buf, size_t blen, bool eos, + size_t *pnwritten); + + +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, + 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/contrib/curl/lib/checksrc.pl b/contrib/curl/lib/checksrc.pl deleted file mode 100644 index 7857bbfc78..0000000000 --- a/contrib/curl/lib/checksrc.pl +++ /dev/null @@ -1,552 +0,0 @@ -#!/usr/bin/perl -#*************************************************************************** -# _ _ ____ _ -# Project ___| | | | _ \| | -# / __| | | | |_) | | -# | (__| |_| | _ <| |___ -# \___|\___/|_| \_\_____| -# -# Copyright (C) 2011 - 2016, Daniel Stenberg, , et al. -# -# This software is licensed as described in the file COPYING, which -# you should have received as part of this distribution. The terms -# are also available at https://curl.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. -# -########################################################################### - -my $max_column = 79; -my $indent = 2; - -my $warnings; -my $errors; -my $supressed; # whitelisted problems -my $file; -my $dir="."; -my $wlist; -my $windows_os = $^O eq 'MSWin32' || $^O eq 'msys' || $^O eq 'cygwin'; -my $verbose; -my %whitelist; - -my %warnings = ( - 'LONGLINE' => "Line longer than $max_column", - 'TABS' => 'TAB characters not allowed', - 'TRAILINGSPACE' => 'Trailing white space on the line', - 'CPPCOMMENTS' => '// comment detected', - 'SPACEBEFOREPAREN' => 'space before an open parenthesis', - 'SPACEAFTERPAREN' => 'space after open parenthesis', - 'SPACEBEFORECLOSE' => 'space before a close parenthesis', - 'SPACEBEFORECOMMA' => 'space before a comma', - 'RETURNNOSPACE' => 'return without space', - 'COMMANOSPACE' => 'comma without following space', - 'BRACEELSE' => '} else on the same line', - 'PARENBRACE' => '){ without sufficient space', - 'SPACESEMILCOLON' => 'space before semicolon', - 'BANNEDFUNC' => 'a banned function was used', - 'FOPENMODE' => 'fopen needs a macro for the mode string', - 'BRACEPOS' => 'wrong position for an open brace', - 'INDENTATION' => 'wrong start column for code', - 'COPYRIGHT' => 'file missing a copyright statement', - 'BADCOMMAND' => 'bad !checksrc! instruction', - 'UNUSEDIGNORE' => 'a warning ignore was not used', - 'OPENCOMMENT' => 'file ended with a /* comment still "open"', - 'ASTERISKSPACE' => 'pointer declared with space after asterisk', - 'ASTERISKNOSPACE' => 'pointer declared without space before asterisk', - 'ASSIGNWITHINCONDITION' => 'assignment within conditional expression' - ); - -sub readwhitelist { - open(W, "<$dir/checksrc.whitelist"); - my @all=; - for(@all) { - $windows_os ? $_ =~ s/\r?\n$// : chomp; - $whitelist{$_}=1; - } - close(W); -} - -sub checkwarn { - my ($name, $num, $col, $file, $line, $msg, $error) = @_; - - my $w=$error?"error":"warning"; - my $nowarn=0; - - #if(!$warnings{$name}) { - # print STDERR "Dev! there's no description for $name!\n"; - #} - - # checksrc.whitelist - if($whitelist{$line}) { - $nowarn = 1; - } - # !checksrc! controlled - elsif($ignore{$name}) { - $ignore{$name}--; - $ignore_used{$name}++; - $nowarn = 1; - if(!$ignore{$name}) { - # reached zero, enable again - enable_warn($name, $line, $file, $l); - } - } - - if($nowarn) { - $supressed++; - if($w) { - $swarnings++; - } - else { - $serrors++; - } - return; - } - - if($w) { - $warnings++; - } - else { - $errors++; - } - - $col++; - print "$file:$num:$col: $w: $msg ($name)\n"; - print " $line\n"; - - if($col < 80) { - my $pref = (' ' x $col); - print "${pref}^\n"; - } -} - -$file = shift @ARGV; - -while(1) { - - if($file =~ /-D(.*)/) { - $dir = $1; - $file = shift @ARGV; - next; - } - elsif($file =~ /-W(.*)/) { - $wlist .= " $1 "; - $file = shift @ARGV; - next; - } - elsif($file =~ /^(-h|--help)/) { - undef $file; - last; - } - - last; -} - -if(!$file) { - print "checksrc.pl [option] [file2] ...\n"; - print " Options:\n"; - print " -D[DIR] Directory to prepend file names\n"; - print " -h Show help output\n"; - print " -W[file] Whitelist the given file - ignore all its flaws\n"; - print "\nDetects and warns for these problems:\n"; - for(sort keys %warnings) { - printf (" %-18s: %s\n", $_, $warnings{$_}); - } - exit; -} - -readwhitelist(); - -do { - if("$wlist" !~ / $file /) { - my $fullname = $file; - $fullname = "$dir/$file" if ($fullname !~ '^\.?\.?/'); - scanfile($fullname); - } - $file = shift @ARGV; - -} while($file); - -sub checksrc_clear { - undef %ignore; - undef %ignore_set; - undef @ignore_line; -} - -sub checksrc_endoffile { - my ($file) = @_; - for(keys %ignore_set) { - if($ignore_set{$_} && !$ignore_used{$_}) { - checkwarn("UNUSEDIGNORE", $ignore_set{$_}, - length($_)+11, $file, - $ignore_line[$ignore_set{$_}], - "Unused ignore: $_"); - } - } -} - -sub enable_warn { - my ($what, $line, $file, $l) = @_; - - # switch it back on, but warn if not triggered! - if(!$ignore_used{$what}) { - checkwarn("UNUSEDIGNORE", - $line, length($what) + 11, $file, $l, - "No warning was inhibited!"); - } - $ignore_set{$what}=0; - $ignore_used{$what}=0; - $ignore{$what}=0; -} -sub checksrc { - my ($cmd, $line, $file, $l) = @_; - if($cmd =~ / *([^ ]*) *(.*)/) { - my ($enable, $what) = ($1, $2); - $what =~ s: *\*/$::; # cut off end of C comment - # print "ENABLE $enable WHAT $what\n"; - if($enable eq "disable") { - my ($warn, $scope)=($1, $2); - if($what =~ /([^ ]*) +(.*)/) { - ($warn, $scope)=($1, $2); - } - else { - $warn = $what; - $scope = 1; - } - # print "IGNORE $warn for SCOPE $scope\n"; - if($scope eq "all") { - $scope=999999; - } - - if($ignore_set{$warn}) { - checkwarn("BADCOMMAND", - $line, 0, $file, $l, - "$warn already disabled from line $ignore_set{$warn}"); - } - else { - $ignore{$warn}=$scope; - $ignore_set{$warn}=$line; - $ignore_line[$line]=$l; - } - } - elsif($enable eq "enable") { - enable_warn($what, $line, $file, $l); - } - else { - checkwarn("BADCOMMAND", - $line, 0, $file, $l, - "Illegal !checksrc! command"); - } - } -} - -sub nostrings { - my ($str) = @_; - $str =~ s/\".*\"//g; - return $str; -} - -sub scanfile { - my ($file) = @_; - - my $line = 1; - my $prevl; - my $l; - open(R, "<$file") || die "failed to open $file"; - - my $incomment=0; - my $copyright=0; - checksrc_clear(); # for file based ignores - - while() { - $windows_os ? $_ =~ s/\r?\n$// : chomp; - my $l = $_; - my $ol = $l; # keep the unmodified line for error reporting - my $column = 0; - - # check for !checksrc! commands - if($l =~ /\!checksrc\! (.*)/) { - my $cmd = $1; - checksrc($cmd, $line, $file, $l) - } - - # check for a copyright statement - if(!$copyright && ($l =~ /copyright .* \d\d\d\d/i)) { - $copyright=1; - } - - # detect long lines - if(length($l) > $max_column) { - checkwarn("LONGLINE", $line, length($l), $file, $l, - "Longer than $max_column columns"); - } - # detect TAB characters - if($l =~ /^(.*)\t/) { - checkwarn("TABS", - $line, length($1), $file, $l, "Contains TAB character", 1); - } - # detect trailing white space - if($l =~ /^(.*)[ \t]+\z/) { - checkwarn("TRAILINGSPACE", - $line, length($1), $file, $l, "Trailing whitespace"); - } - - # ------------------------------------------------------------ - # Above this marker, the checks were done on lines *including* - # comments - # ------------------------------------------------------------ - - # strip off C89 comments - - comment: - if(!$incomment) { - if($l =~ s/\/\*.*\*\// /g) { - # full /* comments */ were removed! - } - if($l =~ s/\/\*.*//) { - # start of /* comment was removed - $incomment = 1; - } - } - else { - if($l =~ s/.*\*\///) { - # end of comment */ was removed - $incomment = 0; - goto comment; - } - else { - # still within a comment - $l=""; - } - } - - # ------------------------------------------------------------ - # Below this marker, the checks were done on lines *without* - # comments - # ------------------------------------------------------------ - - # crude attempt to detect // comments without too many false - # positives - if($l =~ /^([^"\*]*)[^:"]\/\//) { - checkwarn("CPPCOMMENTS", - $line, length($1), $file, $l, "\/\/ comment"); - } - - my $nostr = nostrings($l); - # check spaces after for/if/while/function call - if($nostr =~ /^(.*)(for|if|while| ([a-zA-Z0-9_]+)) \((.)/) { - if($1 =~ / *\#/) { - # this is a #if, treat it differently - } - elsif($3 eq "return") { - # return must have a space - } - elsif($4 eq "*") { - # (* beginning makes the space OK! - } - elsif($1 =~ / *typedef/) { - # typedefs can use space-paren - } - else { - checkwarn("SPACEBEFOREPAREN", $line, length($1)+length($2), $file, $l, - "$2 with space"); - } - } - - if($nostr =~ /^((.*)(if) *\()(.*)\)/) { - my $pos = length($1); - if($4 =~ / = /) { - checkwarn("ASSIGNWITHINCONDITION", - $line, $pos+1, $file, $l, - "assignment within conditional expression"); - } - } - # check spaces after open parentheses - if($l =~ /^(.*[a-z])\( /i) { - checkwarn("SPACEAFTERPAREN", - $line, length($1)+1, $file, $l, - "space after open parenthesis"); - } - - # check spaces before close parentheses, unless it was a space or a - # close parenthesis! - if($l =~ /(.*[^\) ]) \)/) { - checkwarn("SPACEBEFORECLOSE", - $line, length($1)+1, $file, $l, - "space before close parenthesis"); - } - - # check spaces before comma! - if($l =~ /(.*[^ ]) ,/) { - checkwarn("SPACEBEFORECOMMA", - $line, length($1)+1, $file, $l, - "space before comma"); - } - - # check for "return(" without space - if($l =~ /^(.*)return\(/) { - if($1 =~ / *\#/) { - # this is a #if, treat it differently - } - else { - checkwarn("RETURNNOSPACE", $line, length($1)+6, $file, $l, - "return without space before paren"); - } - } - - # check for comma without space - if($l =~ /^(.*),[^ \n]/) { - my $pref=$1; - my $ign=0; - if($pref =~ / *\#/) { - # this is a #if, treat it differently - $ign=1; - } - elsif($pref =~ /\/\*/) { - # this is a comment - $ign=1; - } - elsif($pref =~ /[\"\']/) { - $ign = 1; - # There is a quote here, figure out whether the comma is - # within a string or '' or not. - if($pref =~ /\"/) { - # withing a string - } - elsif($pref =~ /\'$/) { - # a single letter - } - else { - $ign = 0; - } - } - if(!$ign) { - checkwarn("COMMANOSPACE", $line, length($pref)+1, $file, $l, - "comma without following space"); - } - } - - # check for "} else" - if($l =~ /^(.*)\} *else/) { - checkwarn("BRACEELSE", - $line, length($1), $file, $l, "else after closing brace on same line"); - } - # check for "){" - if($l =~ /^(.*)\)\{/) { - checkwarn("PARENBRACE", - $line, length($1)+1, $file, $l, "missing space after close paren"); - } - - # check for space before the semicolon last in a line - if($l =~ /^(.*[^ ].*) ;$/) { - checkwarn("SPACESEMILCOLON", - $line, length($1), $file, $ol, "space before last semicolon"); - } - - # scan for use of banned functions - if($l =~ /^(.*\W) - (gets| - strtok| - v?sprintf| - (str|_mbs|_tcs|_wcs)n?cat| - LoadLibrary(Ex)?(A|W)?) - \s*\( - /x) { - checkwarn("BANNEDFUNC", - $line, length($1), $file, $ol, - "use of $2 is banned"); - } - - # scan for use of non-binary fopen without the macro - if($l =~ /^(.*\W)fopen\s*\([^,]*, *\"([^"]*)/) { - my $mode = $2; - if($mode !~ /b/) { - checkwarn("FOPENMODE", - $line, length($1), $file, $ol, - "use of non-binary fopen without FOPEN_* macro: $mode"); - } - } - - # check for open brace first on line but not first column - # only alert if previous line ended with a close paren and wasn't a cpp - # line - if((($prevl =~ /\)\z/) && ($prevl !~ /^ *#/)) && ($l =~ /^( +)\{/)) { - checkwarn("BRACEPOS", - $line, length($1), $file, $ol, "badly placed open brace"); - } - - # if the previous line starts with if/while/for AND ends with an open - # brace, check that this line is indented $indent more steps, if not - # a cpp line - if($prevl =~ /^( *)(if|while|for)\(.*\{\z/) { - my $first = length($1); - - # this line has some character besides spaces - if(($l !~ /^ *#/) && ($l =~ /^( *)[^ ]/)) { - my $second = length($1); - my $expect = $first+$indent; - if($expect != $second) { - my $diff = $second - $first; - checkwarn("INDENTATION", $line, length($1), $file, $ol, - "not indented $indent steps, uses $diff)"); - - } - } - } - - # check for 'char * name' - if(($l =~ /(^.*(char|int|long|void|curl_slist|CURL|CURLM|CURLMsg|curl_httppost) *(\*+)) (\w+)/) && ($4 ne "const")) { - checkwarn("ASTERISKNOSPACE", - $line, length($1), $file, $ol, - "no space after declarative asterisk"); - } - # check for 'char*' - if(($l =~ /(^.*(char|int|long|void|curl_slist|CURL|CURLM|CURLMsg|curl_httppost|sockaddr_in|FILE)\*)/)) { - checkwarn("ASTERISKNOSPACE", - $line, length($1)-1, $file, $ol, - "no space before asterisk"); - } - - # check for 'void func() {', but avoid false positives by requiring - # both an open and closed parentheses before the open brace - if($l =~ /^((\w).*){\z/) { - my $k = $1; - $k =~ s/const *//; - $k =~ s/static *//; - if($k =~ /\(.*\)/) { - checkwarn("BRACEPOS", - $line, length($l)-1, $file, $ol, - "wrongly placed open brace"); - } - } - $line++; - $prevl = $ol; - } - - if(!$copyright) { - checkwarn("COPYRIGHT", 1, 0, $file, "", "Missing copyright statement", 1); - } - if($incomment) { - checkwarn("OPENCOMMENT", 1, 0, $file, "", "Missing closing comment", 1); - } - - checksrc_endoffile($file); - - close(R); - -} - - -if($errors || $warnings || $verbose) { - printf "checksrc: %d errors and %d warnings\n", $errors, $warnings; - if($supressed) { - printf "checksrc: %d errors and %d warnings suppressed\n", - $serrors, - $swarnings; - } - exit 5; # return failure -} diff --git a/contrib/curl/lib/config-amigaos.h b/contrib/curl/lib/config-amigaos.h deleted file mode 100644 index 31cfc3afc2..0000000000 --- a/contrib/curl/lib/config-amigaos.h +++ /dev/null @@ -1,166 +0,0 @@ -#ifndef HEADER_CURL_CONFIG_AMIGAOS_H -#define HEADER_CURL_CONFIG_AMIGAOS_H -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) 1998 - 2016, Daniel Stenberg, , et al. - * - * This software is licensed as described in the file COPYING, which - * you should have received as part of this distribution. The terms - * are also available at https://curl.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. - * - ***************************************************************************/ - -/* ================================================================ */ -/* Hand crafted config file for AmigaOS */ -/* ================================================================ */ - -#ifdef __AMIGA__ /* Any AmigaOS flavour */ - -#define HAVE_ARPA_INET_H 1 -#define HAVE_CLOSESOCKET_CAMEL 1 -#define HAVE_ERRNO_H 1 -#define HAVE_GETHOSTBYADDR 1 -#define HAVE_INET_ADDR 1 -#define HAVE_INTTYPES_H 1 -#define HAVE_IOCTLSOCKET_CAMEL 1 -#define HAVE_IOCTLSOCKET_CAMEL_FIONBIO 1 -#define HAVE_LIBCRYPTO 1 -#define HAVE_LIBSSL 1 -#define HAVE_LIBZ 1 -#define HAVE_LONGLONG 1 -#define HAVE_MALLOC_H 1 -#define HAVE_MEMORY_H 1 -#define HAVE_NETDB_H 1 -#define HAVE_NETINET_IN_H 1 -#define HAVE_NET_IF_H 1 -#define HAVE_OPENSSL_CRYPTO_H 1 -#define HAVE_OPENSSL_ERR_H 1 -#define HAVE_OPENSSL_PEM_H 1 -#define HAVE_OPENSSL_RSA_H 1 -#define HAVE_OPENSSL_SSL_H 1 -#define HAVE_OPENSSL_X509_H 1 -#define HAVE_PERROR 1 -#define HAVE_PWD_H 1 -#define HAVE_RAND_EGD 1 -#define HAVE_RAND_STATUS 1 -#define HAVE_SELECT 1 -#define HAVE_SETJMP_H 1 -#define HAVE_SGTTY_H 1 -#define HAVE_SIGNAL 1 -#define HAVE_SIGNAL_H 1 -#define HAVE_SIG_ATOMIC_T 1 -#define HAVE_SOCKET 1 -#define HAVE_STRCASECMP 1 -#define HAVE_STRDUP 1 -#define HAVE_STRFTIME 1 -#define HAVE_STRICMP 1 -#define HAVE_STRINGS_H 1 -#define HAVE_STRING_H 1 -#define HAVE_STRSTR 1 -#define HAVE_STRUCT_TIMEVAL 1 -#define HAVE_SYS_PARAM_H 1 -#define HAVE_SYS_SOCKET_H 1 -#define HAVE_SYS_SOCKIO_H 1 -#define HAVE_SYS_STAT_H 1 -#define HAVE_SYS_TIME_H 1 -#define HAVE_SYS_TYPES_H 1 -#define HAVE_TIME_H 1 -#define HAVE_UNAME 1 -#define HAVE_UNISTD_H 1 -#define HAVE_UTIME 1 -#define HAVE_UTIME_H 1 -#define HAVE_WRITABLE_ARGV 1 -#define HAVE_ZLIB_H 1 -#define HAVE_SYS_IOCTL_H 1 - -#define NEED_MALLOC_H 1 - -#define SIZEOF_INT 4 -#define SIZEOF_SHORT 2 -#define SIZEOF_SIZE_T 4 - -#define USE_MANUAL 1 -#define USE_OPENSSL 1 -#define CURL_DISABLE_LDAP 1 - -#define OS "AmigaOS" - -#define PACKAGE "curl" -#define PACKAGE_BUGREPORT "a suitable mailing list: https://curl.haxx.se/mail/" -#define PACKAGE_NAME "curl" -#define PACKAGE_STRING "curl -" -#define PACKAGE_TARNAME "curl" -#define PACKAGE_VERSION "-" -#define CURL_CA_BUNDLE "s:curl-ca-bundle.crt" - -#define RETSIGTYPE void -#define SELECT_TYPE_ARG1 int -#define SELECT_TYPE_ARG234 (fd_set *) -#define SELECT_TYPE_ARG5 (struct timeval *) - -#define STDC_HEADERS 1 -#define TIME_WITH_SYS_TIME 1 - -#define in_addr_t int - -#ifndef F_OK -# define F_OK 0 -#endif - -#ifndef O_RDONLY -# define O_RDONLY 0x0000 -#endif - -#ifndef LONG_MAX -# define LONG_MAX 0x7fffffffL -#endif - -#ifndef LONG_MIN -# define LONG_MIN (-0x7fffffffL-1) -#endif - -#define HAVE_GETNAMEINFO 1 -#define GETNAMEINFO_QUAL_ARG1 const -#define GETNAMEINFO_TYPE_ARG1 struct sockaddr * -#define GETNAMEINFO_TYPE_ARG2 int -#define GETNAMEINFO_TYPE_ARG46 size_t -#define GETNAMEINFO_TYPE_ARG7 int - -#define HAVE_RECV 1 -#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 - -#define HAVE_RECVFROM 1 -#define RECVFROM_TYPE_ARG1 long -#define RECVFROM_TYPE_ARG2 char -#define RECVFROM_TYPE_ARG3 long -#define RECVFROM_TYPE_ARG4 long -#define RECVFROM_TYPE_ARG5 struct sockaddr -#define RECVFROM_TYPE_ARG6 long -#define RECVFROM_TYPE_RETV long - -#define HAVE_SEND 1 -#define SEND_TYPE_ARG1 int -#define SEND_QUAL_ARG2 const -#define SEND_TYPE_ARG2 char * -#define SEND_TYPE_ARG3 int -#define SEND_TYPE_ARG4 int -#define SEND_TYPE_RETV int - -#endif /* __AMIGA__ */ -#endif /* HEADER_CURL_CONFIG_AMIGAOS_H */ diff --git a/contrib/curl/lib/config-cosmo.h b/contrib/curl/lib/config-cosmo.h new file mode 100644 index 0000000000..47548c0b02 --- /dev/null +++ b/contrib/curl/lib/config-cosmo.h @@ -0,0 +1,836 @@ +/* lib/curl_config.h.in. Generated somehow by cmake. */ + +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +/* Location of default ca bundle */ +/* #undef CURL_CA_BUNDLE */ + +/* define "1" to use built-in ca store of TLS backend */ +/* #undef CURL_CA_FALLBACK */ + +/* Location of default ca path */ +/* #undef CURL_CA_PATH */ + +/* Default SSL backend */ +/* #undef CURL_DEFAULT_SSL_BACKEND */ + +/* disables alt-svc */ +/* #undef CURL_DISABLE_ALTSVC */ + +/* disables cookies support */ +/* #undef CURL_DISABLE_COOKIES */ + +/* disables Basic authentication */ +/* #undef CURL_DISABLE_BASIC_AUTH */ + +/* disables Bearer authentication */ +/* #undef CURL_DISABLE_BEARER_AUTH */ + +/* disables Digest authentication */ +/* #undef CURL_DISABLE_DIGEST_AUTH */ + +/* disables Kerberos authentication */ +/* #undef CURL_DISABLE_KERBEROS_AUTH */ + +/* disables negotiate authentication */ +/* #undef CURL_DISABLE_NEGOTIATE_AUTH */ + +/* disables AWS-SIG4 */ +/* #undef CURL_DISABLE_AWS */ + +/* disables DICT */ +/* #undef CURL_DISABLE_DICT */ + +/* disables DNS-over-HTTPS */ +/* #undef CURL_DISABLE_DOH */ + +/* disables FILE */ +/* #undef CURL_DISABLE_FILE */ + +/* disables form api */ +/* #undef CURL_DISABLE_FORM_API */ + +/* disables FTP */ +/* #undef CURL_DISABLE_FTP */ + +/* disables curl_easy_options API for existing options to curl_easy_setopt */ +/* #undef CURL_DISABLE_GETOPTIONS */ + +/* disables GOPHER */ +/* #undef CURL_DISABLE_GOPHER */ + +/* disables headers-api support */ +/* #undef CURL_DISABLE_HEADERS_API */ + +/* disables HSTS support */ +/* #undef CURL_DISABLE_HSTS */ + +/* disables HTTP */ +/* #undef CURL_DISABLE_HTTP */ + +/* disabled all HTTP authentication methods */ +/* #undef CURL_DISABLE_HTTP_AUTH */ + +/* disables IMAP */ +/* #undef CURL_DISABLE_IMAP */ + +/* disables LDAP */ +#define CURL_DISABLE_LDAP 1 + +/* disables LDAPS */ +#define CURL_DISABLE_LDAPS 1 + +/* disables --libcurl option from the curl tool */ +/* #undef CURL_DISABLE_LIBCURL_OPTION */ + +/* disables MIME support */ +/* #undef CURL_DISABLE_MIME */ + +/* disables local binding support */ +/* #undef CURL_DISABLE_BINDLOCAL */ + +/* disables MQTT */ +/* #undef CURL_DISABLE_MQTT */ + +/* disables netrc parser */ +/* #undef CURL_DISABLE_NETRC */ + +/* disables NTLM support */ +/* #undef CURL_DISABLE_NTLM */ + +/* disables date parsing */ +/* #undef CURL_DISABLE_PARSEDATE */ + +/* disables POP3 */ +/* #undef CURL_DISABLE_POP3 */ + +/* disables built-in progress meter */ +/* #undef CURL_DISABLE_PROGRESS_METER */ + +/* disables proxies */ +/* #undef CURL_DISABLE_PROXY */ + +/* disables IPFS from the curl tool */ +/* #undef CURL_DISABLE_IPFS */ + +/* disables RTSP */ +/* #undef CURL_DISABLE_RTSP */ + +/* disables SHA-512/256 hash algorithm */ +/* #undef CURL_DISABLE_SHA512_256 */ + +/* disabled shuffle DNS feature */ +/* #undef CURL_DISABLE_SHUFFLE_DNS */ + +/* disables SMB */ +/* #undef CURL_DISABLE_SMB */ + +/* disables SMTP */ +/* #undef CURL_DISABLE_SMTP */ + +/* disabled WebSockets */ +/* #undef CURL_DISABLE_WEBSOCKETS */ + +/* disables use of socketpair for curl_multi_poll */ +/* #undef CURL_DISABLE_SOCKETPAIR */ + +/* disables TELNET */ +/* #undef CURL_DISABLE_TELNET */ + +/* disables TFTP */ +/* #undef CURL_DISABLE_TFTP */ + +/* disables verbose strings */ +/* #undef CURL_DISABLE_VERBOSE_STRINGS */ + +/* disables unsafe CA bundle search on Windows from the curl tool */ +/* #undef CURL_DISABLE_CA_SEARCH */ + +/* safe CA bundle search (within the curl tool directory) on Windows */ +/* #undef CURL_CA_SEARCH_SAFE */ + +/* to make a symbol visible */ +#define CURL_EXTERN_SYMBOL __attribute__ ((__visibility__ ("default"))) +/* Ensure using CURL_EXTERN_SYMBOL is possible */ +#ifndef CURL_EXTERN_SYMBOL +#define CURL_EXTERN_SYMBOL +#endif + +/* Allow SMB to work on Windows */ +/* #undef USE_WIN32_CRYPTO */ + +/* Use Windows LDAP implementation */ +/* #undef USE_WIN32_LDAP */ + +/* Define if you want to enable IPv6 support */ +#define USE_IPV6 1 + +/* Define to 1 if you have the alarm function. */ +#define HAVE_ALARM 1 + +/* Define to 1 if you have the arc4random function. */ +/* #undef HAVE_ARC4RANDOM */ + +/* Define to 1 if you have the header file. */ +#define HAVE_ARPA_INET_H 1 + +/* Define to 1 if you have _Atomic support. */ +#define HAVE_ATOMIC 1 + +/* Define to 1 if you have the `fnmatch' function. */ +#define HAVE_FNMATCH 1 + +/* Define to 1 if you have the `basename' function. */ +#define HAVE_BASENAME 1 + +/* Define to 1 if bool is an available type. */ +#define HAVE_BOOL_T 1 + +/* Define to 1 if you have the __builtin_available function. */ +/* #undef HAVE_BUILTIN_AVAILABLE */ + +/* Define to 1 if you have the clock_gettime function and monotonic timer. */ +#define HAVE_CLOCK_GETTIME_MONOTONIC 1 + +/* Define to 1 if you have the clock_gettime function and raw monotonic timer. + */ +#define HAVE_CLOCK_GETTIME_MONOTONIC_RAW 1 + +/* Define to 1 if you have the `closesocket' function. */ +/* #undef HAVE_CLOSESOCKET */ + +/* Define to 1 if you have the header file. */ +#define HAVE_DIRENT_H 1 + +/* Define to 1 if you have the `opendir' function. */ +#define HAVE_OPENDIR 1 + +/* Define to 1 if you have the fcntl function. */ +#define HAVE_FCNTL 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_FCNTL_H 1 + +/* Define to 1 if you have a working fcntl O_NONBLOCK function. */ +#define HAVE_FCNTL_O_NONBLOCK 1 + +/* Define to 1 if you have the freeaddrinfo function. */ +#define HAVE_FREEADDRINFO 1 + +/* Define to 1 if you have the fseeko function. */ +#define HAVE_FSEEKO 1 + +/* Define to 1 if you have the fseeko declaration. */ +#define HAVE_DECL_FSEEKO 1 + +/* Define to 1 if you have the _fseeki64 function. */ +/* #undef HAVE__FSEEKI64 */ + +/* Define to 1 if you have the ftruncate function. */ +#define HAVE_FTRUNCATE 1 + +/* Define to 1 if you have a working getaddrinfo function. */ +#define HAVE_GETADDRINFO 1 + +/* Define to 1 if the getaddrinfo function is threadsafe. */ +#define HAVE_GETADDRINFO_THREADSAFE 1 + +/* Define to 1 if you have the `geteuid' function. */ +#define HAVE_GETEUID 1 + +/* Define to 1 if you have the `getppid' function. */ +#define HAVE_GETPPID 1 + +/* Define to 1 if you have the gethostbyname_r function. */ +#define HAVE_GETHOSTBYNAME_R 1 + +/* gethostbyname_r() takes 3 args */ +/* #undef HAVE_GETHOSTBYNAME_R_3 */ + +/* gethostbyname_r() takes 5 args */ +/* #undef HAVE_GETHOSTBYNAME_R_5 */ + +/* gethostbyname_r() takes 6 args */ +#define HAVE_GETHOSTBYNAME_R_6 1 + +/* Define to 1 if you have the gethostname function. */ +#define HAVE_GETHOSTNAME 1 + +/* Define to 1 if you have a working getifaddrs function. */ +#define HAVE_GETIFADDRS 1 + +/* Define to 1 if you have the `getpass_r' function. */ +/* #undef HAVE_GETPASS_R */ + +/* Define to 1 if you have the `getpeername' function. */ +#define HAVE_GETPEERNAME 1 + +/* Define to 1 if you have the `getsockname' function. */ +#define HAVE_GETSOCKNAME 1 + +/* Define to 1 if you have the `if_nametoindex' function. */ +/* #undef HAVE_IF_NAMETOINDEX */ + +/* Define to 1 if you have the `getpwuid' function. */ +#define HAVE_GETPWUID 1 + +/* Define to 1 if you have the `getpwuid_r' function. */ +#define HAVE_GETPWUID_R 1 + +/* Define to 1 if you have the `getrlimit' function. */ +#define HAVE_GETRLIMIT 1 + +/* Define to 1 if you have the `gettimeofday' function. */ +#define HAVE_GETTIMEOFDAY 1 + +/* Define to 1 if you have a working glibc-style strerror_r function. */ +#define HAVE_GLIBC_STRERROR_R 1 + +/* Define to 1 if you have a working gmtime_r function. */ +#define HAVE_GMTIME_R 1 + +/* if you have the gssapi libraries */ +/* #undef HAVE_GSSAPI */ + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_GSSAPI_GSSAPI_GENERIC_H */ + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_GSSAPI_GSSAPI_H */ + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_GSSAPI_GSSAPI_KRB5_H */ + +/* if you have the GNU gssapi libraries */ +/* #undef HAVE_GSSGNU */ + +/* Define to 1 if you have the header file. */ +#define HAVE_IFADDRS_H 1 + +/* Define to 1 if you have a IPv6 capable working inet_ntop function. */ +#define HAVE_INET_NTOP 1 + +/* Define to 1 if you have a IPv6 capable working inet_pton function. */ +#define HAVE_INET_PTON 1 + +/* Define to 1 if symbol `sa_family_t' exists */ +#define HAVE_SA_FAMILY_T 1 + +/* Define to 1 if symbol `ADDRESS_FAMILY' exists */ +/* #undef HAVE_ADDRESS_FAMILY */ + +/* Define to 1 if you have the ioctlsocket function. */ +/* #undef HAVE_IOCTLSOCKET */ + +/* Define to 1 if you have the IoctlSocket camel case function. */ +/* #undef HAVE_IOCTLSOCKET_CAMEL */ + +/* Define to 1 if you have a working IoctlSocket camel case FIONBIO function. + */ +/* #undef HAVE_IOCTLSOCKET_CAMEL_FIONBIO */ + +/* Define to 1 if you have a working ioctlsocket FIONBIO function. */ +/* #undef HAVE_IOCTLSOCKET_FIONBIO */ + +/* Define to 1 if you have a working ioctl FIONBIO function. */ +#define HAVE_IOCTL_FIONBIO 1 + +/* Define to 1 if you have a working ioctl SIOCGIFADDR function. */ +#define HAVE_IOCTL_SIOCGIFADDR 1 + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_IO_H */ + +/* Define to 1 if you have the lber.h header file. */ +/* #undef HAVE_LBER_H */ + +/* Use LDAPS implementation */ +/* #undef HAVE_LDAP_SSL */ + +/* Define to 1 if you have the ldap_ssl.h header file. */ +/* #undef HAVE_LDAP_SSL_H */ + +/* Define to 1 if you have the `ldap_url_parse' function. */ +/* #undef HAVE_LDAP_URL_PARSE */ + +/* Define to 1 if you have the header file. */ +#define HAVE_LIBGEN_H 1 + +/* Define to 1 if you have the `idn2' library (-lidn2). */ +/* #undef HAVE_LIBIDN2 */ + +/* Define to 1 if you have the idn2.h header file. */ +/* #undef HAVE_IDN2_H */ + +/* if zlib is available */ +#define HAVE_LIBZ 1 + +/* if brotli is available */ +/* #undef HAVE_BROTLI */ + +/* if zstd is available */ +/* #undef HAVE_ZSTD */ + +/* Define to 1 if you have the header file. */ +#define HAVE_LOCALE_H 1 + +/* Define to 1 if the compiler supports the 'long long' data type. */ +#define HAVE_LONGLONG 1 + +/* Define to 1 if you have the 'suseconds_t' data type. */ +#define HAVE_SUSECONDS_T 1 + +/* Define to 1 if you have the MSG_NOSIGNAL flag. */ +#define HAVE_MSG_NOSIGNAL 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_NETDB_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_NETINET_IN_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_NETINET_TCP_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_NETINET_UDP_H 1 + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_LINUX_TCP_H */ + +/* Define to 1 if you have the header file. */ +#define HAVE_NET_IF_H 1 + +/* if you have an old MIT gssapi library, lacking GSS_C_NT_HOSTBASED_SERVICE */ +/* #undef HAVE_OLD_GSSMIT */ + +/* Define to 1 if you have the `pipe' function. */ +#define HAVE_PIPE 1 + +/* Define to 1 if you have the `eventfd' function. */ +#define HAVE_EVENTFD 1 + +/* If you have poll */ +#define HAVE_POLL 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_POLL_H 1 + +/* Define to 1 if you have a working POSIX-style strerror_r function. */ +/* #undef HAVE_POSIX_STRERROR_R */ + +/* Define to 1 if you have the header file */ +#define HAVE_PTHREAD_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_PWD_H 1 + +/* Define to 1 if OpenSSL has the `SSL_set0_wbio` function. */ +#define HAVE_SSL_SET0_WBIO 1 + +/* Define to 1 if you have the recv function. */ +#define HAVE_RECV 1 + +/* Define to 1 if you have the select function. */ +#define HAVE_SELECT 1 + +/* Define to 1 if you have the sched_yield function. */ +#define HAVE_SCHED_YIELD 1 + +/* Define to 1 if you have the send function. */ +#define HAVE_SEND 1 + +/* Define to 1 if you have the sendmsg function. */ +#define HAVE_SENDMSG 1 + +/* Define to 1 if you have the sendmmsg function. */ +#define HAVE_SENDMMSG 1 + +/* Define to 1 if you have the 'fsetxattr' function. */ +#define HAVE_FSETXATTR 1 + +/* fsetxattr() takes 5 args */ +#define HAVE_FSETXATTR_5 1 + +/* fsetxattr() takes 6 args */ +/* #undef HAVE_FSETXATTR_6 */ + +/* Define to 1 if you have the `setlocale' function. */ +#define HAVE_SETLOCALE 1 + +/* Define to 1 if you have the `setmode' function. */ +/* #undef HAVE_SETMODE */ + +/* Define to 1 if you have the `setrlimit' function. */ +#define HAVE_SETRLIMIT 1 + +/* Define to 1 if you have a working setsockopt SO_NONBLOCK function. */ +/* #undef HAVE_SETSOCKOPT_SO_NONBLOCK */ + +/* Define to 1 if you have the sigaction function. */ +#define HAVE_SIGACTION 1 + +/* Define to 1 if you have the siginterrupt function. */ +#define HAVE_SIGINTERRUPT 1 + +/* Define to 1 if you have the signal function. */ +#define HAVE_SIGNAL 1 + +/* Define to 1 if you have the sigsetjmp function or macro. */ +#define HAVE_SIGSETJMP 1 + +/* Define to 1 if you have the `snprintf' function. */ +#define HAVE_SNPRINTF 1 + +/* Define to 1 if struct sockaddr_in6 has the sin6_scope_id member */ +#define HAVE_SOCKADDR_IN6_SIN6_SCOPE_ID 1 + +/* Define to 1 if you have the `socket' function. */ +#define HAVE_SOCKET 1 + +/* Define to 1 if you have the socketpair function. */ +#define HAVE_SOCKETPAIR 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STDATOMIC_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STDBOOL_H 1 + +/* Define to 1 if you have the strcasecmp function. */ +#define HAVE_STRCASECMP 1 + +/* Define to 1 if you have the strcmpi function. */ +/* #undef HAVE_STRCMPI */ + +/* Define to 1 if you have the strdup function. */ +#define HAVE_STRDUP 1 + +/* Define to 1 if you have the strerror_r function. */ +/* #undef HAVE_STRERROR_R */ + +/* Define to 1 if you have the stricmp function. */ +/* #undef HAVE_STRICMP */ + +/* Define to 1 if you have the header file. */ +#define HAVE_STRINGS_H 1 + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_STROPTS_H */ + +/* Define to 1 if you have the strtok_r function. */ +#define HAVE_STRTOK_R 1 + +/* Define to 1 if you have the strtoll function. */ +#define HAVE_STRTOLL 1 + +/* Define to 1 if you have the memrchr function. */ +#define HAVE_MEMRCHR 1 + +/* if struct sockaddr_storage is defined */ +#define HAVE_STRUCT_SOCKADDR_STORAGE 1 + +/* Define to 1 if you have the timeval struct. */ +#define HAVE_STRUCT_TIMEVAL 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_EVENTFD_H 1 + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_SYS_FILIO_H */ + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_WAIT_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_IOCTL_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_PARAM_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_POLL_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_RESOURCE_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_SELECT_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_SOCKET_H 1 + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_SYS_SOCKIO_H */ + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_STAT_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_TIME_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_TYPES_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_UN_H 1 + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_SYS_UTIME_H */ + +/* Define to 1 if you have the header file. */ +#define HAVE_TERMIOS_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_TERMIO_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_UNISTD_H 1 + +/* Define to 1 if you have the `utime' function. */ +#define HAVE_UTIME 1 + +/* Define to 1 if you have the `utimes' function. */ +#define HAVE_UTIMES 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_UTIME_H 1 + +/* Define this symbol if your OS supports changing the contents of argv */ +#define HAVE_WRITABLE_ARGV 1 + +/* Define to 1 if you need the malloc.h header file even with stdlib.h */ +/* #undef NEED_MALLOC_H */ + +/* Define to 1 if _REENTRANT preprocessor symbol must be defined. */ +/* #undef NEED_REENTRANT */ + +/* cpu-machine-OS */ +#define OS "Linux" + +/* Name of package */ +/* #undef PACKAGE */ + +/* Define to the address where bug reports for this package should be sent. */ +/* #undef PACKAGE_BUGREPORT */ + +/* Define to the full name of this package. */ +/* #undef PACKAGE_NAME */ + +/* Define to the full name and version of this package. */ +/* #undef PACKAGE_STRING */ + +/* Define to the one symbol short name of this package. */ +/* #undef PACKAGE_TARNAME */ + +/* Define to the version of this package. */ +/* #undef PACKAGE_VERSION */ + +/* + 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 4 + +/* The size of `long', as computed by sizeof. */ +#define SIZEOF_LONG 8 + +/* The size of `long long', as computed by sizeof. */ +#define SIZEOF_LONG_LONG 8 + +/* The size of `off_t', as computed by sizeof. */ +#define SIZEOF_OFF_T 8 + +/* The size of `curl_off_t', as computed by sizeof. */ +#define SIZEOF_CURL_OFF_T 8 + +/* The size of `curl_socket_t', as computed by sizeof. */ +#define SIZEOF_CURL_SOCKET_T 4 + +/* The size of `size_t', as computed by sizeof. */ +#define SIZEOF_SIZE_T 8 + +/* The size of `time_t', as computed by sizeof. */ +#define SIZEOF_TIME_T 8 + +/* Define to 1 if you have the ANSI C header files. */ +#define STDC_HEADERS 1 + +/* Define if you want to enable c-ares support */ +/* #undef USE_ARES */ + +/* Define if you want to enable POSIX threaded DNS lookup */ +#define USE_THREADS_POSIX 1 + +/* Define if you want to enable Win32 threaded DNS lookup */ +/* #undef USE_THREADS_WIN32 */ + +/* if GnuTLS is enabled */ +/* #undef USE_GNUTLS */ + +/* if Secure Transport is enabled */ +/* #undef USE_SECTRANSP */ + +/* if mbedTLS is enabled */ +/* #undef USE_MBEDTLS */ + +/* if BearSSL is enabled */ +/* #undef USE_BEARSSL */ + +/* if Rustls is enabled */ +/* #undef USE_RUSTLS */ + +/* if wolfSSL is enabled */ +/* #undef USE_WOLFSSL */ + +/* if wolfSSL has the wolfSSL_DES_ecb_encrypt function. */ +/* #undef HAVE_WOLFSSL_DES_ECB_ENCRYPT */ + +/* if wolfSSL has the wolfSSL_BIO_set_shutdown function. */ +/* #undef HAVE_WOLFSSL_FULL_BIO */ + +/* if libssh is in use */ +/* #undef USE_LIBSSH */ + +/* if libssh2 is in use */ +/* #undef USE_LIBSSH2 */ + +/* if wolfssh is in use */ +/* #undef USE_WOLFSSH */ + +/* if libpsl is in use */ +/* #undef USE_LIBPSL */ + +/* if you want to use OpenLDAP code instead of legacy ldap implementation */ +/* #undef USE_OPENLDAP */ + +/* if OpenSSL is in use */ +/* #undef USE_OPENSSL */ + +/* if librtmp/rtmpdump is in use */ +/* #undef USE_LIBRTMP */ + +/* if GSASL is in use */ +/* #undef USE_GSASL */ + +/* if libuv is in use */ +/* #undef USE_LIBUV */ + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_UV_H */ + +/* Define to 1 if you do not want the OpenSSL configuration to be loaded + automatically */ +/* #undef CURL_DISABLE_OPENSSL_AUTO_LOAD_CONFIG */ + +/* to enable NGHTTP2 */ +/* #undef USE_NGHTTP2 */ + +/* to enable NGTCP2 */ +/* #undef USE_NGTCP2 */ + +/* to enable NGHTTP3 */ +/* #undef USE_NGHTTP3 */ + +/* to enable quiche */ +/* #undef USE_QUICHE */ + +/* to enable openssl + nghttp3 */ +/* #undef USE_OPENSSL_QUIC */ + +/* Define to 1 if you have the quiche_conn_set_qlog_fd function. */ +/* #undef HAVE_QUICHE_CONN_SET_QLOG_FD */ + +/* to enable msh3 */ +/* #undef USE_MSH3 */ + +/* if Unix domain sockets are enabled */ +#define USE_UNIX_SOCKETS 1 + +/* Define to 1 if you are building a Windows target with large file support. */ +/* #undef USE_WIN32_LARGE_FILES */ + +/* to enable SSPI support */ +/* #undef USE_WINDOWS_SSPI */ + +/* to enable Windows SSL */ +/* #undef USE_SCHANNEL */ + +/* enable multiple SSL backends */ +/* #undef CURL_WITH_MULTI_SSL */ + +/* Version number of package */ +/* #undef VERSION */ + +/* Number of bits in a file offset, on hosts where this is settable. */ +#define _FILE_OFFSET_BITS 64 + +/* Define for large files, on AIX-style hosts. */ +/* #undef _LARGE_FILES */ + +/* define this if you need it to compile thread-safe code */ +/* #undef _THREAD_SAFE */ + +/* Define to empty if `const' does not conform to ANSI C. */ +/* #undef const */ + +/* Type to use in place of in_addr_t when system does not provide it. */ +/* #undef in_addr_t */ + +/* Define to `unsigned int' if does not define. */ +/* #undef size_t */ + +/* the signed version of size_t */ +/* #undef ssize_t */ + +/* Define to 1 if you have the mach_absolute_time function. */ +/* #undef HAVE_MACH_ABSOLUTE_TIME */ + +/* to enable Windows IDN */ +/* #undef USE_WIN32_IDN */ + +/* to enable Apple IDN */ +/* #undef USE_APPLE_IDN */ + +/* Define to 1 if OpenSSL has the SSL_CTX_set_srp_username function. */ +#define HAVE_OPENSSL_SRP 1 + +/* Define to 1 if GnuTLS has the gnutls_srp_verifier function. */ +/* #undef HAVE_GNUTLS_SRP */ + +/* Define to 1 to enable TLS-SRP support. */ +#define USE_TLS_SRP 1 + +/* Define to 1 to query for HTTPSRR when using DoH */ +/* #undef USE_HTTPSRR */ + +/* if ECH support is available */ +/* #undef USE_ECH */ diff --git a/contrib/curl/lib/config-dos.h b/contrib/curl/lib/config-dos.h deleted file mode 100644 index f2c9ff4035..0000000000 --- a/contrib/curl/lib/config-dos.h +++ /dev/null @@ -1,181 +0,0 @@ -#ifndef HEADER_CURL_CONFIG_DOS_H -#define HEADER_CURL_CONFIG_DOS_H -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) 1998 - 2015, Daniel Stenberg, , et al. - * - * This software is licensed as described in the file COPYING, which - * you should have received as part of this distribution. The terms - * are also available at https://curl.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. - * - ***************************************************************************/ - - -/* ================================================================ */ -/* lib/config-dos.h - Hand crafted config file for DOS */ -/* ================================================================ */ - -#if defined(DJGPP) - #define OS "MSDOS/djgpp" -#elif defined(__HIGHC__) - #define OS "MSDOS/HighC" -#elif defined(__WATCOMC__) - #define OS "MSDOS/Watcom" -#else - #define OS "MSDOS/?" -#endif - -#define PACKAGE "curl" - -#define HAVE_ARPA_INET_H 1 -#define HAVE_ERRNO_H 1 -#define HAVE_FCNTL_H 1 -#define HAVE_GETADDRINFO 1 -#define HAVE_GETNAMEINFO 1 -#define HAVE_GETPROTOBYNAME 1 -#define HAVE_GETTIMEOFDAY 1 -#define HAVE_IO_H 1 -#define HAVE_IOCTL 1 -#define HAVE_IOCTL_FIONBIO 1 -#define HAVE_IOCTLSOCKET 1 -#define HAVE_IOCTLSOCKET_FIONBIO 1 -#define HAVE_LIMITS_H 1 -#define HAVE_LOCALE_H 1 -#define HAVE_LONGLONG 1 -#define HAVE_MEMORY_H 1 -#define HAVE_NETDB_H 1 -#define HAVE_NETINET_IN_H 1 -#define HAVE_NETINET_TCP_H 1 -#define HAVE_NET_IF_H 1 -#define HAVE_PROCESS_H 1 -#define HAVE_RECV 1 -#define HAVE_RECVFROM 1 -#define HAVE_SELECT 1 -#define HAVE_SEND 1 -#define HAVE_SETJMP_H 1 -#define HAVE_SETLOCALE 1 -#define HAVE_SETMODE 1 -#define HAVE_SIGNAL 1 -#define HAVE_SOCKET 1 -#define HAVE_STRDUP 1 -#define HAVE_STRICMP 1 -#define HAVE_STRTOLL 1 -#define HAVE_STRUCT_TIMEVAL 1 -#define HAVE_STRUCT_IN6_ADDR 1 -#define HAVE_SYS_IOCTL_H 1 -#define HAVE_SYS_SOCKET_H 1 -#define HAVE_SYS_STAT_H 1 -#define HAVE_SYS_TYPES_H 1 -#define HAVE_TIME_H 1 -#define HAVE_UNISTD_H 1 - -#define NEED_MALLOC_H 1 - -#define RETSIGTYPE void -#define SIZEOF_INT 4 -#define SIZEOF_LONG_DOUBLE 16 -#define SIZEOF_SHORT 2 -#define SIZEOF_SIZE_T 4 -#define STDC_HEADERS 1 -#define TIME_WITH_SYS_TIME 1 - -/* Qualifiers for send(), recv(), recvfrom() and getnameinfo(). */ - -#define SEND_TYPE_ARG1 int -#define SEND_QUAL_ARG2 const -#define SEND_TYPE_ARG2 void * -#define SEND_TYPE_ARG3 int -#define SEND_TYPE_ARG4 int -#define SEND_TYPE_RETV int - -#define RECV_TYPE_ARG1 int -#define RECV_TYPE_ARG2 void * -#define RECV_TYPE_ARG3 int -#define RECV_TYPE_ARG4 int -#define RECV_TYPE_RETV int - -#define RECVFROM_TYPE_ARG1 int -#define RECVFROM_TYPE_ARG2 void -#define RECVFROM_TYPE_ARG3 int -#define RECVFROM_TYPE_ARG4 int -#define RECVFROM_TYPE_ARG5 struct sockaddr -#define RECVFROM_TYPE_ARG6 int -#define RECVFROM_TYPE_RETV int -#define RECVFROM_TYPE_ARG2_IS_VOID 1 - -#define GETNAMEINFO_QUAL_ARG1 const -#define GETNAMEINFO_TYPE_ARG1 struct sockaddr * -#define GETNAMEINFO_TYPE_ARG2 int -#define GETNAMEINFO_TYPE_ARG46 int -#define GETNAMEINFO_TYPE_ARG7 int - -#define BSD - -/* CURLDEBUG definition enables memory tracking */ -/* #define CURLDEBUG */ - -/* USE_ZLIB on cmd-line */ -#ifdef USE_ZLIB - #define HAVE_ZLIB_H 1 - #define HAVE_LIBZ 1 -#endif - -/* USE_OPENSSL on cmd-line */ -#ifdef USE_OPENSSL - #define HAVE_CRYPTO_CLEANUP_ALL_EX_DATA 1 - #define HAVE_OPENSSL_ENGINE_H 1 - #define OPENSSL_NO_KRB5 1 -#endif - -/* to disable LDAP */ -#define CURL_DISABLE_LDAP 1 - -#define in_addr_t u_long - -#if defined(__HIGHC__) || \ - (defined(__GNUC__) && (__GNUC__ < 4)) - #define ssize_t int -#endif - -#define CURL_CA_BUNDLE getenv("CURL_CA_BUNDLE") - -/* Target HAVE_x section */ - -#if defined(DJGPP) - #define HAVE_BASENAME 1 - #define HAVE_STRCASECMP 1 - #define HAVE_SIGACTION 1 - #define HAVE_SIGSETJMP 1 - #define HAVE_SYS_TIME_H 1 - #define HAVE_TERMIOS_H 1 - #define HAVE_VARIADIC_MACROS_GCC 1 - -#elif defined(__WATCOMC__) - #define HAVE_STRCASECMP 1 - -#elif defined(__HIGHC__) - #define HAVE_SYS_TIME_H 1 - #define strerror(e) strerror_s_((e)) -#endif - -#ifdef MSDOS /* Watt-32 */ - #define HAVE_CLOSE_S 1 -#endif - -#undef word -#undef byte - -#endif /* HEADER_CURL_CONFIG_DOS_H */ - diff --git a/contrib/curl/lib/config-linux.h b/contrib/curl/lib/config-linux.h index b8a4640fc9..0222459227 100644 --- a/contrib/curl/lib/config-linux.h +++ b/contrib/curl/lib/config-linux.h @@ -858,8 +858,14 @@ /* The size of `long', as computed by sizeof. */ #define SIZEOF_LONG 8 +/* The size of `long long', as computed by sizeof. */ +#define SIZEOF_LONG_LONG 8 + /* The size of `off_t', as computed by sizeof. */ -/* #undef SIZEOF_OFF_T */ +#define SIZEOF_OFF_T 8 + +/* The size of `curl_off_t', as computed by sizeof. */ +#define SIZEOF_CURL_OFF_T 8 /* The size of `size_t', as computed by sizeof. */ #define SIZEOF_SIZE_T 8 diff --git a/contrib/curl/lib/config-mac.h b/contrib/curl/lib/config-mac.h deleted file mode 100644 index 3c12bdfacc..0000000000 --- a/contrib/curl/lib/config-mac.h +++ /dev/null @@ -1,125 +0,0 @@ -#ifndef HEADER_CURL_CONFIG_MAC_H -#define HEADER_CURL_CONFIG_MAC_H -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) 1998 - 2015, Daniel Stenberg, , et al. - * - * This software is licensed as described in the file COPYING, which - * you should have received as part of this distribution. The terms - * are also available at https://curl.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. - * - ***************************************************************************/ - -/* =================================================================== */ -/* Hand crafted config file for Mac OS 9 */ -/* =================================================================== */ -/* On Mac OS X you must run configure to generate curl_config.h file */ -/* =================================================================== */ - -#define OS "mac" - -/* Define if you want the built-in manual */ -#define USE_MANUAL 1 - -#define HAVE_ERRNO_H 1 -#define HAVE_NETINET_IN_H 1 -#define HAVE_SYS_SOCKET_H 1 -#define HAVE_SYS_SELECT_H 1 -#define HAVE_NETDB_H 1 -#define HAVE_ARPA_INET_H 1 -#define HAVE_UNISTD_H 1 -#define HAVE_NET_IF_H 1 -#define HAVE_SYS_TYPES_H 1 -#define HAVE_GETTIMEOFDAY 1 -#define HAVE_FCNTL_H 1 -#define HAVE_SYS_STAT_H 1 -#define HAVE_ALLOCA_H 1 -#define HAVE_STDLIB_H 1 -#define HAVE_TIME_H 1 -#define HAVE_UTIME_H 1 -#define HAVE_SYS_TIME_H 1 -#define HAVE_SYS_UTIME_H 1 - -#define TIME_WITH_SYS_TIME 1 - -#define HAVE_ALARM 1 -#define HAVE_FTRUNCATE 1 -#define HAVE_UTIME 1 -#define HAVE_SETVBUF 1 -#define HAVE_STRFTIME 1 -#define HAVE_INET_ADDR 1 -#define HAVE_MEMCPY 1 -#define HAVE_SELECT 1 -#define HAVE_SOCKET 1 -#define HAVE_STRUCT_TIMEVAL 1 - -#define HAVE_SIGACTION 1 -#define HAVE_SIGNAL_H 1 -#define HAVE_SIG_ATOMIC_T 1 - -#ifdef MACOS_SSL_SUPPORT -# define USE_OPENSSL 1 -#endif - -#define CURL_DISABLE_LDAP 1 - -#define HAVE_RAND_STATUS 1 -#define HAVE_RAND_EGD 1 - -#define HAVE_IOCTL 1 -#define HAVE_IOCTL_FIONBIO 1 - -#define RETSIGTYPE void - -#define SIZEOF_INT 4 -#define SIZEOF_SHORT 2 -#define SIZEOF_SIZE_T 4 - -#define HAVE_GETNAMEINFO 1 -#define GETNAMEINFO_QUAL_ARG1 const -#define GETNAMEINFO_TYPE_ARG1 struct sockaddr * -#define GETNAMEINFO_TYPE_ARG2 socklen_t -#define GETNAMEINFO_TYPE_ARG46 size_t -#define GETNAMEINFO_TYPE_ARG7 int - -#define HAVE_RECV 1 -#define RECV_TYPE_ARG1 int -#define RECV_TYPE_ARG2 void * -#define RECV_TYPE_ARG3 size_t -#define RECV_TYPE_ARG4 int -#define RECV_TYPE_RETV ssize_t - -#define HAVE_RECVFROM 1 -#define RECVFROM_TYPE_ARG1 int -#define RECVFROM_TYPE_ARG2 void -#define RECVFROM_TYPE_ARG3 size_t -#define RECVFROM_TYPE_ARG4 int -#define RECVFROM_TYPE_ARG5 struct sockaddr -#define RECVFROM_TYPE_ARG6 int -#define RECVFROM_TYPE_RETV ssize_t -#define RECVFROM_TYPE_ARG2_IS_VOID 1 - -#define HAVE_SEND 1 -#define SEND_TYPE_ARG1 int -#define SEND_QUAL_ARG2 const -#define SEND_TYPE_ARG2 void * -#define SEND_TYPE_ARG3 size_T -#define SEND_TYPE_ARG4 int -#define SEND_TYPE_RETV ssize_t - -#define HAVE_EXTRA_STRICMP_H 1 -#define HAVE_EXTRA_STRDUP_H 1 - -#endif /* HEADER_CURL_CONFIG_MAC_H */ diff --git a/contrib/curl/lib/config-os400.h b/contrib/curl/lib/config-os400.h deleted file mode 100644 index fe5b864d64..0000000000 --- a/contrib/curl/lib/config-os400.h +++ /dev/null @@ -1,563 +0,0 @@ -#ifndef HEADER_CURL_CONFIG_OS400_H -#define HEADER_CURL_CONFIG_OS400_H -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) 1998 - 2014, Daniel Stenberg, , et al. - * - * This software is licensed as described in the file COPYING, which - * you should have received as part of this distribution. The terms - * are also available at https://curl.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. - * - ***************************************************************************/ - -/* ================================================================ */ -/* Hand crafted config file for OS/400 */ -/* ================================================================ */ - -#pragma enum(int) - -#undef PACKAGE - -/* Version number of this archive. */ -#undef VERSION - -/* Define if you have the getpass function. */ -#undef HAVE_GETPASS - -/* Define cpu-machine-OS */ -#define OS "OS/400" - -/* Define if you have the gethostbyaddr_r() function with 5 arguments */ -#define HAVE_GETHOSTBYADDR_R_5 - -/* Define if you have the gethostbyaddr_r() function with 7 arguments */ -#undef HAVE_GETHOSTBYADDR_R_7 - -/* Define if you have the gethostbyaddr_r() function with 8 arguments */ -#undef HAVE_GETHOSTBYADDR_R_8 - -/* OS400 supports a 3-argument ASCII version of gethostbyaddr_r(), but its - * prototype is incompatible with the "standard" one (1st argument is not - * const). However, getaddrinfo() is supported (ASCII version defined as - * a local wrapper in setup-os400.h) in a threadsafe way: we can then - * configure getaddrinfo() as such and get rid of gethostbyname_r() without - * loss of threadsafeness. */ -#undef HAVE_GETHOSTBYNAME_R -#undef HAVE_GETHOSTBYNAME_R_3 -#undef HAVE_GETHOSTBYNAME_R_5 -#undef HAVE_GETHOSTBYNAME_R_6 -#define HAVE_GETADDRINFO -#define HAVE_GETADDRINFO_THREADSAFE - -/* Define if you need the _REENTRANT define for some functions */ -#undef NEED_REENTRANT - -/* Define if you have the Kerberos4 libraries (including -ldes) */ -#undef HAVE_KRB4 - -/* Define if you want to enable IPv6 support */ -#define ENABLE_IPV6 - -/* Define if struct sockaddr_in6 has the sin6_scope_id member */ -#define HAVE_SOCKADDR_IN6_SIN6_SCOPE_ID 1 - -/* Define this to 'int' if ssize_t is not an available typedefed type */ -#undef ssize_t - -/* Define this as a suitable file to read random data from */ -#undef RANDOM_FILE - -/* Define this to your Entropy Gathering Daemon socket pathname */ -#undef EGD_SOCKET - -/* Define to 1 if you have the alarm function. */ -#define HAVE_ALARM 1 - -/* Define if you have the header file. */ -#undef HAVE_ALLOCA_H - -/* Define if you have the header file. */ -#define HAVE_ARPA_INET_H - -/* Define if you have the `closesocket' function. */ -#undef HAVE_CLOSESOCKET - -/* Define if you have the header file. */ -#undef HAVE_CRYPTO_H - -/* Define if you have the header file. */ -#undef HAVE_DES_H - -/* Define if you have the header file. */ -#define HAVE_ERRNO_H - -/* Define if you have the header file. */ -#undef HAVE_ERR_H - -/* Define if you have the header file. */ -#define HAVE_FCNTL_H - -/* Define if you have the `geteuid' function. */ -#define HAVE_GETEUID - -/* Define if you have the `gethostbyaddr' function. */ -#define HAVE_GETHOSTBYADDR - -/* Define if you have the `gethostbyaddr_r' function. */ -#define HAVE_GETHOSTBYADDR_R - -/* Define if you have the `gethostname' function. */ -#define HAVE_GETHOSTNAME - -/* Define if you have the header file. */ -#undef HAVE_GETOPT_H - -/* Define if you have the `getpass_r' function. */ -#undef HAVE_GETPASS_R - -/* Define if you have the `getpwuid' function. */ -#define HAVE_GETPWUID - -/* Define if you have the `getservbyname' function. */ -#define HAVE_GETSERVBYNAME - -/* Define if you have the `gettimeofday' function. */ -#define HAVE_GETTIMEOFDAY - -/* Define if you have the `timeval' struct. */ -#define HAVE_STRUCT_TIMEVAL - -/* Define if you have the `inet_addr' function. */ -#define HAVE_INET_ADDR - -/* Define if you have the header file. */ -#define HAVE_INTTYPES_H - -/* Define if you have the header file. */ -#undef HAVE_IO_H - -/* Define if you have the `krb_get_our_ip_for_realm' function. */ -#undef HAVE_KRB_GET_OUR_IP_FOR_REALM - -/* Define if you have the header file. */ -#undef HAVE_KRB_H - -/* Define if you have the `crypto' library (-lcrypto). */ -#undef HAVE_LIBCRYPTO - -/* Define if you have the `nsl' library (-lnsl). */ -#undef HAVE_LIBNSL - -/* Define if you have the `resolv' library (-lresolv). */ -#undef HAVE_LIBRESOLV - -/* Define if you have the `resolve' library (-lresolve). */ -#undef HAVE_LIBRESOLVE - -/* Define if you have the `socket' library (-lsocket). */ -#undef HAVE_LIBSOCKET - -/* Define if you have the `ssl' library (-lssl). */ -#undef HAVE_LIBSSL - -/* Define if you have GSS API. */ -#define HAVE_GSSAPI - -/* Define if you have the GNU gssapi libraries */ -#undef HAVE_GSSGNU - -/* Define if you have the Heimdal gssapi libraries */ -#define HAVE_GSSHEIMDAL - -/* Define if you have the MIT gssapi libraries */ -#undef HAVE_GSSMIT - -/* Define if you have the `ucb' library (-lucb). */ -#undef HAVE_LIBUCB - -/* Define if you have the `localtime_r' function. */ -#define HAVE_LOCALTIME_R - -/* Define if you have the header file. */ -#define HAVE_MALLOC_H - -/* Define if you need the malloc.h header file even with stdlib.h */ -/* #define NEED_MALLOC_H 1 */ - -/* Define if you have the header file. */ -#undef HAVE_MEMORY_H - -/* Define if you have the header file. */ -#define HAVE_NETDB_H - -/* Define if you have the header file. */ -#undef HAVE_NETINET_IF_ETHER_H - -/* Define if you have the header file. */ -#define HAVE_NETINET_IN_H - -/* Define if you have the header file. */ -#define HAVE_NET_IF_H - -/* Define if you have the header file. */ -#undef HAVE_OPENSSL_CRYPTO_H - -/* Define if you have the header file. */ -#undef HAVE_OPENSSL_ERR_H - -/* Define if you have the header file. */ -#undef HAVE_OPENSSL_PEM_H - -/* Define if you have the header file. */ -#undef HAVE_OPENSSL_RSA_H - -/* Define if you have the header file. */ -#undef HAVE_OPENSSL_SSL_H - -/* Define if you have the header file. */ -#undef HAVE_OPENSSL_X509_H - -/* Define if you have the header file. */ -#undef HAVE_PEM_H - -/* Define if you have the `perror' function. */ -#define HAVE_PERROR - -/* Define if you have the header file. */ -#define HAVE_PWD_H - -/* Define if you have the `RAND_egd' function. */ -#undef HAVE_RAND_EGD - -/* Define if you have the `RAND_screen' function. */ -#undef HAVE_RAND_SCREEN - -/* Define if you have the `RAND_status' function. */ -#undef HAVE_RAND_STATUS - -/* Define if you have the header file. */ -#undef HAVE_RSA_H - -/* Define if you have the `select' function. */ -#define HAVE_SELECT - -/* Define if you have the `setvbuf' function. */ -#define HAVE_SETVBUF - -/* Define if you have the header file. */ -#undef HAVE_SGTTY_H - -/* Define if you have the `sigaction' function. */ -#define HAVE_SIGACTION - -/* Define if you have the `signal' function. */ -#undef HAVE_SIGNAL - -/* Define if you have the header file. */ -#define HAVE_SIGNAL_H - -/* Define if sig_atomic_t is an available typedef. */ -#define HAVE_SIG_ATOMIC_T - -/* Define if sig_atomic_t is already defined as volatile. */ -#undef HAVE_SIG_ATOMIC_T_VOLATILE - -/* Define if you have the `socket' function. */ -#define HAVE_SOCKET - -/* Define if you have the header file. */ -#undef HAVE_SSL_H - -/* Define if you have the header file. */ -#undef HAVE_STDINT_H - -/* Define if you have the header file. */ -#define HAVE_STDLIB_H - - -/* The following define is needed on OS400 to enable strcmpi(), stricmp() and - strdup(). */ -#define __cplusplus__strings__ - -/* Define if you have the `strcasecmp' function. */ -#undef HAVE_STRCASECMP - -/* Define if you have the `strcmpi' function. */ -#define HAVE_STRCMPI - -/* Define if you have the `stricmp' function. */ -#define HAVE_STRICMP - -/* Define if you have the `strdup' function. */ -#define HAVE_STRDUP - - -/* Define if you have the `strftime' function. */ -#define HAVE_STRFTIME - -/* Define if you have the header file. */ -#define HAVE_STRINGS_H - -/* Define if you have the header file. */ -#define HAVE_STRING_H - -/* Define if you have the `strlcpy' function. */ -#undef HAVE_STRLCPY - -/* Define if you have the header file. */ -#undef HAVE_STROPTS_H - -/* Define if you have the `strstr' function. */ -#define HAVE_STRSTR - -/* Define if you have the `strtok_r' function. */ -#define HAVE_STRTOK_R - -/* Define if you have the `strtoll' function. */ -#undef HAVE_STRTOLL /* Allows ASCII compile on V5R1. */ - -/* Define if you have the header file. */ -#define HAVE_SYS_PARAM_H - -/* Define if you have the header file. */ -#undef HAVE_SYS_SELECT_H - -/* Define if you have the header file. */ -#define HAVE_SYS_SOCKET_H - -/* Define if you have the header file. */ -#undef HAVE_SYS_SOCKIO_H - -/* Define if you have the header file. */ -#define HAVE_SYS_STAT_H - -/* Define if you have the header file. */ -#define HAVE_SYS_TIME_H - -/* Define if you have the header file. */ -#define HAVE_SYS_TYPES_H - -/* Define if you have the header file. */ -#define HAVE_SYS_UN_H - -/* Define if you have the header file. */ -#define HAVE_SYS_IOCTL_H - -/* Define if you have the `tcgetattr' function. */ -#undef HAVE_TCGETATTR - -/* Define if you have the `tcsetattr' function. */ -#undef HAVE_TCSETATTR - -/* Define if you have the header file. */ -#undef HAVE_TERMIOS_H - -/* Define if you have the header file. */ -#undef HAVE_TERMIO_H - -/* Define if you have the header file. */ -#define HAVE_TIME_H - -/* Define if you have the `uname' function. */ -#undef HAVE_UNAME - -/* Define if you have the header file. */ -#define HAVE_UNISTD_H - -/* Define if you have the header file. */ -#undef HAVE_WINSOCK_H - -/* Define if you have the header file. */ -#undef HAVE_X509_H - -/* Name of package */ -#undef PACKAGE - -/* Define as the return type of signal handlers (`int' or `void'). */ -#define RETSIGTYPE void - -/* The size of `int', as computed by sizeof. */ -#define SIZEOF_INT 4 - -/* The size of a `long double', as computed by sizeof. */ -#define SIZEOF_LONG_DOUBLE 8 - -/* Define if the compiler supports the 'long long' data type. */ -#define HAVE_LONGLONG - -/* The size of a `long long', as computed by sizeof. */ -#define SIZEOF_LONG_LONG 8 - -/* The size of `short', as computed by sizeof. */ -#define SIZEOF_SHORT 2 - -/* The size of `size_t', as computed by sizeof. */ -#define SIZEOF_SIZE_T 8 - -/* Whether long long constants must be suffixed by LL. */ - -#define HAVE_LL - -/* Define this if you have struct sockaddr_storage */ -#define HAVE_STRUCT_SOCKADDR_STORAGE - -/* Define if you have the ANSI C header files. */ -#define STDC_HEADERS - -/* Define if you can safely include both and . */ -#define TIME_WITH_SYS_TIME - -/* Version number of package */ -#undef VERSION - -/* Number of bits in a file offset, on hosts where this is settable. */ -#undef _FILE_OFFSET_BITS - -/* Define for large files, on AIX-style hosts. */ -#define _LARGE_FILES - -/* Define to empty if `const' does not conform to ANSI C. */ -#undef const - -/* type to use in place of in_addr_t if not defined */ -#define in_addr_t unsigned long - -/* Define to `unsigned' if does not define. */ -#undef size_t - -/* Define if you have the ioctl function. */ -#define HAVE_IOCTL - -/* Define if you have a working ioctl FIONBIO function. */ -#define HAVE_IOCTL_FIONBIO - -/* Define if you have a working ioctl SIOCGIFADDR function. */ -#define HAVE_IOCTL_SIOCGIFADDR - -/* To disable LDAP */ -#undef CURL_DISABLE_LDAP - -/* Definition to make a library symbol externally visible. */ -#define CURL_EXTERN_SYMBOL - -/* Define if you have the ldap_url_parse procedure. */ -/* #define HAVE_LDAP_URL_PARSE */ /* Disabled because of an IBM bug. */ - -/* Define if you have the getnameinfo function. */ -/* OS400 has no ASCII version of this procedure: wrapped in setup-os400.h. */ -#define HAVE_GETNAMEINFO - -/* Define to the type qualifier of arg 1 for getnameinfo. */ -#define GETNAMEINFO_QUAL_ARG1 const - -/* Define to the type of arg 1 for getnameinfo. */ -#define GETNAMEINFO_TYPE_ARG1 struct sockaddr * - -/* Define to the type of arg 2 for getnameinfo. */ -#define GETNAMEINFO_TYPE_ARG2 socklen_t - -/* Define to the type of args 4 and 6 for getnameinfo. */ -#define GETNAMEINFO_TYPE_ARG46 socklen_t - -/* Define to the type of arg 7 for getnameinfo. */ -#define GETNAMEINFO_TYPE_ARG7 int - -/* Define if you have the recv function. */ -#define HAVE_RECV - -/* Define to the type of arg 1 for recv. */ -#define RECV_TYPE_ARG1 int - -/* Define to the type of arg 2 for recv. */ -#define RECV_TYPE_ARG2 char * - -/* Define to the type of arg 3 for recv. */ -#define RECV_TYPE_ARG3 int - -/* Define to the type of arg 4 for recv. */ -#define RECV_TYPE_ARG4 int - -/* Define to the function return type for recv. */ -#define RECV_TYPE_RETV int - -/* Define if you have the recvfrom function. */ -#define HAVE_RECVFROM - -/* Define to the type of arg 1 for recvfrom. */ -#define RECVFROM_TYPE_ARG1 int - -/* Define to the type pointed by arg 2 for recvfrom. */ -#define RECVFROM_TYPE_ARG2 char - -/* Define to the type of arg 3 for recvfrom. */ -#define RECVFROM_TYPE_ARG3 int - -/* Define to the type of arg 4 for recvfrom. */ -#define RECVFROM_TYPE_ARG4 int - -/* Define to the type pointed by arg 5 for recvfrom. */ -#define RECVFROM_TYPE_ARG5 struct sockaddr - -/* Define to the type pointed by arg 6 for recvfrom. */ -#define RECVFROM_TYPE_ARG6 int - -/* Define to the function return type for recvfrom. */ -#define RECVFROM_TYPE_RETV int - -/* Define if you have the send function. */ -#define HAVE_SEND - -/* Define to the type of arg 1 for send. */ -#define SEND_TYPE_ARG1 int - -/* Define to the type qualifier of arg 2 for send. */ -#define SEND_QUAL_ARG2 - -/* Define to the type of arg 2 for send. */ -#define SEND_TYPE_ARG2 char * - -/* Define to the type of arg 3 for send. */ -#define SEND_TYPE_ARG3 int - -/* Define to the type of arg 4 for send. */ -#define SEND_TYPE_ARG4 int - -/* Define to the function return type for send. */ -#define SEND_TYPE_RETV int - -/* Define to use the GSKit package. */ -#define USE_GSKIT - -/* Define to use the OS/400 crypto library. */ -#define USE_OS400CRYPTO - -/* Define to use Unix sockets. */ -#define USE_UNIX_SOCKETS - -/* Use the system keyring as the default CA bundle. */ -#define CURL_CA_BUNDLE "/QIBM/UserData/ICSS/Cert/Server/DEFAULT.KDB" - -/* ---------------------------------------------------------------- */ -/* ADDITIONAL DEFINITIONS */ -/* ---------------------------------------------------------------- */ - -/* The following must be defined BEFORE system header files inclusion. */ - -#define __ptr128 /* No teraspace. */ -#define qadrt_use_fputc_inline /* Generate fputc() wrapper inline. */ -#define qadrt_use_fread_inline /* Generate fread() wrapper inline. */ -#define qadrt_use_fwrite_inline /* Generate fwrite() wrapper inline. */ - -#endif /* HEADER_CURL_CONFIG_OS400_H */ diff --git a/contrib/curl/lib/config-osx.h b/contrib/curl/lib/config-osx.h index 8ce6024424..49f24522df 100644 --- a/contrib/curl/lib/config-osx.h +++ b/contrib/curl/lib/config-osx.h @@ -858,8 +858,14 @@ /* The size of `long', as computed by sizeof. */ #define SIZEOF_LONG 8 +/* The size of `long long', as computed by sizeof. */ +#define SIZEOF_LONG_LONG 8 + /* The size of `off_t', as computed by sizeof. */ -/* #undef SIZEOF_OFF_T */ +#define SIZEOF_OFF_T 8 + +/* The size of `curl_off_t', as computed by sizeof. */ +#define SIZEOF_CURL_OFF_T 8 /* The size of `size_t', as computed by sizeof. */ #define SIZEOF_SIZE_T 8 diff --git a/contrib/curl/lib/config-riscos.h b/contrib/curl/lib/config-riscos.h deleted file mode 100644 index 0379524fb3..0000000000 --- a/contrib/curl/lib/config-riscos.h +++ /dev/null @@ -1,513 +0,0 @@ -#ifndef HEADER_CURL_CONFIG_RISCOS_H -#define HEADER_CURL_CONFIG_RISCOS_H -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) 1998 - 2013, Daniel Stenberg, , et al. - * - * This software is licensed as described in the file COPYING, which - * you should have received as part of this distribution. The terms - * are also available at https://curl.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. - * - ***************************************************************************/ - -/* ================================================================ */ -/* Hand crafted config file for RISC OS */ -/* ================================================================ */ - -/* Name of this package! */ -#undef PACKAGE - -/* Version number of this archive. */ -#undef VERSION - -/* Define if you have the getpass function. */ -#undef HAVE_GETPASS - -/* Define cpu-machine-OS */ -#define OS "ARM-RISC OS" - -/* Define if you want the built-in manual */ -#define USE_MANUAL - -/* Define if you have the gethostbyaddr_r() function with 5 arguments */ -#undef HAVE_GETHOSTBYADDR_R_5 - -/* Define if you have the gethostbyaddr_r() function with 7 arguments */ -#undef HAVE_GETHOSTBYADDR_R_7 - -/* Define if you have the gethostbyaddr_r() function with 8 arguments */ -#undef HAVE_GETHOSTBYADDR_R_8 - -/* Define if you have the gethostbyname_r() function with 3 arguments */ -#undef HAVE_GETHOSTBYNAME_R_3 - -/* Define if you have the gethostbyname_r() function with 5 arguments */ -#undef HAVE_GETHOSTBYNAME_R_5 - -/* Define if you have the gethostbyname_r() function with 6 arguments */ -#undef HAVE_GETHOSTBYNAME_R_6 - -/* Define if you need the _REENTRANT define for some functions */ -#undef NEED_REENTRANT - -/* Define if you have the Kerberos4 libraries (including -ldes) */ -#undef HAVE_KRB4 - -/* Define if you want to enable IPv6 support */ -#undef ENABLE_IPV6 - -/* Define if struct sockaddr_in6 has the sin6_scope_id member */ -#define HAVE_SOCKADDR_IN6_SIN6_SCOPE_ID 1 - -/* Define this to 'int' if ssize_t is not an available typedefed type */ -#undef ssize_t - -/* Define this as a suitable file to read random data from */ -#undef RANDOM_FILE - -/* Define this to your Entropy Gathering Daemon socket pathname */ -#undef EGD_SOCKET - -/* Define if you want to enable IPv6 support */ -#undef ENABLE_IPV6 - -/* Define if you have the alarm function. */ -#define HAVE_ALARM - -/* Define if you have the header file. */ -#define HAVE_ALLOCA_H - -/* Define if you have the header file. */ -#define HAVE_ARPA_INET_H - -/* Define if you have the `closesocket' function. */ -#undef HAVE_CLOSESOCKET - -/* Define if you have the header file. */ -#undef HAVE_CRYPTO_H - -/* Define if you have the header file. */ -#undef HAVE_DES_H - -/* Define if you have the header file. */ -#define HAVE_ERRNO_H - -/* Define if you have the header file. */ -#undef HAVE_ERR_H - -/* Define if you have the header file. */ -#define HAVE_FCNTL_H - -/* Define if you have the `ftruncate' function. */ -#define HAVE_FTRUNCATE - -/* Define if getaddrinfo exists and works */ -#define HAVE_GETADDRINFO - -/* Define if you have the `geteuid' function. */ -#undef HAVE_GETEUID - -/* Define if you have the `gethostbyaddr' function. */ -#define HAVE_GETHOSTBYADDR - -/* Define if you have the `gethostbyaddr_r' function. */ -#undef HAVE_GETHOSTBYADDR_R - -/* Define if you have the `gethostbyname_r' function. */ -#undef HAVE_GETHOSTBYNAME_R - -/* Define if you have the `gethostname' function. */ -#define HAVE_GETHOSTNAME - -/* Define if you have the header file. */ -#define HAVE_GETOPT_H - -/* Define if you have the `getpass_r' function. */ -#undef HAVE_GETPASS_R - -/* Define if you have the `getpwuid' function. */ -#undef HAVE_GETPWUID - -/* Define if you have the `getservbyname' function. */ -#undef HAVE_GETSERVBYNAME - -/* Define if you have the `gettimeofday' function. */ -#define HAVE_GETTIMEOFDAY - -/* Define if you have the `timeval' struct. */ -#define HAVE_STRUCT_TIMEVAL - -/* Define if you have the `inet_addr' function. */ -#undef HAVE_INET_ADDR - -/* Define if you have the header file. */ -#define HAVE_INTTYPES_H - -/* Define if you have the header file. */ -#undef HAVE_IO_H - -/* Define if you have the `krb_get_our_ip_for_realm' function. */ -#undef HAVE_KRB_GET_OUR_IP_FOR_REALM - -/* Define if you have the header file. */ -#undef HAVE_KRB_H - -/* Define if you have the `crypto' library (-lcrypto). */ -#undef HAVE_LIBCRYPTO - -/* Define if you have the `nsl' library (-lnsl). */ -#undef HAVE_LIBNSL - -/* Define if you have the `resolv' library (-lresolv). */ -#undef HAVE_LIBRESOLV - -/* Define if you have the `resolve' library (-lresolve). */ -#undef HAVE_LIBRESOLVE - -/* Define if you have the `socket' library (-lsocket). */ -#undef HAVE_LIBSOCKET - -/* Define if you have the `ssl' library (-lssl). */ -#undef HAVE_LIBSSL - -/* Define if you have the `ucb' library (-lucb). */ -#undef HAVE_LIBUCB - -/* Define if you have the `localtime_r' function. */ -#undef HAVE_LOCALTIME_R - -/* Define if you have the header file. */ -#define HAVE_MALLOC_H - -/* Define if you need the malloc.h header file even with stdlib.h */ -/* #define NEED_MALLOC_H 1 */ - -/* Define if you have the header file. */ -#undef HAVE_MEMORY_H - -/* Define if you have the header file. */ -#define HAVE_NETDB_H - -/* Define if you have the header file. */ -#undef HAVE_NETINET_IF_ETHER_H - -/* Define if you have the header file. */ -#define HAVE_NETINET_IN_H - -/* Define if you have the header file. */ -#define HAVE_NET_IF_H - -/* Define if you have the header file. */ -#undef HAVE_OPENSSL_CRYPTO_H - -/* Define if you have the header file. */ -#undef HAVE_OPENSSL_ERR_H - -/* Define if you have the header file. */ -#undef HAVE_OPENSSL_PEM_H - -/* Define if you have the header file. */ -#undef HAVE_OPENSSL_RSA_H - -/* Define if you have the header file. */ -#undef HAVE_OPENSSL_SSL_H - -/* Define if you have the header file. */ -#undef HAVE_OPENSSL_X509_H - -/* Define if you have the header file. */ -#undef HAVE_PEM_H - -/* Define if you have the `perror' function. */ -#undef HAVE_PERROR - -/* Define if you have the header file. */ -#undef HAVE_PWD_H - -/* Define if you have the `RAND_egd' function. */ -#undef HAVE_RAND_EGD - -/* Define if you have the `RAND_screen' function. */ -#undef HAVE_RAND_SCREEN - -/* Define if you have the `RAND_status' function. */ -#undef HAVE_RAND_STATUS - -/* Define if you have the header file. */ -#undef HAVE_RSA_H - -/* Define if you have the `select' function. */ -#define HAVE_SELECT - -/* Define if you have the `setvbuf' function. */ -#undef HAVE_SETVBUF - -/* Define if you have the header file. */ -#define HAVE_SGTTY_H - -/* Define if you have the `sigaction' function. */ -#undef HAVE_SIGACTION - -/* Define if you have the `signal' function. */ -#define HAVE_SIGNAL - -/* Define if you have the header file. */ -#define HAVE_SIGNAL_H - -/* Define if sig_atomic_t is an available typedef. */ -#define HAVE_SIG_ATOMIC_T - -/* Define if sig_atomic_t is already defined as volatile. */ -#undef HAVE_SIG_ATOMIC_T_VOLATILE - -/* Define if you have the `socket' function. */ -#define HAVE_SOCKET - -/* Define if you have the header file. */ -#undef HAVE_SSL_H - -/* Define if you have the header file. */ -#undef HAVE_STDINT_H - -/* Define if you have the header file. */ -#define HAVE_STDLIB_H - -/* Define if you have the `strcasecmp' function. */ -#undef HAVE_STRCASECMP - -/* Define if you have the `strcmpi' function. */ -#undef HAVE_STRCMPI - -/* Define if you have the `strdup' function. */ -#define HAVE_STRDUP - -/* Define if you have the `strftime' function. */ -#define HAVE_STRFTIME - -/* Define if you have the `stricmp' function. */ -#define HAVE_STRICMP - -/* Define if you have the header file. */ -#undef HAVE_STRINGS_H - -/* Define if you have the header file. */ -#define HAVE_STRING_H - -/* Define if you have the `strlcpy' function. */ -#undef HAVE_STRLCPY - -/* Define if you have the `strstr' function. */ -#define HAVE_STRSTR - -/* Define if you have the `strtok_r' function. */ -#undef HAVE_STRTOK_R - -/* Define if you have the `strtoll' function. */ -#undef HAVE_STRTOLL - -/* Define if you have the header file. */ -#undef HAVE_SYS_PARAM_H - -/* Define if you have the header file. */ -#undef HAVE_SYS_SELECT_H - -/* Define if you have the header file. */ -#define HAVE_SYS_SOCKET_H - -/* Define if you have the header file. */ -#undef HAVE_SYS_SOCKIO_H - -/* Define if you have the header file. */ -#undef HAVE_SYS_STAT_H - -/* Define if you have the header file. */ -#define HAVE_SYS_TIME_H - -/* Define if you have the header file. */ -#define HAVE_SYS_TYPES_H - -/* Define if you have the `tcgetattr' function. */ -#define HAVE_TCGETATTR - -/* Define if you have the `tcsetattr' function. */ -#define HAVE_TCSETATTR - -/* Define if you have the header file. */ -#define HAVE_TERMIOS_H - -/* Define if you have the header file. */ -#undef HAVE_TERMIO_H - -/* Define if you have the header file. */ -#undef HAVE_TIME_H - -/* Define if you have the `uname' function. */ -#define HAVE_UNAME - -/* Define if you have the header file. */ -#define HAVE_UNISTD_H - -/* Define if you have the header file. */ -#undef HAVE_WINSOCK_H - -/* Define if you have the header file. */ -#undef HAVE_X509_H - -/* Name of package */ -#undef PACKAGE - -/* Define as the return type of signal handlers (`int' or `void'). */ -#define RETSIGTYPE void - -/* The size of `int', as computed by sizeof. */ -#define SIZEOF_INT 4 - -/* The size of `long double', as computed by sizeof. */ -#undef SIZEOF_LONG_DOUBLE - -/* The size of `long long', as computed by sizeof. */ -#undef SIZEOF_LONG_LONG - -/* The size of `short', as computed by sizeof. */ -#define SIZEOF_SHORT 2 - -/* The size of `size_t', as computed by sizeof. */ -#define SIZEOF_SIZE_T 4 - -/* Define if you have the ANSI C header files. */ -#undef STDC_HEADERS - -/* Define if you can safely include both and . */ -#undef TIME_WITH_SYS_TIME - -/* Version number of package */ -#undef VERSION - -/* Define if on AIX 3. - System headers sometimes define this. - We just want to avoid a redefinition error message. */ -#ifndef _ALL_SOURCE -# undef _ALL_SOURCE -#endif - -/* Number of bits in a file offset, on hosts where this is settable. */ -#undef _FILE_OFFSET_BITS - -/* Define for large files, on AIX-style hosts. */ -#undef _LARGE_FILES - -/* Define to empty if `const' does not conform to ANSI C. */ -#undef const - -/* Define to `unsigned' if does not define. */ -#undef size_t - -/* Define to `int' if does not define. */ -#undef ssize_t - -/* Define if you have the ioctl function. */ -#define HAVE_IOCTL - -/* Define if you have a working ioctl FIONBIO function. */ -#define HAVE_IOCTL_FIONBIO - -/* to disable LDAP */ -#define CURL_DISABLE_LDAP - -/* Define if you have the getnameinfo function. */ -#define HAVE_GETNAMEINFO 1 - -/* Define to the type qualifier of arg 1 for getnameinfo. */ -#define GETNAMEINFO_QUAL_ARG1 const - -/* Define to the type of arg 1 for getnameinfo. */ -#define GETNAMEINFO_TYPE_ARG1 struct sockaddr * - -/* Define to the type of arg 2 for getnameinfo. */ -#define GETNAMEINFO_TYPE_ARG2 socklen_t - -/* Define to the type of args 4 and 6 for getnameinfo. */ -#define GETNAMEINFO_TYPE_ARG46 size_t - -/* Define to the type of arg 7 for getnameinfo. */ -#define GETNAMEINFO_TYPE_ARG7 int - -/* Define if you have the recv function. */ -#define HAVE_RECV 1 - -/* Define to the type of arg 1 for recv. */ -#define RECV_TYPE_ARG1 int - -/* Define to the type of arg 2 for recv. */ -#define RECV_TYPE_ARG2 void * - -/* Define to the type of arg 3 for recv. */ -#define RECV_TYPE_ARG3 size_t - -/* Define to the type of arg 4 for recv. */ -#define RECV_TYPE_ARG4 int - -/* Define to the function return type for recv. */ -#define RECV_TYPE_RETV ssize_t - -/* Define 1 if you have the recvfrom function. */ -#define HAVE_RECVFROM 1 - -/* Define to the type of arg 1 for recvfrom. */ -#define RECVFROM_TYPE_ARG1 int - -/* Define to the type pointed by arg 2 for recvfrom. */ -#define RECVFROM_TYPE_ARG2 void - -/* Define if the type pointed by arg 2 for recvfrom is void. */ -#define RECVFROM_TYPE_ARG2_IS_VOID - -/* Define to the type of arg 3 for recvfrom. */ -#define RECVFROM_TYPE_ARG3 size_t - -/* Define to the type of arg 4 for recvfrom. */ -#define RECVFROM_TYPE_ARG4 int - -/* Define to the type pointed by arg 5 for recvfrom. */ -#define RECVFROM_TYPE_ARG5 struct sockaddr - -/* Define to the type pointed by arg 6 for recvfrom. */ -#define RECVFROM_TYPE_ARG6 int - -/* Define to the function return type for recvfrom. */ -#define RECVFROM_TYPE_RETV ssize_t - -/* Define if you have the send function. */ -#define HAVE_SEND 1 - -/* Define to the type of arg 1 for send. */ -#define SEND_TYPE_ARG1 int - -/* Define to the type qualifier of arg 2 for send. */ -#define SEND_QUAL_ARG2 const - -/* Define to the type of arg 2 for send. */ -#define SEND_TYPE_ARG2 void * - -/* Define to the type of arg 3 for send. */ -#define SEND_TYPE_ARG3 size_t - -/* Define to the type of arg 4 for send. */ -#define SEND_TYPE_ARG4 int - -/* Define to the function return type for send. */ -#define SEND_TYPE_RETV ssize_t - -#endif /* HEADER_CURL_CONFIG_RISCOS_H */ diff --git a/contrib/curl/lib/config-symbian.h b/contrib/curl/lib/config-symbian.h deleted file mode 100644 index 92983d2421..0000000000 --- a/contrib/curl/lib/config-symbian.h +++ /dev/null @@ -1,811 +0,0 @@ -#ifndef HEADER_CURL_CONFIG_SYMBIAN_H -#define HEADER_CURL_CONFIG_SYMBIAN_H -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) 1998 - 2016, Daniel Stenberg, , et al. - * - * This software is licensed as described in the file COPYING, which - * you should have received as part of this distribution. The terms - * are also available at https://curl.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. - * - ***************************************************************************/ - -/* ================================================================ */ -/* Hand crafted config file for Symbian */ -/* ================================================================ */ - -/* Location of default ca bundle */ -/* #define CURL_CA_BUNDLE "/etc/pki/tls/certs/ca-bundle.crt"*/ - -/* Location of default ca path */ -/* #undef CURL_CA_PATH */ - -/* to disable cookies support */ -/* #undef CURL_DISABLE_COOKIES */ - -/* to disable cryptographic authentication */ -/* #undef CURL_DISABLE_CRYPTO_AUTH */ - -/* to disable DICT */ -/* #undef CURL_DISABLE_DICT */ - -/* to disable FILE */ -/* #undef CURL_DISABLE_FILE */ - -/* to disable FTP */ -/* #undef CURL_DISABLE_FTP */ - -/* to disable HTTP */ -/* #undef CURL_DISABLE_HTTP */ - -/* to disable LDAP */ -#define CURL_DISABLE_LDAP 1 - -/* to disable LDAPS */ -#define CURL_DISABLE_LDAPS 1 - -/* to disable TELNET */ -/* #undef CURL_DISABLE_TELNET */ - -/* to disable TFTP */ -/* #undef CURL_DISABLE_TFTP */ - -/* to disable verbose strings */ -/* #define CURL_DISABLE_VERBOSE_STRINGS 1*/ - -/* Definition to make a library symbol externally visible. */ -/* #undef CURL_EXTERN_SYMBOL */ - -/* Use Windows LDAP implementation */ -/* #undef USE_WIN32_LDAP */ - -/* your Entropy Gathering Daemon socket pathname */ -/* #undef EGD_SOCKET */ - -/* Define if you want to enable IPv6 support */ -#define ENABLE_IPV6 1 - -/* Define if struct sockaddr_in6 has the sin6_scope_id member */ -#define HAVE_SOCKADDR_IN6_SIN6_SCOPE_ID 1 - -/* Define to the type qualifier of arg 1 for getnameinfo. */ -#define GETNAMEINFO_QUAL_ARG1 const - -/* Define to the type of arg 1 for getnameinfo. */ -#define GETNAMEINFO_TYPE_ARG1 struct sockaddr * - -/* Define to the type of arg 2 for getnameinfo. */ -#define GETNAMEINFO_TYPE_ARG2 socklen_t - -/* Define to the type of args 4 and 6 for getnameinfo. */ -#define GETNAMEINFO_TYPE_ARG46 size_t - -/* Define to the type of arg 7 for getnameinfo. */ -#define GETNAMEINFO_TYPE_ARG7 int - -/* Define to 1 if you have the header file. */ -/*#define HAVE_ALLOCA_H 1*/ - -/* Define to 1 if you have the header file. */ -#define HAVE_ARPA_INET_H 1 - -/* Define to 1 if you have the header file. */ -/*#define HAVE_ARPA_TFTP_H 1*/ - -/* Define to 1 if you have the header file. */ -#define HAVE_ASSERT_H 1 - -/* Define to 1 if you have the `basename' function. */ -/*#define HAVE_BASENAME 1*/ - -/* Define to 1 if bool is an available type. */ -/*#define HAVE_BOOL_T 1*/ - -/* Define to 1 if you have the `closesocket' function. */ -/* #undef HAVE_CLOSESOCKET */ - -/* Define to 1 if you have the `CRYPTO_cleanup_all_ex_data' function. */ -/*#define HAVE_CRYPTO_CLEANUP_ALL_EX_DATA 1*/ - -/* Define to 1 if you have the header file. */ -/* #undef HAVE_CRYPTO_H */ - -/* Define to 1 if you have the header file. */ -/* #undef HAVE_DES_H */ - -/* Define to 1 if you have the header file. */ -#define HAVE_DLFCN_H 1 - -/* Define to 1 if you have the `ENGINE_load_builtin_engines' function. */ -/*#define HAVE_ENGINE_LOAD_BUILTIN_ENGINES 1*/ - -/* Define to 1 if you have the header file. */ -#define HAVE_ERRNO_H 1 - -/* Define to 1 if you have the header file. */ -#define HAVE_ERR_H 1 - -/* Define to 1 if you have the header file. */ -#define HAVE_FCNTL_H 1 - -/* Define to 1 if you have the fcntl function. */ -#define HAVE_FCNTL 1 - -/* Define to 1 if you have a working fcntl O_NONBLOCK function. */ -#define HAVE_FCNTL_O_NONBLOCK 1 - -/* Define to 1 if you have the `fork' function. */ -/*#define HAVE_FORK 1*/ - -/* Define to 1 if you have the `ftruncate' function. */ -#define HAVE_FTRUNCATE 1 - -/* Define if getaddrinfo exists and works */ -#define HAVE_GETADDRINFO 1 - -/* Define to 1 if you have the `geteuid' function. */ -#define HAVE_GETEUID 1 - -/* Define to 1 if you have the `gethostbyaddr' function. */ -#define HAVE_GETHOSTBYADDR 1 - -/* If you have gethostbyname */ -#define HAVE_GETHOSTBYNAME 1 - -/* Define to 1 if you have the `gethostbyname_r' function. */ -/* #undef HAVE_GETHOSTBYNAME_R */ - -/* gethostbyname_r() takes 3 args */ -/* #undef HAVE_GETHOSTBYNAME_R_3 */ - -/* gethostbyname_r() takes 5 args */ -/* #undef HAVE_GETHOSTBYNAME_R_5 */ - -/* gethostbyname_r() takes 6 args */ -/* #undef HAVE_GETHOSTBYNAME_R_6 */ - -/* Define to 1 if you have the getnameinfo function. */ -#define HAVE_GETNAMEINFO 1 - -/* Define to 1 if you have the `getpass_r' function. */ -/* #undef HAVE_GETPASS_R */ - -/* Define to 1 if you have the `getppid' function. */ -#define HAVE_GETPPID 1 - -/* Define to 1 if you have the `getprotobyname' function. */ -#define HAVE_GETPROTOBYNAME 1 - -/* Define to 1 if you have the `getpwuid' function. */ -#define HAVE_GETPWUID 1 - -/* Define to 1 if you have the `getrlimit' function. */ -/*#define HAVE_GETRLIMIT 1*/ - -/* Define to 1 if you have the `gettimeofday' function. */ -#define HAVE_GETTIMEOFDAY 1 - -/* we have a glibc-style strerror_r() */ -/* #undef HAVE_GLIBC_STRERROR_R */ - -/* Define to 1 if you have the `gmtime_r' function. */ -#define HAVE_GMTIME_R 1 - -/* if you have the gssapi libraries */ -/* #undef HAVE_GSSAPI */ - -/* Define to 1 if you have the header file. */ -/* #undef HAVE_GSSAPI_GSSAPI_GENERIC_H */ - -/* Define to 1 if you have the header file. */ -/* #undef HAVE_GSSAPI_GSSAPI_H */ - -/* Define to 1 if you have the header file. */ -/* #undef HAVE_GSSAPI_GSSAPI_KRB5_H */ - -/* if you have the GNU gssapi libraries */ -/* #undef HAVE_GSSGNU */ - -/* if you have the Heimdal gssapi libraries */ -/* #undef HAVE_GSSHEIMDAL */ - -/* if you have the MIT gssapi libraries */ -/* #undef HAVE_GSSMIT */ - -/* Define to 1 if you have the `idna_strerror' function. */ -/*#define HAVE_IDNA_STRERROR 1*/ - -/* Define to 1 if you have the `idn_free' function. */ -/*#define HAVE_IDN_FREE 1*/ - -/* Define to 1 if you have the header file. */ -/*#define HAVE_IDN_FREE_H 1*/ - -/* Define to 1 if you have the `inet_addr' function. */ -/*#define HAVE_INET_ADDR 1*/ - -/* Define to 1 if you have a IPv6 capable working inet_ntop function. */ -/*#define HAVE_INET_NTOP 1*/ - -/* Define to 1 if you have a IPv6 capable working inet_pton function. */ -/*#define HAVE_INET_PTON 1*/ - -/* Define to 1 if you have the header file. */ -#define HAVE_INTTYPES_H 1 - -/* Define to 1 if you have the ioctl function. */ -#define HAVE_IOCTL 1 - -/* Define to 1 if you have a working ioctl FIONBIO function. */ -#define HAVE_IOCTL_FIONBIO 1 - -/* Define to 1 if you have the ioctlsocket function. */ -/* #undef HAVE_IOCTLSOCKET */ - -/* Define to 1 if you have a working ioctlsocket FIONBIO function. */ -/* #undef HAVE_IOCTLSOCKET_FIONBIO */ - -/* Define to 1 if you have the IoctlSocket camel case function. */ -/* #undef HAVE_IOCTLSOCKET_CAMEL */ - -/* Define to 1 if you have a working IoctlSocket camel case FIONBIO - function. */ -/* #undef HAVE_IOCTLSOCKET_CAMEL_FIONBIO */ - -/* Define to 1 if you have the header file. */ -/* #undef HAVE_IO_H */ - -/* if you have the Kerberos4 libraries (including -ldes) */ -/* #undef HAVE_KRB4 */ - -/* Define to 1 if you have the `krb_get_our_ip_for_realm' function. */ -/* #undef HAVE_KRB_GET_OUR_IP_FOR_REALM */ - -/* Define to 1 if you have the header file. */ -/* #undef HAVE_KRB_H */ - -/* Define to 1 if you have the lber.h header file. */ -/*#define HAVE_LBER_H 1*/ - -/* Define to 1 if you have the ldapssl.h header file. */ -/* #undef HAVE_LDAPSSL_H */ - -/* Define to 1 if you have the ldap.h header file. */ -/*#define HAVE_LDAP_H 1*/ - -/* Use LDAPS implementation */ -/*#define HAVE_LDAP_SSL 1*/ - -/* Define to 1 if you have the ldap_ssl.h header file. */ -/* #undef HAVE_LDAP_SSL_H */ - -/* Define to 1 if you have the `ldap_url_parse' function. */ -/*#define HAVE_LDAP_URL_PARSE 1*/ - -/* Define to 1 if you have the header file. */ -/*#define HAVE_LIBGEN_H 1*/ - -/* Define to 1 if you have the `idn' library (-lidn). */ -/*#define HAVE_LIBIDN 1*/ - -/* Define to 1 if you have the `resolv' library (-lresolv). */ -/* #undef HAVE_LIBRESOLV */ - -/* Define to 1 if you have the `resolve' library (-lresolve). */ -/* #undef HAVE_LIBRESOLVE */ - -/* Define to 1 if you have the `socket' library (-lsocket). */ -/* #undef HAVE_LIBSOCKET */ - -/* Define to 1 if you have the `ssh2' library (-lssh2). */ -/*#define HAVE_LIBSSH2 1*/ - -/* Define to 1 if you have the header file. */ -/*#define HAVE_LIBSSH2_H 1*/ - -/* Define to 1 if you have the `ssl' library (-lssl). */ -/*#define HAVE_LIBSSL 1*/ - -/* Define to 1 if you have the header file. */ -#define HAVE_LIMITS_H 1 - -/* if your compiler supports LL */ -#define HAVE_LL 1 - -/* Define to 1 if you have the header file. */ -#define HAVE_LOCALE_H 1 - -/* Define to 1 if you have the `localtime_r' function. */ -#define HAVE_LOCALTIME_R 1 - -/* Define to 1 if the compiler supports the 'long long' data type. */ -#define HAVE_LONGLONG 1 - -/* Define to 1 if you have the malloc.h header file. */ -/*#define HAVE_MALLOC_H 1*/ - -/* Define to 1 if you have the header file. */ -#define HAVE_MEMORY_H 1 - -/* Define to 1 if you have the MSG_NOSIGNAL flag. */ -/*#define HAVE_MSG_NOSIGNAL 1*/ - -/* Define to 1 if you have the header file. */ -#define HAVE_NETDB_H 1 - -/* Define to 1 if you have the header file. */ -#define HAVE_NETINET_IN_H 1 - -/* Define to 1 if you have the header file. */ -/*#define HAVE_NETINET_TCP_H 1*/ - -/* Define to 1 if you have the header file. */ -#define HAVE_NET_IF_H 1 - -/* Define to 1 if NI_WITHSCOPEID exists and works. */ -/*#define HAVE_NI_WITHSCOPEID 1*/ - -/* we have no strerror_r() proto */ -/* #undef HAVE_NO_STRERROR_R_DECL */ - -/* if you have an old MIT gssapi library, lacking GSS_C_NT_HOSTBASED_SERVICE - */ -/* #undef HAVE_OLD_GSSMIT */ - -/* Define to 1 if you have the header file. */ -/*#define HAVE_OPENSSL_CRYPTO_H 1*/ - -/* Define to 1 if you have the header file. */ -/*#define HAVE_OPENSSL_ENGINE_H 1*/ - -/* Define to 1 if you have the header file. */ -/*#define HAVE_OPENSSL_ERR_H 1*/ - -/* Define to 1 if you have the header file. */ -/*#define HAVE_OPENSSL_PEM_H 1*/ - -/* Define to 1 if you have the header file. */ -/*#define HAVE_OPENSSL_PKCS12_H 1*/ - -/* Define to 1 if you have the header file. */ -/*#define HAVE_OPENSSL_RSA_H 1*/ - -/* Define to 1 if you have the header file. */ -/*#define HAVE_OPENSSL_SSL_H 1*/ - -/* Define to 1 if you have the header file. */ -/*#define HAVE_OPENSSL_X509_H 1*/ - -/* Define to 1 if you have the header file. */ -/* #undef HAVE_PEM_H */ - -/* Define to 1 if you have the `perror' function. */ -#define HAVE_PERROR 1 - -/* Define to 1 if you have the `pipe' function. */ -#define HAVE_PIPE 1 - -/* Define to 1 if you have the `poll' function. */ -/*#define HAVE_POLL 1*/ - -/* If you have a fine poll */ -/*#define HAVE_POLL_FINE 1*/ - -/* Define to 1 if you have the header file. */ -/*#define HAVE_POLL_H 1*/ - -/* we have a POSIX-style strerror_r() */ -#define HAVE_POSIX_STRERROR_R 1 - -/* Define to 1 if you have the header file. */ -#define HAVE_PWD_H 1 - -/* Define to 1 if you have the `RAND_egd' function. */ -#define HAVE_RAND_EGD 1 - -/* Define to 1 if you have the `RAND_screen' function. */ -/* #undef HAVE_RAND_SCREEN */ - -/* Define to 1 if you have the `RAND_status' function. */ -/*#define HAVE_RAND_STATUS 1*/ - -/* Define to 1 if you have the recv function. */ -#define HAVE_RECV 1 - -/* Define to 1 if you have the recvfrom function. */ -#define HAVE_RECVFROM 1 - -/* Define to 1 if you have the header file. */ -/* #undef HAVE_RSA_H */ - -/* Define to 1 if you have the select function. */ -#define HAVE_SELECT 1 - -/* Define to 1 if you have the send function. */ -#define HAVE_SEND 1 - -/* Define to 1 if you have the header file. */ -#define HAVE_SETJMP_H 1 - -/* Define to 1 if you have the `setlocale' function. */ -#define HAVE_SETLOCALE 1 - -/* Define to 1 if you have the `setmode' function. */ -/* #undef HAVE_SETMODE */ - -/* Define to 1 if you have the `setrlimit' function. */ -/*#define HAVE_SETRLIMIT 1*/ - -/* Define to 1 if you have the setsockopt function. */ -/* #undef HAVE_SETSOCKOPT */ - -/* Define to 1 if you have a working setsockopt SO_NONBLOCK function. */ -/* #undef HAVE_SETSOCKOPT_SO_NONBLOCK */ - -/* Define to 1 if you have the header file. */ -/*#define HAVE_SGTTY_H 1*/ - -/* Define to 1 if you have the `sigaction' function. */ -/*#define HAVE_SIGACTION 1*/ - -/* Define to 1 if you have the `siginterrupt' function. */ -/*#define HAVE_SIGINTERRUPT 1*/ - -/* Define to 1 if you have the `signal' function. */ -/*#define HAVE_SIGNAL 1*/ - -/* Define to 1 if you have the header file. */ -#define HAVE_SIGNAL_H 1 - -/* If you have sigsetjmp */ -/*#define HAVE_SIGSETJMP 1*/ - -/* Define to 1 if sig_atomic_t is an available typedef. */ -/*#define HAVE_SIG_ATOMIC_T 1*/ - -/* Define to 1 if sig_atomic_t is already defined as volatile. */ -/* #undef HAVE_SIG_ATOMIC_T_VOLATILE */ - -/* Define to 1 if you have the `socket' function. */ -#define HAVE_SOCKET 1 - -/* Define to 1 if you have the `SSL_get_shutdown' function. */ -/*#define HAVE_SSL_GET_SHUTDOWN 1*/ - -/* Define to 1 if you have the header file. */ -/* #undef HAVE_SSL_H */ - -/* Define to 1 if you have the header file. */ -#define HAVE_STDBOOL_H 1 - -/* Define to 1 if you have the header file. */ -#define HAVE_STDINT_H 1 - -/* Define to 1 if you have the header file. */ -#define HAVE_STDIO_H 1 - -/* Define to 1 if you have the header file. */ -#define HAVE_STDLIB_H 1 - -/* Define to 1 if you have the `strcasecmp' function. */ -#define HAVE_STRCASECMP 1 - -/* Define to 1 if you have the `strcmpi' function. */ -/* #undef HAVE_STRCMPI */ - -/* Define to 1 if you have the `strdup' function. */ -#define HAVE_STRDUP 1 - -/* Define to 1 if you have the `strerror_r' function. */ -#define HAVE_STRERROR_R 1 - -/* Define to 1 if you have the `stricmp' function. */ -/* #undef HAVE_STRICMP */ - -/* Define to 1 if you have the header file. */ -#define HAVE_STRINGS_H 1 - -/* Define to 1 if you have the header file. */ -#define HAVE_STRING_H 1 - -/* Define to 1 if you have the `strlcpy' function. */ -#define HAVE_STRLCPY 1 - -/* Define to 1 if you have the `strstr' function. */ -#define HAVE_STRSTR 1 - -/* Define to 1 if you have the `strtok_r' function. */ -#define HAVE_STRTOK_R 1 - -/* Define to 1 if you have the `strtoll' function. */ -#define HAVE_STRTOLL 1 - -/* if struct sockaddr_storage is defined */ -#define HAVE_STRUCT_SOCKADDR_STORAGE 1 - -/* Define to 1 if you have the timeval struct. */ -#define HAVE_STRUCT_TIMEVAL 1 - -/* Define to 1 if you have the header file. */ -#define HAVE_SYS_FILIO_H 1 - -/* Define to 1 if you have the header file. */ -#define HAVE_SYS_IOCTL_H 1 - -/* Define to 1 if you have the header file. */ -#define HAVE_SYS_PARAM_H 1 - -/* Define to 1 if you have the header file. */ -/*#define HAVE_SYS_POLL_H 1*/ - -/* Define to 1 if you have the header file. */ -#define HAVE_SYS_RESOURCE_H 1 - -/* Define to 1 if you have the header file. */ -#define HAVE_SYS_SELECT_H 1 - -/* Define to 1 if you have the header file. */ -#define HAVE_SYS_SOCKET_H 1 - -/* Define to 1 if you have the header file. */ -#define HAVE_SYS_SOCKIO_H 1 - -/* Define to 1 if you have the header file. */ -#define HAVE_SYS_STAT_H 1 - -/* Define to 1 if you have the header file. */ -#define HAVE_SYS_TIME_H 1 - -/* Define to 1 if you have the header file. */ -#define HAVE_SYS_TYPES_H 1 - -/* Define to 1 if you have the header file. */ -/* #undef HAVE_SYS_UTIME_H */ - -/* Define to 1 if you have the header file. */ -/*#define HAVE_TERMIOS_H 1*/ - -/* Define to 1 if you have the header file. */ -/*#define HAVE_TERMIO_H 1*/ - -/* Define to 1 if you have the header file. */ -#define HAVE_TIME_H 1 - -/* Define to 1 if you have the header file. */ -/*#define HAVE_TLD_H 1*/ - -/* Define to 1 if you have the `tld_strerror' function. */ -/*#define HAVE_TLD_STRERROR 1*/ - -/* Define to 1 if you have the `uname' function. */ -#define HAVE_UNAME 1 - -/* Define to 1 if you have the header file. */ -#define HAVE_UNISTD_H 1 - -/* Define to 1 if you have the `utime' function. */ -#define HAVE_UTIME 1 - -/* Define to 1 if you have the header file. */ -#define HAVE_UTIME_H 1 - -/* Define to 1 if compiler supports C99 variadic macro style. */ -#define HAVE_VARIADIC_MACROS_C99 1 - -/* Define to 1 if compiler supports old gcc variadic macro style. */ -/*#define HAVE_VARIADIC_MACROS_GCC 1*/ - -/* Define to 1 if you have the winber.h header file. */ -/* #undef HAVE_WINBER_H */ - -/* Define to 1 if you have the windows.h header file. */ -/* #undef HAVE_WINDOWS_H */ - -/* Define to 1 if you have the winldap.h header file. */ -/* #undef HAVE_WINLDAP_H */ - -/* Define to 1 if you have the winsock2.h header file. */ -/* #undef HAVE_WINSOCK2_H */ - -/* Define to 1 if you have the winsock.h header file. */ -/* #undef HAVE_WINSOCK_H */ - -/* Define this symbol if your OS supports changing the contents of argv */ -/*#define HAVE_WRITABLE_ARGV 1*/ - -/* Define to 1 if you have the ws2tcpip.h header file. */ -/* #undef HAVE_WS2TCPIP_H */ - -/* Define to 1 if you have the header file. */ -/* #undef HAVE_X509_H */ - -/* Define to 1 if you need the lber.h header file even with ldap.h */ -/* #undef NEED_LBER_H */ - -/* Define to 1 if you need the malloc.h header file even with stdlib.h */ -/* #undef NEED_MALLOC_H */ - -/* Define to 1 if _REENTRANT preprocessor symbol must be defined. */ -/* #undef NEED_REENTRANT */ - -/* Define to 1 if _THREAD_SAFE preprocessor symbol must be defined. */ -/* #undef NEED_THREAD_SAFE */ - -/* cpu-machine-OS */ -#ifdef __WINS__ -#define OS "i386-pc-epoc32" -#elif __MARM__ -#define OS "arm-unknown-epoc32" -#else -/* This won't happen on any current Symbian version */ -#define OS "unknown-unknown-epoc32" -#endif - -/* Name of package */ -/*#define PACKAGE "curl"*/ - -/* Define to the address where bug reports for this package should be sent. */ -/*#define PACKAGE_BUGREPORT \ - "a suitable curl mailing list => https://curl.haxx.se/mail/"*/ - -/* Define to the full name of this package. */ -/*#define PACKAGE_NAME "curl"*/ - -/* Define to the full name and version of this package. */ -/*#define PACKAGE_STRING "curl -"*/ - -/* Define to the one symbol short name of this package. */ -/*#define PACKAGE_TARNAME "curl"*/ - -/* Define to the version of this package. */ -/*#define PACKAGE_VERSION "-"*/ - -/* a suitable file to read random data from */ -/*#define RANDOM_FILE "/dev/urandom"*/ - -#define RECV_TYPE_ARG1 int -#define RECV_TYPE_ARG2 void * -#define RECV_TYPE_ARG3 size_t -#define RECV_TYPE_ARG4 int -#define RECV_TYPE_RETV ssize_t - -#define RECVFROM_TYPE_ARG1 int -#define RECVFROM_TYPE_ARG2 void -#define RECVFROM_TYPE_ARG3 size_t -#define RECVFROM_TYPE_ARG4 int -#define RECVFROM_TYPE_ARG5 struct sockaddr -#define RECVFROM_TYPE_ARG6 size_t -#define RECVFROM_TYPE_RETV ssize_t -#define RECVFROM_TYPE_ARG2_IS_VOID 1 - -#define SEND_TYPE_ARG1 int -#define SEND_QUAL_ARG2 const -#define SEND_TYPE_ARG2 void * -#define SEND_TYPE_ARG3 size_t -#define SEND_TYPE_ARG4 int -#define SEND_TYPE_RETV ssize_t - - -/* Define as the return type of signal handlers (`int' or `void'). */ -/*#define RETSIGTYPE void*/ - -/* Define to the type of arg 1 for `select'. */ -#define SELECT_TYPE_ARG1 int - -/* Define to the type of args 2, 3 and 4 for `select'. */ -#define SELECT_TYPE_ARG234 (fd_set *) - -/* Define to the type of arg 5 for `select'. */ -#define SELECT_TYPE_ARG5 (struct timeval *) - -/* The size of `int', as computed by sizeof. */ -#define SIZEOF_INT 4 - -/* The size of `off_t', as computed by sizeof. */ -#define SIZEOF_OFF_T 8 - -/* The size of `short', as computed by sizeof. */ -#define SIZEOF_SHORT 2 - -/* The size of `size_t', as computed by sizeof. */ -#define SIZEOF_SIZE_T 4 - -/* The size of `time_t', as computed by sizeof. */ -#define SIZEOF_TIME_T 4 - -/* Define to 1 if you have the ANSI C header files. */ -#define STDC_HEADERS 1 - -/* Define to 1 if you can safely include both and . */ -#define TIME_WITH_SYS_TIME 1 - -/* Define if you want to enable c-ares support */ -/* #undef USE_ARES */ - -/* Define to disable non-blocking sockets */ -/* #undef USE_BLOCKING_SOCKETS */ - -/* if GnuTLS is enabled */ -/* #undef USE_GNUTLS */ - -/* if libSSH2 is in use */ -/*#define USE_LIBSSH2 1*/ - -/* If you want to build curl with the built-in manual */ -/*#define USE_MANUAL 1*/ - -/* if NSS is enabled */ -/* #undef USE_NSS */ - -/* to enable SSPI support */ -/* #undef USE_WINDOWS_SSPI */ - -/* Define to 1 if using yaSSL in OpenSSL compatibility mode. */ -/* #undef USE_YASSLEMUL */ - -/* Version number of package */ -/*#define VERSION "7.18.2-CVS"*/ - -/* Define to avoid automatic inclusion of winsock.h */ -/* #undef WIN32_LEAN_AND_MEAN */ - -/* Define to 1 if on AIX 3. - System headers sometimes define this. - We just want to avoid a redefinition error message. */ -#ifndef _ALL_SOURCE -/* # undef _ALL_SOURCE */ -#endif - -/* Number of bits in a file offset, on hosts where this is settable. */ -#define _FILE_OFFSET_BITS 64 - -/* Define for large files, on AIX-style hosts. */ -/* #undef _LARGE_FILES */ - -/* Define to empty if `const' does not conform to ANSI C. */ -/* #undef const */ - -/* type to use in place of in_addr_t if not defined */ -/* #undef 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 does not define. */ -/* #undef size_t */ - -/* the signed version of size_t */ -/* #undef ssize_t */ - -/* Enabling curl debug mode when building in Symbian debug mode would work */ -/* except that debug mode introduces new exports that must be frozen. */ -#ifdef _DEBUG -/* #define CURLDEBUG */ -#endif - -/* sys/cdefs.h fails to define this for WINSCW prior to Symbian OS ver. 9.4 */ -#ifndef __LONG_LONG_SUPPORTED -#define __LONG_LONG_SUPPORTED -#endif - -/* Enable appropriate header only when zlib support is enabled */ -#ifdef HAVE_LIBZ -#define HAVE_ZLIB_H 1 -#endif - -#endif /* HEADER_CURL_CONFIG_SYMBIAN_H */ diff --git a/contrib/curl/lib/config-tpf.h b/contrib/curl/lib/config-tpf.h deleted file mode 100644 index d1714fdfc4..0000000000 --- a/contrib/curl/lib/config-tpf.h +++ /dev/null @@ -1,772 +0,0 @@ -#ifndef HEADER_CURL_CONFIG_TPF_H -#define HEADER_CURL_CONFIG_TPF_H -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) 1998 - 2015, Daniel Stenberg, , et al. - * - * This software is licensed as described in the file COPYING, which - * you should have received as part of this distribution. The terms - * are also available at https://curl.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. - * - ***************************************************************************/ - -/* ================================================================ */ -/* Hand crafted config file for TPF */ -/* ================================================================ */ - -/* ---------------------------------------------------------------- */ -/* FEATURES, FUNCTIONS, and DEFINITIONS */ -/* ---------------------------------------------------------------- */ - -/* NOTE: Refer also to the .mak file for some of the flags below */ - -/* to disable cookies support */ -/* #undef CURL_DISABLE_COOKIES */ - -/* to disable cryptographic authentication */ -/* #undef CURL_DISABLE_CRYPTO_AUTH */ - -/* to disable DICT */ -/* #undef CURL_DISABLE_DICT */ - -/* to disable FILE */ -/* #undef CURL_DISABLE_FILE */ - -/* to disable FTP */ -/* #undef CURL_DISABLE_FTP */ - -/* to disable HTTP */ -/* #undef CURL_DISABLE_HTTP */ - -/* to disable LDAP */ -/* #undef CURL_DISABLE_LDAP */ - -/* to disable TELNET */ -/* #undef CURL_DISABLE_TELNET */ - -/* to disable TFTP */ -/* #undef CURL_DISABLE_TFTP */ - -/* to disable verbose strings */ -/* #undef CURL_DISABLE_VERBOSE_STRINGS */ - -/* lber dynamic library file */ -/* #undef DL_LBER_FILE */ - -/* ldap dynamic library file */ -/* #undef DL_LDAP_FILE */ - -/* your Entropy Gathering Daemon socket pathname */ -/* #undef EGD_SOCKET */ - -/* Define if you want to enable IPv6 support */ -/* #undef ENABLE_IPV6 */ - -/* Define if struct sockaddr_in6 has the sin6_scope_id member */ -/* #undef HAVE_SOCKADDR_IN6_SIN6_SCOPE_ID */ - -/* Define to the type of arg 1 for getnameinfo. */ -/* #undef GETNAMEINFO_TYPE_ARG1 */ - -/* Define to the type of arg 2 for getnameinfo. */ -/* #undef GETNAMEINFO_TYPE_ARG2 */ - -/* Define to the type of args 4 and 6 for getnameinfo. */ -/* #undef GETNAMEINFO_TYPE_ARG46 */ - -/* Define to the type of arg 7 for getnameinfo. */ -/* #undef GETNAMEINFO_TYPE_ARG7 */ - -/* Define to 1 if you have the alarm function. */ -#define HAVE_ALARM 1 - -/* Define to 1 if you have the header file. */ -#define HAVE_ARPA_INET_H 1 - -/* Define to 1 if you have the header file. */ -/* #undef HAVE_ARPA_TFTP_H */ - -/* Define to 1 if you have the header file. */ -#define HAVE_ASSERT_H 1 - -/* Define to 1 if you have the `basename' function. */ -#define HAVE_BASENAME 1 - -/* Define to 1 if you have the `closesocket' function. */ -/* #undef HAVE_CLOSESOCKET */ - -/* Define to 1 if you have the `CRYPTO_cleanup_all_ex_data' function. */ -/* #undef HAVE_CRYPTO_CLEANUP_ALL_EX_DATA */ -#define HAVE_CRYPTO_CLEANUP_ALL_EX_DATA 1 - -/* Define to 1 if you have the header file. */ -/* #undef HAVE_CRYPTO_H */ -#define HAVE_CRYPTO_H 1 - -/* Define to 1 if you have the header file. */ -/* #undef HAVE_DES_H */ -#define HAVE_DES_H 1 - -/* Define to 1 if you have the `ENGINE_load_builtin_engines' function. */ -/* #undef HAVE_ENGINE_LOAD_BUILTIN_ENGINES */ -#define HAVE_ENGINE_LOAD_BUILTIN_ENGINES 1 - -/* Define to 1 if you have the header file. */ -#define HAVE_ERRNO_H 1 - -/* Define to 1 if you have the header file. */ -/* #undef HAVE_ERR_H */ -#define HAVE_ERR_H 1 - -/* Define to 1 if you have the header file. */ -#define HAVE_FCNTL_H 1 - -/* Define to 1 if you have the fcntl function. */ -#define HAVE_FCNTL 1 - -/* Define to 1 if you have a working fcntl O_NONBLOCK function. */ -#define HAVE_FCNTL_O_NONBLOCK 1 - -/* Define to 1 if you have the `fork' function. */ -/* #undef HAVE_FORK */ -#define HAVE_FORK 1 - -/* Define to 1 if you have the `ftruncate' function. */ -#define HAVE_FTRUNCATE 1 - -/* Define if getaddrinfo exists and works */ -/* #undef HAVE_GETADDRINFO */ - -/* Define to 1 if you have the `geteuid' function. */ -#define HAVE_GETEUID 1 - -/* Define to 1 if you have the `gethostbyaddr' function. */ -#define HAVE_GETHOSTBYADDR 1 - -/* If you have gethostbyname */ -#define HAVE_GETHOSTBYNAME 1 - -/* Define to 1 if you have the `gethostbyname_r' function. */ -/* #undef HAVE_GETHOSTBYNAME_R */ - -/* gethostbyname_r() takes 3 args */ -/* #undef HAVE_GETHOSTBYNAME_R_3 */ - -/* gethostbyname_r() takes 5 args */ -/* #undef HAVE_GETHOSTBYNAME_R_5 */ - -/* gethostbyname_r() takes 6 args */ -/* #undef HAVE_GETHOSTBYNAME_R_6 1 */ - -/* Define to 1 if you have the getnameinfo function. */ -/* #undef HAVE_GETNAMEINFO */ - -/* Define to 1 if you have the `getpass_r' function. */ -/* #undef HAVE_GETPASS_R */ - -/* Define to 1 if you have the `getprotobyname' function. */ -/* #undef HAVE_GETPROTOBYNAME */ - -/* Define to 1 if you have the `getpwuid' function. */ -#define HAVE_GETPWUID 1 - -/* Define to 1 if you have the `getrlimit' function. */ -/* #undef HAVE_GETRLIMIT */ - -/* Define to 1 if you have the `gettimeofday' function. */ -#define HAVE_GETTIMEOFDAY 1 - -/* we have a glibc-style strerror_r() */ -/* #undef HAVE_GLIBC_STRERROR_R */ -#define HAVE_GLIBC_STRERROR_R 1 - -/* Define to 1 if you have the `gmtime_r' function. */ -#define HAVE_GMTIME_R 1 - -/* if you have the gssapi libraries */ -/* #undef HAVE_GSSAPI */ - -/* if you have the GNU gssapi libraries */ -/* #undef HAVE_GSSGNU */ - -/* if you have the Heimdal gssapi libraries */ -/* #undef HAVE_GSSHEIMDAL */ - -/* if you have the MIT gssapi libraries */ -/* #undef HAVE_GSSMIT */ - -/* Define to 1 if you have the `iconv' functions. */ -#define HAVE_ICONV 1 - -/* Define to 1 if you have the `idna_strerror' function. */ -/* #undef HAVE_IDNA_STRERROR */ - -/* Define to 1 if you have the `idn_free' function. */ -/* #undef HAVE_IDN_FREE */ - -/* Define to 1 if you have the header file. */ -/* #undef HAVE_IDN_FREE_H */ - -/* Define to 1 if you have the `inet_addr' function. */ -#define HAVE_INET_ADDR 1 - -/* Define to 1 if you have a IPv6 capable working inet_ntop function. */ -/* #undef HAVE_INET_NTOP */ - -/* Define to 1 if you have a IPv6 capable working inet_pton function. */ -/* #undef HAVE_INET_PTON */ - -/* Define to 1 if you have the header file. */ -#define HAVE_INTTYPES_H 1 - -/* Define to 1 if you have the ioctl function. */ -#define HAVE_IOCTL 1 - -/* Define to 1 if you have a working ioctl FIONBIO function. */ -#define HAVE_IOCTL_FIONBIO 1 - -/* Define to 1 if you have the ioctlsocket function. */ -/* #undef HAVE_IOCTLSOCKET */ - -/* Define to 1 if you have a working ioctlsocket FIONBIO function. */ -/* #undef HAVE_IOCTLSOCKET_FIONBIO */ - -/* Define to 1 if you have the IoctlSocket camel case function. */ -/* #undef HAVE_IOCTLSOCKET_CAMEL */ - -/* Define to 1 if you have a working IoctlSocket camel case FIONBIO - function. */ -/* #undef HAVE_IOCTLSOCKET_CAMEL_FIONBIO */ - -/* Define to 1 if you have the header file. */ -/* #undef HAVE_IO_H */ - -/* if you have the Kerberos4 libraries (including -ldes) */ -/* #undef HAVE_KRB4 */ - -/* Define to 1 if you have the `krb_get_our_ip_for_realm' function. */ -/* #undef HAVE_KRB_GET_OUR_IP_FOR_REALM */ - -/* Define to 1 if you have the header file. */ -/* #undef HAVE_KRB_H */ - -/* Define to 1 if you have the header file. */ -/* #undef HAVE_LIBGEN_H 1 */ - -/* Define to 1 if you have the `idn' library (-lidn). */ -/* #undef HAVE_LIBIDN */ - -/* Define to 1 if you have the `resolv' library (-lresolv). */ -/* #undef HAVE_LIBRESOLV */ - -/* Define to 1 if you have the `resolve' library (-lresolve). */ -/* #undef HAVE_LIBRESOLVE */ - -/* Define to 1 if you have the `socket' library (-lsocket). */ -/* #undef HAVE_LIBSOCKET */ - -/* Define to 1 if you have the `ssl' library (-lssl). */ -/* #undef HAVE_LIBSSL */ -#define HAVE_LIBSSL 1 - -/* if zlib is available */ -/* #undef HAVE_LIBZ */ - -/* Define to 1 if you have the header file. */ -#define HAVE_LIMITS_H 1 - -/* if your compiler supports LL */ -#define HAVE_LL 1 - -/* Define to 1 if you have the header file. */ -#define HAVE_LOCALE_H 1 - -/* Define to 1 if you have the `localtime_r' function. */ -#define HAVE_LOCALTIME_R 1 - -/* Define to 1 if the compiler supports the 'long long' data type. */ -#define HAVE_LONGLONG 1 - -/* Define to 1 if you need the malloc.h header file even with stdlib.h */ -/* #undef NEED_MALLOC_H */ - -/* Define to 1 if you have the header file. */ -#define HAVE_MEMORY_H 1 - -/* Define to 1 if you have the header file. */ -#define HAVE_NETDB_H 1 - -/* Define to 1 if you have the header file. */ -#define HAVE_NETINET_IN_H 1 - -/* Define to 1 if you have the header file. */ -/* undef HAVE_NETINET_TCP_H */ - -/* Define to 1 if you have the header file. */ -#define HAVE_NET_IF_H 1 - -/* Define if NI_WITHSCOPEID exists and works */ -/* #undef HAVE_NI_WITHSCOPEID */ - -/* we have no strerror_r() proto */ -/* #undef HAVE_NO_STRERROR_R_DECL */ - -/* Define to 1 if you have the header file. */ -/* #undef HAVE_OPENSSL_CRYPTO_H */ -#define HAVE_OPENSSL_CRYPTO_H 1 - -/* Define to 1 if you have the header file. */ -/* #undef HAVE_OPENSSL_ENGINE_H */ -#define HAVE_OPENSSL_ENGINE_H 1 - -/* Define to 1 if you have the header file. */ -/* #undef HAVE_OPENSSL_ERR_H */ -#define HAVE_OPENSSL_ERR_H 1 - -/* Define to 1 if you have the header file. */ -/* #undef HAVE_OPENSSL_PEM_H */ -#define HAVE_OPENSSL_PEM_H 1 - -/* Define to 1 if you have the header file. */ -/* #undef HAVE_OPENSSL_PKCS12_H */ -#define HAVE_OPENSSL_PKCS12_H 1 - -/* Define to 1 if you have the header file. */ -/* #undef HAVE_OPENSSL_RSA_H */ -#define HAVE_OPENSSL_RSA_H 1 - -/* Define to 1 if you have the header file. */ -/* #undef HAVE_OPENSSL_SSL_H */ -#define HAVE_OPENSSL_SSL_H 1 - -/* Define to 1 if you have the header file. */ -/* #undef HAVE_OPENSSL_X509_H */ -#define HAVE_OPENSSL_X509_H 1 - -/* Define to 1 if you have the header file. */ -/* #undef HAVE_PEM_H */ -#define HAVE_PEM_H 1 - -/* Define to 1 if you have the `perror' function. */ -#define HAVE_PERROR 1 - -/* Define to 1 if you have the `pipe' function. */ -#define HAVE_PIPE 1 - -/* Define to 1 if you have the `poll' function. */ -/* #undef HAVE_POLL */ - -/* If you have a fine poll */ -/* #undef HAVE_POLL_FINE */ - -/* we have a POSIX-style strerror_r() */ -/* #undef HAVE_POSIX_STRERROR_R */ - -/* Define to 1 if you have the header file. */ -#define HAVE_PWD_H 1 - -/* Define to 1 if you have the `RAND_egd' function. */ -/* #undef HAVE_RAND_EGD */ -#define HAVE_RAND_EGD 1 - -/* Define to 1 if you have the `RAND_screen' function. */ -/* #undef HAVE_RAND_SCREEN */ - -/* Define to 1 if you have the `RAND_status' function. */ -/* #undef HAVE_RAND_STATUS */ -#define HAVE_RAND_STATUS 1 - -/* Define to 1 if you have the header file. */ -/* #undef HAVE_RSA_H */ -#define HAVE_RSA_H 1 - -/* Define to 1 if you have the `select' function. */ -#define HAVE_SELECT 1 - -/* Define to 1 if you have the header file. */ -#define HAVE_SETJMP_H 1 - -/* Define to 1 if you have the `setlocale' function. */ -#define HAVE_SETLOCALE 1 - -/* Define to 1 if you have the `setrlimit' function. */ -#define HAVE_SETRLIMIT 1 - -/* Define to 1 if you have the setsockopt function. */ -/* #undef HAVE_SETSOCKOPT */ - -/* Define to 1 if you have a working setsockopt SO_NONBLOCK function. */ -/* #undef HAVE_SETSOCKOPT_SO_NONBLOCK */ - -/* Define to 1 if you have the header file. */ -/* #undef HAVE_SGTTY_H 1 */ - -/* Define to 1 if you have the `sigaction' function. */ -#define HAVE_SIGACTION 1 - -/* Define to 1 if you have the `siginterrupt' function. */ -/* #undef HAVE_SIGINTERRUPT */ - -/* Define to 1 if you have the `signal' function. */ -#define HAVE_SIGNAL 1 - -/* Define to 1 if you have the header file. */ -#define HAVE_SIGNAL_H 1 - -/* Define to 1 if sig_atomic_t is an available typedef. */ -#define HAVE_SIG_ATOMIC_T 1 - -/* Define to 1 if sig_atomic_t is already defined as volatile. */ -/* #undef HAVE_SIG_ATOMIC_T_VOLATILE */ - -/* If you have sigsetjmp */ -/* #undef HAVE_SIGSETJMP */ - -/* Define to 1 if you have the `socket' function. */ -#define HAVE_SOCKET 1 - -/* Define to 1 if you have the header file. */ -/* #undef HAVE_SSL_H */ -#define HAVE_SSL_H 1 - -/* Define to 1 if you have the header file. */ -#define HAVE_STDINT_H 1 - -/* Define to 1 if you have the header file. */ -#define HAVE_STDLIB_H 1 - -/* Define to 1 if you have the `strcasecmp' function. */ -#define HAVE_STRCASECMP 1 - -/* Define to 1 if you have the `strcmpi' function. */ -/* #undef HAVE_STRCMPI */ - -/* Define to 1 if you have the `strdup' function. */ -#define HAVE_STRDUP 1 - -/* Define to 1 if you have the `strerror_r' function. */ -#define HAVE_STRERROR_R 1 - -/* Define to 1 if you have the `stricmp' function. */ -/* #undef HAVE_STRICMP */ -#define HAVE_STRICMP 1 - -/* Define to 1 if you have the header file. */ -#define HAVE_STRINGS_H 1 - -/* Define to 1 if you have the header file. */ -#define HAVE_STRING_H 1 - -/* Define to 1 if you have the `strlcpy' function. */ -/* #undef HAVE_STRLCPY */ - -/* Define to 1 if you have the `strstr' function. */ -#define HAVE_STRSTR 1 - -/* Define to 1 if you have the `strtok_r' function. */ -#define HAVE_STRTOK_R 1 - -/* Define to 1 if you have the `strtoll' function. */ -#define HAVE_STRTOLL 1 - -/* if struct sockaddr_storage is defined */ -/* #undef HAVE_STRUCT_SOCKADDR_STORAGE */ - -/* Define this if you have struct timeval */ -#define HAVE_STRUCT_TIMEVAL 1 - -/* Define to 1 if you have the header file. */ -#define HAVE_SYS_FILIO_H 1 - -/* Define to 1 if you have the header file. */ -#define HAVE_SYS_IOCTL_H 1 - -/* Define to 1 if you have the header file. */ -#define HAVE_SYS_PARAM_H 1 - -/* Define to 1 if you have the header file. */ -/* #undef HAVE_SYS_POLL_H */ - -/* Define to 1 if you have the header file. */ -#define HAVE_SYS_RESOURCE_H 1 - -/* Define to 1 if you have the header file. */ -#define HAVE_SYS_SELECT_H 1 - -/* Define to 1 if you have the header file. */ -#define HAVE_SYS_SOCKET_H 1 - -/* Define to 1 if you have the header file. */ -/* #undef HAVE_SYS_SOCKIO_H */ -#define HAVE_SYS_SOCKIO_H 1 - -/* Define to 1 if you have the header file. */ -#define HAVE_SYS_STAT_H 1 - -/* Define to 1 if you have the header file. */ -#define HAVE_SYS_TIME_H 1 - -/* Define to 1 if you have the header file. */ -#define HAVE_SYS_TYPES_H 1 - -/* Define to 1 if you have the header file. */ -/* #undef HAVE_SYS_UTIME_H */ - -/* Define to 1 if you have the header file. */ -/* #undef HAVE_TERMIOS_H */ - -/* Define to 1 if you have the header file. */ -/* #undef HAVE_TERMIO_H */ - -/* Define to 1 if you have the header file. */ -#define HAVE_TIME_H 1 - -/* Define to 1 if you have the header file. */ -/* #undef HAVE_TLD_H */ - -/* Define to 1 if you have the `tld_strerror' function. */ -/* #undef HAVE_TLD_STRERROR */ - -/* Define to 1 if you have the header file. */ -#define HAVE_UNISTD_H 1 - -/* Define to 1 if you have the `utime' function. */ -#define HAVE_UTIME 1 - -/* Define to 1 if you have the header file. */ -#define HAVE_UTIME_H 1 - -/* Define to 1 if you have the header file. */ -/* #undef HAVE_WINSOCK2_H */ - -/* Define to 1 if you have the header file. */ -/* #undef HAVE_WINSOCK_H */ - -/* Define this symbol if your OS supports changing the contents of argv */ -/* #undef HAVE_WRITABLE_ARGV */ - -/* Define to 1 if you have the ws2tcpip.h header file. */ -/* #undef HAVE_WS2TCPIP_H */ - -/* Define to 1 if you have the header file. */ -/* #undef HAVE_X509_H */ - -/* if you have the zlib.h header file */ -/* #undef HAVE_ZLIB_H */ - -/* Define to 1 if _REENTRANT preprocessor symbol must be defined. */ -/* #undef NEED_REENTRANT */ - -/* Define to 1 if _THREAD_SAFE preprocessor symbol must be defined. */ -/* #undef NEED_THREAD_SAFE */ - -/* cpu-machine-OS */ -#define OS "s390x-ibm-tpf" - -/* Name of package */ -#define PACKAGE "curl" - -/* Define to the address where bug reports for this package should be sent. */ -#define PACKAGE_BUGREPORT \ - "a suitable curl mailing list => https://curl.haxx.se/mail/" - -/* Define to the full name of this package. */ -#define PACKAGE_NAME "curl" - -/* Define to the full name and version of this package. */ -#define PACKAGE_STRING "curl -" - -/* Define to the one symbol short name of this package. */ -#define PACKAGE_TARNAME "curl" - -/* Define to the version of this package. */ -#define PACKAGE_VERSION "-" - -/* a suitable file to read random data from */ -/* #undef RANDOM_FILE */ - -/* Define as the return type of signal handlers (`int' or `void'). */ -#define RETSIGTYPE void - -/* Define to the type of arg 1 for `select'. */ -#define SELECT_TYPE_ARG1 int - -/* Define to the type of args 2, 3 and 4 for `select'. */ -#define SELECT_TYPE_ARG234 (fd_set *) - -/* Define to the type of arg 5 for `select'. */ -#define SELECT_TYPE_ARG5 (struct timeval *) - -/* The size of `int', as computed by sizeof. */ -#define SIZEOF_INT 4 - -/* The size of `off_t', as computed by sizeof. */ -#define SIZEOF_OFF_T 8 - -/* The size of `short', as computed by sizeof. */ -#define SIZEOF_SHORT 2 - -/* The size of `size_t', as computed by sizeof. */ -#define SIZEOF_SIZE_T 8 - -/* The size of `time_t', as computed by sizeof. */ -#define SIZEOF_TIME_T 8 - -/* Define to 1 if you have the ANSI C header files. */ -#define STDC_HEADERS 1 - -/* Define to 1 if you can safely include both and . */ -#define TIME_WITH_SYS_TIME 1 - -/* Define if you want to enable ares support */ -/* #undef USE_ARES */ - -/* Define to disable non-blocking sockets */ -/* #undef USE_BLOCKING_SOCKETS */ - -/* if GnuTLS is enabled */ -/* #undef USE_GNUTLS */ - -/* If you want to build curl with the built-in manual */ -/* #undef USE_MANUAL */ - -/* if OpenSSL is in use */ -/* #undef USE_OPENSSL */ - -/* if SSL is enabled */ -/* #undef USE_OPENSSL */ - -/* to enable SSPI support */ -/* #undef USE_WINDOWS_SSPI */ - -/* Version number of package */ -#define VERSION "not-used" - -/* Define to avoid automatic inclusion of winsock.h */ -/* #undef WIN32_LEAN_AND_MEAN */ - -/* Define to 1 if on AIX 3. - System headers sometimes define this. - We just want to avoid a redefinition error message. */ -#ifndef _ALL_SOURCE -/* # undef _ALL_SOURCE */ -#endif - -/* Number of bits in a file offset, on hosts where this is settable. */ -/* #undef _FILE_OFFSET_BITS */ - -/* Define for large files, on AIX-style hosts. */ -/* #undef _LARGE_FILES */ - -/* Define to empty if `const' does not conform to ANSI C. */ -/* #undef const */ - -/* type to use in place of in_addr_t if not defined */ -/* #undef in_addr_t */ - -/* Define to `unsigned' if does not define. */ -/* #undef size_t */ - -/* the signed version of size_t */ -/* #undef ssize_t */ - -/* Define to 1 if you have the getnameinfo function. */ -/* #undef HAVE_GETNAMEINFO 1 */ - -/* Define to the type qualifier of arg 1 for getnameinfo. */ -/* #undef GETNAMEINFO_QUAL_ARG1 const */ - -/* Define to the type of arg 1 for getnameinfo. */ -/* #undef GETNAMEINFO_TYPE_ARG1 struct sockaddr * */ - -/* Define to the type of arg 2 for getnameinfo. */ -/* #undef GETNAMEINFO_TYPE_ARG2 socklen_t */ - -/* Define to the type of args 4 and 6 for getnameinfo. */ -/* #undef GETNAMEINFO_TYPE_ARG46 size_t */ - -/* Define to the type of arg 7 for getnameinfo. */ -/* #undef GETNAMEINFO_TYPE_ARG7 int */ - -/* Define to 1 if you have the recv function. */ -#define HAVE_RECV 1 - -/* Define to the type of arg 1 for recv. */ -#define RECV_TYPE_ARG1 int - -/* Define to the type of arg 2 for recv. */ -#define RECV_TYPE_ARG2 char * - -/* Define to the type of arg 3 for recv. */ -#define RECV_TYPE_ARG3 int - -/* Define to the type of arg 4 for recv. */ -#define RECV_TYPE_ARG4 int - -/* Define to the function return type for recv. */ -#define RECV_TYPE_RETV int - -/* Define to 1 if you have the recvfrom function. */ -#define HAVE_RECVFROM 1 - -/* Define to the type of arg 1 for recvfrom. */ -#define RECVFROM_TYPE_ARG1 int - -/* Define to the type pointed by arg 2 for recvfrom. */ -#define RECVFROM_TYPE_ARG2 char - -/* Define to the type of arg 3 for recvfrom. */ -#define RECVFROM_TYPE_ARG3 int - -/* Define to the type of arg 4 for recvfrom. */ -#define RECVFROM_TYPE_ARG4 int - -/* Define to the type pointed by arg 5 for recvfrom. */ -#define RECVFROM_TYPE_ARG5 struct sockaddr - -/* Define to the type pointed by arg 6 for recvfrom. */ -#define RECVFROM_TYPE_ARG6 int - -/* Define to the function return type for recvfrom. */ -#define RECVFROM_TYPE_RETV int - -/* Define to 1 if you have the send function. */ -#define HAVE_SEND 1 - -/* Define to the type of arg 1 for send. */ -#define SEND_TYPE_ARG1 int - -/* Define to the type qualifier of arg 2 for send. */ -#define SEND_QUAL_ARG2 const - -/* Define to the type of arg 2 for send. */ -#define SEND_TYPE_ARG2 char * - -/* Define to the type of arg 3 for send. */ -#define SEND_TYPE_ARG3 int - -/* Define to the type of arg 4 for send. */ -#define SEND_TYPE_ARG4 int - -/* Define to the function return type for send. */ -#define SEND_TYPE_RETV int - -#define CURL_DOES_CONVERSIONS -#ifndef CURL_ICONV_CODESET_OF_HOST -#define CURL_ICONV_CODESET_OF_HOST "IBM-1047" -#endif - - -#endif /* HEADER_CURL_CONFIG_TPF_H */ diff --git a/contrib/curl/lib/config-vxworks.h b/contrib/curl/lib/config-vxworks.h deleted file mode 100644 index 780a4a225f..0000000000 --- a/contrib/curl/lib/config-vxworks.h +++ /dev/null @@ -1,928 +0,0 @@ -#ifndef HEADER_CURL_CONFIG_VXWORKS_H -#define HEADER_CURL_CONFIG_VXWORKS_H -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) 1998 - 2015, Daniel Stenberg, , et al. - * - * This software is licensed as described in the file COPYING, which - * you should have received as part of this distribution. The terms - * are also available at https://curl.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. - * - ***************************************************************************/ - -/* =============================================================== */ -/* Hand crafted config file for VxWorks */ -/* =============================================================== */ - -/* Location of default ca bundle */ -/* #undef CURL_CA_BUNDLE */ - -/* Location of default ca path */ -/* #undef CURL_CA_PATH */ - -/* to disable cookies support */ -/* #undef CURL_DISABLE_COOKIES */ - -/* to disable cryptographic authentication */ -/* #undef CURL_DISABLE_CRYPTO_AUTH */ - -/* to disable DICT */ -/* #undef CURL_DISABLE_DICT */ - -/* to disable FILE */ -/* #undef CURL_DISABLE_FILE */ - -/* to disable FTP */ -#define CURL_DISABLE_FTP 1 - -/* to disable HTTP */ -/* #undef CURL_DISABLE_HTTP */ - -/* to disable LDAP */ -#define CURL_DISABLE_LDAP 1 - -/* to disable LDAPS */ -#define CURL_DISABLE_LDAPS 1 - -/* to disable NTLM authentication */ -#define CURL_DISABLE_NTLM 1 - -/* to disable proxies */ -/* #undef CURL_DISABLE_PROXY */ - -/* to disable TELNET */ -#define CURL_DISABLE_TELNET 1 - -/* to disable TFTP */ -#define CURL_DISABLE_TFTP 1 - -/* to disable verbose strings */ -/* #undef CURL_DISABLE_VERBOSE_STRINGS */ - -/* Definition to make a library symbol externally visible. */ -/* #undef CURL_EXTERN_SYMBOL */ - -/* Use Windows LDAP implementation */ -/* #undef USE_WIN32_LDAP */ - -/* your Entropy Gathering Daemon socket pathname */ -/* #undef EGD_SOCKET */ - -/* Define if you want to enable IPv6 support */ -#define ENABLE_IPV6 1 - -/* Define to the type qualifier of arg 1 for getnameinfo. */ -#define GETNAMEINFO_QUAL_ARG1 const - -/* Define to the type of arg 1 for getnameinfo. */ -#define GETNAMEINFO_TYPE_ARG1 struct sockaddr * - -/* Define to the type of arg 2 for getnameinfo. */ -#define GETNAMEINFO_TYPE_ARG2 socklen_t - -/* Define to the type of args 4 and 6 for getnameinfo. */ -#define GETNAMEINFO_TYPE_ARG46 size_t - -/* Define to the type of arg 7 for getnameinfo. */ -#define GETNAMEINFO_TYPE_ARG7 unsigned int - -/* Specifies the number of arguments to getservbyport_r */ -#define GETSERVBYPORT_R_ARGS 6 - -/* Specifies the size of the buffer to pass to getservbyport_r */ -#define GETSERVBYPORT_R_BUFSIZE 4096 - -/* Define to 1 if you have the alarm function. */ -#define HAVE_ALARM 1 - -/* Define to 1 if you have the header file. */ -#define HAVE_ALLOCA_H 1 - -/* Define to 1 if you have the header file. */ -#define HAVE_ARPA_INET_H 1 - -/* Define to 1 if you have the header file. */ -/* #undef HAVE_ARPA_TFTP_H */ - -/* Define to 1 if you have the header file. */ -#define HAVE_ASSERT_H 1 - -/* Define to 1 if you have the `basename' function. */ -/* #undef HAVE_BASENAME */ - -/* Define to 1 if bool is an available type. */ -#define HAVE_BOOL_T 1 - -/* Define to 1 if you have the clock_gettime function and monotonic timer. */ -/* #undef HAVE_CLOCK_GETTIME_MONOTONIC */ - -/* Define to 1 if you have the `closesocket' function. */ -/* #undef HAVE_CLOSESOCKET */ - -/* Define to 1 if you have the `CRYPTO_cleanup_all_ex_data' function. */ -#define HAVE_CRYPTO_CLEANUP_ALL_EX_DATA 1 - -/* Define to 1 if you have the header file. */ -/* #undef HAVE_CRYPTO_H */ - -/* Define to 1 if you have the header file. */ -/* #undef HAVE_DES_H */ - -/* Define to 1 if you have the header file. */ -#define HAVE_DLFCN_H 1 - -/* Define to 1 if you have the `ENGINE_load_builtin_engines' function. */ -#define HAVE_ENGINE_LOAD_BUILTIN_ENGINES 1 - -/* Define to 1 if you have the header file. */ -#define HAVE_ERRNO_H 1 - -/* Define to 1 if you have the header file. */ -/* #undef HAVE_ERR_H */ - -/* Define to 1 if you have the fcntl function. */ -#define HAVE_FCNTL 1 - -/* Define to 1 if you have the header file. */ -#define HAVE_FCNTL_H 1 - -/* Define to 1 if you have a working fcntl O_NONBLOCK function. */ -#define HAVE_FCNTL_O_NONBLOCK 1 - -/* Define to 1 if you have the fdopen function. */ -#define HAVE_FDOPEN 1 - -/* Define to 1 if you have the `fork' function. */ -#define HAVE_FORK 1 - -/* Define to 1 if you have the freeaddrinfo function. */ -#define HAVE_FREEADDRINFO 1 - -/* Define to 1 if you have the freeifaddrs function. */ -#define HAVE_FREEIFADDRS 1 - -/* Define to 1 if you have the ftruncate function. */ -#define HAVE_FTRUNCATE 1 - -/* Define to 1 if you have a working getaddrinfo function. */ -#define HAVE_GETADDRINFO 1 - -/* Define to 1 if you have the `geteuid' function. */ -/* #undef HAVE_GETEUID */ - -/* Define to 1 if you have the gethostbyaddr function. */ -#define HAVE_GETHOSTBYADDR 1 - -/* Define to 1 if you have the gethostbyaddr_r function. */ -#define HAVE_GETHOSTBYADDR_R 1 - -/* gethostbyaddr_r() takes 5 args */ -/* #undef HAVE_GETHOSTBYADDR_R_5 */ - -/* gethostbyaddr_r() takes 7 args */ -/* #undef HAVE_GETHOSTBYADDR_R_7 */ - -/* gethostbyaddr_r() takes 8 args */ -#define HAVE_GETHOSTBYADDR_R_8 1 - -/* Define to 1 if you have the gethostbyname function. */ -#define HAVE_GETHOSTBYNAME 1 - -/* Define to 1 if you have the gethostbyname_r function. */ -/* #undef HAVE_GETHOSTBYNAME_R */ - -/* gethostbyname_r() takes 3 args */ -/* #undef HAVE_GETHOSTBYNAME_R_3 */ - -/* gethostbyname_r() takes 5 args */ -/* #undef HAVE_GETHOSTBYNAME_R_5 */ - -/* gethostbyname_r() takes 6 args */ -/* #undef HAVE_GETHOSTBYNAME_R_6 */ - -/* Define to 1 if you have the gethostname function. */ -#define HAVE_GETHOSTNAME 1 - -/* Define to 1 if you have a working getifaddrs function. */ -/* #undef HAVE_GETIFADDRS */ - -/* Define to 1 if you have the getnameinfo function. */ -#define HAVE_GETNAMEINFO 1 - -/* Define to 1 if you have the `getpass_r' function. */ -/* #undef HAVE_GETPASS_R */ - -/* Define to 1 if you have the `getppid' function. */ -#define HAVE_GETPPID 1 - -/* Define to 1 if you have the `getprotobyname' function. */ -#define HAVE_GETPROTOBYNAME 1 - -/* Define to 1 if you have the `getpwuid' function. */ -/* #undef HAVE_GETPWUID */ - -/* Define to 1 if you have the `getrlimit' function. */ -#define HAVE_GETRLIMIT 1 - -/* Define to 1 if you have the getservbyport_r function. */ -/* #undef HAVE_GETSERVBYPORT_R */ - -/* Define to 1 if you have the `gettimeofday' function. */ -/* #undef HAVE_GETTIMEOFDAY */ - -/* Define to 1 if you have a working glibc-style strerror_r function. */ -/* #undef HAVE_GLIBC_STRERROR_R */ - -/* Define to 1 if you have a working gmtime_r function. */ -#define HAVE_GMTIME_R 1 - -/* if you have the gssapi libraries */ -/* #undef HAVE_GSSAPI */ - -/* Define to 1 if you have the header file. */ -/* #undef HAVE_GSSAPI_GSSAPI_GENERIC_H */ - -/* Define to 1 if you have the header file. */ -/* #undef HAVE_GSSAPI_GSSAPI_H */ - -/* Define to 1 if you have the header file. */ -/* #undef HAVE_GSSAPI_GSSAPI_KRB5_H */ - -/* if you have the GNU gssapi libraries */ -/* #undef HAVE_GSSGNU */ - -/* if you have the Heimdal gssapi libraries */ -/* #undef HAVE_GSSHEIMDAL */ - -/* if you have the MIT gssapi libraries */ -/* #undef HAVE_GSSMIT */ - -/* Define to 1 if you have the `idna_strerror' function. */ -/* #undef HAVE_IDNA_STRERROR */ - -/* Define to 1 if you have the `idn_free' function. */ -/* #undef HAVE_IDN_FREE */ - -/* Define to 1 if you have the header file. */ -/* #undef HAVE_IDN_FREE_H */ - -/* Define to 1 if you have the header file. */ -/* #undef HAVE_IFADDRS_H */ - -/* Define to 1 if you have the `inet_addr' function. */ -#define HAVE_INET_ADDR 1 - -/* Define to 1 if you have the inet_ntoa_r function. */ -/* #undef HAVE_INET_NTOA_R */ - -/* inet_ntoa_r() takes 2 args */ -/* #undef HAVE_INET_NTOA_R_2 */ - -/* inet_ntoa_r() takes 3 args */ -/* #undef HAVE_INET_NTOA_R_3 */ - -/* Define to 1 if you have a IPv6 capable working inet_ntop function. */ -/* #undef HAVE_INET_NTOP */ - -/* Define to 1 if you have a IPv6 capable working inet_pton function. */ -/* #undef HAVE_INET_PTON */ - -/* Define to 1 if you have the header file. */ -#define HAVE_INTTYPES_H 1 - -/* Define to 1 if you have the ioctl function. */ -#define HAVE_IOCTL 1 - -/* Define to 1 if you have the ioctlsocket function. */ -/* #undef HAVE_IOCTLSOCKET */ - -/* Define to 1 if you have the IoctlSocket camel case function. */ -/* #undef HAVE_IOCTLSOCKET_CAMEL */ - -/* Define to 1 if you have a working IoctlSocket camel case FIONBIO function. - */ -/* #undef HAVE_IOCTLSOCKET_CAMEL_FIONBIO */ - -/* Define to 1 if you have a working ioctlsocket FIONBIO function. */ -/* #undef HAVE_IOCTLSOCKET_FIONBIO */ - -/* Define to 1 if you have a working ioctl FIONBIO function. */ -#define HAVE_IOCTL_FIONBIO 1 - -/* Define to 1 if you have a working ioctl SIOCGIFADDR function. */ -#define HAVE_IOCTL_SIOCGIFADDR 1 - -/* Define to 1 if you have the header file. */ -#define HAVE_IO_H 1 - -/* if you have the Kerberos4 libraries (including -ldes) */ -/* #undef HAVE_KRB4 */ - -/* Define to 1 if you have the `krb_get_our_ip_for_realm' function. */ -/* #undef HAVE_KRB_GET_OUR_IP_FOR_REALM */ - -/* Define to 1 if you have the header file. */ -/* #undef HAVE_KRB_H */ - -/* Define to 1 if you have the lber.h header file. */ -/* #undef HAVE_LBER_H */ - -/* Define to 1 if you have the ldapssl.h header file. */ -/* #undef HAVE_LDAPSSL_H */ - -/* Define to 1 if you have the ldap.h header file. */ -/* #undef HAVE_LDAP_H */ - -/* Use LDAPS implementation */ -/* #undef HAVE_LDAP_SSL */ - -/* Define to 1 if you have the ldap_ssl.h header file. */ -/* #undef HAVE_LDAP_SSL_H */ - -/* Define to 1 if you have the `ldap_url_parse' function. */ -/* #undef HAVE_LDAP_URL_PARSE */ - -/* Define to 1 if you have the header file. */ -/* #undef HAVE_LIBGEN_H */ - -/* Define to 1 if you have the `idn' library (-lidn). */ -/* #undef HAVE_LIBIDN */ - -/* Define to 1 if you have the `resolv' library (-lresolv). */ -/* #undef HAVE_LIBRESOLV */ - -/* Define to 1 if you have the `resolve' library (-lresolve). */ -/* #undef HAVE_LIBRESOLVE */ - -/* Define to 1 if you have the `socket' library (-lsocket). */ -/* #undef HAVE_LIBSOCKET */ - -/* Define to 1 if you have the `ssh2' library (-lssh2). */ -/* #undef HAVE_LIBSSH2 */ - -/* Define to 1 if you have the header file. */ -/* #undef HAVE_LIBSSH2_H */ - -/* Define to 1 if you have the `libssh2_version' function. */ -/* #undef HAVE_LIBSSH2_VERSION */ - -/* Define to 1 if you have the `ssl' library (-lssl). */ -#define HAVE_LIBSSL 1 - -/* if zlib is available */ -#define HAVE_LIBZ 1 - -/* Define to 1 if you have the header file. */ -#define HAVE_LIMITS_H 1 - -/* if your compiler supports LL */ -#define HAVE_LL 1 - -/* Define to 1 if you have the header file. */ -#define HAVE_LOCALE_H 1 - -/* Define to 1 if you have a working localtime_r function. */ -#define HAVE_LOCALTIME_R 1 - -/* Define to 1 if the compiler supports the 'long long' data type. */ -#define HAVE_LONGLONG 1 - -/* Define to 1 if you have the malloc.h header file. */ -#define HAVE_MALLOC_H 1 - -/* Define to 1 if you have the memory.h header file. */ -#define HAVE_MEMORY_H 1 - -/* Define to 1 if you have the MSG_NOSIGNAL flag. */ -/* #undef HAVE_MSG_NOSIGNAL */ - -/* Define to 1 if you have the header file. */ -#define HAVE_NETDB_H 1 - -/* Define to 1 if you have the header file. */ -#define HAVE_NETINET_IN_H 1 - -/* Define to 1 if you have the header file. */ -#define HAVE_NETINET_TCP_H 1 - -/* Define to 1 if you have the header file. */ -#define HAVE_NET_IF_H 1 - -/* Define to 1 if NI_WITHSCOPEID exists and works. */ -/* #undef HAVE_NI_WITHSCOPEID */ - -/* if you have an old MIT gssapi library, lacking GSS_C_NT_HOSTBASED_SERVICE - */ -/* #undef HAVE_OLD_GSSMIT */ - -/* Define to 1 if you have the header file. */ -#define HAVE_OPENSSL_CRYPTO_H 1 - -/* Define to 1 if you have the header file. */ -#define HAVE_OPENSSL_ENGINE_H 1 - -/* Define to 1 if you have the header file. */ -#define HAVE_OPENSSL_ERR_H 1 - -/* Define to 1 if you have the header file. */ -#define HAVE_OPENSSL_PEM_H 1 - -/* Define to 1 if you have the header file. */ -#define HAVE_OPENSSL_PKCS12_H 1 - -/* Define to 1 if you have the header file. */ -#define HAVE_OPENSSL_RSA_H 1 - -/* Define to 1 if you have the header file. */ -#define HAVE_OPENSSL_SSL_H 1 - -/* Define to 1 if you have the header file. */ -#define HAVE_OPENSSL_X509_H 1 - -/* Define to 1 if you have the header file. */ -/* #undef HAVE_PEM_H */ - -/* Define to 1 if you have the `perror' function. */ -#define HAVE_PERROR 1 - -/* Define to 1 if you have the `pipe' function. */ -#define HAVE_PIPE 1 - -/* Define to 1 if you have a working poll function. */ -/* #undef HAVE_POLL */ - -/* If you have a fine poll */ -/* #undef HAVE_POLL_FINE */ - -/* Define to 1 if you have the header file. */ -/* #undef HAVE_POLL_H */ - -/* Define to 1 if you have a working POSIX-style strerror_r function. */ -/* #undef HAVE_POSIX_STRERROR_R */ - -/* Define to 1 if you have the header file. */ -/* #undef HAVE_PWD_H */ - -/* Define to 1 if you have the `RAND_egd' function. */ -#define HAVE_RAND_EGD 1 - -/* Define to 1 if you have the `RAND_screen' function. */ -/* #undef HAVE_RAND_SCREEN */ - -/* Define to 1 if you have the `RAND_status' function. */ -#define HAVE_RAND_STATUS 1 - -/* Define to 1 if you have the recv function. */ -#define HAVE_RECV 1 - -/* Define to 1 if you have the recvfrom function. */ -#define HAVE_RECVFROM 1 - -/* Define to 1 if you have the header file. */ -/* #undef HAVE_RSA_H */ - -/* Define to 1 if you have the select function. */ -#define HAVE_SELECT 1 - -/* Define to 1 if you have the send function. */ -#define HAVE_SEND 1 - -/* Define to 1 if you have the header file. */ -#define HAVE_SETJMP_H 1 - -/* Define to 1 if you have the `setlocale' function. */ -#define HAVE_SETLOCALE 1 - -/* Define to 1 if you have the `setmode' function. */ -#define HAVE_SETMODE 1 - -/* Define to 1 if you have the `setrlimit' function. */ -#define HAVE_SETRLIMIT 1 - -/* Define to 1 if you have the setsockopt function. */ -#define HAVE_SETSOCKOPT 1 - -/* Define to 1 if you have a working setsockopt SO_NONBLOCK function. */ -/* #undef HAVE_SETSOCKOPT_SO_NONBLOCK */ - -/* Define to 1 if you have the header file. */ -/* #undef HAVE_SGTTY_H */ - -/* Define to 1 if you have the sigaction function. */ -#define HAVE_SIGACTION 1 - -/* Define to 1 if you have the siginterrupt function. */ -#define HAVE_SIGINTERRUPT 1 - -/* Define to 1 if you have the signal function. */ -#define HAVE_SIGNAL 1 - -/* Define to 1 if you have the header file. */ -#define HAVE_SIGNAL_H 1 - -/* Define to 1 if you have the sigsetjmp function or macro. */ -/* #undef HAVE_SIGSETJMP */ - -/* Define to 1 if sig_atomic_t is an available typedef. */ -#define HAVE_SIG_ATOMIC_T 1 - -/* Define to 1 if sig_atomic_t is already defined as volatile. */ -/* #undef HAVE_SIG_ATOMIC_T_VOLATILE */ - -/* Define to 1 if struct sockaddr_in6 has the sin6_scope_id member */ -#define HAVE_SOCKADDR_IN6_SIN6_SCOPE_ID 1 - -/* Define to 1 if you have the `socket' function. */ -#define HAVE_SOCKET 1 - -/* Define to 1 if you have the `SSL_get_shutdown' function. */ -#define HAVE_SSL_GET_SHUTDOWN 1 - -/* Define to 1 if you have the header file. */ -/* #undef HAVE_SSL_H */ - -/* Define to 1 if you have the header file. */ -#define HAVE_STDBOOL_H 1 - -/* Define to 1 if you have the header file. */ -/* #undef HAVE_STDINT_H */ - -/* Define to 1 if you have the header file. */ -#define HAVE_STDIO_H 1 - -/* Define to 1 if you have the header file. */ -#define HAVE_STDLIB_H 1 - -/* Define to 1 if you have the strcasecmp function. */ -#define HAVE_STRCASECMP 1 - -/* Define to 1 if you have the strcmpi function. */ -/* #undef HAVE_STRCMPI */ - -/* Define to 1 if you have the strdup function. */ -#define HAVE_STRDUP 1 - -/* Define to 1 if you have the strerror_r function. */ -#define HAVE_STRERROR_R 1 - -/* Define to 1 if you have the stricmp function. */ -/* #undef HAVE_STRICMP */ - -/* Define to 1 if you have the header file. */ -#define HAVE_STRINGS_H 1 - -/* Define to 1 if you have the header file. */ -#define HAVE_STRING_H 1 - -/* Define to 1 if you have the `strlcpy' function. */ -/* #undef HAVE_STRLCPY */ - -/* Define to 1 if you have the strncasecmp function. */ -#define HAVE_STRNCASECMP 1 - -/* Define to 1 if you have the strncmpi function. */ -/* #undef HAVE_STRNCMPI */ - -/* Define to 1 if you have the strnicmp function. */ -/* #undef HAVE_STRNICMP */ - -/* Define to 1 if you have the header file. */ -/* #undef HAVE_STROPTS_H */ - -/* Define to 1 if you have the strstr function. */ -#define HAVE_STRSTR 1 - -/* Define to 1 if you have the strtok_r function. */ -#define HAVE_STRTOK_R 1 - -/* Define to 1 if you have the strtoll function. */ -/* #undef HAVE_STRTOLL */ - -/* if struct sockaddr_storage is defined */ -#define HAVE_STRUCT_SOCKADDR_STORAGE 1 - -/* Define to 1 if you have the timeval struct. */ -#define HAVE_STRUCT_TIMEVAL 1 - -/* Define to 1 if you have the header file. */ -/* #undef HAVE_SYS_FILIO_H */ - -/* Define to 1 if you have the header file. */ -#define HAVE_SYS_IOCTL_H 1 - -/* Define to 1 if you have the header file. */ -/* #undef HAVE_SYS_PARAM_H */ - -/* Define to 1 if you have the header file. */ -/* #undef HAVE_SYS_POLL_H */ - -/* Define to 1 if you have the header file. */ -#define HAVE_SYS_RESOURCE_H 1 - -/* Define to 1 if you have the header file. */ -/* #undef HAVE_SYS_SELECT_H */ - -/* Define to 1 if you have the header file. */ -#define HAVE_SYS_SOCKET_H 1 - -/* Define to 1 if you have the header file. */ -/* #undef HAVE_SYS_SOCKIO_H */ - -/* Define to 1 if you have the header file. */ -#define HAVE_SYS_STAT_H 1 - -/* Define to 1 if you have the header file. */ -/* #undef HAVE_SYS_TIME_H */ - -/* Define to 1 if you have the header file. */ -#define HAVE_SYS_TYPES_H 1 - -/* Define to 1 if you have the header file. */ -#define HAVE_SYS_UIO_H 1 - -/* Define to 1 if you have the header file. */ -#define HAVE_SYS_UN_H 1 - -/* Define to 1 if you have the header file. */ -#define HAVE_SYS_UTIME_H 1 - -/* Define to 1 if you have the header file. */ -#define HAVE_TERMIOS_H 1 - -/* Define to 1 if you have the header file. */ -#define HAVE_TERMIO_H 1 - -/* Define to 1 if you have the header file. */ -#define HAVE_TIME_H 1 - -/* Define to 1 if you have the header file. */ -/* #undef HAVE_TLD_H */ - -/* Define to 1 if you have the `tld_strerror' function. */ -/* #undef HAVE_TLD_STRERROR */ - -/* Define to 1 if you have the `uname' function. */ -#define HAVE_UNAME 1 - -/* Define to 1 if you have the header file. */ -#define HAVE_UNISTD_H 1 - -/* Define to 1 if you have the `utime' function. */ -#define HAVE_UTIME 1 - -/* Define to 1 if you have the header file. */ -#define HAVE_UTIME_H 1 - -/* Define to 1 if compiler supports C99 variadic macro style. */ -#define HAVE_VARIADIC_MACROS_C99 1 - -/* Define to 1 if compiler supports old gcc variadic macro style. */ -#define HAVE_VARIADIC_MACROS_GCC 1 - -/* Define to 1 if you have a working vxworks-style strerror_r function. */ -#define HAVE_VXWORKS_STRERROR_R 1 - -/* Define to 1 if you have the winber.h header file. */ -/* #undef HAVE_WINBER_H */ - -/* Define to 1 if you have the windows.h header file. */ -/* #undef HAVE_WINDOWS_H */ - -/* Define to 1 if you have the winldap.h header file. */ -/* #undef HAVE_WINLDAP_H */ - -/* Define to 1 if you have the winsock2.h header file. */ -/* #undef HAVE_WINSOCK2_H */ - -/* Define to 1 if you have the winsock.h header file. */ -/* #undef HAVE_WINSOCK_H */ - -/* Define this symbol if your OS supports changing the contents of argv */ -#define HAVE_WRITABLE_ARGV 1 - -/* Define to 1 if you have the writev function. */ -#define HAVE_WRITEV 1 - -/* Define to 1 if you have the ws2tcpip.h header file. */ -/* #undef HAVE_WS2TCPIP_H */ - -/* Define to 1 if you have the header file. */ -/* #undef HAVE_X509_H */ - -/* if you have the zlib.h header file */ -#define HAVE_ZLIB_H 1 - -/* Define to 1 if you need the lber.h header file even with ldap.h */ -/* #undef NEED_LBER_H */ - -/* Define to 1 if you need the malloc.h header file even with stdlib.h */ -/* #undef NEED_MALLOC_H */ - -/* Define to 1 if you need the memory.h header file even with stdlib.h */ -/* #undef NEED_MEMORY_H */ - -/* Define to 1 if _REENTRANT preprocessor symbol must be defined. */ -/* #undef NEED_REENTRANT */ - -/* Define to 1 if _THREAD_SAFE preprocessor symbol must be defined. */ -/* #undef NEED_THREAD_SAFE */ - -/* Define to 1 if the open function requires three arguments. */ -#define OPEN_NEEDS_ARG3 1 - -/* cpu-machine-OS */ -#define OS "unknown-unknown-vxworks" - -/* Name of package */ -#define PACKAGE "curl" - -/* a suitable file to read random data from */ -#define RANDOM_FILE "/dev/urandom" - -/* Define to the type of arg 1 for recvfrom. */ -#define RECVFROM_TYPE_ARG1 int - -/* Define to the type pointed by arg 2 for recvfrom. */ -#define RECVFROM_TYPE_ARG2 void - -/* Define to 1 if the type pointed by arg 2 for recvfrom is void. */ -#define RECVFROM_TYPE_ARG2_IS_VOID 1 - -/* Define to the type of arg 3 for recvfrom. */ -#define RECVFROM_TYPE_ARG3 size_t - -/* Define to the type of arg 4 for recvfrom. */ -#define RECVFROM_TYPE_ARG4 int - -/* Define to the type pointed by arg 5 for recvfrom. */ -#define RECVFROM_TYPE_ARG5 struct sockaddr - -/* Define to 1 if the type pointed by arg 5 for recvfrom is void. */ -/* #undef RECVFROM_TYPE_ARG5_IS_VOID */ - -/* Define to the type pointed by arg 6 for recvfrom. */ -#define RECVFROM_TYPE_ARG6 socklen_t - -/* Define to 1 if the type pointed by arg 6 for recvfrom is void. */ -/* #undef RECVFROM_TYPE_ARG6_IS_VOID */ - -/* Define to the function return type for recvfrom. */ -#define RECVFROM_TYPE_RETV int - -/* Define to the type of arg 1 for recv. */ -#define RECV_TYPE_ARG1 int - -/* Define to the type of arg 2 for recv. */ -#define RECV_TYPE_ARG2 void * - -/* Define to the type of arg 3 for recv. */ -#define RECV_TYPE_ARG3 size_t - -/* Define to the type of arg 4 for recv. */ -#define RECV_TYPE_ARG4 int - -/* Define to the function return type for recv. */ -#define RECV_TYPE_RETV int - -/* Define as the return type of signal handlers (`int' or `void'). */ -#define RETSIGTYPE void - -/* Define to the type qualifier of arg 5 for select. */ -#define SELECT_QUAL_ARG5 - -/* Define to the type of arg 1 for select. */ -#define SELECT_TYPE_ARG1 int - -/* Define to the type of args 2, 3 and 4 for select. */ -#define SELECT_TYPE_ARG234 fd_set * - -/* Define to the type of arg 5 for select. */ -#define SELECT_TYPE_ARG5 struct timeval * - -/* Define to the function return type for select. */ -#define SELECT_TYPE_RETV int - -/* Define to the type qualifier of arg 2 for send. */ -#define SEND_QUAL_ARG2 const - -/* Define to the type of arg 1 for send. */ -#define SEND_TYPE_ARG1 int - -/* Define to the type of arg 2 for send. */ -#define SEND_TYPE_ARG2 void * - -/* Define to the type of arg 3 for send. */ -#define SEND_TYPE_ARG3 size_t - -/* Define to the type of arg 4 for send. */ -#define SEND_TYPE_ARG4 int - -/* Define to the function return type for send. */ -#define SEND_TYPE_RETV int - -/* The size of `int', as computed by sizeof. */ -#define SIZEOF_INT 4 - -/* The size of `long', as computed by sizeof. */ -#define SIZEOF_LONG 4 - -/* The size of `off_t', as computed by sizeof. */ -#define SIZEOF_OFF_T 8 - -/* The size of `short', as computed by sizeof. */ -#define SIZEOF_SHORT 2 - -/* The size of `size_t', as computed by sizeof. */ -#define SIZEOF_SIZE_T 4 - -/* The size of `time_t', as computed by sizeof. */ -#define SIZEOF_TIME_T 4 - -/* The size of `void*', as computed by sizeof. */ -#define SIZEOF_VOIDP 4 - -/* Define to 1 if you have the ANSI C header files. */ -#define STDC_HEADERS 1 - -/* Define to the type of arg 3 for strerror_r. */ -/* #undef STRERROR_R_TYPE_ARG3 */ - -/* Define to 1 if you can safely include both and . */ -/* #undef TIME_WITH_SYS_TIME */ - -/* Define if you want to enable c-ares support */ -/* #undef USE_ARES */ - -/* Define to disable non-blocking sockets. */ -/* #undef USE_BLOCKING_SOCKETS */ - -/* if GnuTLS is enabled */ -/* #undef USE_GNUTLS */ - -/* if libSSH2 is in use */ -/* #undef USE_LIBSSH2 */ - -/* If you want to build curl with the built-in manual */ -#define USE_MANUAL 1 - -/* if NSS is enabled */ -/* #undef USE_NSS */ - -/* if OpenSSL is in use */ -#define USE_OPENSSL 1 - -/* Define to 1 if you are building a Windows target without large file - support. */ -/* #undef USE_WIN32_LARGE_FILES */ - -/* to enable SSPI support */ -/* #undef USE_WINDOWS_SSPI */ - -/* Define to 1 if using yaSSL in OpenSSL compatibility mode. */ -/* #undef USE_YASSLEMUL */ - -/* Define to avoid automatic inclusion of winsock.h */ -/* #undef WIN32_LEAN_AND_MEAN */ - -/* 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. */ -/* #undef _FILE_OFFSET_BITS */ - -/* Define for large files, on AIX-style hosts. */ -/* #undef _LARGE_FILES */ - -/* Define to empty if `const' does not conform to ANSI C. */ -/* #undef const */ - -/* Type to use in place of in_addr_t when system does not provide it. */ -/* #undef 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 does not define. */ -/* #undef size_t */ - -/* the signed version of size_t */ -/* #undef ssize_t */ - -#endif /* HEADER_CURL_CONFIG_VXWORKS_H */ diff --git a/contrib/curl/lib/config-win32.h b/contrib/curl/lib/config-win32.h index af7bb1fb0c..84414ae4d7 100644 --- a/contrib/curl/lib/config-win32.h +++ b/contrib/curl/lib/config-win32.h @@ -7,11 +7,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2016, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -20,6 +20,8 @@ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY * KIND, either express or implied. * + * SPDX-License-Identifier: curl + * ***************************************************************************/ /* ================================================================ */ @@ -33,44 +35,17 @@ /* Define if you have the header file. */ /* #define HAVE_ARPA_INET_H 1 */ -/* Define if you have the header file. */ -#define HAVE_ASSERT_H 1 - -/* Define if you have the header file. */ -/* #define HAVE_CRYPTO_H 1 */ - -/* Define if you have the header file. */ -#define HAVE_ERRNO_H 1 - -/* Define if you have the header file. */ -/* #define HAVE_ERR_H 1 */ - /* Define if you have the header file. */ #define HAVE_FCNTL_H 1 -/* Define if you have the header file. */ -#if defined(__MINGW32__) || defined(__POCC__) -#define HAVE_GETOPT_H 1 -#endif - -/* Define to 1 if you have the header file. */ -#if defined(_MSC_VER) && (_MSC_VER >= 1800) -#define HAVE_INTTYPES_H 1 -#endif - /* Define if you have the header file. */ #define HAVE_IO_H 1 -/* Define if you have the header file. */ -#define HAVE_LIMITS_H 1 - /* Define if you have the header file. */ #define HAVE_LOCALE_H 1 /* Define if you need header even with header file. */ -#if !defined(__SALFORDC__) && !defined(__POCC__) #define NEED_MALLOC_H 1 -#endif /* Define if you have the header file. */ /* #define HAVE_NETDB_H 1 */ @@ -78,30 +53,15 @@ /* Define if you have the header file. */ /* #define HAVE_NETINET_IN_H 1 */ -/* Define if you have the header file. */ -#ifndef __SALFORDC__ -#define HAVE_PROCESS_H 1 -#endif - -/* Define if you have the header file. */ -#define HAVE_SIGNAL_H 1 - -/* Define if you have the header file. */ -/* #define HAVE_SGTTY_H 1 */ - -/* Define if you have the header file. */ -/* #define HAVE_SSL_H 1 */ - /* Define to 1 if you have the header file. */ -#if defined(_MSC_VER) && (_MSC_VER >= 1800) +#if (defined(_MSC_VER) && (_MSC_VER >= 1800)) || defined(__MINGW32__) #define HAVE_STDBOOL_H 1 #endif -/* Define if you have the header file. */ -#define HAVE_STDLIB_H 1 - /* Define if you have the header file. */ -/* #define HAVE_SYS_PARAM_H 1 */ +#if defined(__MINGW32__) +#define HAVE_SYS_PARAM_H 1 +#endif /* Define if you have the header file. */ /* #define HAVE_SYS_SELECT_H 1 */ @@ -116,15 +76,15 @@ #define HAVE_SYS_STAT_H 1 /* Define if you have the header file. */ -/* #define HAVE_SYS_TIME_H 1 */ +#if defined(__MINGW32__) +#define HAVE_SYS_TIME_H 1 +#endif /* Define if you have the header file. */ #define HAVE_SYS_TYPES_H 1 /* Define if you have the header file. */ -#ifndef __BORLANDC__ #define HAVE_SYS_UTIME_H 1 -#endif /* Define if you have the header file. */ /* #define HAVE_TERMIO_H 1 */ @@ -132,46 +92,25 @@ /* Define if you have the header file. */ /* #define HAVE_TERMIOS_H 1 */ -/* Define if you have the header file. */ -#define HAVE_TIME_H 1 - /* Define if you have the header file. */ -#if defined(__MINGW32__) || defined(__WATCOMC__) || defined(__LCC__) || \ - defined(__POCC__) +#if defined(__MINGW32__) #define HAVE_UNISTD_H 1 #endif -/* Define if you have the header file. */ -#define HAVE_WINDOWS_H 1 - -/* Define if you have the header file. */ -#define HAVE_WINSOCK_H 1 - -/* Define if you have the header file. */ -#ifndef __SALFORDC__ -#define HAVE_WINSOCK2_H 1 -#endif - -/* Define if you have the header file. */ -#ifndef __SALFORDC__ -#define HAVE_WS2TCPIP_H 1 +/* Define to 1 if you have the header file. */ +#if defined(__MINGW32__) +#define HAVE_LIBGEN_H 1 #endif /* ---------------------------------------------------------------- */ /* OTHER HEADER INFO */ /* ---------------------------------------------------------------- */ -/* Define if sig_atomic_t is an available typedef. */ -#define HAVE_SIG_ATOMIC_T 1 - /* Define if you have the ANSI C header files. */ #define STDC_HEADERS 1 -/* Define if you can safely include both and . */ -/* #define TIME_WITH_SYS_TIME 1 */ - /* Define to 1 if bool is an available type. */ -#if defined(_MSC_VER) && (_MSC_VER >= 1800) +#if (defined(_MSC_VER) && (_MSC_VER >= 1800)) || defined(__MINGW32__) #define HAVE_BOOL_T 1 #endif @@ -182,32 +121,24 @@ /* Define if you have the closesocket function. */ #define HAVE_CLOSESOCKET 1 -/* Define if you don't have vprintf but do have _doprnt. */ -/* #define HAVE_DOPRNT 1 */ - /* Define if you have the ftruncate function. */ +#if defined(__MINGW32__) #define HAVE_FTRUNCATE 1 +#endif + +/* Define to 1 if you have the `getpeername' function. */ +#define HAVE_GETPEERNAME 1 -/* Define if you have the gethostbyaddr function. */ -#define HAVE_GETHOSTBYADDR 1 +/* Define to 1 if you have the getsockname function. */ +#define HAVE_GETSOCKNAME 1 /* Define if you have the gethostname function. */ #define HAVE_GETHOSTNAME 1 -/* Define if you have the getpass function. */ -/* #define HAVE_GETPASS 1 */ - -/* Define if you have the getservbyname function. */ -#define HAVE_GETSERVBYNAME 1 - -/* Define if you have the getprotobyname function. */ -#define HAVE_GETPROTOBYNAME - /* Define if you have the gettimeofday function. */ -/* #define HAVE_GETTIMEOFDAY 1 */ - -/* Define if you have the inet_addr function. */ -#define HAVE_INET_ADDR 1 +#if defined(__MINGW32__) +#define HAVE_GETTIMEOFDAY 1 +#endif /* Define if you have the ioctlsocket function. */ #define HAVE_IOCTLSOCKET 1 @@ -215,19 +146,6 @@ /* Define if you have a working ioctlsocket FIONBIO function. */ #define HAVE_IOCTLSOCKET_FIONBIO 1 -/* Define if you have the perror function. */ -#define HAVE_PERROR 1 - -/* Define if you have the RAND_screen function when using SSL. */ -#define HAVE_RAND_SCREEN 1 - -/* Define if you have the `RAND_status' function when using SSL. */ -#define HAVE_RAND_STATUS 1 - -/* Define if you have the `CRYPTO_cleanup_all_ex_data' function. - This is present in OpenSSL versions after 0.9.6b */ -#define HAVE_CRYPTO_CLEANUP_ALL_EX_DATA 1 - /* Define if you have the select function. */ #define HAVE_SELECT 1 @@ -237,64 +155,27 @@ /* Define if you have the setmode function. */ #define HAVE_SETMODE 1 -/* Define if you have the setvbuf function. */ -#define HAVE_SETVBUF 1 - /* Define if you have the socket function. */ #define HAVE_SOCKET 1 /* Define if you have the strcasecmp function. */ -/* #define HAVE_STRCASECMP 1 */ +#if defined(__MINGW32__) +#define HAVE_STRCASECMP 1 +#endif /* Define if you have the strdup function. */ #define HAVE_STRDUP 1 -/* Define if you have the strftime function. */ -#define HAVE_STRFTIME 1 - /* Define if you have the stricmp function. */ #define HAVE_STRICMP 1 -/* Define if you have the strncasecmp function. */ -/* #define HAVE_STRNCASECMP 1 */ - -/* Define if you have the strnicmp function. */ -#define HAVE_STRNICMP 1 - -/* Define if you have the strstr function. */ -#define HAVE_STRSTR 1 - /* Define if you have the strtoll function. */ -#if defined(__MINGW32__) || defined(__WATCOMC__) || defined(__POCC__) || \ - (defined(_MSC_VER) && (_MSC_VER >= 1800)) +#if (defined(_MSC_VER) && (_MSC_VER >= 1800)) || defined(__MINGW32__) #define HAVE_STRTOLL 1 #endif -/* Define if you have the tcgetattr function. */ -/* #define HAVE_TCGETATTR 1 */ - -/* Define if you have the tcsetattr function. */ -/* #define HAVE_TCSETATTR 1 */ - /* Define if you have the utime function. */ -#ifndef __BORLANDC__ #define HAVE_UTIME 1 -#endif - -/* Define to the type qualifier of arg 1 for getnameinfo. */ -#define GETNAMEINFO_QUAL_ARG1 const - -/* Define to the type of arg 1 for getnameinfo. */ -#define GETNAMEINFO_TYPE_ARG1 struct sockaddr * - -/* Define to the type of arg 2 for getnameinfo. */ -#define GETNAMEINFO_TYPE_ARG2 socklen_t - -/* Define to the type of args 4 and 6 for getnameinfo. */ -#define GETNAMEINFO_TYPE_ARG46 DWORD - -/* Define to the type of arg 7 for getnameinfo. */ -#define GETNAMEINFO_TYPE_ARG7 int /* Define if you have the recv function. */ #define HAVE_RECV 1 @@ -314,30 +195,6 @@ /* Define to the function return type for recv. */ #define RECV_TYPE_RETV int -/* Define if you have the recvfrom function. */ -#define HAVE_RECVFROM 1 - -/* Define to the type of arg 1 for recvfrom. */ -#define RECVFROM_TYPE_ARG1 SOCKET - -/* Define to the type pointed by arg 2 for recvfrom. */ -#define RECVFROM_TYPE_ARG2 char - -/* Define to the type of arg 3 for recvfrom. */ -#define RECVFROM_TYPE_ARG3 int - -/* Define to the type of arg 4 for recvfrom. */ -#define RECVFROM_TYPE_ARG4 int - -/* Define to the type pointed by arg 5 for recvfrom. */ -#define RECVFROM_TYPE_ARG5 struct sockaddr - -/* Define to the type pointed by arg 6 for recvfrom. */ -#define RECVFROM_TYPE_ARG6 int - -/* Define to the function return type for recvfrom. */ -#define RECVFROM_TYPE_RETV int - /* Define if you have the send function. */ #define HAVE_SEND 1 @@ -359,6 +216,31 @@ /* Define to the function return type for send. */ #define SEND_TYPE_RETV int +/* Define to 1 if you have the snprintf function. */ +#if (defined(_MSC_VER) && (_MSC_VER >= 1900)) || defined(__MINGW32__) +#define HAVE_SNPRINTF 1 +#endif + +#if defined(_WIN32_WINNT) && _WIN32_WINNT >= 0x600 /* Vista */ +/* Define to 1 if you have a IPv6 capable working inet_ntop function. */ +#define HAVE_INET_NTOP 1 +/* Define to 1 if you have a IPv6 capable working inet_pton function. */ +#define HAVE_INET_PTON 1 +#endif + +/* Define to 1 if you have the `basename' function. */ +#if defined(__MINGW32__) +#define HAVE_BASENAME 1 +#endif + +/* Define to 1 if you have the strtok_r function. */ +#if defined(__MINGW32__) +#define HAVE_STRTOK_R 1 +#endif + +/* Define to 1 if you have the signal function. */ +#define HAVE_SIGNAL 1 + /* ---------------------------------------------------------------- */ /* TYPEDEF REPLACEMENTS */ /* ---------------------------------------------------------------- */ @@ -366,14 +248,9 @@ /* Define if in_addr_t is not an available 'typedefed' type. */ #define in_addr_t unsigned long -/* Define to the return type of signal handlers (int or void). */ -#define RETSIGTYPE void - /* Define if ssize_t is not an available 'typedefed' type. */ #ifndef _SSIZE_T_DEFINED -# if (defined(__WATCOMC__) && (__WATCOMC__ >= 1240)) || \ - defined(__POCC__) || \ - defined(__MINGW32__) +# if defined(__MINGW32__) # elif defined(_WIN64) # define _SSIZE_T_DEFINED # define ssize_t __int64 @@ -390,14 +267,11 @@ /* Define to the size of `int', as computed by sizeof. */ #define SIZEOF_INT 4 -/* Define to the size of `long double', as computed by sizeof. */ -#define SIZEOF_LONG_DOUBLE 16 - /* Define to the size of `long long', as computed by sizeof. */ /* #define SIZEOF_LONG_LONG 8 */ -/* Define to the size of `short', as computed by sizeof. */ -#define SIZEOF_SHORT 2 +/* Define to the size of `long', as computed by sizeof. */ +#define SIZEOF_LONG 4 /* Define to the size of `size_t', as computed by sizeof. */ #if defined(_WIN64) @@ -406,61 +280,8 @@ # define SIZEOF_SIZE_T 4 #endif -/* ---------------------------------------------------------------- */ -/* BSD-style lwIP TCP/IP stack SPECIFIC */ -/* ---------------------------------------------------------------- */ - -/* Define to use BSD-style lwIP TCP/IP stack. */ -/* #define USE_LWIPSOCK 1 */ - -#ifdef USE_LWIPSOCK -# undef USE_WINSOCK -# undef HAVE_WINSOCK_H -# undef HAVE_WINSOCK2_H -# undef HAVE_WS2TCPIP_H -# undef HAVE_ERRNO_H -# undef HAVE_GETHOSTNAME -# undef HAVE_GETNAMEINFO -# undef LWIP_POSIX_SOCKETS_IO_NAMES -# undef RECV_TYPE_ARG1 -# undef RECV_TYPE_ARG3 -# undef SEND_TYPE_ARG1 -# undef SEND_TYPE_ARG3 -# define HAVE_FREEADDRINFO -# define HAVE_GETADDRINFO -# define HAVE_GETHOSTBYNAME -# define HAVE_GETHOSTBYNAME_R -# define HAVE_GETHOSTBYNAME_R_6 -# define LWIP_POSIX_SOCKETS_IO_NAMES 0 -# define RECV_TYPE_ARG1 int -# define RECV_TYPE_ARG3 size_t -# define SEND_TYPE_ARG1 int -# define SEND_TYPE_ARG3 size_t -#endif - -/* ---------------------------------------------------------------- */ -/* Watt-32 tcp/ip SPECIFIC */ -/* ---------------------------------------------------------------- */ - -#ifdef USE_WATT32 - #include - #undef byte - #undef word - #undef USE_WINSOCK - #undef HAVE_WINSOCK_H - #undef HAVE_WINSOCK2_H - #undef HAVE_WS2TCPIP_H - #define HAVE_GETADDRINFO - #define HAVE_GETNAMEINFO - #define HAVE_SYS_IOCTL_H - #define HAVE_SYS_SOCKET_H - #define HAVE_NETINET_IN_H - #define HAVE_NETDB_H - #define HAVE_ARPA_INET_H - #define HAVE_FREEADDRINFO - #define SOCKET int -#endif - +/* Define to the size of `curl_off_t', as computed by sizeof. */ +#define SIZEOF_CURL_OFF_T 8 /* ---------------------------------------------------------------- */ /* COMPILER SPECIFIC */ @@ -475,15 +296,8 @@ /* Windows should not have HAVE_GMTIME_R defined */ /* #undef HAVE_GMTIME_R */ -/* Define if the compiler supports C99 variadic macro style. */ -#if defined(_MSC_VER) && (_MSC_VER >= 1400) -#define HAVE_VARIADIC_MACROS_C99 1 -#endif - /* Define if the compiler supports the 'long long' data type. */ -#if defined(__MINGW32__) || defined(__WATCOMC__) || \ - (defined(_MSC_VER) && (_MSC_VER >= 1310)) || \ - (defined(__BORLANDC__) && (__BORLANDC__ >= 0x561)) +#if (defined(_MSC_VER) && (_MSC_VER >= 1310)) || defined(__MINGW32__) #define HAVE_LONGLONG 1 #endif @@ -493,9 +307,9 @@ #define _CRT_NONSTDC_NO_DEPRECATE 1 #endif -/* VS2005 and later dafault size for time_t is 64-bit, unless - _USE_32BIT_TIME_T has been defined to get a 32-bit time_t. */ -#if defined(_MSC_VER) && (_MSC_VER >= 1400) +/* mingw-w64 and visual studio >= 2005 (MSVCR80) + all default to 64-bit time_t unless _USE_32BIT_TIME_T is defined */ +#if (defined(_MSC_VER) && (_MSC_VER >= 1400)) || defined(__MINGW32__) # ifndef _USE_32BIT_TIME_T # define SIZEOF_TIME_T 8 # else @@ -514,7 +328,7 @@ # define VS2008_MIN_TARGET 0x0500 /* The minimum build target for VS2012 is Vista unless Update 1 is installed - and the v110_xp toolset is choosen. */ + and the v110_xp toolset is chosen. */ # if defined(_USING_V110_SDK71_) # define VS2012_MIN_TARGET 0x0501 # else @@ -526,7 +340,7 @@ # define VS2008_DEF_TARGET 0x0501 /* VS2012 default build target is Windows Vista unless Update 1 is installed - and the v110_xp toolset is choosen. */ + and the v110_xp toolset is chosen. */ # if defined(_USING_V110_SDK71_) # define VS2012_DEF_TARGET 0x0501 # else @@ -565,55 +379,17 @@ Vista # endif #endif -/* When no build target is specified Pelles C 5.00 and later default build - target is Windows Vista. We override default target to be Windows 2000. */ -#if defined(__POCC__) && (__POCC__ >= 500) -# ifndef _WIN32_WINNT -# define _WIN32_WINNT 0x0500 -# endif -# ifndef WINVER -# define WINVER 0x0500 -# endif -#endif - -/* Availability of freeaddrinfo, getaddrinfo and getnameinfo functions is - quite convoluted, compiler dependent and even build target dependent. */ -#if defined(HAVE_WS2TCPIP_H) -# if defined(__POCC__) -# define HAVE_FREEADDRINFO 1 -# define HAVE_GETADDRINFO 1 -# define HAVE_GETADDRINFO_THREADSAFE 1 -# define HAVE_GETNAMEINFO 1 -# elif defined(_WIN32_WINNT) && (_WIN32_WINNT >= 0x0501) -# define HAVE_FREEADDRINFO 1 -# define HAVE_GETADDRINFO 1 -# define HAVE_GETADDRINFO_THREADSAFE 1 -# define HAVE_GETNAMEINFO 1 -# elif defined(_MSC_VER) && (_MSC_VER >= 1200) -# define HAVE_FREEADDRINFO 1 -# define HAVE_GETADDRINFO 1 -# define HAVE_GETADDRINFO_THREADSAFE 1 -# define HAVE_GETNAMEINFO 1 -# endif -#endif - -#if defined(__POCC__) -# ifndef _MSC_VER -# error Microsoft extensions /Ze compiler option is required -# endif -# ifndef __POCC__OLDNAMES -# error Compatibility names /Go compiler option is required -# endif -#endif +/* Windows XP is required for freeaddrinfo, getaddrinfo */ +#define HAVE_FREEADDRINFO 1 +#define HAVE_GETADDRINFO 1 +#define HAVE_GETADDRINFO_THREADSAFE 1 /* ---------------------------------------------------------------- */ /* STRUCT RELATED */ /* ---------------------------------------------------------------- */ /* Define if you have struct sockaddr_storage. */ -#if !defined(__SALFORDC__) && !defined(__BORLANDC__) #define HAVE_STRUCT_SOCKADDR_STORAGE 1 -#endif /* Define if you have struct timeval. */ #define HAVE_STRUCT_TIMEVAL 1 @@ -621,11 +397,6 @@ Vista /* Define if struct sockaddr_in6 has the sin6_scope_id member. */ #define HAVE_SOCKADDR_IN6_SIN6_SCOPE_ID 1 -#if defined(HAVE_WINSOCK2_H) && defined(_WIN32_WINNT) && \ - (_WIN32_WINNT >= 0x0600) -#define HAVE_STRUCT_POLLFD 1 -#endif - /* ---------------------------------------------------------------- */ /* LARGE FILE SUPPORT */ /* ---------------------------------------------------------------- */ @@ -642,16 +413,27 @@ Vista # define USE_WIN32_LARGE_FILES #endif -#if defined(__WATCOMC__) && !defined(USE_WIN32_LARGE_FILES) -# define USE_WIN32_LARGE_FILES +#if !defined(USE_WIN32_LARGE_FILES) && !defined(USE_WIN32_SMALL_FILES) +# define USE_WIN32_SMALL_FILES #endif -#if defined(__POCC__) -# undef USE_WIN32_LARGE_FILES +/* Number of bits in a file offset, on hosts where this is settable. */ +#if defined(USE_WIN32_LARGE_FILES) && defined(__MINGW32__) +# ifndef _FILE_OFFSET_BITS +# define _FILE_OFFSET_BITS 64 +# endif #endif -#if !defined(USE_WIN32_LARGE_FILES) && !defined(USE_WIN32_SMALL_FILES) -# define USE_WIN32_SMALL_FILES +#ifdef USE_WIN32_LARGE_FILES +#define HAVE__FSEEKI64 +#endif + +/* Define to the size of `off_t', as computed by sizeof. */ +#if defined(__MINGW32__) && \ + defined(_FILE_OFFSET_BITS) && (_FILE_OFFSET_BITS == 64) +# define SIZEOF_OFF_T 8 +#else +# define SIZEOF_OFF_T 4 #endif /* ---------------------------------------------------------------- */ @@ -679,7 +461,7 @@ Vista /* LDAP SUPPORT */ /* ---------------------------------------------------------------- */ -#if defined(CURL_HAS_NOVELL_LDAPSDK) || defined(CURL_HAS_MOZILLA_LDAPSDK) +#if defined(CURL_HAS_NOVELL_LDAPSDK) #undef USE_WIN32_LDAP #define HAVE_LDAP_SSL_H 1 #define HAVE_LDAP_URL_PARSE 1 @@ -688,40 +470,38 @@ Vista #define HAVE_LDAP_URL_PARSE 1 #else #undef HAVE_LDAP_URL_PARSE +#define HAVE_LDAP_SSL 1 #define USE_WIN32_LDAP 1 #endif -#if defined(__WATCOMC__) && defined(USE_WIN32_LDAP) -#if __WATCOMC__ < 1280 -#define WINBERAPI __declspec(cdecl) -#define WINLDAPAPI __declspec(cdecl) -#endif -#endif - -#if defined(__POCC__) && defined(USE_WIN32_LDAP) -# define CURL_DISABLE_LDAP 1 -#endif - /* Define to use the Windows crypto library. */ -#if !defined(USE_OPENSSL) && !defined(USE_NSS) +#if !defined(CURL_WINDOWS_UWP) #define USE_WIN32_CRYPTO #endif +/* Define to use Unix sockets. */ +#define USE_UNIX_SOCKETS + /* ---------------------------------------------------------------- */ /* ADDITIONAL DEFINITIONS */ /* ---------------------------------------------------------------- */ /* Define cpu-machine-OS */ -#undef OS +#ifndef OS #if defined(_M_IX86) || defined(__i386__) /* x86 (MSVC or gcc) */ #define OS "i386-pc-win32" #elif defined(_M_X64) || defined(__x86_64__) /* x86_64 (MSVC >=2005 or gcc) */ #define OS "x86_64-pc-win32" -#elif defined(_M_IA64) /* Itanium */ +#elif defined(_M_IA64) || defined(__ia64__) /* Itanium */ #define OS "ia64-pc-win32" +#elif defined(_M_ARM_NT) || defined(__arm__) /* ARMv7-Thumb2 (Windows RT) */ +#define OS "thumbv7a-pc-win32" +#elif defined(_M_ARM64) || defined(__aarch64__) /* ARM64 (Windows 10) */ +#define OS "aarch64-pc-win32" #else #define OS "unknown-pc-win32" #endif +#endif /* Name of package */ #define PACKAGE "curl" @@ -729,8 +509,4 @@ Vista /* If you want to build curl with the built-in manual */ #define USE_MANUAL 1 -#if defined(__POCC__) || defined(USE_IPV6) -# define ENABLE_IPV6 1 -#endif - -#endif /* HEADER_CURL_CONFIG_WIN32_H */ +#endif /* HEADER_CURL_CONFIG_WIN32_H */ \ No newline at end of file diff --git a/contrib/curl/lib/config-win32ce.h b/contrib/curl/lib/config-win32ce.h deleted file mode 100644 index 383948576d..0000000000 --- a/contrib/curl/lib/config-win32ce.h +++ /dev/null @@ -1,448 +0,0 @@ -#ifndef HEADER_CURL_CONFIG_WIN32CE_H -#define HEADER_CURL_CONFIG_WIN32CE_H -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) 1998 - 2015, Daniel Stenberg, , et al. - * - * This software is licensed as described in the file COPYING, which - * you should have received as part of this distribution. The terms - * are also available at https://curl.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. - * - ***************************************************************************/ - -/* ================================================================ */ -/* lib/config-win32ce.h - Hand crafted config file for windows ce */ -/* ================================================================ */ - -/* ---------------------------------------------------------------- */ -/* HEADER FILES */ -/* ---------------------------------------------------------------- */ - -/* Define if you have the header file. */ -/* #define HAVE_ARPA_INET_H 1 */ - -/* Define if you have the header file. */ -/* #define HAVE_ASSERT_H 1 */ - -/* Define if you have the header file. */ -/* #define HAVE_CRYPTO_H 1 */ - -/* Define if you have the header file. */ -/* #define HAVE_ERRNO_H 1 */ - -/* Define if you have the header file. */ -/* #define HAVE_ERR_H 1 */ - -/* Define if you have the header file. */ -#define HAVE_FCNTL_H 1 - -/* Define if you have the header file. */ -/* #define HAVE_GETOPT_H 1 */ - -/* Define if you have the header file. */ -#define HAVE_IO_H 1 - -/* Define if you have the header file. */ -#define HAVE_LIMITS_H 1 - -/* Define if you need the malloc.h header header file even with stdlib.h */ -#define NEED_MALLOC_H 1 - -/* Define if you have the header file. */ -/* #define HAVE_NETDB_H 1 */ - -/* Define if you have the header file. */ -/* #define HAVE_NETINET_IN_H 1 */ - -/* Define if you have the header file. */ -#define HAVE_SIGNAL_H 1 - -/* Define if you have the header file. */ -/* #define HAVE_SGTTY_H 1 */ - -/* Define if you have the header file. */ -/* #define HAVE_SSL_H 1 */ - -/* Define if you have the header file. */ -#define HAVE_STDLIB_H 1 - -/* Define if you have the header file. */ -/* #define HAVE_PROCESS_H 1 */ - -/* Define if you have the header file. */ -/* #define HAVE_SYS_PARAM_H 1 */ - -/* Define if you have the header file. */ -/* #define HAVE_SYS_SELECT_H 1 */ - -/* Define if you have the header file. */ -/* #define HAVE_SYS_SOCKET_H 1 */ - -/* Define if you have the header file. */ -/* #define HAVE_SYS_SOCKIO_H 1 */ - -/* Define if you have the header file. */ -#define HAVE_SYS_STAT_H 1 - -/* Define if you have the header file */ -/* #define HAVE_SYS_TIME_H 1 */ - -/* Define if you have the header file. */ -/* #define HAVE_SYS_TYPES_H 1 */ - -/* Define if you have the header file */ -#define HAVE_SYS_UTIME_H 1 - -/* Define if you have the header file. */ -/* #define HAVE_TERMIO_H 1 */ - -/* Define if you have the header file. */ -/* #define HAVE_TERMIOS_H 1 */ - -/* Define if you have the header file. */ -#define HAVE_TIME_H 1 - -/* Define if you have the header file. */ -#if defined(__MINGW32__) || defined(__WATCOMC__) || defined(__LCC__) -#define HAVE_UNISTD_H 1 -#endif - -/* Define if you have the header file. */ -#define HAVE_WINDOWS_H 1 - -/* Define if you have the header file. */ -#define HAVE_WINSOCK_H 1 - -/* Define if you have the header file. */ -/* #define HAVE_WINSOCK2_H 1 */ - -/* Define if you have the header file. */ -/* #define HAVE_WS2TCPIP_H 1 */ - -/* ---------------------------------------------------------------- */ -/* OTHER HEADER INFO */ -/* ---------------------------------------------------------------- */ - -/* Define if sig_atomic_t is an available typedef. */ -#define HAVE_SIG_ATOMIC_T 1 - -/* Define if you have the ANSI C header files. */ -#define STDC_HEADERS 1 - -/* Define if you can safely include both and . */ -/* #define TIME_WITH_SYS_TIME 1 */ - -/* ---------------------------------------------------------------- */ -/* FUNCTIONS */ -/* ---------------------------------------------------------------- */ - -/* Define if you have the closesocket function. */ -#define HAVE_CLOSESOCKET 1 - -/* Define if you don't have vprintf but do have _doprnt. */ -/* #define HAVE_DOPRNT 1 */ - -/* Define if you have the gethostbyaddr function. */ -#define HAVE_GETHOSTBYADDR 1 - -/* Define if you have the gethostname function. */ -#define HAVE_GETHOSTNAME 1 - -/* Define if you have the getpass function. */ -/* #define HAVE_GETPASS 1 */ - -/* Define if you have the getservbyname function. */ -#define HAVE_GETSERVBYNAME 1 - -/* Define if you have the gettimeofday function. */ -/* #define HAVE_GETTIMEOFDAY 1 */ - -/* Define if you have the inet_addr function. */ -#define HAVE_INET_ADDR 1 - -/* Define if you have the ioctlsocket function. */ -#define HAVE_IOCTLSOCKET 1 - -/* Define if you have a working ioctlsocket FIONBIO function. */ -#define HAVE_IOCTLSOCKET_FIONBIO 1 - -/* Define if you have the perror function. */ -#define HAVE_PERROR 1 - -/* Define if you have the RAND_screen function when using SSL */ -#define HAVE_RAND_SCREEN 1 - -/* Define if you have the `RAND_status' function when using SSL. */ -#define HAVE_RAND_STATUS 1 - -/* Define if you have the select function. */ -#define HAVE_SELECT 1 - -/* Define if you have the setvbuf function. */ -#define HAVE_SETVBUF 1 - -/* Define if you have the socket function. */ -#define HAVE_SOCKET 1 - -/* Define if you have the strcasecmp function. */ -/* #define HAVE_STRCASECMP 1 */ - -/* Define if you have the strdup function. */ -/* #define HAVE_STRDUP 1 */ - -/* Define if you have the strftime function. */ -/* #define HAVE_STRFTIME 1 */ - -/* Define if you have the stricmp function. */ -/* #define HAVE_STRICMP 1 */ - -/* Define if you have the strncasecmp function. */ -/* #define HAVE_STRNCASECMP 1 */ - -/* Define if you have the strnicmp function. */ -/* #define HAVE_STRNICMP 1 */ - -/* Define if you have the strstr function. */ -#define HAVE_STRSTR 1 - -/* Define if you have the strtoll function. */ -#if defined(__MINGW32__) || defined(__WATCOMC__) -#define HAVE_STRTOLL 1 -#endif - -/* Define if you have the tcgetattr function. */ -/* #define HAVE_TCGETATTR 1 */ - -/* Define if you have the tcsetattr function. */ -/* #define HAVE_TCSETATTR 1 */ - -/* Define if you have the utime function */ -#define HAVE_UTIME 1 - -/* Define if you have the getnameinfo function. */ -#define HAVE_GETNAMEINFO 1 - -/* Define to the type qualifier of arg 1 for getnameinfo. */ -#define GETNAMEINFO_QUAL_ARG1 const - -/* Define to the type of arg 1 for getnameinfo. */ -#define GETNAMEINFO_TYPE_ARG1 struct sockaddr * - -/* Define to the type of arg 2 for getnameinfo. */ -#define GETNAMEINFO_TYPE_ARG2 socklen_t - -/* Define to the type of args 4 and 6 for getnameinfo. */ -#define GETNAMEINFO_TYPE_ARG46 DWORD - -/* Define to the type of arg 7 for getnameinfo. */ -#define GETNAMEINFO_TYPE_ARG7 int - -/* Define if you have the recv function. */ -#define HAVE_RECV 1 - -/* Define to the type of arg 1 for recv. */ -#define RECV_TYPE_ARG1 SOCKET - -/* Define to the type of arg 2 for recv. */ -#define RECV_TYPE_ARG2 char * - -/* Define to the type of arg 3 for recv. */ -#define RECV_TYPE_ARG3 int - -/* Define to the type of arg 4 for recv. */ -#define RECV_TYPE_ARG4 int - -/* Define to the function return type for recv. */ -#define RECV_TYPE_RETV int - -/* Define if you have the recvfrom function. */ -#define HAVE_RECVFROM 1 - -/* Define to the type of arg 1 for recvfrom. */ -#define RECVFROM_TYPE_ARG1 SOCKET - -/* Define to the type pointed by arg 2 for recvfrom. */ -#define RECVFROM_TYPE_ARG2 char - -/* Define to the type of arg 3 for recvfrom. */ -#define RECVFROM_TYPE_ARG3 int - -/* Define to the type of arg 4 for recvfrom. */ -#define RECVFROM_TYPE_ARG4 int - -/* Define to the type pointed by arg 5 for recvfrom. */ -#define RECVFROM_TYPE_ARG5 struct sockaddr - -/* Define to the type pointed by arg 6 for recvfrom. */ -#define RECVFROM_TYPE_ARG6 int - -/* Define to the function return type for recvfrom. */ -#define RECVFROM_TYPE_RETV int - -/* Define if you have the send function. */ -#define HAVE_SEND 1 - -/* Define to the type of arg 1 for send. */ -#define SEND_TYPE_ARG1 SOCKET - -/* Define to the type qualifier of arg 2 for send. */ -#define SEND_QUAL_ARG2 const - -/* Define to the type of arg 2 for send. */ -#define SEND_TYPE_ARG2 char * - -/* Define to the type of arg 3 for send. */ -#define SEND_TYPE_ARG3 int - -/* Define to the type of arg 4 for send. */ -#define SEND_TYPE_ARG4 int - -/* Define to the function return type for send. */ -#define SEND_TYPE_RETV int - -/* ---------------------------------------------------------------- */ -/* TYPEDEF REPLACEMENTS */ -/* ---------------------------------------------------------------- */ - -/* Define this if in_addr_t is not an available 'typedefed' type */ -#define in_addr_t unsigned long - -/* Define as the return type of signal handlers (int or void). */ -#define RETSIGTYPE void - -/* Define ssize_t if it is not an available 'typedefed' type */ -#if (defined(__WATCOMC__) && (__WATCOMC__ >= 1240)) || defined(__POCC__) -#elif defined(_WIN64) -#define ssize_t __int64 -#else -#define ssize_t int -#endif - -/* ---------------------------------------------------------------- */ -/* TYPE SIZES */ -/* ---------------------------------------------------------------- */ - -/* The size of `int', as computed by sizeof. */ -#define SIZEOF_INT 4 - -/* The size of `long double', as computed by sizeof. */ -#define SIZEOF_LONG_DOUBLE 16 - -/* The size of `long long', as computed by sizeof. */ -/* #define SIZEOF_LONG_LONG 8 */ - -/* The size of `short', as computed by sizeof. */ -#define SIZEOF_SHORT 2 - -/* The size of `size_t', as computed by sizeof. */ -#if defined(_WIN64) -# define SIZEOF_SIZE_T 8 -#else -# define SIZEOF_SIZE_T 4 -#endif - -/* ---------------------------------------------------------------- */ -/* STRUCT RELATED */ -/* ---------------------------------------------------------------- */ - -/* Define this if you have struct sockaddr_storage */ -/* #define HAVE_STRUCT_SOCKADDR_STORAGE 1 */ - -/* Define this if you have struct timeval */ -#define HAVE_STRUCT_TIMEVAL 1 - -/* Define this if struct sockaddr_in6 has the sin6_scope_id member */ -#define HAVE_SOCKADDR_IN6_SIN6_SCOPE_ID 1 - -/* ---------------------------------------------------------------- */ -/* COMPILER SPECIFIC */ -/* ---------------------------------------------------------------- */ - -/* Undef keyword 'const' if it does not work. */ -/* #undef const */ - -/* Define to avoid VS2005 complaining about portable C functions */ -#if defined(_MSC_VER) && (_MSC_VER >= 1400) -#define _CRT_SECURE_NO_DEPRECATE 1 -#define _CRT_NONSTDC_NO_DEPRECATE 1 -#endif - -/* VS2005 and later dafault size for time_t is 64-bit, unless */ -/* _USE_32BIT_TIME_T has been defined to get a 32-bit time_t. */ -#if defined(_MSC_VER) && (_MSC_VER >= 1400) -# ifndef _USE_32BIT_TIME_T -# define SIZEOF_TIME_T 8 -# else -# define SIZEOF_TIME_T 4 -# endif -#endif - -/* ---------------------------------------------------------------- */ -/* LARGE FILE SUPPORT */ -/* ---------------------------------------------------------------- */ - -#if defined(_MSC_VER) && !defined(_WIN32_WCE) -# if (_MSC_VER >= 900) && (_INTEGRAL_MAX_BITS >= 64) -# define USE_WIN32_LARGE_FILES -# else -# define USE_WIN32_SMALL_FILES -# endif -#endif - -#if !defined(USE_WIN32_LARGE_FILES) && !defined(USE_WIN32_SMALL_FILES) -# define USE_WIN32_SMALL_FILES -#endif - -/* ---------------------------------------------------------------- */ -/* LDAP SUPPORT */ -/* ---------------------------------------------------------------- */ - -#define USE_WIN32_LDAP 1 -#undef HAVE_LDAP_URL_PARSE - -/* ---------------------------------------------------------------- */ -/* ADDITIONAL DEFINITIONS */ -/* ---------------------------------------------------------------- */ - -/* Define cpu-machine-OS */ -#undef OS -#define OS "i386-pc-win32ce" - -/* Name of package */ -#define PACKAGE "curl" - -/* ---------------------------------------------------------------- */ -/* WinCE */ -/* ---------------------------------------------------------------- */ - -#ifndef UNICODE -# define UNICODE -#endif - -#ifndef _UNICODE -# define _UNICODE -#endif - -#define CURL_DISABLE_FILE 1 -#define CURL_DISABLE_TELNET 1 -#define CURL_DISABLE_LDAP 1 - -#define ENOSPC 1 -#define ENOMEM 2 -#define EAGAIN 3 - -extern int stat(const char *path, struct stat *buffer); - -#endif /* HEADER_CURL_CONFIG_WIN32CE_H */ diff --git a/contrib/curl/lib/conncache.c b/contrib/curl/lib/conncache.c index a51c8fd760..73239c611d 100644 --- a/contrib/curl/lib/conncache.c +++ b/contrib/curl/lib/conncache.c @@ -5,12 +5,12 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 2012, 2016, Linus Nielsen Feltzing, - * Copyright (C) 2012 - 2015, Daniel Stenberg, , et al. + * Copyright (C) Linus Nielsen Feltzing, + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -19,6 +19,8 @@ * 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" @@ -27,341 +29,1350 @@ #include "urldata.h" #include "url.h" +#include "cfilters.h" #include "progress.h" #include "multiif.h" #include "sendf.h" #include "conncache.h" +#include "http_negotiate.h" +#include "http_ntlm.h" +#include "share.h" +#include "sigpipe.h" +#include "connect.h" +#include "select.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" -static void conn_llist_dtor(void *user, void *element) + +#define CPOOL_IS_LOCKED(c) ((c) && (c)->locked) + +#define CPOOL_LOCK(c) \ + do { \ + if((c)) { \ + if(CURL_SHARE_KEEP_CONNECT((c)->share)) \ + Curl_share_lock(((c)->idata), CURL_LOCK_DATA_CONNECT, \ + CURL_LOCK_ACCESS_SINGLE); \ + DEBUGASSERT(!(c)->locked); \ + (c)->locked = TRUE; \ + } \ + } while(0) + +#define CPOOL_UNLOCK(c) \ + do { \ + if((c)) { \ + DEBUGASSERT((c)->locked); \ + (c)->locked = FALSE; \ + if(CURL_SHARE_KEEP_CONNECT((c)->share)) \ + Curl_share_unlock((c)->idata, CURL_LOCK_DATA_CONNECT); \ + } \ + } while(0) + + +/* A list of connections to the same destinationn. */ +struct cpool_bundle { + struct Curl_llist conns; /* connections in the bundle */ + size_t dest_len; /* total length of destination, including NUL */ + char *dest[1]; /* destination of bundle, allocated to keep dest_len bytes */ +}; + + +static void cpool_discard_conn(struct cpool *cpool, + struct Curl_easy *data, + struct connectdata *conn, + bool aborted); +static void cpool_close_and_destroy(struct cpool *cpool, + struct connectdata *conn, + struct Curl_easy *data, + bool do_shutdown); +static void cpool_run_conn_shutdown(struct Curl_easy *data, + struct connectdata *conn, + bool *done); +static void cpool_run_conn_shutdown_handler(struct Curl_easy *data, + struct connectdata *conn); +static CURLMcode cpool_update_shutdown_ev(struct Curl_multi *multi, + struct Curl_easy *data, + struct connectdata *conn); +static void cpool_shutdown_all(struct cpool *cpool, + struct Curl_easy *data, int timeout_ms); +static void cpool_close_and_destroy_all(struct cpool *cpool); +static struct connectdata *cpool_get_oldest_idle(struct cpool *cpool); + +static struct cpool_bundle *cpool_bundle_create(const char *dest, + size_t dest_len) { - struct connectdata *data = element; - (void)user; + struct cpool_bundle *bundle; + bundle = calloc(1, sizeof(*bundle) + dest_len); + if(!bundle) + return NULL; + Curl_llist_init(&bundle->conns, NULL); + bundle->dest_len = dest_len; + memcpy(bundle->dest, dest, dest_len); + return bundle; +} - data->bundle = NULL; +static void cpool_bundle_destroy(struct cpool_bundle *bundle) +{ + DEBUGASSERT(!Curl_llist_count(&bundle->conns)); + free(bundle); } -static CURLcode bundle_create(struct Curl_easy *data, - struct connectbundle **cb_ptr) +/* Add a connection to a bundle */ +static void cpool_bundle_add(struct cpool_bundle *bundle, + struct connectdata *conn) { - (void)data; - DEBUGASSERT(*cb_ptr == NULL); - *cb_ptr = malloc(sizeof(struct connectbundle)); - if(!*cb_ptr) - return CURLE_OUT_OF_MEMORY; + DEBUGASSERT(!Curl_node_llist(&conn->cpool_node)); + Curl_llist_append(&bundle->conns, conn, &conn->cpool_node); + conn->bits.in_cpool = TRUE; +} + +/* Remove a connection from a bundle */ +static void cpool_bundle_remove(struct cpool_bundle *bundle, + struct connectdata *conn) +{ + (void)bundle; + DEBUGASSERT(Curl_node_llist(&conn->cpool_node) == &bundle->conns); + Curl_node_remove(&conn->cpool_node); + conn->bits.in_cpool = FALSE; +} + +static void cpool_bundle_free_entry(void *freethis) +{ + cpool_bundle_destroy((struct cpool_bundle *)freethis); +} + +int Curl_cpool_init(struct cpool *cpool, + Curl_cpool_disconnect_cb *disconnect_cb, + struct Curl_multi *multi, + struct Curl_share *share, + size_t size) +{ + DEBUGASSERT(!!multi != !!share); /* either one */ + Curl_hash_init(&cpool->dest2bundle, size, Curl_hash_str, + Curl_str_key_compare, cpool_bundle_free_entry); + Curl_llist_init(&cpool->shutdowns, NULL); + + DEBUGASSERT(disconnect_cb); + if(!disconnect_cb) + return 1; + + /* allocate a new easy handle to use when closing cached connections */ + cpool->idata = curl_easy_init(); + if(!cpool->idata) + return 1; /* bad */ + cpool->idata->state.internal = true; + /* TODO: this is quirky. We need an internal handle for certain + * operations, but we do not add it to the multi (if there is one). + * But we give it the multi so that socket event operations can work. + * Probably better to have an internal handle owned by the multi that + * can be used for cpool operations. */ + cpool->idata->multi = multi; + #ifdef DEBUGBUILD + if(getenv("CURL_DEBUG")) + cpool->idata->set.verbose = true; +#endif + + cpool->disconnect_cb = disconnect_cb; + cpool->idata->multi = cpool->multi = multi; + cpool->idata->share = cpool->share = share; - (*cb_ptr)->num_connections = 0; - (*cb_ptr)->multiuse = BUNDLE_UNKNOWN; + return 0; /* good */ +} + +void Curl_cpool_destroy(struct cpool *cpool) +{ + if(cpool) { + if(cpool->idata) { + cpool_close_and_destroy_all(cpool); + /* The internal closure handle is special and we need to + * disconnect it from multi/share before closing it down. */ + cpool->idata->multi = NULL; + cpool->idata->share = NULL; + Curl_close(&cpool->idata); + } + Curl_hash_destroy(&cpool->dest2bundle); + cpool->multi = NULL; + } +} - (*cb_ptr)->conn_list = Curl_llist_alloc((curl_llist_dtor) conn_llist_dtor); - if(!(*cb_ptr)->conn_list) { - Curl_safefree(*cb_ptr); - return CURLE_OUT_OF_MEMORY; +static struct cpool *cpool_get_instance(struct Curl_easy *data) +{ + if(data) { + if(CURL_SHARE_KEEP_CONNECT(data->share)) + return &data->share->cpool; + else if(data->multi_easy) + return &data->multi_easy->cpool; + else if(data->multi) + return &data->multi->cpool; } - return CURLE_OK; + return NULL; +} + +void Curl_cpool_xfer_init(struct Curl_easy *data) +{ + struct cpool *cpool = cpool_get_instance(data); + + DEBUGASSERT(cpool); + if(cpool) { + CPOOL_LOCK(cpool); + /* the identifier inside the connection cache */ + data->id = cpool->next_easy_id++; + if(cpool->next_easy_id <= 0) + cpool->next_easy_id = 0; + data->state.lastconnect_id = -1; + + /* 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. */ + cpool->idata->set.timeout = data->set.timeout; + cpool->idata->set.server_response_timeout = + data->set.server_response_timeout; + cpool->idata->set.no_signal = data->set.no_signal; + + CPOOL_UNLOCK(cpool); + } + else { + /* We should not get here, but in a non-debug build, do something */ + data->id = 0; + data->state.lastconnect_id = -1; + } +} + +static struct cpool_bundle *cpool_find_bundle(struct cpool *cpool, + struct connectdata *conn) +{ + return Curl_hash_pick(&cpool->dest2bundle, + conn->destination, conn->destination_len); } -static void bundle_destroy(struct connectbundle *cb_ptr) +static struct cpool_bundle * +cpool_add_bundle(struct cpool *cpool, struct connectdata *conn) { - if(!cb_ptr) + struct cpool_bundle *bundle; + + bundle = cpool_bundle_create(conn->destination, conn->destination_len); + if(!bundle) + return NULL; + + if(!Curl_hash_add(&cpool->dest2bundle, + bundle->dest, bundle->dest_len, bundle)) { + cpool_bundle_destroy(bundle); + return NULL; + } + return bundle; +} + +static void cpool_remove_bundle(struct cpool *cpool, + struct cpool_bundle *bundle) +{ + struct Curl_hash_iterator iter; + struct Curl_hash_element *he; + + if(!cpool) return; - if(cb_ptr->conn_list) { - Curl_llist_destroy(cb_ptr->conn_list, NULL); - cb_ptr->conn_list = NULL; + Curl_hash_start_iterate(&cpool->dest2bundle, &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(&cpool->dest2bundle, he->key, he->key_len); + return; + } + + he = Curl_hash_next_element(&iter); } - free(cb_ptr); } -/* Add a connection to a bundle */ -static CURLcode bundle_add_conn(struct connectbundle *cb_ptr, - struct connectdata *conn) +static struct connectdata * +cpool_bundle_get_oldest_idle(struct cpool_bundle *bundle); + +int Curl_cpool_check_limits(struct Curl_easy *data, + struct connectdata *conn) { - if(!Curl_llist_insert_next(cb_ptr->conn_list, cb_ptr->conn_list->tail, conn)) - return CURLE_OUT_OF_MEMORY; + struct cpool *cpool = cpool_get_instance(data); + struct cpool_bundle *bundle; + size_t dest_limit = 0; + size_t total_limit = 0; + int result = CPOOL_LIMIT_OK; + + if(!cpool) + return CPOOL_LIMIT_OK; + + if(data && data->multi) { + dest_limit = data->multi->max_host_connections; + total_limit = data->multi->max_total_connections; + } - conn->bundle = cb_ptr; + if(!dest_limit && !total_limit) + return CPOOL_LIMIT_OK; + + CPOOL_LOCK(cpool); + if(dest_limit) { + bundle = cpool_find_bundle(cpool, conn); + while(bundle && (Curl_llist_count(&bundle->conns) >= dest_limit)) { + struct connectdata *oldest_idle = NULL; + /* The bundle is full. Extract the oldest connection that may + * be removed now, if there is one. */ + oldest_idle = cpool_bundle_get_oldest_idle(bundle); + if(!oldest_idle) + break; + /* disconnect the old conn and continue */ + DEBUGF(infof(data, "Discarding connection #%" + FMT_OFF_T " from %zu to reach destination " + "limit of %zu", oldest_idle->connection_id, + Curl_llist_count(&bundle->conns), dest_limit)); + Curl_cpool_disconnect(data, oldest_idle, FALSE); + } + if(bundle && (Curl_llist_count(&bundle->conns) >= dest_limit)) { + result = CPOOL_LIMIT_DEST; + goto out; + } + } - cb_ptr->num_connections++; - return CURLE_OK; + if(total_limit) { + while(cpool->num_conn >= total_limit) { + struct connectdata *oldest_idle = cpool_get_oldest_idle(cpool); + if(!oldest_idle) + break; + /* disconnect the old conn and continue */ + DEBUGF(infof(data, "Discarding connection #%" + FMT_OFF_T " from %zu to reach total " + "limit of %zu", + oldest_idle->connection_id, cpool->num_conn, total_limit)); + Curl_cpool_disconnect(data, oldest_idle, FALSE); + } + if(cpool->num_conn >= total_limit) { + result = CPOOL_LIMIT_TOTAL; + goto out; + } + } + +out: + CPOOL_UNLOCK(cpool); + return result; } -/* Remove a connection from a bundle */ -static int bundle_remove_conn(struct connectbundle *cb_ptr, - struct connectdata *conn) +CURLcode Curl_cpool_add_conn(struct Curl_easy *data, + struct connectdata *conn) { - struct curl_llist_element *curr; + CURLcode result = CURLE_OK; + struct cpool_bundle *bundle = NULL; + struct cpool *cpool = cpool_get_instance(data); + DEBUGASSERT(conn); - curr = cb_ptr->conn_list->head; - while(curr) { - if(curr->ptr == conn) { - Curl_llist_remove(cb_ptr->conn_list, curr, NULL); - cb_ptr->num_connections--; - conn->bundle = NULL; - return 1; /* we removed a handle */ + DEBUGASSERT(cpool); + if(!cpool) + return CURLE_FAILED_INIT; + + CPOOL_LOCK(cpool); + bundle = cpool_find_bundle(cpool, conn); + if(!bundle) { + bundle = cpool_add_bundle(cpool, conn); + if(!bundle) { + result = CURLE_OUT_OF_MEMORY; + goto out; } - curr = curr->next; } - return 0; + + cpool_bundle_add(bundle, conn); + conn->connection_id = cpool->next_connection_id++; + cpool->num_conn++; + DEBUGF(infof(data, "Added connection %" FMT_OFF_T ". " + "The cache now contains %zu members", + conn->connection_id, cpool->num_conn)); +out: + CPOOL_UNLOCK(cpool); + + return result; } -static void free_bundle_hash_entry(void *freethis) +static void cpool_remove_conn(struct cpool *cpool, + struct connectdata *conn) { - struct connectbundle *b = (struct connectbundle *) freethis; + struct Curl_llist *list = Curl_node_llist(&conn->cpool_node); + DEBUGASSERT(cpool); + if(list) { + /* The connection is certainly in the pool, but where? */ + struct cpool_bundle *bundle = cpool_find_bundle(cpool, conn); + if(bundle && (list == &bundle->conns)) { + cpool_bundle_remove(bundle, conn); + if(!Curl_llist_count(&bundle->conns)) + cpool_remove_bundle(cpool, bundle); + conn->bits.in_cpool = FALSE; + cpool->num_conn--; + } + else { + /* Not in a bundle, already in the shutdown list? */ + DEBUGASSERT(list == &cpool->shutdowns); + } + } +} + +/* This function iterates the entire connection pool and calls the function + func() with the connection pointer as the first argument and the supplied + 'param' argument as the other. + + The cpool 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 TRUE 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. + */ +static bool cpool_foreach(struct Curl_easy *data, + struct cpool *cpool, + void *param, + int (*func)(struct Curl_easy *data, + struct connectdata *conn, void *param)) +{ + struct Curl_hash_iterator iter; + struct Curl_hash_element *he; + + if(!cpool) + return FALSE; + + Curl_hash_start_iterate(&cpool->dest2bundle, &iter); + + he = Curl_hash_next_element(&iter); + while(he) { + struct Curl_llist_node *curr; + struct cpool_bundle *bundle = he->ptr; + he = Curl_hash_next_element(&iter); + + curr = Curl_llist_head(&bundle->conns); + while(curr) { + /* Yes, we need to update curr before calling func(), because func() + might decide to remove the connection */ + struct connectdata *conn = Curl_node_elem(curr); + curr = Curl_node_next(curr); - bundle_destroy(b); + if(1 == func(data, conn, param)) { + return TRUE; + } + } + } + return FALSE; } -int Curl_conncache_init(struct conncache *connc, int size) +/* Return a live connection in the pool or NULL. */ +static struct connectdata *cpool_get_live_conn(struct cpool *cpool) { - return Curl_hash_init(&connc->hash, size, Curl_hash_str, - Curl_str_key_compare, free_bundle_hash_entry); + struct Curl_hash_iterator iter; + struct Curl_hash_element *he; + struct cpool_bundle *bundle; + struct Curl_llist_node *conn_node; + + Curl_hash_start_iterate(&cpool->dest2bundle, &iter); + for(he = Curl_hash_next_element(&iter); he; + he = Curl_hash_next_element(&iter)) { + bundle = he->ptr; + conn_node = Curl_llist_head(&bundle->conns); + if(conn_node) + return Curl_node_elem(conn_node); + } + return NULL; } -void Curl_conncache_destroy(struct conncache *connc) +/* + * A connection (already in the pool) has become idle. Do any + * cleanups in regard to the pool's limits. + * + * Return TRUE if idle connection kept in pool, FALSE if closed. + */ +bool Curl_cpool_conn_now_idle(struct Curl_easy *data, + struct connectdata *conn) { - if(connc) - Curl_hash_destroy(&connc->hash); + unsigned int maxconnects = !data->multi->maxconnects ? + data->multi->num_easy * 4 : data->multi->maxconnects; + struct connectdata *oldest_idle = NULL; + struct cpool *cpool = cpool_get_instance(data); + bool kept = TRUE; + + conn->lastused = Curl_now(); /* it was used up until now */ + if(cpool && maxconnects) { + /* may be called form a callback already under lock */ + bool do_lock = !CPOOL_IS_LOCKED(cpool); + if(do_lock) + CPOOL_LOCK(cpool); + if(cpool->num_conn > maxconnects) { + infof(data, "Connection pool is full, closing the oldest one"); + + oldest_idle = cpool_get_oldest_idle(cpool); + kept = (oldest_idle != conn); + if(oldest_idle) { + Curl_cpool_disconnect(cpool->idata, oldest_idle, FALSE); + } + } + if(do_lock) + CPOOL_UNLOCK(cpool); + } + + return kept; } -/* returns an allocated key to find a bundle for this connection */ -static char *hashkey(struct connectdata *conn) +/* + * This function finds the connection in the connection bundle that has been + * unused for the longest time. + */ +static struct connectdata * +cpool_bundle_get_oldest_idle(struct cpool_bundle *bundle) { - const char *hostname; + struct Curl_llist_node *curr; + timediff_t highscore = -1; + timediff_t score; + struct curltime now; + struct connectdata *oldest_idle = NULL; + struct connectdata *conn; + + now = Curl_now(); + curr = Curl_llist_head(&bundle->conns); + while(curr) { + conn = Curl_node_elem(curr); - if(conn->bits.socksproxy) - hostname = conn->socks_proxy.host.name; - else if(conn->bits.httpproxy) - hostname = conn->http_proxy.host.name; - else if(conn->bits.conn_to_host) - hostname = conn->conn_to_host.name; - else - hostname = conn->host.name; + 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; + oldest_idle = conn; + } + } + curr = Curl_node_next(curr); + } + return oldest_idle; +} + +static struct connectdata *cpool_get_oldest_idle(struct cpool *cpool) +{ + struct Curl_hash_iterator iter; + struct Curl_llist_node *curr; + struct Curl_hash_element *he; + struct connectdata *oldest_idle = NULL; + struct cpool_bundle *bundle; + struct curltime now; + timediff_t highscore =- 1; + timediff_t score; + + now = Curl_now(); + Curl_hash_start_iterate(&cpool->dest2bundle, &iter); + + for(he = Curl_hash_next_element(&iter); he; + he = Curl_hash_next_element(&iter)) { + struct connectdata *conn; + bundle = he->ptr; - return aprintf("%s:%ld", hostname, conn->port); + for(curr = Curl_llist_head(&bundle->conns); curr; + curr = Curl_node_next(curr)) { + conn = Curl_node_elem(curr); + if(CONN_INUSE(conn) || conn->bits.close || conn->connect_only) + continue; + /* Set higher score for the age passed since the connection was used */ + score = Curl_timediff(now, conn->lastused); + if(score > highscore) { + highscore = score; + oldest_idle = conn; + } + } + } + return oldest_idle; } -/* Look up the bundle with all the connections to the same host this - connectdata struct is setup to use. */ -struct connectbundle *Curl_conncache_find_bundle(struct connectdata *conn, - struct conncache *connc) +bool Curl_cpool_find(struct Curl_easy *data, + const char *destination, size_t dest_len, + Curl_cpool_conn_match_cb *conn_cb, + Curl_cpool_done_match_cb *done_cb, + void *userdata) { - struct connectbundle *bundle = NULL; - if(connc) { - char *key = hashkey(conn); - if(key) { - bundle = Curl_hash_pick(&connc->hash, key, strlen(key)); - free(key); + struct cpool *cpool = cpool_get_instance(data); + struct cpool_bundle *bundle; + bool result = FALSE; + + DEBUGASSERT(cpool); + DEBUGASSERT(conn_cb); + if(!cpool) + return FALSE; + + CPOOL_LOCK(cpool); + bundle = Curl_hash_pick(&cpool->dest2bundle, (void *)destination, dest_len); + if(bundle) { + struct Curl_llist_node *curr = Curl_llist_head(&bundle->conns); + while(curr) { + struct connectdata *conn = Curl_node_elem(curr); + /* Get next node now. callback might discard current */ + curr = Curl_node_next(curr); + + if(conn_cb(conn, userdata)) { + result = TRUE; + break; + } } } - return bundle; + if(done_cb) { + result = done_cb(result, userdata); + } + CPOOL_UNLOCK(cpool); + return result; +} + +static void cpool_shutdown_discard_all(struct cpool *cpool) +{ + struct Curl_llist_node *e = Curl_llist_head(&cpool->shutdowns); + struct connectdata *conn; + + if(!e) + return; + + DEBUGF(infof(cpool->idata, "cpool_shutdown_discard_all")); + while(e) { + conn = Curl_node_elem(e); + Curl_node_remove(e); + DEBUGF(infof(cpool->idata, "discard connection #%" FMT_OFF_T, + conn->connection_id)); + cpool_close_and_destroy(cpool, conn, NULL, FALSE); + e = Curl_llist_head(&cpool->shutdowns); + } } -static bool conncache_add_bundle(struct conncache *connc, - char *key, - struct connectbundle *bundle) +static void cpool_close_and_destroy_all(struct cpool *cpool) { - void *p = Curl_hash_add(&connc->hash, key, strlen(key), bundle); + struct connectdata *conn; + int timeout_ms = 0; + SIGPIPE_VARIABLE(pipe_st); + + DEBUGASSERT(cpool); + /* Move all connections to the shutdown list */ + sigpipe_init(&pipe_st); + CPOOL_LOCK(cpool); + conn = cpool_get_live_conn(cpool); + while(conn) { + cpool_remove_conn(cpool, conn); + sigpipe_apply(cpool->idata, &pipe_st); + connclose(conn, "kill all"); + cpool_discard_conn(cpool, cpool->idata, conn, FALSE); + + conn = cpool_get_live_conn(cpool); + } + CPOOL_UNLOCK(cpool); + + /* Just for testing, run graceful shutdown */ +#ifdef DEBUGBUILD + { + char *p = getenv("CURL_GRACEFUL_SHUTDOWN"); + if(p) { + long l = strtol(p, NULL, 10); + if(l > 0 && l < INT_MAX) + timeout_ms = (int)l; + } + } +#endif + sigpipe_apply(cpool->idata, &pipe_st); + cpool_shutdown_all(cpool, cpool->idata, timeout_ms); + + /* discard all connections in the shutdown list */ + cpool_shutdown_discard_all(cpool); - return p?TRUE:FALSE; + Curl_hostcache_clean(cpool->idata, cpool->idata->dns.hostcache); + sigpipe_restore(&pipe_st); } -static void conncache_remove_bundle(struct conncache *connc, - struct connectbundle *bundle) + +static void cpool_shutdown_destroy_oldest(struct cpool *cpool) { - struct curl_hash_iterator iter; - struct curl_hash_element *he; + struct Curl_llist_node *e; + struct connectdata *conn; + + e = Curl_llist_head(&cpool->shutdowns); + if(e) { + SIGPIPE_VARIABLE(pipe_st); + conn = Curl_node_elem(e); + Curl_node_remove(e); + sigpipe_init(&pipe_st); + sigpipe_apply(cpool->idata, &pipe_st); + cpool_close_and_destroy(cpool, conn, NULL, FALSE); + sigpipe_restore(&pipe_st); + } +} - if(!connc) +static void cpool_discard_conn(struct cpool *cpool, + struct Curl_easy *data, + struct connectdata *conn, + bool aborted) +{ + bool done = FALSE; + + DEBUGASSERT(data); + DEBUGASSERT(cpool); + DEBUGASSERT(!conn->bits.in_cpool); + + /* + * If this connection is not marked to force-close, leave it open if there + * are other users of it + */ + if(CONN_INUSE(conn) && !aborted) { + DEBUGF(infof(data, "[CCACHE] not discarding #%" FMT_OFF_T + " still in use by %zu transfers", conn->connection_id, + CONN_INUSE(conn))); return; + } - Curl_hash_start_iterate(&connc->hash, &iter); + /* treat the connection as aborted in CONNECT_ONLY situations, we do + * not know what the APP did with it. */ + if(conn->connect_only) + aborted = TRUE; + conn->bits.aborted = aborted; + + /* We do not shutdown dead connections. The term 'dead' can be misleading + * here, as we also mark errored connections/transfers as 'dead'. + * If we do a shutdown for an aborted transfer, the server might think + * it was successful otherwise (for example an ftps: upload). This is + * not what we want. */ + if(aborted) + done = TRUE; + if(!done) { + /* Attempt to shutdown the connection right away. */ + Curl_attach_connection(data, conn); + cpool_run_conn_shutdown(data, conn, &done); + DEBUGF(infof(data, "[CCACHE] shutdown #%" FMT_OFF_T ", done=%d", + conn->connection_id, done)); + Curl_detach_connection(data); + } - 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); + if(done) { + cpool_close_and_destroy(cpool, conn, data, FALSE); + return; + } + + /* Add the connection to our shutdown list for non-blocking shutdown + * during multi processing. */ + if(data->multi && data->multi->max_shutdown_connections > 0 && + (data->multi->max_shutdown_connections >= + (long)Curl_llist_count(&cpool->shutdowns))) { + DEBUGF(infof(data, "[CCACHE] discarding oldest shutdown connection " + "due to limit of %ld", + data->multi->max_shutdown_connections)); + cpool_shutdown_destroy_oldest(cpool); + } + + if(data->multi && data->multi->socket_cb) { + DEBUGASSERT(cpool == &data->multi->cpool); + /* Start with an empty shutdown pollset, so out internal closure handle + * is added to the sockets. */ + memset(&conn->shutdown_poll, 0, sizeof(conn->shutdown_poll)); + if(cpool_update_shutdown_ev(data->multi, cpool->idata, conn)) { + DEBUGF(infof(data, "[CCACHE] update events for shutdown failed, " + "discarding #%" FMT_OFF_T, + conn->connection_id)); + cpool_close_and_destroy(cpool, conn, data, FALSE); return; } + } - he = Curl_hash_next_element(&iter); + Curl_llist_append(&cpool->shutdowns, conn, &conn->cpool_node); + DEBUGF(infof(data, "[CCACHE] added #%" FMT_OFF_T + " to shutdown list of length %zu", conn->connection_id, + Curl_llist_count(&cpool->shutdowns))); +} + +void Curl_cpool_disconnect(struct Curl_easy *data, + struct connectdata *conn, + bool aborted) +{ + struct cpool *cpool = cpool_get_instance(data); + bool do_lock; + + DEBUGASSERT(cpool); + DEBUGASSERT(data && !data->conn); + if(!cpool) + return; + + /* If this connection is not marked to force-close, leave it open if there + * are other users of it */ + if(CONN_INUSE(conn) && !aborted) { + DEBUGASSERT(0); /* does this ever happen? */ + DEBUGF(infof(data, "Curl_disconnect when inuse: %zu", CONN_INUSE(conn))); + return; + } + + /* This method may be called while we are under lock, e.g. from a + * user callback in find. */ + do_lock = !CPOOL_IS_LOCKED(cpool); + if(do_lock) + CPOOL_LOCK(cpool); + + if(conn->bits.in_cpool) { + cpool_remove_conn(cpool, conn); + DEBUGASSERT(!conn->bits.in_cpool); + } + + /* Run the callback to let it clean up anything it wants to. */ + aborted = cpool->disconnect_cb(data, conn, aborted); + + if(data->multi) { + /* Add it to the multi's cpool for shutdown handling */ + infof(data, "%s connection #%" FMT_OFF_T, + aborted ? "closing" : "shutting down", conn->connection_id); + cpool_discard_conn(&data->multi->cpool, data, conn, aborted); } + else { + /* No multi available. Make a best-effort shutdown + close */ + infof(data, "closing connection #%" FMT_OFF_T, conn->connection_id); + cpool_close_and_destroy(NULL, conn, data, !aborted); + } + + if(do_lock) + CPOOL_UNLOCK(cpool); } -CURLcode Curl_conncache_add_conn(struct conncache *connc, - struct connectdata *conn) +static void cpool_run_conn_shutdown_handler(struct Curl_easy *data, + struct connectdata *conn) { - CURLcode result; - struct connectbundle *bundle; - struct connectbundle *new_bundle = NULL; - struct Curl_easy *data = conn->data; + if(!conn->bits.shutdown_handler) { + if(conn->dns_entry) + Curl_resolv_unlink(data, &conn->dns_entry); - bundle = Curl_conncache_find_bundle(conn, data->state.conn_cache); - if(!bundle) { - char *key; - int rc; + /* Cleanup NTLM connection-related data */ + Curl_http_auth_cleanup_ntlm(conn); - result = bundle_create(data, &new_bundle); - if(result) - return result; + /* Cleanup NEGOTIATE connection-related data */ + Curl_http_auth_cleanup_negotiate(conn); - key = hashkey(conn); - if(!key) { - bundle_destroy(new_bundle); - return CURLE_OUT_OF_MEMORY; - } + if(conn->handler && conn->handler->disconnect) { + /* This is set if protocol-specific cleanups should be made */ + DEBUGF(infof(data, "connection #%" FMT_OFF_T + ", shutdown protocol handler (aborted=%d)", + conn->connection_id, conn->bits.aborted)); - rc = conncache_add_bundle(data->state.conn_cache, key, new_bundle); - free(key); - if(!rc) { - bundle_destroy(new_bundle); - return CURLE_OUT_OF_MEMORY; + conn->handler->disconnect(data, conn, conn->bits.aborted); } - bundle = new_bundle; + + /* possible left-overs from the async name resolvers */ + Curl_resolver_cancel(data); + + conn->bits.shutdown_handler = TRUE; } +} + +static void cpool_run_conn_shutdown(struct Curl_easy *data, + struct connectdata *conn, + bool *done) +{ + CURLcode r1, r2; + bool done1, done2; - result = bundle_add_conn(bundle, conn); - if(result) { - if(new_bundle) - conncache_remove_bundle(data->state.conn_cache, new_bundle); - return result; + /* We expect to be attached when called */ + DEBUGASSERT(data->conn == conn); + + cpool_run_conn_shutdown_handler(data, conn); + + if(conn->bits.shutdown_filters) { + *done = TRUE; + return; } - conn->connection_id = connc->next_connection_id++; - connc->num_connections++; + if(!conn->connect_only && Curl_conn_is_connected(conn, FIRSTSOCKET)) + r1 = Curl_conn_shutdown(data, FIRSTSOCKET, &done1); + else { + r1 = CURLE_OK; + done1 = TRUE; + } - DEBUGF(infof(conn->data, "Added connection %ld. " - "The cache now contains %" CURL_FORMAT_CURL_OFF_TU " members\n", - conn->connection_id, (curl_off_t) connc->num_connections)); + if(!conn->connect_only && Curl_conn_is_connected(conn, SECONDARYSOCKET)) + r2 = Curl_conn_shutdown(data, SECONDARYSOCKET, &done2); + else { + r2 = CURLE_OK; + done2 = TRUE; + } - return CURLE_OK; + /* we are done when any failed or both report success */ + *done = (r1 || r2 || (done1 && done2)); + if(*done) + conn->bits.shutdown_filters = TRUE; } -void Curl_conncache_remove_conn(struct conncache *connc, - struct connectdata *conn) +static CURLcode cpool_add_pollfds(struct cpool *cpool, + struct curl_pollfds *cpfds) { - struct connectbundle *bundle = conn->bundle; + CURLcode result = CURLE_OK; - /* 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) { - bundle_remove_conn(bundle, conn); - if(bundle->num_connections == 0) { - conncache_remove_bundle(connc, bundle); + if(Curl_llist_head(&cpool->shutdowns)) { + struct Curl_llist_node *e; + struct easy_pollset ps; + struct connectdata *conn; + + for(e = Curl_llist_head(&cpool->shutdowns); e; + e = Curl_node_next(e)) { + conn = Curl_node_elem(e); + memset(&ps, 0, sizeof(ps)); + Curl_attach_connection(cpool->idata, conn); + Curl_conn_adjust_pollset(cpool->idata, &ps); + Curl_detach_connection(cpool->idata); + + result = Curl_pollfds_add_ps(cpfds, &ps); + if(result) { + Curl_pollfds_cleanup(cpfds); + goto out; + } } + } +out: + return result; +} + +CURLcode Curl_cpool_add_pollfds(struct cpool *cpool, + struct curl_pollfds *cpfds) +{ + CURLcode result; + CPOOL_LOCK(cpool); + result = cpool_add_pollfds(cpool, cpfds); + CPOOL_UNLOCK(cpool); + return result; +} - if(connc) { - connc->num_connections--; +CURLcode Curl_cpool_add_waitfds(struct cpool *cpool, + struct curl_waitfds *cwfds) +{ + CURLcode result = CURLE_OK; + + CPOOL_LOCK(cpool); + if(Curl_llist_head(&cpool->shutdowns)) { + struct Curl_llist_node *e; + struct easy_pollset ps; + struct connectdata *conn; - DEBUGF(infof(conn->data, "The cache now contains %" - CURL_FORMAT_CURL_OFF_TU " members\n", - (curl_off_t) connc->num_connections)); + for(e = Curl_llist_head(&cpool->shutdowns); e; + e = Curl_node_next(e)) { + conn = Curl_node_elem(e); + memset(&ps, 0, sizeof(ps)); + Curl_attach_connection(cpool->idata, conn); + Curl_conn_adjust_pollset(cpool->idata, &ps); + Curl_detach_connection(cpool->idata); + + result = Curl_waitfds_add_ps(cwfds, &ps); + if(result) + goto out; } } +out: + CPOOL_UNLOCK(cpool); + return result; } -/* 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, +static void cpool_perform(struct cpool *cpool) +{ + struct Curl_easy *data = cpool->idata; + struct Curl_llist_node *e = Curl_llist_head(&cpool->shutdowns); + struct Curl_llist_node *enext; + struct connectdata *conn; + struct curltime *nowp = NULL; + struct curltime now; + timediff_t next_from_now_ms = 0, ms; + bool done; + + if(!e) + return; - Return 0 from func() to continue the loop, return 1 to abort it. + DEBUGASSERT(data); + DEBUGF(infof(data, "[CCACHE] perform, %zu connections being shutdown", + Curl_llist_count(&cpool->shutdowns))); + while(e) { + enext = Curl_node_next(e); + conn = Curl_node_elem(e); + Curl_attach_connection(data, conn); + cpool_run_conn_shutdown(data, conn, &done); + DEBUGF(infof(data, "[CCACHE] shutdown #%" FMT_OFF_T ", done=%d", + conn->connection_id, done)); + Curl_detach_connection(data); + if(done) { + Curl_node_remove(e); + cpool_close_and_destroy(cpool, conn, NULL, FALSE); + } + else { + /* Not done, when does this connection time out? */ + if(!nowp) { + now = Curl_now(); + nowp = &now; + } + ms = Curl_conn_shutdown_timeleft(conn, nowp); + if(ms && ms < next_from_now_ms) + next_from_now_ms = ms; + } + e = enext; + } + + if(next_from_now_ms) + Curl_expire(data, next_from_now_ms, EXPIRE_RUN_NOW); +} + +void Curl_cpool_multi_perform(struct Curl_multi *multi) +{ + CPOOL_LOCK(&multi->cpool); + cpool_perform(&multi->cpool); + CPOOL_UNLOCK(&multi->cpool); +} + + +/* + * Close and destroy the connection. Run the shutdown sequence once, + * of so requested. */ -void Curl_conncache_foreach(struct conncache *connc, - void *param, - int (*func)(struct connectdata *conn, void *param)) +static void cpool_close_and_destroy(struct cpool *cpool, + struct connectdata *conn, + struct Curl_easy *data, + bool do_shutdown) { - struct curl_hash_iterator iter; - struct curl_llist_element *curr; - struct curl_hash_element *he; + bool done; + + /* there must be a connection to close */ + DEBUGASSERT(conn); + /* it must be removed from the connection pool */ + DEBUGASSERT(!conn->bits.in_cpool); + /* there must be an associated transfer */ + DEBUGASSERT(data || cpool); + if(!data) + data = cpool->idata; + + /* the transfer must be detached from the connection */ + DEBUGASSERT(data && !data->conn); + + Curl_attach_connection(data, conn); + + cpool_run_conn_shutdown_handler(data, conn); + if(do_shutdown) { + /* Make a last attempt to shutdown handlers and filters, if + * not done so already. */ + cpool_run_conn_shutdown(data, conn, &done); + } - if(!connc) - return; + if(cpool) + DEBUGF(infof(data, "[CCACHE] closing #%" FMT_OFF_T, + conn->connection_id)); + else + DEBUGF(infof(data, "closing connection #%" FMT_OFF_T, + conn->connection_id)); + Curl_conn_close(data, SECONDARYSOCKET); + Curl_conn_close(data, FIRSTSOCKET); + Curl_detach_connection(data); - Curl_hash_start_iterate(&connc->hash, &iter); + Curl_conn_free(data, conn); +} - he = Curl_hash_next_element(&iter); - while(he) { - struct connectbundle *bundle; - bundle = he->ptr; - he = Curl_hash_next_element(&iter); +static CURLMcode cpool_update_shutdown_ev(struct Curl_multi *multi, + struct Curl_easy *data, + struct connectdata *conn) +{ + struct easy_pollset ps; + CURLMcode mresult; - 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; + DEBUGASSERT(data); + DEBUGASSERT(multi); + DEBUGASSERT(multi->socket_cb); + + memset(&ps, 0, sizeof(ps)); + Curl_attach_connection(data, conn); + Curl_conn_adjust_pollset(data, &ps); + Curl_detach_connection(data); - if(1 == func(conn, param)) - return; + mresult = Curl_multi_pollset_ev(multi, data, &ps, &conn->shutdown_poll); + + if(!mresult) /* Remember for next time */ + memcpy(&conn->shutdown_poll, &ps, sizeof(ps)); + return mresult; +} + +void Curl_cpool_multi_socket(struct Curl_multi *multi, + curl_socket_t s, int ev_bitmask) +{ + struct cpool *cpool = &multi->cpool; + struct Curl_easy *data = cpool->idata; + struct Curl_llist_node *e; + struct connectdata *conn; + bool done; + + (void)ev_bitmask; + DEBUGASSERT(multi->socket_cb); + CPOOL_LOCK(cpool); + e = Curl_llist_head(&cpool->shutdowns); + while(e) { + conn = Curl_node_elem(e); + if(s == conn->sock[FIRSTSOCKET] || s == conn->sock[SECONDARYSOCKET]) { + Curl_attach_connection(data, conn); + cpool_run_conn_shutdown(data, conn, &done); + DEBUGF(infof(data, "[CCACHE] shutdown #%" FMT_OFF_T ", done=%d", + conn->connection_id, done)); + Curl_detach_connection(data); + if(done || cpool_update_shutdown_ev(multi, data, conn)) { + Curl_node_remove(e); + cpool_close_and_destroy(cpool, conn, NULL, FALSE); + } + break; } + e = Curl_node_next(e); } + CPOOL_UNLOCK(cpool); } -/* Return the first connection found in the cache. Used when closing all - connections */ -struct connectdata * -Curl_conncache_find_first_connection(struct conncache *connc) +#define NUM_POLLS_ON_STACK 10 + +static CURLcode cpool_shutdown_wait(struct cpool *cpool, int timeout_ms) { - struct curl_hash_iterator iter; - struct curl_hash_element *he; - struct connectbundle *bundle; + struct pollfd a_few_on_stack[NUM_POLLS_ON_STACK]; + struct curl_pollfds cpfds; + CURLcode result; - Curl_hash_start_iterate(&connc->hash, &iter); + Curl_pollfds_init(&cpfds, a_few_on_stack, NUM_POLLS_ON_STACK); - he = Curl_hash_next_element(&iter); - while(he) { - struct curl_llist_element *curr; - bundle = he->ptr; + result = cpool_add_pollfds(cpool, &cpfds); + if(result) + goto out; + + Curl_poll(cpfds.pfds, cpfds.n, CURLMIN(timeout_ms, 1000)); + +out: + Curl_pollfds_cleanup(&cpfds); + return result; +} + +static void cpool_shutdown_all(struct cpool *cpool, + struct Curl_easy *data, int timeout_ms) +{ + struct connectdata *conn; + struct curltime started = Curl_now(); + + if(!data) + return; + (void)data; + + DEBUGF(infof(data, "cpool shutdown all")); + + /* Move all connections into the shutdown queue */ + for(conn = cpool_get_live_conn(cpool); conn; + conn = cpool_get_live_conn(cpool)) { + /* Move conn from live set to shutdown or destroy right away */ + DEBUGF(infof(data, "moving connection #%" FMT_OFF_T + " to shutdown queue", conn->connection_id)); + cpool_remove_conn(cpool, conn); + cpool_discard_conn(cpool, data, conn, FALSE); + } + + while(Curl_llist_head(&cpool->shutdowns)) { + timediff_t timespent; + int remain_ms; - curr = bundle->conn_list->head; - if(curr) { - return curr->ptr; + cpool_perform(cpool); + + if(!Curl_llist_head(&cpool->shutdowns)) { + DEBUGF(infof(data, "cpool shutdown ok")); + break; } - he = Curl_hash_next_element(&iter); + /* wait for activity, timeout or "nothing" */ + timespent = Curl_timediff(Curl_now(), started); + if(timespent >= (timediff_t)timeout_ms) { + DEBUGF(infof(data, "cpool shutdown %s", + (timeout_ms > 0) ? "timeout" : "best effort done")); + break; + } + + remain_ms = timeout_ms - (int)timespent; + if(cpool_shutdown_wait(cpool, remain_ms)) { + DEBUGF(infof(data, "cpool shutdown all, abort")); + break; + } } - return NULL; + /* Due to errors/timeout, we might come here without being done. */ + cpool_shutdown_discard_all(cpool); } +struct cpool_reaper_ctx { + struct curltime now; +}; + +static int cpool_reap_dead_cb(struct Curl_easy *data, + struct connectdata *conn, void *param) +{ + struct cpool_reaper_ctx *rctx = param; + if(Curl_conn_seems_dead(conn, data, &rctx->now)) { + /* stop the iteration here, pass back the connection that was pruned */ + Curl_cpool_disconnect(data, conn, FALSE); + return 1; + } + return 0; /* continue iteration */ +} + +/* + * This function scans the data's connection pool for half-open/dead + * connections, closes and removes them. + * The cleanup is done at most once per second. + * + * When called, this transfer has no connection attached. + */ +void Curl_cpool_prune_dead(struct Curl_easy *data) +{ + struct cpool *cpool = cpool_get_instance(data); + struct cpool_reaper_ctx rctx; + timediff_t elapsed; + + if(!cpool) + return; + + rctx.now = Curl_now(); + CPOOL_LOCK(cpool); + elapsed = Curl_timediff(rctx.now, cpool->last_cleanup); + + if(elapsed >= 1000L) { + while(cpool_foreach(data, cpool, &rctx, cpool_reap_dead_cb)) + ; + cpool->last_cleanup = rctx.now; + } + CPOOL_UNLOCK(cpool); +} + +static int conn_upkeep(struct Curl_easy *data, + struct connectdata *conn, + void *param) +{ + struct curltime *now = param; + /* TODO, shall we reap connections that return an error here? */ + Curl_conn_upkeep(data, conn, now); + return 0; /* continue iteration */ +} + +CURLcode Curl_cpool_upkeep(void *data) +{ + struct cpool *cpool = cpool_get_instance(data); + struct curltime now = Curl_now(); + + if(!cpool) + return CURLE_OK; + + CPOOL_LOCK(cpool); + cpool_foreach(data, cpool, &now, conn_upkeep); + CPOOL_UNLOCK(cpool); + return CURLE_OK; +} + +struct cpool_find_ctx { + curl_off_t id; + struct connectdata *conn; +}; + +static int cpool_find_conn(struct Curl_easy *data, + struct connectdata *conn, void *param) +{ + struct cpool_find_ctx *fctx = param; + (void)data; + if(conn->connection_id == fctx->id) { + fctx->conn = conn; + return 1; + } + return 0; +} + +struct connectdata *Curl_cpool_get_conn(struct Curl_easy *data, + curl_off_t conn_id) +{ + struct cpool *cpool = cpool_get_instance(data); + struct cpool_find_ctx fctx; + + if(!cpool) + return NULL; + fctx.id = conn_id; + fctx.conn = NULL; + CPOOL_LOCK(cpool); + cpool_foreach(cpool->idata, cpool, &fctx, cpool_find_conn); + CPOOL_UNLOCK(cpool); + return fctx.conn; +} + +struct cpool_do_conn_ctx { + curl_off_t id; + Curl_cpool_conn_do_cb *cb; + void *cbdata; +}; + +static int cpool_do_conn(struct Curl_easy *data, + struct connectdata *conn, void *param) +{ + struct cpool_do_conn_ctx *dctx = param; + (void)data; + if(conn->connection_id == dctx->id) { + dctx->cb(conn, data, dctx->cbdata); + return 1; + } + return 0; +} + +void Curl_cpool_do_by_id(struct Curl_easy *data, curl_off_t conn_id, + Curl_cpool_conn_do_cb *cb, void *cbdata) +{ + struct cpool *cpool = cpool_get_instance(data); + struct cpool_do_conn_ctx dctx; + + if(!cpool) + return; + dctx.id = conn_id; + dctx.cb = cb; + dctx.cbdata = cbdata; + CPOOL_LOCK(cpool); + cpool_foreach(data, cpool, &dctx, cpool_do_conn); + CPOOL_UNLOCK(cpool); +} + +void Curl_cpool_do_locked(struct Curl_easy *data, + struct connectdata *conn, + Curl_cpool_conn_do_cb *cb, void *cbdata) +{ + struct cpool *cpool = cpool_get_instance(data); + if(cpool) { + CPOOL_LOCK(cpool); + cb(conn, data, cbdata); + CPOOL_UNLOCK(cpool); + } + else + cb(conn, data, cbdata); +} #if 0 -/* Useful for debugging the connection cache */ -void Curl_conncache_print(struct conncache *connc) +/* Useful for debugging the connection pool */ +void Curl_cpool_print(struct cpool *cpool) { - struct curl_hash_iterator iter; - struct curl_llist_element *curr; - struct curl_hash_element *he; + struct Curl_hash_iterator iter; + struct Curl_llist_node *curr; + struct Curl_hash_element *he; - if(!connc) + if(!cpool) return; fprintf(stderr, "=Bundle cache=\n"); - Curl_hash_start_iterate(connc->hash, &iter); + Curl_hash_start_iterate(cpool->dest2bundle, &iter); he = Curl_hash_next_element(&iter); while(he) { - struct connectbundle *bundle; + struct cpool_bundle *bundle; struct connectdata *conn; bundle = he->ptr; fprintf(stderr, "%s -", he->key); - curr = bundle->conn_list->head; + curr = Curl_llist_head(bundle->conns); while(curr) { - conn = curr->ptr; + conn = Curl_node_elem(curr); - fprintf(stderr, " [%p %d]", (void *)conn, conn->inuse); - curr = curr->next; + fprintf(stderr, " [%p %d]", (void *)conn, conn->refcount); + curr = Curl_node_next(curr); } fprintf(stderr, "\n"); diff --git a/contrib/curl/lib/conncache.h b/contrib/curl/lib/conncache.h index b1dadf9906..a379ee747d 100644 --- a/contrib/curl/lib/conncache.h +++ b/contrib/curl/lib/conncache.h @@ -7,12 +7,12 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 2015, Daniel Stenberg, , et al. - * Copyright (C) 2012 - 2014, Linus Nielsen Feltzing, + * Copyright (C) Daniel Stenberg, , et al. + * Copyright (C) Linus Nielsen Feltzing, * * This software is licensed 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. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -21,48 +21,181 @@ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY * KIND, either express or implied. * + * SPDX-License-Identifier: curl + * ***************************************************************************/ -struct conncache { - struct curl_hash hash; - size_t num_connections; - long next_connection_id; - struct timeval last_cleanup; -}; +#include +#include "timeval.h" + +struct connectdata; +struct Curl_easy; +struct curl_pollfds; +struct curl_waitfds; +struct Curl_multi; +struct Curl_share; -#define BUNDLE_NO_MULTIUSE -1 -#define BUNDLE_UNKNOWN 0 /* initial value */ -#define BUNDLE_PIPELINING 1 -#define BUNDLE_MULTIPLEX 2 +/** + * Callback invoked when disconnecting connections. + * @param data transfer last handling the connection, not attached + * @param conn the connection to discard + * @param aborted if the connection is being aborted + * @return if the connection is being aborted, e.g. should NOT perform + * a shutdown and just close. + **/ +typedef bool Curl_cpool_disconnect_cb(struct Curl_easy *data, + struct connectdata *conn, + bool aborted); -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 */ +struct cpool { + /* the pooled connections, bundled per destination */ + struct Curl_hash dest2bundle; + size_t num_conn; + curl_off_t next_connection_id; + curl_off_t next_easy_id; + struct curltime last_cleanup; + struct Curl_llist shutdowns; /* The connections being shut down */ + struct Curl_easy *idata; /* internal handle used for discard */ + struct Curl_multi *multi; /* != NULL iff pool belongs to multi */ + struct Curl_share *share; /* != NULL iff pool belongs to share */ + Curl_cpool_disconnect_cb *disconnect_cb; + BIT(locked); }; -int Curl_conncache_init(struct conncache *, int size); +/* Init the pool, pass multi only if pool is owned by it. + * returns 1 on error, 0 is fine. + */ +int Curl_cpool_init(struct cpool *cpool, + Curl_cpool_disconnect_cb *disconnect_cb, + struct Curl_multi *multi, + struct Curl_share *share, + size_t size); + +/* Destroy all connections and free all members */ +void Curl_cpool_destroy(struct cpool *connc); + +/* Init the transfer to be used within its connection pool. + * Assigns `data->id`. */ +void Curl_cpool_xfer_init(struct Curl_easy *data); + +/** + * Get the connection with the given id from the transfer's pool. + */ +struct connectdata *Curl_cpool_get_conn(struct Curl_easy *data, + curl_off_t conn_id); + +CURLcode Curl_cpool_add_conn(struct Curl_easy *data, + struct connectdata *conn) WARN_UNUSED_RESULT; + +/** + * Return if the pool has reached its configured limits for adding + * the given connection. Will try to discard the oldest, idle + * connections to make space. + */ +#define CPOOL_LIMIT_OK 0 +#define CPOOL_LIMIT_DEST 1 +#define CPOOL_LIMIT_TOTAL 2 +int Curl_cpool_check_limits(struct Curl_easy *data, + struct connectdata *conn); + +/* Return of conn is suitable. If so, stops iteration. */ +typedef bool Curl_cpool_conn_match_cb(struct connectdata *conn, + void *userdata); + +/* Act on the result of the find, may override it. */ +typedef bool Curl_cpool_done_match_cb(bool result, void *userdata); + +/** + * Find a connection in the pool matching `destination`. + * All callbacks are invoked while the pool's lock is held. + * @param data current transfer + * @param destination match agaonst `conn->destination` in pool + * @param dest_len destination length, including terminating NUL + * @param conn_cb must be present, called for each connection in the + * bundle until it returns TRUE + * @param result_cb if not NULL, is called at the end with the result + * of the `conn_cb` or FALSE if never called. + * @return combined result of last conn_db and result_cb or FALSE if no + connections were present. + */ +bool Curl_cpool_find(struct Curl_easy *data, + const char *destination, size_t dest_len, + Curl_cpool_conn_match_cb *conn_cb, + Curl_cpool_done_match_cb *done_cb, + void *userdata); + +/* + * A connection (already in the pool) is now idle. Do any + * cleanups in regard to the pool's limits. + * + * Return TRUE if idle connection kept in pool, FALSE if closed. + */ +bool Curl_cpool_conn_now_idle(struct Curl_easy *data, + struct connectdata *conn); + +/** + * Remove the connection from the pool and tear it down. + * If `aborted` is FALSE, the connection will be shut down first + * before closing and destroying it. + * If the shutdown is not immediately complete, the connection + * will be placed into the pool's shutdown queue. + */ +void Curl_cpool_disconnect(struct Curl_easy *data, + struct connectdata *conn, + bool aborted); + +/** + * This function scans the data's connection pool for half-open/dead + * connections, closes and removes them. + * The cleanup is done at most once per second. + * + * When called, this transfer has no connection attached. + */ +void Curl_cpool_prune_dead(struct Curl_easy *data); + +/** + * Perform upkeep actions on connections in the transfer's pool. + */ +CURLcode Curl_cpool_upkeep(void *data); -void Curl_conncache_destroy(struct conncache *connc); +typedef void Curl_cpool_conn_do_cb(struct connectdata *conn, + struct Curl_easy *data, + void *cbdata); -/* return the correct bundle, to a host or a proxy */ -struct connectbundle *Curl_conncache_find_bundle(struct connectdata *conn, - struct conncache *connc); +/** + * Invoke the callback on the pool's connection with the + * given connection id (if it exists). + */ +void Curl_cpool_do_by_id(struct Curl_easy *data, + curl_off_t conn_id, + Curl_cpool_conn_do_cb *cb, void *cbdata); -CURLcode Curl_conncache_add_conn(struct conncache *connc, - struct connectdata *conn); +/** + * Invoked the callback for the given data + connection under the + * connection pool's lock. + * The callback is always invoked, even if the transfer has no connection + * pool associated. + */ +void Curl_cpool_do_locked(struct Curl_easy *data, + struct connectdata *conn, + Curl_cpool_conn_do_cb *cb, void *cbdata); -void Curl_conncache_remove_conn(struct conncache *connc, - struct connectdata *conn); +/** + * Add sockets and POLLIN/OUT flags for connections handled by the pool. + */ +CURLcode Curl_cpool_add_pollfds(struct cpool *connc, + struct curl_pollfds *cpfds); +CURLcode Curl_cpool_add_waitfds(struct cpool *connc, + struct curl_waitfds *cwfds); -void Curl_conncache_foreach(struct conncache *connc, - void *param, - int (*func)(struct connectdata *conn, - void *param)); +/** + * Perform maintenance on connections in the pool. Specifically, + * progress the shutdown of connections in the queue. + */ +void Curl_cpool_multi_perform(struct Curl_multi *multi); -struct connectdata * -Curl_conncache_find_first_connection(struct conncache *connc); +void Curl_cpool_multi_socket(struct Curl_multi *multi, + curl_socket_t s, int ev_bitmask); -void Curl_conncache_print(struct conncache *connc); #endif /* HEADER_CURL_CONNCACHE_H */ diff --git a/contrib/curl/lib/connect.c b/contrib/curl/lib/connect.c index 524d885eb6..debaf355f2 100644 --- a/contrib/curl/lib/connect.c +++ b/contrib/curl/lib/connect.c @@ -5,11 +5,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2016, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -18,6 +18,8 @@ * 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" @@ -28,8 +30,10 @@ #ifdef HAVE_SYS_UN_H #include /* for sockaddr_un */ #endif -#ifdef HAVE_NETINET_TCP_H -#include /* for TCP_NODELAY */ +#ifdef HAVE_LINUX_TCP_H +#include +#elif defined(HAVE_NETINET_TCP_H) +#include #endif #ifdef HAVE_SYS_IOCTL_H #include @@ -44,13 +48,6 @@ #include #endif -#if (defined(HAVE_IOCTL_FIONBIO) && defined(NETWARE)) -#include -#endif -#ifdef NETWARE -#undef in_addr_t -#define in_addr_t unsigned long -#endif #ifdef __VMS #include #include @@ -60,564 +57,192 @@ #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 Curl_ssl_check_cxn() */ +#include "vtls/vtls.h" /* for vtsl cfilters */ #include "progress.h" #include "warnless.h" #include "conncache.h" #include "multihandle.h" -#include "system_win32.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" -#ifdef __SYMBIAN32__ -/* This isn't actually supported under Symbian OS */ -#undef SO_NOSIGPIPE -#endif - -static bool verifyconnect(curl_socket_t sockfd, int *error); - -#if defined(__DragonFly__) || defined(HAVE_WINSOCK_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\n", 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\n", - (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\n", 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\n", sockfd); - } -#endif -#ifdef 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\n", sockfd); - } +#ifndef ARRAYSIZE +#define ARRAYSIZE(A) (sizeof(A)/sizeof((A)[0])) #endif -#endif - } -} - -static CURLcode -singleipconnect(struct connectdata *conn, - const Curl_addrinfo *ai, /* start connecting to this */ - curl_socket_t *sock); /* * Curl_timeleft() returns the amount of milliseconds left allowed for the - * transfer/connection. If the value is negative, the timeout time has already + * transfer/connection. If the value is 0, there is no timeout (ie there is + * infinite time left). If the value is negative, the timeout time has already * elapsed. - * - * The start time is stored in progress.t_startsingle - as set with - * Curl_pgrsTime(..., TIMER_STARTSINGLE); - * - * 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! - * + * @param data the transfer to check on + * @param nowp timestamp to use for calculation, NULL to use Curl_now() + * @param duringconnect TRUE iff connect timeout is also taken into account. * @unittest: 1303 */ -time_t Curl_timeleft(struct Curl_easy *data, - struct timeval *nowp, - bool duringconnect) +timediff_t Curl_timeleft(struct Curl_easy *data, + struct curltime *nowp, + bool duringconnect) { - int timeout_set = 0; - time_t timeout_ms = duringconnect?DEFAULT_CONNECT_TIMEOUT:0; - struct timeval now; - - /* if a timeout is set, use the most restrictive one */ - - if(data->set.timeout > 0) - timeout_set |= 1; - if(duringconnect && (data->set.connecttimeout > 0)) - timeout_set |= 2; - - switch(timeout_set) { - case 1: - timeout_ms = data->set.timeout; - break; - case 2: - timeout_ms = data->set.connecttimeout; - break; - case 3: - if(data->set.timeout < data->set.connecttimeout) - timeout_ms = data->set.timeout; - else - timeout_ms = data->set.connecttimeout; - break; - default: - /* use the default */ - if(!duringconnect) - /* if we're not during connect, there's no default timeout so if we're - at zero we better just return zero and not make it a negative number - by the math below */ - return 0; - break; - } + timediff_t timeleft_ms = 0; + timediff_t ctimeleft_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 && !duringconnect) + return 0; /* no timeout in place or checked, return "no limit" */ if(!nowp) { - now = Curl_tvnow(); + now = Curl_now(); nowp = &now; } - /* subtract elapsed time */ - if(duringconnect) - /* since this most recent connect started */ - timeout_ms -= Curl_tvdiff(*nowp, data->progress.t_startsingle); - else - /* since the entire operation started */ - timeout_ms -= Curl_tvdiff(*nowp, data->progress.t_startop); - if(!timeout_ms) - /* avoid returning 0 as that means no timeout! */ - return -1; + if(data->set.timeout > 0) { + timeleft_ms = data->set.timeout - + Curl_timediff(*nowp, data->progress.t_startop); + if(!timeleft_ms) + timeleft_ms = -1; /* 0 is "no limit", fake 1 ms expiry */ + if(!duringconnect) + return timeleft_ms; /* no connect check, this is it */ + } - return timeout_ms; + if(duringconnect) { + timediff_t ctimeout_ms = (data->set.connecttimeout > 0) ? + data->set.connecttimeout : DEFAULT_CONNECT_TIMEOUT; + ctimeleft_ms = ctimeout_ms - + Curl_timediff(*nowp, data->progress.t_startsingle); + if(!ctimeleft_ms) + ctimeleft_ms = -1; /* 0 is "no limit", fake 1 ms expiry */ + if(!timeleft_ms) + return ctimeleft_ms; /* no general timeout, this is it */ + } + /* return minimal time left or max amount already expired */ + return (ctimeleft_ms < timeleft_ms) ? ctimeleft_ms : timeleft_ms; } -static CURLcode bindlocal(struct connectdata *conn, - curl_socket_t sockfd, int af, unsigned int scope) +void Curl_shutdown_start(struct Curl_easy *data, int sockindex, + struct curltime *nowp) { - struct Curl_easy *data = conn->data; - - 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; - - /************************************************************* - * 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) { - switch(Curl_if2ip(af, scope, conn->scope_id, 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\n", - dev, myhost, af); - done = 1; - -#ifdef SO_BINDTODEVICE - /* I am not sure any other OSs than Linux that provide this feature, - * and at the least I cannot test. --Ben - * - * This feature allows one to tightly bind 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. - */ - if(setsockopt(sockfd, SOL_SOCKET, SO_BINDTODEVICE, - dev, (curl_socklen_t)strlen(dev)+1) != 0) { - error = SOCKERRNO; - infof(data, "SO_BINDTODEVICE %s failed with errno %d: %s;" - " will do regular bind\n", - dev, error, Curl_strerror(conn, error)); - /* This is typically "errno 1, error: Operation not permitted" if - you're not running as root or another suitable privileged - user */ - } -#endif - 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. - */ - long 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(conn, dev, 0, &h); - if(rc == CURLRESOLV_PENDING) - (void)Curl_resolver_wait_resolv(conn, &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\n", - dev, af, myhost, h->addr->ai_family); - Curl_resolv_unlock(data, h); - 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 */ - si6->sin6_scope_id = atoi(scope_ptr); -#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) { - 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); - } - } - - 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) { - data->state.os_errno = error = SOCKERRNO; - failf(data, "getsockname() failed with errno %d: %s", - error, Curl_strerror(conn, error)); - return CURLE_INTERFACE_FAILED; - } - infof(data, "Local port: %hu\n", port); - conn->bits.bound = TRUE; - return CURLE_OK; - } + struct curltime now; - if(--portnum > 0) { - infof(data, "Bind to local port %hu failed, trying next\n", port); - port++; /* try next port */ - /* We re-use/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; + DEBUGASSERT(data->conn); + if(!nowp) { + now = Curl_now(); + nowp = &now; } - - data->state.os_errno = error = SOCKERRNO; - failf(data, "bind failed with errno %d: %s", - error, Curl_strerror(conn, error)); - - return CURLE_INTERFACE_FAILED; + data->conn->shutdown.start[sockindex] = *nowp; + data->conn->shutdown.timeout_ms = (data->set.shutdowntimeout > 0) ? + data->set.shutdowntimeout : DEFAULT_SHUTDOWN_TIMEOUT_MS; } -/* - * verifyconnect() returns TRUE if the connect really has happened. - */ -static bool verifyconnect(curl_socket_t sockfd, int *error) +timediff_t Curl_shutdown_timeleft(struct connectdata *conn, int sockindex, + struct curltime *nowp) { - 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 + struct curltime now; + timediff_t left_ms; -#endif + if(!conn->shutdown.start[sockindex].tv_sec || !conn->shutdown.timeout_ms) + return 0; /* not started or no limits */ - 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 -#ifdef __minix - /* Minix 3.1.x doesn't support getsockopt on UDP sockets */ - if(EBADIOCTL == err) { - SET_SOCKERRNO(0); - err = 0; + if(!nowp) { + now = Curl_now(); + nowp = &now; } -#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; + left_ms = conn->shutdown.timeout_ms - + Curl_timediff(*nowp, conn->shutdown.start[sockindex]); + return left_ms ? left_ms : -1; } -/* Used within the multi interface. Try next IP address, return TRUE if no - more address exists or error */ -static CURLcode trynextip(struct connectdata *conn, - int sockindex, - int tempindex) +timediff_t Curl_conn_shutdown_timeleft(struct connectdata *conn, + struct curltime *nowp) { - const int other = tempindex ^ 1; - CURLcode result = CURLE_COULDNT_CONNECT; + timediff_t left_ms = 0, ms; + struct curltime now; + int i; - /* First clean up after the failed socket. - Don't close it 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. */ - curl_socket_t fd_to_close = conn->tempsock[tempindex]; - conn->tempsock[tempindex] = CURL_SOCKET_BAD; - - if(sockindex == FIRSTSOCKET) { - Curl_addrinfo *ai = NULL; - int family = AF_UNSPEC; - - if(conn->tempaddr[tempindex]) { - /* find next address in the same protocol family */ - family = conn->tempaddr[tempindex]->ai_family; - ai = conn->tempaddr[tempindex]->ai_next; - } -#ifdef ENABLE_IPV6 - else if(conn->tempaddr[0]) { - /* happy eyeballs - try the other protocol family */ - int firstfamily = conn->tempaddr[0]->ai_family; - family = (firstfamily == AF_INET) ? AF_INET6 : AF_INET; - ai = conn->tempaddr[0]->ai_next; + for(i = 0; conn->shutdown.timeout_ms && (i < 2); ++i) { + if(!conn->shutdown.start[i].tv_sec) + continue; + if(!nowp) { + now = Curl_now(); + nowp = &now; } -#endif + ms = Curl_shutdown_timeleft(conn, i, nowp); + if(ms && (!left_ms || ms < left_ms)) + left_ms = ms; + } + return left_ms; +} - while(ai) { - if(conn->tempaddr[other]) { - /* we can safely skip addresses of the other protocol family */ - while(ai && ai->ai_family != family) - ai = ai->ai_next; - } +void Curl_shutdown_clear(struct Curl_easy *data, int sockindex) +{ + struct curltime *pt = &data->conn->shutdown.start[sockindex]; + memset(pt, 0, sizeof(*pt)); +} - if(ai) { - result = singleipconnect(conn, ai, &conn->tempsock[tempindex]); - if(result == CURLE_COULDNT_CONNECT) { - ai = ai->ai_next; - continue; - } +bool Curl_shutdown_started(struct Curl_easy *data, int sockindex) +{ + struct curltime *pt = &data->conn->shutdown.start[sockindex]; + return (pt->tv_sec > 0) || (pt->tv_usec > 0); +} - conn->tempaddr[tempindex] = ai; - } - break; - } +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; } - - if(fd_to_close != CURL_SOCKET_BAD) - Curl_closesocket(conn, fd_to_close); - - return result; + return NULL; } -/* Copies connection info into the session handle to make it available - when the session handle is no longer associated with a connection. */ -void Curl_persistconninfo(struct connectdata *conn) +static const struct Curl_addrinfo * +addr_next_match(const struct Curl_addrinfo *addr, int family) { - memcpy(conn->data->info.conn_primary_ip, conn->primary_ip, MAX_IPADR_LEN); - memcpy(conn->data->info.conn_local_ip, conn->local_ip, MAX_IPADR_LEN); - conn->data->info.conn_scheme = conn->handler->scheme; - conn->data->info.conn_protocol = conn->handler->protocol; - conn->data->info.conn_primary_port = conn->primary_port; - conn->data->info.conn_local_port = conn->local_port; + 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 */ -static bool getaddressinfo(struct sockaddr *sa, char *addr, - long *port) +/* 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) { - unsigned short us_port; struct sockaddr_in *si = NULL; -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 struct sockaddr_in6 *si6 = NULL; #endif -#if defined(HAVE_SYS_UN_H) && defined(AF_UNIX) +#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) { @@ -625,26 +250,30 @@ static bool getaddressinfo(struct sockaddr *sa, char *addr, si = (struct sockaddr_in *)(void *) sa; if(Curl_inet_ntop(sa->sa_family, &si->sin_addr, addr, MAX_IPADR_LEN)) { - us_port = ntohs(si->sin_port); + unsigned short us_port = ntohs(si->sin_port); *port = us_port; return TRUE; } break; -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 case AF_INET6: si6 = (struct sockaddr_in6 *)(void *) sa; if(Curl_inet_ntop(sa->sa_family, &si6->sin6_addr, addr, MAX_IPADR_LEN)) { - us_port = ntohs(si6->sin6_port); + unsigned short us_port = ntohs(si6->sin6_port); *port = us_port; return TRUE; } break; #endif -#if defined(HAVE_SYS_UN_H) && defined(AF_UNIX) +#if (defined(HAVE_SYS_UN_H) || defined(WIN32_SOCKADDR_UN)) && defined(AF_UNIX) case AF_UNIX: - su = (struct sockaddr_un*)sa; - snprintf(addr, MAX_IPADR_LEN, "%s", su->sun_path); + 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 @@ -654,510 +283,495 @@ static bool getaddressinfo(struct sockaddr *sa, char *addr, addr[0] = '\0'; *port = 0; - + errno = EAFNOSUPPORT; return FALSE; } -/* retrieves the start/end point information of a socket of an established - connection */ -void Curl_updateconninfo(struct connectdata *conn, curl_socket_t sockfd) +/* + * 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) { - curl_socklen_t len; - struct Curl_sockaddr_storage ssrem; - struct Curl_sockaddr_storage ssloc; - struct Curl_easy *data = conn->data; - - if(conn->socktype == SOCK_DGRAM) - /* there's no connection! */ - return; - - if(!conn->bits.reuse && !conn->bits.tcp_fastopen) { - int error; - - len = sizeof(struct Curl_sockaddr_storage); - if(getpeername(sockfd, (struct sockaddr*) &ssrem, &len)) { - error = SOCKERRNO; - failf(data, "getpeername() failed with errno %d: %s", - error, Curl_strerror(conn, error)); - return; - } - - len = sizeof(struct Curl_sockaddr_storage); - memset(&ssloc, 0, sizeof(ssloc)); - if(getsockname(sockfd, (struct sockaddr*) &ssloc, &len)) { - error = SOCKERRNO; - failf(data, "getsockname() failed with errno %d: %s", - error, Curl_strerror(conn, error)); - return; - } + DEBUGASSERT(data); - if(!getaddressinfo((struct sockaddr*)&ssrem, - conn->primary_ip, &conn->primary_port)) { - error = ERRNO; - failf(data, "ssrem inet_ntop() failed with errno %d: %s", - error, Curl_strerror(conn, error)); - return; - } - memcpy(conn->ip_addr_str, conn->primary_ip, MAX_IPADR_LEN); + /* 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) { + struct connectdata *conn; - if(!getaddressinfo((struct sockaddr*)&ssloc, - conn->local_ip, &conn->local_port)) { - error = ERRNO; - failf(data, "ssloc inet_ntop() failed with errno %d: %s", - error, Curl_strerror(conn, error)); - return; + conn = Curl_cpool_get_conn(data, data->state.lastconnect_id); + if(!conn) { + data->state.lastconnect_id = -1; + return CURL_SOCKET_BAD; } + if(connp) + /* only store this if the caller cares for it */ + *connp = conn; + return conn->sock[FIRSTSOCKET]; } - - /* persist connection info in session handle */ - Curl_persistconninfo(conn); + return CURL_SOCKET_BAD; } /* - * Curl_is_connected() checks if the socket has connected. + * Curl_conncontrol() marks streams or connection for closure. */ - -CURLcode Curl_is_connected(struct connectdata *conn, - int sockindex, - bool *connected) +void Curl_conncontrol(struct connectdata *conn, + int ctrl /* see defines in header */ +#if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS) + , const char *reason +#endif + ) { - struct Curl_easy *data = conn->data; - CURLcode result = CURLE_OK; - time_t allow; - int error = 0; - struct timeval now; - int rc; - int i; - - DEBUGASSERT(sockindex >= FIRSTSOCKET && sockindex <= SECONDARYSOCKET); - - *connected = FALSE; /* a very negative world view is best */ - - if(conn->bits.tcpconnect[sockindex]) { - /* we are connected already! */ - *connected = TRUE; - return CURLE_OK; - } - - now = Curl_tvnow(); - - /* figure out how long time we have left to connect */ - allow = Curl_timeleft(data, &now, TRUE); - - if(allow < 0) { - /* time-out, bail out, go home */ - failf(data, "Connection time-out"); - return CURLE_OPERATION_TIMEDOUT; - } - - for(i=0; i<2; i++) { - const int other = i ^ 1; - if(conn->tempsock[i] == CURL_SOCKET_BAD) - continue; - -#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(conn->tempsock[i], NULL); + /* close if a connection, or a stream that is not multiplexed. */ + /* This function will be called both before and after this connection is + associated with a transfer. */ + bool closeit, is_multiplex; + DEBUGASSERT(conn); +#if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS) + (void)reason; /* useful for debugging */ #endif - - /* check socket for connect */ - rc = SOCKET_WRITABLE(conn->tempsock[i], 0); - - if(rc == 0) { /* no connection yet */ - error = 0; - if(curlx_tvdiff(now, conn->connecttime) >= conn->timeoutms_per_addr) { - infof(data, "After %ldms connect time, move on!\n", - conn->timeoutms_per_addr); - error = ETIMEDOUT; - } - - /* should we try another protocol family? */ - if(i == 0 && conn->tempaddr[1] == NULL && - curlx_tvdiff(now, conn->connecttime) >= HAPPY_EYEBALLS_TIMEOUT) { - trynextip(conn, sockindex, 1); - } - } - else if(rc == CURL_CSELECT_OUT || conn->bits.tcp_fastopen) { - if(verifyconnect(conn->tempsock[i], &error)) { - /* we are connected with TCP, awesome! */ - - /* use this socket from now on */ - conn->sock[sockindex] = conn->tempsock[i]; - conn->ip_addr = conn->tempaddr[i]; - conn->tempsock[i] = CURL_SOCKET_BAD; - - /* close the other socket, if open */ - if(conn->tempsock[other] != CURL_SOCKET_BAD) { - Curl_closesocket(conn, conn->tempsock[other]); - conn->tempsock[other] = CURL_SOCKET_BAD; - } - - /* see if we need to do any proxy magic first once we connected */ - result = Curl_connected_proxy(conn, sockindex); - if(result) - return result; - - conn->bits.tcpconnect[sockindex] = TRUE; - - *connected = TRUE; - if(sockindex == FIRSTSOCKET) - Curl_pgrsTime(data, TIMER_CONNECT); /* connect done */ - Curl_updateconninfo(conn, conn->sock[sockindex]); - Curl_verboseconnect(conn); - - return CURLE_OK; - } - else - infof(data, "Connection failed\n"); - } - else if(rc & CURL_CSELECT_ERR) - (void)verifyconnect(conn->tempsock[i], &error); - - /* - * The connection failed here, we should attempt to connect to the "next - * address" for the given host. But first remember the latest error. - */ - if(error) { - data->state.os_errno = error; - SET_SOCKERRNO(error); - if(conn->tempaddr[i]) { - CURLcode status; - char ipaddress[MAX_IPADR_LEN]; - Curl_printable_address(conn->tempaddr[i], ipaddress, MAX_IPADR_LEN); - infof(data, "connect to %s port %ld failed: %s\n", - ipaddress, conn->port, Curl_strerror(conn, error)); - - conn->timeoutms_per_addr = conn->tempaddr[i]->ai_next == NULL ? - allow : allow / 2; - - status = trynextip(conn, sockindex, i); - if(status != CURLE_COULDNT_CONNECT - || conn->tempsock[other] == CURL_SOCKET_BAD) - /* the last attempt failed and no other sockets remain open */ - result = status; - } - } + 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 */ } +} - if(result) { - /* no more addresses to try */ - - const char *hostname; - - /* if the first address family runs out of addresses to try before - the happy eyeball timeout, go ahead and try the next family now */ - if(conn->tempaddr[1] == NULL) { - result = trynextip(conn, sockindex, 1); - if(!result) - return result; - } - - if(conn->bits.socksproxy) - hostname = conn->socks_proxy.host.name; - else if(conn->bits.httpproxy) - hostname = conn->http_proxy.host.name; - else if(conn->bits.conn_to_host) - hostname = conn->conn_to_host.name; - else - hostname = conn->host.name; +/** + * 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(shutdown); /* cf has shutdown */ + BIT(inconclusive); /* connect was not a hard failure, we + * might talk to a restarting server */ +}; - failf(data, "Failed to connect to %s port %ld: %s", - hostname, conn->port, Curl_strerror(conn, error)); - } - return result; -} +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; +}; -void Curl_tcpnodelay(struct connectdata *conn, curl_socket_t sockfd) +/* 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) { -#if defined(TCP_NODELAY) -#if !defined(CURL_DISABLE_VERBOSE_STRINGS) - struct Curl_easy *data = conn->data; -#endif - curl_socklen_t onoff = (curl_socklen_t) 1; - int level = IPPROTO_TCP; - -#if 0 - /* The use of getprotobyname() is disabled since it isn't thread-safe on - numerous systems. On these getprotobyname_r() should be used instead, but - that exists in at least one 4 arg version and one 5 arg version, and - since the proto number rarely changes anyway we now just use the hard - coded number. The "proper" fix would need a configure check for the - correct function much in the same style the gethostbyname_r versions are - detected. */ - struct protoent *pe = getprotobyname("tcp"); - if(pe) - level = pe->p_proto; -#endif + struct eyeballer *baller; -#if defined(CURL_DISABLE_VERBOSE_STRINGS) - (void) conn; -#endif + *pballer = NULL; + baller = calloc(1, sizeof(*baller)); + if(!baller) + return CURLE_OUT_OF_MEMORY; - if(setsockopt(sockfd, level, TCP_NODELAY, (void *)&onoff, - sizeof(onoff)) < 0) - infof(data, "Could not set TCP_NODELAY: %s\n", - Curl_strerror(conn, SOCKERRNO)); - else - infof(data, "TCP_NODELAY set\n"); -#else - (void)conn; - (void)sockfd; + baller->name = ((ai_family == AF_INET) ? "ipv4" : ( +#ifdef USE_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; } -#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 connectdata *conn, - curl_socket_t sockfd) +static void baller_close(struct eyeballer *baller, + struct Curl_easy *data) { - struct Curl_easy *data= conn->data; - int onoff = 1; - if(setsockopt(sockfd, SOL_SOCKET, SO_NOSIGPIPE, (void *)&onoff, - sizeof(onoff)) < 0) - infof(data, "Could not set SO_NOSIGPIPE: %s\n", - Curl_strerror(conn, SOCKERRNO)); + if(baller && baller->cf) { + Curl_conn_cf_discard_chain(&baller->cf, data); + } } -#else -#define nosigpipe(x,y) Curl_nop_stmt -#endif - -#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) +static void baller_free(struct eyeballer *baller, + struct Curl_easy *data) { - 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(Curl_verify_windows_version(6, 0, PLATFORM_WINNT, - VERSION_GREATER_THAN_EQUAL)) - detectOsState = DETECT_OS_VISTA_OR_LATER; - else - detectOsState = DETECT_OS_PREVISTA; + if(baller) { + baller_close(baller, data); + free(baller); } +} - if(detectOsState == DETECT_OS_VISTA_OR_LATER) - return; - - if(getsockopt(sockfd, SOL_SOCKET, SO_SNDBUF, (char *)&curval, &curlen) == 0) - if(curval > val) - return; +static void baller_rewind(struct eyeballer *baller) +{ + baller->rewinded = TRUE; + baller->addr = baller->first; + baller->inconclusive = FALSE; +} - setsockopt(sockfd, SOL_SOCKET, SO_SNDBUF, (const char *)&val, sizeof(val)); +static void baller_next_addr(struct eyeballer *baller) +{ + baller->addr = addr_next_match(baller->addr, baller->ai_family); } -#endif /* - * singleipconnect() + * 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. - * - * singleipconnect() connects to the given IP only, and it may return without - * having connected. */ -static CURLcode singleipconnect(struct connectdata *conn, - const Curl_addrinfo *ai, - curl_socket_t *sockp) +static void baller_initiate(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct eyeballer *baller) { - struct Curl_sockaddr_ex addr; - int rc = -1; - int error = 0; - bool isconnected = FALSE; - struct Curl_easy *data = conn->data; - curl_socket_t sockfd; + struct cf_he_ctx *ctx = cf->ctx; + struct Curl_cfilter *cf_prev = baller->cf; + struct Curl_cfilter *wcf; CURLcode result; - char ipaddress[MAX_IPADR_LEN]; - long port; - bool is_tcp; - *sockp = CURL_SOCKET_BAD; - result = Curl_socket(conn, ai, &addr, &sockfd); + /* Do not 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) - /* Failed to create the socket, but still return OK since we signal the - lack of socket as well. This allows the parent function to keep looping - over alternative addresses/socket families etc. */ - return CURLE_OK; + goto out; - /* store remote address and port used in this connection attempt */ - if(!getaddressinfo((struct sockaddr*)&addr.sa_addr, - ipaddress, &port)) { - /* malformed address or bug in inet_ntop, try next address */ - error = ERRNO; - failf(data, "sa_addr inet_ntop() failed with errno %d: %s", - error, Curl_strerror(conn, error)); - Curl_closesocket(conn, sockfd); - return CURLE_OK; + /* the new filter might have sub-filters */ + for(wcf = baller->cf; wcf; wcf = wcf->next) { + wcf->conn = cf->conn; + wcf->sockindex = cf->sockindex; } - infof(data, " Trying %s...\n", ipaddress); - -#ifdef ENABLE_IPV6 - is_tcp = (addr.family == AF_INET || addr.family == AF_INET6) && - addr.socktype == SOCK_STREAM; -#else - is_tcp = (addr.family == AF_INET) && addr.socktype == SOCK_STREAM; -#endif - if(is_tcp && data->set.tcp_nodelay) - Curl_tcpnodelay(conn, sockfd); - - nosigpipe(conn, sockfd); - - Curl_sndbufset(sockfd); - - if(is_tcp && data->set.tcp_keepalive) - tcpkeepalive(data, sockfd); - if(data->set.fsockopt) { - /* activate callback for setting socket options */ - error = data->set.fsockopt(data->set.sockopt_client, - sockfd, - CURLSOCKTYPE_IPCXN); - - if(error == CURL_SOCKOPT_ALREADY_CONNECTED) - isconnected = TRUE; - else if(error) { - Curl_closesocket(conn, sockfd); /* close the socket and bail out */ - return CURLE_ABORTED_BY_CALLBACK; - } + if(addr_next_match(baller->addr, baller->ai_family)) { + Curl_expire(data, baller->timeoutms, baller->timeout_id); } - /* possibly bind the local end to an IP, interface or port */ - if(addr.family == AF_INET -#ifdef ENABLE_IPV6 - || addr.family == AF_INET6 -#endif - ) { - result = bindlocal(conn, sockfd, addr.family, - Curl_ipv6_scope((struct sockaddr*)&addr.sa_addr)); - if(result) { - Curl_closesocket(conn, sockfd); /* close socket and bail out */ - if(result == CURLE_UNSUPPORTED_PROTOCOL) { - /* The address family is not supported on this interface. - We can continue trying addresses */ - return CURLE_COULDNT_CONNECT; - } - return result; - } +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; +} - /* set socket non-blocking */ - (void)curlx_nonblock(sockfd, TRUE); - - conn->connecttime = Curl_tvnow(); - if(conn->num_addr > 1) - Curl_expire_latest(data, conn->timeoutms_per_addr); - - /* Connect TCP sockets, bind UDP */ - if(!isconnected && (conn->socktype == SOCK_STREAM)) { - if(conn->bits.tcp_fastopen) { -#if defined(CONNECT_DATA_IDEMPOTENT) /* OS X */ - sa_endpoints_t endpoints; - endpoints.sae_srcif = 0; - endpoints.sae_srcaddr = NULL; - endpoints.sae_srcaddrlen = 0; - endpoints.sae_dstaddr = &addr.sa_addr; - endpoints.sae_dstaddrlen = addr.addrlen; - - rc = connectx(sockfd, &endpoints, SAE_ASSOCID_ANY, - CONNECT_RESUME_ON_READ_WRITE | CONNECT_DATA_IDEMPOTENT, - NULL, 0, NULL, NULL); -#elif defined(MSG_FASTOPEN) /* Linux */ - if(conn->given->flags & PROTOPT_SSL) - rc = connect(sockfd, &addr.sa_addr, addr.addrlen); - else - rc = 0; /* Do nothing */ +/** + * 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 start + * again until this whole thing times out. This allows us to + * connect to servers that are gracefully restarting and the + * packet routing to the new instance has not happened yet (e.g. QUIC). */ + if(!baller->addr && baller->inconclusive) + 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 %" FMT_TIMEDIFF_T + "ms, move on!", baller->name, baller->timeoutms); +#if defined(ETIMEDOUT) + baller->error = ETIMEDOUT; #endif + baller->result = CURLE_OPERATION_TIMEDOUT; + } } - else { - rc = connect(sockfd, &addr.sa_addr, addr.addrlen); - } + 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(-1 == rc) - error = SOCKERRNO; + 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); + } + } } - else { - *sockp = sockfd; + + if(ctx->winner) { + *connected = TRUE; return CURLE_OK; } -#ifdef ENABLE_IPV6 - conn->bits.ipv6 = (addr.family == AF_INET6)?TRUE:FALSE; -#endif + /* 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 %" FMT_OFF_T " ms", + Curl_timediff(now, data->progress.t_startsingle)); + return CURLE_OPERATION_TIMEDOUT; + } - if(-1 == rc) { - 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 - result = CURLE_OK; - break; + /* 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=%" FMT_TIMEDIFF_T "ms)", + baller->name, baller->timeoutms); + ++ongoing; + ++added; + } + } + } + if(added > 0) + goto evaluate; + } - default: - /* unknown error, fallthrough and try another address! */ - infof(data, "Immediate connect fail for %s: %s\n", - ipaddress, Curl_strerror(conn, error)); - data->state.os_errno = error; - - /* connect failed */ - Curl_closesocket(conn, sockfd); - result = CURLE_COULDNT_CONNECT; + 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; } } - if(!result) - *sockp = sockfd; +#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 " + "%" FMT_TIMEDIFF_T " ms: %s", + hostname, conn->primary.remote_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; } /* - * TCP connect to the given host with timeout, proxy or remote doesn't matter. - * There might be more than one IP address to try out. Fill in the passed - * pointer with the connected socket. + * Connect to the given host with timeout, proxy or remote does not matter. + * There might be more than one IP address to try out. */ - -CURLcode Curl_connecthost(struct connectdata *conn, /* context */ - const struct Curl_dns_entry *remotehost) +static CURLcode start_connect(struct Curl_cfilter *cf, + struct Curl_easy *data, + const struct Curl_dns_entry *remotehost) { - struct Curl_easy *data = conn->data; - struct timeval before = Curl_tvnow(); + struct cf_he_ctx *ctx = cf->ctx; + struct connectdata *conn = cf->conn; CURLcode result = CURLE_COULDNT_CONNECT; - - time_t timeout_ms = Curl_timeleft(data, &before, TRUE); + int ai_family0 = 0, ai_family1 = 0; + timediff_t timeout_ms = Curl_timeleft(data, NULL, TRUE); + const struct Curl_addrinfo *addr0 = NULL, *addr1 = NULL; if(timeout_ms < 0) { /* a precaution, no need to continue if time already is up */ @@ -1165,255 +779,731 @@ CURLcode Curl_connecthost(struct connectdata *conn, /* context */ return CURLE_OPERATION_TIMEDOUT; } - conn->num_addr = Curl_num_addresses(remotehost->addr); - conn->tempaddr[0] = remotehost->addr; - conn->tempaddr[1] = NULL; - conn->tempsock[0] = CURL_SOCKET_BAD; - conn->tempsock[1] = CURL_SOCKET_BAD; - Curl_expire(conn->data, HAPPY_EYEBALLS_TIMEOUT); - - /* Max time for the next connection attempt */ - conn->timeoutms_per_addr = - conn->tempaddr[0]->ai_next == NULL ? timeout_ms : timeout_ms / 2; - - /* start connecting to first IP */ - while(conn->tempaddr[0]) { - result = singleipconnect(conn, conn->tempaddr[0], &(conn->tempsock[0])); - if(!result) - break; - conn->tempaddr[0] = conn->tempaddr[0]->ai_next; + 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_V6) { +#ifdef USE_IPV6 + ai_family0 = AF_INET6; + addr0 = addr_first_match(remotehost->addr, ai_family0); +#endif + } + else if(conn->ip_version == CURL_IPRESOLVE_V4) { + ai_family0 = AF_INET; + addr0 = addr_first_match(remotehost->addr, ai_family0); + } + else { + /* no user preference, we try ipv6 always first when available */ +#ifdef USE_IPV6 + ai_family0 = AF_INET6; + addr0 = addr_first_match(remotehost->addr, ai_family0); +#endif + /* next candidate is ipv4 */ + ai_family1 = AF_INET; + addr1 = addr_first_match(remotehost->addr, ai_family1); + /* no ip address families, probably AF_UNIX or something, use the + * address family given to us */ + if(!addr1 && !addr0 && remotehost->addr) { + ai_family0 = remotehost->addr->ai_family; + addr0 = addr_first_match(remotehost->addr, ai_family0); + } } - if(conn->tempsock[0] == CURL_SOCKET_BAD) { - if(!result) - result = CURLE_COULDNT_CONNECT; - return result; + if(!addr0 && addr1) { + /* switch around, so a single baller always uses addr0 */ + addr0 = addr1; + ai_family0 = ai_family1; + addr1 = NULL; } - data->info.numconnects++; /* to track the number of connections made */ + /* 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 %" FMT_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 %" FMT_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; } -struct connfind { - struct connectdata *tofind; - bool found; -}; - -static int conn_is_conn(struct connectdata *conn, void *param) +static void cf_he_ctx_clear(struct Curl_cfilter *cf, struct Curl_easy *data) { - struct connfind *f = (struct connfind *)param; - if(conn == f->tofind) { - f->found = TRUE; - return 1; + 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; } - return 0; + baller_free(ctx->winner, data); + ctx->winner = NULL; } -/* - * 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) +static CURLcode cf_he_shutdown(struct Curl_cfilter *cf, + struct Curl_easy *data, bool *done) { - curl_socket_t sockfd; + struct cf_he_ctx *ctx = cf->ctx; + size_t i; + CURLcode result = CURLE_OK; DEBUGASSERT(data); + if(cf->connected) { + *done = TRUE; + return CURLE_OK; + } - /* 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 && (data->multi_easy || data->multi)) { - struct connectdata *c = data->state.lastconnect; - struct connfind find; - find.tofind = data->state.lastconnect; - find.found = FALSE; - - Curl_conncache_foreach(data->multi_easy? - &data->multi_easy->conn_cache: - &data->multi->conn_cache, &find, conn_is_conn); - - if(!find.found) { - data->state.lastconnect = NULL; - return CURL_SOCKET_BAD; + /* shutdown all ballers that have not done so already. If one fails, + * continue shutting down others until all are shutdown. */ + for(i = 0; i < ARRAYSIZE(ctx->baller); i++) { + struct eyeballer *baller = ctx->baller[i]; + bool bdone = FALSE; + if(!baller || !baller->cf || baller->shutdown) + continue; + baller->result = baller->cf->cft->do_shutdown(baller->cf, data, &bdone); + if(baller->result || bdone) + baller->shutdown = TRUE; /* treat a failed shutdown as done */ + } + + *done = TRUE; + for(i = 0; i < ARRAYSIZE(ctx->baller); i++) { + if(ctx->baller[i] && !ctx->baller[i]->shutdown) + *done = FALSE; + } + if(*done) { + for(i = 0; i < ARRAYSIZE(ctx->baller); i++) { + if(ctx->baller[i] && ctx->baller[i]->result) + result = ctx->baller[i]->result; } + } + CURL_TRC_CF(data, cf, "shutdown -> %d, done=%d", result, *done); + return result; +} - if(connp) - /* only store this if the caller cares for it */ - *connp = c; - sockfd = c->sock[FIRSTSOCKET]; +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); } - else - return CURL_SOCKET_BAD; +} - return sockfd; +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); + + if(cf->conn->handler->protocol & PROTO_FAMILY_SSH) + Curl_pgrsTime(data, TIMER_APPCONNECT); /* we are connected already */ + if(Curl_trc_cf_is_verbose(cf, data)) { + struct ip_quadruple ipquad; + int is_ipv6; + if(!Curl_conn_cf_get_ip_info(cf->next, data, &is_ipv6, &ipquad)) { + const char *host, *disphost; + int port; + cf->next->cft->get_host(cf->next, data, &host, &disphost, &port); + CURL_TRC_CF(data, cf, "Connected to %s (%s) port %u", + disphost, ipquad.remote_ip, ipquad.remote_port); + } + } + data->info.numconnects++; /* to track the # of connections made */ + } + break; + case SCFST_DONE: + *done = TRUE; + break; + } + return result; } -/* - * Check if a connection seems to be alive. - */ -bool Curl_connalive(struct connectdata *conn) +static void cf_he_close(struct Curl_cfilter *cf, + struct Curl_easy *data) { - /* First determine if ssl */ - if(conn->ssl[FIRSTSOCKET].use) { - /* use the SSL context */ - if(!Curl_ssl_check_cxn(conn)) - return false; /* FIN received */ + 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); } -/* Minix 3.1 doesn't support any flags on recv; just assume socket is OK */ -#ifdef MSG_PEEK - else if(conn->sock[FIRSTSOCKET] == CURL_SOCKET_BAD) - return false; - else { - /* use the socket */ - char buf; - if(recv((RECV_TYPE_ARG1)conn->sock[FIRSTSOCKET], (RECV_TYPE_ARG2)&buf, - (RECV_TYPE_ARG3)1, (RECV_TYPE_ARG4)MSG_PEEK) == 0) { - return false; /* FIN received */ +} + +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; } } -#endif - return true; + return tmax; } -/* - * Close a socket. - * - * 'conn' can be NULL, beware! - */ -int Curl_closesocket(struct connectdata *conn, - curl_socket_t sock) +static CURLcode cf_he_query(struct Curl_cfilter *cf, + struct Curl_easy *data, + int query, int *pres1, void *pres2) { - if(conn && conn->fclosesocket) { - if((sock == conn->sock[SECONDARYSOCKET]) && - conn->sock_accepted[SECONDARYSOCKET]) - /* if this socket matches the second socket, and that was created with - accept, then we MUST NOT call the callback but clear the accepted - status */ - conn->sock_accepted[SECONDARYSOCKET] = FALSE; - else { - Curl_multi_closed(conn, sock); - return conn->fclosesocket(conn->closesocket_client, sock); + 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; } } - if(conn) - /* tell the multi-socket code about this */ - Curl_multi_closed(conn, sock); + return cf->next ? + cf->next->cft->query(cf->next, data, query, pres1, pres2) : + CURLE_UNKNOWN_OPTION; +} - sclose(sock); +static void cf_he_destroy(struct Curl_cfilter *cf, struct Curl_easy *data) +{ + struct cf_he_ctx *ctx = cf->ctx; - return 0; + CURL_TRC_CF(data, cf, "destroy"); + if(ctx) { + cf_he_ctx_clear(cf, data); + } + /* release any resources held in state */ + Curl_safefree(ctx); } -/* - * 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! - * +struct Curl_cftype Curl_cft_happy_eyeballs = { + "HAPPY-EYEBALLS", + 0, + CURL_LOG_LVL_NONE, + cf_he_destroy, + cf_he_connect, + cf_he_close, + cf_he_shutdown, + Curl_cf_def_get_host, + cf_he_adjust_pollset, + cf_he_data_pending, + 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. */ -CURLcode Curl_socket(struct connectdata *conn, - const Curl_addrinfo *ai, - struct Curl_sockaddr_ex *addr, - curl_socket_t *sockfd) +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 Curl_easy *data = conn->data; - struct Curl_sockaddr_ex dummy; - - if(!addr) - /* if the caller doesn't want info back, use a local temp copy */ - addr = &dummy; - - /* - * 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 cf_he_ctx *ctx = NULL; + CURLcode result; - addr->family = ai->ai_family; - addr->socktype = conn->socktype; - addr->protocol = conn->socktype==SOCK_DGRAM?IPPROTO_UDP:ai->ai_protocol; - addr->addrlen = ai->ai_addrlen; - - if(addr->addrlen > sizeof(struct Curl_sockaddr_storage)) - addr->addrlen = sizeof(struct Curl_sockaddr_storage); - memcpy(&addr->sa_addr, ai->ai_addr, addr->addrlen); - - 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. - */ - *sockfd = data->set.fopensocket(data->set.opensocket_client, - CURLSOCKTYPE_IPCXN, - (struct curl_sockaddr *)addr); - else - /* opensocket callback not set, so simply create the socket now */ - *sockfd = socket(addr->family, addr->socktype, addr->protocol); + (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; - if(*sockfd == CURL_SOCKET_BAD) - /* no socket, no connection */ - return CURLE_COULDNT_CONNECT; + result = Curl_cf_create(pcf, &Curl_cft_happy_eyeballs, ctx); -#if defined(ENABLE_IPV6) && defined(HAVE_SOCKADDR_IN6_SIN6_SCOPE_ID) - if(conn->scope_id && (addr->family == AF_INET6)) { - struct sockaddr_in6 * const sa6 = (void *)&addr->sa_addr; - sa6->sin6_scope_id = conn->scope_id; +out: + if(result) { + Curl_safefree(*pcf); + Curl_safefree(ctx); } + return result; +} + +struct transport_provider { + int transport; + cf_ip_connect_create *cf_create; +}; + +static +#ifndef UNITTESTS +const +#endif +struct transport_provider transport_providers[] = { + { TRNSPRT_TCP, Curl_cf_tcp_create }, +#ifdef USE_HTTP3 + { 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); + } } -/* - * Curl_conncontrol() marks streams or connection for closure. - */ -void Curl_conncontrol(struct connectdata *conn, - int ctrl /* see defines in header */ -#ifdef DEBUGBUILD - , const char *reason -#endif - ) +static void cf_setup_destroy(struct Curl_cfilter *cf, struct Curl_easy *data) { - /* close if a connection, or a stream that isn't multiplexed */ - bool closeit = (ctrl == CONNCTRL_CONNECTION) || - ((ctrl == CONNCTRL_STREAM) && !(conn->handler->flags & PROTOPT_STREAM)); - if((ctrl == CONNCTRL_STREAM) && - (conn->handler->flags & PROTOPT_STREAM)) - DEBUGF(infof(conn->data, "Kill stream: %s\n", reason)); - else if(closeit != conn->bits.close) { - DEBUGF(infof(conn->data, "Marked for [%s]: %s\n", - closeit?"closure":"keep alive", reason)); - conn->bits.close = closeit; /* the only place in the source code that - should assign this bit */ + 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_shutdown, + 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 UNITTESTS +/* 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 /* UNITTESTS */ + +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; +} -/* Data received can be cached at various levels, so check them all here. */ -bool Curl_conn_data_pending(struct connectdata *conn, int sockindex) +CURLcode Curl_conn_setup(struct Curl_easy *data, + struct connectdata *conn, + int sockindex, + const struct Curl_dns_entry *remotehost, + int ssl_mode) { - int readable; + CURLcode result = CURLE_OK; - if(Curl_ssl_data_pending(conn, sockindex) || - Curl_recv_has_postponed_data(conn, sockindex)) - return true; + 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; + } - readable = SOCKET_READABLE(conn->sock[sockindex], 0); - return (readable > 0 && (readable & CURL_CSELECT_IN)); + DEBUGASSERT(conn->cfilter[sockindex]); +out: + return result; } diff --git a/contrib/curl/lib/connect.h b/contrib/curl/lib/connect.h index 5653cb4e50..160db9420f 100644 --- a/contrib/curl/lib/connect.h +++ b/contrib/curl/lib/connect.h @@ -7,11 +7,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2016, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -20,94 +20,57 @@ * 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" -CURLcode Curl_is_connected(struct connectdata *conn, - int sockindex, - bool *connected); - -CURLcode Curl_connecthost(struct connectdata *conn, - const struct Curl_dns_entry *host); +struct Curl_dns_entry; +struct ip_quadruple; -/* generic function that returns how much time there's left to run, according +/* generic function that returns how much time there is left to run, according to the timeouts set */ -time_t Curl_timeleft(struct Curl_easy *data, - struct timeval *nowp, - bool duringconnect); +timediff_t Curl_timeleft(struct Curl_easy *data, + struct curltime *nowp, + bool duringconnect); #define DEFAULT_CONNECT_TIMEOUT 300000 /* milliseconds == five minutes */ -#define HAPPY_EYEBALLS_TIMEOUT 200 /* milliseconds to wait between - IPv4/IPv6 connection attempts */ - -/* - * 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); - -/* - * Check if a connection seems to be alive. - */ -bool Curl_connalive(struct connectdata *conn); -#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. +#define DEFAULT_SHUTDOWN_TIMEOUT_MS (2 * 1000) - https://support.microsoft.com/kb/823764 +void Curl_shutdown_start(struct Curl_easy *data, int sockindex, + struct curltime *nowp); - Work-around: Make the Socket Send Buffer Size Larger Than the Program Send - Buffer Size +/* return how much time there is left to shutdown the connection at + * sockindex. Returns 0 if there is no limit or shutdown has not started. */ +timediff_t Curl_shutdown_timeleft(struct connectdata *conn, int sockindex, + struct curltime *nowp); -*/ -void Curl_sndbufset(curl_socket_t sockfd); -#else -#define Curl_sndbufset(y) Curl_nop_stmt -#endif +/* return how much time there is left to shutdown the connection. + * Returns 0 if there is no limit or shutdown has not started. */ +timediff_t Curl_conn_shutdown_timeleft(struct connectdata *conn, + struct curltime *nowp); -void Curl_updateconninfo(struct connectdata *conn, curl_socket_t sockfd); -void Curl_persistconninfo(struct connectdata *conn); -int Curl_closesocket(struct connectdata *conn, curl_socket_t sock); +void Curl_shutdown_clear(struct Curl_easy *data, int sockindex); -/* - * 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 +/* TRUE iff shutdown has been started */ +bool Curl_shutdown_started(struct Curl_easy *data, int sockindex); /* - * 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! + * 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! */ -CURLcode Curl_socket(struct connectdata *conn, - const Curl_addrinfo *ai, - struct Curl_sockaddr_ex *addr, - curl_socket_t *sockfd); +curl_socket_t Curl_getconnectinfo(struct Curl_easy *data, + struct connectdata **connp); -void Curl_tcpnodelay(struct connectdata *conn, curl_socket_t sockfd); +bool Curl_addr2string(struct sockaddr *sa, curl_socklen_t salen, + char *addr, int *port); /* * Curl_conncontrol() marks the end of a connection/stream. The 'closeit' @@ -127,21 +90,61 @@ void Curl_tcpnodelay(struct connectdata *conn, curl_socket_t sockfd); void Curl_conncontrol(struct connectdata *conn, int closeit -#ifdef DEBUGBUILD +#if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS) , const char *reason #endif ); -#ifdef DEBUGBUILD +#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 !CURLDEBUG */ +#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 -bool Curl_conn_data_pending(struct connectdata *conn, int sockindex); +/** + * 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 UNITTESTS +void Curl_debug_set_transport_provider(int transport, + cf_ip_connect_create *cf_create); +#endif #endif /* HEADER_CURL_CONNECT_H */ diff --git a/contrib/curl/lib/content_encoding.c b/contrib/curl/lib/content_encoding.c index c3996a1130..67c2fb758c 100644 --- a/contrib/curl/lib/content_encoding.c +++ b/contrib/curl/lib/content_encoding.c @@ -5,11 +5,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2016, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -18,37 +18,93 @@ * 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_LIBZ - #include "urldata.h" #include +#include + +#ifdef HAVE_LIBZ +#include +#endif + +#ifdef HAVE_BROTLI +#if defined(__GNUC__) +/* Ignore -Wvla warnings in brotli headers */ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wvla" +#endif +#include +#if defined(__GNUC__) +#pragma GCC diagnostic pop +#endif +#endif + +#ifdef HAVE_ZSTD +#include +#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 DSIZ CURL_MAX_WRITE_SIZE /* buffer size for decompressed data */ - #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 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 ORIG_NAME 0x08 /* bit 3 set: original filename 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) { @@ -65,9 +121,8 @@ zfree_cb(voidpf opaque, voidpf ptr) } static CURLcode -process_zlib_error(struct connectdata *conn, z_stream *z) +process_zlib_error(struct Curl_easy *data, z_stream *z) { - struct Curl_easy *data = conn->data; if(z->msg) failf(data, "Error while processing content unencoding: %s", z->msg); @@ -79,124 +134,245 @@ process_zlib_error(struct connectdata *conn, z_stream *z) } static CURLcode -exit_zlib(z_stream *z, zlibInitState *zlib_init, CURLcode result) +exit_zlib(struct Curl_easy *data, + z_stream *z, zlibInitState *zlib_init, CURLcode result) { - inflateEnd(z); - *zlib_init = ZLIB_UNINIT; + 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 -inflate_stream(struct connectdata *conn, - struct SingleRequest *k) +static CURLcode process_trailer(struct Curl_easy *data, + struct zlib_writer *zp) { - int allow_restart = 1; - z_stream *z = &k->z; /* zlib state structure */ + 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; - int status; /* zlib status */ + bool done = FALSE; CURLcode result = CURLE_OK; /* Curl_client_write status */ char *decomp; /* Put the decompressed data here. */ - /* Dynamically allocate a buffer for decompression because it's uncommonly + /* 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 is uncommonly large to hold on the stack */ decomp = malloc(DSIZ); - if(decomp == NULL) { - return exit_zlib(z, &k->zlib_init, CURLE_OUT_OF_MEMORY); - } + 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 client_write. */ - for(;;) { + 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->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); - if(status == Z_OK || status == Z_STREAM_END) { - allow_restart = 0; - if((DSIZ - z->avail_out) && (!k->ignorebody)) { - result = Curl_client_write(conn, CLIENTWRITE_BODY, decomp, - DSIZ - z->avail_out); - /* if !CURLE_OK, clean up, return */ +#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) { - free(decomp); - return exit_zlib(z, &k->zlib_init, result); + exit_zlib(data, z, &zp->zlib_init, result); + break; } } - - /* Done? clean up, return */ - if(status == Z_STREAM_END) { - free(decomp); - if(inflateEnd(z) == Z_OK) - return exit_zlib(z, &k->zlib_init, result); - else - return exit_zlib(z, &k->zlib_init, process_zlib_error(conn, z)); - } - - /* Done with these bytes, exit */ - - /* status is always Z_OK at this point! */ - if(z->avail_in == 0) { - free(decomp); - return result; - } } - else if(allow_restart && status == Z_DATA_ERROR) { + + /* 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 */ - - (void) inflateEnd(z); /* don't care about the return code */ - if(inflateInit2(z, -MAX_WBITS) != Z_OK) { - free(decomp); - return exit_zlib(z, &k->zlib_init, process_zlib_error(conn, z)); + if(zp->zlib_init == ZLIB_INIT) { + /* Do not use inflateReset2(): only available since zlib 1.2.3.4. */ + (void) inflateEnd(z); /* do not 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. */ } - z->next_in = orig_in; - z->avail_in = nread; - allow_restart = 0; - continue; - } - else { /* Error; exit loop, handle below */ - free(decomp); - return exit_zlib(z, &k->zlib_init, process_zlib_error(conn, z)); + 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; } } - /* Will never get here */ + free(decomp); + + /* We are about to leave this call so the `nread' data bytes will not 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; } -CURLcode -Curl_unencode_deflate_write(struct connectdata *conn, - struct SingleRequest *k, - ssize_t nread) + +/* Deflate handler. */ +static CURLcode deflate_do_init(struct Curl_easy *data, + struct Curl_cwriter *writer) { - z_stream *z = &k->z; /* zlib state structure */ + struct zlib_writer *zp = (struct zlib_writer *) writer; + z_stream *z = &zp->z; /* zlib state structure */ - /* Initialize zlib? */ - if(k->zlib_init == ZLIB_UNINIT) { - memset(z, 0, sizeof(z_stream)); - z->zalloc = (alloc_func)zalloc_cb; - z->zfree = (free_func)zfree_cb; + /* Initialize zlib */ + z->zalloc = (alloc_func) zalloc_cb; + z->zfree = (free_func) zfree_cb; - if(inflateInit(z) != Z_OK) - return process_zlib_error(conn, z); - k->zlib_init = ZLIB_INIT; - } + 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) || !nbytes) + return Curl_cwriter_write(data, writer->next, type, buf, nbytes); /* Set the compressed input when this function is called */ - z->next_in = (Bytef *)k->str; - z->avail_in = (uInt)nread; + 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(conn, k); + 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 { +typedef enum { GZIP_OK, GZIP_BAD, GZIP_UNDERFLOW -} check_gzip_header(unsigned char const *data, ssize_t len, ssize_t *headerlen) +} gzip_status; + +static gzip_status check_gzip_header(unsigned char const *data, ssize_t len, + ssize_t *headerlen) { int method, flags; const ssize_t totallen = len; @@ -212,7 +388,7 @@ static enum { flags = data[3]; if(method != Z_DEFLATED || (flags & RESERVED) != 0) { - /* Can't handle this compression method or unknown flag */ + /* cannot handle this compression method or unknown flag */ return GZIP_BAD; } @@ -228,7 +404,7 @@ static enum { extra_len = (data[1] << 8) | data[0]; - if(len < (extra_len+2)) + if(len < (extra_len + 2)) return GZIP_UNDERFLOW; len -= (extra_len + 2); @@ -236,7 +412,7 @@ static enum { } if(flags & ORIG_NAME) { - /* Skip over NUL-terminated file name */ + /* Skip over NUL-terminated filename */ while(len && *data) { --len; ++data; @@ -274,94 +450,75 @@ static enum { } #endif -CURLcode -Curl_unencode_gzip_write(struct connectdata *conn, - struct SingleRequest *k, - ssize_t nread) +static CURLcode gzip_do_write(struct Curl_easy *data, + struct Curl_cwriter *writer, int type, + const char *buf, size_t nbytes) { - z_stream *z = &k->z; /* zlib state structure */ - - /* Initialize zlib? */ - if(k->zlib_init == ZLIB_UNINIT) { - memset(z, 0, sizeof(z_stream)); - 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(conn, z); - } - k->zlib_init = ZLIB_INIT_GZIP; /* Transparent gzip decompress state */ - } - else { - /* we must parse the gzip header ourselves */ - if(inflateInit2(z, -MAX_WBITS) != Z_OK) { - return process_zlib_error(conn, z); - } - k->zlib_init = ZLIB_INIT; /* Initial call state */ - } - } + struct zlib_writer *zp = (struct zlib_writer *) writer; + z_stream *z = &zp->z; /* zlib state structure */ + + if(!(type & CLIENTWRITE_BODY) || !nbytes) + return Curl_cwriter_write(data, writer->next, type, buf, nbytes); - if(k->zlib_init == ZLIB_INIT_GZIP) { + if(zp->zlib_init == ZLIB_INIT_GZIP) { /* Let zlib handle the gzip decompression entirely */ - z->next_in = (Bytef *)k->str; - z->avail_in = (uInt)nread; + z->next_in = (Bytef *) buf; + z->avail_in = (uInt) nbytes; /* Now uncompress the data */ - return inflate_stream(conn, k); + 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(z, &k->zlib_init, CURLE_WRITE_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 + /* This next mess is to get around the potential case where there is not + * 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 is not 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(k->zlib_init) { + switch(zp->zlib_init) { /* Skip over gzip header? */ case ZLIB_INIT: { /* Initial call state */ ssize_t hlen; - switch(check_gzip_header((unsigned char *)k->str, nread, &hlen)) { + switch(check_gzip_header((unsigned char *) buf, nbytes, &hlen)) { case GZIP_OK: - z->next_in = (Bytef *)k->str + hlen; - z->avail_in = (uInt)(nread - hlen); - k->zlib_init = ZLIB_GZIP_INFLATING; /* Inflating stream state */ + 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 + /* We need more data so we can find the end of the gzip header. it is * possible that the memory block we malloc here will never be freed if - * the transfer abruptly aborts after this point. Since it's unlikely + * the transfer abruptly aborts after this point. Since it is 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 + * the first place, and it is even more unlikely for a transfer to fail * immediately afterwards, it should seldom be a problem. */ - z->avail_in = (uInt)nread; + z->avail_in = (uInt) nbytes; z->next_in = malloc(z->avail_in); - if(z->next_in == NULL) { - return exit_zlib(z, &k->zlib_init, CURLE_OUT_OF_MEMORY); + if(!z->next_in) { + return exit_zlib(data, z, &zp->zlib_init, CURLE_OUT_OF_MEMORY); } - memcpy(z->next_in, k->str, z->avail_in); - k->zlib_init = ZLIB_GZIP_HEADER; /* Need more gzip header data state */ - /* We don't have any data to inflate yet */ + memcpy(z->next_in, buf, z->avail_in); + zp->zlib_init = ZLIB_GZIP_HEADER; /* Need more gzip header data state */ + /* We do not have any data to inflate yet */ return CURLE_OK; case GZIP_BAD: default: - return exit_zlib(z, &k->zlib_init, process_zlib_error(conn, z)); + return exit_zlib(data, z, &zp->zlib_init, process_zlib_error(data, z)); } } @@ -371,62 +528,559 @@ Curl_unencode_gzip_write(struct connectdata *conn, { /* Need more gzip header data state */ ssize_t hlen; - z->avail_in += (uInt)nread; + z->avail_in += (uInt) nbytes; z->next_in = Curl_saferealloc(z->next_in, z->avail_in); - if(z->next_in == NULL) { - return exit_zlib(z, &k->zlib_init, CURLE_OUT_OF_MEMORY); + 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 - nread, k->str, nread); + memcpy(z->next_in + z->avail_in - nbytes, buf, nbytes); - switch(check_gzip_header(z->next_in, z->avail_in, &hlen)) { + switch(check_gzip_header(z->next_in, (ssize_t)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 *)k->str + hlen + nread - z->avail_in; - z->avail_in = (uInt)(z->avail_in - hlen); - k->zlib_init = ZLIB_GZIP_INFLATING; /* Inflating stream state */ + /* Do not point into the malloced block since we just freed it */ + z->next_in = (Bytef *) buf + hlen + nbytes - z->avail_in; + z->avail_in = z->avail_in - (uInt)hlen; + zp->zlib_init = ZLIB_GZIP_INFLATING; /* Inflating stream state */ break; case GZIP_UNDERFLOW: - /* We still don't have any data to inflate! */ + /* We still do not have any data to inflate! */ return CURLE_OK; case GZIP_BAD: default: - free(z->next_in); - return exit_zlib(z, &k->zlib_init, process_zlib_error(conn, z)); + 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 *)k->str; - z->avail_in = (uInt)nread; + 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 */ + /* We do not have any data to inflate; wait until next time */ return CURLE_OK; } - /* We've parsed the header, now uncompress the data */ - return inflate_stream(conn, k); + /* We have parsed the header, now uncompress the data */ + return inflate_stream(data, writer, type, ZLIB_GZIP_INFLATING); #endif } -void Curl_unencode_cleanup(struct connectdata *conn) +static void gzip_do_close(struct Curl_easy *data, + struct Curl_cwriter *writer) { - struct Curl_easy *data = conn->data; - struct SingleRequest *k = &data->req; - z_stream *z = &k->z; - if(k->zlib_init != ZLIB_UNINIT) - (void) exit_zlib(z, &k->zlib_init, CURLE_OK); + 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) || !nbytes) + 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) || !nbytes) + 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 general content decoders. */ +static const struct Curl_cwtype * const general_unencoders[] = { + &identity_encoding, +#ifdef HAVE_LIBZ + &deflate_encoding, + &gzip_encoding, +#endif +#ifdef HAVE_BROTLI + &brotli_encoding, +#endif +#ifdef HAVE_ZSTD + &zstd_encoding, +#endif + NULL +}; + +/* supported content decoders only for transfer encodings */ +static const struct Curl_cwtype * const transfer_unencoders[] = { +#ifndef CURL_DISABLE_HTTP + &Curl_httpchunk_unencoder, +#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 = general_unencoders; *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 = general_unencoders; *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) +{ + (void) writer; + (void) buf; + (void) nbytes; + + if(!(type & CLIENTWRITE_BODY) || !nbytes) + return Curl_cwriter_write(data, writer->next, type, buf, nbytes); + else { + char all[256]; + (void)Curl_all_content_encodings(all, sizeof(all)); + 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_unencode_writer(const char *name, + size_t len, + Curl_cwriter_phase phase) +{ + const struct Curl_cwtype * const *cep; + + if(phase == CURL_CW_TRANSFER_DECODE) { + for(cep = transfer_unencoders; *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; + } + } + /* look among the general decoders */ + for(cep = general_unencoders; *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; +} + +/* Setup 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) +{ + Curl_cwriter_phase phase = is_transfer ? + CURL_CW_TRANSFER_DECODE : CURL_CW_CONTENT_DECODE; + CURLcode result; + + do { + const char *name; + size_t namelen; + bool is_chunked = FALSE; + + /* 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; + + if(namelen) { + const struct Curl_cwtype *cwt; + struct Curl_cwriter *writer; + + CURL_TRC_WRITE(data, "looking for %s decoder: %.*s", + is_transfer ? "transfer" : "content", (int)namelen, name); + is_chunked = (is_transfer && (namelen == 7) && + strncasecompare(name, "chunked", 7)); + /* if we skip the decoding in this phase, do not look further. + * Exception is "chunked" transfer-encoding which always must happen */ + if((is_transfer && !data->set.http_transfer_encoding && !is_chunked) || + (!is_transfer && data->set.http_ce_skip)) { + /* not requested, ignore */ + CURL_TRC_WRITE(data, "decoder not requested, ignored: %.*s", + (int)namelen, name); + 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_unencode_writer(name, namelen, phase); + if(cwt && is_chunked && Curl_cwriter_get_by_type(data, cwt)) { + /* A 'chunked' transfer encoding has already been added. + * Ignore duplicates. See #13451. + * Also RFC 9112, ch. 6.1: + * "A sender MUST NOT apply the chunked transfer coding more than + * once to a message body." + */ + CURL_TRC_WRITE(data, "ignoring duplicate 'chunked' decoder"); + return CURLE_OK; + } + + if(is_transfer && !is_chunked && + Curl_cwriter_get_by_name(data, "chunked")) { + /* RFC 9112, ch. 6.1: + * "If any transfer coding other than chunked is applied to a + * response's content, the sender MUST either apply chunked as the + * final transfer coding or terminate the message by closing the + * connection." + * "chunked" must be the last added to be the first in its phase, + * reject this. + */ + failf(data, "Reject response due to 'chunked' not being the last " + "Transfer-Encoding"); + return CURLE_BAD_CONTENT_ENCODING; + } + + if(!cwt) + cwt = &error_writer; /* Defer error at use. */ + + result = Curl_cwriter_create(&writer, data, cwt, phase); + CURL_TRC_WRITE(data, "added %s decoder %s -> %d", + is_transfer ? "transfer" : "content", cwt->name, result); + 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/contrib/curl/lib/content_encoding.h b/contrib/curl/lib/content_encoding.h index 3fadd28997..1addf230bb 100644 --- a/contrib/curl/lib/content_encoding.h +++ b/contrib/curl/lib/content_encoding.h @@ -7,11 +7,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2011, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -20,29 +20,15 @@ * 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" -/* - * Comma-separated list all supported Content-Encodings ('identity' is implied) - */ -#ifdef HAVE_LIBZ -#define ALL_CONTENT_ENCODINGS "deflate, gzip" -/* force a cleanup */ -void Curl_unencode_cleanup(struct connectdata *conn); -#else -#define ALL_CONTENT_ENCODINGS "identity" -#define Curl_unencode_cleanup(x) Curl_nop_stmt -#endif - -CURLcode Curl_unencode_deflate_write(struct connectdata *conn, - struct SingleRequest *req, - ssize_t nread); - -CURLcode -Curl_unencode_gzip_write(struct connectdata *conn, - struct SingleRequest *k, - ssize_t nread); +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/contrib/curl/lib/cookie.c b/contrib/curl/lib/cookie.c index 946284331d..36d1147b5d 100644 --- a/contrib/curl/lib/cookie.c +++ b/contrib/curl/lib/cookie.c @@ -5,11 +5,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2017, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -18,6 +18,8 @@ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY * KIND, either express or implied. * + * SPDX-License-Identifier: curl + * ***************************************************************************/ /*** @@ -33,14 +35,15 @@ struct CookieInfo *Curl_cookie_init(struct Curl_easy *data, called before any cookies are set. struct Cookie *Curl_cookie_add(struct Curl_easy *data, - struct CookieInfo *c, bool httpheader, char *lineptr, - const char *domain, const char *path); + 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 superceeds. + line supersedes. It may remove lines that are expired. @@ -58,7 +61,7 @@ struct Cookies *Curl_cookie_getlist(struct CookieInfo *cookie, boolean informs the cookie if a secure connection is achieved or not. - It shall only return cookies that haven't expired. + It shall only return cookies that have not expired. Example set of cookies: @@ -84,56 +87,60 @@ Example set of cookies: #if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_COOKIES) -#ifdef USE_LIBPSL -# include -#endif - #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 "inet_pton.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->expirestr); free(co->domain); free(co->path); free(co->spath); free(co->name); free(co->value); - free(co->maxage); - free(co->version); free(co); } -static bool tailmatch(const char *cooke_domain, const char *hostname) +static bool cookie_tailmatch(const char *cookie_domain, + size_t cookie_domain_len, + const char *hostname) { - size_t cookie_domain_len = strlen(cooke_domain); size_t hostname_len = strlen(hostname); if(hostname_len < cookie_domain_len) return FALSE; - if(!strcasecompare(cooke_domain, hostname+hostname_len-cookie_domain_len)) + 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. + /* + * 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; @@ -143,7 +150,7 @@ static bool tailmatch(const char *cooke_domain, const char *hostname) } /* - * matching cookie path and url path + * 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) @@ -170,19 +177,19 @@ static bool pathmatch(const char *cookie_path, const char *request_uri) /* #-fragments are already cut off! */ if(0 == strlen(uri_path) || uri_path[0] != '/') { - free(uri_path); - uri_path = strdup("/"); + 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 /). + /* + * 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); @@ -204,7 +211,7 @@ static bool pathmatch(const char *cookie_path, const char *request_uri) goto pathmatched; } - /* here, cookie_path_len < url_path_len */ + /* here, cookie_path_len < uri_path_len */ if(uri_path[cookie_path_len] == '/') { ret = TRUE; goto pathmatched; @@ -217,6 +224,71 @@ static bool pathmatch(const char *cookie_path, const char *request_uri) 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) { + size_t j = (size_t)Curl_raw_toupper(*domain++); + h += h << 5; + h ^= j; + } + + 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 */ @@ -230,19 +302,17 @@ static char *sanitize_cookie_path(const char *cookie_path) /* some stupid site sends path attribute with '"'. */ len = strlen(new_path); if(new_path[0] == '\"') { - memmove((void *)new_path, (const void *)(new_path + 1), len); + memmove(new_path, new_path + 1, len); len--; } if(len && (new_path[len - 1] == '\"')) { - new_path[len - 1] = 0x0; - len--; + new_path[--len] = 0x0; } /* RFC6265 5.2.4 The Path Attribute */ if(new_path[0] != '/') { /* Let cookie-path be the default-path. */ - free(new_path); - new_path = strdup("/"); + strstore(&new_path, "/", 1); return new_path; } @@ -261,389 +331,501 @@ static char *sanitize_cookie_path(const char *cookie_path) */ void Curl_cookie_loadfiles(struct Curl_easy *data) { - struct curl_slist *list = data->change.cookielist; + 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); + 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 + /* + * 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\n", list->data); + infof(data, "ignoring failed cookie_init for %s", list->data); else data->cookies = newcookies; list = list->next; } - curl_slist_free_all(data->change.cookielist); /* clean up list */ - data->change.cookielist = NULL; /* don't do this again! */ Curl_share_unlock(data, CURL_LOCK_DATA_COOKIE); } } /* - * strstore() makes a strdup() on the 'newstr' and if '*str' is non-NULL - * that will be freed before the allocated string is stored there. + * strstore * - * It is meant to easily replace strdup() + * 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) +static void strstore(char **str, const char *newstr, size_t len) { + DEBUGASSERT(newstr); + DEBUGASSERT(str); free(*str); - *str = strdup(newstr); + *str = Curl_memdup0(newstr, len); } /* - * remove_expired() removes expired cookies. + * 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, *pv; + struct Cookie *co, *nx; curl_off_t now = (curl_off_t)time(NULL); - - co = cookies->cookies; - pv = NULL; - while(co) { - nx = co->next; - if(co->expires && co->expires < now) { - if(co == cookies->cookies) { - cookies->cookies = co->next; + 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 will not 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 { - pv->next = co->next; + /* + * If this cookie has an expiration timestamp earlier than what we have + * 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; } - cookies->numcookies--; - freecookie(co); + co = nx; } - else { - pv = co; - } - co = nx; } } -/* - * Return true if the given string is an IP(v4|v6) address. - */ -static bool isip(const char *domain) +#ifndef USE_LIBPSL +/* Make sure domain contains a dot or is localhost. */ +static bool bad_domain(const char *domain, size_t len) { - struct in_addr addr; -#ifdef ENABLE_IPV6 - struct in6_addr addr6; + 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; +} #endif - if(Curl_inet_pton(AF_INET, domain, &addr) -#ifdef ENABLE_IPV6 - || Curl_inet_pton(AF_INET6, domain, &addr6) -#endif - ) { - /* domain name given as IP address */ - return TRUE; - } +/* + RFC 6265 section 4.1.1 says a server should accept this range: - return FALSE; + 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. +/* + * Curl_cookie_add * - * Be aware that sometimes we get an IP-only host name, and that might also be - * a numerical IPv6 address. + * Add a single cookie line to the cookie keeping object. Be aware that + * sometimes we get an IP-only hostname, 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, - /* The 'data' pointer here may be NULL at times, and thus - must only be used very carefully for things that can deal - with data being NULL. Such as infof() and similar */ - struct CookieInfo *c, bool httpheader, /* TRUE if HTTP header-style line */ - char *lineptr, /* first character of the 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, + 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; - char name[MAX_NAME]; struct Cookie *co; - struct Cookie *lastc=NULL; + 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; -#ifdef USE_LIBPSL - const psl_ctx_t *psl; -#endif - -#ifdef CURL_DISABLE_VERBOSE_STRINGS - (void)data; -#endif + 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 */ + return NULL; /* bail out if we are this low on memory */ if(httpheader) { - /* This line was read off a HTTP-header */ + /* This line was read off an HTTP-header */ const char *ptr; - const char *semiptr; - char *what; - what = malloc(MAX_COOKIE_LINE); - if(!what) { + size_t linelength = strlen(lineptr); + if(linelength > MAX_COOKIE_LINE) { + /* discard overly long lines at once */ free(co); return NULL; } - semiptr=strchr(lineptr, ';'); /* first, find a semicolon */ - - while(*lineptr && ISBLANK(*lineptr)) - lineptr++; - ptr = lineptr; do { - /* we have a = pair or a stand-alone word here */ - name[0]=what[0]=0; /* init the buffers */ - if(1 <= sscanf(ptr, "%" MAX_NAME_TXT "[^;\r\n=] =%" - MAX_COOKIE_LINE_TXT "[^;\r\n]", - name, what)) { - /* 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. */ - const char *whatptr; + size_t vlen; + size_t nlen; + + while(*ptr && ISBLANK(*ptr)) + ptr++; + + /* we have a = pair or a stand-alone word here */ + nlen = strcspn(ptr, ";\t\r\n="); + if(nlen) { bool done = FALSE; - bool sep; - size_t len=strlen(what); - size_t nlen = strlen(name); - const char *endofn = &ptr[ nlen ]; - - /* name ends with a '=' ? */ - sep = (*endofn == '=')?TRUE:FALSE; - - if(nlen) { - endofn--; /* move to the last character */ - if(ISBLANK(*endofn)) { - /* skip trailing spaces in name */ - while(*endofn && ISBLANK(*endofn) && nlen) { - endofn--; - nlen--; - } - name[nlen]=0; /* new end of name */ + 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; + } - /* Strip off trailing whitespace from the 'what' */ - while(len && ISBLANK(what[len-1])) { - what[len-1]=0; - len--; + /* + * 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; } - /* Skip leading whitespace from the 'what' */ - whatptr=what; - while(*whatptr && ISBLANK(*whatptr)) - whatptr++; + /* + * 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 && sep) { + if(!co->name) { /* The very first name/value pair is the actual cookie name */ - co->name = strdup(name); - co->value = strdup(whatptr); + 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(!len) { - /* this was a "=" with no content, and we must allow - 'secure' and 'httponly' specified this weirdly */ + else if(!vlen) { + /* + * this was a "=" with no content, and we must allow + * 'secure' and 'httponly' specified this weirdly + */ done = TRUE; - if(strcasecompare("secure", name)) - co->secure = TRUE; - else if(strcasecompare("httponly", name)) + /* + * 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 */ + /* there was a '=' so we are not done parsing this field */ done = FALSE; } if(done) ; - else if(strcasecompare("path", name)) { - strstore(&co->path, whatptr); + 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(strcasecompare("domain", name)) { + 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. */ + /* + * 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('.' == whatptr[0]) - whatptr++; /* ignore preceding dot */ + 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 + * Without PSL we do not 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". + * dot OR the exact hostname being "localhost". */ - { - const char *dotp; - /* check for more dots */ - dotp = strchr(whatptr, '.'); - if(!dotp && !strcasecompare("localhost", whatptr)) - domain=":"; - } + if(bad_domain(valuep, vlen)) + domain = ":"; #endif - is_ip = isip(domain ? domain : whatptr); + is_ip = Curl_host_is_ipnum(domain ? domain : valuep); if(!domain - || (is_ip && !strcmp(whatptr, domain)) - || (!is_ip && tailmatch(whatptr, domain))) { - strstore(&co->domain, whatptr); + || (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 */ + 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\n", - whatptr); - } - } - else if(strcasecompare("version", name)) { - strstore(&co->version, whatptr); - if(!co->version) { + /* + * 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; - break; + infof(data, "skipped cookie with bad tailmatch domain: %s", + valuep); } } - else if(strcasecompare("max-age", name)) { - /* 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. - - */ - strstore(&co->maxage, whatptr); - if(!co->maxage) { - badcookie = TRUE; + 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(strcasecompare("expires", name)) { - strstore(&co->expirestr, whatptr); - if(!co->expirestr) { - badcookie = TRUE; - 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 the second (or more) name we do not know about! + */ } else { /* this is an "illegal" = pair */ } - if(!semiptr || !*semiptr) { - /* we already know there are no more cookies */ - semiptr = NULL; - continue; - } - - ptr=semiptr+1; while(*ptr && ISBLANK(*ptr)) ptr++; - semiptr=strchr(ptr, ';'); /* now, find the next semicolon */ - - if(!semiptr && *ptr) - /* There are no more semicolons, but there's a final name=value pair - coming up */ - semiptr=strchr(ptr, '\0'); - } while(semiptr); - - if(co->maxage) { - co->expires = - curlx_strtoofft((*co->maxage=='\"')? - &co->maxage[1]:&co->maxage[0], NULL, 10); - if(CURL_OFF_T_MAX - now < co->expires) - /* avoid overflow */ - co->expires = CURL_OFF_T_MAX; + if(*ptr == ';') + ptr++; else - co->expires += now; - } - else if(co->expirestr) { - /* Note that if the date couldn't get parsed for whatever reason, - the cookie will be treated as a session cookie */ - co->expires = curl_getdate(co->expirestr, NULL); - - /* 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; - } + break; + } while(1); if(!badcookie && !co->domain) { if(domain) { /* no domain was given in the header line, set the default */ - co->domain=strdup(domain); + 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. */ + /* + * 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 */ + /* + * 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, '/', (size_t)(queryp - path)); + endslash = memrchr(path, '/', (queryp - path)); if(endslash) { - size_t pathlen = (size_t)(endslash-path+1); /* include ending slash */ - co->path=malloc(pathlen+1); /* one extra for the zero byte */ + size_t pathlen = (endslash-path + 1); /* include end slash */ + co->path = Curl_memdup0(path, pathlen); if(co->path) { - memcpy(co->path, path, pathlen); - co->path[pathlen]=0; /* zero terminate */ co->spath = sanitize_cookie_path(co->path); if(!co->spath) badcookie = TRUE; /* out of memory bad */ @@ -653,55 +835,61 @@ Curl_cookie_add(struct Curl_easy *data, } } - free(what); - + /* + * If we did not get a cookie name, or a bad one, the this is an illegal + * line so bail out. + */ if(badcookie || !co->name) { - /* we didn't get a cookie name or a bad one, - this is an illegal line, bail out */ freecookie(co); return NULL; } - + data->req.setcookies++; } else { - /* This line is NOT a HTTP header style line, we do offer support for - reading the odd netscape cookies-file format here */ + /* + * 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; + 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.. - */ + /* + * 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 */ + /* do not even try the comments */ free(co); return NULL; } /* strip off the possible end-of-line characters */ - ptr=strchr(lineptr, '\r'); + ptr = strchr(lineptr, '\r'); if(ptr) - *ptr=0; /* clear it */ - ptr=strchr(lineptr, '\n'); + *ptr = 0; /* clear it */ + ptr = strchr(lineptr, '\n'); if(ptr) - *ptr=0; /* clear it */ + *ptr = 0; /* clear it */ - firstptr=strtok_r(lineptr, "\t", &tok_buf); /* tokenize it on the TAB */ + 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++) { + /* + * Now loop through the fields and init the struct we already have + * allocated + */ + fields = 0; + for(ptr = firstptr; ptr && !badcookie; + ptr = strtok_r(NULL, "\t", &tok_buf), fields++) { switch(fields) { case 0: if(ptr[0]=='.') /* skip preceding dots */ @@ -711,24 +899,17 @@ Curl_cookie_add(struct Curl_easy *data, badcookie = TRUE; break; case 1: - /* This field got its explanation on the 23rd of May 2001 by - Andrés Garcķa: - - flag: A TRUE/FALSE value indicating if all machines within a given - domain can access the variable. This value is set automatically by - the browser, depending on the value you set for the domain. - - As far as I can see, it is set to 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; + /* + * 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: - /* It turns out, that sometimes the file format allows the path - field to remain not filled in, we try to detect this and work - around it! Andrés Garcķa made us aware of this... */ + /* 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! */ + /* only if the path does not look like a boolean option! */ co->path = strdup(ptr); if(!co->path) badcookie = TRUE; @@ -740,7 +921,7 @@ Curl_cookie_add(struct Curl_easy *data, } break; } - /* this doesn't look like a path, make one up! */ + /* this does not look like a path, make one up! */ co->path = strdup("/"); if(!co->path) badcookie = TRUE; @@ -748,17 +929,31 @@ Curl_cookie_add(struct Curl_easy *data, if(!co->spath) badcookie = TRUE; fields++; /* add a field and fall down to secure */ - /* FALLTHROUGH */ + FALLTHROUGH(); case 3: - co->secure = strcasecompare(ptr, "TRUE")?TRUE:FALSE; + co->secure = FALSE; + if(strcasecompare(ptr, "TRUE")) { + if(secure || c->running) + co->secure = TRUE; + else + badcookie = TRUE; + } break; case 4: - co->expires = curlx_strtoofft(ptr, NULL, 10); + 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); @@ -787,48 +982,138 @@ Curl_cookie_add(struct Curl_easy *data, } + 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! */ + !co->expires) { /* this is a session cookie since it does not 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 - superceeds an already existing cookie, which it may if the previous have - the same domain and path as this */ + /* + * 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 */ - remove_expired(c); + if(!noexpire) + remove_expired(c); #ifdef USE_LIBPSL - /* Check if the domain is a Public Suffix and if yes, ignore the cookie. - This needs a libpsl compiled with builtin data. */ - if(domain && co->domain && !isip(co->domain)) { - psl = psl_builtin(); - if(psl && !psl_is_cookie_domain_acceptable(psl, domain, co->domain)) { - infof(data, - "cookie '%s' dropped, domain '%s' must not set cookies for '%s'\n", - co->name, domain, co->domain); + /* + * Check if the domain is a Public Suffix and if yes, ignore the cookie. We + * must also check that the data handle is not NULL since the psl code will + * dereference it. + */ + DEBUGF(infof(data, "PSL check set-cookie '%s' for domain=%s in %s", + co->name, co->domain, domain)); + if(data && (domain && co->domain && !Curl_host_is_ipnum(co->domain))) { + 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 + infof(data, "libpsl problem, rejecting cookie for satety"); + } + + if(!acceptable) { + infof(data, "cookie '%s' dropped, domain '%s' must not " + "set cookies for '%s'", co->name, domain, co->domain); freecookie(co); return NULL; } } +#else + DEBUGF(infof(data, "NO PSL to check set-cookie '%s' for domain=%s in %s", + co->name, co->domain, domain)); #endif - clist = c->cookies; - replace_old = FALSE; + /* 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; + replace_old = TRUE; } else if(!clist->domain && !co->domain) replace_old = TRUE; @@ -836,67 +1121,57 @@ Curl_cookie_add(struct Curl_easy *data, if(replace_old) { /* the domains were identical */ - if(clist->spath && co->spath) { - if(strcasecompare(clist->spath, co->spath)) { - replace_old = TRUE; - } - else - replace_old = FALSE; - } - else if(!clist->spath && !co->spath) - replace_old = TRUE; - else + 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 isn't "live" and thus only read from a file. We let - live cookies stay alive */ - - /* Free the newcomer and get out of here! */ + /* + * 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 is not "live". "live" cookies are + * preferred so the new cookie is freed. + */ freecookie(co); return NULL; } - if(replace_old) { - co->next = clist->next; /* get the next-pointer first */ - - /* then free all the old pointers */ - free(clist->name); - free(clist->value); - free(clist->domain); - free(clist->path); - free(clist->spath); - free(clist->expirestr); - free(clist->version); - free(clist->maxage); - - *clist = *co; /* then store all the new data */ - - free(co); /* free the newly alloced memory */ - co = clist; /* point to the previous struct instead */ - - /* We have replaced a cookie, now skip the rest of the list but - make sure the 'lastc' pointer is properly set */ - do { - lastc = clist; - clist = clist->next; - } while(clist); - break; + 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 "\n", - replace_old?"Replaced":"Added", co->name, co->value, + "expire %" FMT_OFF_T, + replace_old ? "Replaced":"Added", co->name, co->value, co->domain, co->path, co->expires); if(!replace_old) { @@ -904,135 +1179,117 @@ Curl_cookie_add(struct Curl_easy *data, if(lastc) lastc->next = co; else - c->cookies = co; + c->cookies[myhash] = co; c->numcookies++; /* one more cookie in the jar */ } - return co; -} + /* + * Now that we have 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; -/* - * get_line() makes sure to only return complete whole lines that fit in 'len' - * bytes and end with a newline. - */ -static char *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 && (b[rlen-1] == '\n')) { - if(partial) { - partial = FALSE; - continue; - } - return b; - } - else - /* read a partial, discard the next piece that ends with newline */ - partial = TRUE; - } - else - break; - } - return NULL; + 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. + * 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; - FILE *fp = NULL; - bool fromfile=TRUE; - char *line = NULL; + FILE *handle = NULL; - if(NULL == inc) { - /* we didn't get a struct, create one */ + if(!inc) { + /* we did not get a struct, create one */ c = calloc(1, sizeof(struct CookieInfo)); if(!c) return NULL; /* failed to get memory */ - c->filename = strdup(file?file:"none"); /* copy the name just in case */ - if(!c->filename) - goto fail; /* failed to get memory */ + /* + * Initialize the next_expiration time to signal that we do not have enough + * information yet. + */ + c->next_expiration = CURL_OFF_T_MAX; } else { /* we got an already existing one, use that */ c = inc; } - c->running = FALSE; /* this is not running, this is init */ - - if(file && !strcmp(file, "-")) { - fp = stdin; - fromfile=FALSE; - } - else if(file && !*file) { - /* points to a "" string */ - fp = NULL; - } - else - fp = file?fopen(file, FOPEN_READTEXT):NULL; - c->newsession = newsession; /* new session? */ - if(fp) { - char *lineptr; - bool headerline; - - line = malloc(MAX_COOKIE_LINE); - if(!line) - goto fail; - while(get_line(line, MAX_COOKIE_LINE, fp)) { - if(checkprefix("Set-Cookie:", line)) { - /* This is a cookie line, get it! */ - lineptr=&line[11]; - headerline=TRUE; - } + if(data) { + FILE *fp = NULL; + if(file && *file) { + if(!strcmp(file, "-")) + fp = stdin; else { - lineptr=line; - headerline=FALSE; + fp = fopen(file, "rb"); + if(!fp) + infof(data, "WARNING: failed to open cookie file \"%s\"", file); + else + handle = fp; } - while(*lineptr && ISBLANK(*lineptr)) - lineptr++; - - Curl_cookie_add(data, c, headerline, lineptr, NULL, NULL); } - free(line); /* free the line buffer */ - if(fromfile) - fclose(fp); - } + c->running = FALSE; /* this is not running, this is init */ + if(fp) { + struct dynbuf buf; + Curl_dyn_init(&buf, MAX_COOKIE_LINE); + while(Curl_get_line(&buf, fp)) { + char *lineptr = Curl_dyn_ptr(&buf); + bool headerline = FALSE; + if(checkprefix("Set-Cookie:", lineptr)) { + /* This is a cookie line, get it! */ + lineptr += 11; + headerline = TRUE; + while(*lineptr && ISBLANK(*lineptr)) + lineptr++; + } - c->running = TRUE; /* now, we're running */ + Curl_cookie_add(data, c, headerline, TRUE, lineptr, NULL, NULL, TRUE); + } + Curl_dyn_free(&buf); /* free the line buffer */ - return c; + /* + * 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); -fail: - free(line); - if(!inc) - /* Only clean up if we allocated it here, as the original could still be in - * use by a share handle */ - Curl_cookie_cleanup(c); - if(fromfile && fp) - fclose(fp); - return NULL; /* out of memory */ + if(handle) + fclose(handle); + } + data->state.cookie_engine = TRUE; + } + c->running = TRUE; /* now, we are running */ + + return c; } -/* sort this so that the longest path gets before the shorter path */ +/* + * 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; @@ -1053,12 +1310,28 @@ static int cookie_sort(const void *p1, const void *p2) if(l1 != l2) return (l2 > l1) ? 1 : -1 ; /* avoid size_t <=> int conversions */ - /* 3 - compare cookie names */ - if(c1->name && c2->name) - return strcmp(c1->name, c2->name); + /* 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; - /* sorry, can't be more deterministic */ - return 0; + return (c2->creationtime > c1->creationtime) ? 1 : -1; } #define CLONE(field) \ @@ -1072,83 +1345,83 @@ static int cookie_sort(const void *p1, const void *p2) static struct Cookie *dup_cookie(struct Cookie *src) { - struct Cookie *d = calloc(sizeof(struct Cookie), 1); + struct Cookie *d = calloc(1, sizeof(struct Cookie)); if(d) { - CLONE(expirestr); CLONE(domain); CLONE(path); CLONE(spath); CLONE(name); CLONE(value); - CLONE(maxage); - CLONE(version); 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: +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. +/* + * Curl_cookie_getlist * - * It shall only return cookies that haven't expired. + * 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. * - ****************************************************************************/ - -struct Cookie *Curl_cookie_getlist(struct CookieInfo *c, + * It shall only return cookies that have not 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; - time_t now = time(NULL); - struct Cookie *mainco=NULL; + struct Cookie *mainco = NULL; size_t matches = 0; bool is_ip; + const size_t myhash = cookiehash(host); - if(!c || !c->cookies) + 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 = isip(host); + is_ip = Curl_host_is_ipnum(host); - co = c->cookies; + co = c->cookies[myhash]; while(co) { - /* only process this cookie if it is not expired or had no expire - date AND that if the cookie requires we're secure we must only - continue if we are! */ - if((!co->expires || (co->expires > now)) && - (co->secure?secure:TRUE)) { + /* if the cookie requires we are 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 && tailmatch(co->domain, host)) || + (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 */ + /* + * 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 */ + /* + * 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 */ + /* + * 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) { @@ -1159,13 +1432,14 @@ struct Cookie *Curl_cookie_getlist(struct CookieInfo *c, mainco = newco; matches++; + if(matches >= MAX_COOKIE_SEND_AMOUNT) { + infof(data, "Included max number of cookies (%zu) in request!", + matches); + break; + } } - else { - fail: - /* failure, clear up the allocated chain and return NULL */ - Curl_cookie_freelist(mainco); - return NULL; - } + else + goto fail; } } } @@ -1173,9 +1447,11 @@ struct Cookie *Curl_cookie_getlist(struct CookieInfo *c, } 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. */ + /* + * 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; @@ -1186,7 +1462,7 @@ struct Cookie *Curl_cookie_getlist(struct CookieInfo *c, co = mainco; - for(i=0; co; co = co->next) + for(i = 0; co; co = co->next) array[i++] = co; /* now sort the cookie pointers in path length order */ @@ -1195,40 +1471,43 @@ struct Cookie *Curl_cookie_getlist(struct CookieInfo *c, /* remake the linked list order according to the new order */ mainco = array[0]; /* start here */ - for(i=0; inext = array[i+1]; + 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() +/* + * Curl_cookie_clearall * * Clear all existing cookies and reset the counter. - * - ****************************************************************************/ + */ void Curl_cookie_clearall(struct CookieInfo *cookies) { if(cookies) { - Curl_cookie_freelist(cookies->cookies); - cookies->cookies = NULL; + 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() +/* + * Curl_cookie_freelist * * Free a list of cookies previously returned by Curl_cookie_getlist(); - * - ****************************************************************************/ - + */ void Curl_cookie_freelist(struct Cookie *co) { struct Cookie *next; @@ -1239,67 +1518,69 @@ void Curl_cookie_freelist(struct Cookie *co) } } - -/***************************************************************************** - * - * Curl_cookie_clearsess() +/* + * 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 || !cookies->cookies) + if(!cookies) return; - first = curr = prev = cookies->cookies; + for(i = 0; i < COOKIE_HASH_SIZE; i++) { + if(!cookies->cookies[i]) + continue; - for(; curr; curr = next) { - next = curr->next; - if(!curr->expires) { - if(first == curr) - first = next; + first = curr = prev = cookies->cookies[i]; - if(prev == curr) - prev = next; - else - prev->next = next; + for(; curr; curr = next) { + next = curr->next; + if(!curr->expires) { + if(first == curr) + first = next; - freecookie(curr); - cookies->numcookies--; + if(prev == curr) + prev = next; + else + prev->next = next; + + freecookie(curr); + cookies->numcookies--; + } + else + prev = curr; } - else - prev = curr; - } - cookies->cookies = first; + 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) { - free(c->filename); - Curl_cookie_freelist(c->cookies); + 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() +/* + * 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. Has to be free()d -*/ + * 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( @@ -1308,141 +1589,187 @@ static char *get_netscape_format(const struct Cookie *co) "%s\t" /* tailmatch */ "%s\t" /* path */ "%s\t" /* secure */ - "%" CURL_FORMAT_CURL_OFF_T "\t" /* expires */ + "%" FMT_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->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:""); + co->value ? co->value : ""); } /* * cookie_output() * * Writes all internally known cookies to the specified file. Specify - * "-" as file name to write to stdout. + * "-" as filename to write to stdout. * * The function returns non-zero on write failure. */ -static int cookie_output(struct CookieInfo *c, const char *dumphere) +static CURLcode cookie_output(struct Curl_easy *data, + struct CookieInfo *c, const char *filename) { struct Cookie *co; - FILE *out; - bool use_stdout=FALSE; - char *format_ptr; + FILE *out = NULL; + bool use_stdout = FALSE; + char *tempstore = NULL; + CURLcode error = CURLE_OK; - if((NULL == c) || (0 == c->numcookies)) - /* If there are no known cookies, we don't write or even create any - destination file */ - return 0; + if(!c) + /* no cookie engine alive */ + return CURLE_OK; /* at first, remove expired cookies */ remove_expired(c); - if(!strcmp("-", dumphere)) { + if(!strcmp("-", filename)) { /* use stdout */ out = stdout; - use_stdout=TRUE; + use_stdout = TRUE; } else { - out = fopen(dumphere, FOPEN_WRITETEXT); - if(!out) - return 1; /* failure */ + error = Curl_fopen(data, filename, &out, &tempstore); + if(error) + goto error; } fputs("# Netscape HTTP Cookie File\n" - "# https://curl.haxx.se/docs/http-cookies.html\n" + "# https://curl.se/docs/http-cookies.html\n" "# This file was generated by libcurl! Edit at your own risk.\n\n", out); - for(co = c->cookies; co; co = co->next) { - if(!co->domain) - continue; - format_ptr = get_netscape_format(co); - if(format_ptr == NULL) { - fprintf(out, "#\n# Fatal libcurl error\n"); - if(!use_stdout) - fclose(out); - return 1; + 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; + } } - fprintf(out, "%s\n", format_ptr); - free(format_ptr); + + 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) + 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; - return 0; +error: + if(out && !use_stdout) + fclose(out); + free(tempstore); + return error; } -struct curl_slist *Curl_cookie_list(struct Curl_easy *data) +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 == NULL) || - (data->cookies->numcookies == 0)) + if(!data->cookies || (data->cookies->numcookies == 0)) return NULL; - for(c = data->cookies->cookies; 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; + 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; } - list = beg; } return list; } -void Curl_flush_cookies(struct Curl_easy *data, int cleanup) +struct curl_slist *Curl_cookie_list(struct Curl_easy *data) { - if(data->set.str[STRING_COOKIEJAR]) { - if(data->change.cookielist) { - /* If there is a list of cookie files to read, do it first so that - we have all the told files read before we write the new jar. - Curl_cookie_loadfiles() LOCKS and UNLOCKS the share itself! */ - Curl_cookie_loadfiles(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 */ - if(cookie_output(data->cookies, data->set.str[STRING_COOKIEJAR])) - infof(data, "WARNING: failed to save cookies in %s\n", - data->set.str[STRING_COOKIEJAR]); + 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 { - if(cleanup && data->change.cookielist) { - /* since nothing is written, we can just free the list of cookie file - names */ - curl_slist_free_all(data->change.cookielist); /* clean up list */ - data->change.cookielist = NULL; - } 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); } diff --git a/contrib/curl/lib/cookie.h b/contrib/curl/lib/cookie.h index a9a45785c6..838d74d82f 100644 --- a/contrib/curl/lib/cookie.h +++ b/contrib/curl/lib/cookie.h @@ -7,11 +7,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2016, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -20,6 +20,8 @@ * 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" @@ -33,42 +35,70 @@ struct Cookie { char *spath; /* sanitized cookie path */ char *domain; /* domain = */ curl_off_t expires; /* expires = */ - char *expirestr; /* the plain text version */ - bool tailmatch; /* weather we do tail-matchning of the domain name */ - - /* RFC 2109 keywords. Version=1 means 2109-compliant cookie sending */ - char *version; /* Version = */ - char *maxage; /* Max-Age = */ - + 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; - - char *filename; /* file we read from/write to */ + 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 */ - long numcookies; /* number of cookies in the "jar" */ bool newsession; /* new session, discard session cookies on load */ }; -/* This is the maximum line length we accept for a cookie line. RFC 2109 - section 6.3 says: - - "at least 4096 bytes per cookie (as measured by the size of the characters - that comprise the cookie non-terminal in the syntax description of the - Set-Cookie header)" +/* 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 an HTTP header + or from a cookie jar */ #define MAX_COOKIE_LINE 5000 -#define MAX_COOKIE_LINE_TXT "4999" -/* This is the maximum length of a cookie name we deal with: */ -#define MAX_NAME 1024 -#define MAX_NAME_TXT "1023" +/* 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; /* @@ -77,11 +107,14 @@ struct Curl_easy; */ struct Cookie *Curl_cookie_add(struct Curl_easy *data, - struct CookieInfo *, bool header, char *lineptr, - const char *domain, const char *path); + struct CookieInfo *c, bool header, + bool noexpiry, const char *lineptr, + const char *domain, const char *path, + bool secure); -struct Cookie *Curl_cookie_getlist(struct CookieInfo *, const char *, - const char *, bool); +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); @@ -93,10 +126,11 @@ void Curl_cookie_clearsess(struct CookieInfo *cookies); #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, int cleanup); -void Curl_cookie_cleanup(struct CookieInfo *); +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 *, struct CookieInfo *, bool); + 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 diff --git a/contrib/curl/lib/curl_addrinfo.c b/contrib/curl/lib/curl_addrinfo.c index 3dbfb3ebf4..44e10e9c9a 100644 --- a/contrib/curl/lib/curl_addrinfo.c +++ b/contrib/curl/lib/curl_addrinfo.c @@ -5,11 +5,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2017, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -18,6 +18,8 @@ * 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" @@ -27,6 +29,9 @@ #ifdef HAVE_NETINET_IN_H # include #endif +#ifdef HAVE_NETINET_IN6_H +# include +#endif #ifdef HAVE_NETDB_H # include #endif @@ -42,11 +47,6 @@ # include #endif -#if defined(NETWARE) && defined(__NOVELL_LIBC__) -# undef in_addr_t -# define in_addr_t unsigned long -#endif - #include #include "curl_addrinfo.h" @@ -75,16 +75,13 @@ #endif void -Curl_freeaddrinfo(Curl_addrinfo *cahead) +Curl_freeaddrinfo(struct Curl_addrinfo *cahead) { - Curl_addrinfo *vqualifier canext; - Curl_addrinfo *ca; + struct Curl_addrinfo *vqualifier canext; + struct Curl_addrinfo *ca; - for(ca = cahead; ca != NULL; ca = canext) { - free(ca->ai_addr); - free(ca->ai_canonname); + for(ca = cahead; ca; ca = canext) { canext = ca->ai_next; - free(ca); } } @@ -98,7 +95,7 @@ Curl_freeaddrinfo(Curl_addrinfo *cahead) * 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 + * 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 @@ -109,13 +106,13 @@ int Curl_getaddrinfo_ex(const char *nodename, const char *servname, const struct addrinfo *hints, - Curl_addrinfo **result) + struct Curl_addrinfo **result) { const struct addrinfo *ai; struct addrinfo *aihead; - Curl_addrinfo *cafirst = NULL; - Curl_addrinfo *calast = NULL; - Curl_addrinfo *ca; + struct Curl_addrinfo *cafirst = NULL; + struct Curl_addrinfo *calast = NULL; + struct Curl_addrinfo *ca; size_t ss_size; int error; @@ -128,12 +125,12 @@ Curl_getaddrinfo_ex(const char *nodename, /* 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 +#ifdef USE_IPV6 else if(ai->ai_family == AF_INET6) ss_size = sizeof(struct sockaddr_in6); #endif @@ -141,14 +138,14 @@ Curl_getaddrinfo_ex(const char *nodename, continue; /* ignore elements without required address info */ - if((ai->ai_addr == NULL) || !(ai->ai_addrlen > 0)) + 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(Curl_addrinfo)); + ca = malloc(sizeof(struct Curl_addrinfo) + ss_size + namelen); if(!ca) { error = EAI_MEMORY; break; @@ -166,22 +163,12 @@ Curl_getaddrinfo_ex(const char *nodename, ca->ai_canonname = NULL; ca->ai_next = NULL; - ca->ai_addr = malloc(ss_size); - if(!ca->ai_addr) { - error = EAI_MEMORY; - free(ca); - break; - } + ca->ai_addr = (void *)((char *)ca + sizeof(struct Curl_addrinfo)); memcpy(ca->ai_addr, ai->ai_addr, ss_size); - if(ai->ai_canonname != NULL) { - ca->ai_canonname = strdup(ai->ai_canonname); - if(!ca->ai_canonname) { - error = EAI_MEMORY; - free(ca->ai_addr); - free(ca); - break; - } + 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 */ @@ -234,7 +221,7 @@ Curl_getaddrinfo_ex(const char *nodename, * 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 + * 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" @@ -249,7 +236,6 @@ Curl_getaddrinfo_ex(const char *nodename, * struct sockaddr *ai_addr; * struct Curl_addrinfo *ai_next; * }; - * typedef struct Curl_addrinfo Curl_addrinfo; * * hostent defined in * @@ -266,14 +252,14 @@ Curl_getaddrinfo_ex(const char *nodename, * #define h_addr h_addr_list[0] */ -Curl_addrinfo * +struct Curl_addrinfo * Curl_he2ai(const struct hostent *he, int port) { - Curl_addrinfo *ai; - Curl_addrinfo *prevai = NULL; - Curl_addrinfo *firstai = NULL; + struct Curl_addrinfo *ai; + struct Curl_addrinfo *prevai = NULL; + struct Curl_addrinfo *firstai = NULL; struct sockaddr_in *addr; -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 struct sockaddr_in6 *addr6; #endif CURLcode result = CURLE_OK; @@ -286,34 +272,27 @@ Curl_he2ai(const struct hostent *he, int port) DEBUGASSERT((he->h_name != NULL) && (he->h_addr_list != NULL)); - for(i=0; (curr = he->h_addr_list[i]) != NULL; i++) { - + for(i = 0; (curr = he->h_addr_list[i]) != NULL; i++) { size_t ss_size; -#ifdef ENABLE_IPV6 + size_t namelen = strlen(he->h_name) + 1; /* include null-terminator */ +#ifdef USE_IPV6 if(he->h_addrtype == AF_INET6) ss_size = sizeof(struct sockaddr_in6); else #endif ss_size = sizeof(struct sockaddr_in); - ai = calloc(1, sizeof(Curl_addrinfo)); + /* 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; } - ai->ai_canonname = strdup(he->h_name); - if(!ai->ai_canonname) { - result = CURLE_OUT_OF_MEMORY; - free(ai); - break; - } - ai->ai_addr = calloc(1, ss_size); - if(!ai->ai_addr) { - result = CURLE_OUT_OF_MEMORY; - free(ai->ai_canonname); - free(ai); - 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 */ @@ -338,16 +317,24 @@ Curl_he2ai(const struct hostent *he, int port) addr = (void *)ai->ai_addr; /* storage area for this info */ memcpy(&addr->sin_addr, curr, sizeof(struct in_addr)); - addr->sin_family = (unsigned short)(he->h_addrtype); +#ifdef __MINGW32__ + addr->sin_family = (short)(he->h_addrtype); +#else + addr->sin_family = (CURL_SA_FAMILY_T)(he->h_addrtype); +#endif addr->sin_port = htons((unsigned short)port); break; -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 case AF_INET6: addr6 = (void *)ai->ai_addr; /* storage area for this info */ memcpy(&addr6->sin6_addr, curr, sizeof(struct in6_addr)); - addr6->sin6_family = (unsigned short)(he->h_addrtype); +#ifdef __MINGW32__ + addr6->sin6_family = (short)(he->h_addrtype); +#else + addr6->sin6_family = (CURL_SA_FAMILY_T)(he->h_addrtype); +#endif addr6->sin6_port = htons((unsigned short)port); break; #endif @@ -369,7 +356,7 @@ struct namebuff { struct hostent hostentry; union { struct in_addr ina4; -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 struct in6_addr ina6; #endif } addrentry; @@ -380,16 +367,16 @@ struct namebuff { /* * Curl_ip2addr() * - * This function takes an internet address, in binary form, as input parameter + * 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 */ -Curl_addrinfo * +struct Curl_addrinfo * Curl_ip2addr(int af, const void *inaddr, const char *hostname, int port) { - Curl_addrinfo *ai; + struct Curl_addrinfo *ai; #if defined(__VMS) && \ defined(__INITIAL_POINTER_SIZE) && (__INITIAL_POINTER_SIZE == 64) @@ -422,7 +409,7 @@ Curl_ip2addr(int af, const void *inaddr, const char *hostname, int port) addrentry = (void *)&buf->addrentry.ina4; memcpy(addrentry, inaddr, sizeof(struct in_addr)); break; -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 case AF_INET6: addrsize = sizeof(struct in6_addr); addrentry = (void *)&buf->addrentry.ina6; @@ -462,14 +449,14 @@ Curl_ip2addr(int af, const void *inaddr, const char *hostname, int port) * Given an IPv4 or IPv6 dotted string address, this converts it to a proper * allocated Curl_addrinfo struct and returns it. */ -Curl_addrinfo *Curl_str2addr(char *address, int port) +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 - else { +#ifdef USE_IPV6 + { struct in6_addr in6; if(Curl_inet_pton(AF_INET6, address, &in6) > 0) /* This is a dotted IPv6 address ::1-style */ @@ -485,22 +472,19 @@ Curl_addrinfo *Curl_str2addr(char *address, int port) * struct initialized with this path. * Set '*longpath' to TRUE if the error is a too long path. */ -Curl_addrinfo *Curl_unix2addr(const char *path, bool *longpath, bool abstract) +struct Curl_addrinfo *Curl_unix2addr(const char *path, bool *longpath, + bool abstract) { - Curl_addrinfo *ai; + struct Curl_addrinfo *ai; struct sockaddr_un *sa_un; size_t path_len; *longpath = FALSE; - ai = calloc(1, sizeof(Curl_addrinfo)); + ai = calloc(1, sizeof(struct Curl_addrinfo) + sizeof(struct sockaddr_un)); if(!ai) return NULL; - ai->ai_addr = calloc(1, sizeof(struct sockaddr_un)); - if(!ai->ai_addr) { - free(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; @@ -508,7 +492,6 @@ Curl_addrinfo *Curl_unix2addr(const char *path, bool *longpath, bool abstract) /* 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->ai_addr); free(ai); *longpath = TRUE; return NULL; @@ -529,72 +512,73 @@ Curl_addrinfo *Curl_unix2addr(const char *path, bool *longpath, bool abstract) } #endif -#if defined(CURLDEBUG) && defined(HAVE_FREEADDRINFO) +#if defined(CURLDEBUG) && defined(HAVE_GETADDRINFO) && \ + defined(HAVE_FREEADDRINFO) /* - * curl_dofreeaddrinfo() + * 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 + * require a bunch of structs I did not want to include in memdebug.c */ void -curl_dofreeaddrinfo(struct addrinfo *freethis, - int line, const char *source) +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 - curl_memlog("ADDR %s:%d freeaddrinfo(%p)\n", - source, line, (void *)freethis); } #endif /* defined(CURLDEBUG) && defined(HAVE_FREEADDRINFO) */ #if defined(CURLDEBUG) && defined(HAVE_GETADDRINFO) /* - * curl_dogetaddrinfo() + * 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 + * require a bunch of structs I did not want to include in memdebug.c */ int -curl_dogetaddrinfo(const char *hostname, - const char *service, - const struct addrinfo *hints, - struct addrinfo **result, - int line, const char *source) +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); + int res = lwip_getaddrinfo(hostname, service, hints, result); #else - int res=(getaddrinfo)(hostname, service, hints, result); + int res = (getaddrinfo)(hostname, service, hints, result); #endif if(0 == res) /* success */ - curl_memlog("ADDR %s:%d getaddrinfo() = %p\n", - source, line, (void *)*result); + curl_dbg_log("ADDR %s:%d getaddrinfo() = %p\n", + source, line, (void *)*result); else - curl_memlog("ADDR %s:%d getaddrinfo() failed\n", - source, line); + 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 + * Work-arounds the sin6_port is always zero bug on iOS 9.3.2 and macOS * 10.11.5. */ -void Curl_addrinfo_set_port(Curl_addrinfo *addrinfo, int port) +void Curl_addrinfo_set_port(struct Curl_addrinfo *addrinfo, int port) { - Curl_addrinfo *ca; + struct Curl_addrinfo *ca; struct sockaddr_in *addr; -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 struct sockaddr_in6 *addr6; #endif for(ca = addrinfo; ca != NULL; ca = ca->ai_next) { @@ -604,7 +588,7 @@ void Curl_addrinfo_set_port(Curl_addrinfo *addrinfo, int port) addr->sin_port = htons((unsigned short)port); break; -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 case AF_INET6: addr6 = (void *)ca->ai_addr; /* storage area for this info */ addr6->sin6_port = htons((unsigned short)port); diff --git a/contrib/curl/lib/curl_addrinfo.h b/contrib/curl/lib/curl_addrinfo.h index 8f6f3d1064..9ceac997da 100644 --- a/contrib/curl/lib/curl_addrinfo.h +++ b/contrib/curl/lib/curl_addrinfo.h @@ -7,11 +7,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2016, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -20,6 +20,8 @@ * 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" @@ -40,12 +42,11 @@ # include #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. + * consistent internal handling of this data. We use this even when the system + * provides an addrinfo structure definition. We use this for all sorts of + * IPv4 and IPV6 builds. */ struct Curl_addrinfo { @@ -58,50 +59,47 @@ struct Curl_addrinfo { struct sockaddr *ai_addr; struct Curl_addrinfo *ai_next; }; -typedef struct Curl_addrinfo Curl_addrinfo; void -Curl_freeaddrinfo(Curl_addrinfo *cahead); +Curl_freeaddrinfo(struct Curl_addrinfo *cahead); #ifdef HAVE_GETADDRINFO int Curl_getaddrinfo_ex(const char *nodename, const char *servname, const struct addrinfo *hints, - Curl_addrinfo **result); + struct Curl_addrinfo **result); #endif -Curl_addrinfo * +struct Curl_addrinfo * Curl_he2ai(const struct hostent *he, int port); -Curl_addrinfo * +struct Curl_addrinfo * Curl_ip2addr(int af, const void *inaddr, const char *hostname, int port); -Curl_addrinfo *Curl_str2addr(char *dotted, int port); +struct Curl_addrinfo *Curl_str2addr(char *dotted, int port); #ifdef USE_UNIX_SOCKETS -Curl_addrinfo *Curl_unix2addr(const char *path, bool *longpath, bool abstract); +struct Curl_addrinfo *Curl_unix2addr(const char *path, bool *longpath, + bool abstract); #endif #if defined(CURLDEBUG) && defined(HAVE_GETADDRINFO) && \ defined(HAVE_FREEADDRINFO) void -curl_dofreeaddrinfo(struct addrinfo *freethis, - int line, const char *source); +curl_dbg_freeaddrinfo(struct addrinfo *freethis, int line, const char *source); #endif #if defined(CURLDEBUG) && defined(HAVE_GETADDRINFO) int -curl_dogetaddrinfo(const char *hostname, - const char *service, - const struct addrinfo *hints, - struct addrinfo **result, - int line, const char *source); +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(Curl_addrinfo *addrinfo, int port); +void Curl_addrinfo_set_port(struct Curl_addrinfo *addrinfo, int port); #else #define Curl_addrinfo_set_port(x,y) #endif diff --git a/contrib/curl/lib/curl_base64.h b/contrib/curl/lib/curl_base64.h index 7e9fc26062..7f7cd1d98a 100644 --- a/contrib/curl/lib/curl_base64.h +++ b/contrib/curl/lib/curl_base64.h @@ -7,11 +7,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2014, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -20,16 +20,22 @@ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY * KIND, either express or implied. * + * SPDX-License-Identifier: curl + * ***************************************************************************/ -CURLcode Curl_base64_encode(struct Curl_easy *data, - const char *inputbuff, size_t insize, +#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(struct Curl_easy *data, - const char *inputbuff, size_t insize, +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/contrib/curl/lib/curl_config.h.cmake b/contrib/curl/lib/curl_config.h.cmake index 49c1b8a2d0..dd806da9bc 100644 --- a/contrib/curl/lib/curl_config.h.cmake +++ b/contrib/curl/lib/curl_config.h.cmake @@ -1,71 +1,174 @@ -/* lib/curl_config.h.in. Generated somehow by cmake. */ - -/* when building libcurl itself */ -#cmakedefine BUILDING_LIBCURL 1 +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ /* Location of default ca bundle */ -#cmakedefine CURL_CA_BUNDLE ${CURL_CA_BUNDLE} +#cmakedefine CURL_CA_BUNDLE "${CURL_CA_BUNDLE}" + +/* define "1" to use built-in ca store of TLS backend */ +#cmakedefine CURL_CA_FALLBACK 1 /* Location of default ca path */ -#cmakedefine CURL_CA_PATH ${CURL_CA_PATH} +#cmakedefine CURL_CA_PATH "${CURL_CA_PATH}" + +/* Default SSL backend */ +#cmakedefine CURL_DEFAULT_SSL_BACKEND "${CURL_DEFAULT_SSL_BACKEND}" + +/* disables alt-svc */ +#cmakedefine CURL_DISABLE_ALTSVC 1 -/* to disable cookies support */ +/* disables cookies support */ #cmakedefine CURL_DISABLE_COOKIES 1 -/* to disable cryptographic authentication */ -#cmakedefine CURL_DISABLE_CRYPTO_AUTH 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 -/* to disable DICT */ +/* disables negotiate authentication */ +#cmakedefine CURL_DISABLE_NEGOTIATE_AUTH 1 + +/* disables AWS-SIG4 */ +#cmakedefine CURL_DISABLE_AWS 1 + +/* disables DICT */ #cmakedefine CURL_DISABLE_DICT 1 -/* to disable FILE */ +/* disables DNS-over-HTTPS */ +#cmakedefine CURL_DISABLE_DOH 1 + +/* disables FILE */ #cmakedefine CURL_DISABLE_FILE 1 -/* to disable FTP */ +/* disables form api */ +#cmakedefine CURL_DISABLE_FORM_API 1 + +/* disables FTP */ #cmakedefine CURL_DISABLE_FTP 1 -/* to disable GOPHER */ +/* disables curl_easy_options API for existing options to curl_easy_setopt */ +#cmakedefine CURL_DISABLE_GETOPTIONS 1 + +/* disables GOPHER */ #cmakedefine CURL_DISABLE_GOPHER 1 -/* to disable IMAP */ -#cmakedefine CURL_DISABLE_IMAP 1 +/* disables headers-api support */ +#cmakedefine CURL_DISABLE_HEADERS_API 1 + +/* disables HSTS support */ +#cmakedefine CURL_DISABLE_HSTS 1 -/* to disable HTTP */ +/* disables HTTP */ #cmakedefine CURL_DISABLE_HTTP 1 -/* to disable LDAP */ +/* disabled all HTTP authentication methods */ +#cmakedefine CURL_DISABLE_HTTP_AUTH 1 + +/* disables IMAP */ +#cmakedefine CURL_DISABLE_IMAP 1 + +/* disables LDAP */ #cmakedefine CURL_DISABLE_LDAP 1 -/* to disable LDAPS */ +/* disables LDAPS */ #cmakedefine CURL_DISABLE_LDAPS 1 -/* to disable POP3 */ +/* 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 -/* to disable proxies */ +/* disables built-in progress meter */ +#cmakedefine CURL_DISABLE_PROGRESS_METER 1 + +/* disables proxies */ #cmakedefine CURL_DISABLE_PROXY 1 -/* to disable RTSP */ +/* disables IPFS from the curl tool */ +#cmakedefine CURL_DISABLE_IPFS 1 + +/* disables RTSP */ #cmakedefine CURL_DISABLE_RTSP 1 -/* to disable RTMP */ -#cmakedefine CURL_DISABLE_RTMP 1 +/* disables SHA-512/256 hash algorithm */ +#cmakedefine CURL_DISABLE_SHA512_256 1 + +/* disabled shuffle DNS feature */ +#cmakedefine CURL_DISABLE_SHUFFLE_DNS 1 -/* to disable SMB */ +/* disables SMB */ #cmakedefine CURL_DISABLE_SMB 1 -/* to disable SMTP */ +/* disables SMTP */ #cmakedefine CURL_DISABLE_SMTP 1 -/* to disable TELNET */ +/* disabled WebSockets */ +#cmakedefine CURL_DISABLE_WEBSOCKETS 1 + +/* disables use of socketpair for curl_multi_poll */ +#cmakedefine CURL_DISABLE_SOCKETPAIR 1 + +/* disables TELNET */ #cmakedefine CURL_DISABLE_TELNET 1 -/* to disable TFTP */ +/* disables TFTP */ #cmakedefine CURL_DISABLE_TFTP 1 -/* to disable verbose strings */ +/* disables verbose strings */ #cmakedefine CURL_DISABLE_VERBOSE_STRINGS 1 +/* disables unsafe CA bundle search on Windows from the curl tool */ +#cmakedefine CURL_DISABLE_CA_SEARCH 1 + +/* safe CA bundle search (within the curl tool directory) on Windows */ +#cmakedefine CURL_CA_SEARCH_SAFE 1 + /* to make a symbol visible */ #cmakedefine CURL_EXTERN_SYMBOL ${CURL_EXTERN_SYMBOL} /* Ensure using CURL_EXTERN_SYMBOL is possible */ @@ -73,56 +176,29 @@ #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 -/* when not building a shared library */ -#cmakedefine CURL_STATICLIB 1 - -/* Set to explicitly specify we don't want to use thread-safe functions */ -#cmakedefine DISABLED_THREADSAFE 1 - -/* your Entropy Gathering Daemon socket pathname */ -#cmakedefine EGD_SOCKET ${EGD_SOCKET} - /* Define if you want to enable IPv6 support */ -#cmakedefine ENABLE_IPV6 1 - -/* Define to the type qualifier of arg 1 for getnameinfo. */ -#cmakedefine GETNAMEINFO_QUAL_ARG1 ${GETNAMEINFO_QUAL_ARG1} - -/* Define to the type of arg 1 for getnameinfo. */ -#cmakedefine GETNAMEINFO_TYPE_ARG1 ${GETNAMEINFO_TYPE_ARG1} - -/* Define to the type of arg 2 for getnameinfo. */ -#cmakedefine GETNAMEINFO_TYPE_ARG2 ${GETNAMEINFO_TYPE_ARG2} - -/* Define to the type of args 4 and 6 for getnameinfo. */ -#cmakedefine GETNAMEINFO_TYPE_ARG46 ${GETNAMEINFO_TYPE_ARG46} - -/* Define to the type of arg 7 for getnameinfo. */ -#cmakedefine GETNAMEINFO_TYPE_ARG7 ${GETNAMEINFO_TYPE_ARG7} - -/* Specifies the number of arguments to getservbyport_r */ -#cmakedefine GETSERVBYPORT_R_ARGS ${GETSERVBYPORT_R_ARGS} - -/* Specifies the size of the buffer to pass to getservbyport_r */ -#cmakedefine GETSERVBYPORT_R_BUFSIZE ${GETSERVBYPORT_R_BUFSIZE} +#cmakedefine USE_IPV6 1 /* Define to 1 if you have the alarm function. */ #cmakedefine HAVE_ALARM 1 -/* Define to 1 if you have the header file. */ -#cmakedefine HAVE_ALLOCA_H 1 +/* Define to 1 if you have the arc4random function. */ +#cmakedefine HAVE_ARC4RANDOM 1 /* Define to 1 if you have the header file. */ #cmakedefine HAVE_ARPA_INET_H 1 -/* Define to 1 if you have the header file. */ -#cmakedefine HAVE_ARPA_TFTP_H 1 +/* Define to 1 if you have _Atomic support. */ +#cmakedefine HAVE_ATOMIC 1 -/* Define to 1 if you have the header file. */ -#cmakedefine HAVE_ASSERT_H 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 @@ -130,32 +206,24 @@ /* 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 `CRYPTO_cleanup_all_ex_data' function. */ -#cmakedefine HAVE_CRYPTO_CLEANUP_ALL_EX_DATA 1 - -/* Define to 1 if you have the header file. */ -#cmakedefine HAVE_CRYPTO_H 1 - -/* Define to 1 if you have the header file. */ -#cmakedefine HAVE_DES_H 1 - -/* Define to 1 if you have the header file. */ -#cmakedefine HAVE_DLFCN_H 1 - -/* Define to 1 if you have the `ENGINE_load_builtin_engines' function. */ -#cmakedefine HAVE_ENGINE_LOAD_BUILTIN_ENGINES 1 +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_DIRENT_H 1 -/* Define to 1 if you have the header file. */ -#cmakedefine HAVE_ERRNO_H 1 - -/* Define to 1 if you have the header file. */ -#cmakedefine HAVE_ERR_H 1 +/* Define to 1 if you have the `opendir' function. */ +#cmakedefine HAVE_OPENDIR 1 /* Define to 1 if you have the fcntl function. */ #cmakedefine HAVE_FCNTL 1 @@ -166,17 +234,17 @@ /* 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 fdopen function. */ -#cmakedefine HAVE_FDOPEN 1 - -/* Define to 1 if you have the `fork' function. */ -#cmakedefine HAVE_FORK 1 - /* Define to 1 if you have the freeaddrinfo function. */ #cmakedefine HAVE_FREEADDRINFO 1 -/* Define to 1 if you have the freeifaddrs function. */ -#cmakedefine HAVE_FREEIFADDRS 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 @@ -184,26 +252,14 @@ /* 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 gethostbyaddr function. */ -#cmakedefine HAVE_GETHOSTBYADDR 1 - -/* Define to 1 if you have the gethostbyaddr_r function. */ -#cmakedefine HAVE_GETHOSTBYADDR_R 1 - -/* gethostbyaddr_r() takes 5 args */ -#cmakedefine HAVE_GETHOSTBYADDR_R_5 1 - -/* gethostbyaddr_r() takes 7 args */ -#cmakedefine HAVE_GETHOSTBYADDR_R_7 1 - -/* gethostbyaddr_r() takes 8 args */ -#cmakedefine HAVE_GETHOSTBYADDR_R_8 1 - -/* Define to 1 if you have the gethostbyname function. */ -#cmakedefine HAVE_GETHOSTBYNAME 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 @@ -223,27 +279,27 @@ /* Define to 1 if you have a working getifaddrs function. */ #cmakedefine HAVE_GETIFADDRS 1 -/* Define to 1 if you have the getnameinfo function. */ -#cmakedefine HAVE_GETNAMEINFO 1 - /* Define to 1 if you have the `getpass_r' function. */ #cmakedefine HAVE_GETPASS_R 1 -/* Define to 1 if you have the `getppid' function. */ -#cmakedefine HAVE_GETPPID 1 +/* Define to 1 if you have the `getpeername' function. */ +#cmakedefine HAVE_GETPEERNAME 1 -/* Define to 1 if you have the `getprotobyname' function. */ -#cmakedefine HAVE_GETPROTOBYNAME 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 getservbyport_r function. */ -#cmakedefine HAVE_GETSERVBYPORT_R 1 - /* Define to 1 if you have the `gettimeofday' function. */ #cmakedefine HAVE_GETTIMEOFDAY 1 @@ -268,47 +324,20 @@ /* 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 `idn_free' function. */ -#cmakedefine HAVE_IDN_FREE 1 - -/* Define to 1 if you have the header file. */ -#cmakedefine HAVE_IDN_FREE_H 1 - /* Define to 1 if you have the header file. */ #cmakedefine HAVE_IFADDRS_H 1 -/* Define to 1 if you have the `inet_addr' function. */ -#cmakedefine HAVE_INET_ADDR 1 - -/* Define to 1 if you have the inet_ntoa_r function. */ -#cmakedefine HAVE_INET_NTOA_R 1 - -/* inet_ntoa_r() takes 2 args */ -#cmakedefine HAVE_INET_NTOA_R_2 1 - -/* inet_ntoa_r() takes 3 args */ -#cmakedefine HAVE_INET_NTOA_R_3 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 you have the header file. */ -#cmakedefine HAVE_INTTYPES_H 1 +/* Define to 1 if symbol `sa_family_t' exists */ +#cmakedefine HAVE_SA_FAMILY_T 1 -/* Define to 1 if you have the ioctl function. */ -#cmakedefine HAVE_IOCTL 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 @@ -332,24 +361,9 @@ /* Define to 1 if you have the header file. */ #cmakedefine HAVE_IO_H 1 -/* if you have the Kerberos4 libraries (including -ldes) */ -#cmakedefine HAVE_KRB4 1 - -/* Define to 1 if you have the `krb_get_our_ip_for_realm' function. */ -#cmakedefine HAVE_KRB_GET_OUR_IP_FOR_REALM 1 - -/* Define to 1 if you have the header file. */ -#cmakedefine HAVE_KRB_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 ldapssl.h header file. */ -#cmakedefine HAVE_LDAPSSL_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 @@ -362,65 +376,29 @@ /* Define to 1 if you have the header file. */ #cmakedefine HAVE_LIBGEN_H 1 -/* Define to 1 if you have the `idn' library (-lidn). */ -#cmakedefine HAVE_LIBIDN 1 - -/* Define to 1 if you have the `resolv' library (-lresolv). */ -#cmakedefine HAVE_LIBRESOLV 1 - -/* Define to 1 if you have the `resolve' library (-lresolve). */ -#cmakedefine HAVE_LIBRESOLVE 1 +/* Define to 1 if you have the `idn2' library (-lidn2). */ +#cmakedefine HAVE_LIBIDN2 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 - -/* Define to 1 if libssh2 provides `libssh2_version'. */ -#cmakedefine HAVE_LIBSSH2_VERSION 1 - -/* Define to 1 if libssh2 provides `libssh2_init'. */ -#cmakedefine HAVE_LIBSSH2_INIT 1 - -/* Define to 1 if libssh2 provides `libssh2_exit'. */ -#cmakedefine HAVE_LIBSSH2_EXIT 1 - -/* Define to 1 if libssh2 provides `libssh2_scp_send64'. */ -#cmakedefine HAVE_LIBSSH2_SCP_SEND64 1 - -/* Define to 1 if libssh2 provides `libssh2_session_handshake'. */ -#cmakedefine HAVE_LIBSSH2_SESSION_HANDSHAKE 1 - -/* Define to 1 if you have the header file. */ -#cmakedefine HAVE_LIBSSH2_H 1 - -/* Define to 1 if you have the `ssl' library (-lssl). */ -#cmakedefine HAVE_LIBSSL 1 +/* Define to 1 if you have the idn2.h header file. */ +#cmakedefine HAVE_IDN2_H 1 /* if zlib is available */ #cmakedefine HAVE_LIBZ 1 -/* Define to 1 if you have the header file. */ -#cmakedefine HAVE_LIMITS_H 1 +/* if brotli is available */ +#cmakedefine HAVE_BROTLI 1 -/* if your compiler supports LL */ -#cmakedefine HAVE_LL 1 +/* if zstd is available */ +#cmakedefine HAVE_ZSTD 1 /* Define to 1 if you have the header file. */ #cmakedefine HAVE_LOCALE_H 1 -/* Define to 1 if you have a working localtime_r function. */ -#cmakedefine HAVE_LOCALTIME_R 1 - /* Define to 1 if the compiler supports the 'long long' data type. */ #cmakedefine HAVE_LONGLONG 1 -/* Define to 1 if you have the malloc.h header file. */ -#cmakedefine HAVE_MALLOC_H 1 - -/* Define to 1 if you have the header file. */ -#cmakedefine HAVE_MEMORY_H 1 +/* 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 @@ -434,53 +412,26 @@ /* Define to 1 if you have the header file. */ #cmakedefine HAVE_NETINET_TCP_H 1 +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_NETINET_UDP_H 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_LINUX_TCP_H 1 + /* Define to 1 if you have the header file. */ #cmakedefine HAVE_NET_IF_H 1 -/* Define to 1 if NI_WITHSCOPEID exists and works. */ -#cmakedefine HAVE_NI_WITHSCOPEID 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 header file. */ -#cmakedefine HAVE_OPENSSL_CRYPTO_H 1 - -/* Define to 1 if you have the header file. */ -#cmakedefine HAVE_OPENSSL_ENGINE_H 1 - -/* Define to 1 if you have the header file. */ -#cmakedefine HAVE_OPENSSL_ERR_H 1 - -/* Define to 1 if you have the header file. */ -#cmakedefine HAVE_OPENSSL_PEM_H 1 - -/* Define to 1 if you have the header file. */ -#cmakedefine HAVE_OPENSSL_PKCS12_H 1 - -/* Define to 1 if you have the header file. */ -#cmakedefine HAVE_OPENSSL_RSA_H 1 - -/* Define to 1 if you have the header file. */ -#cmakedefine HAVE_OPENSSL_SSL_H 1 - -/* Define to 1 if you have the header file. */ -#cmakedefine HAVE_OPENSSL_X509_H 1 - -/* Define to 1 if you have the header file. */ -#cmakedefine HAVE_PEM_H 1 - -/* Define to 1 if you have the `perror' function. */ -#cmakedefine HAVE_PERROR 1 - /* Define to 1 if you have the `pipe' function. */ #cmakedefine HAVE_PIPE 1 -/* Define to 1 if you have a working poll function. */ -#cmakedefine HAVE_POLL 1 +/* Define to 1 if you have the `eventfd' function. */ +#cmakedefine HAVE_EVENTFD 1 -/* If you have a fine poll */ -#cmakedefine HAVE_POLL_FINE 1 +/* If you have poll */ +#cmakedefine HAVE_POLL 1 /* Define to 1 if you have the header file. */ #cmakedefine HAVE_POLL_H 1 @@ -494,30 +445,27 @@ /* Define to 1 if you have the header file. */ #cmakedefine HAVE_PWD_H 1 -/* Define to 1 if you have the `RAND_egd' function. */ -#cmakedefine HAVE_RAND_EGD 1 - -/* Define to 1 if you have the `RAND_screen' function. */ -#cmakedefine HAVE_RAND_SCREEN 1 - -/* Define to 1 if you have the `RAND_status' function. */ -#cmakedefine HAVE_RAND_STATUS 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 recvfrom function. */ -#cmakedefine HAVE_RECVFROM 1 - -/* Define to 1 if you have the header file. */ -#cmakedefine HAVE_RSA_H 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 sendmmsg function. */ +#cmakedefine HAVE_SENDMMSG 1 + /* Define to 1 if you have the 'fsetxattr' function. */ #cmakedefine HAVE_FSETXATTR 1 @@ -527,9 +475,6 @@ /* fsetxattr() takes 6 args */ #cmakedefine HAVE_FSETXATTR_6 1 -/* Define to 1 if you have the header file. */ -#cmakedefine HAVE_SETJMP_H 1 - /* Define to 1 if you have the `setlocale' function. */ #cmakedefine HAVE_SETLOCALE 1 @@ -539,15 +484,9 @@ /* Define to 1 if you have the `setrlimit' function. */ #cmakedefine HAVE_SETRLIMIT 1 -/* Define to 1 if you have the setsockopt function. */ -#cmakedefine HAVE_SETSOCKOPT 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 header file. */ -#cmakedefine HAVE_SGTTY_H 1 - /* Define to 1 if you have the sigaction function. */ #cmakedefine HAVE_SIGACTION 1 @@ -557,17 +496,11 @@ /* Define to 1 if you have the signal function. */ #cmakedefine HAVE_SIGNAL 1 -/* Define to 1 if you have the header file. */ -#cmakedefine HAVE_SIGNAL_H 1 - /* Define to 1 if you have the sigsetjmp function or macro. */ #cmakedefine HAVE_SIGSETJMP 1 -/* Define to 1 if sig_atomic_t is an available typedef. */ -#cmakedefine HAVE_SIG_ATOMIC_T 1 - -/* Define to 1 if sig_atomic_t is already defined as volatile. */ -#cmakedefine HAVE_SIG_ATOMIC_T_VOLATILE 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 @@ -575,30 +508,18 @@ /* Define to 1 if you have the `socket' function. */ #cmakedefine HAVE_SOCKET 1 -/* Define to 1 if you have the `SSL_get_shutdown' function. */ -#cmakedefine HAVE_SSL_GET_SHUTDOWN 1 +/* Define to 1 if you have the socketpair function. */ +#cmakedefine HAVE_SOCKETPAIR 1 -/* Define to 1 if you have the header file. */ -#cmakedefine HAVE_SSL_H 1 +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_STDATOMIC_H 1 /* Define to 1 if you have the header file. */ #cmakedefine HAVE_STDBOOL_H 1 -/* Define to 1 if you have the header file. */ -#cmakedefine HAVE_STDINT_H 1 - -/* Define to 1 if you have the header file. */ -#cmakedefine HAVE_STDIO_H 1 - -/* Define to 1 if you have the header file. */ -#cmakedefine HAVE_STDLIB_H 1 - /* Define to 1 if you have the strcasecmp function. */ #cmakedefine HAVE_STRCASECMP 1 -/* Define to 1 if you have the strcasestr function. */ -#cmakedefine HAVE_STRCASESTR 1 - /* Define to 1 if you have the strcmpi function. */ #cmakedefine HAVE_STRCMPI 1 @@ -614,45 +535,33 @@ /* Define to 1 if you have the header file. */ #cmakedefine HAVE_STRINGS_H 1 -/* Define to 1 if you have the header file. */ -#cmakedefine HAVE_STRING_H 1 - -/* Define to 1 if you have the strlcat function. */ -#cmakedefine HAVE_STRLCAT 1 - -/* Define to 1 if you have the `strlcpy' function. */ -#cmakedefine HAVE_STRLCPY 1 - -/* Define to 1 if you have the strncasecmp function. */ -#cmakedefine HAVE_STRNCASECMP 1 - -/* Define to 1 if you have the strncmpi function. */ -#cmakedefine HAVE_STRNCMPI 1 - -/* Define to 1 if you have the strnicmp function. */ -#cmakedefine HAVE_STRNICMP 1 - /* Define to 1 if you have the header file. */ #cmakedefine HAVE_STROPTS_H 1 -/* Define to 1 if you have the strstr function. */ -#cmakedefine HAVE_STRSTR 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 header file. */ +#cmakedefine HAVE_SYS_EVENTFD_H 1 + /* Define to 1 if you have the header file. */ #cmakedefine HAVE_SYS_FILIO_H 1 +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_SYS_WAIT_H 1 + /* Define to 1 if you have the header file. */ #cmakedefine HAVE_SYS_IOCTL_H 1 @@ -683,9 +592,6 @@ /* Define to 1 if you have the header file. */ #cmakedefine HAVE_SYS_TYPES_H 1 -/* Define to 1 if you have the header file. */ -#cmakedefine HAVE_SYS_UIO_H 1 - /* Define to 1 if you have the header file. */ #cmakedefine HAVE_SYS_UN_H 1 @@ -698,76 +604,21 @@ /* Define to 1 if you have the header file. */ #cmakedefine HAVE_TERMIO_H 1 -/* Define to 1 if you have the header file. */ -#cmakedefine HAVE_TIME_H 1 - -/* Define to 1 if you have the header file. */ -#cmakedefine HAVE_TLD_H 1 - -/* Define to 1 if you have the `tld_strerror' function. */ -#cmakedefine HAVE_TLD_STRERROR 1 - -/* Define to 1 if you have the `uname' function. */ -#cmakedefine HAVE_UNAME 1 - /* Define to 1 if you have the 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 header file. */ #cmakedefine HAVE_UTIME_H 1 -/* Define to 1 if compiler supports C99 variadic macro style. */ -#cmakedefine HAVE_VARIADIC_MACROS_C99 1 - -/* Define to 1 if compiler supports old gcc variadic macro style. */ -#cmakedefine HAVE_VARIADIC_MACROS_GCC 1 - -/* Define to 1 if you have the winber.h header file. */ -#cmakedefine HAVE_WINBER_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 winldap.h header file. */ -#cmakedefine HAVE_WINLDAP_H 1 - -/* Define to 1 if you have the winsock2.h header file. */ -#cmakedefine HAVE_WINSOCK2_H 1 - -/* Define to 1 if you have the winsock.h header file. */ -#cmakedefine HAVE_WINSOCK_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 writev function. */ -#cmakedefine HAVE_WRITEV 1 - -/* Define to 1 if you have the ws2tcpip.h header file. */ -#cmakedefine HAVE_WS2TCPIP_H 1 - -/* Define to 1 if you have the header file. */ -#cmakedefine HAVE_X509_H 1 - -/* Define if you have the header file. */ -#cmakedefine HAVE_PROCESS_H 1 - -/* if you have the zlib.h header file */ -#cmakedefine HAVE_ZLIB_H 1 - -/* Define to the sub-directory in which libtool stores uninstalled libraries. - */ -#cmakedefine LT_OBJDIR ${LT_OBJDIR} - -/* If you lack a fine basename() prototype */ -#cmakedefine NEED_BASENAME_PROTO 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 @@ -795,149 +646,86 @@ /* 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}" - -/* Define to the type of arg 1 for recvfrom. */ -#cmakedefine RECVFROM_TYPE_ARG1 ${RECVFROM_TYPE_ARG1} - -/* Define to the type pointed by arg 2 for recvfrom. */ -#cmakedefine RECVFROM_TYPE_ARG2 ${RECVFROM_TYPE_ARG2} - -/* Define to 1 if the type pointed by arg 2 for recvfrom is void. */ -#cmakedefine RECVFROM_TYPE_ARG2_IS_VOID 1 - -/* Define to the type of arg 3 for recvfrom. */ -#cmakedefine RECVFROM_TYPE_ARG3 ${RECVFROM_TYPE_ARG3} - -/* Define to the type of arg 4 for recvfrom. */ -#cmakedefine RECVFROM_TYPE_ARG4 ${RECVFROM_TYPE_ARG4} - -/* Define to the type pointed by arg 5 for recvfrom. */ -#cmakedefine RECVFROM_TYPE_ARG5 ${RECVFROM_TYPE_ARG5} - -/* Define to 1 if the type pointed by arg 5 for recvfrom is void. */ -#cmakedefine RECVFROM_TYPE_ARG5_IS_VOID 1 - -/* Define to the type pointed by arg 6 for recvfrom. */ -#cmakedefine RECVFROM_TYPE_ARG6 ${RECVFROM_TYPE_ARG6} - -/* Define to 1 if the type pointed by arg 6 for recvfrom is void. */ -#cmakedefine RECVFROM_TYPE_ARG6_IS_VOID 1 +/* + 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. -/* Define to the function return type for recvfrom. */ -#cmakedefine RECVFROM_TYPE_RETV ${RECVFROM_TYPE_RETV} - -/* Define to the type of arg 1 for recv. */ -#cmakedefine RECV_TYPE_ARG1 ${RECV_TYPE_ARG1} - -/* Define to the type of arg 2 for recv. */ -#cmakedefine RECV_TYPE_ARG2 ${RECV_TYPE_ARG2} - -/* Define to the type of arg 3 for recv. */ -#cmakedefine RECV_TYPE_ARG3 ${RECV_TYPE_ARG3} - -/* Define to the type of arg 4 for recv. */ -#cmakedefine RECV_TYPE_ARG4 ${RECV_TYPE_ARG4} - -/* Define to the function return type for recv. */ -#cmakedefine RECV_TYPE_RETV ${RECV_TYPE_RETV} - -/* Define as the return type of signal handlers (`int' or `void'). */ -#cmakedefine RETSIGTYPE ${RETSIGTYPE} - -/* Define to the type qualifier of arg 5 for select. */ -#cmakedefine SELECT_QUAL_ARG5 ${SELECT_QUAL_ARG5} - -/* Define to the type of arg 1 for select. */ -#cmakedefine SELECT_TYPE_ARG1 ${SELECT_TYPE_ARG1} - -/* Define to the type of args 2, 3 and 4 for select. */ -#cmakedefine SELECT_TYPE_ARG234 ${SELECT_TYPE_ARG234} - -/* Define to the type of arg 5 for select. */ -#cmakedefine SELECT_TYPE_ARG5 ${SELECT_TYPE_ARG5} - -/* Define to the function return type for select. */ -#cmakedefine SELECT_TYPE_RETV ${SELECT_TYPE_RETV} - -/* Define to the type qualifier of arg 2 for send. */ -#cmakedefine SEND_QUAL_ARG2 ${SEND_QUAL_ARG2} - -/* Define to the type of arg 1 for send. */ -#cmakedefine SEND_TYPE_ARG1 ${SEND_TYPE_ARG1} - -/* Define to the type of arg 2 for send. */ -#cmakedefine SEND_TYPE_ARG2 ${SEND_TYPE_ARG2} - -/* Define to the type of arg 3 for send. */ -#cmakedefine SEND_TYPE_ARG3 ${SEND_TYPE_ARG3} - -/* Define to the type of arg 4 for send. */ -#cmakedefine SEND_TYPE_ARG4 ${SEND_TYPE_ARG4} - -/* Define to the function return type for send. */ -#cmakedefine SEND_TYPE_RETV ${SEND_TYPE_RETV} + Reference: https://cmake.org/cmake/help/latest/module/CheckTypeSize.html +*/ /* The size of `int', as computed by sizeof. */ -#cmakedefine SIZEOF_INT ${SIZEOF_INT} - -/* The size of `short', as computed by sizeof. */ -#cmakedefine SIZEOF_SHORT ${SIZEOF_SHORT} +${SIZEOF_INT_CODE} /* The size of `long', as computed by sizeof. */ -#cmakedefine SIZEOF_LONG ${SIZEOF_LONG} +${SIZEOF_LONG_CODE} + +/* The size of `long long', as computed by sizeof. */ +${SIZEOF_LONG_LONG_CODE} /* The size of `off_t', as computed by sizeof. */ -#cmakedefine SIZEOF_OFF_T ${SIZEOF_OFF_T} +${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. */ -#cmakedefine SIZEOF_SIZE_T ${SIZEOF_SIZE_T} +${SIZEOF_SIZE_T_CODE} /* The size of `time_t', as computed by sizeof. */ -#cmakedefine SIZEOF_TIME_T ${SIZEOF_TIME_T} - -/* The size of `void*', as computed by sizeof. */ -#cmakedefine SIZEOF_VOIDP ${SIZEOF_VOIDP} +${SIZEOF_TIME_T_CODE} /* Define to 1 if you have the ANSI C header files. */ #cmakedefine STDC_HEADERS 1 -/* Define to the type of arg 3 for strerror_r. */ -#cmakedefine STRERROR_R_TYPE_ARG3 ${STRERROR_R_TYPE_ARG3} - -/* Define to 1 if you can safely include both and . */ -#cmakedefine TIME_WITH_SYS_TIME 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 */ +/* Define if you want to enable Win32 threaded DNS lookup */ #cmakedefine USE_THREADS_WIN32 1 -/* Define to disable non-blocking sockets. */ -#cmakedefine USE_BLOCKING_SOCKETS 1 - /* if GnuTLS is enabled */ #cmakedefine USE_GNUTLS 1 -/* if PolarSSL is enabled */ -#cmakedefine USE_POLARSSL 1 +/* if Secure Transport is enabled */ +#cmakedefine USE_SECTRANSP 1 /* if mbedTLS is enabled */ #cmakedefine USE_MBEDTLS 1 -/* if libSSH2 is in use */ +/* if BearSSL is enabled */ +#cmakedefine USE_BEARSSL 1 + +/* if Rustls is enabled */ +#cmakedefine USE_RUSTLS 1 + +/* if wolfSSL is enabled */ +#cmakedefine USE_WOLFSSL 1 + +/* if wolfSSL has the wolfSSL_DES_ecb_encrypt function. */ +#cmakedefine HAVE_WOLFSSL_DES_ECB_ENCRYPT 1 + +/* if wolfSSL has the wolfSSL_BIO_set_shutdown function. */ +#cmakedefine HAVE_WOLFSSL_FULL_BIO 1 + +/* if libssh is in use */ +#cmakedefine USE_LIBSSH 1 + +/* if libssh2 is in use */ #cmakedefine USE_LIBSSH2 1 -/* If you want to build curl with the built-in manual */ -#cmakedefine USE_MANUAL 1 +/* if wolfssh is in use */ +#cmakedefine USE_WOLFSSH 1 -/* if NSS is enabled */ -#cmakedefine USE_NSS 1 +/* if libpsl is in use */ +#cmakedefine USE_LIBPSL 1 /* if you want to use OpenLDAP code instead of legacy ldap implementation */ #cmakedefine USE_OPENLDAP 1 @@ -945,11 +733,45 @@ /* if OpenSSL is in use */ #cmakedefine USE_OPENSSL 1 +/* if librtmp/rtmpdump is in use */ +#cmakedefine USE_LIBRTMP 1 + +/* if GSASL is in use */ +#cmakedefine USE_GSASL 1 + +/* if libuv is in use */ +#cmakedefine USE_LIBUV 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_UV_H 1 + +/* Define to 1 if you do not 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 + +/* to enable openssl + nghttp3 */ +#cmakedefine USE_OPENSSL_QUIC 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 +#cmakedefine USE_UNIX_SOCKETS 1 /* Define to 1 if you are building a Windows target with large file support. */ #cmakedefine USE_WIN32_LARGE_FILES 1 @@ -960,20 +782,12 @@ /* to enable Windows SSL */ #cmakedefine USE_SCHANNEL 1 -/* Define to 1 if using yaSSL in OpenSSL compatibility mode. */ -#cmakedefine USE_YASSLEMUL 1 +/* enable multiple SSL backends */ +#cmakedefine CURL_WITH_MULTI_SSL 1 /* Version number of package */ #cmakedefine VERSION ${VERSION} -/* Define to avoid automatic inclusion of winsock.h */ -#cmakedefine WIN32_LEAN_AND_MEAN 1 - -/* 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} @@ -989,14 +803,32 @@ /* 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 does not define. */ #cmakedefine size_t ${size_t} /* the signed version of size_t */ #cmakedefine ssize_t ${ssize_t} + +/* 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 + +/* to enable Apple IDN */ +#cmakedefine USE_APPLE_IDN 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 + +/* Define to 1 to query for HTTPSRR when using DoH */ +#cmakedefine USE_HTTPSRR 1 + +/* if ECH support is available */ +#cmakedefine USE_ECH 1 diff --git a/contrib/curl/lib/curl_ctype.h b/contrib/curl/lib/curl_ctype.h new file mode 100644 index 0000000000..7f0d0cc291 --- /dev/null +++ b/contrib/curl/lib/curl_ctype.h @@ -0,0 +1,51 @@ +#ifndef HEADER_CURL_CTYPE_H +#define HEADER_CURL_CTYPE_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#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/contrib/curl/lib/curl_des.c b/contrib/curl/lib/curl_des.c index b123a00f01..15836f58b9 100644 --- a/contrib/curl/lib/curl_des.c +++ b/contrib/curl/lib/curl_des.c @@ -5,11 +5,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 2015, Steve Holme, . + * Copyright (C) Steve Holme, . * * This software is licensed 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. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -18,11 +18,17 @@ * 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_OPENSSL) +#if defined(USE_CURL_NTLM_CORE) && \ + (defined(USE_GNUTLS) || \ + defined(USE_SECTRANSP) || \ + defined(USE_OS400CRYPTO) || \ + defined(USE_WIN32_CRYPTO)) #include "curl_des.h" @@ -30,11 +36,11 @@ * 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. + * used by when a cryptography engine does not have its own version. * * The function is a port of the Java based oddParity() function over at: * - * https://davenport.sourceforge.io/ntlm.html + * https://davenport.sourceforge.net/ntlm.html * * Parameters: * @@ -60,4 +66,4 @@ void Curl_des_set_odd_parity(unsigned char *bytes, size_t len) } } -#endif /* USE_NTLM && !USE_OPENSSL */ +#endif diff --git a/contrib/curl/lib/curl_des.h b/contrib/curl/lib/curl_des.h index 129060ff7d..2dd498da24 100644 --- a/contrib/curl/lib/curl_des.h +++ b/contrib/curl/lib/curl_des.h @@ -7,11 +7,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 2015, Steve Holme, . + * Copyright (C) Steve Holme, . * * This software is licensed 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. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -20,15 +20,21 @@ * 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_OPENSSL) +#if defined(USE_CURL_NTLM_CORE) && \ + (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 /* USE_NTLM && !USE_OPENSSL */ +#endif #endif /* HEADER_CURL_DES_H */ diff --git a/contrib/curl/lib/curl_endian.c b/contrib/curl/lib/curl_endian.c index c2d21de5ee..d982e31269 100644 --- a/contrib/curl/lib/curl_endian.c +++ b/contrib/curl/lib/curl_endian.c @@ -5,11 +5,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2016, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -18,6 +18,8 @@ * 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" @@ -28,7 +30,7 @@ * 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 + * used in the incoming package to whatever endian format we are using * natively. * * Parameters: @@ -47,7 +49,7 @@ unsigned short Curl_read16_le(const unsigned char *buf) * 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 + * used in the incoming package to whatever endian format we are using * natively. * * Parameters: @@ -62,49 +64,11 @@ unsigned int Curl_read32_le(const unsigned char *buf) ((unsigned int)buf[2] << 16) | ((unsigned int)buf[3] << 24); } -#if (CURL_SIZEOF_CURL_OFF_T > 4) -/* - * Curl_read64_le() - * - * This function converts a 64-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 8 byte buffer. - * - * Returns the integer. - */ -#if defined(HAVE_LONGLONG) -unsigned long long Curl_read64_le(const unsigned char *buf) -{ - return ((unsigned long long)buf[0]) | - ((unsigned long long)buf[1] << 8) | - ((unsigned long long)buf[2] << 16) | - ((unsigned long long)buf[3] << 24) | - ((unsigned long long)buf[4] << 32) | - ((unsigned long long)buf[5] << 40) | - ((unsigned long long)buf[6] << 48) | - ((unsigned long long)buf[7] << 56); -} -#else -unsigned __int64 Curl_read64_le(const unsigned char *buf) -{ - return ((unsigned __int64)buf[0]) | ((unsigned __int64)buf[1] << 8) | - ((unsigned __int64)buf[2] << 16) | ((unsigned __int64)buf[3] << 24) | - ((unsigned __int64)buf[4] << 32) | ((unsigned __int64)buf[5] << 40) | - ((unsigned __int64)buf[6] << 48) | ((unsigned __int64)buf[7] << 56); -} -#endif - -#endif /* CURL_SIZEOF_CURL_OFF_T > 4 */ - /* * 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 + * used in the incoming package to whatever endian format we are using * natively. * * Parameters: @@ -118,119 +82,3 @@ unsigned short Curl_read16_be(const unsigned char *buf) return (unsigned short)(((unsigned short)buf[0] << 8) | ((unsigned short)buf[1])); } - -/* - * Curl_read32_be() - * - * This function converts a 32-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 4 byte buffer. - * - * Returns the integer. - */ -unsigned int Curl_read32_be(const unsigned char *buf) -{ - return ((unsigned int)buf[0] << 24) | ((unsigned int)buf[1] << 16) | - ((unsigned int)buf[2] << 8) | ((unsigned int)buf[3]); -} - -#if (CURL_SIZEOF_CURL_OFF_T > 4) -/* - * Curl_read64_be() - * - * This function converts a 64-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 8 byte buffer. - * - * Returns the integer. - */ -#if defined(HAVE_LONGLONG) -unsigned long long Curl_read64_be(const unsigned char *buf) -{ - return ((unsigned long long)buf[0] << 56) | - ((unsigned long long)buf[1] << 48) | - ((unsigned long long)buf[2] << 40) | - ((unsigned long long)buf[3] << 32) | - ((unsigned long long)buf[4] << 24) | - ((unsigned long long)buf[5] << 16) | - ((unsigned long long)buf[6] << 8) | - ((unsigned long long)buf[7]); -} -#else -unsigned __int64 Curl_read64_be(const unsigned char *buf) -{ - return ((unsigned __int64)buf[0] << 56) | ((unsigned __int64)buf[1] << 48) | - ((unsigned __int64)buf[2] << 40) | ((unsigned __int64)buf[3] << 32) | - ((unsigned __int64)buf[4] << 24) | ((unsigned __int64)buf[5] << 16) | - ((unsigned __int64)buf[6] << 8) | ((unsigned __int64)buf[7]); -} -#endif - -#endif /* CURL_SIZEOF_CURL_OFF_T > 4 */ - -/* - * Curl_write16_le() - * - * This function converts a 16-bit integer from the native endian format, - * to little endian format ready for sending down the wire. - * - * Parameters: - * - * value [in] - The 16-bit integer value. - * buffer [in] - A pointer to the output buffer. - */ -void Curl_write16_le(const short value, unsigned char *buffer) -{ - buffer[0] = (char)(value & 0x00FF); - buffer[1] = (char)((value & 0xFF00) >> 8); -} - -/* - * Curl_write32_le() - * - * This function converts a 32-bit integer from the native endian format, - * to little endian format ready for sending down the wire. - * - * Parameters: - * - * value [in] - The 32-bit integer value. - * buffer [in] - A pointer to the output buffer. - */ -void Curl_write32_le(const int value, unsigned char *buffer) -{ - buffer[0] = (char)(value & 0x000000FF); - buffer[1] = (char)((value & 0x0000FF00) >> 8); - buffer[2] = (char)((value & 0x00FF0000) >> 16); - buffer[3] = (char)((value & 0xFF000000) >> 24); -} - -#if (CURL_SIZEOF_CURL_OFF_T > 4) -/* - * Curl_write64_le() - * - * This function converts a 64-bit integer from the native endian format, - * to little endian format ready for sending down the wire. - * - * Parameters: - * - * value [in] - The 64-bit integer value. - * buffer [in] - A pointer to the output buffer. - */ -#if defined(HAVE_LONGLONG) -void Curl_write64_le(const long long value, unsigned char *buffer) -#else -void Curl_write64_le(const __int64 value, unsigned char *buffer) -#endif -{ - Curl_write32_le((int)value, buffer); - Curl_write32_le((int)(value >> 32), buffer + 4); -} -#endif /* CURL_SIZEOF_CURL_OFF_T > 4 */ diff --git a/contrib/curl/lib/curl_endian.h b/contrib/curl/lib/curl_endian.h index 8a2b07ad85..fa283214b1 100644 --- a/contrib/curl/lib/curl_endian.h +++ b/contrib/curl/lib/curl_endian.h @@ -7,11 +7,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2016, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -20,6 +20,8 @@ * 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 */ @@ -28,43 +30,7 @@ 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); -#if (CURL_SIZEOF_CURL_OFF_T > 4) -/* Converts a 64-bit integer from little endian */ -#if defined(HAVE_LONGLONG) -unsigned long long Curl_read64_le(const unsigned char *buf); -#else -unsigned __int64 Curl_read64_le(const unsigned char *buf); -#endif -#endif - /* Converts a 16-bit integer from big endian */ unsigned short Curl_read16_be(const unsigned char *buf); -/* Converts a 32-bit integer from big endian */ -unsigned int Curl_read32_be(const unsigned char *buf); - -#if (CURL_SIZEOF_CURL_OFF_T > 4) -/* Converts a 64-bit integer from big endian */ -#if defined(HAVE_LONGLONG) -unsigned long long Curl_read64_be(const unsigned char *buf); -#else -unsigned __int64 Curl_read64_be(const unsigned char *buf); -#endif -#endif - -/* Converts a 16-bit integer to little endian */ -void Curl_write16_le(const short value, unsigned char *buffer); - -/* Converts a 32-bit integer to little endian */ -void Curl_write32_le(const int value, unsigned char *buffer); - -#if (CURL_SIZEOF_CURL_OFF_T > 4) -/* Converts a 64-bit integer to little endian */ -#if defined(HAVE_LONGLONG) -void Curl_write64_le(const long long value, unsigned char *buffer); -#else -void Curl_write64_le(const __int64 value, unsigned char *buffer); -#endif -#endif - #endif /* HEADER_CURL_ENDIAN_H */ diff --git a/contrib/curl/lib/curl_fnmatch.c b/contrib/curl/lib/curl_fnmatch.c index e8108bb10e..4f863ccb9e 100644 --- a/contrib/curl/lib/curl_fnmatch.c +++ b/contrib/curl/lib/curl_fnmatch.c @@ -5,11 +5,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2016, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -18,10 +18,12 @@ * 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 #include "curl_fnmatch.h" @@ -30,6 +32,8 @@ /* 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) @@ -46,15 +50,8 @@ #define CURLFNM_SPACE (CURLFNM_CHARSET_LEN + 9) #define CURLFNM_UPPER (CURLFNM_CHARSET_LEN + 10) -typedef enum { - CURLFNM_LOOP_DEFAULT = 0, - CURLFNM_LOOP_BACKSLASH -} loop_state; - typedef enum { CURLFNM_SCHS_DEFAULT = 0, - CURLFNM_SCHS_MAYRANGE, - CURLFNM_SCHS_MAYRANGE2, CURLFNM_SCHS_RIGHTBR, CURLFNM_SCHS_RIGHTBRLEFTBR } setcharset_state; @@ -64,6 +61,13 @@ typedef enum { 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 @@ -72,21 +76,21 @@ static int parsekeyword(unsigned char **pattern, unsigned char *charset) parsekey_state state = CURLFNM_PKW_INIT; #define KEYLEN 10 char keyword[KEYLEN] = { 0 }; - int found = FALSE; int i; unsigned char *p = *pattern; + bool found = FALSE; for(i = 0; !found; i++) { - char c = *p++; + char c = (char)*p++; if(i >= KEYLEN) return SETCHARSET_FAIL; switch(state) { case CURLFNM_PKW_INIT: - if(ISALPHA(c) && ISLOWER(c)) + if(ISLOWER(c)) keyword[i] = c; else if(c == ':') state = CURLFNM_PKW_DDOT; else - return 0; + return SETCHARSET_FAIL; break; case CURLFNM_PKW_DDOT: if(c == ']') @@ -123,55 +127,74 @@ static int parsekeyword(unsigned char **pattern, unsigned char *charset) 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; - unsigned char rangestart = 0; - unsigned char lastchar = 0; 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(ISALNUM(c)) { /* ASCII value */ - rangestart = c; - charset[c] = 1; - (*p)++; - state = CURLFNM_SCHS_MAYRANGE; - something_found = TRUE; - } - else if(c == ']') { + if(c == ']') { if(something_found) return SETCHARSET_OK; - else - something_found = TRUE; + something_found = TRUE; state = CURLFNM_SCHS_RIGHTBR; charset[c] = 1; (*p)++; } else if(c == '[') { - char c2 = *((*p)+1); - if(c2 == ':') { /* there has to be a keyword */ - (*p) += 2; - if(parsekeyword(p, charset)) { - state = CURLFNM_SCHS_DEFAULT; - } - else - return SETCHARSET_FAIL; - } + unsigned char *pp = *p + 1; + + if(*pp++ == ':' && parsekeyword(&pp, charset)) + *p = pp; else { charset[c] = 1; (*p)++; } something_found = TRUE; } - else if(c == '?' || c == '*') { - something_found = TRUE; - charset[c] = 1; - (*p)++; - } else if(c == '^' || c == '!') { if(!something_found) { if(charset[CURLFNM_NEGATE]) { @@ -187,88 +210,17 @@ static int setcharset(unsigned char **p, unsigned char *charset) } else if(c == '\\') { c = *(++(*p)); - if(ISPRINT((c))) { - something_found = TRUE; - state = CURLFNM_SCHS_MAYRANGE; - charset[c] = 1; - rangestart = c; - (*p)++; - } + if(c) + setcharorrange(p, charset); else - return SETCHARSET_FAIL; - } - else if(c == '\0') { - return SETCHARSET_FAIL; + charset['\\'] = 1; + something_found = TRUE; } else { - charset[c] = 1; - (*p)++; + setcharorrange(p, charset); something_found = TRUE; } break; - case CURLFNM_SCHS_MAYRANGE: - if(c == '-') { - charset[c] = 1; - (*p)++; - lastchar = '-'; - state = CURLFNM_SCHS_MAYRANGE2; - } - else if(c == '[') { - state = CURLFNM_SCHS_DEFAULT; - } - else if(ISALNUM(c)) { - charset[c] = 1; - (*p)++; - } - else if(c == '\\') { - c = *(++(*p)); - if(ISPRINT(c)) { - charset[c] = 1; - (*p)++; - } - else - return SETCHARSET_FAIL; - } - else if(c == ']') { - return SETCHARSET_OK; - } - else - return SETCHARSET_FAIL; - break; - case CURLFNM_SCHS_MAYRANGE2: - if(c == '\\') { - c = *(++(*p)); - if(!ISPRINT(c)) - return SETCHARSET_FAIL; - } - if(c == ']') { - return SETCHARSET_OK; - } - else if(c == '\\') { - c = *(++(*p)); - if(ISPRINT(c)) { - charset[c] = 1; - state = CURLFNM_SCHS_DEFAULT; - (*p)++; - } - else - return SETCHARSET_FAIL; - } - if(c >= rangestart) { - if((ISLOWER(c) && ISLOWER(rangestart)) || - (ISDIGIT(c) && ISDIGIT(rangestart)) || - (ISUPPER(c) && ISUPPER(rangestart))) { - charset[lastchar] = 0; - rangestart++; - while(rangestart++ <= c) - charset[rangestart-1] = 1; - (*p)++; - state = CURLFNM_SCHS_DEFAULT; - } - else - return SETCHARSET_FAIL; - } - break; case CURLFNM_SCHS_RIGHTBR: if(c == '[') { state = CURLFNM_SCHS_RIGHTBRLEFTBR; @@ -278,9 +230,6 @@ static int setcharset(unsigned char **p, unsigned char *charset) else if(c == ']') { return SETCHARSET_OK; } - else if(c == '\0') { - return SETCHARSET_FAIL; - } else if(ISPRINT(c)) { charset[c] = 1; (*p)++; @@ -293,14 +242,11 @@ static int setcharset(unsigned char **p, unsigned char *charset) goto fail; break; case CURLFNM_SCHS_RIGHTBRLEFTBR: - if(c == ']') { + if(c == ']') return SETCHARSET_OK; - } - else { - state = CURLFNM_SCHS_DEFAULT; - charset[c] = 1; - (*p)++; - } + state = CURLFNM_SCHS_DEFAULT; + charset[c] = 1; + (*p)++; break; } } @@ -308,105 +254,96 @@ static int setcharset(unsigned char **p, unsigned char *charset) return SETCHARSET_FAIL; } -static int loop(const unsigned char *pattern, const unsigned char *string) +static int loop(const unsigned char *pattern, const unsigned char *string, + int maxstars) { - loop_state state = CURLFNM_LOOP_DEFAULT; unsigned char *p = (unsigned char *)pattern; unsigned char *s = (unsigned char *)string; unsigned char charset[CURLFNM_CHSET_SIZE] = { 0 }; - int rc = 0; for(;;) { - switch(state) { - case CURLFNM_LOOP_DEFAULT: - if(*p == '*') { - while(*(p+1) == '*') /* eliminate multiple stars */ - p++; - if(*s == '\0' && *(p+1) == '\0') - return CURL_FNMATCH_MATCH; - rc = loop(p + 1, s); /* *.txt matches .txt <=> .txt matches .txt */ - if(rc == CURL_FNMATCH_MATCH) + 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(*s) /* let the star eat up one character */ - s++; - else - return CURL_FNMATCH_NOMATCH; - } - else if(*p == '?') { - if(ISPRINT(*s)) { - s++; - p++; + if(*p == '?') { + if(!*s++) + return CURL_FNMATCH_NOMATCH; } - else if(*s == '\0') - return CURL_FNMATCH_NOMATCH; - else - return CURL_FNMATCH_FAIL; /* cannot deal with other character */ + else if(*p != '*') + break; } - else if(*p == '\0') { - if(*s == '\0') + /* 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; - else - return CURL_FNMATCH_NOMATCH; } - else if(*p == '\\') { - state = CURLFNM_LOOP_BACKSLASH; + 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++; - } - else if(*p == '[') { - unsigned char *pp = p+1; /* cannot handle with pointer to register */ - if(setcharset(&pp, charset)) { - int found = FALSE; - 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(*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(charset[CURLFNM_NEGATE]) + found = !found; - if(found) { - p = pp+1; - s++; - memset(charset, 0, CURLFNM_CHSET_SIZE); - } - else - return CURL_FNMATCH_NOMATCH; - } - else - return CURL_FNMATCH_FAIL; - } - else { - if(*p++ != *s++) - return CURL_FNMATCH_NOMATCH; - } - break; - case CURLFNM_LOOP_BACKSLASH: - if(ISPRINT(*p)) { - if(*p++ == *s++) - state = CURLFNM_LOOP_DEFAULT; - else + if(!found) return CURL_FNMATCH_NOMATCH; + p = pp + 1; + s++; + break; } - else - return CURL_FNMATCH_FAIL; + /* Syntax error in set; mismatch! */ + return CURL_FNMATCH_NOMATCH; + + default: + if(*p++ != *s++) + return CURL_FNMATCH_NOMATCH; break; } } @@ -422,5 +359,32 @@ int Curl_fnmatch(void *ptr, const char *pattern, const char *string) if(!pattern || !string) { return CURL_FNMATCH_FAIL; } - return loop((unsigned char *)pattern, (unsigned char *)string); + return loop((unsigned char *)pattern, (unsigned char *)string, 2); } +#else +#include +/* + * @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/contrib/curl/lib/curl_fnmatch.h b/contrib/curl/lib/curl_fnmatch.h index 69ffe392fd..b8c2a4353c 100644 --- a/contrib/curl/lib/curl_fnmatch.h +++ b/contrib/curl/lib/curl_fnmatch.h @@ -7,11 +7,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2009, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -20,6 +20,8 @@ * 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 @@ -29,7 +31,7 @@ /* 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. + * please note that there is not implemented UTF/Unicode support. * * Implemented features: * '?' notation, does not match UTF characters diff --git a/contrib/curl/lib/curl_get_line.c b/contrib/curl/lib/curl_get_line.c new file mode 100644 index 0000000000..100207331d --- /dev/null +++ b/contrib/curl/lib/curl_get_line.c @@ -0,0 +1,77 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#if !defined(CURL_DISABLE_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 end + * newlines. + */ +int Curl_get_line(struct dynbuf *buf, FILE *input) +{ + CURLcode result; + char buffer[128]; + Curl_dyn_reset(buf); + while(1) { + char *b = fgets(buffer, sizeof(buffer), input); + + if(b) { + size_t rlen = strlen(b); + + if(!rlen) + break; + + result = Curl_dyn_addn(buf, b, rlen); + if(result) + /* too long line or out of memory */ + return 0; /* error */ + + else if(b[rlen-1] == '\n') + /* end of the line */ + return 1; /* all good */ + + else if(feof(input)) { + /* append a newline */ + result = Curl_dyn_addn(buf, "\n", 1); + if(result) + /* too long line or out of memory */ + return 0; /* error */ + return 1; /* all good */ + } + } + else + break; + } + return 0; +} + +#endif /* if not disabled */ diff --git a/contrib/curl/lib/curl_get_line.h b/contrib/curl/lib/curl_get_line.h new file mode 100644 index 0000000000..7907cde880 --- /dev/null +++ b/contrib/curl/lib/curl_get_line.h @@ -0,0 +1,32 @@ +#ifndef HEADER_CURL_GET_LINE_H +#define HEADER_CURL_GET_LINE_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "dynbuf.h" + +/* Curl_get_line() returns complete lines that end with a newline. */ +int Curl_get_line(struct dynbuf *buf, FILE *input); + +#endif /* HEADER_CURL_GET_LINE_H */ diff --git a/contrib/curl/lib/curl_gethostname.c b/contrib/curl/lib/curl_gethostname.c index 8337c72e81..617a8ad52f 100644 --- a/contrib/curl/lib/curl_gethostname.c +++ b/contrib/curl/lib/curl_gethostname.c @@ -5,11 +5,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2016, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -18,6 +18,8 @@ * 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" @@ -26,29 +28,20 @@ /* * Curl_gethostname() is a wrapper around gethostname() which allows - * overriding the host name that the function would normally return. + * overriding the hostname 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 + * For libcurl debug enabled builds hostname overriding takes place * when environment variable CURL_GETHOSTNAME is set, using the value - * held by the variable to override returned host name. + * held by the variable to override returned hostname. * * 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 *name, GETHOSTNAME_TYPE_ARG2 namelen) +int Curl_gethostname(char * const name, GETHOSTNAME_TYPE_ARG2 namelen) { #ifndef HAVE_GETHOSTNAME @@ -63,10 +56,13 @@ int Curl_gethostname(char *name, GETHOSTNAME_TYPE_ARG2 namelen) #ifdef DEBUGBUILD - /* Override host name when environment variable CURL_GETHOSTNAME is set */ + /* Override hostname when environment variable CURL_GETHOSTNAME is set */ const char *force_hostname = getenv("CURL_GETHOSTNAME"); if(force_hostname) { - strncpy(name, force_hostname, namelen); + if(strlen(force_hostname) < (size_t)namelen) + strcpy(name, force_hostname); + else + return 1; /* can't do it */ err = 0; } else { @@ -76,9 +72,6 @@ int Curl_gethostname(char *name, GETHOSTNAME_TYPE_ARG2 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); diff --git a/contrib/curl/lib/curl_gethostname.h b/contrib/curl/lib/curl_gethostname.h index 07517c5359..9281d9c242 100644 --- a/contrib/curl/lib/curl_gethostname.h +++ b/contrib/curl/lib/curl_gethostname.h @@ -7,11 +7,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2010, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -20,12 +20,14 @@ * 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 *name, GETHOSTNAME_TYPE_ARG2 namelen); +int Curl_gethostname(char * const name, GETHOSTNAME_TYPE_ARG2 namelen); #endif /* HEADER_CURL_GETHOSTNAME_H */ diff --git a/contrib/curl/lib/curl_gssapi.c b/contrib/curl/lib/curl_gssapi.c index 83f3fa0c43..c6fe1256b2 100644 --- a/contrib/curl/lib/curl_gssapi.c +++ b/contrib/curl/lib/curl_gssapi.c @@ -5,11 +5,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 2011 - 2016, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -18,6 +18,8 @@ * 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" @@ -27,10 +29,23 @@ #include "curl_gssapi.h" #include "sendf.h" -static char spnego_oid_bytes[] = "\x2b\x06\x01\x05\x05\x02"; -gss_OID_desc Curl_spnego_mech_oid = { 6, &spnego_oid_bytes }; -static char krb5_oid_bytes[] = "\x2a\x86\x48\x86\xf7\x12\x01\x02\x02"; -gss_OID_desc Curl_krb5_mech_oid = { 9, &krb5_oid_bytes }; +/* 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, @@ -53,8 +68,8 @@ OM_uint32 Curl_gss_init_sec_context( #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\n"); + infof(data, "WARNING: support for CURLGSSAPI_DELEGATION_POLICY_FLAG not " + "compiled in"); #endif } @@ -82,7 +97,7 @@ static size_t display_gss_error(OM_uint32 status, int type, OM_uint32 maj_stat; OM_uint32 min_stat; OM_uint32 msg_ctx = 0; - gss_buffer_desc status_string; + gss_buffer_desc status_string = GSS_C_EMPTY_BUFFER; do { maj_stat = gss_display_status(&min_stat, @@ -91,13 +106,15 @@ static size_t display_gss_error(OM_uint32 status, int type, GSS_C_NO_OID, &msg_ctx, &status_string); - if(GSS_LOG_BUFFER_LEN > len + status_string.length + 3) { - len += snprintf(buf + len, GSS_LOG_BUFFER_LEN - len, - "%.*s. ", (int)status_string.length, - (char *)status_string.value); + 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 != 0); + } while(!GSS_ERROR(maj_stat) && msg_ctx); return len; } @@ -125,7 +142,11 @@ void Curl_gss_log_error(struct Curl_easy *data, const char *prefix, display_gss_error(minor, GSS_C_MECH_CODE, buf, len); - infof(data, "%s%s\n", prefix, buf); + infof(data, "%s%s", prefix, buf); +#ifdef CURL_DISABLE_VERBOSE_STRINGS + (void)data; + (void)prefix; +#endif } #endif /* HAVE_GSSAPI */ diff --git a/contrib/curl/lib/curl_gssapi.h b/contrib/curl/lib/curl_gssapi.h index 9700a28174..7b9a534ea2 100644 --- a/contrib/curl/lib/curl_gssapi.h +++ b/contrib/curl/lib/curl_gssapi.h @@ -7,11 +7,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 2011 - 2015, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -20,25 +20,14 @@ * 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 - -#ifdef HAVE_GSSGNU -# include -#elif defined HAVE_GSSMIT - /* MIT style */ -# include -# include -# include -#else - /* Heimdal-style */ -# include -#endif - extern gss_OID_desc Curl_spnego_mech_oid; extern gss_OID_desc Curl_krb5_mech_oid; @@ -71,5 +60,4 @@ void Curl_gss_log_error(struct Curl_easy *data, const char *prefix, #define GSSAUTH_P_PRIVACY 4 #endif /* HAVE_GSSAPI */ - #endif /* HEADER_CURL_GSSAPI_H */ diff --git a/contrib/curl/lib/curl_hmac.h b/contrib/curl/lib/curl_hmac.h index 756dc9e4cd..7a5387a948 100644 --- a/contrib/curl/lib/curl_hmac.h +++ b/contrib/curl/lib/curl_hmac.h @@ -7,11 +7,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2016, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -20,11 +20,19 @@ * 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_CRYPTO_AUTH +#if (defined(USE_CURL_NTLM_CORE) && !defined(USE_WINDOWS_SSPI)) \ + || !defined(CURL_DISABLE_AWS) || !defined(CURL_DISABLE_DIGEST_AUTH) \ + || defined(USE_LIBSSH2) + +#include -typedef void (* HMAC_hinit_func)(void *context); +#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); @@ -32,35 +40,38 @@ typedef void (* HMAC_hfinal_func)(unsigned char *result, void *context); /* Per-hash function HMAC parameters. */ - -typedef struct { - HMAC_hinit_func hmac_hinit; /* Initialize context procedure. */ +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_params; +}; /* HMAC computation context. */ - -typedef struct { - const HMAC_params *hmac_hash; /* Hash function definition. */ +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. */ -} HMAC_context; +}; /* Prototypes. */ - -HMAC_context * Curl_HMAC_init(const HMAC_params *hashparams, - const unsigned char *key, - unsigned int keylen); -int Curl_HMAC_update(HMAC_context *context, +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(HMAC_context *context, unsigned char *result); +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 diff --git a/contrib/curl/lib/curl_sec.h b/contrib/curl/lib/curl_krb5.h similarity index 74% rename from contrib/curl/lib/curl_sec.h rename to contrib/curl/lib/curl_krb5.h index 7bdde269b1..ccf6b96a87 100644 --- a/contrib/curl/lib/curl_sec.h +++ b/contrib/curl/lib/curl_krb5.h @@ -1,5 +1,5 @@ -#ifndef HEADER_CURL_SECURITY_H -#define HEADER_CURL_SECURITY_H +#ifndef HEADER_CURL_KRB5_H +#define HEADER_CURL_KRB5_H /*************************************************************************** * _ _ ____ _ * Project ___| | | | _ \| | @@ -7,11 +7,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2016, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -20,16 +20,17 @@ * 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 connectdata *); + int (*auth)(void *, struct Curl_easy *data, struct connectdata *); void (*end)(void *); int (*check_prot)(void *, int); - int (*overhead)(void *, int, int); int (*encode)(void *, const void *, int, int, void **); int (*decode)(void *, void *, int, int, struct connectdata *); }; @@ -39,13 +40,13 @@ struct Curl_sec_client_mech { #define AUTH_ERROR 2 #ifdef HAVE_GSSAPI -int Curl_sec_read_msg(struct connectdata *conn, char *, +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 connectdata *); +CURLcode Curl_sec_login(struct Curl_easy *, struct connectdata *); int Curl_sec_request_prot(struct connectdata *conn, const char *level); - -extern struct Curl_sec_client_mech Curl_krb5_client_mech; +#else +#define Curl_sec_end(x) #endif -#endif /* HEADER_CURL_SECURITY_H */ +#endif /* HEADER_CURL_KRB5_H */ diff --git a/contrib/curl/lib/curl_ldap.h b/contrib/curl/lib/curl_ldap.h index 27d03810fb..8a1d807ed9 100644 --- a/contrib/curl/lib/curl_ldap.h +++ b/contrib/curl/lib/curl_ldap.h @@ -7,11 +7,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2010, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -20,6 +20,8 @@ * 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; @@ -32,4 +34,3 @@ extern const struct Curl_handler Curl_handler_ldaps; #endif #endif /* HEADER_CURL_LDAP_H */ - diff --git a/contrib/curl/lib/curl_md4.h b/contrib/curl/lib/curl_md4.h index 8c26d1222a..4706e49578 100644 --- a/contrib/curl/lib/curl_md4.h +++ b/contrib/curl/lib/curl_md4.h @@ -7,11 +7,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2014, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -20,16 +20,20 @@ * 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 + +#if defined(USE_CURL_NTLM_CORE) -/* NSS and OS/400 crypto library do not provide the MD4 hash algorithm, so - * that we have a local implementation of it */ -#if defined(USE_NSS) || defined(USE_OS400CRYPTO) +#define MD4_DIGEST_LENGTH 16 -void Curl_md4it(unsigned char *output, const unsigned char *input, size_t len); +CURLcode Curl_md4it(unsigned char *output, const unsigned char *input, + const size_t len); -#endif /* defined(USE_NSS) || defined(USE_OS400CRYPTO) */ +#endif /* defined(USE_CURL_NTLM_CORE) */ #endif /* HEADER_CURL_MD4_H */ diff --git a/contrib/curl/lib/curl_md5.h b/contrib/curl/lib/curl_md5.h index 5f70c96346..61671c306a 100644 --- a/contrib/curl/lib/curl_md5.h +++ b/contrib/curl/lib/curl_md5.h @@ -7,11 +7,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2010, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -20,43 +20,47 @@ * 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_CRYPTO_AUTH +#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 void (* Curl_MD5_init_func)(void *context); +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); -typedef struct { +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) */ -} MD5_params; +}; -typedef struct { - const MD5_params *md5_hash; /* Hash function definition */ +struct MD5_context { + const struct MD5_params *md5_hash; /* Hash function definition */ void *md5_hashctx; /* Hash function context */ -} MD5_context; +}; -extern const MD5_params Curl_DIGEST_MD5[1]; -extern const HMAC_params Curl_HMAC_MD5[1]; +extern const struct MD5_params Curl_DIGEST_MD5[1]; +extern const struct HMAC_params Curl_HMAC_MD5[1]; -void Curl_md5it(unsigned char *output, - const unsigned char *input); +CURLcode Curl_md5it(unsigned char *output, const unsigned char *input, + const size_t len); -MD5_context * Curl_MD5_init(const MD5_params *md5params); -int Curl_MD5_update(MD5_context *context, - const unsigned char *data, - unsigned int len); -int Curl_MD5_final(MD5_context *context, unsigned char *result); +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 diff --git a/contrib/curl/lib/curl_memory.h b/contrib/curl/lib/curl_memory.h index 6f792fffda..7f110dab7d 100644 --- a/contrib/curl/lib/curl_memory.h +++ b/contrib/curl/lib/curl_memory.h @@ -7,11 +7,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2016, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -20,6 +20,8 @@ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY * KIND, either express or implied. * + * SPDX-License-Identifier: curl + * ***************************************************************************/ /* @@ -39,7 +41,7 @@ * * File lib/strdup.c is an exception, given that it provides a strdup * clone implementation while using malloc. Extra care needed inside - * this one. TODO: revisit this paragraph and related code. + * this one. * * The need for curl_memory.h inclusion is due to libcurl's feature * of allowing library user to provide memory replacement functions, @@ -50,38 +52,69 @@ * mentioned above will compile without any indication, but it will * trigger weird memory related issues at runtime. * - * OTOH some source files from 'lib' subdirectory may additionally be - * used directly as source code when using some curlx_ functions by - * third party programs that don't even use libcurl at all. When using - * these source files in this way it is necessary these are compiled - * with CURLX_NO_MEMORY_CALLBACKS defined, in order to ensure that no - * attempt of calling libcurl's memory callbacks is done from code - * which can not use this machinery. - * - * Notice that libcurl's 'memory tracking' system works chaining into - * the memory callback machinery. This implies that when compiling - * 'lib' source files with CURLX_NO_MEMORY_CALLBACKS defined this file - * disengages usage of libcurl's 'memory tracking' system, defining - * MEMDEBUG_NODEFINES and overriding CURLDEBUG purpose. - * - * CURLX_NO_MEMORY_CALLBACKS takes precedence over CURLDEBUG. This is - * done in order to allow building a 'memory tracking' enabled libcurl - * and at the same time allow building programs which do not use it. - * - * Programs and libraries in 'tests' subdirectories have specific - * purposes and needs, and as such each one will use whatever fits - * best, depending additionally wether it links with libcurl or not. - * - * Caveat emptor. Proper curlx_* separation is a work in progress - * the same as CURLX_NO_MEMORY_CALLBACKS usage, some adjustments may - * still be required. IOW don't use them yet, there are sharp edges. */ #ifdef HEADER_CURL_MEMDEBUG_H -#error "Header memdebug.h shall not be included before curl_memory.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 -#ifndef CURLX_NO_MEMORY_CALLBACKS +#undef socket +#undef accept +#ifdef HAVE_SOCKETPAIR +#undef socketpair +#endif + +#ifndef CURL_NO_GETADDRINFO_OVERRIDE +#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 */ +#endif /* !CURL_NO_GETADDRINFO_OVERRIDE */ + +/* 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 */ /* @@ -103,7 +136,7 @@ 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) +#if defined(_WIN32) && defined(UNICODE) extern curl_wcsdup_callback Curl_cwcsdup; #endif @@ -129,7 +162,7 @@ extern curl_wcsdup_callback Curl_cwcsdup; #undef free #define free(ptr) Curl_cfree(ptr) -#ifdef WIN32 +#ifdef _WIN32 # ifdef UNICODE # undef wcsdup # define wcsdup(ptr) Curl_cwcsdup(ptr) @@ -144,13 +177,4 @@ extern curl_wcsdup_callback Curl_cwcsdup; #endif #endif /* CURLDEBUG */ - -#else /* CURLX_NO_MEMORY_CALLBACKS */ - -#ifndef MEMDEBUG_NODEFINES -#define MEMDEBUG_NODEFINES -#endif - -#endif /* CURLX_NO_MEMORY_CALLBACKS */ - #endif /* HEADER_CURL_MEMORY_H */ diff --git a/contrib/curl/lib/curl_memrchr.c b/contrib/curl/lib/curl_memrchr.c index c521497b21..c6d55f1042 100644 --- a/contrib/curl/lib/curl_memrchr.c +++ b/contrib/curl/lib/curl_memrchr.c @@ -5,11 +5,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2016, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -18,6 +18,8 @@ * 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" @@ -31,6 +33,9 @@ #include "memdebug.h" #ifndef HAVE_MEMRCHR +#if (!defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_COOKIES)) || \ + defined(USE_OPENSSL) || \ + defined(USE_SCHANNEL) /* * Curl_memrchr() @@ -44,18 +49,20 @@ void * Curl_memrchr(const void *s, int c, size_t n) { - const unsigned char *p = s; - const unsigned char *q = s; + if(n > 0) { + const unsigned char *p = s; + const unsigned char *q = s; - p += n - 1; + p += n - 1; - while(p >= q) { - if(*p == (unsigned char)c) - return (void *)p; - p--; + while(p >= q) { + if(*p == (unsigned char)c) + return (void *)p; + p--; + } } - return NULL; } +#endif #endif /* HAVE_MEMRCHR */ diff --git a/contrib/curl/lib/curl_memrchr.h b/contrib/curl/lib/curl_memrchr.h index 747509c45a..67a21ef361 100644 --- a/contrib/curl/lib/curl_memrchr.h +++ b/contrib/curl/lib/curl_memrchr.h @@ -7,11 +7,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2009, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -20,25 +20,29 @@ * 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 -#ifdef HAVE_STRING_H -# include -#endif +#include #ifdef HAVE_STRINGS_H # include #endif #else /* HAVE_MEMRCHR */ +#if (!defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_COOKIES)) || \ + defined(USE_OPENSSL) || \ + defined(USE_SCHANNEL) void *Curl_memrchr(const void *s, int c, size_t n); #define memrchr(x,y,z) Curl_memrchr((x),(y),(z)) +#endif #endif /* HAVE_MEMRCHR */ #endif /* HEADER_CURL_MEMRCHR_H */ diff --git a/contrib/curl/lib/curl_multibyte.c b/contrib/curl/lib/curl_multibyte.c index e78bb5002e..86ac74ff4b 100644 --- a/contrib/curl/lib/curl_multibyte.c +++ b/contrib/curl/lib/curl_multibyte.c @@ -5,11 +5,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2015, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -18,26 +18,29 @@ * 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 +/* + * 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. + */ -#if defined(USE_WIN32_IDN) || ((defined(USE_WINDOWS_SSPI) || \ - defined(USE_WIN32_LDAP)) && defined(UNICODE)) +#include "curl_setup.h" - /* - * MultiByte conversions using Windows kernel32 library. - */ +#if defined(_WIN32) #include "curl_multibyte.h" -#include "curl_memory.h" -/* The last #include file should be: */ -#include "memdebug.h" +/* + * MultiByte conversions using Windows kernel32 library. + */ -wchar_t *Curl_convert_UTF8_to_wchar(const char *str_utf8) +wchar_t *curlx_convert_UTF8_to_wchar(const char *str_utf8) { wchar_t *str_w = NULL; @@ -59,18 +62,18 @@ wchar_t *Curl_convert_UTF8_to_wchar(const char *str_utf8) return str_w; } -char *Curl_convert_wchar_to_UTF8(const wchar_t *str_w) +char *curlx_convert_wchar_to_UTF8(const wchar_t *str_w) { char *str_utf8 = NULL; if(str_w) { - int str_utf8_len = WideCharToMultiByte(CP_UTF8, 0, str_w, -1, NULL, - 0, NULL, NULL); - if(str_utf8_len > 0) { - str_utf8 = malloc(str_utf8_len * sizeof(wchar_t)); + 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, str_utf8_len, - NULL, FALSE) == 0) { + if(WideCharToMultiByte(CP_UTF8, 0, str_w, -1, str_utf8, bytes, + NULL, NULL) == 0) { free(str_utf8); return NULL; } @@ -81,4 +84,79 @@ char *Curl_convert_wchar_to_UTF8(const wchar_t *str_w) return str_utf8; } -#endif /* USE_WIN32_IDN || ((USE_WINDOWS_SSPI || USE_WIN32_LDAP) && UNICODE) */ +#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 +} + +#endif /* USE_WIN32_LARGE_FILES || USE_WIN32_SMALL_FILES */ diff --git a/contrib/curl/lib/curl_multibyte.h b/contrib/curl/lib/curl_multibyte.h index 615f5c086c..dec384e2fe 100644 --- a/contrib/curl/lib/curl_multibyte.h +++ b/contrib/curl/lib/curl_multibyte.h @@ -7,11 +7,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2015, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -20,49 +20,45 @@ * 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_WIN32_IDN) || ((defined(USE_WINDOWS_SSPI) || \ - defined(USE_WIN32_LDAP)) && defined(UNICODE)) +#if defined(_WIN32) /* * MultiByte conversions using Windows kernel32 library. */ -wchar_t *Curl_convert_UTF8_to_wchar(const char *str_utf8); -char *Curl_convert_wchar_to_UTF8(const wchar_t *str_w); - -#endif /* USE_WIN32_IDN || ((USE_WINDOWS_SSPI || USE_WIN32_LDAP) && UNICODE) */ - - -#if defined(USE_WIN32_IDN) || defined(USE_WINDOWS_SSPI) || \ - defined(USE_WIN32_LDAP) +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 Curl_convert_UTF8_to_tchar(), Curl_convert_tchar_to_UTF8() - * and Curl_unicodefree() main purpose is to minimize the number of + * 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. + * 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. * - * When building with UNICODE defined, this two macros - * Curl_convert_UTF8_to_tchar() and Curl_convert_tchar_to_UTF8() - * return a pointer to a newly allocated memory area holding result. - * When the result is no longer needed, allocated memory is intended - * to be free'ed with Curl_unicodefree(). + * Allocated memory should be free'd with curlx_unicodefree(). * - * When building without UNICODE defined, this macros - * Curl_convert_UTF8_to_tchar() and Curl_convert_tchar_to_UTF8() - * return the pointer received as argument. Curl_unicodefree() does - * no actual free'ing of this pointer it is simply set to NULL. + * Note: Because these are curlx functions their memory usage is not tracked + * by the curl memory tracker memdebug. you will notice that curlx + * function-like macros call free and strdup in parentheses, eg (strdup)(ptr), + * and that is to ensure that the curl memdebug override macros do not replace + * them. */ -#ifdef UNICODE +#if defined(UNICODE) && defined(_WIN32) -#define Curl_convert_UTF8_to_tchar(ptr) Curl_convert_UTF8_to_wchar((ptr)) -#define Curl_convert_tchar_to_UTF8(ptr) Curl_convert_wchar_to_UTF8((ptr)) -#define Curl_unicodefree(ptr) \ - do {if((ptr)) {free((ptr)); (ptr) = NULL;}} WHILE_FALSE +#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; @@ -73,10 +69,8 @@ typedef union { #else -#define Curl_convert_UTF8_to_tchar(ptr) (ptr) -#define Curl_convert_tchar_to_UTF8(ptr) (ptr) -#define Curl_unicodefree(ptr) \ - do {(ptr) = NULL;} WHILE_FALSE +#define curlx_convert_UTF8_to_tchar(ptr) (strdup)(ptr) +#define curlx_convert_tchar_to_UTF8(ptr) (strdup)(ptr) typedef union { char *tchar_ptr; @@ -85,8 +79,14 @@ typedef union { const unsigned char *const_tbyte_ptr; } xcharp_u; -#endif /* UNICODE */ +#endif /* UNICODE && _WIN32 */ -#endif /* USE_WIN32_IDN || USE_WINDOWS_SSPI || USE_WIN32_LDAP */ +#define curlx_unicodefree(ptr) \ + do { \ + if(ptr) { \ + (free)(ptr); \ + (ptr) = NULL; \ + } \ + } while(0) #endif /* HEADER_CURL_MULTIBYTE_H */ diff --git a/contrib/curl/lib/curl_ntlm_core.c b/contrib/curl/lib/curl_ntlm_core.c index e02983ce6a..cb8e78a924 100644 --- a/contrib/curl/lib/curl_ntlm_core.c +++ b/contrib/curl/lib/curl_ntlm_core.c @@ -5,11 +5,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2016, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -18,41 +18,68 @@ * 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) +#if defined(USE_CURL_NTLM_CORE) /* * NTLM details: * - * https://davenport.sourceforge.io/ntlm.html + * https://davenport.sourceforge.net/ntlm.html * https://www.innovation.ch/java/ntlm.html */ -#if !defined(USE_WINDOWS_SSPI) || defined(USE_WIN32_CRYPTO) +/* 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. + */ -#ifdef USE_OPENSSL +#if defined(USE_OPENSSL) + #include + #if !defined(OPENSSL_NO_DES) && !defined(OPENSSL_NO_DEPRECATED_3_0) + #define USE_OPENSSL_DES + #endif +#elif defined(USE_WOLFSSL) + #include + #if !defined(NO_DES3) + #define USE_OPENSSL_DES + #endif +#endif -# ifdef USE_OPENSSL -# include -# ifndef OPENSSL_NO_MD4 -# include -# endif -# include -# include -# include -# else -# include -# ifndef OPENSSL_NO_MD4 -# include -# endif -# include -# include -# include -# endif -# if (OPENSSL_VERSION_NUMBER < 0x00907001L) +#if defined(USE_OPENSSL_DES) + +#if defined(USE_OPENSSL) +# include +# include +# include +# include +#else +# include +# include +# include +# include +#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 @@ -60,51 +87,38 @@ # 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_NETTLE) - -# include -# include - #elif defined(USE_GNUTLS) -# include -# define MD5_DIGEST_LENGTH 16 -# define MD4_DIGEST_LENGTH 16 +# include #elif defined(USE_MBEDTLS) # include -# include - -#elif defined(USE_NSS) -# include -# include -# include -# include "curl_md4.h" -# define MD5_DIGEST_LENGTH MD5_LENGTH - -#elif defined(USE_DARWINSSL) +#elif defined(USE_SECTRANSP) # include # include #elif defined(USE_OS400CRYPTO) # include "cipher.mih" /* mih/cipher */ -# include "curl_md4.h" #elif defined(USE_WIN32_CRYPTO) # include #else -# error "Can't compile NTLM support without a crypto library." +# error "cannot compile NTLM support without a crypto library with DES." +# define CURL_NTLM_NOT_SUPPORTED #endif #include "urldata.h" -#include "non-ascii.h" #include "strcase.h" #include "curl_ntlm_core.h" #include "curl_md5.h" @@ -112,33 +126,35 @@ #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 NTLM_HMAC_MD5_LEN (16) #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); + key[0] = (char)key_56[0]; + key[1] = (char)(((key_56[0] << 7) & 0xFF) | (key_56[1] >> 1)); + key[2] = (char)(((key_56[1] << 6) & 0xFF) | (key_56[2] >> 2)); + key[3] = (char)(((key_56[2] << 5) & 0xFF) | (key_56[3] >> 3)); + key[4] = (char)(((key_56[3] << 4) & 0xFF) | (key_56[4] >> 4)); + key[5] = (char)(((key_56[4] << 3) & 0xFF) | (key_56[5] >> 5)); + key[6] = (char)(((key_56[5] << 2) & 0xFF) | (key_56[6] >> 6)); + key[7] = (char) ((key_56[6] << 1) & 0xFF); } +#endif -#ifdef USE_OPENSSL +#if defined(USE_OPENSSL_DES) /* - * Turns a 56 bit key into the 64 bit, odd parity key and sets the key. The + * Turns a 56-bit key into a 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, @@ -146,24 +162,24 @@ static void setup_des_key(const unsigned char *key_56, { DES_cblock key; - /* Expand the 56-bit key to 64-bits */ + /* 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(&key, ks); + DES_set_key_unchecked(&key, ks); } -#elif defined(USE_GNUTLS_NETTLE) +#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 */ + /* Expand the 56-bit key to 64 bits */ extend_key_56_to_64(key_56, key); /* Set the key parity to odd */ @@ -173,26 +189,6 @@ static void setup_des_key(const unsigned char *key_56, des_set_key(des, (const uint8_t *) key); } -#elif defined(USE_GNUTLS) - -/* - * Turns a 56 bit key into the 64 bit, odd parity key and sets the key. - */ -static void setup_des_key(const unsigned char *key_56, - gcry_cipher_hd_t *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 */ - gcry_cipher_setkey(*des, key, sizeof(key)); -} - #elif defined(USE_MBEDTLS) static bool encrypt_des(const unsigned char *in, unsigned char *out, @@ -201,7 +197,7 @@ static bool encrypt_des(const unsigned char *in, unsigned char *out, mbedtls_des_context ctx; char key[8]; - /* Expand the 56-bit key to 64-bits */ + /* Expand the 56-bit key to 64 bits */ extend_key_56_to_64(key_56, key); /* Set the key parity to odd */ @@ -213,72 +209,7 @@ static bool encrypt_des(const unsigned char *in, unsigned char *out, return mbedtls_des_crypt_ecb(&ctx, in, out) == 0; } -#elif defined(USE_NSS) - -/* - * Expands a 56 bit key KEY_56 to 64 bit and encrypts 64 bit of data, using - * the expanded key. The caller is responsible for giving 64 bit of valid - * data is IN and (at least) 64 bit large buffer as OUT. - */ -static bool encrypt_des(const unsigned char *in, unsigned char *out, - const unsigned char *key_56) -{ - const CK_MECHANISM_TYPE mech = CKM_DES_ECB; /* DES cipher in ECB mode */ - PK11SlotInfo *slot = NULL; - char key[8]; /* expanded 64 bit key */ - SECItem key_item; - PK11SymKey *symkey = NULL; - SECItem *param = NULL; - PK11Context *ctx = NULL; - int out_len; /* not used, required by NSS */ - bool rv = FALSE; - - /* use internal slot for DES encryption (requires NSS to be initialized) */ - slot = PK11_GetInternalKeySlot(); - if(!slot) - return FALSE; - - /* 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)); - - /* Import the key */ - key_item.data = (unsigned char *)key; - key_item.len = sizeof(key); - symkey = PK11_ImportSymKey(slot, mech, PK11_OriginUnwrap, CKA_ENCRYPT, - &key_item, NULL); - if(!symkey) - goto fail; - - /* Create the DES encryption context */ - param = PK11_ParamFromIV(mech, /* no IV in ECB mode */ NULL); - if(!param) - goto fail; - ctx = PK11_CreateContextBySymKey(mech, CKA_ENCRYPT, symkey, param); - if(!ctx) - goto fail; - - /* Perform the encryption */ - if(SECSuccess == PK11_CipherOp(ctx, out, &out_len, /* outbuflen */ 8, - (unsigned char *)in, /* inbuflen */ 8) - && SECSuccess == PK11_Finalize(ctx)) - rv = /* all OK */ TRUE; - -fail: - /* cleanup */ - if(ctx) - PK11_DestroyContext(ctx, PR_TRUE); - if(symkey) - PK11_FreeSymKey(symkey); - if(param) - SECITEM_FreeItem(param, PR_TRUE); - PK11_FreeSlot(slot); - return rv; -} - -#elif defined(USE_DARWINSSL) +#elif defined(USE_SECTRANSP) static bool encrypt_des(const unsigned char *in, unsigned char *out, const unsigned char *key_56) @@ -287,7 +218,7 @@ static bool encrypt_des(const unsigned char *in, unsigned char *out, size_t out_len; CCCryptorStatus err; - /* Expand the 56-bit key to 64-bits */ + /* Expand the 56-bit key to 64 bits */ extend_key_56_to_64(key_56, key); /* Set the key parity to odd */ @@ -313,7 +244,7 @@ static bool encrypt_des(const unsigned char *in, unsigned char *out, ctl.Func_ID = ENCRYPT_ONLY; ctl.Data_Len = sizeof(key); - /* Expand the 56-bit key to 64-bits */ + /* Expand the 56-bit key to 64 bits */ extend_key_56_to_64(key_56, ctl.Crypto_Key); /* Set the key parity to odd */ @@ -341,7 +272,7 @@ static bool encrypt_des(const unsigned char *in, unsigned char *out, /* Acquire the crypto provider */ if(!CryptAcquireContext(&hprov, NULL, NULL, PROV_RSA_FULL, - CRYPT_VERIFYCONTEXT)) + CRYPT_VERIFYCONTEXT | CRYPT_SILENT)) return FALSE; /* Setup the key blob structure */ @@ -351,7 +282,7 @@ static bool encrypt_des(const unsigned char *in, unsigned char *out, blob.hdr.aiKeyAlg = CALG_DES; blob.len = sizeof(blob.key); - /* Expand the 56-bit key to 64-bits */ + /* Expand the 56-bit key to 64 bits */ extend_key_56_to_64(key_56, blob.key); /* Set the key parity to odd */ @@ -386,7 +317,7 @@ void Curl_ntlm_core_lm_resp(const unsigned char *keys, const unsigned char *plaintext, unsigned char *results) { -#ifdef USE_OPENSSL +#if defined(USE_OPENSSL_DES) DES_key_schedule ks; setup_des_key(keys, DESKEY(ks)); @@ -400,7 +331,7 @@ void Curl_ntlm_core_lm_resp(const unsigned char *keys, 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_NETTLE) +#elif defined(USE_GNUTLS) struct des_ctx des; setup_des_key(keys, &des); des_encrypt(&des, 8, results, plaintext); @@ -408,60 +339,39 @@ void Curl_ntlm_core_lm_resp(const unsigned char *keys, des_encrypt(&des, 8, results + 8, plaintext); setup_des_key(keys + 14, &des); des_encrypt(&des, 8, results + 16, plaintext); -#elif defined(USE_GNUTLS) - gcry_cipher_hd_t des; - - gcry_cipher_open(&des, GCRY_CIPHER_DES, GCRY_CIPHER_MODE_ECB, 0); - setup_des_key(keys, &des); - gcry_cipher_encrypt(des, results, 8, plaintext, 8); - gcry_cipher_close(des); - - gcry_cipher_open(&des, GCRY_CIPHER_DES, GCRY_CIPHER_MODE_ECB, 0); - setup_des_key(keys + 7, &des); - gcry_cipher_encrypt(des, results + 8, 8, plaintext, 8); - gcry_cipher_close(des); - - gcry_cipher_open(&des, GCRY_CIPHER_DES, GCRY_CIPHER_MODE_ECB, 0); - setup_des_key(keys + 14, &des); - gcry_cipher_encrypt(des, results + 16, 8, plaintext, 8); - gcry_cipher_close(des); -#elif defined(USE_MBEDTLS) || defined(USE_NSS) || defined(USE_DARWINSSL) \ +#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(struct Curl_easy *data, - const char *password, +CURLcode Curl_ntlm_core_mk_lm_hash(const char *password, unsigned char *lmbuffer /* 21 bytes */) { - CURLcode result; 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); - /* - * The LanManager hashed password needs to be created using the - * password in the network encoding not the host encoding. - */ - result = Curl_convert_to_network(data, (char *)pw, 14); - if(result) - return result; - { /* Create LanManager hashed password. */ -#ifdef USE_OPENSSL +#if defined(USE_OPENSSL_DES) DES_key_schedule ks; setup_des_key(pw, DESKEY(ks)); @@ -471,25 +381,13 @@ CURLcode Curl_ntlm_core_mk_lm_hash(struct Curl_easy *data, 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_NETTLE) +#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_GNUTLS) - gcry_cipher_hd_t des; - - gcry_cipher_open(&des, GCRY_CIPHER_DES, GCRY_CIPHER_MODE_ECB, 0); - setup_des_key(pw, &des); - gcry_cipher_encrypt(des, lmbuffer, 8, magic, 8); - gcry_cipher_close(des); - - gcry_cipher_open(&des, GCRY_CIPHER_DES, GCRY_CIPHER_MODE_ECB, 0); - setup_des_key(pw + 7, &des); - gcry_cipher_encrypt(des, lmbuffer + 8, 8, magic, 8); - gcry_cipher_close(des); -#elif defined(USE_MBEDTLS) || defined(USE_NSS) || defined(USE_DARWINSSL) \ +#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); @@ -501,7 +399,6 @@ CURLcode Curl_ntlm_core_mk_lm_hash(struct Curl_easy *data, return CURLE_OK; } -#if USE_NTRESPONSES static void ascii_to_unicode_le(unsigned char *dest, const char *src, size_t srclen) { @@ -512,110 +409,98 @@ static void ascii_to_unicode_le(unsigned char *dest, const char *src, } } -#if USE_NTLM_V2 && !defined(USE_WINDOWS_SSPI) +#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)(toupper(src[i])); + dest[2 * i] = (unsigned char)(Curl_raw_toupper(src[i])); dest[2 * i + 1] = '\0'; } } -#endif /* USE_NTLM_V2 && !USE_WINDOWS_SSPI */ +#endif /* !USE_WINDOWS_SSPI */ /* * Set up nt hashed passwords * @unittest: 1600 */ -CURLcode Curl_ntlm_core_mk_nt_hash(struct Curl_easy *data, - const char *password, +CURLcode Curl_ntlm_core_mk_nt_hash(const char *password, unsigned char *ntbuffer /* 21 bytes */) { size_t len = strlen(password); - unsigned char *pw = malloc(len * 2); + 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); - /* - * The NT hashed password needs to be created using the password in the - * network encoding not the host encoding. - */ - result = Curl_convert_to_network(data, (char *)pw, len * 2); - if(result) - return result; - - { - /* Create NT hashed password. */ -#ifdef USE_OPENSSL - MD4_CTX MD4pw; - MD4_Init(&MD4pw); - MD4_Update(&MD4pw, pw, 2 * len); - MD4_Final(ntbuffer, &MD4pw); -#elif defined(USE_GNUTLS_NETTLE) - struct md4_ctx MD4pw; - md4_init(&MD4pw); - md4_update(&MD4pw, (unsigned int)(2 * len), pw); - md4_digest(&MD4pw, MD4_DIGEST_SIZE, ntbuffer); -#elif defined(USE_GNUTLS) - gcry_md_hd_t MD4pw; - gcry_md_open(&MD4pw, GCRY_MD_MD4, 0); - gcry_md_write(MD4pw, pw, 2 * len); - memcpy(ntbuffer, gcry_md_read(MD4pw, 0), MD4_DIGEST_LENGTH); - gcry_md_close(MD4pw); -#elif defined(USE_MBEDTLS) - mbedtls_md4(pw, 2 * len, ntbuffer); -#elif defined(USE_NSS) || defined(USE_OS400CRYPTO) - Curl_md4it(ntbuffer, pw, 2 * len); -#elif defined(USE_DARWINSSL) - (void)CC_MD4(pw, (CC_LONG)(2 * len), ntbuffer); -#elif defined(USE_WIN32_CRYPTO) - HCRYPTPROV hprov; - if(CryptAcquireContext(&hprov, NULL, NULL, PROV_RSA_FULL, - CRYPT_VERIFYCONTEXT)) { - HCRYPTHASH hhash; - if(CryptCreateHash(hprov, CALG_MD4, 0, 0, &hhash)) { - DWORD length = 16; - CryptHashData(hhash, pw, (unsigned int)len * 2, 0); - CryptGetHashParam(hhash, HP_HASHVAL, ntbuffer, &length, 0); - CryptDestroyHash(hhash); - } - CryptReleaseContext(hprov, 0); - } -#endif - + /* Create NT hashed password. */ + result = Curl_md4it(ntbuffer, pw, 2 * len); + if(!result) memset(ntbuffer + 16, 0, 21 - 16); - } free(pw); - return CURLE_OK; + return result; } -#if USE_NTLM_V2 && !defined(USE_WINDOWS_SSPI) +#if !defined(USE_WINDOWS_SSPI) -/* This returns the HMAC MD5 digest */ -CURLcode Curl_hmac_md5(const unsigned char *key, unsigned int keylen, - const unsigned char *data, unsigned int datalen, - unsigned char *output) -{ - HMAC_context *ctxt = Curl_HMAC_init(Curl_HMAC_MD5, key, keylen); - - if(!ctxt) - return CURLE_OUT_OF_MEMORY; +/* Timestamp in tenths of a microsecond since January 1, 1601 00:00:00 UTC. */ +struct ms_filetime { + unsigned int dwLowDateTime; + unsigned int dwHighDateTime; +}; - /* Update the digest with the given challenge */ - Curl_HMAC_update(ctxt, data, datalen); - - /* Finalise the digest */ - Curl_HMAC_final(ctxt, output); - - return CURLE_OK; +/* 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 = (unsigned int)t & 0xFFFFFFFF; + ft->dwHighDateTime = 0; + +# ifndef HAVE_TIME_T_UNSIGNED + /* Extend sign if needed. */ + if(ft->dwLowDateTime & 0x80000000) + ft->dwHighDateTime = ~(unsigned int)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 @@ -627,19 +512,24 @@ CURLcode Curl_ntlm_core_mk_ntlmv2_hash(const char *user, size_t userlen, unsigned char *ntlmv2hash) { /* Unicode representation */ - size_t identity_len = (userlen + domlen) * 2; - unsigned char *identity = malloc(identity_len); + 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_hmac_md5(ntlmhash, 16, identity, curlx_uztoui(identity_len), - ntlmv2hash); - + result = Curl_hmacit(Curl_HMAC_MD5, ntlmhash, 16, identity, identity_len, + ntlmv2hash); free(identity); return result; @@ -648,13 +538,13 @@ CURLcode Curl_ntlm_core_mk_ntlmv2_hash(const char *user, size_t userlen, /* * Curl_ntlm_core_mk_ntlmv2_resp() * - * This creates the NTLMv2 response as set in the ntlm type-3 message. + * This creates the NTLMv2 response as set in the NTLM type-3 message. * * Parameters: * - * ntlmv2hash [in] - The ntlmv2 hash (16 bytes) + * 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 + * 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. @@ -685,57 +575,53 @@ CURLcode Curl_ntlm_core_mk_ntlmv2_resp(unsigned char *ntlmv2hash, unsigned int len = 0; unsigned char *ptr = NULL; - unsigned char hmac_output[NTLM_HMAC_MD5_LEN]; - curl_off_t tw; + unsigned char hmac_output[HMAC_MD5_LENGTH]; + struct ms_filetime tw; CURLcode result = CURLE_OK; -#if CURL_SIZEOF_CURL_OFF_T < 8 -#error "this section needs 64bit support to work" -#endif - /* Calculate the timestamp */ #ifdef DEBUGBUILD char *force_timestamp = getenv("CURL_FORCETIME"); if(force_timestamp) - tw = CURL_OFF_T_C(11644473600) * 10000000; + time2filetime(&tw, (time_t) 0); else #endif - tw = ((curl_off_t)time(NULL) + CURL_OFF_T_C(11644473600)) * 10000000; + time2filetime(&tw, time(NULL)); /* Calculate the response len */ - len = NTLM_HMAC_MD5_LEN + NTLMv2_BLOB_LEN; + len = HMAC_MD5_LENGTH + NTLMv2_BLOB_LEN; /* Allocate the response */ - ptr = malloc(len); + ptr = calloc(1, len); if(!ptr) return CURLE_OUT_OF_MEMORY; - memset(ptr, 0, len); - /* Create the BLOB structure */ - snprintf((char *)ptr + NTLM_HMAC_MD5_LEN, NTLMv2_BLOB_LEN, - "%c%c%c%c" /* NTLMv2_BLOB_SIGNATURE */ - "%c%c%c%c", /* Reserved = 0 */ - NTLMv2_BLOB_SIGNATURE[0], NTLMv2_BLOB_SIGNATURE[1], - NTLMv2_BLOB_SIGNATURE[2], NTLMv2_BLOB_SIGNATURE[3], - 0, 0, 0, 0); - - Curl_write64_le(tw, ptr + 24); + 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); - memcpy(ptr + 44, ntlm->target_info, ntlm->target_info_len); + 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_hmac_md5(ntlmv2hash, NTLM_HMAC_MD5_LEN, ptr + 8, - NTLMv2_BLOB_LEN + 8, hmac_output); + 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, NTLM_HMAC_MD5_LEN); + memcpy(ptr, hmac_output, HMAC_MD5_LENGTH); /* Return the response */ *ntresp = ptr; @@ -747,11 +633,11 @@ CURLcode Curl_ntlm_core_mk_ntlmv2_resp(unsigned char *ntlmv2hash, /* * Curl_ntlm_core_mk_lmv2_resp() * - * This creates the LMv2 response as used in the ntlm type-3 message. + * This creates the LMv2 response as used in the NTLM type-3 message. * * Parameters: * - * ntlmv2hash [in] - The ntlmv2 hash (16 bytes) + * 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) @@ -770,21 +656,18 @@ CURLcode Curl_ntlm_core_mk_lmv2_resp(unsigned char *ntlmv2hash, memcpy(&data[0], challenge_server, 8); memcpy(&data[8], challenge_client, 8); - result = Curl_hmac_md5(ntlmv2hash, 16, &data[0], 16, hmac_output); + 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 */ + /* Concatenate the HMAC MD5 output with the client nonce */ memcpy(lmresp, hmac_output, 16); - memcpy(lmresp+16, challenge_client, 8); + memcpy(lmresp + 16, challenge_client, 8); return result; } -#endif /* USE_NTLM_V2 && !USE_WINDOWS_SSPI */ - -#endif /* USE_NTRESPONSES */ - -#endif /* !USE_WINDOWS_SSPI || USE_WIN32_CRYPTO */ +#endif /* !USE_WINDOWS_SSPI */ -#endif /* USE_NTLM */ +#endif /* USE_CURL_NTLM_CORE */ diff --git a/contrib/curl/lib/curl_ntlm_core.h b/contrib/curl/lib/curl_ntlm_core.h index c5f90e778e..e2e4b1bd43 100644 --- a/contrib/curl/lib/curl_ntlm_core.h +++ b/contrib/curl/lib/curl_ntlm_core.h @@ -7,11 +7,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2015, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -20,60 +20,30 @@ * 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) - -#if !defined(USE_WINDOWS_SSPI) || defined(USE_WIN32_CRYPTO) - -#ifdef USE_OPENSSL -# if !defined(OPENSSL_VERSION_NUMBER) && \ - !defined(HEADER_SSL_H) && !defined(HEADER_MD5_H) -# error "curl_ntlm_core.h shall not be included before OpenSSL headers." -# endif -# ifdef OPENSSL_NO_MD4 -# define USE_NTRESPONSES 0 -# define USE_NTLM2SESSION 0 -# define USE_NTLM_V2 0 -# endif -#endif - -/* Define USE_NTRESPONSES to 1 in order to make the type-3 message include - * the NT response message. */ -#ifndef USE_NTRESPONSES -#define USE_NTRESPONSES 1 -#endif +#if defined(USE_CURL_NTLM_CORE) -/* Define USE_NTLM2SESSION to 1 in order to make the type-3 message include the - NTLM2Session response message, requires USE_NTRESPONSES defined to 1 and a - Crypto engine that we have curl_ssl_md5sum() for. */ -#if !defined(USE_NTLM2SESSION) && USE_NTRESPONSES && !defined(USE_WIN32_CRYPTO) -#define USE_NTLM2SESSION 1 -#endif - -/* Define USE_NTLM_V2 to 1 in order to allow the type-3 message to include the - LMv2 and NTLMv2 response messages, requires USE_NTRESPONSES defined to 1 - and support for 64-bit integers. */ -#if !defined(USE_NTLM_V2) && USE_NTRESPONSES && (CURL_SIZEOF_CURL_OFF_T > 4) -#define USE_NTLM_V2 1 -#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(struct Curl_easy *data, - const char *password, +CURLcode Curl_ntlm_core_mk_lm_hash(const char *password, unsigned char *lmbuffer /* 21 bytes */); -#if USE_NTRESPONSES -CURLcode Curl_ntlm_core_mk_nt_hash(struct Curl_easy *data, - const char *password, +CURLcode Curl_ntlm_core_mk_nt_hash(const char *password, unsigned char *ntbuffer /* 21 bytes */); -#if USE_NTLM_V2 && !defined(USE_WINDOWS_SSPI) +#if !defined(USE_WINDOWS_SSPI) CURLcode Curl_hmac_md5(const unsigned char *key, unsigned int keylen, const unsigned char *data, unsigned int datalen, @@ -95,12 +65,8 @@ CURLcode Curl_ntlm_core_mk_lmv2_resp(unsigned char *ntlmv2hash, unsigned char *challenge_server, unsigned char *lmresp); -#endif /* USE_NTLM_V2 && !USE_WINDOWS_SSPI */ - -#endif /* USE_NTRESPONSES */ - -#endif /* !USE_WINDOWS_SSPI || USE_WIN32_CRYPTO */ +#endif /* !USE_WINDOWS_SSPI */ -#endif /* USE_NTLM */ +#endif /* USE_CURL_NTLM_CORE */ #endif /* HEADER_CURL_NTLM_CORE_H */ diff --git a/contrib/curl/lib/curl_ntlm_wb.c b/contrib/curl/lib/curl_ntlm_wb.c deleted file mode 100644 index 9f5b87bc3f..0000000000 --- a/contrib/curl/lib/curl_ntlm_wb.c +++ /dev/null @@ -1,431 +0,0 @@ -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) 1998 - 2016, Daniel Stenberg, , et al. - * - * This software is licensed as described in the file COPYING, which - * you should have received as part of this distribution. The terms - * are also available at https://curl.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. - * - ***************************************************************************/ - -#include "curl_setup.h" - -#if !defined(CURL_DISABLE_HTTP) && defined(USE_NTLM) && \ - defined(NTLM_WB_ENABLED) - -/* - * NTLM details: - * - * https://davenport.sourceforge.io/ntlm.html - * https://www.innovation.ch/java/ntlm.html - */ - -#define DEBUG_ME 0 - -#ifdef HAVE_SYS_WAIT_H -#include -#endif -#ifdef HAVE_SIGNAL_H -#include -#endif -#ifdef HAVE_PWD_H -#include -#endif - -#include "urldata.h" -#include "sendf.h" -#include "select.h" -#include "vauth/ntlm.h" -#include "curl_ntlm_wb.h" -#include "url.h" -#include "strerror.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" - -#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 */ -#if 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 - -void Curl_ntlm_wb_cleanup(struct connectdata *conn) -{ - if(conn->ntlm_auth_hlpr_socket != CURL_SOCKET_BAD) { - sclose(conn->ntlm_auth_hlpr_socket); - conn->ntlm_auth_hlpr_socket = CURL_SOCKET_BAD; - } - - if(conn->ntlm_auth_hlpr_pid) { - int i; - for(i = 0; i < 4; i++) { - pid_t ret = waitpid(conn->ntlm_auth_hlpr_pid, NULL, WNOHANG); - if(ret == conn->ntlm_auth_hlpr_pid || errno == ECHILD) - break; - switch(i) { - case 0: - kill(conn->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(conn->ntlm_auth_hlpr_pid, SIGKILL); - break; - case 3: - break; - } - } - conn->ntlm_auth_hlpr_pid = 0; - } - - free(conn->challenge_header); - conn->challenge_header = NULL; - free(conn->response_header); - conn->response_header = NULL; -} - -static CURLcode ntlm_wb_init(struct connectdata *conn, 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 - int error; - - /* Return if communication with ntlm_auth already set up */ - if(conn->ntlm_auth_hlpr_socket != CURL_SOCKET_BAD || - conn->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) { - error = ERRNO; - failf(conn->data, "Could not access ntlm_auth: %s errno %d: %s", - ntlm_auth, error, Curl_strerror(conn, error)); - goto done; - } - - if(socketpair(AF_UNIX, SOCK_STREAM, 0, sockfds)) { - error = ERRNO; - failf(conn->data, "Could not open socket pair. errno %d: %s", - error, Curl_strerror(conn, error)); - goto done; - } - - child_pid = fork(); - if(child_pid == -1) { - error = ERRNO; - sclose(sockfds[0]); - sclose(sockfds[1]); - failf(conn->data, "Could not fork. errno %d: %s", - error, Curl_strerror(conn, error)); - 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) { - error = ERRNO; - failf(conn->data, "Could not redirect child stdin. errno %d: %s", - error, Curl_strerror(conn, error)); - exit(1); - } - - if(dup2(sockfds[1], STDOUT_FILENO) == -1) { - error = ERRNO; - failf(conn->data, "Could not redirect child stdout. errno %d: %s", - error, Curl_strerror(conn, error)); - 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); - - error = ERRNO; - sclose_nolog(sockfds[1]); - failf(conn->data, "Could not execl(). errno %d: %s", - error, Curl_strerror(conn, error)); - exit(1); - } - - sclose(sockfds[1]); - conn->ntlm_auth_hlpr_socket = sockfds[0]; - conn->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; -} - -static CURLcode ntlm_wb_response(struct connectdata *conn, - const char *input, curlntlm state) -{ - char *buf = malloc(NTLM_BUFSIZE); - size_t len_in = strlen(input), len_out = 0; - - if(!buf) - return CURLE_OUT_OF_MEMORY; - - while(len_in > 0) { - ssize_t written = swrite(conn->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; - char *newbuf; - - size = sread(conn->ntlm_auth_hlpr_socket, buf + len_out, NTLM_BUFSIZE); - if(size == -1) { - if(errno == EINTR) - continue; - goto done; - } - else if(size == 0) - goto done; - - len_out += size; - if(buf[len_out - 1] == '\n') { - buf[len_out - 1] = '\0'; - break; - } - newbuf = Curl_saferealloc(buf, len_out + NTLM_BUFSIZE); - if(!newbuf) - return CURLE_OUT_OF_MEMORY; - - buf = newbuf; - } - - /* Samba/winbind installed but not configured */ - if(state == NTLMSTATE_TYPE1 && - len_out == 3 && - buf[0] == 'P' && buf[1] == 'W') - goto done; - /* invalid response */ - if(len_out < 4) - goto done; - if(state == NTLMSTATE_TYPE1 && - (buf[0]!='Y' || buf[1]!='R' || buf[2]!=' ')) - goto done; - if(state == NTLMSTATE_TYPE2 && - (buf[0]!='K' || buf[1]!='K' || buf[2]!=' ') && - (buf[0]!='A' || buf[1]!='F' || buf[2]!=' ')) - goto done; - - conn->response_header = aprintf("NTLM %.*s", len_out - 4, buf + 3); - free(buf); - return CURLE_OK; -done: - free(buf); - return CURLE_REMOTE_ACCESS_DENIED; -} - -/* - * 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 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 a HTTP proxy */ - char **allocuserpwd; - /* point to the name and password for this */ - const char *userp; - /* point to the correct struct with this */ - struct ntlmdata *ntlm; - struct auth *authp; - - CURLcode res = CURLE_OK; - char *input; - - DEBUGASSERT(conn); - DEBUGASSERT(conn->data); - - if(proxy) { - allocuserpwd = &conn->allocptr.proxyuserpwd; - userp = conn->http_proxy.user; - ntlm = &conn->proxyntlm; - authp = &conn->data->state.authproxy; - } - else { - allocuserpwd = &conn->allocptr.userpwd; - userp = conn->user; - ntlm = &conn->ntlm; - authp = &conn->data->state.authhost; - } - authp->done = FALSE; - - /* not set means empty */ - if(!userp) - userp=""; - - switch(ntlm->state) { - case NTLMSTATE_TYPE1: - default: - /* Use Samba's 'winbind' daemon to support NTLM authentication, - * by delegating the NTLM challenge/response protocal to a helper - * in ntlm_auth. - * http://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(conn, userp); - if(res) - return res; - res = ntlm_wb_response(conn, "YR\n", ntlm->state); - if(res) - return res; - - free(*allocuserpwd); - *allocuserpwd = aprintf("%sAuthorization: %s\r\n", - proxy ? "Proxy-" : "", - conn->response_header); - DEBUG_OUT(fprintf(stderr, "**** Header %s\n ", *allocuserpwd)); - free(conn->response_header); - conn->response_header = NULL; - break; - case NTLMSTATE_TYPE2: - input = aprintf("TT %s\n", conn->challenge_header); - if(!input) - return CURLE_OUT_OF_MEMORY; - res = ntlm_wb_response(conn, input, ntlm->state); - free(input); - input = NULL; - if(res) - return res; - - free(*allocuserpwd); - *allocuserpwd = aprintf("%sAuthorization: %s\r\n", - proxy ? "Proxy-" : "", - conn->response_header); - DEBUG_OUT(fprintf(stderr, "**** %s\n ", *allocuserpwd)); - ntlm->state = NTLMSTATE_TYPE3; /* we sent a type-3 */ - authp->done = TRUE; - Curl_ntlm_wb_cleanup(conn); - break; - case NTLMSTATE_TYPE3: - /* connection is already authenticated, - * don't send a header in future requests */ - free(*allocuserpwd); - *allocuserpwd=NULL; - authp->done = TRUE; - break; - } - - return CURLE_OK; -} - -#endif /* !CURL_DISABLE_HTTP && USE_NTLM && NTLM_WB_ENABLED */ diff --git a/contrib/curl/lib/curl_path.c b/contrib/curl/lib/curl_path.c new file mode 100644 index 0000000000..144f8803d3 --- /dev/null +++ b/contrib/curl/lib/curl_path.c @@ -0,0 +1,203 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl AND ISC + * + ***************************************************************************/ + +#include "curl_setup.h" + +#if defined(USE_SSH) + +#include +#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 original get_pathname() function came from OpenSSH sftp.c version + 4.6p1. */ +/* + * Copyright (c) 2001-2004 Damien Miller + * + * 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. + */ + +#define MAX_PATHLENGTH 65535 /* arbitrary long */ + +CURLcode Curl_get_pathname(const char **cpp, char **path, const char *homedir) +{ + const char *cp = *cpp, *end; + char quot; + unsigned int i; + static const char WHITESPACE[] = " \t\r\n"; + struct dynbuf out; + CURLcode result; + + DEBUGASSERT(homedir); + *path = NULL; + *cpp = NULL; + if(!*cp || !homedir) + return CURLE_QUOTE_ERROR; + + Curl_dyn_init(&out, MAX_PATHLENGTH); + + /* Ignore leading whitespace */ + cp += strspn(cp, WHITESPACE); + + /* Check for quoted filenames */ + if(*cp == '\"' || *cp == '\'') { + quot = *cp++; + + /* Search for terminating quote, unescape some chars */ + for(i = 0; i <= strlen(cp); i++) { + if(cp[i] == quot) { /* Found quote */ + i++; + 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; + } + } + result = Curl_dyn_addn(&out, &cp[i], 1); + if(result) + return result; + } + + if(!Curl_dyn_len(&out)) + goto fail; + + /* return pointer to second parameter if it exists */ + *cpp = &cp[i] + strspn(&cp[i], WHITESPACE); + } + else { + /* Read to end of filename - either to whitespace or terminator */ + end = strpbrk(cp, WHITESPACE); + if(!end) + end = strchr(cp, '\0'); + + /* return pointer to second parameter if it exists */ + *cpp = end + strspn(end, WHITESPACE); + + /* Handling for relative path - prepend home directory */ + if(cp[0] == '/' && cp[1] == '~' && cp[2] == '/') { + result = Curl_dyn_add(&out, homedir); + if(!result) + result = Curl_dyn_addn(&out, "/", 1); + if(result) + return result; + cp += 3; + } + /* Copy path name up until first "whitespace" */ + result = Curl_dyn_addn(&out, cp, (end - cp)); + if(result) + return result; + } + *path = Curl_dyn_ptr(&out); + return CURLE_OK; + +fail: + Curl_dyn_free(&out); + return CURLE_QUOTE_ERROR; +} + +#endif /* if SSH is used */ diff --git a/contrib/curl/lib/curl_path.h b/contrib/curl/lib/curl_path.h new file mode 100644 index 0000000000..6fdb2fddff --- /dev/null +++ b/contrib/curl/lib/curl_path.h @@ -0,0 +1,49 @@ +#ifndef HEADER_CURL_PATH_H +#define HEADER_CURL_PATH_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" +#include +#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, const char *homedir); +#endif /* HEADER_CURL_PATH_H */ diff --git a/contrib/curl/lib/curl_printf.h b/contrib/curl/lib/curl_printf.h index 49857cdb0d..e851b14a50 100644 --- a/contrib/curl/lib/curl_printf.h +++ b/contrib/curl/lib/curl_printf.h @@ -7,11 +7,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2015, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -20,6 +20,8 @@ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY * KIND, either express or implied. * + * SPDX-License-Identifier: curl + * ***************************************************************************/ /* @@ -27,30 +29,30 @@ * *rintf() functions. */ +#ifndef CURL_TEMP_PRINTF +#error "CURL_TEMP_PRINTF must be set before including curl/mprintf.h" +#endif + #include +#define MERR_OK 0 +#define MERR_MEM 1 +#define MERR_TOO_LARGE 2 + # undef printf # undef fprintf -# undef snprintf +# undef msnprintf # undef vprintf # undef vfprintf -# undef vsnprintf +# undef mvsnprintf # undef aprintf # undef vaprintf # define printf curl_mprintf # define fprintf curl_mfprintf -# define snprintf curl_msnprintf +# define msnprintf curl_msnprintf # define vprintf curl_mvprintf # define vfprintf curl_mvfprintf -# define vsnprintf curl_mvsnprintf +# define mvsnprintf curl_mvsnprintf # define aprintf curl_maprintf # define vaprintf curl_mvaprintf - -/* We define away the sprintf functions unconditonally since we don't want - internal code to be using them, intentionally or by mistake!*/ -# undef sprintf -# undef vsprintf -# define sprintf sprintf_was_used -# define vsprintf vsprintf_was_used - #endif /* HEADER_CURL_PRINTF_H */ diff --git a/contrib/curl/lib/curl_range.c b/contrib/curl/lib/curl_range.c new file mode 100644 index 0000000000..49fb5f0778 --- /dev/null +++ b/contrib/curl/lib/curl_range.c @@ -0,0 +1,93 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" +#include +#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 %" FMT_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 %" FMT_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 %" FMT_OFF_T + " getting %" FMT_OFF_T " bytes", + from, data->req.maxdownload)); + } + DEBUGF(infof(data, "range-download from %" FMT_OFF_T + " to %" FMT_OFF_T ", totally %" FMT_OFF_T " bytes", + from, to, data->req.maxdownload)); + } + else + data->req.maxdownload = -1; + return CURLE_OK; +} + +#endif diff --git a/contrib/curl/lib/curl_range.h b/contrib/curl/lib/curl_range.h new file mode 100644 index 0000000000..77679e2b94 --- /dev/null +++ b/contrib/curl/lib/curl_range.h @@ -0,0 +1,31 @@ +#ifndef HEADER_CURL_RANGE_H +#define HEADER_CURL_RANGE_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" +#include "urldata.h" + +CURLcode Curl_range(struct Curl_easy *data); +#endif /* HEADER_CURL_RANGE_H */ diff --git a/contrib/curl/lib/curl_rtmp.c b/contrib/curl/lib/curl_rtmp.c index 06dd047a40..49f59e3a76 100644 --- a/contrib/curl/lib/curl_rtmp.c +++ b/contrib/curl/lib/curl_rtmp.c @@ -5,12 +5,12 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 2012 - 2015, Daniel Stenberg, , et al. - * Copyright (C) 2010, Howard Chu, + * Copyright (C) Daniel Stenberg, , et al. + * Copyright (C) Howard Chu, * * This software is licensed 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. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -19,12 +19,15 @@ * 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 */ @@ -32,24 +35,30 @@ #include "warnless.h" #include #include + +/* The last 3 #include files should be in this order */ +#include "curl_printf.h" #include "curl_memory.h" -/* The last #include file should be: */ #include "memdebug.h" -#ifdef _WIN32 +#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 connectdata *conn); -static CURLcode rtmp_do(struct connectdata *conn, bool *done); -static CURLcode rtmp_done(struct connectdata *conn, CURLcode, bool premature); -static CURLcode rtmp_connect(struct connectdata *conn, bool *done); -static CURLcode rtmp_disconnect(struct connectdata *conn, bool dead); +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; @@ -59,7 +68,7 @@ static Curl_send rtmp_send; */ const struct Curl_handler Curl_handler_rtmp = { - "RTMP", /* scheme */ + "rtmp", /* scheme */ rtmp_setup_connection, /* setup_connection */ rtmp_do, /* do_it */ rtmp_done, /* done */ @@ -72,14 +81,18 @@ const struct Curl_handler Curl_handler_rtmp = { ZERO_NULL, /* domore_getsock */ ZERO_NULL, /* perform_getsock */ rtmp_disconnect, /* disconnect */ - ZERO_NULL, /* readwrite */ + ZERO_NULL, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ + ZERO_NULL, /* connection_check */ + ZERO_NULL, /* attach connection */ PORT_RTMP, /* defport */ CURLPROTO_RTMP, /* protocol */ - PROTOPT_NONE /* flags*/ + CURLPROTO_RTMP, /* family */ + PROTOPT_NONE /* flags */ }; const struct Curl_handler Curl_handler_rtmpt = { - "RTMPT", /* scheme */ + "rtmpt", /* scheme */ rtmp_setup_connection, /* setup_connection */ rtmp_do, /* do_it */ rtmp_done, /* done */ @@ -92,14 +105,18 @@ const struct Curl_handler Curl_handler_rtmpt = { ZERO_NULL, /* domore_getsock */ ZERO_NULL, /* perform_getsock */ rtmp_disconnect, /* disconnect */ - ZERO_NULL, /* readwrite */ + ZERO_NULL, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ + ZERO_NULL, /* connection_check */ + ZERO_NULL, /* attach connection */ PORT_RTMPT, /* defport */ CURLPROTO_RTMPT, /* protocol */ - PROTOPT_NONE /* flags*/ + CURLPROTO_RTMPT, /* family */ + PROTOPT_NONE /* flags */ }; const struct Curl_handler Curl_handler_rtmpe = { - "RTMPE", /* scheme */ + "rtmpe", /* scheme */ rtmp_setup_connection, /* setup_connection */ rtmp_do, /* do_it */ rtmp_done, /* done */ @@ -112,14 +129,18 @@ const struct Curl_handler Curl_handler_rtmpe = { ZERO_NULL, /* domore_getsock */ ZERO_NULL, /* perform_getsock */ rtmp_disconnect, /* disconnect */ - ZERO_NULL, /* readwrite */ + ZERO_NULL, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ + ZERO_NULL, /* connection_check */ + ZERO_NULL, /* attach connection */ PORT_RTMP, /* defport */ CURLPROTO_RTMPE, /* protocol */ - PROTOPT_NONE /* flags*/ + CURLPROTO_RTMPE, /* family */ + PROTOPT_NONE /* flags */ }; const struct Curl_handler Curl_handler_rtmpte = { - "RTMPTE", /* scheme */ + "rtmpte", /* scheme */ rtmp_setup_connection, /* setup_connection */ rtmp_do, /* do_it */ rtmp_done, /* done */ @@ -132,14 +153,18 @@ const struct Curl_handler Curl_handler_rtmpte = { ZERO_NULL, /* domore_getsock */ ZERO_NULL, /* perform_getsock */ rtmp_disconnect, /* disconnect */ - ZERO_NULL, /* readwrite */ + ZERO_NULL, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ + ZERO_NULL, /* connection_check */ + ZERO_NULL, /* attach connection */ PORT_RTMPT, /* defport */ CURLPROTO_RTMPTE, /* protocol */ - PROTOPT_NONE /* flags*/ + CURLPROTO_RTMPTE, /* family */ + PROTOPT_NONE /* flags */ }; const struct Curl_handler Curl_handler_rtmps = { - "RTMPS", /* scheme */ + "rtmps", /* scheme */ rtmp_setup_connection, /* setup_connection */ rtmp_do, /* do_it */ rtmp_done, /* done */ @@ -152,14 +177,18 @@ const struct Curl_handler Curl_handler_rtmps = { ZERO_NULL, /* domore_getsock */ ZERO_NULL, /* perform_getsock */ rtmp_disconnect, /* disconnect */ - ZERO_NULL, /* readwrite */ + ZERO_NULL, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ + ZERO_NULL, /* connection_check */ + ZERO_NULL, /* attach connection */ PORT_RTMPS, /* defport */ CURLPROTO_RTMPS, /* protocol */ - PROTOPT_NONE /* flags*/ + CURLPROTO_RTMP, /* family */ + PROTOPT_NONE /* flags */ }; const struct Curl_handler Curl_handler_rtmpts = { - "RTMPTS", /* scheme */ + "rtmpts", /* scheme */ rtmp_setup_connection, /* setup_connection */ rtmp_do, /* do_it */ rtmp_done, /* done */ @@ -172,13 +201,18 @@ const struct Curl_handler Curl_handler_rtmpts = { ZERO_NULL, /* domore_getsock */ ZERO_NULL, /* perform_getsock */ rtmp_disconnect, /* disconnect */ - ZERO_NULL, /* readwrite */ + ZERO_NULL, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ + ZERO_NULL, /* connection_check */ + ZERO_NULL, /* attach connection */ PORT_RTMPS, /* defport */ CURLPROTO_RTMPTS, /* protocol */ - PROTOPT_NONE /* flags*/ + CURLPROTO_RTMPT, /* family */ + PROTOPT_NONE /* flags */ }; -static CURLcode rtmp_setup_connection(struct connectdata *conn) +static CURLcode rtmp_setup_connection(struct Curl_easy *data, + struct connectdata *conn) { RTMP *r = RTMP_Alloc(); if(!r) @@ -186,25 +220,26 @@ static CURLcode rtmp_setup_connection(struct connectdata *conn) RTMP_Init(r); RTMP_SetBufferMS(r, DEF_BUFTIME); - if(!RTMP_SetupURL(r, conn->data->change.url)) { + if(!RTMP_SetupURL(r, data->state.url)) { RTMP_Free(r); return CURLE_URL_MALFORMAT; } - conn->proto.generic = r; + conn->proto.rtmp = r; return CURLE_OK; } -static CURLcode rtmp_connect(struct connectdata *conn, bool *done) +static CURLcode rtmp_connect(struct Curl_easy *data, bool *done) { - RTMP *r = conn->proto.generic; + struct connectdata *conn = data->conn; + RTMP *r = conn->proto.rtmp; SET_RCVTIMEO(tv, 10); - r->m_sb.sb_socket = conn->sock[FIRSTSOCKET]; + r->m_sb.sb_socket = (int)conn->sock[FIRSTSOCKET]; - /* We have to know if it's a write before we send the + /* We have to know if it is a write before we send the * connect request packet */ - if(conn->data->set.upload) + if(data->state.upload) r->Link.protocol |= RTMP_FEATURE_WRITE; /* For plain streams, use the buffer toggle trick to keep data flowing */ @@ -228,50 +263,54 @@ static CURLcode rtmp_connect(struct connectdata *conn, bool *done) return CURLE_OK; } -static CURLcode rtmp_do(struct connectdata *conn, bool *done) +static CURLcode rtmp_do(struct Curl_easy *data, bool *done) { - RTMP *r = conn->proto.generic; + struct connectdata *conn = data->conn; + RTMP *r = conn->proto.rtmp; if(!RTMP_ConnectStream(r, 0)) return CURLE_FAILED_INIT; - if(conn->data->set.upload) { - Curl_pgrsSetUploadSize(conn->data, conn->data->state.infilesize); - Curl_setup_transfer(conn, -1, -1, FALSE, NULL, FIRSTSOCKET, NULL); + if(data->state.upload) { + Curl_pgrsSetUploadSize(data, data->state.infilesize); + Curl_xfer_setup1(data, CURL_XFER_SEND, -1, FALSE); } else - Curl_setup_transfer(conn, FIRSTSOCKET, -1, FALSE, NULL, -1, NULL); + Curl_xfer_setup1(data, CURL_XFER_RECV, -1, FALSE); *done = TRUE; return CURLE_OK; } -static CURLcode rtmp_done(struct connectdata *conn, CURLcode status, +static CURLcode rtmp_done(struct Curl_easy *data, CURLcode status, bool premature) { - (void)conn; /* unused */ + (void)data; /* unused */ (void)status; /* unused */ (void)premature; /* unused */ return CURLE_OK; } -static CURLcode rtmp_disconnect(struct connectdata *conn, +static CURLcode rtmp_disconnect(struct Curl_easy *data, + struct connectdata *conn, bool dead_connection) { - RTMP *r = conn->proto.generic; + RTMP *r = conn->proto.rtmp; + (void)data; (void)dead_connection; if(r) { - conn->proto.generic = NULL; + conn->proto.rtmp = NULL; RTMP_Close(r); RTMP_Free(r); } return CURLE_OK; } -static ssize_t rtmp_recv(struct connectdata *conn, int sockindex, char *buf, +static ssize_t rtmp_recv(struct Curl_easy *data, int sockindex, char *buf, size_t len, CURLcode *err) { - RTMP *r = conn->proto.generic; + struct connectdata *conn = data->conn; + RTMP *r = conn->proto.rtmp; ssize_t nread; (void)sockindex; /* unused */ @@ -279,8 +318,8 @@ static ssize_t rtmp_recv(struct connectdata *conn, int sockindex, char *buf, 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) { - conn->data->req.size = conn->data->req.bytecount; + r->m_read.status == RTMP_READ_EOF) { + data->req.size = data->req.bytecount; nread = 0; } else @@ -289,13 +328,15 @@ static ssize_t rtmp_recv(struct connectdata *conn, int sockindex, char *buf, return nread; } -static ssize_t rtmp_send(struct connectdata *conn, int sockindex, - const void *buf, size_t len, CURLcode *err) +static ssize_t rtmp_send(struct Curl_easy *data, int sockindex, + const void *buf, size_t len, bool eos, CURLcode *err) { - RTMP *r = conn->proto.generic; + struct connectdata *conn = data->conn; + RTMP *r = conn->proto.rtmp; ssize_t num; (void)sockindex; /* unused */ + (void)eos; /* unused */ num = RTMP_Write(r, (char *)buf, curlx_uztosi(len)); if(num < 0) @@ -303,4 +344,20 @@ static ssize_t rtmp_send(struct connectdata *conn, int sockindex, return num; } + +void Curl_rtmp_version(char *version, size_t len) +{ + char suff[2]; + if(RTMP_LIB_VERSION & 0xff) { + suff[0] = (RTMP_LIB_VERSION & 0xff) + 'a' - 1; + suff[1] = '\0'; + } + else + suff[0] = '\0'; + + msnprintf(version, len, "librtmp/%d.%d%s", + RTMP_LIB_VERSION >> 16, (RTMP_LIB_VERSION >> 8) & 0xff, + suff); +} + #endif /* USE_LIBRTMP */ diff --git a/contrib/curl/lib/curl_rtmp.h b/contrib/curl/lib/curl_rtmp.h index 3306e22005..339d3a4384 100644 --- a/contrib/curl/lib/curl_rtmp.h +++ b/contrib/curl/lib/curl_rtmp.h @@ -7,11 +7,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 2010, Howard Chu, + * Copyright (C) Howard Chu, * * This software is licensed 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. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -20,6 +20,8 @@ * 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; @@ -28,6 +30,8 @@ extern const struct Curl_handler Curl_handler_rtmpe; extern const struct Curl_handler Curl_handler_rtmpte; extern const struct Curl_handler Curl_handler_rtmps; extern const struct Curl_handler Curl_handler_rtmpts; + +void Curl_rtmp_version(char *version, size_t len); #endif #endif /* HEADER_CURL_RTMP_H */ diff --git a/contrib/curl/lib/curl_sasl.c b/contrib/curl/lib/curl_sasl.c index 807f5de7e8..24f8c8c53c 100644 --- a/contrib/curl/lib/curl_sasl.c +++ b/contrib/curl/lib/curl_sasl.c @@ -5,11 +5,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 2012 - 2016, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -18,11 +18,15 @@ * 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 @@ -31,29 +35,33 @@ #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 #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" -#include "non-ascii.h" /* included for Curl_convert_... prototypes */ /* The last 3 #include files should be in this order */ #include "curl_printf.h" #include "curl_memory.h" #include "memdebug.h" /* Supported mechanisms */ -const struct { - const char *name; /* Name */ - size_t len; /* Name length */ - unsigned int bit; /* Flag bit */ +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 }, @@ -64,6 +72,8 @@ const struct { { "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 } }; @@ -78,27 +88,31 @@ const struct { * conn [in] - The connection data. * authused [in] - The authentication mechanism used. */ -void Curl_sasl_cleanup(struct connectdata *conn, unsigned int authused) +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_gssapi_cleanup(&conn->krb5); + 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_ntlm_cleanup(&conn->ntlm); + Curl_auth_cleanup_ntlm(&conn->ntlm); } #endif - -#if !defined(USE_KERBEROS5) && !defined(USE_NTLM) - /* Reserved for future use */ - (void)conn; - (void)authused; -#endif } /* @@ -114,7 +128,8 @@ void Curl_sasl_cleanup(struct connectdata *conn, unsigned int authused) * * Returns the SASL mechanism token or 0 if no match. */ -unsigned int Curl_sasl_decode_mech(const char *ptr, size_t maxlen, size_t *len) +unsigned short Curl_sasl_decode_mech(const char *ptr, size_t maxlen, + size_t *len) { unsigned int i; char c; @@ -146,7 +161,6 @@ CURLcode Curl_sasl_parse_url_auth_option(struct SASL *sasl, const char *value, size_t len) { CURLcode result = CURLE_OK; - unsigned int mechbit; size_t mechlen; if(!len) @@ -160,7 +174,7 @@ CURLcode Curl_sasl_parse_url_auth_option(struct SASL *sasl, if(!strncmp(value, "*", len)) sasl->prefmech = SASL_AUTH_DEFAULT; else { - mechbit = Curl_sasl_decode_mech(value, len, &mechlen); + unsigned short mechbit = Curl_sasl_decode_mech(value, len, &mechlen); if(mechbit && mechlen == len) sasl->prefmech |= mechbit; else @@ -175,25 +189,49 @@ CURLcode Curl_sasl_parse_url_auth_option(struct SASL *sasl, * * Initializes the SASL structure. */ -void Curl_sasl_init(struct SASL *sasl, const struct SASLproto *params) +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 = SASL_AUTH_DEFAULT; /* Prefer all mechanisms */ - sasl->authused = SASL_AUTH_NONE; /* No the authentication mechanism used */ + 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) { + unsigned short mechs = SASL_AUTH_NONE; + + /* If some usable http authentication options have been set, determine + new defaults from them. */ + if(auth & CURLAUTH_BASIC) + mechs |= SASL_MECH_PLAIN | SASL_MECH_LOGIN; + if(auth & CURLAUTH_DIGEST) + mechs |= SASL_MECH_DIGEST_MD5; + if(auth & CURLAUTH_NTLM) + mechs |= SASL_MECH_NTLM; + if(auth & CURLAUTH_BEARER) + mechs |= SASL_MECH_OAUTHBEARER | SASL_MECH_XOAUTH2; + if(auth & CURLAUTH_GSSAPI) + mechs |= SASL_MECH_GSSAPI; + + if(mechs != SASL_AUTH_NONE) + sasl->prefmech = mechs; + } } /* - * state() + * sasl_state() * * This is the ONLY way to change SASL state! */ -static void state(struct SASL *sasl, struct connectdata *conn, - saslstate newstate) +static void sasl_state(struct SASL *sasl, struct Curl_easy *data, + saslstate newstate) { #if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS) /* for debug purposes */ @@ -213,33 +251,84 @@ static void state(struct SASL *sasl, struct connectdata *conn, "GSSAPI_NO_DATA", "OAUTH2", "OAUTH2_RESP", + "GSASL", "CANCEL", "FINAL", /* LAST */ }; if(sasl->state != newstate) - infof(conn->data, "SASL %p state change from %s to %s\n", + infof(data, "SASL %p state change from %s to %s", (void *)sasl, names[sasl->state], names[newstate]); #else - (void) conn; + (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 connectdata *conn) +bool Curl_sasl_can_authenticate(struct SASL *sasl, struct Curl_easy *data) { /* Have credentials been provided? */ - if(conn->bits.user_passwd) + if(data->state.aptr.user) return TRUE; - /* EXTERNAL can authenticate without a user name and/or password */ + /* EXTERNAL can authenticate without a username and/or password */ if(sasl->authmechs & sasl->prefmech & SASL_MECH_EXTERNAL) return TRUE; @@ -251,26 +340,29 @@ bool Curl_sasl_can_authenticate(struct SASL *sasl, struct connectdata *conn) * * Calculate the required login details for SASL authentication. */ -CURLcode Curl_sasl_start(struct SASL *sasl, struct connectdata *conn, +CURLcode Curl_sasl_start(struct SASL *sasl, struct Curl_easy *data, bool force_ir, saslprogress *progress) { CURLcode result = CURLE_OK; - struct Curl_easy *data = conn->data; - unsigned int enabledmechs; + struct connectdata *conn = data->conn; + unsigned short enabledmechs; const char *mech = NULL; - char *resp = NULL; - size_t len = 0; + struct bufref resp; saslstate state1 = SASL_STOP; saslstate state2 = SASL_FINAL; - const char * const hostname = SSL_IS_PROXY() ? conn->http_proxy.host.name : - conn->host.name; - const long int port = SSL_IS_PROXY() ? conn->port : conn->remote_port; -#if defined(USE_KERBEROS5) + 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; @@ -284,14 +376,13 @@ CURLcode Curl_sasl_start(struct SASL *sasl, struct connectdata *conn, sasl->authused = SASL_MECH_EXTERNAL; if(force_ir || data->set.sasl_ir) - result = Curl_auth_create_external_message(data, conn->user, &resp, - &len); + Curl_auth_create_external_message(conn->user, &resp); } - else if(conn->bits.user_passwd) { + 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; /* TODO: Calculate mutual authentication */ + sasl->mutual_auth = FALSE; mech = SASL_MECH_STRING_GSSAPI; state1 = SASL_GSSAPI; state2 = SASL_GSSAPI_TOKEN; @@ -301,15 +392,43 @@ CURLcode Curl_sasl_start(struct SASL *sasl, struct connectdata *conn, result = Curl_auth_create_gssapi_user_message(data, conn->user, conn->passwd, service, - data->easy_conn-> - host.name, + conn->host.name, sasl->mutual_auth, NULL, &conn->krb5, - &resp, &len); + &resp); } else #endif -#ifndef CURL_DISABLE_CRYPTO_AUTH +#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; @@ -331,34 +450,46 @@ CURLcode Curl_sasl_start(struct SASL *sasl, struct connectdata *conn, sasl->authused = SASL_MECH_NTLM; if(force_ir || data->set.sasl_ir) - result = Curl_auth_create_ntlm_type1_message(conn->user, conn->passwd, - &conn->ntlm, &resp, &len); + result = Curl_auth_create_ntlm_type1_message(data, + conn->user, conn->passwd, + service, + hostname, + &conn->ntlm, &resp); } else #endif - if((enabledmechs & SASL_MECH_OAUTHBEARER) && conn->oauth_bearer) { + 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(data, conn->user, + result = Curl_auth_create_oauth_bearer_message(conn->user, hostname, port, - conn->oauth_bearer, - &resp, &len); + oauth_bearer, + &resp); } - else if((enabledmechs & SASL_MECH_XOAUTH2) && conn->oauth_bearer) { + 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_oauth_bearer_message(data, conn->user, - NULL, 0, - conn->oauth_bearer, - &resp, &len); + 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; @@ -367,35 +498,29 @@ CURLcode Curl_sasl_start(struct SASL *sasl, struct connectdata *conn, sasl->authused = SASL_MECH_LOGIN; if(force_ir || data->set.sasl_ir) - result = Curl_auth_create_login_message(data, conn->user, &resp, &len); - } - 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(data, conn->user, conn->passwd, - &resp, &len); + Curl_auth_create_login_message(conn->user, &resp); } } if(!result && mech) { - if(resp && sasl->params->maxirlen && - strlen(mech) + len > sasl->params->maxirlen) { - free(resp); - resp = NULL; - } + 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); - result = sasl->params->sendauth(conn, mech, resp); if(!result) { *progress = SASL_INPROGRESS; - state(sasl, conn, resp ? state2 : state1); + sasl_state(sasl, data, Curl_bufref_ptr(&resp) ? state2 : state1); } } - free(resp); - + Curl_bufref_free(&resp); return result; } @@ -404,42 +529,41 @@ CURLcode Curl_sasl_start(struct SASL *sasl, struct connectdata *conn, * * Continue the authentication. */ -CURLcode Curl_sasl_continue(struct SASL *sasl, struct connectdata *conn, +CURLcode Curl_sasl_continue(struct SASL *sasl, struct Curl_easy *data, int code, saslprogress *progress) { CURLcode result = CURLE_OK; - struct Curl_easy *data = conn->data; + struct connectdata *conn = data->conn; saslstate newstate = SASL_FINAL; - char *resp = NULL; - const char * const hostname = SSL_IS_PROXY() ? conn->http_proxy.host.name : - conn->host.name; - const long int port = SSL_IS_PROXY() ? conn->port : conn->remote_port; -#if !defined(CURL_DISABLE_CRYPTO_AUTH) - char *serverdata; - char *chlg = NULL; - size_t chlglen = 0; -#endif -#if !defined(CURL_DISABLE_CRYPTO_AUTH) || defined(USE_KERBEROS5) + 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; + data->set.str[STRING_SERVICE_NAME] : + sasl->params->service; #endif - size_t len = 0; + 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; - state(sasl, conn, SASL_STOP); + 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; - state(sasl, conn, SASL_STOP); + sasl_state(sasl, data, SASL_STOP); return CURLE_LOGIN_DENIED; } @@ -448,61 +572,68 @@ CURLcode Curl_sasl_continue(struct SASL *sasl, struct connectdata *conn, *progress = SASL_DONE; return result; case SASL_PLAIN: - result = Curl_auth_create_plain_message(data, conn->user, conn->passwd, - &resp, - &len); + result = Curl_auth_create_plain_message(conn->sasl_authzid, + conn->user, conn->passwd, &resp); break; case SASL_LOGIN: - result = Curl_auth_create_login_message(data, conn->user, &resp, &len); + Curl_auth_create_login_message(conn->user, &resp); newstate = SASL_LOGIN_PASSWD; break; case SASL_LOGIN_PASSWD: - result = Curl_auth_create_login_message(data, conn->passwd, &resp, &len); + Curl_auth_create_login_message(conn->passwd, &resp); break; case SASL_EXTERNAL: - result = Curl_auth_create_external_message(data, conn->user, &resp, &len); + Curl_auth_create_external_message(conn->user, &resp); break; - -#ifndef CURL_DISABLE_CRYPTO_AUTH +#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: - sasl->params->getmessage(data->state.buffer, &serverdata); - result = Curl_auth_decode_cram_md5_message(serverdata, &chlg, &chlglen); + result = get_server_message(sasl, data, &serverdata); if(!result) - result = Curl_auth_create_cram_md5_message(data, chlg, conn->user, - conn->passwd, &resp, &len); - free(chlg); + result = Curl_auth_create_cram_md5_message(&serverdata, conn->user, + conn->passwd, &resp); break; case SASL_DIGESTMD5: - sasl->params->getmessage(data->state.buffer, &serverdata); - result = Curl_auth_create_digest_md5_message(data, serverdata, - conn->user, conn->passwd, - service, - &resp, &len); - newstate = SASL_DIGESTMD5_RESP; + 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: - resp = strdup(""); - if(!resp) - result = CURLE_OUT_OF_MEMORY; + /* 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(conn->user, conn->passwd, - &conn->ntlm, &resp, &len); + 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 */ - sasl->params->getmessage(data->state.buffer, &serverdata); - result = Curl_auth_decode_ntlm_type2_message(data, serverdata, - &conn->ntlm); + 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, &len); + &resp); break; #endif @@ -511,56 +642,63 @@ CURLcode Curl_sasl_continue(struct SASL *sasl, struct connectdata *conn, result = Curl_auth_create_gssapi_user_message(data, conn->user, conn->passwd, service, - data->easy_conn->host.name, + conn->host.name, sasl->mutual_auth, NULL, &conn->krb5, - &resp, &len); + &resp); newstate = SASL_GSSAPI_TOKEN; break; case SASL_GSSAPI_TOKEN: - sasl->params->getmessage(data->state.buffer, &serverdata); - 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, &len); - newstate = SASL_GSSAPI_NO_DATA; + 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); } - else - /* Decode the security challenge and create the response message */ - result = Curl_auth_create_gssapi_security_message(data, serverdata, - &conn->krb5, - &resp, &len); break; case SASL_GSSAPI_NO_DATA: - sasl->params->getmessage(data->state.buffer, &serverdata); /* Decode the security challenge and create the response message */ - result = Curl_auth_create_gssapi_security_message(data, serverdata, - &conn->krb5, - &resp, &len); + 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 authorisation message */ + /* Create the authorization message */ if(sasl->authused == SASL_MECH_OAUTHBEARER) { - result = Curl_auth_create_oauth_bearer_message(data, conn->user, + result = Curl_auth_create_oauth_bearer_message(conn->user, hostname, port, - conn->oauth_bearer, - &resp, &len); + oauth_bearer, + &resp); /* Failures maybe sent by the server as continuations for OAUTHBEARER */ newstate = SASL_OAUTH2_RESP; } else - result = Curl_auth_create_oauth_bearer_message(data, conn->user, - NULL, 0, - conn->oauth_bearer, - &resp, &len); + result = Curl_auth_create_xoauth_bearer_message(conn->user, + oauth_bearer, + &resp); break; case SASL_OAUTH2_RESP: @@ -568,20 +706,17 @@ CURLcode Curl_sasl_continue(struct SASL *sasl, struct connectdata *conn, if(code == sasl->params->finalcode) { /* Final response was received so we are done */ *progress = SASL_DONE; - state(sasl, conn, SASL_STOP); + sasl_state(sasl, data, SASL_STOP); return result; } else if(code == sasl->params->contcode) { - /* Acknowledge the continuation by sending a 0x01 response base64 - encoded */ - resp = strdup("AQ=="); - if(!resp) - result = CURLE_OUT_OF_MEMORY; + /* Acknowledge the continuation by sending a 0x01 response. */ + Curl_bufref_set(&resp, "\x01", 1, NULL); break; } else { *progress = SASL_DONE; - state(sasl, conn, SASL_STOP); + sasl_state(sasl, data, SASL_STOP); return CURLE_LOGIN_DENIED; } @@ -590,24 +725,25 @@ CURLcode Curl_sasl_continue(struct SASL *sasl, struct connectdata *conn, sasl->authmechs ^= sasl->authused; /* Start an alternative SASL authentication */ - result = Curl_sasl_start(sasl, conn, sasl->force_ir, progress); - newstate = sasl->state; /* Use state from Curl_sasl_start() */ - break; + 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->sendcont(conn, "*"); + result = sasl->params->cancelauth(data, sasl->curmech); newstate = SASL_CANCEL; break; case CURLE_OK: - if(resp) - result = sasl->params->sendcont(conn, resp); + result = build_message(sasl, &resp); + if(!result) + result = sasl->params->contauth(data, sasl->curmech, &resp); break; default: newstate = SASL_STOP; /* Stop on error */ @@ -615,9 +751,10 @@ CURLcode Curl_sasl_continue(struct SASL *sasl, struct connectdata *conn, break; } - free(resp); + Curl_bufref_free(&resp); - state(sasl, conn, newstate); + sasl_state(sasl, data, newstate); return result; } +#endif /* protocols are enabled that use SASL */ diff --git a/contrib/curl/lib/curl_sasl.h b/contrib/curl/lib/curl_sasl.h index 7647a48be4..e94e6431a2 100644 --- a/contrib/curl/lib/curl_sasl.h +++ b/contrib/curl/lib/curl_sasl.h @@ -7,11 +7,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 2012 - 2016, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -20,10 +20,14 @@ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY * KIND, either express or implied. * + * SPDX-License-Identifier: curl + * ***************************************************************************/ #include +#include "bufref.h" + struct Curl_easy; struct connectdata; @@ -37,22 +41,29 @@ struct connectdata; #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 ~0U +#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_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 { @@ -71,6 +82,7 @@ typedef enum { SASL_GSSAPI_NO_DATA, SASL_OAUTH2, SASL_OAUTH2_RESP, + SASL_GSASL, SASL_CANCEL, SASL_FINAL } saslstate; @@ -85,28 +97,37 @@ typedef enum { /* Protocol dependent SASL parameters */ struct SASLproto { const char *service; /* The service name */ - int contcode; /* Code to receive when continuation is expected */ - int finalcode; /* Code to receive upon authentication success */ - size_t maxirlen; /* Maximum initial response length */ - CURLcode (*sendauth)(struct connectdata *conn, - const char *mech, const char *ir); + CURLcode (*sendauth)(struct Curl_easy *data, const char *mech, + const struct bufref *ir); /* Send authentication command */ - CURLcode (*sendcont)(struct connectdata *conn, const char *contauth); + CURLcode (*contauth)(struct Curl_easy *data, const char *mech, + const struct bufref *contauth); /* Send authentication continuation */ - void (*getmessage)(char *buffer, char **outptr); + 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 */ - unsigned int authmechs; /* Accepted authentication mechanisms */ - unsigned int prefmech; /* Preferred authentication mechanism */ - unsigned int authused; /* Auth mechanism used for the connection */ - bool resetprefs; /* For URL auth option parsing. */ - bool mutual_auth; /* Mutual authentication enabled (GSSAPI only) */ - bool force_ir; /* Protocol always supports initial response */ + 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 */ @@ -116,28 +137,29 @@ struct SASL { /* This is used to cleanup any libraries or curl modules used by the sasl functions */ -void Curl_sasl_cleanup(struct connectdata *conn, unsigned int authused); +void Curl_sasl_cleanup(struct connectdata *conn, unsigned short authused); /* Convert a mechanism name to a token */ -unsigned int Curl_sasl_decode_mech(const char *ptr, - size_t maxlen, size_t *len); +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, const struct SASLproto *params); +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 connectdata *conn); +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 connectdata *conn, +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 connectdata *conn, +CURLcode Curl_sasl_continue(struct SASL *sasl, struct Curl_easy *data, int code, saslprogress *progress); #endif /* HEADER_CURL_SASL_H */ diff --git a/contrib/curl/lib/curl_setup.h b/contrib/curl/lib/curl_setup.h index 5b20ad7806..479dcee88f 100644 --- a/contrib/curl/lib/curl_setup.h +++ b/contrib/curl/lib/curl_setup.h @@ -7,11 +7,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2016, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -20,15 +20,103 @@ * 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 + +/* Tell "curl/curl.h" not to include "curl/mprintf.h" */ +#define CURL_SKIP_INCLUDE_MPRINTF + +/* FIXME: Delete this once the warnings have been fixed. */ +#if !defined(CURL_WARN_SIGN_CONVERSION) +#ifdef __GNUC__ +#pragma GCC diagnostic ignored "-Wsign-conversion" +#endif +#endif + +/* Set default _WIN32_WINNT */ +#ifdef __MINGW32__ +#include <_mingw.h> +#endif + +/* Workaround for Homebrew gcc 12.4.0, 13.3.0, 14.1.0 and newer (as of 14.1.0) + that started advertising the `availability` attribute, which then gets used + by Apple SDK, but, in a way incompatible with gcc, resulting in misc errors + inside SDK headers, e.g.: + error: attributes should be specified before the declarator in a function + definition + error: expected ',' or '}' before + Followed by missing declarations. + Fix it by overriding the built-in feature-check macro used by the headers + to enable the problematic attributes. This makes the feature check fail. */ +#if defined(__APPLE__) && \ + !defined(__clang__) && \ + defined(__GNUC__) && __GNUC__ >= 12 && \ + defined(__has_attribute) +#define availability curl_pp_attribute_disabled +#endif + +#if defined(__APPLE__) +#include +#include +/* Fixup faulty target macro initialization in macOS SDK since v14.4 (as of + 15.0 beta). The SDK target detection in `TargetConditionals.h` correctly + detects macOS, but fails to set the macro's old name `TARGET_OS_OSX`, then + continues to set it to a default value of 0. Other parts of the SDK still + rely on the old name, and with this inconsistency our builds fail due to + missing declarations. It happens when using mainline llvm older than v18. + Later versions fixed it by predefining these target macros, avoiding the + faulty dynamic detection. gcc is not affected (for now) because it lacks + the necessary dynamic detection features, so the SDK falls back to + a codepath that sets both the old and new macro to 1. */ +#if defined(TARGET_OS_MAC) && TARGET_OS_MAC && \ + defined(TARGET_OS_OSX) && !TARGET_OS_OSX && \ + (!defined(TARGET_OS_IPHONE) || !TARGET_OS_IPHONE) && \ + (!defined(TARGET_OS_SIMULATOR) || !TARGET_OS_SIMULATOR) +#undef TARGET_OS_OSX +#define TARGET_OS_OSX TARGET_OS_MAC +#endif +#endif + +/* + * Disable Visual Studio warnings: + * 4127 "conditional expression is constant" + */ +#ifdef _MSC_VER +#pragma warning(disable:4127) +#endif + +#ifdef _WIN32 /* - * Define WIN32 when build target is Win32 API + * Do not 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 +# if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_APP) && \ + !WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP) +# define CURL_WINDOWS_UWP +# endif +# endif +#endif -#if (defined(_WIN32) || defined(__WIN32__)) && !defined(WIN32) && \ - !defined(__SYMBIAN32__) -#define WIN32 +/* Compatibility */ +#if defined(ENABLE_IPV6) +# define USE_IPV6 1 #endif /* @@ -45,12 +133,12 @@ #ifdef _WIN32_WCE # include "config-win32ce.h" #else -# ifdef WIN32 +# ifdef _WIN32 # include "config-win32.h" # endif #endif -#if defined(macintosh) && defined(__MRC__) +#ifdef macintosh # include "config-mac.h" #endif @@ -62,20 +150,16 @@ # include "config-amigaos.h" #endif -#ifdef __SYMBIAN32__ -# include "config-symbian.h" -#endif - #ifdef __OS400__ # include "config-os400.h" #endif -#ifdef TPF -# include "config-tpf.h" +#ifdef __PLAN9__ +# include "config-plan9.h" #endif -#ifdef __VXWORKS__ -# include "config-vxworks.h" +#ifdef MSDOS +# include "config-dos.h" #endif #ifdef __linux__ @@ -91,7 +175,7 @@ # undef HAVE_IOCTL_SIOCGIFADDR #endif -#ifdef __APPLE__ && __MACH__ +#if defined(__APPLE__) && defined(__MACH__) # include "config-osx.h" #endif @@ -141,26 +225,9 @@ /* please, do it beyond the point further indicated in this file. */ /* ================================================================ */ -/* - * libcurl's external interface definitions are also used internally, - * and might also include required system header files to define them. - */ - -#include - -/* - * Compile time sanity checks must also be done when building the library. - */ - -#include - -/* - * Ensure that no one is using the old SIZEOF_CURL_OFF_T macro - */ - -#ifdef SIZEOF_CURL_OFF_T -# error "SIZEOF_CURL_OFF_T shall not be defined!" - Error Compilation_aborted_SIZEOF_CURL_OFF_T_shall_not_be_defined +/* Give calloc a chance to be dragging in early, so we do not redefine */ +#if defined(USE_THREADS_POSIX) && defined(HAVE_PTHREAD_H) +# include #endif /* @@ -168,44 +235,47 @@ */ #ifdef HTTP_ONLY -# ifndef CURL_DISABLE_TFTP -# define CURL_DISABLE_TFTP +# 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_TELNET -# define CURL_DISABLE_TELNET +# ifndef CURL_DISABLE_LDAPS +# define CURL_DISABLE_LDAPS # endif -# ifndef CURL_DISABLE_DICT -# define CURL_DISABLE_DICT +# ifndef CURL_DISABLE_MQTT +# define CURL_DISABLE_MQTT # endif -# ifndef CURL_DISABLE_FILE -# define CURL_DISABLE_FILE +# ifndef CURL_DISABLE_POP3 +# define CURL_DISABLE_POP3 # endif # ifndef CURL_DISABLE_RTSP # define CURL_DISABLE_RTSP # endif -# ifndef CURL_DISABLE_POP3 -# define CURL_DISABLE_POP3 -# endif -# ifndef CURL_DISABLE_IMAP -# define CURL_DISABLE_IMAP +# ifndef CURL_DISABLE_SMB +# define CURL_DISABLE_SMB # endif # ifndef CURL_DISABLE_SMTP # define CURL_DISABLE_SMTP # endif -# ifndef CURL_DISABLE_RTMP -# define CURL_DISABLE_RTMP -# endif -# ifndef CURL_DISABLE_GOPHER -# define CURL_DISABLE_GOPHER +# ifndef CURL_DISABLE_TELNET +# define CURL_DISABLE_TELNET # endif -# ifndef CURL_DISABLE_SMB -# define CURL_DISABLE_SMB +# ifndef CURL_DISABLE_TFTP +# define CURL_DISABLE_TFTP # endif #endif @@ -217,9 +287,26 @@ # 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. The only allowed ones are those included from curlbuild.h */ +/* point. */ /* ================================================================ */ /* @@ -239,65 +326,76 @@ #endif /* - * 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. + * Windows setup file includes some system headers. */ -#ifdef __APPLE__ -#define USE_RESOLVE_ON_IPS 1 + +#ifdef _WIN32 +# include "setup-win32.h" #endif -/* - * Include header files for windows builds before redefining anything. - * Use this preprocessor block only to include or exclude windows.h, - * winsock2.h, ws2tcpip.h or winsock.h. Any other windows thing belongs - * to any other further and independent block. Under Cygwin things work - * just as under linux (e.g. ) and the winsock headers should - * never be included when __CYGWIN__ is defined. configure script takes - * care of this, not defining HAVE_WINDOWS_H, HAVE_WINSOCK_H, HAVE_WINSOCK2_H, - * neither HAVE_WS2TCPIP_H when __CYGWIN__ is defined. - */ +#include -#ifdef HAVE_WINDOWS_H -# if defined(UNICODE) && !defined(_UNICODE) -# define _UNICODE -# endif -# if defined(_UNICODE) && !defined(UNICODE) -# define UNICODE -# endif -# ifndef WIN32_LEAN_AND_MEAN -# define WIN32_LEAN_AND_MEAN -# endif -# include -# ifdef HAVE_WINSOCK2_H -# include -# ifdef HAVE_WS2TCPIP_H -# include -# endif -# else -# ifdef HAVE_WINSOCK_H -# include -# endif -# endif -# include -# ifdef UNICODE - typedef wchar_t *(*curl_wcsdup_callback)(const wchar_t *str); -# endif -#endif +/* Helper macro to expand and concatenate two macros. + * Direct macros concatenation does not work because macros + * are not expanded before direct concatenation. + */ +#define CURL_CONC_MACROS_(A,B) A ## B +#define CURL_CONC_MACROS(A,B) CURL_CONC_MACROS_(A,B) -/* - * Define USE_WINSOCK to 2 if we have and use WINSOCK2 API, else - * define USE_WINSOCK to 1 if we have and use WINSOCK API, else - * undefine USE_WINSOCK. +/* curl uses its own printf() function internally. It understands the GNU + * format. Use this format, so that is matches the GNU format attribute we + * use with the MinGW compiler, allowing it to verify them at compile-time. */ +#ifdef __MINGW32__ +# undef CURL_FORMAT_CURL_OFF_T +# undef CURL_FORMAT_CURL_OFF_TU +# define CURL_FORMAT_CURL_OFF_T "lld" +# define CURL_FORMAT_CURL_OFF_TU "llu" +#endif -#undef USE_WINSOCK +/* based on logic in "curl/mprintf.h" */ -#ifdef HAVE_WINSOCK2_H -# define USE_WINSOCK 2 +#if (defined(__GNUC__) || defined(__clang__) || \ + defined(__IAR_SYSTEMS_ICC__)) && \ + defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) && \ + !defined(CURL_NO_FMT_CHECKS) +#if defined(__MINGW32__) && !defined(__clang__) +#define CURL_PRINTF(fmt, arg) \ + __attribute__((format(gnu_printf, fmt, arg))) +#else +#define CURL_PRINTF(fmt, arg) \ + __attribute__((format(__printf__, fmt, arg))) +#endif #else -# ifdef HAVE_WINSOCK_H -# define USE_WINSOCK 1 +#define CURL_PRINTF(fmt, arg) +#endif + +/* Override default printf mask check rules in "curl/mprintf.h" */ +#define CURL_TEMP_PRINTF CURL_PRINTF + +/* Workaround for mainline llvm v16 and earlier missing a built-in macro + expected by macOS SDK v14 / Xcode v15 (2023) and newer. + gcc (as of v14) is also missing it. */ +#if defined(__APPLE__) && \ + ((!defined(__apple_build_version__) && \ + defined(__clang__) && __clang_major__ < 17) || \ + (defined(__GNUC__) && __GNUC__ <= 14)) && \ + defined(__ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__) && \ + !defined(__ENVIRONMENT_OS_VERSION_MIN_REQUIRED__) +#define __ENVIRONMENT_OS_VERSION_MIN_REQUIRED__ \ + __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ +#endif + +/* + * Use getaddrinfo to resolve the IPv4 address literal. If the current network + * interface does not support IPv4, but supports IPv6, NAT64, and DNS64, + * performing this task will result in a synthesized IPv6 address. + */ +#if defined(__APPLE__) && !defined(USE_ARES) +#define USE_RESOLVE_ON_IPS 1 +# if TARGET_OS_MAC && !(defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE) && \ + defined(USE_IPV6) +# define CURL_MACOS_CALL_COPYPROXIES 1 # endif #endif @@ -315,64 +413,66 @@ # include #endif -#ifdef TPF -# include /* for bzero, strcasecmp, and strncasecmp */ -# include /* for strcpy and strlen */ -# include /* for rand and srand */ -# include /* for select and ioctl*/ -# include /* for in_addr_t definition */ -# include /* for tpf_process_signals */ - /* change which select is used for libcurl */ -# define select(a,b,c,d,e) tpf_select_libcurl(a,b,c,d,e) -#endif - -#ifdef __VXWORKS__ -# include /* for generic BSD socket functions */ -# include /* for basic I/O interface functions */ -#endif - #ifdef __AMIGA__ -# ifndef __ixemul__ -# include -# include -# include -# include -# define select(a,b,c,d,e) WaitSelect(a,b,c,d,e,0) +# 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 +# include +# include +# include +# include +# 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 +# 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 -#ifdef HAVE_ASSERT_H #include -#endif -#ifdef __TANDEM /* for nsr-tandem-nsk systems */ -#include +#ifdef __TANDEM /* for ns*-tandem-nsk systems */ +# if ! defined __LP64 +# include /* FLOSS is only used for 32-bit builds. */ +# endif #endif #ifndef STDC_HEADERS /* no standard C headers! */ #include #endif -#ifdef __POCC__ -# include -# include -# define sys_nerr EILSEQ -#endif - -/* - * Salford-C kludge section (mostly borrowed from wxWidgets). - */ -#ifdef __SALFORDC__ - #pragma suppress 353 /* Possible nested comments */ - #pragma suppress 593 /* Define not used */ - #pragma suppress 61 /* enum has no name */ - #pragma suppress 106 /* unnamed, unused parameter */ - #include -#endif - /* - * Large file (>2Gb) support using WIN32 functions. + * Large file (>2Gb) support using Win32 functions. */ #ifdef USE_WIN32_LARGE_FILES @@ -384,13 +484,18 @@ # undef fstat # define fstat(fdes,stp) _fstati64(fdes, stp) # undef stat -# define stat(fname,stp) _stati64(fname, stp) +# 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) + 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); #endif /* - * Small file (<2Gb) support using WIN32 functions. + * Small file (<2Gb) support using Win32 functions. */ #ifdef USE_WIN32_SMALL_FILES @@ -401,8 +506,13 @@ # undef lseek # define lseek(fdes,offset,whence) _lseek(fdes, (long)offset, whence) # define fstat(fdes,stp) _fstat(fdes, stp) -# define stat(fname,stp) _stat(fname, 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) + 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); # endif # define LSEEK_ERROR (long)-1 #endif @@ -415,8 +525,31 @@ # define LSEEK_ERROR (off_t)-1 #endif +#ifndef SIZEOF_TIME_T +/* assume default size of time_t to be 32 bits */ +#define SIZEOF_TIME_T 4 +#endif + +#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 FMT_SOCKET_T "d" +#elif defined(__MINGW32__) +# define FMT_SOCKET_T "zd" +#else +# define FMT_SOCKET_T "qd" +#endif + /* - * Default sizeof(off_t) in case it hasn't been defined in config file. + * Default sizeof(off_t) in case it has not been defined in config file. */ #ifndef SIZEOF_OFF_T @@ -442,8 +575,69 @@ # 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_CURL_OFF_T != 8) +# error "curl_off_t must be exactly 64 bits" +#else + typedef unsigned CURL_TYPEOF_CURL_OFF_T curl_uint64_t; + typedef CURL_TYPEOF_CURL_OFF_T curl_int64_t; +# ifndef CURL_SUFFIX_CURL_OFF_TU +# error "CURL_SUFFIX_CURL_OFF_TU must be defined" +# endif +# define CURL_UINT64_SUFFIX CURL_SUFFIX_CURL_OFF_TU +# define CURL_UINT64_C(val) CURL_CONC_MACROS(val,CURL_UINT64_SUFFIX) +# define FMT_PRId64 CURL_FORMAT_CURL_OFF_T +# define FMT_PRIu64 CURL_FORMAT_CURL_OFF_TU +#endif + +#define FMT_OFF_T CURL_FORMAT_CURL_OFF_T +#define FMT_OFF_TU CURL_FORMAT_CURL_OFF_TU + +#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 do not */ +#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 do not */ +#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. + * Arg 2 type for gethostname in case it has not been defined in config file. */ #ifndef GETHOSTNAME_TYPE_ARG2 @@ -460,12 +654,11 @@ 5. set dir/file naming defines */ -#ifdef WIN32 +#ifdef _WIN32 # define DIR_CHAR "\\" -# define DOT_CHAR "_" -#else /* WIN32 */ +#else /* _WIN32 */ # ifdef MSDOS /* Watt-32 */ @@ -489,58 +682,20 @@ # endif # define DIR_CHAR "/" -# ifndef DOT_CHAR -# define DOT_CHAR "." -# endif - -# ifdef MSDOS -# undef DOT_CHAR -# define DOT_CHAR "_" -# endif -# ifndef fileno /* sunos 4 have this as a macro! */ - int fileno(FILE *stream); -# endif - -#endif /* WIN32 */ - -/* - * msvc 6.0 requires PSDK in order to have INET6_ADDRSTRLEN - * defined in ws2tcpip.h as well as to provide IPv6 support. - * Does not apply if lwIP is used. - */ - -#if defined(_MSC_VER) && !defined(__POCC__) && !defined(USE_LWIPSOCK) -# if !defined(HAVE_WS2TCPIP_H) || \ - ((_MSC_VER < 1300) && !defined(INET6_ADDRSTRLEN)) -# undef HAVE_GETADDRINFO_THREADSAFE -# undef HAVE_FREEADDRINFO -# undef HAVE_GETADDRINFO -# undef HAVE_GETNAMEINFO -# undef ENABLE_IPV6 -# endif -#endif +#endif /* _WIN32 */ /* ---------------------------------------------------------------- */ /* resolver specialty compile-time defines */ /* CURLRES_* defines to use in the host*.c sources */ /* ---------------------------------------------------------------- */ -/* - * lcc-win32 doesn't have _beginthreadex(), lacks threads support. - */ - -#if defined(__LCC__) && defined(WIN32) -# undef USE_THREADS_POSIX -# undef USE_THREADS_WIN32 -#endif - /* * MSVC threads support requires a multi-threaded runtime library. * _beginthreadex() is not available in single-threaded ones. */ -#if defined(_MSC_VER) && !defined(__POCC__) && !defined(_MT) +#if defined(_MSC_VER) && !defined(_MT) # undef USE_THREADS_POSIX # undef USE_THREADS_WIN32 #endif @@ -549,13 +704,21 @@ * Mutually exclusive CURLRES_* definitions. */ +#if defined(USE_IPV6) && defined(HAVE_GETADDRINFO) +# define CURLRES_IPV6 +#elif defined(USE_IPV6) && (defined(_WIN32) || defined(__CYGWIN__)) +/* assume on Windows that IPv6 without getaddrinfo is a broken build */ +# error "Unexpected build: IPv6 is enabled but getaddrinfo was not found." +#else +# 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 -# undef HAVE_GETHOSTBYNAME #elif defined(USE_THREADS_POSIX) || defined(USE_THREADS_WIN32) # define CURLRES_ASYNCH # define CURLRES_THREADED @@ -563,110 +726,62 @@ # define CURLRES_SYNCH #endif -#ifdef ENABLE_IPV6 -# define CURLRES_IPV6 -#else -# define CURLRES_IPV4 -#endif - /* ---------------------------------------------------------------- */ -/* - * When using WINSOCK, TELNET protocol requires WINSOCK2 API. - */ - -#if defined(USE_WINSOCK) && (USE_WINSOCK != 2) -# define CURL_DISABLE_TELNET 1 -#endif - -/* - * msvc 6.0 does not have struct sockaddr_storage and - * does not define IPPROTO_ESP in winsock2.h. But both - * are available if PSDK is properly installed. - */ - -#if defined(_MSC_VER) && !defined(__POCC__) -# if !defined(HAVE_WINSOCK2_H) || ((_MSC_VER < 1300) && !defined(IPPROTO_ESP)) -# undef HAVE_STRUCT_SOCKADDR_STORAGE -# endif -#endif - -/* - * Intentionally fail to build when using msvc 6.0 without PSDK installed. - * The brave of heart can circumvent this, defining ALLOW_MSVC6_WITHOUT_PSDK - * in lib/config-win32.h although absolutely discouraged and unsupported. - */ - -#if defined(_MSC_VER) && !defined(__POCC__) -# if !defined(HAVE_WINDOWS_H) || ((_MSC_VER < 1300) && !defined(_FILETIME_)) -# if !defined(ALLOW_MSVC6_WITHOUT_PSDK) -# error MSVC 6.0 requires "February 2003 Platform SDK" a.k.a. \ - "Windows Server 2003 PSDK" -# else -# define CURL_DISABLE_LDAP 1 -# endif -# endif -#endif - -#ifdef NETWARE -int netware_init(void); -#ifndef __NOVELL_LIBC__ -#include -#include -#endif -#endif - -#if defined(HAVE_LIBIDN2) && defined(HAVE_IDN2_H) +#if defined(HAVE_LIBIDN2) && defined(HAVE_IDN2_H) && \ + !defined(USE_WIN32_IDN) && !defined(USE_APPLE_IDN) /* The lib and header are present */ #define USE_LIBIDN2 #endif -#ifndef SIZEOF_TIME_T -/* assume default size of time_t to be 32 bit */ -#define SIZEOF_TIME_T 4 +#if defined(USE_LIBIDN2) && (defined(USE_WIN32_IDN) || defined(USE_APPLE_IDN)) +#error "libidn2 cannot be enabled with WinIDN or AppleIDN, choose one." #endif #define LIBIDN_REQUIRED_VERSION "0.4.1" -#if defined(USE_GNUTLS) || defined(USE_OPENSSL) || defined(USE_NSS) || \ - defined(USE_POLARSSL) || defined(USE_AXTLS) || defined(USE_MBEDTLS) || \ - defined(USE_CYASSL) || defined(USE_SCHANNEL) || \ - defined(USE_DARWINSSL) || defined(USE_GSKIT) +#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 +#if defined(USE_WOLFSSL) && defined(USE_GNUTLS) +/* Avoid defining unprefixed wolfSSL SHA macros colliding with nettle ones */ +#define NO_OLD_WC_NAMES +#endif + /* Single point where USE_SPNEGO definition might be defined */ -#if !defined(CURL_DISABLE_CRYPTO_AUTH) && \ +#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_CRYPTO_AUTH) && \ +#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) && !defined(CURL_DISABLE_CRYPTO_AUTH) -#if defined(USE_OPENSSL) || defined(USE_WINDOWS_SSPI) || \ - defined(USE_GNUTLS) || defined(USE_NSS) || defined(USE_DARWINSSL) || \ - defined(USE_OS400CRYPTO) || defined(USE_WIN32_CRYPTO) - -#define USE_NTLM - -#elif defined(USE_MBEDTLS) -# include -# if defined(MBEDTLS_MD4_C) -#define USE_NTLM +#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 -/* non-configure builds may define CURL_WANTS_CA_BUNDLE_ENV */ -#if defined(CURL_WANTS_CA_BUNDLE_ENV) && !defined(CURL_CA_BUNDLE) -#define CURL_CA_BUNDLE getenv("CURL_CA_BUNDLE") +#if defined(USE_LIBSSH2) || defined(USE_LIBSSH) || defined(USE_WOLFSSH) +#define USE_SSH #endif /* @@ -679,11 +794,42 @@ int netware_init(void); ((__GNUC__ == 2) && defined(__GNUC_MINOR__) && (__GNUC_MINOR__ >= 7))) # define UNUSED_PARAM __attribute__((__unused__)) # define WARN_UNUSED_RESULT __attribute__((warn_unused_result)) +#elif defined(__IAR_SYSTEMS_ICC__) +# define UNUSED_PARAM __attribute__((__unused__)) +# if (__VER__ >= 9040001) +# define WARN_UNUSED_RESULT __attribute__((warn_unused_result)) +# else +# define WARN_UNUSED_RESULT +# endif #else -# define UNUSED_PARAM /*NOTHING*/ +# define UNUSED_PARAM /* NOTHING */ # define WARN_UNUSED_RESULT #endif +/* noreturn attribute */ + +#if !defined(CURL_NORETURN) +#if (defined(__GNUC__) && (__GNUC__ >= 3)) || defined(__clang__) || \ + defined(__IAR_SYSTEMS_ICC__) +# define CURL_NORETURN __attribute__((__noreturn__)) +#elif defined(_MSC_VER) && (_MSC_VER >= 1200) +# define CURL_NORETURN __declspec(noreturn) +#else +# define CURL_NORETURN +#endif +#endif + +/* fallthrough attribute */ + +#if !defined(FALLTHROUGH) +#if (defined(__GNUC__) && __GNUC__ >= 7) || \ + (defined(__clang__) && __clang_major__ >= 10) +# define FALLTHROUGH() __attribute__((fallthrough)) +#else +# define FALLTHROUGH() do {} while (0) +#endif +#endif + /* * Include macros and defines that should only be processed once. */ @@ -697,7 +843,7 @@ int netware_init(void); */ #ifndef Curl_nop_stmt -# define Curl_nop_stmt do { } WHILE_FALSE +# define Curl_nop_stmt do { } while(0) #endif /* @@ -705,31 +851,33 @@ int netware_init(void); */ #if defined(__LWIP_OPT_H__) || defined(LWIP_HDR_OPT_H) -# if defined(SOCKET) || \ - defined(USE_WINSOCK) || \ - defined(HAVE_WINSOCK_H) || \ - defined(HAVE_WINSOCK2_H) || \ - defined(HAVE_WS2TCPIP_H) +# if defined(SOCKET) || defined(USE_WINSOCK) # error "Winsock and lwIP TCP/IP stack definitions shall not coexist!" # endif #endif /* - * Portable symbolic names for Winsock shutdown() mode flags. + * shutdown() flags for systems that do not define them */ -#ifdef USE_WINSOCK -# define SHUT_RD 0x00 -# define SHUT_WR 0x01 -# define SHUT_RDWR 0x02 +#ifndef SHUT_RD +#define SHUT_RD 0x00 #endif -/* Define S_ISREG if not defined by system headers, f.e. MSVC */ +#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, f.e. MSVC */ +/* 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 @@ -737,11 +885,12 @@ int netware_init(void); /* 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) +#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. +/* 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 @@ -749,34 +898,111 @@ 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 -/* WinSock destroys recv() buffer when send() failed. - * Enabled automatically for Windows and for Cygwin as Cygwin sockets are - * wrappers for WinSock sockets. https://github.com/curl/curl/issues/657 - * Define DONT_USE_RECV_BEFORE_SEND_WORKAROUND to force disable workaround. - */ -#if !defined(DONT_USE_RECV_BEFORE_SEND_WORKAROUND) -# if defined(WIN32) || defined(__CYGWIN__) -# define USE_RECV_BEFORE_SEND_WORKAROUND -# endif -#else /* DONT_USE_RECV_BEFORE_SEND_WORKAROUNDS */ -# ifdef USE_RECV_BEFORE_SEND_WORKAROUND -# undef USE_RECV_BEFORE_SEND_WORKAROUND +/* for systems that do not 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 /* DONT_USE_RECV_BEFORE_SEND_WORKAROUNDS */ +#endif -/* Detect Windows App environment which has a restricted access - * to the Win32 APIs. */ -# if defined(_WIN32_WINNT) && (_WIN32_WINNT >= 0x0602) -# include -# if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_APP) && \ - !WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP) -# define CURL_WINDOWS_APP +/* 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 UNITTESTS +#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_OPENSSL_QUIC) && defined(USE_NGHTTP3)) || \ + defined(USE_QUICHE) || defined(USE_MSH3) + +#ifdef CURL_WITH_MULTI_SSL +#error "Multi-SSL combined with QUIC is not supported" +#endif + +#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 +#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 + +#if defined(inline) + /* 'inline' is defined as macro and assumed to be correct */ + /* No need for 'inline' replacement */ +#elif defined(__cplusplus) + /* The code is compiled with C++ compiler. + C++ always supports 'inline'. */ + /* No need for 'inline' replacement */ +#elif defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901 + /* C99 (and later) supports 'inline' keyword */ + /* No need for 'inline' replacement */ +#elif defined(__GNUC__) && __GNUC__ >= 3 + /* GCC supports '__inline__' as an extension */ +# define inline __inline__ +#elif defined(_MSC_VER) && _MSC_VER >= 1400 + /* MSC supports '__inline' from VS 2005 (or even earlier) */ +# define inline __inline +#else + /* Probably 'inline' is not supported by compiler. + Define to the empty string to be on the safe side. */ +# define inline /* empty */ +#endif #endif /* HEADER_CURL_SETUP_H */ diff --git a/contrib/curl/lib/curl_setup_once.h b/contrib/curl/lib/curl_setup_once.h index 4da83499af..1521e69f91 100644 --- a/contrib/curl/lib/curl_setup_once.h +++ b/contrib/curl/lib/curl_setup_once.h @@ -7,11 +7,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2013, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -20,6 +20,8 @@ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY * KIND, either express or implied. * + * SPDX-License-Identifier: curl + * ***************************************************************************/ @@ -31,11 +33,8 @@ #include #include #include -#include - -#ifdef HAVE_ERRNO_H +#include #include -#endif #ifdef HAVE_SYS_TYPES_H #include @@ -55,16 +54,9 @@ #ifdef HAVE_SYS_TIME_H #include -#ifdef TIME_WITH_SYS_TIME -#include -#endif -#else -#ifdef HAVE_TIME_H -#include -#endif #endif -#ifdef WIN32 +#ifdef _WIN32 #include #include #endif @@ -77,6 +69,16 @@ #include #endif +#ifdef USE_WOLFSSL +#include +#endif + +#ifdef USE_SCHANNEL +/* Must set this before 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 @@ -92,6 +94,8 @@ #include #endif +#include "functypes.h" + #ifdef __hpux # if !defined(_XOPEN_SOURCE_EXTENDED) || defined(_KERNEL) # ifdef OLD_APP32_64BIT_OFF_T @@ -101,9 +105,8 @@ # endif #endif - /* - * Definition of timeval struct for platforms that don't have it. + * Definition of timeval struct for platforms that do not have it. */ #ifndef HAVE_STRUCT_TIMEVAL @@ -127,7 +130,7 @@ struct timeval { #if defined(__minix) -/* Minix doesn't support recv on TCP sockets */ +/* Minix does not 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)) @@ -140,7 +143,7 @@ struct timeval { * * 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. + * function has another name then do not 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 @@ -148,107 +151,42 @@ struct timeval { * * 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 yours has another name then do not 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. */ -#if !defined(RECV_TYPE_ARG1) || \ - !defined(RECV_TYPE_ARG2) || \ - !defined(RECV_TYPE_ARG3) || \ - !defined(RECV_TYPE_ARG4) || \ - !defined(RECV_TYPE_RETV) - /* */ - Error Missing_definition_of_return_and_arguments_types_of_recv - /* */ -#else #define sread(x,y,z) (ssize_t)recv((RECV_TYPE_ARG1)(x), \ (RECV_TYPE_ARG2)(y), \ (RECV_TYPE_ARG3)(z), \ (RECV_TYPE_ARG4)(0)) -#endif #else /* HAVE_RECV */ #ifndef sread - /* */ - Error Missing_definition_of_macro_sread - /* */ +#error "Missing definition of macro sread!" #endif #endif /* HAVE_RECV */ #if defined(__minix) -/* Minix doesn't support send on TCP sockets */ +/* Minix does not 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) -#if !defined(SEND_TYPE_ARG1) || \ - !defined(SEND_QUAL_ARG2) || \ - !defined(SEND_TYPE_ARG2) || \ - !defined(SEND_TYPE_ARG3) || \ - !defined(SEND_TYPE_ARG4) || \ - !defined(SEND_TYPE_RETV) - /* */ - Error Missing_definition_of_return_and_arguments_types_of_send - /* */ -#else #define swrite(x,y,z) (ssize_t)send((SEND_TYPE_ARG1)(x), \ - (SEND_TYPE_ARG2)(y), \ + (SEND_QUAL_ARG2 SEND_TYPE_ARG2)(y), \ (SEND_TYPE_ARG3)(z), \ (SEND_TYPE_ARG4)(SEND_4TH_ARG)) -#endif #else /* HAVE_SEND */ #ifndef swrite - /* */ - Error Missing_definition_of_macro_swrite - /* */ +#error "Missing definition of macro swrite!" #endif #endif /* HAVE_SEND */ -#if 0 -#if defined(HAVE_RECVFROM) -/* - * Currently recvfrom is only used on udp sockets. - */ -#if !defined(RECVFROM_TYPE_ARG1) || \ - !defined(RECVFROM_TYPE_ARG2) || \ - !defined(RECVFROM_TYPE_ARG3) || \ - !defined(RECVFROM_TYPE_ARG4) || \ - !defined(RECVFROM_TYPE_ARG5) || \ - !defined(RECVFROM_TYPE_ARG6) || \ - !defined(RECVFROM_TYPE_RETV) - /* */ - Error Missing_definition_of_return_and_arguments_types_of_recvfrom - /* */ -#else -#define sreadfrom(s,b,bl,f,fl) (ssize_t)recvfrom((RECVFROM_TYPE_ARG1) (s), \ - (RECVFROM_TYPE_ARG2 *)(b), \ - (RECVFROM_TYPE_ARG3) (bl), \ - (RECVFROM_TYPE_ARG4) (0), \ - (RECVFROM_TYPE_ARG5 *)(f), \ - (RECVFROM_TYPE_ARG6 *)(fl)) -#endif -#else /* HAVE_RECVFROM */ -#ifndef sreadfrom - /* */ - Error Missing_definition_of_macro_sreadfrom - /* */ -#endif -#endif /* HAVE_RECVFROM */ - - -#ifdef RECVFROM_TYPE_ARG6_IS_VOID -# define RECVFROM_ARG6_T int -#else -# define RECVFROM_ARG6_T RECVFROM_TYPE_ARG6 -#endif -#endif /* if 0 */ - - /* * Function-like macro definition used to close a socket. */ @@ -274,28 +212,6 @@ struct timeval { # define sfcntl fcntl #endif -/* - * Uppercase macro versions of ANSI/ISO is*() functions/macros which - * avoid negative number inputs with argument byte codes > 127. - */ - -#define ISSPACE(x) (isspace((int) ((unsigned char)x))) -#define ISDIGIT(x) (isdigit((int) ((unsigned char)x))) -#define ISALNUM(x) (isalnum((int) ((unsigned char)x))) -#define ISXDIGIT(x) (isxdigit((int) ((unsigned char)x))) -#define ISGRAPH(x) (isgraph((int) ((unsigned char)x))) -#define ISALPHA(x) (isalpha((int) ((unsigned char)x))) -#define ISPRINT(x) (isprint((int) ((unsigned char)x))) -#define ISUPPER(x) (isupper((int) ((unsigned char)x))) -#define ISLOWER(x) (islower((int) ((unsigned char)x))) -#define ISASCII(x) (isascii((int) ((unsigned char)x))) - -#define ISBLANK(x) (int)((((unsigned char)x) == ' ') || \ - (((unsigned char)x) == '\t')) - -#define TOLOWER(x) (tolower((int) ((unsigned char)x))) - - /* * 'bool' stuff compatible with HP-UX headers. */ @@ -310,7 +226,7 @@ struct timeval { /* * 'bool' exists on platforms with , i.e. C99 platforms. - * On non-C99 platforms there's no bool, so define an enum for that. + * On non-C99 platforms there is 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. */ @@ -322,7 +238,7 @@ struct timeval { } bool; /* - * Use a define to let 'true' and 'false' use those enums. There + * 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. @@ -332,6 +248,14 @@ struct timeval { # 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 @@ -347,56 +271,7 @@ struct timeval { #define FALSE false #endif - -/* - * Macro WHILE_FALSE may be used to build single-iteration do-while loops, - * avoiding compiler warnings. Mostly intended for other macro definitions. - */ - -#define WHILE_FALSE while(0) - -#if defined(_MSC_VER) && !defined(__POCC__) -# undef WHILE_FALSE -# if (_MSC_VER < 1500) -# define WHILE_FALSE while(1, 0) -# else -# define WHILE_FALSE \ -__pragma(warning(push)) \ -__pragma(warning(disable:4127)) \ -while(0) \ -__pragma(warning(pop)) -# endif -#endif - - -/* - * Typedef to 'int' if sig_atomic_t is not an available 'typedefed' type. - */ - -#ifndef HAVE_SIG_ATOMIC_T -typedef int sig_atomic_t; -#define HAVE_SIG_ATOMIC_T -#endif - - -/* - * Convenience SIG_ATOMIC_T definition - */ - -#ifdef HAVE_SIG_ATOMIC_T_VOLATILE -#define SIG_ATOMIC_T static sig_atomic_t -#else -#define SIG_ATOMIC_T static volatile sig_atomic_t -#endif - - -/* - * Default return type for signal handlers. - */ - -#ifndef RETSIGTYPE -#define RETSIGTYPE void -#endif +#include "curl_ctype.h" /* @@ -406,7 +281,7 @@ typedef int sig_atomic_t; #ifdef DEBUGBUILD #define DEBUGF(x) x #else -#define DEBUGF(x) do { } WHILE_FALSE +#define DEBUGF(x) do { } while(0) #endif @@ -414,10 +289,11 @@ typedef int sig_atomic_t; * Macro used to include assertion code only in debug builds. */ -#if defined(DEBUGBUILD) && defined(HAVE_ASSERT_H) +#undef DEBUGASSERT +#if defined(DEBUGBUILD) #define DEBUGASSERT(x) assert(x) #else -#define DEBUGASSERT(x) do { } WHILE_FALSE +#define DEBUGASSERT(x) do { } while(0) #endif @@ -435,20 +311,6 @@ typedef int sig_atomic_t; #endif -/* - * Macro ERRNO / SET_ERRNO() returns / sets the NOT *socket-related* errno - * (or equivalent) on this platform to hide platform details to code using it. - */ - -#if defined(WIN32) && !defined(USE_LWIPSOCK) -#define ERRNO ((int)GetLastError()) -#define SET_ERRNO(x) (SetLastError((DWORD)(x))) -#else -#define ERRNO (errno) -#define SET_ERRNO(x) (errno = (x)) -#endif - - /* * Portable error number symbolic names defined to Winsock error codes. */ @@ -534,6 +396,8 @@ typedef int sig_atomic_t; #ifdef __VMS #define argv_item_t __char_ptr32 +#elif defined(_UNICODE) +#define argv_item_t wchar_t * #else #define argv_item_t char * #endif @@ -548,4 +412,3 @@ typedef int sig_atomic_t; #endif /* HEADER_CURL_SETUP_ONCE_H */ - diff --git a/contrib/curl/lib/curl_sha256.h b/contrib/curl/lib/curl_sha256.h new file mode 100644 index 0000000000..c3cf00a217 --- /dev/null +++ b/contrib/curl/lib/curl_sha256.h @@ -0,0 +1,45 @@ +#ifndef HEADER_CURL_SHA256_H +#define HEADER_CURL_SHA256_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Florin Petriuc, + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#if !defined(CURL_DISABLE_AWS) || !defined(CURL_DISABLE_DIGEST_AUTH) \ + || defined(USE_LIBSSH2) + +#include +#include "curl_hmac.h" + +extern const struct HMAC_params Curl_HMAC_SHA256[1]; + +#ifndef CURL_SHA256_DIGEST_LENGTH +#define CURL_SHA256_DIGEST_LENGTH 32 /* fixed size */ +#endif + +CURLcode Curl_sha256it(unsigned char *outbuffer, const unsigned char *input, + const size_t len); + +#endif + +#endif /* HEADER_CURL_SHA256_H */ diff --git a/contrib/curl/lib/curl_sha512_256.c b/contrib/curl/lib/curl_sha512_256.c new file mode 100644 index 0000000000..80fd8cf129 --- /dev/null +++ b/contrib/curl/lib/curl_sha512_256.c @@ -0,0 +1,857 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Evgeny Grin (Karlson2k), . + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the 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_DIGEST_AUTH) && !defined(CURL_DISABLE_SHA512_256) + +#include "curl_sha512_256.h" +#include "warnless.h" + +/* The recommended order of the TLS backends: + * * OpenSSL + * * GnuTLS + * * wolfSSL + * * Schannel SSPI + * * Secure Transport (Darwin) + * * mbedTLS + * * BearSSL + * * Rustls + * Skip the backend if it does not support the required algorithm */ + +#if defined(USE_OPENSSL) +# include +# if (!defined(LIBRESSL_VERSION_NUMBER) && \ + defined(OPENSSL_VERSION_NUMBER) && \ + (OPENSSL_VERSION_NUMBER >= 0x10101000L)) || \ + (defined(LIBRESSL_VERSION_NUMBER) && \ + (LIBRESSL_VERSION_NUMBER >= 0x3080000fL)) +# include +# if !defined(OPENSSL_NO_SHA) && !defined(OPENSSL_NO_SHA512) +# include +# define USE_OPENSSL_SHA512_256 1 +# define HAS_SHA512_256_IMPLEMENTATION 1 +# ifdef __NetBSD__ +/* Some NetBSD versions has a bug in SHA-512/256. + * See https://gnats.netbsd.org/cgi-bin/query-pr-single.pl?number=58039 + * The problematic versions: + * - NetBSD before 9.4 + * - NetBSD 9 all development versions (9.99.x) + * - NetBSD 10 development versions (10.99.x) before 10.99.11 + * The bug was fixed in NetBSD 9.4 release, NetBSD 10.0 release, + * NetBSD 10.99.11 development. + * It is safe to apply the workaround even if the bug is not present, as + * the workaround just reduces performance slightly. */ +# include +# if __NetBSD_Version__ < 904000000 || \ + (__NetBSD_Version__ >= 999000000 && \ + __NetBSD_Version__ < 1000000000) || \ + (__NetBSD_Version__ >= 1099000000 && \ + __NetBSD_Version__ < 1099001100) +# define NEED_NETBSD_SHA512_256_WORKAROUND 1 +# include +# endif +# endif +# endif +# endif +#endif /* USE_OPENSSL */ + + +#if !defined(HAS_SHA512_256_IMPLEMENTATION) && defined(USE_GNUTLS) +# include +# if defined(SHA512_256_DIGEST_SIZE) +# define USE_GNUTLS_SHA512_256 1 +# define HAS_SHA512_256_IMPLEMENTATION 1 +# endif +#endif /* ! HAS_SHA512_256_IMPLEMENTATION && USE_GNUTLS */ + +#if defined(USE_OPENSSL_SHA512_256) + +/* OpenSSL does not provide macros for SHA-512/256 sizes */ + +/** + * Size of the SHA-512/256 single processing block in bytes. + */ +#define CURL_SHA512_256_BLOCK_SIZE 128 + +/** + * Size of the SHA-512/256 resulting digest in bytes. + * This is the final digest size, not intermediate hash. + */ +#define CURL_SHA512_256_DIGEST_SIZE CURL_SHA512_256_DIGEST_LENGTH + +/** + * Context type used for SHA-512/256 calculations + */ +typedef EVP_MD_CTX *Curl_sha512_256_ctx; + +/** + * Initialise structure for SHA-512/256 calculation. + * + * @param context the calculation context + * @return CURLE_OK if succeed, + * error code otherwise + */ +static CURLcode +Curl_sha512_256_init(void *context) +{ + Curl_sha512_256_ctx *const ctx = (Curl_sha512_256_ctx *)context; + + *ctx = EVP_MD_CTX_create(); + if(!*ctx) + return CURLE_OUT_OF_MEMORY; + + if(EVP_DigestInit_ex(*ctx, EVP_sha512_256(), NULL)) { + /* Check whether the header and this file use the same numbers */ + DEBUGASSERT(EVP_MD_CTX_size(*ctx) == CURL_SHA512_256_DIGEST_SIZE); + /* Check whether the block size is correct */ + DEBUGASSERT(EVP_MD_CTX_block_size(*ctx) == CURL_SHA512_256_BLOCK_SIZE); + + return CURLE_OK; /* Success */ + } + + /* Cleanup */ + EVP_MD_CTX_destroy(*ctx); + return CURLE_FAILED_INIT; +} + + +/** + * Process portion of bytes. + * + * @param context the calculation context + * @param data bytes to add to hash + * @return CURLE_OK if succeed, + * error code otherwise + */ +static CURLcode +Curl_sha512_256_update(void *context, + const unsigned char *data, + size_t length) +{ + Curl_sha512_256_ctx *const ctx = (Curl_sha512_256_ctx *)context; + + if(!EVP_DigestUpdate(*ctx, data, length)) + return CURLE_SSL_CIPHER; + + return CURLE_OK; +} + + +/** + * Finalise SHA-512/256 calculation, return digest. + * + * @param context the calculation context + * @param[out] digest set to the hash, must be #CURL_SHA512_256_DIGEST_SIZE + # bytes + * @return CURLE_OK if succeed, + * error code otherwise + */ +static CURLcode +Curl_sha512_256_finish(unsigned char *digest, + void *context) +{ + CURLcode ret; + Curl_sha512_256_ctx *const ctx = (Curl_sha512_256_ctx *)context; + +#ifdef NEED_NETBSD_SHA512_256_WORKAROUND + /* Use a larger buffer to work around a bug in NetBSD: + https://gnats.netbsd.org/cgi-bin/query-pr-single.pl?number=58039 */ + unsigned char tmp_digest[CURL_SHA512_256_DIGEST_SIZE * 2]; + ret = EVP_DigestFinal_ex(*ctx, + tmp_digest, NULL) ? CURLE_OK : CURLE_SSL_CIPHER; + if(ret == CURLE_OK) + memcpy(digest, tmp_digest, CURL_SHA512_256_DIGEST_SIZE); + explicit_memset(tmp_digest, 0, sizeof(tmp_digest)); +#else /* ! NEED_NETBSD_SHA512_256_WORKAROUND */ + ret = EVP_DigestFinal_ex(*ctx, digest, NULL) ? CURLE_OK : CURLE_SSL_CIPHER; +#endif /* ! NEED_NETBSD_SHA512_256_WORKAROUND */ + + EVP_MD_CTX_destroy(*ctx); + *ctx = NULL; + + return ret; +} + +#elif defined(USE_GNUTLS_SHA512_256) + +#define CURL_SHA512_256_BLOCK_SIZE SHA512_256_BLOCK_SIZE +#define CURL_SHA512_256_DIGEST_SIZE SHA512_256_DIGEST_SIZE + +/** + * Context type used for SHA-512/256 calculations + */ +typedef struct sha512_256_ctx Curl_sha512_256_ctx; + +/** + * Initialise structure for SHA-512/256 calculation. + * + * @param context the calculation context + * @return always CURLE_OK + */ +static CURLcode +Curl_sha512_256_init(void *context) +{ + Curl_sha512_256_ctx *const ctx = (Curl_sha512_256_ctx *)context; + + /* Check whether the header and this file use the same numbers */ + DEBUGASSERT(CURL_SHA512_256_DIGEST_LENGTH == CURL_SHA512_256_DIGEST_SIZE); + + sha512_256_init(ctx); + + return CURLE_OK; +} + + +/** + * Process portion of bytes. + * + * @param context the calculation context + * @param data bytes to add to hash + * @param length number of bytes in @a data + * @return always CURLE_OK + */ +static CURLcode +Curl_sha512_256_update(void *context, + const unsigned char *data, + size_t length) +{ + Curl_sha512_256_ctx *const ctx = (Curl_sha512_256_ctx *)context; + + DEBUGASSERT((data != NULL) || (length == 0)); + + sha512_256_update(ctx, length, (const uint8_t *)data); + + return CURLE_OK; +} + + +/** + * Finalise SHA-512/256 calculation, return digest. + * + * @param context the calculation context + * @param[out] digest set to the hash, must be #CURL_SHA512_256_DIGEST_SIZE + # bytes + * @return always CURLE_OK + */ +static CURLcode +Curl_sha512_256_finish(unsigned char *digest, + void *context) +{ + Curl_sha512_256_ctx *const ctx = (Curl_sha512_256_ctx *)context; + + sha512_256_digest(ctx, + (size_t)CURL_SHA512_256_DIGEST_SIZE, (uint8_t *)digest); + + return CURLE_OK; +} + +#else /* No system or TLS backend SHA-512/256 implementation available */ + +/* Use local implementation */ +#define HAS_SHA512_256_IMPLEMENTATION 1 + +/* ** This implementation of SHA-512/256 hash calculation was originally ** * + * ** written by Evgeny Grin (Karlson2k) for GNU libmicrohttpd. ** * + * ** The author ported the code to libcurl. The ported code is provided ** * + * ** under curl license. ** * + * ** This is a minimal version with minimal optimizations. Performance ** * + * ** can be significantly improved. Big-endian store and load macros ** * + * ** are obvious targets for optimization. ** */ + +#ifdef __GNUC__ +# if defined(__has_attribute) && defined(__STDC_VERSION__) +# if __has_attribute(always_inline) && __STDC_VERSION__ >= 199901 +# define MHDX_INLINE inline __attribute__((always_inline)) +# endif +# endif +#endif + +#if !defined(MHDX_INLINE) && \ + defined(_MSC_VER) && !defined(__GNUC__) && !defined(__clang__) +# if _MSC_VER >= 1400 +# define MHDX_INLINE __forceinline +# endif +#endif + +#if !defined(MHDX_INLINE) + /* Assume that 'inline' keyword works or the + * macro was already defined correctly. */ +# define MHDX_INLINE inline +#endif + +/* Bits manipulation macros and functions. + Can be moved to other headers to reuse. */ + +#define MHDX_GET_64BIT_BE(ptr) \ + ( ((curl_uint64_t)(((const unsigned char*)(ptr))[0]) << 56) | \ + ((curl_uint64_t)(((const unsigned char*)(ptr))[1]) << 48) | \ + ((curl_uint64_t)(((const unsigned char*)(ptr))[2]) << 40) | \ + ((curl_uint64_t)(((const unsigned char*)(ptr))[3]) << 32) | \ + ((curl_uint64_t)(((const unsigned char*)(ptr))[4]) << 24) | \ + ((curl_uint64_t)(((const unsigned char*)(ptr))[5]) << 16) | \ + ((curl_uint64_t)(((const unsigned char*)(ptr))[6]) << 8) | \ + (curl_uint64_t)(((const unsigned char*)(ptr))[7]) ) + +#define MHDX_PUT_64BIT_BE(ptr,val) do { \ + ((unsigned char*)(ptr))[7]=(unsigned char)((curl_uint64_t)(val)); \ + ((unsigned char*)(ptr))[6]=(unsigned char)(((curl_uint64_t)(val)) >> 8); \ + ((unsigned char*)(ptr))[5]=(unsigned char)(((curl_uint64_t)(val)) >> 16); \ + ((unsigned char*)(ptr))[4]=(unsigned char)(((curl_uint64_t)(val)) >> 24); \ + ((unsigned char*)(ptr))[3]=(unsigned char)(((curl_uint64_t)(val)) >> 32); \ + ((unsigned char*)(ptr))[2]=(unsigned char)(((curl_uint64_t)(val)) >> 40); \ + ((unsigned char*)(ptr))[1]=(unsigned char)(((curl_uint64_t)(val)) >> 48); \ + ((unsigned char*)(ptr))[0]=(unsigned char)(((curl_uint64_t)(val)) >> 56); \ + } while(0) + +/* Defined as a function. The macro version may duplicate the binary code + * size as each argument is used twice, so if any calculation is used + * as an argument, the calculation could be done twice. */ +static MHDX_INLINE curl_uint64_t +MHDx_rotr64(curl_uint64_t value, unsigned int bits) +{ + bits %= 64; + if(0 == bits) + return value; + /* Defined in a form which modern compiler could optimize. */ + return (value >> bits) | (value << (64 - bits)); +} + +/* SHA-512/256 specific data */ + +/** + * Number of bits in a single SHA-512/256 word. + */ +#define SHA512_256_WORD_SIZE_BITS 64 + +/** + * Number of bytes in a single SHA-512/256 word. + */ +#define SHA512_256_BYTES_IN_WORD (SHA512_256_WORD_SIZE_BITS / 8) + +/** + * Hash is kept internally as 8 64-bit words. + * This is the intermediate hash size, used during computing the final digest. + */ +#define SHA512_256_HASH_SIZE_WORDS 8 + +/** + * Size of the SHA-512/256 resulting digest in words. + * This is the final digest size, not intermediate hash. + */ +#define SHA512_256_DIGEST_SIZE_WORDS (SHA512_256_HASH_SIZE_WORDS / 2) + +/** + * Size of the SHA-512/256 resulting digest in bytes + * This is the final digest size, not intermediate hash. + */ +#define CURL_SHA512_256_DIGEST_SIZE \ + (SHA512_256_DIGEST_SIZE_WORDS * SHA512_256_BYTES_IN_WORD) + +/** + * Size of the SHA-512/256 single processing block in bits. + */ +#define SHA512_256_BLOCK_SIZE_BITS 1024 + +/** + * Size of the SHA-512/256 single processing block in bytes. + */ +#define CURL_SHA512_256_BLOCK_SIZE (SHA512_256_BLOCK_SIZE_BITS / 8) + +/** + * Size of the SHA-512/256 single processing block in words. + */ +#define SHA512_256_BLOCK_SIZE_WORDS \ + (SHA512_256_BLOCK_SIZE_BITS / SHA512_256_WORD_SIZE_BITS) + +/** + * SHA-512/256 calculation context + */ +struct mhdx_sha512_256ctx +{ + /** + * Intermediate hash value. The variable is properly aligned. Smart + * compilers may automatically use fast load/store instruction for big + * endian data on little endian machine. + */ + curl_uint64_t H[SHA512_256_HASH_SIZE_WORDS]; + /** + * SHA-512/256 input data buffer. The buffer is properly aligned. Smart + * compilers may automatically use fast load/store instruction for big + * endian data on little endian machine. + */ + curl_uint64_t buffer[SHA512_256_BLOCK_SIZE_WORDS]; + /** + * The number of bytes, lower part + */ + curl_uint64_t count; + /** + * The number of bits, high part. Unlike lower part, this counts the number + * of bits, not bytes. + */ + curl_uint64_t count_bits_hi; +}; + +/** + * Context type used for SHA-512/256 calculations + */ +typedef struct mhdx_sha512_256ctx Curl_sha512_256_ctx; + + +/** + * Initialise structure for SHA-512/256 calculation. + * + * @param context the calculation context + * @return always CURLE_OK + */ +static CURLcode +MHDx_sha512_256_init(void *context) +{ + struct mhdx_sha512_256ctx *const ctx = (struct mhdx_sha512_256ctx *) context; + + /* Check whether the header and this file use the same numbers */ + DEBUGASSERT(CURL_SHA512_256_DIGEST_LENGTH == CURL_SHA512_256_DIGEST_SIZE); + + DEBUGASSERT(sizeof(curl_uint64_t) == 8); + + /* Initial hash values, see FIPS PUB 180-4 section 5.3.6.2 */ + /* Values generated by "IV Generation Function" as described in + * section 5.3.6 */ + ctx->H[0] = CURL_UINT64_C(0x22312194FC2BF72C); + ctx->H[1] = CURL_UINT64_C(0x9F555FA3C84C64C2); + ctx->H[2] = CURL_UINT64_C(0x2393B86B6F53B151); + ctx->H[3] = CURL_UINT64_C(0x963877195940EABD); + ctx->H[4] = CURL_UINT64_C(0x96283EE2A88EFFE3); + ctx->H[5] = CURL_UINT64_C(0xBE5E1E2553863992); + ctx->H[6] = CURL_UINT64_C(0x2B0199FC2C85B8AA); + ctx->H[7] = CURL_UINT64_C(0x0EB72DDC81C52CA2); + + /* Initialise number of bytes and high part of number of bits. */ + ctx->count = CURL_UINT64_C(0); + ctx->count_bits_hi = CURL_UINT64_C(0); + + return CURLE_OK; +} + + +/** + * Base of the SHA-512/256 transformation. + * Gets a full 128 bytes block of data and updates hash values; + * @param H hash values + * @param data the data buffer with #CURL_SHA512_256_BLOCK_SIZE bytes block + */ +static void +MHDx_sha512_256_transform(curl_uint64_t H[SHA512_256_HASH_SIZE_WORDS], + const void *data) +{ + /* Working variables, + see FIPS PUB 180-4 section 6.7, 6.4. */ + curl_uint64_t a = H[0]; + curl_uint64_t b = H[1]; + curl_uint64_t c = H[2]; + curl_uint64_t d = H[3]; + curl_uint64_t e = H[4]; + curl_uint64_t f = H[5]; + curl_uint64_t g = H[6]; + curl_uint64_t h = H[7]; + + /* Data buffer, used as a cyclic buffer. + See FIPS PUB 180-4 section 5.2.2, 6.7, 6.4. */ + curl_uint64_t W[16]; + + /* 'Ch' and 'Maj' macro functions are defined with widely-used optimization. + See FIPS PUB 180-4 formulae 4.8, 4.9. */ +#define Sha512_Ch(x,y,z) ( (z) ^ ((x) & ((y) ^ (z))) ) +#define Sha512_Maj(x,y,z) ( ((x) & (y)) ^ ((z) & ((x) ^ (y))) ) + + /* Four 'Sigma' macro functions. + See FIPS PUB 180-4 formulae 4.10, 4.11, 4.12, 4.13. */ +#define SIG0(x) \ + ( MHDx_rotr64((x), 28) ^ MHDx_rotr64((x), 34) ^ MHDx_rotr64((x), 39) ) +#define SIG1(x) \ + ( MHDx_rotr64((x), 14) ^ MHDx_rotr64((x), 18) ^ MHDx_rotr64((x), 41) ) +#define sig0(x) \ + ( MHDx_rotr64((x), 1) ^ MHDx_rotr64((x), 8) ^ ((x) >> 7) ) +#define sig1(x) \ + ( MHDx_rotr64((x), 19) ^ MHDx_rotr64((x), 61) ^ ((x) >> 6) ) + + if(1) { + unsigned int t; + /* K constants array. + See FIPS PUB 180-4 section 4.2.3 for K values. */ + static const curl_uint64_t K[80] = { + CURL_UINT64_C(0x428a2f98d728ae22), CURL_UINT64_C(0x7137449123ef65cd), + CURL_UINT64_C(0xb5c0fbcfec4d3b2f), CURL_UINT64_C(0xe9b5dba58189dbbc), + CURL_UINT64_C(0x3956c25bf348b538), CURL_UINT64_C(0x59f111f1b605d019), + CURL_UINT64_C(0x923f82a4af194f9b), CURL_UINT64_C(0xab1c5ed5da6d8118), + CURL_UINT64_C(0xd807aa98a3030242), CURL_UINT64_C(0x12835b0145706fbe), + CURL_UINT64_C(0x243185be4ee4b28c), CURL_UINT64_C(0x550c7dc3d5ffb4e2), + CURL_UINT64_C(0x72be5d74f27b896f), CURL_UINT64_C(0x80deb1fe3b1696b1), + CURL_UINT64_C(0x9bdc06a725c71235), CURL_UINT64_C(0xc19bf174cf692694), + CURL_UINT64_C(0xe49b69c19ef14ad2), CURL_UINT64_C(0xefbe4786384f25e3), + CURL_UINT64_C(0x0fc19dc68b8cd5b5), CURL_UINT64_C(0x240ca1cc77ac9c65), + CURL_UINT64_C(0x2de92c6f592b0275), CURL_UINT64_C(0x4a7484aa6ea6e483), + CURL_UINT64_C(0x5cb0a9dcbd41fbd4), CURL_UINT64_C(0x76f988da831153b5), + CURL_UINT64_C(0x983e5152ee66dfab), CURL_UINT64_C(0xa831c66d2db43210), + CURL_UINT64_C(0xb00327c898fb213f), CURL_UINT64_C(0xbf597fc7beef0ee4), + CURL_UINT64_C(0xc6e00bf33da88fc2), CURL_UINT64_C(0xd5a79147930aa725), + CURL_UINT64_C(0x06ca6351e003826f), CURL_UINT64_C(0x142929670a0e6e70), + CURL_UINT64_C(0x27b70a8546d22ffc), CURL_UINT64_C(0x2e1b21385c26c926), + CURL_UINT64_C(0x4d2c6dfc5ac42aed), CURL_UINT64_C(0x53380d139d95b3df), + CURL_UINT64_C(0x650a73548baf63de), CURL_UINT64_C(0x766a0abb3c77b2a8), + CURL_UINT64_C(0x81c2c92e47edaee6), CURL_UINT64_C(0x92722c851482353b), + CURL_UINT64_C(0xa2bfe8a14cf10364), CURL_UINT64_C(0xa81a664bbc423001), + CURL_UINT64_C(0xc24b8b70d0f89791), CURL_UINT64_C(0xc76c51a30654be30), + CURL_UINT64_C(0xd192e819d6ef5218), CURL_UINT64_C(0xd69906245565a910), + CURL_UINT64_C(0xf40e35855771202a), CURL_UINT64_C(0x106aa07032bbd1b8), + CURL_UINT64_C(0x19a4c116b8d2d0c8), CURL_UINT64_C(0x1e376c085141ab53), + CURL_UINT64_C(0x2748774cdf8eeb99), CURL_UINT64_C(0x34b0bcb5e19b48a8), + CURL_UINT64_C(0x391c0cb3c5c95a63), CURL_UINT64_C(0x4ed8aa4ae3418acb), + CURL_UINT64_C(0x5b9cca4f7763e373), CURL_UINT64_C(0x682e6ff3d6b2b8a3), + CURL_UINT64_C(0x748f82ee5defb2fc), CURL_UINT64_C(0x78a5636f43172f60), + CURL_UINT64_C(0x84c87814a1f0ab72), CURL_UINT64_C(0x8cc702081a6439ec), + CURL_UINT64_C(0x90befffa23631e28), CURL_UINT64_C(0xa4506cebde82bde9), + CURL_UINT64_C(0xbef9a3f7b2c67915), CURL_UINT64_C(0xc67178f2e372532b), + CURL_UINT64_C(0xca273eceea26619c), CURL_UINT64_C(0xd186b8c721c0c207), + CURL_UINT64_C(0xeada7dd6cde0eb1e), CURL_UINT64_C(0xf57d4f7fee6ed178), + CURL_UINT64_C(0x06f067aa72176fba), CURL_UINT64_C(0x0a637dc5a2c898a6), + CURL_UINT64_C(0x113f9804bef90dae), CURL_UINT64_C(0x1b710b35131c471b), + CURL_UINT64_C(0x28db77f523047d84), CURL_UINT64_C(0x32caab7b40c72493), + CURL_UINT64_C(0x3c9ebe0a15c9bebc), CURL_UINT64_C(0x431d67c49c100d4c), + CURL_UINT64_C(0x4cc5d4becb3e42b6), CURL_UINT64_C(0x597f299cfc657e2a), + CURL_UINT64_C(0x5fcb6fab3ad6faec), CURL_UINT64_C(0x6c44198c4a475817) + }; + + /* One step of SHA-512/256 computation, + see FIPS PUB 180-4 section 6.4.2 step 3. + * Note: this macro updates working variables in-place, without rotation. + * Note: the first (vH += SIG1(vE) + Ch(vE,vF,vG) + kt + wt) equals T1 in + FIPS PUB 180-4 section 6.4.2 step 3. + the second (vH += SIG0(vA) + Maj(vE,vF,vC) equals T1 + T2 in + FIPS PUB 180-4 section 6.4.2 step 3. + * Note: 'wt' must be used exactly one time in this macro as macro for + 'wt' calculation may change other data as well every time when + used. */ +#define SHA2STEP64(vA,vB,vC,vD,vE,vF,vG,vH,kt,wt) do { \ + (vD) += ((vH) += SIG1((vE)) + Sha512_Ch((vE),(vF),(vG)) + (kt) + (wt)); \ + (vH) += SIG0((vA)) + Sha512_Maj((vA),(vB),(vC)); } while (0) + + /* One step of SHA-512/256 computation with working variables rotation, + see FIPS PUB 180-4 section 6.4.2 step 3. This macro version reassigns + all working variables on each step. */ +#define SHA2STEP64RV(vA,vB,vC,vD,vE,vF,vG,vH,kt,wt) do { \ + curl_uint64_t tmp_h_ = (vH); \ + SHA2STEP64((vA),(vB),(vC),(vD),(vE),(vF),(vG),tmp_h_,(kt),(wt)); \ + (vH) = (vG); \ + (vG) = (vF); \ + (vF) = (vE); \ + (vE) = (vD); \ + (vD) = (vC); \ + (vC) = (vB); \ + (vB) = (vA); \ + (vA) = tmp_h_; } while(0) + + /* Get value of W(t) from input data buffer for 0 <= t <= 15, + See FIPS PUB 180-4 section 6.2. + Input data must be read in big-endian bytes order, + see FIPS PUB 180-4 section 3.1.2. */ +#define SHA512_GET_W_FROM_DATA(buf,t) \ + MHDX_GET_64BIT_BE( \ + ((const unsigned char*) (buf)) + (t) * SHA512_256_BYTES_IN_WORD) + + /* During first 16 steps, before making any calculation on each step, the + W element is read from the input data buffer as a big-endian value and + stored in the array of W elements. */ + for(t = 0; t < 16; ++t) { + SHA2STEP64RV(a, b, c, d, e, f, g, h, K[t], \ + W[t] = SHA512_GET_W_FROM_DATA(data, t)); + } + + /* 'W' generation and assignment for 16 <= t <= 79. + See FIPS PUB 180-4 section 6.4.2. + As only the last 16 'W' are used in calculations, it is possible to + use 16 elements array of W as a cyclic buffer. + Note: ((t-16) & 15) have same value as (t & 15) */ +#define Wgen(w,t) \ + (curl_uint64_t)( (w)[(t - 16) & 15] + sig1((w)[((t) - 2) & 15]) \ + + (w)[((t) - 7) & 15] + sig0((w)[((t) - 15) & 15]) ) + + /* During the last 64 steps, before making any calculation on each step, + current W element is generated from other W elements of the cyclic + buffer and the generated value is stored back in the cyclic buffer. */ + for(t = 16; t < 80; ++t) { + SHA2STEP64RV(a, b, c, d, e, f, g, h, K[t], \ + W[t & 15] = Wgen(W, t)); + } + } + + /* Compute and store the intermediate hash. + See FIPS PUB 180-4 section 6.4.2 step 4. */ + H[0] += a; + H[1] += b; + H[2] += c; + H[3] += d; + H[4] += e; + H[5] += f; + H[6] += g; + H[7] += h; +} + + +/** + * Process portion of bytes. + * + * @param context the calculation context + * @param data bytes to add to hash + * @param length number of bytes in @a data + * @return always CURLE_OK + */ +static CURLcode +MHDx_sha512_256_update(void *context, + const unsigned char *data, + size_t length) +{ + unsigned int bytes_have; /**< Number of bytes in the context buffer */ + struct mhdx_sha512_256ctx *const ctx = (struct mhdx_sha512_256ctx *)context; + /* the void pointer here is required to mute Intel compiler warning */ + void *const ctx_buf = ctx->buffer; + + DEBUGASSERT((data != NULL) || (length == 0)); + + if(0 == length) + return CURLE_OK; /* Shortcut, do nothing */ + + /* Note: (count & (CURL_SHA512_256_BLOCK_SIZE-1)) + equals (count % CURL_SHA512_256_BLOCK_SIZE) for this block size. */ + bytes_have = (unsigned int) (ctx->count & (CURL_SHA512_256_BLOCK_SIZE - 1)); + ctx->count += length; + if(length > ctx->count) + ctx->count_bits_hi += 1U << 3; /* Value wrap */ + ctx->count_bits_hi += ctx->count >> 61; + ctx->count &= CURL_UINT64_C(0x1FFFFFFFFFFFFFFF); + + if(0 != bytes_have) { + unsigned int bytes_left = CURL_SHA512_256_BLOCK_SIZE - bytes_have; + if(length >= bytes_left) { + /* Combine new data with data in the buffer and process the full + block. */ + memcpy(((unsigned char *) ctx_buf) + bytes_have, + data, + bytes_left); + data += bytes_left; + length -= bytes_left; + MHDx_sha512_256_transform(ctx->H, ctx->buffer); + bytes_have = 0; + } + } + + while(CURL_SHA512_256_BLOCK_SIZE <= length) { + /* Process any full blocks of new data directly, + without copying to the buffer. */ + MHDx_sha512_256_transform(ctx->H, data); + data += CURL_SHA512_256_BLOCK_SIZE; + length -= CURL_SHA512_256_BLOCK_SIZE; + } + + if(0 != length) { + /* Copy incomplete block of new data (if any) + to the buffer. */ + memcpy(((unsigned char *) ctx_buf) + bytes_have, data, length); + } + + return CURLE_OK; +} + + + +/** + * Size of "length" insertion in bits. + * See FIPS PUB 180-4 section 5.1.2. + */ +#define SHA512_256_SIZE_OF_LEN_ADD_BITS 128 + +/** + * Size of "length" insertion in bytes. + */ +#define SHA512_256_SIZE_OF_LEN_ADD (SHA512_256_SIZE_OF_LEN_ADD_BITS / 8) + +/** + * Finalise SHA-512/256 calculation, return digest. + * + * @param context the calculation context + * @param[out] digest set to the hash, must be #CURL_SHA512_256_DIGEST_SIZE + # bytes + * @return always CURLE_OK + */ +static CURLcode +MHDx_sha512_256_finish(unsigned char *digest, + void *context) +{ + struct mhdx_sha512_256ctx *const ctx = (struct mhdx_sha512_256ctx *)context; + curl_uint64_t num_bits; /**< Number of processed bits */ + unsigned int bytes_have; /**< Number of bytes in the context buffer */ + /* the void pointer here is required to mute Intel compiler warning */ + void *const ctx_buf = ctx->buffer; + + /* Memorise the number of processed bits. + The padding and other data added here during the postprocessing must + not change the amount of hashed data. */ + num_bits = ctx->count << 3; + + /* Note: (count & (CURL_SHA512_256_BLOCK_SIZE-1)) + equals (count % CURL_SHA512_256_BLOCK_SIZE) for this block size. */ + bytes_have = (unsigned int) (ctx->count & (CURL_SHA512_256_BLOCK_SIZE - 1)); + + /* Input data must be padded with a single bit "1", then with zeros and + the finally the length of data in bits must be added as the final bytes + of the last block. + See FIPS PUB 180-4 section 5.1.2. */ + + /* Data is always processed in form of bytes (not by individual bits), + therefore position of the first padding bit in byte is always + predefined (0x80). */ + /* Buffer always have space at least for one byte (as full buffers are + processed when formed). */ + ((unsigned char *) ctx_buf)[bytes_have++] = 0x80U; + + if(CURL_SHA512_256_BLOCK_SIZE - bytes_have < SHA512_256_SIZE_OF_LEN_ADD) { + /* No space in the current block to put the total length of message. + Pad the current block with zeros and process it. */ + if(bytes_have < CURL_SHA512_256_BLOCK_SIZE) + memset(((unsigned char *) ctx_buf) + bytes_have, 0, + CURL_SHA512_256_BLOCK_SIZE - bytes_have); + /* Process the full block. */ + MHDx_sha512_256_transform(ctx->H, ctx->buffer); + /* Start the new block. */ + bytes_have = 0; + } + + /* Pad the rest of the buffer with zeros. */ + memset(((unsigned char *) ctx_buf) + bytes_have, 0, + CURL_SHA512_256_BLOCK_SIZE - SHA512_256_SIZE_OF_LEN_ADD - bytes_have); + /* Put high part of number of bits in processed message and then lower + part of number of bits as big-endian values. + See FIPS PUB 180-4 section 5.1.2. */ + /* Note: the target location is predefined and buffer is always aligned */ + MHDX_PUT_64BIT_BE(((unsigned char *) ctx_buf) \ + + CURL_SHA512_256_BLOCK_SIZE \ + - SHA512_256_SIZE_OF_LEN_ADD, \ + ctx->count_bits_hi); + MHDX_PUT_64BIT_BE(((unsigned char *) ctx_buf) \ + + CURL_SHA512_256_BLOCK_SIZE \ + - SHA512_256_SIZE_OF_LEN_ADD \ + + SHA512_256_BYTES_IN_WORD, \ + num_bits); + /* Process the full final block. */ + MHDx_sha512_256_transform(ctx->H, ctx->buffer); + + /* Put in BE mode the leftmost part of the hash as the final digest. + See FIPS PUB 180-4 section 6.7. */ + + MHDX_PUT_64BIT_BE((digest + 0 * SHA512_256_BYTES_IN_WORD), ctx->H[0]); + MHDX_PUT_64BIT_BE((digest + 1 * SHA512_256_BYTES_IN_WORD), ctx->H[1]); + MHDX_PUT_64BIT_BE((digest + 2 * SHA512_256_BYTES_IN_WORD), ctx->H[2]); + MHDX_PUT_64BIT_BE((digest + 3 * SHA512_256_BYTES_IN_WORD), ctx->H[3]); + + /* Erase potentially sensitive data. */ + memset(ctx, 0, sizeof(struct mhdx_sha512_256ctx)); + + return CURLE_OK; +} + +/* Map to the local implementation */ +#define Curl_sha512_256_init MHDx_sha512_256_init +#define Curl_sha512_256_update MHDx_sha512_256_update +#define Curl_sha512_256_finish MHDx_sha512_256_finish + +#endif /* Local SHA-512/256 code */ + + +/** + * Compute SHA-512/256 hash for the given data in one function call + * @param[out] output the pointer to put the hash + * @param[in] input the pointer to the data to process + * @param input_size the size of the data pointed by @a input + * @return always #CURLE_OK + */ +CURLcode +Curl_sha512_256it(unsigned char *output, const unsigned char *input, + size_t input_size) +{ + Curl_sha512_256_ctx ctx; + CURLcode res; + + res = Curl_sha512_256_init(&ctx); + if(res != CURLE_OK) + return res; + + res = Curl_sha512_256_update(&ctx, (const void *) input, input_size); + + if(res != CURLE_OK) { + (void) Curl_sha512_256_finish(output, &ctx); + return res; + } + + return Curl_sha512_256_finish(output, &ctx); +} + +/* Wrapper function, takes 'unsigned int' as length type, returns void */ +static void +Curl_sha512_256_update_i(void *context, + const unsigned char *data, + unsigned int length) +{ + /* Hypothetically the function may fail, but assume it does not */ + (void) Curl_sha512_256_update(context, data, length); +} + +/* Wrapper function, returns void */ +static void +Curl_sha512_256_finish_v(unsigned char *result, + void *context) +{ + /* Hypothetically the function may fail, but assume it does not */ + (void) Curl_sha512_256_finish(result, context); +} + +/* Wrapper function, takes 'unsigned int' as length type, returns void */ + +const struct HMAC_params Curl_HMAC_SHA512_256[] = { + { + /* Initialize context procedure. */ + Curl_sha512_256_init, + /* Update context with data. */ + Curl_sha512_256_update_i, + /* Get final result procedure. */ + Curl_sha512_256_finish_v, + /* Context structure size. */ + sizeof(Curl_sha512_256_ctx), + /* Maximum key length (bytes). */ + CURL_SHA512_256_BLOCK_SIZE, + /* Result length (bytes). */ + CURL_SHA512_256_DIGEST_SIZE + } +}; + +#endif /* !CURL_DISABLE_DIGEST_AUTH && !CURL_DISABLE_SHA512_256 */ diff --git a/contrib/curl/lib/curl_sha512_256.h b/contrib/curl/lib/curl_sha512_256.h new file mode 100644 index 0000000000..a84e77bc30 --- /dev/null +++ b/contrib/curl/lib/curl_sha512_256.h @@ -0,0 +1,44 @@ +#ifndef HEADER_CURL_SHA512_256_H +#define HEADER_CURL_SHA512_256_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Evgeny Grin (Karlson2k), . + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the 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_DIGEST_AUTH) && !defined(CURL_DISABLE_SHA512_256) + +#include +#include "curl_hmac.h" + +#define CURL_HAVE_SHA512_256 + +extern const struct HMAC_params Curl_HMAC_SHA512_256[1]; + +#define CURL_SHA512_256_DIGEST_LENGTH 32 + +CURLcode +Curl_sha512_256it(unsigned char *output, const unsigned char *input, + size_t input_size); + +#endif /* !CURL_DISABLE_DIGEST_AUTH && !CURL_DISABLE_SHA512_256 */ + +#endif /* HEADER_CURL_SHA256_H */ diff --git a/contrib/curl/lib/curl_sspi.c b/contrib/curl/lib/curl_sspi.c index 11a7120a9f..680bb661b2 100644 --- a/contrib/curl/lib/curl_sspi.c +++ b/contrib/curl/lib/curl_sspi.c @@ -5,11 +5,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2016, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -18,6 +18,8 @@ * 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" @@ -28,6 +30,7 @@ #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: */ @@ -49,10 +52,10 @@ typedef PSecurityFunctionTable (APIENTRY *INITSECURITYINTERFACE_FN)(VOID); #endif /* Handle of security.dll or secur32.dll, depending on Windows version */ -HMODULE s_hSecDll = NULL; +HMODULE Curl_hSecDll = NULL; /* Pointer to SSPI dispatch table */ -PSecurityFunctionTable s_pSecFn = NULL; +PSecurityFunctionTable Curl_pSecFn = NULL; /* * Curl_sspi_global_init() @@ -76,28 +79,29 @@ CURLcode Curl_sspi_global_init(void) INITSECURITYINTERFACE_FN pInitSecurityInterface; /* If security interface is not yet initialized try to do this */ - if(!s_hSecDll) { + if(!Curl_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(Curl_verify_windows_version(4, 0, PLATFORM_WINNT, VERSION_EQUAL)) - s_hSecDll = Curl_load_library(TEXT("security.dll")); + if(curlx_verify_windows_version(4, 0, 0, PLATFORM_WINNT, VERSION_EQUAL)) + Curl_hSecDll = Curl_load_library(TEXT("security.dll")); else - s_hSecDll = Curl_load_library(TEXT("secur32.dll")); - if(!s_hSecDll) + Curl_hSecDll = Curl_load_library(TEXT("secur32.dll")); + if(!Curl_hSecDll) return CURLE_FAILED_INIT; /* Get address of the InitSecurityInterfaceA function from the SSPI dll */ - pInitSecurityInterface = (INITSECURITYINTERFACE_FN) - GetProcAddress(s_hSecDll, SECURITYENTRYPOINT); + pInitSecurityInterface = + CURLX_FUNCTION_CAST(INITSECURITYINTERFACE_FN, + (GetProcAddress(Curl_hSecDll, SECURITYENTRYPOINT))); if(!pInitSecurityInterface) return CURLE_FAILED_INIT; /* Get pointer to Security Service Provider Interface dispatch table */ - s_pSecFn = pInitSecurityInterface(); - if(!s_pSecFn) + Curl_pSecFn = pInitSecurityInterface(); + if(!Curl_pSecFn) return CURLE_FAILED_INIT; } @@ -115,10 +119,10 @@ CURLcode Curl_sspi_global_init(void) */ void Curl_sspi_global_cleanup(void) { - if(s_hSecDll) { - FreeLibrary(s_hSecDll); - s_hSecDll = NULL; - s_pSecFn = NULL; + if(Curl_hSecDll) { + FreeLibrary(Curl_hSecDll); + Curl_hSecDll = NULL; + Curl_pSecFn = NULL; } } @@ -130,8 +134,8 @@ void Curl_sspi_global_cleanup(void) * * Parameters: * - * userp [in] - The user name in the format User or Domain\User. - * passdwp [in] - The user's password. + * userp [in] - The username in the format User or Domain\User. + * passwdp [in] - The user's password. * identity [in/out] - The identity structure. * * Returns CURLE_OK on success. @@ -150,7 +154,7 @@ CURLcode Curl_create_sspi_identity(const char *userp, const char *passwdp, /* Initialize the identity */ memset(identity, 0, sizeof(*identity)); - useranddomain.tchar_ptr = Curl_convert_UTF8_to_tchar((char *)userp); + useranddomain.tchar_ptr = curlx_convert_UTF8_to_tchar((char *)userp); if(!useranddomain.tchar_ptr) return CURLE_OUT_OF_MEMORY; @@ -172,7 +176,7 @@ CURLcode Curl_create_sspi_identity(const char *userp, const char *passwdp, /* Setup the identity's user and length */ dup_user.tchar_ptr = _tcsdup(user.tchar_ptr); if(!dup_user.tchar_ptr) { - Curl_unicodefree(useranddomain.tchar_ptr); + curlx_unicodefree(useranddomain.tchar_ptr); return CURLE_OUT_OF_MEMORY; } identity->User = dup_user.tbyte_ptr; @@ -182,7 +186,7 @@ CURLcode Curl_create_sspi_identity(const char *userp, const char *passwdp, /* Setup the identity's domain and length */ dup_domain.tchar_ptr = malloc(sizeof(TCHAR) * (domlen + 1)); if(!dup_domain.tchar_ptr) { - Curl_unicodefree(useranddomain.tchar_ptr); + curlx_unicodefree(useranddomain.tchar_ptr); return CURLE_OUT_OF_MEMORY; } _tcsncpy(dup_domain.tchar_ptr, domain.tchar_ptr, domlen); @@ -191,22 +195,22 @@ CURLcode Curl_create_sspi_identity(const char *userp, const char *passwdp, identity->DomainLength = curlx_uztoul(domlen); dup_domain.tchar_ptr = NULL; - Curl_unicodefree(useranddomain.tchar_ptr); + curlx_unicodefree(useranddomain.tchar_ptr); /* Setup the identity's password and length */ - passwd.tchar_ptr = Curl_convert_UTF8_to_tchar((char *)passwdp); + 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) { - Curl_unicodefree(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; - Curl_unicodefree(passwd.tchar_ptr); + curlx_unicodefree(passwd.tchar_ptr); /* Setup the identity's flags */ identity->Flags = SECFLAG_WINNT_AUTH_IDENTITY; diff --git a/contrib/curl/lib/curl_sspi.h b/contrib/curl/lib/curl_sspi.h index 2bbf9477bb..535a1ff650 100644 --- a/contrib/curl/lib/curl_sspi.h +++ b/contrib/curl/lib/curl_sspi.h @@ -7,11 +7,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2014, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -20,6 +20,8 @@ * 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" @@ -55,8 +57,8 @@ CURLcode Curl_create_sspi_identity(const char *userp, const char *passwdp, 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; +extern HMODULE Curl_hSecDll; +extern PSecurityFunctionTable Curl_pSecFn; /* Provide some definitions missing in old headers */ #define SP_NAME_DIGEST "WDigest" @@ -68,227 +70,6 @@ extern PSecurityFunctionTable s_pSecFn; #define ISC_REQ_USE_HTTP_STYLE 0x01000000 #endif -#ifndef ISC_RET_REPLAY_DETECT -#define ISC_RET_REPLAY_DETECT 0x00000004 -#endif - -#ifndef ISC_RET_SEQUENCE_DETECT -#define ISC_RET_SEQUENCE_DETECT 0x00000008 -#endif - -#ifndef ISC_RET_CONFIDENTIALITY -#define ISC_RET_CONFIDENTIALITY 0x00000010 -#endif - -#ifndef ISC_RET_ALLOCATED_MEMORY -#define ISC_RET_ALLOCATED_MEMORY 0x00000100 -#endif - -#ifndef ISC_RET_STREAM -#define ISC_RET_STREAM 0x00008000 -#endif - -#ifndef SEC_E_INSUFFICIENT_MEMORY -# define SEC_E_INSUFFICIENT_MEMORY ((HRESULT)0x80090300L) -#endif -#ifndef SEC_E_INVALID_HANDLE -# define SEC_E_INVALID_HANDLE ((HRESULT)0x80090301L) -#endif -#ifndef SEC_E_UNSUPPORTED_FUNCTION -# define SEC_E_UNSUPPORTED_FUNCTION ((HRESULT)0x80090302L) -#endif -#ifndef SEC_E_TARGET_UNKNOWN -# define SEC_E_TARGET_UNKNOWN ((HRESULT)0x80090303L) -#endif -#ifndef SEC_E_INTERNAL_ERROR -# define SEC_E_INTERNAL_ERROR ((HRESULT)0x80090304L) -#endif -#ifndef SEC_E_SECPKG_NOT_FOUND -# define SEC_E_SECPKG_NOT_FOUND ((HRESULT)0x80090305L) -#endif -#ifndef SEC_E_NOT_OWNER -# define SEC_E_NOT_OWNER ((HRESULT)0x80090306L) -#endif -#ifndef SEC_E_CANNOT_INSTALL -# define SEC_E_CANNOT_INSTALL ((HRESULT)0x80090307L) -#endif -#ifndef SEC_E_INVALID_TOKEN -# define SEC_E_INVALID_TOKEN ((HRESULT)0x80090308L) -#endif -#ifndef SEC_E_CANNOT_PACK -# define SEC_E_CANNOT_PACK ((HRESULT)0x80090309L) -#endif -#ifndef SEC_E_QOP_NOT_SUPPORTED -# define SEC_E_QOP_NOT_SUPPORTED ((HRESULT)0x8009030AL) -#endif -#ifndef SEC_E_NO_IMPERSONATION -# define SEC_E_NO_IMPERSONATION ((HRESULT)0x8009030BL) -#endif -#ifndef SEC_E_LOGON_DENIED -# define SEC_E_LOGON_DENIED ((HRESULT)0x8009030CL) -#endif -#ifndef SEC_E_UNKNOWN_CREDENTIALS -# define SEC_E_UNKNOWN_CREDENTIALS ((HRESULT)0x8009030DL) -#endif -#ifndef SEC_E_NO_CREDENTIALS -# define SEC_E_NO_CREDENTIALS ((HRESULT)0x8009030EL) -#endif -#ifndef SEC_E_MESSAGE_ALTERED -# define SEC_E_MESSAGE_ALTERED ((HRESULT)0x8009030FL) -#endif -#ifndef SEC_E_OUT_OF_SEQUENCE -# define SEC_E_OUT_OF_SEQUENCE ((HRESULT)0x80090310L) -#endif -#ifndef SEC_E_NO_AUTHENTICATING_AUTHORITY -# define SEC_E_NO_AUTHENTICATING_AUTHORITY ((HRESULT)0x80090311L) -#endif -#ifndef SEC_E_BAD_PKGID -# define SEC_E_BAD_PKGID ((HRESULT)0x80090316L) -#endif -#ifndef SEC_E_CONTEXT_EXPIRED -# define SEC_E_CONTEXT_EXPIRED ((HRESULT)0x80090317L) -#endif -#ifndef SEC_E_INCOMPLETE_MESSAGE -# define SEC_E_INCOMPLETE_MESSAGE ((HRESULT)0x80090318L) -#endif -#ifndef SEC_E_INCOMPLETE_CREDENTIALS -# define SEC_E_INCOMPLETE_CREDENTIALS ((HRESULT)0x80090320L) -#endif -#ifndef SEC_E_BUFFER_TOO_SMALL -# define SEC_E_BUFFER_TOO_SMALL ((HRESULT)0x80090321L) -#endif -#ifndef SEC_E_WRONG_PRINCIPAL -# define SEC_E_WRONG_PRINCIPAL ((HRESULT)0x80090322L) -#endif -#ifndef SEC_E_TIME_SKEW -# define SEC_E_TIME_SKEW ((HRESULT)0x80090324L) -#endif -#ifndef SEC_E_UNTRUSTED_ROOT -# define SEC_E_UNTRUSTED_ROOT ((HRESULT)0x80090325L) -#endif -#ifndef SEC_E_ILLEGAL_MESSAGE -# define SEC_E_ILLEGAL_MESSAGE ((HRESULT)0x80090326L) -#endif -#ifndef SEC_E_CERT_UNKNOWN -# define SEC_E_CERT_UNKNOWN ((HRESULT)0x80090327L) -#endif -#ifndef SEC_E_CERT_EXPIRED -# define SEC_E_CERT_EXPIRED ((HRESULT)0x80090328L) -#endif -#ifndef SEC_E_ENCRYPT_FAILURE -# define SEC_E_ENCRYPT_FAILURE ((HRESULT)0x80090329L) -#endif -#ifndef SEC_E_DECRYPT_FAILURE -# define SEC_E_DECRYPT_FAILURE ((HRESULT)0x80090330L) -#endif -#ifndef SEC_E_ALGORITHM_MISMATCH -# define SEC_E_ALGORITHM_MISMATCH ((HRESULT)0x80090331L) -#endif -#ifndef SEC_E_SECURITY_QOS_FAILED -# define SEC_E_SECURITY_QOS_FAILED ((HRESULT)0x80090332L) -#endif -#ifndef SEC_E_UNFINISHED_CONTEXT_DELETED -# define SEC_E_UNFINISHED_CONTEXT_DELETED ((HRESULT)0x80090333L) -#endif -#ifndef SEC_E_NO_TGT_REPLY -# define SEC_E_NO_TGT_REPLY ((HRESULT)0x80090334L) -#endif -#ifndef SEC_E_NO_IP_ADDRESSES -# define SEC_E_NO_IP_ADDRESSES ((HRESULT)0x80090335L) -#endif -#ifndef SEC_E_WRONG_CREDENTIAL_HANDLE -# define SEC_E_WRONG_CREDENTIAL_HANDLE ((HRESULT)0x80090336L) -#endif -#ifndef SEC_E_CRYPTO_SYSTEM_INVALID -# define SEC_E_CRYPTO_SYSTEM_INVALID ((HRESULT)0x80090337L) -#endif -#ifndef SEC_E_MAX_REFERRALS_EXCEEDED -# define SEC_E_MAX_REFERRALS_EXCEEDED ((HRESULT)0x80090338L) -#endif -#ifndef SEC_E_MUST_BE_KDC -# define SEC_E_MUST_BE_KDC ((HRESULT)0x80090339L) -#endif -#ifndef SEC_E_STRONG_CRYPTO_NOT_SUPPORTED -# define SEC_E_STRONG_CRYPTO_NOT_SUPPORTED ((HRESULT)0x8009033AL) -#endif -#ifndef SEC_E_TOO_MANY_PRINCIPALS -# define SEC_E_TOO_MANY_PRINCIPALS ((HRESULT)0x8009033BL) -#endif -#ifndef SEC_E_NO_PA_DATA -# define SEC_E_NO_PA_DATA ((HRESULT)0x8009033CL) -#endif -#ifndef SEC_E_PKINIT_NAME_MISMATCH -# define SEC_E_PKINIT_NAME_MISMATCH ((HRESULT)0x8009033DL) -#endif -#ifndef SEC_E_SMARTCARD_LOGON_REQUIRED -# define SEC_E_SMARTCARD_LOGON_REQUIRED ((HRESULT)0x8009033EL) -#endif -#ifndef SEC_E_SHUTDOWN_IN_PROGRESS -# define SEC_E_SHUTDOWN_IN_PROGRESS ((HRESULT)0x8009033FL) -#endif -#ifndef SEC_E_KDC_INVALID_REQUEST -# define SEC_E_KDC_INVALID_REQUEST ((HRESULT)0x80090340L) -#endif -#ifndef SEC_E_KDC_UNABLE_TO_REFER -# define SEC_E_KDC_UNABLE_TO_REFER ((HRESULT)0x80090341L) -#endif -#ifndef SEC_E_KDC_UNKNOWN_ETYPE -# define SEC_E_KDC_UNKNOWN_ETYPE ((HRESULT)0x80090342L) -#endif -#ifndef SEC_E_UNSUPPORTED_PREAUTH -# define SEC_E_UNSUPPORTED_PREAUTH ((HRESULT)0x80090343L) -#endif -#ifndef SEC_E_DELEGATION_REQUIRED -# define SEC_E_DELEGATION_REQUIRED ((HRESULT)0x80090345L) -#endif -#ifndef SEC_E_BAD_BINDINGS -# define SEC_E_BAD_BINDINGS ((HRESULT)0x80090346L) -#endif -#ifndef SEC_E_MULTIPLE_ACCOUNTS -# define SEC_E_MULTIPLE_ACCOUNTS ((HRESULT)0x80090347L) -#endif -#ifndef SEC_E_NO_KERB_KEY -# define SEC_E_NO_KERB_KEY ((HRESULT)0x80090348L) -#endif -#ifndef SEC_E_CERT_WRONG_USAGE -# define SEC_E_CERT_WRONG_USAGE ((HRESULT)0x80090349L) -#endif -#ifndef SEC_E_DOWNGRADE_DETECTED -# define SEC_E_DOWNGRADE_DETECTED ((HRESULT)0x80090350L) -#endif -#ifndef SEC_E_SMARTCARD_CERT_REVOKED -# define SEC_E_SMARTCARD_CERT_REVOKED ((HRESULT)0x80090351L) -#endif -#ifndef SEC_E_ISSUING_CA_UNTRUSTED -# define SEC_E_ISSUING_CA_UNTRUSTED ((HRESULT)0x80090352L) -#endif -#ifndef SEC_E_REVOCATION_OFFLINE_C -# define SEC_E_REVOCATION_OFFLINE_C ((HRESULT)0x80090353L) -#endif -#ifndef SEC_E_PKINIT_CLIENT_FAILURE -# define SEC_E_PKINIT_CLIENT_FAILURE ((HRESULT)0x80090354L) -#endif -#ifndef SEC_E_SMARTCARD_CERT_EXPIRED -# define SEC_E_SMARTCARD_CERT_EXPIRED ((HRESULT)0x80090355L) -#endif -#ifndef SEC_E_NO_S4U_PROT_SUPPORT -# define SEC_E_NO_S4U_PROT_SUPPORT ((HRESULT)0x80090356L) -#endif -#ifndef SEC_E_CROSSREALM_DELEGATION_FAILURE -# define SEC_E_CROSSREALM_DELEGATION_FAILURE ((HRESULT)0x80090357L) -#endif -#ifndef SEC_E_REVOCATION_OFFLINE_KDC -# define SEC_E_REVOCATION_OFFLINE_KDC ((HRESULT)0x80090358L) -#endif -#ifndef SEC_E_ISSUING_CA_UNTRUSTED_KDC -# define SEC_E_ISSUING_CA_UNTRUSTED_KDC ((HRESULT)0x80090359L) -#endif -#ifndef SEC_E_KDC_CERT_EXPIRED -# define SEC_E_KDC_CERT_EXPIRED ((HRESULT)0x8009035AL) -#endif -#ifndef SEC_E_KDC_CERT_REVOKED -# define SEC_E_KDC_CERT_REVOKED ((HRESULT)0x8009035BL) -#endif #ifndef SEC_E_INVALID_PARAMETER # define SEC_E_INVALID_PARAMETER ((HRESULT)0x8009035DL) #endif @@ -299,30 +80,6 @@ extern PSecurityFunctionTable s_pSecFn; # define SEC_E_POLICY_NLTM_ONLY ((HRESULT)0x8009035FL) #endif -#ifndef SEC_I_CONTINUE_NEEDED -# define SEC_I_CONTINUE_NEEDED ((HRESULT)0x00090312L) -#endif -#ifndef SEC_I_COMPLETE_NEEDED -# define SEC_I_COMPLETE_NEEDED ((HRESULT)0x00090313L) -#endif -#ifndef SEC_I_COMPLETE_AND_CONTINUE -# define SEC_I_COMPLETE_AND_CONTINUE ((HRESULT)0x00090314L) -#endif -#ifndef SEC_I_LOCAL_LOGON -# define SEC_I_LOCAL_LOGON ((HRESULT)0x00090315L) -#endif -#ifndef SEC_I_CONTEXT_EXPIRED -# define SEC_I_CONTEXT_EXPIRED ((HRESULT)0x00090317L) -#endif -#ifndef SEC_I_INCOMPLETE_CREDENTIALS -# define SEC_I_INCOMPLETE_CREDENTIALS ((HRESULT)0x00090320L) -#endif -#ifndef SEC_I_RENEGOTIATE -# define SEC_I_RENEGOTIATE ((HRESULT)0x00090321L) -#endif -#ifndef SEC_I_NO_LSA_CONTEXT -# define SEC_I_NO_LSA_CONTEXT ((HRESULT)0x00090323L) -#endif #ifndef SEC_I_SIGNATURE_NEEDED # define SEC_I_SIGNATURE_NEEDED ((HRESULT)0x0009035CL) #endif @@ -331,6 +88,22 @@ extern PSecurityFunctionTable s_pSecFn; # 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 diff --git a/contrib/curl/lib/curl_threads.c b/contrib/curl/lib/curl_threads.c index a78eff5c2f..fbbbf9b2d3 100644 --- a/contrib/curl/lib/curl_threads.c +++ b/contrib/curl/lib/curl_threads.c @@ -5,11 +5,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2016, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -18,6 +18,8 @@ * 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" @@ -29,26 +31,26 @@ # include # endif #elif defined(USE_THREADS_WIN32) -# ifdef HAVE_PROCESS_H -# include -# endif +# include #endif #include "curl_threads.h" +#ifdef BUILDING_LIBCURL #include "curl_memory.h" +#endif /* The last #include file should be: */ #include "memdebug.h" #if defined(USE_THREADS_POSIX) -struct curl_actual_call { +struct Curl_actual_call { unsigned int (*func)(void *); void *arg; }; static void *curl_thread_create_thunk(void *arg) { - struct curl_actual_call * ac = arg; + struct Curl_actual_call *ac = arg; unsigned int (*func)(void *) = ac->func; void *real_arg = ac->arg; @@ -62,7 +64,7 @@ static void *curl_thread_create_thunk(void *arg) 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)); + struct Curl_actual_call *ac = malloc(sizeof(struct Curl_actual_call)); if(!(ac && t)) goto err; @@ -100,24 +102,44 @@ int Curl_thread_join(curl_thread_t *hnd) #elif defined(USE_THREADS_WIN32) -/* !checksrc! disable SPACEBEFOREPAREN 1 */ -curl_thread_t Curl_thread_create(unsigned int (CURL_STDCALL *func) (void *), +curl_thread_t Curl_thread_create( +#if defined(_WIN32_WCE) || defined(CURL_WINDOWS_UWP) + DWORD +#else + unsigned int +#endif + (CURL_STDCALL *func) (void *), void *arg) { -#ifdef _WIN32_WCE - return CreateThread(NULL, 0, func, arg, 0, NULL); +#if defined(_WIN32_WCE) || defined(CURL_WINDOWS_UWP) + typedef HANDLE curl_win_thread_handle_t; #else + typedef uintptr_t curl_win_thread_handle_t; +#endif curl_thread_t t; - t = (curl_thread_t)_beginthreadex(NULL, 0, func, arg, 0, NULL); - if((t == 0) || (t == (curl_thread_t)-1L)) + curl_win_thread_handle_t thread_handle; +#if defined(_WIN32_WCE) || defined(CURL_WINDOWS_UWP) + 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; -#endif } void Curl_thread_destroy(curl_thread_t hnd) { - CloseHandle(hnd); + if(hnd != curl_thread_t_null) + CloseHandle(hnd); } int Curl_thread_join(curl_thread_t *hnd) diff --git a/contrib/curl/lib/curl_threads.h b/contrib/curl/lib/curl_threads.h index 9e0d14a30d..c9f18a4e09 100644 --- a/contrib/curl/lib/curl_threads.h +++ b/contrib/curl/lib/curl_threads.h @@ -7,11 +7,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2016, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -20,6 +20,8 @@ * 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" @@ -50,8 +52,13 @@ #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 *), +curl_thread_t Curl_thread_create( +#if defined(_WIN32_WCE) || defined(CURL_WINDOWS_UWP) + DWORD +#else + unsigned int +#endif + (CURL_STDCALL *func) (void *), void *arg); void Curl_thread_destroy(curl_thread_t hnd); diff --git a/contrib/curl/lib/curl_trc.c b/contrib/curl/lib/curl_trc.c new file mode 100644 index 0000000000..301926c665 --- /dev/null +++ b/contrib/curl/lib/curl_trc.c @@ -0,0 +1,430 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#include + +#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 "doh.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" + +#ifndef ARRAYSIZE +#define ARRAYSIZE(A) (sizeof(A)/sizeof((A)[0])) +#endif + +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 + +static void trc_infof(struct Curl_easy *data, struct curl_trc_feat *feat, + const char * const fmt, va_list ap) CURL_PRINTF(3, 0); + +static void trc_infof(struct Curl_easy *data, struct curl_trc_feat *feat, + const char * const fmt, va_list ap) +{ + int len = 0; + char buffer[MAXINFO + 5]; + if(feat) + len = msnprintf(buffer, (MAXINFO + 1), "[%s] ", feat->name); + len += mvsnprintf(buffer + len, (MAXINFO + 1) - len, fmt, ap); + if(len >= MAXINFO) { /* too long, shorten with '...' */ + --len; + buffer[len++] = '.'; + buffer[len++] = '.'; + buffer[len++] = '.'; + } + buffer[len++] = '\n'; + buffer[len] = '\0'; + Curl_debug(data, CURLINFO_TEXT, buffer, len); +} + +void Curl_infof(struct Curl_easy *data, const char *fmt, ...) +{ + DEBUGASSERT(!strchr(fmt, '\n')); + if(Curl_trc_is_verbose(data)) { + va_list ap; + va_start(ap, fmt); + trc_infof(data, data->state.feat, fmt, ap); + va_end(ap); + } +} + +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 = 0; + char buffer[MAXINFO + 2]; + if(data->state.feat) + len += msnprintf(buffer + len, MAXINFO - len, "[%s] ", + data->state.feat->name); + if(cf->sockindex) + len += msnprintf(buffer + len, MAXINFO - len, "[%s-%d] ", + cf->cft->name, cf->sockindex); + else + len += msnprintf(buffer + len, MAXINFO - len, "[%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); + } +} + +struct curl_trc_feat Curl_trc_feat_read = { + "READ", + CURL_LOG_LVL_NONE, +}; +struct curl_trc_feat Curl_trc_feat_write = { + "WRITE", + CURL_LOG_LVL_NONE, +}; + +void Curl_trc_read(struct Curl_easy *data, const char *fmt, ...) +{ + DEBUGASSERT(!strchr(fmt, '\n')); + if(Curl_trc_ft_is_verbose(data, &Curl_trc_feat_read)) { + va_list ap; + va_start(ap, fmt); + trc_infof(data, &Curl_trc_feat_read, fmt, ap); + va_end(ap); + } +} + +void Curl_trc_write(struct Curl_easy *data, const char *fmt, ...) +{ + DEBUGASSERT(!strchr(fmt, '\n')); + if(Curl_trc_ft_is_verbose(data, &Curl_trc_feat_write)) { + va_list ap; + va_start(ap, fmt); + trc_infof(data, &Curl_trc_feat_write, fmt, ap); + va_end(ap); + } +} + +#ifndef CURL_DISABLE_FTP +struct curl_trc_feat Curl_trc_feat_ftp = { + "FTP", + CURL_LOG_LVL_NONE, +}; + +void Curl_trc_ftp(struct Curl_easy *data, const char *fmt, ...) +{ + DEBUGASSERT(!strchr(fmt, '\n')); + if(Curl_trc_ft_is_verbose(data, &Curl_trc_feat_ftp)) { + va_list ap; + va_start(ap, fmt); + trc_infof(data, &Curl_trc_feat_ftp, fmt, ap); + va_end(ap); + } +} +#endif /* !CURL_DISABLE_FTP */ + +#ifndef CURL_DISABLE_SMTP +struct curl_trc_feat Curl_trc_feat_smtp = { + "SMTP", + CURL_LOG_LVL_NONE, +}; + +void Curl_trc_smtp(struct Curl_easy *data, const char *fmt, ...) +{ + DEBUGASSERT(!strchr(fmt, '\n')); + if(Curl_trc_ft_is_verbose(data, &Curl_trc_feat_smtp)) { + va_list ap; + va_start(ap, fmt); + trc_infof(data, &Curl_trc_feat_smtp, fmt, ap); + va_end(ap); + } +} +#endif /* !CURL_DISABLE_SMTP */ + +#if !defined(CURL_DISABLE_WEBSOCKETS) && !defined(CURL_DISABLE_HTTP) +struct curl_trc_feat Curl_trc_feat_ws = { + "WS", + CURL_LOG_LVL_NONE, +}; + +void Curl_trc_ws(struct Curl_easy *data, const char *fmt, ...) +{ + DEBUGASSERT(!strchr(fmt, '\n')); + if(Curl_trc_ft_is_verbose(data, &Curl_trc_feat_ws)) { + va_list ap; + va_start(ap, fmt); + trc_infof(data, &Curl_trc_feat_ws, fmt, ap); + va_end(ap); + } +} +#endif /* !CURL_DISABLE_WEBSOCKETS && !CURL_DISABLE_HTTP */ + +#define TRC_CT_NONE (0) +#define TRC_CT_PROTOCOL (1<<(0)) +#define TRC_CT_NETWORK (1<<(1)) +#define TRC_CT_PROXY (1<<(2)) + +struct trc_feat_def { + struct curl_trc_feat *feat; + unsigned int category; +}; + +static struct trc_feat_def trc_feats[] = { + { &Curl_trc_feat_read, TRC_CT_NONE }, + { &Curl_trc_feat_write, TRC_CT_NONE }, +#ifndef CURL_DISABLE_FTP + { &Curl_trc_feat_ftp, TRC_CT_PROTOCOL }, +#endif +#ifndef CURL_DISABLE_DOH + { &Curl_doh_trc, TRC_CT_NETWORK }, +#endif +#ifndef CURL_DISABLE_SMTP + { &Curl_trc_feat_smtp, TRC_CT_PROTOCOL }, +#endif +#if !defined(CURL_DISABLE_WEBSOCKETS) && !defined(CURL_DISABLE_HTTP) + { &Curl_trc_feat_ws, TRC_CT_PROTOCOL }, +#endif +}; + +struct trc_cft_def { + struct Curl_cftype *cft; + unsigned int category; +}; + +static struct trc_cft_def trc_cfts[] = { + { &Curl_cft_tcp, TRC_CT_NETWORK }, + { &Curl_cft_udp, TRC_CT_NETWORK }, + { &Curl_cft_unix, TRC_CT_NETWORK }, + { &Curl_cft_tcp_accept, TRC_CT_NETWORK }, + { &Curl_cft_happy_eyeballs, TRC_CT_NETWORK }, + { &Curl_cft_setup, TRC_CT_PROTOCOL }, +#ifdef USE_NGHTTP2 + { &Curl_cft_nghttp2, TRC_CT_PROTOCOL }, +#endif +#ifdef USE_SSL + { &Curl_cft_ssl, TRC_CT_NETWORK }, +#ifndef CURL_DISABLE_PROXY + { &Curl_cft_ssl_proxy, TRC_CT_PROXY }, +#endif +#endif +#if !defined(CURL_DISABLE_PROXY) +#if !defined(CURL_DISABLE_HTTP) + { &Curl_cft_h1_proxy, TRC_CT_PROXY }, +#ifdef USE_NGHTTP2 + { &Curl_cft_h2_proxy, TRC_CT_PROXY }, +#endif + { &Curl_cft_http_proxy, TRC_CT_PROXY }, +#endif /* !CURL_DISABLE_HTTP */ + { &Curl_cft_haproxy, TRC_CT_PROXY }, + { &Curl_cft_socks_proxy, TRC_CT_PROXY }, +#endif /* !CURL_DISABLE_PROXY */ +#ifdef USE_HTTP3 + { &Curl_cft_http3, TRC_CT_PROTOCOL }, +#endif +#if !defined(CURL_DISABLE_HTTP) && !defined(USE_HYPER) + { &Curl_cft_http_connect, TRC_CT_PROTOCOL }, +#endif +}; + +static void trc_apply_level_by_name(const char * const token, int lvl) +{ + size_t i; + + for(i = 0; i < ARRAYSIZE(trc_cfts); ++i) { + if(strcasecompare(token, trc_cfts[i].cft->name)) { + trc_cfts[i].cft->log_level = lvl; + break; + } + } + for(i = 0; i < ARRAYSIZE(trc_feats); ++i) { + if(strcasecompare(token, trc_feats[i].feat->name)) { + trc_feats[i].feat->log_level = lvl; + break; + } + } +} + +static void trc_apply_level_by_category(int category, int lvl) +{ + size_t i; + + for(i = 0; i < ARRAYSIZE(trc_cfts); ++i) { + if(!category || (trc_cfts[i].category & category)) + trc_cfts[i].cft->log_level = lvl; + } + for(i = 0; i < ARRAYSIZE(trc_feats); ++i) { + if(!category || (trc_feats[i].category & category)) + trc_feats[i].feat->log_level = lvl; + } +} + +static CURLcode trc_opt(const char *config) +{ + char *token, *tok_buf, *tmp; + 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; + } + if(strcasecompare(token, "all")) + trc_apply_level_by_category(TRC_CT_NONE, lvl); + else if(strcasecompare(token, "protocol")) + trc_apply_level_by_category(TRC_CT_PROTOCOL, lvl); + else if(strcasecompare(token, "network")) + trc_apply_level_by_category(TRC_CT_NETWORK, lvl); + else if(strcasecompare(token, "proxy")) + trc_apply_level_by_category(TRC_CT_PROXY, lvl); + else + trc_apply_level_by_name(token, lvl); + + token = strtok_r(NULL, ", ", &tok_buf); + } + free(tmp); + return CURLE_OK; +} + +CURLcode Curl_trc_opt(const char *config) +{ + CURLcode result = config ? trc_opt(config) : CURLE_OK; +#ifdef DEBUGBUILD + /* CURL_DEBUG can override anything */ + if(!result) { + const char *dbg_config = getenv("CURL_DEBUG"); + if(dbg_config) + result = trc_opt(dbg_config); + } +#endif /* DEBUGBUILD */ + return result; +} + +CURLcode Curl_trc_init(void) +{ +#ifdef DEBUGBUILD + return Curl_trc_opt(NULL); +#else + return CURLE_OK; +#endif +} + +#else /* defined(CURL_DISABLE_VERBOSE_STRINGS) */ + +CURLcode Curl_trc_init(void) +{ + return CURLE_OK; +} + +#endif /* !defined(CURL_DISABLE_VERBOSE_STRINGS) */ diff --git a/contrib/curl/lib/curl_trc.h b/contrib/curl/lib/curl_trc.h new file mode 100644 index 0000000000..67a7f4aa10 --- /dev/null +++ b/contrib/curl/lib/curl_trc.h @@ -0,0 +1,229 @@ +#ifndef HEADER_CURL_TRC_H +#define HEADER_CURL_TRC_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +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, + const char *fmt, ...) CURL_PRINTF(2, 3); + +#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) +#define CURL_TRC_WRITE(data, ...) \ + do { if(Curl_trc_ft_is_verbose(data, &Curl_trc_feat_write)) \ + Curl_trc_write(data, __VA_ARGS__); } while(0) +#define CURL_TRC_READ(data, ...) \ + do { if(Curl_trc_ft_is_verbose(data, &Curl_trc_feat_read)) \ + Curl_trc_read(data, __VA_ARGS__); } while(0) + +#ifndef CURL_DISABLE_FTP +#define CURL_TRC_FTP(data, ...) \ + do { if(Curl_trc_ft_is_verbose(data, &Curl_trc_feat_ftp)) \ + Curl_trc_ftp(data, __VA_ARGS__); } while(0) +#endif /* !CURL_DISABLE_FTP */ +#ifndef CURL_DISABLE_SMTP +#define CURL_TRC_SMTP(data, ...) \ + do { if(Curl_trc_ft_is_verbose(data, &Curl_trc_feat_smtp)) \ + Curl_trc_smtp(data, __VA_ARGS__); } while(0) +#endif /* !CURL_DISABLE_SMTP */ +#if !defined(CURL_DISABLE_WEBSOCKETS) && !defined(CURL_DISABLE_HTTP) +#define CURL_TRC_WS(data, ...) \ + do { if(Curl_trc_ft_is_verbose(data, &Curl_trc_feat_ws)) \ + Curl_trc_ws(data, __VA_ARGS__); } while(0) +#endif /* !CURL_DISABLE_WEBSOCKETS && !CURL_DISABLE_HTTP */ + +#else /* CURL_HAVE_C99 */ + +#define infof Curl_infof +#define CURL_TRC_CF Curl_trc_cf_infof +#define CURL_TRC_WRITE Curl_trc_write +#define CURL_TRC_READ Curl_trc_read + +#ifndef CURL_DISABLE_FTP +#define CURL_TRC_FTP Curl_trc_ftp +#endif +#ifndef CURL_DISABLE_SMTP +#define CURL_TRC_SMTP Curl_trc_smtp +#endif +#if !defined(CURL_DISABLE_WEBSOCKETS) && !defined(CURL_DISABLE_HTTP) +#define CURL_TRC_WS Curl_trc_ws +#endif + +#endif /* !CURL_HAVE_C99 */ + +#ifndef CURL_DISABLE_VERBOSE_STRINGS +/* informational messages enabled */ + +struct curl_trc_feat { + const char *name; + int log_level; +}; +extern struct curl_trc_feat Curl_trc_feat_read; +extern struct curl_trc_feat Curl_trc_feat_write; + +#define Curl_trc_is_verbose(data) \ + ((data) && (data)->set.verbose && \ + (!(data)->state.feat || \ + ((data)->state.feat->log_level >= CURL_LOG_LVL_INFO))) +#define Curl_trc_cf_is_verbose(cf, data) \ + (Curl_trc_is_verbose(data) && \ + (cf) && (cf)->cft->log_level >= CURL_LOG_LVL_INFO) +#define Curl_trc_ft_is_verbose(data, ft) \ + (Curl_trc_is_verbose(data) && \ + (ft)->log_level >= CURL_LOG_LVL_INFO) + +/** + * Output an informational message when transfer's verbose logging is enabled. + */ +void Curl_infof(struct Curl_easy *data, + const char *fmt, ...) CURL_PRINTF(2, 3); + +/** + * 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, + const char *fmt, ...) CURL_PRINTF(3, 4); +void Curl_trc_write(struct Curl_easy *data, + const char *fmt, ...) CURL_PRINTF(2, 3); +void Curl_trc_read(struct Curl_easy *data, + const char *fmt, ...) CURL_PRINTF(2, 3); + +#ifndef CURL_DISABLE_FTP +extern struct curl_trc_feat Curl_trc_feat_ftp; +void Curl_trc_ftp(struct Curl_easy *data, + const char *fmt, ...) CURL_PRINTF(2, 3); +#endif +#ifndef CURL_DISABLE_SMTP +extern struct curl_trc_feat Curl_trc_feat_smtp; +void Curl_trc_smtp(struct Curl_easy *data, + const char *fmt, ...) CURL_PRINTF(2, 3); +#endif +#if !defined(CURL_DISABLE_WEBSOCKETS) && !defined(CURL_DISABLE_HTTP) +extern struct curl_trc_feat Curl_trc_feat_ws; +void Curl_trc_ws(struct Curl_easy *data, + const char *fmt, ...) CURL_PRINTF(2, 3); +#endif + + +#else /* defined(CURL_DISABLE_VERBOSE_STRINGS) */ +/* All informational messages are not compiled in for size savings */ + +#define Curl_trc_is_verbose(d) (FALSE) +#define Curl_trc_cf_is_verbose(x,y) (FALSE) +#define Curl_trc_ft_is_verbose(x,y) (FALSE) + +static void Curl_infof(struct Curl_easy *data, const char *fmt, ...) +{ + (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; +} + +struct curl_trc_feat; + +static void Curl_trc_write(struct Curl_easy *data, const char *fmt, ...) +{ + (void)data; (void)fmt; +} + +static void Curl_trc_read(struct Curl_easy *data, const char *fmt, ...) +{ + (void)data; (void)fmt; +} + +#ifndef CURL_DISABLE_FTP +static void Curl_trc_ftp(struct Curl_easy *data, const char *fmt, ...) +{ + (void)data; (void)fmt; +} +#endif +#ifndef CURL_DISABLE_SMTP +static void Curl_trc_smtp(struct Curl_easy *data, const char *fmt, ...) +{ + (void)data; (void)fmt; +} +#endif +#if !defined(CURL_DISABLE_WEBSOCKETS) || !defined(CURL_DISABLE_HTTP) +static void Curl_trc_ws(struct Curl_easy *data, const char *fmt, ...) +{ + (void)data; (void)fmt; +} +#endif + +#endif /* !defined(CURL_DISABLE_VERBOSE_STRINGS) */ + +#endif /* HEADER_CURL_TRC_H */ diff --git a/contrib/curl/lib/curlx.h b/contrib/curl/lib/curlx.h index 6168dc119f..f0e4e6470b 100644 --- a/contrib/curl/lib/curlx.h +++ b/contrib/curl/lib/curlx.h @@ -7,11 +7,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2016, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -20,6 +20,8 @@ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY * KIND, either express or implied. * + * SPDX-License-Identifier: curl + * ***************************************************************************/ /* @@ -29,10 +31,8 @@ * be. */ -#include -/* 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 */ +/* map standard printf functions to curl implementations */ +#include "curl_printf.h" #include "strcase.h" /* "strcase.h" provides the strcasecompare protos */ @@ -42,16 +42,6 @@ curl_off_t number from a given string. */ -#include "timeval.h" -/* - "timeval.h" sets up a 'struct timeval' even for platforms that otherwise - don't have one and has protos for these functions: - - curlx_tvnow() - curlx_tvdiff() - curlx_tvdiff_secs() -*/ - #include "nonblock.h" /* "nonblock.h" provides curlx_nonblock() */ @@ -63,53 +53,17 @@ curlx_uztosi() */ -/* 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 +#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() */ -#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 snprintf -# undef vprintf -# undef vfprintf -# undef vsprintf -# undef vsnprintf -# undef aprintf -# undef vaprintf - -# define printf curlx_mprintf -# define fprintf curlx_mfprintf -# define sprintf curlx_msprintf -# define snprintf curlx_msnprintf -# define vprintf curlx_mvprintf -# define vfprintf curlx_mvfprintf -# define vsprintf curlx_mvsprintf -# define vsnprintf curlx_mvsnprintf -# define aprintf curlx_maprintf -# define vaprintf curlx_mvaprintf -#endif /* ENABLE_CURLX_PRINTF */ +#include "version_win32.h" +/* "version_win32.h" provides curlx_verify_windows_version() */ #endif /* HEADER_CURL_CURLX_H */ - diff --git a/contrib/curl/lib/cw-out.c b/contrib/curl/lib/cw-out.c new file mode 100644 index 0000000000..4d3df0a650 --- /dev/null +++ b/contrib/curl/lib/cw-out.c @@ -0,0 +1,473 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#include + +#include "urldata.h" +#include "cfilters.h" +#include "headers.h" +#include "multiif.h" +#include "sendf.h" +#include "cw-out.h" + +/* The last 3 #include files should be in this order */ +#include "curl_printf.h" +#include "curl_memory.h" +#include "memdebug.h" + + +/** + * OVERALL DESIGN of this client writer + * + * The 'cw-out' writer is supposed to be the last writer in a transfer's + * stack. It is always added when that stack is initialized. Its purpose + * is to pass BODY and HEADER bytes to the client-installed callback + * functions. + * + * These callback may return `CURL_WRITEFUNC_PAUSE` to indicate that the + * data had not been written and the whole transfer should stop receiving + * new data. Or at least, stop calling the functions. When the transfer + * is "unpaused" by the client, the previous data shall be passed as + * if nothing happened. + * + * The `cw-out` writer therefore manages buffers for bytes that could + * not be written. Data that was already in flight from the server also + * needs buffering on paused transfer when it arrives. + * + * In addition, the writer allows buffering of "small" body writes, + * so client functions are called less often. That is only enabled on a + * number of conditions. + * + * HEADER and BODY data may arrive in any order. For paused transfers, + * a list of `struct cw_out_buf` is kept for `cw_out_type` types. The + * list may be: [BODY]->[HEADER]->[BODY]->[HEADER].... + * When unpausing, this list is "played back" to the client callbacks. + * + * The amount of bytes being buffered is limited by `DYN_PAUSE_BUFFER` + * and when that is exceeded `CURLE_TOO_LARGE` is returned as error. + */ +typedef enum { + CW_OUT_NONE, + CW_OUT_BODY, + CW_OUT_HDS +} cw_out_type; + +struct cw_out_buf { + struct cw_out_buf *next; + struct dynbuf b; + cw_out_type type; +}; + +static struct cw_out_buf *cw_out_buf_create(cw_out_type otype) +{ + struct cw_out_buf *cwbuf = calloc(1, sizeof(*cwbuf)); + if(cwbuf) { + cwbuf->type = otype; + Curl_dyn_init(&cwbuf->b, DYN_PAUSE_BUFFER); + } + return cwbuf; +} + +static void cw_out_buf_free(struct cw_out_buf *cwbuf) +{ + if(cwbuf) { + Curl_dyn_free(&cwbuf->b); + free(cwbuf); + } +} + +struct cw_out_ctx { + struct Curl_cwriter super; + struct cw_out_buf *buf; + BIT(paused); + BIT(errored); +}; + +static CURLcode cw_out_write(struct Curl_easy *data, + struct Curl_cwriter *writer, int type, + const char *buf, size_t nbytes); +static void cw_out_close(struct Curl_easy *data, struct Curl_cwriter *writer); +static CURLcode cw_out_init(struct Curl_easy *data, + struct Curl_cwriter *writer); + +struct Curl_cwtype Curl_cwt_out = { + "cw-out", + NULL, + cw_out_init, + cw_out_write, + cw_out_close, + sizeof(struct cw_out_ctx) +}; + +static CURLcode cw_out_init(struct Curl_easy *data, + struct Curl_cwriter *writer) +{ + struct cw_out_ctx *ctx = writer->ctx; + (void)data; + ctx->buf = NULL; + return CURLE_OK; +} + +static void cw_out_bufs_free(struct cw_out_ctx *ctx) +{ + while(ctx->buf) { + struct cw_out_buf *next = ctx->buf->next; + cw_out_buf_free(ctx->buf); + ctx->buf = next; + } +} + +static size_t cw_out_bufs_len(struct cw_out_ctx *ctx) +{ + struct cw_out_buf *cwbuf = ctx->buf; + size_t len = 0; + while(cwbuf) { + len += Curl_dyn_len(&cwbuf->b); + cwbuf = cwbuf->next; + } + return len; +} + +static void cw_out_close(struct Curl_easy *data, struct Curl_cwriter *writer) +{ + struct cw_out_ctx *ctx = writer->ctx; + + (void)data; + cw_out_bufs_free(ctx); +} + +/** + * Return the current curl_write_callback and user_data for the buf type + */ +static void cw_get_writefunc(struct Curl_easy *data, cw_out_type otype, + curl_write_callback *pwcb, void **pwcb_data, + size_t *pmax_write, size_t *pmin_write) +{ + switch(otype) { + case CW_OUT_BODY: + *pwcb = data->set.fwrite_func; + *pwcb_data = data->set.out; + *pmax_write = CURL_MAX_WRITE_SIZE; + /* if we ever want buffering of BODY output, we can set `min_write` + * the preferred size. The default should always be to pass data + * to the client as it comes without delay */ + *pmin_write = 0; + break; + case CW_OUT_HDS: + *pwcb = data->set.fwrite_header ? data->set.fwrite_header : + (data->set.writeheader ? data->set.fwrite_func : NULL); + *pwcb_data = data->set.writeheader; + *pmax_write = 0; /* do not chunk-write headers, write them as they are */ + *pmin_write = 0; + break; + default: + *pwcb = NULL; + *pwcb_data = NULL; + *pmax_write = CURL_MAX_WRITE_SIZE; + *pmin_write = 0; + } +} + +static CURLcode cw_out_ptr_flush(struct cw_out_ctx *ctx, + struct Curl_easy *data, + cw_out_type otype, + bool flush_all, + const char *buf, size_t blen, + size_t *pconsumed) +{ + curl_write_callback wcb; + void *wcb_data; + size_t max_write, min_write; + size_t wlen, nwritten; + + /* If we errored once, we do not invoke the client callback again */ + if(ctx->errored) + return CURLE_WRITE_ERROR; + + /* write callbacks may get NULLed by the client between calls. */ + cw_get_writefunc(data, otype, &wcb, &wcb_data, &max_write, &min_write); + if(!wcb) { + *pconsumed = blen; + return CURLE_OK; + } + + *pconsumed = 0; + while(blen && !ctx->paused) { + if(!flush_all && blen < min_write) + break; + wlen = max_write ? CURLMIN(blen, max_write) : blen; + Curl_set_in_callback(data, TRUE); + nwritten = wcb((char *)buf, 1, wlen, wcb_data); + Curl_set_in_callback(data, FALSE); + CURL_TRC_WRITE(data, "cw_out, wrote %zu %s bytes -> %zu", + wlen, (otype == CW_OUT_BODY) ? "body" : "header", + nwritten); + if(CURL_WRITEFUNC_PAUSE == nwritten) { + if(data->conn && data->conn->handler->flags & PROTOPT_NONETWORK) { + /* Protocols that work without network cannot be paused. This is + actually only FILE:// just now, and it cannot pause since the + transfer is not done using the "normal" procedure. */ + failf(data, "Write callback asked for PAUSE when not supported"); + return CURLE_WRITE_ERROR; + } + /* mark the connection as RECV paused */ + data->req.keepon |= KEEP_RECV_PAUSE; + ctx->paused = TRUE; + CURL_TRC_WRITE(data, "cw_out, PAUSE requested by client"); + break; + } + else if(CURL_WRITEFUNC_ERROR == nwritten) { + failf(data, "client returned ERROR on write of %zu bytes", wlen); + return CURLE_WRITE_ERROR; + } + else if(nwritten != wlen) { + failf(data, "Failure writing output to destination, " + "passed %zu returned %zd", wlen, nwritten); + return CURLE_WRITE_ERROR; + } + *pconsumed += nwritten; + blen -= nwritten; + buf += nwritten; + } + return CURLE_OK; +} + +static CURLcode cw_out_buf_flush(struct cw_out_ctx *ctx, + struct Curl_easy *data, + struct cw_out_buf *cwbuf, + bool flush_all) +{ + CURLcode result = CURLE_OK; + + if(Curl_dyn_len(&cwbuf->b)) { + size_t consumed; + + result = cw_out_ptr_flush(ctx, data, cwbuf->type, flush_all, + Curl_dyn_ptr(&cwbuf->b), + Curl_dyn_len(&cwbuf->b), + &consumed); + if(result) + return result; + + if(consumed) { + if(consumed == Curl_dyn_len(&cwbuf->b)) { + Curl_dyn_free(&cwbuf->b); + } + else { + DEBUGASSERT(consumed < Curl_dyn_len(&cwbuf->b)); + result = Curl_dyn_tail(&cwbuf->b, Curl_dyn_len(&cwbuf->b) - consumed); + if(result) + return result; + } + } + } + return result; +} + +static CURLcode cw_out_flush_chain(struct cw_out_ctx *ctx, + struct Curl_easy *data, + struct cw_out_buf **pcwbuf, + bool flush_all) +{ + struct cw_out_buf *cwbuf = *pcwbuf; + CURLcode result; + + if(!cwbuf) + return CURLE_OK; + if(ctx->paused) + return CURLE_OK; + + /* write the end of the chain until it blocks or gets empty */ + while(cwbuf->next) { + struct cw_out_buf **plast = &cwbuf->next; + while((*plast)->next) + plast = &(*plast)->next; + result = cw_out_flush_chain(ctx, data, plast, flush_all); + if(result) + return result; + if(*plast) { + /* could not write last, paused again? */ + DEBUGASSERT(ctx->paused); + return CURLE_OK; + } + } + + result = cw_out_buf_flush(ctx, data, cwbuf, flush_all); + if(result) + return result; + if(!Curl_dyn_len(&cwbuf->b)) { + cw_out_buf_free(cwbuf); + *pcwbuf = NULL; + } + return CURLE_OK; +} + +static CURLcode cw_out_append(struct cw_out_ctx *ctx, + cw_out_type otype, + const char *buf, size_t blen) +{ + if(cw_out_bufs_len(ctx) + blen > DYN_PAUSE_BUFFER) + return CURLE_TOO_LARGE; + + /* if we do not have a buffer, or it is of another type, make a new one. + * And for CW_OUT_HDS always make a new one, so we "replay" headers + * exactly as they came in */ + if(!ctx->buf || (ctx->buf->type != otype) || (otype == CW_OUT_HDS)) { + struct cw_out_buf *cwbuf = cw_out_buf_create(otype); + if(!cwbuf) + return CURLE_OUT_OF_MEMORY; + cwbuf->next = ctx->buf; + ctx->buf = cwbuf; + } + DEBUGASSERT(ctx->buf && (ctx->buf->type == otype)); + return Curl_dyn_addn(&ctx->buf->b, buf, blen); +} + +static CURLcode cw_out_do_write(struct cw_out_ctx *ctx, + struct Curl_easy *data, + cw_out_type otype, + bool flush_all, + const char *buf, size_t blen) +{ + CURLcode result = CURLE_OK; + + /* if we have buffered data and it is a different type than what + * we are writing now, try to flush all */ + if(ctx->buf && ctx->buf->type != otype) { + result = cw_out_flush_chain(ctx, data, &ctx->buf, TRUE); + if(result) + goto out; + } + + if(ctx->buf) { + /* still have buffered data, append and flush */ + result = cw_out_append(ctx, otype, buf, blen); + if(result) + return result; + result = cw_out_flush_chain(ctx, data, &ctx->buf, flush_all); + if(result) + goto out; + } + else { + /* nothing buffered, try direct write */ + size_t consumed; + result = cw_out_ptr_flush(ctx, data, otype, flush_all, + buf, blen, &consumed); + if(result) + return result; + if(consumed < blen) { + /* did not write all, append the rest */ + result = cw_out_append(ctx, otype, buf + consumed, blen - consumed); + if(result) + goto out; + } + } + +out: + if(result) { + /* We do not want to invoked client callbacks a second time after + * encountering an error. See issue #13337 */ + ctx->errored = TRUE; + cw_out_bufs_free(ctx); + } + return result; +} + +static CURLcode cw_out_write(struct Curl_easy *data, + struct Curl_cwriter *writer, int type, + const char *buf, size_t blen) +{ + struct cw_out_ctx *ctx = writer->ctx; + CURLcode result; + bool flush_all = !!(type & CLIENTWRITE_EOS); + + if((type & CLIENTWRITE_BODY) || + ((type & CLIENTWRITE_HEADER) && data->set.include_header)) { + result = cw_out_do_write(ctx, data, CW_OUT_BODY, flush_all, buf, blen); + if(result) + return result; + } + + if(type & (CLIENTWRITE_HEADER|CLIENTWRITE_INFO)) { + result = cw_out_do_write(ctx, data, CW_OUT_HDS, flush_all, buf, blen); + if(result) + return result; + } + + return CURLE_OK; +} + +bool Curl_cw_out_is_paused(struct Curl_easy *data) +{ + struct Curl_cwriter *cw_out; + struct cw_out_ctx *ctx; + + cw_out = Curl_cwriter_get_by_type(data, &Curl_cwt_out); + if(!cw_out) + return FALSE; + + ctx = (struct cw_out_ctx *)cw_out; + CURL_TRC_WRITE(data, "cw-out is%spaused", ctx->paused ? "" : " not"); + return ctx->paused; +} + +static CURLcode cw_out_flush(struct Curl_easy *data, + bool unpause, bool flush_all) +{ + struct Curl_cwriter *cw_out; + CURLcode result = CURLE_OK; + + cw_out = Curl_cwriter_get_by_type(data, &Curl_cwt_out); + if(cw_out) { + struct cw_out_ctx *ctx = (struct cw_out_ctx *)cw_out; + if(ctx->errored) + return CURLE_WRITE_ERROR; + if(unpause && ctx->paused) + ctx->paused = FALSE; + if(ctx->paused) + return CURLE_OK; /* not doing it */ + + result = cw_out_flush_chain(ctx, data, &ctx->buf, flush_all); + if(result) { + ctx->errored = TRUE; + cw_out_bufs_free(ctx); + return result; + } + } + return result; +} + +CURLcode Curl_cw_out_unpause(struct Curl_easy *data) +{ + CURL_TRC_WRITE(data, "cw-out unpause"); + return cw_out_flush(data, TRUE, FALSE); +} + +CURLcode Curl_cw_out_done(struct Curl_easy *data) +{ + CURL_TRC_WRITE(data, "cw-out done"); + return cw_out_flush(data, FALSE, TRUE); +} diff --git a/contrib/curl/lib/cw-out.h b/contrib/curl/lib/cw-out.h new file mode 100644 index 0000000000..ca4c2e435d --- /dev/null +++ b/contrib/curl/lib/cw-out.h @@ -0,0 +1,53 @@ +#ifndef HEADER_CURL_CW_OUT_H +#define HEADER_CURL_CW_OUT_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#include "sendf.h" + +/** + * The client writer type "cw-out" that does the actual writing to + * the client callbacks. Intended to be the last installed in the + * client writer stack of a transfer. + */ +extern struct Curl_cwtype Curl_cwt_out; + +/** + * Return TRUE iff 'cw-out' client write has paused data. + */ +bool Curl_cw_out_is_paused(struct Curl_easy *data); + +/** + * Flush any buffered date to the client, chunk collation still applies. + */ +CURLcode Curl_cw_out_unpause(struct Curl_easy *data); + +/** + * Mark EndOfStream reached and flush ALL data to the client. + */ +CURLcode Curl_cw_out_done(struct Curl_easy *data); + +#endif /* HEADER_CURL_CW_OUT_H */ diff --git a/contrib/curl/lib/dict.c b/contrib/curl/lib/dict.c index 69defc4cc8..7d9c18dc9d 100644 --- a/contrib/curl/lib/dict.c +++ b/contrib/curl/lib/dict.c @@ -5,11 +5,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2016, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -18,6 +18,8 @@ * 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" @@ -46,6 +48,8 @@ #ifdef HAVE_SYS_SELECT_H #include +#elif defined(HAVE_UNISTD_H) +#include #endif #include "urldata.h" @@ -55,6 +59,7 @@ #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: */ @@ -64,14 +69,14 @@ * Forward declarations. */ -static CURLcode dict_do(struct connectdata *conn, bool *done); +static CURLcode dict_do(struct Curl_easy *data, bool *done); /* * DICT protocol handler. */ const struct Curl_handler Curl_handler_dict = { - "DICT", /* scheme */ + "dict", /* scheme */ ZERO_NULL, /* setup_connection */ dict_do, /* do_it */ ZERO_NULL, /* done */ @@ -84,65 +89,104 @@ const struct Curl_handler Curl_handler_dict = { ZERO_NULL, /* domore_getsock */ ZERO_NULL, /* perform_getsock */ ZERO_NULL, /* disconnect */ - ZERO_NULL, /* readwrite */ + ZERO_NULL, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ + ZERO_NULL, /* connection_check */ + ZERO_NULL, /* attach connection */ PORT_DICT, /* defport */ CURLPROTO_DICT, /* protocol */ - PROTOPT_NONE | PROTOPT_NOURLQUERY /* flags */ + CURLPROTO_DICT, /* family */ + PROTOPT_NONE | PROTOPT_NOURLQUERY /* flags */ }; -static char *unescape_word(struct Curl_easy *data, const char *inputbuff) +#define DYN_DICT_WORD 10000 +static char *unescape_word(const char *input) { - char *newp; - char *dictp; - char *ptr; - size_t len; - char ch; - int olen=0; - - CURLcode result = Curl_urldecode(data, inputbuff, 0, &newp, &len, FALSE); - if(!newp || result) - return NULL; - - dictp = malloc(((size_t)len)*2 + 1); /* add one for terminating zero */ - if(dictp) { - /* According to RFC2229 section 2.2, these letters need to be escaped with - \[letter] */ - for(ptr = newp; - (ch = *ptr) != 0; - ptr++) { - if((ch <= 32) || (ch == 127) || - (ch == '\'') || (ch == '\"') || (ch == '\\')) { - dictp[olen++] = '\\'; - } - dictp[olen++] = ch; + 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(struct Curl_easy *data, + const char *fmt, ...) CURL_PRINTF(2, 3); + +static CURLcode sendf(struct Curl_easy *data, const char *fmt, ...) +{ + size_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_xfer_send(data, sptr, write_len, FALSE, &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; } - dictp[olen]=0; + else + break; } - free(newp); - return dictp; + + free(s); /* free the output string */ + + return result; } -static CURLcode dict_do(struct connectdata *conn, bool *done) +static CURLcode dict_do(struct Curl_easy *data, bool *done) { char *word; - char *eword; + 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=CURLE_OK; - struct Curl_easy *data=conn->data; - curl_socket_t sockfd = conn->sock[FIRSTSOCKET]; + CURLcode result; - char *path = data->state.path; - curl_off_t *bytecount = &data->req.bytecount; + char *path; *done = TRUE; /* unconditionally */ - if(conn->bits.user_passwd) { - /* AUTH is missing */ - } + /* 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) || @@ -165,42 +209,39 @@ static CURLcode dict_do(struct connectdata *conn, bool *done) } } - if((word == NULL) || (*word == (char)0)) { - infof(data, "lookup word is missing\n"); - word=(char *)"default"; + if(!word || (*word == (char)0)) { + infof(data, "lookup word is missing"); + word = (char *)"default"; } - if((database == NULL) || (*database == (char)0)) { + if(!database || (*database == (char)0)) { database = (char *)"!"; } - if((strategy == NULL) || (*strategy == (char)0)) { + if(!strategy || (*strategy == (char)0)) { strategy = (char *)"."; } - eword = unescape_word(data, word); - if(!eword) - return CURLE_OUT_OF_MEMORY; - - result = Curl_sendf(sockfd, conn, - "CLIENT " LIBCURL_NAME " " LIBCURL_VERSION "\r\n" - "MATCH " - "%s " /* database */ - "%s " /* strategy */ - "%s\r\n" /* word */ - "QUIT\r\n", - - database, - strategy, - eword - ); + eword = unescape_word(word); + if(!eword) { + result = CURLE_OUT_OF_MEMORY; + goto error; + } - free(eword); + result = sendf(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"); - return result; + goto error; } - Curl_setup_transfer(conn, FIRSTSOCKET, -1, FALSE, bytecount, - -1, NULL); /* no upload */ + Curl_xfer_setup1(data, CURL_XFER_RECV, -1, FALSE); /* no upload */ } else if(strncasecompare(path, DICT_DEFINE, sizeof(DICT_DEFINE)-1) || strncasecompare(path, DICT_DEFINE2, sizeof(DICT_DEFINE2)-1) || @@ -219,35 +260,34 @@ static CURLcode dict_do(struct connectdata *conn, bool *done) } } - if((word == NULL) || (*word == (char)0)) { - infof(data, "lookup word is missing\n"); - word=(char *)"default"; + if(!word || (*word == (char)0)) { + infof(data, "lookup word is missing"); + word = (char *)"default"; } - if((database == NULL) || (*database == (char)0)) { + if(!database || (*database == (char)0)) { database = (char *)"!"; } - eword = unescape_word(data, word); - if(!eword) - return CURLE_OUT_OF_MEMORY; - - result = Curl_sendf(sockfd, conn, - "CLIENT " LIBCURL_NAME " " LIBCURL_VERSION "\r\n" - "DEFINE " - "%s " /* database */ - "%s\r\n" /* word */ - "QUIT\r\n", - database, - eword); + eword = unescape_word(word); + if(!eword) { + result = CURLE_OUT_OF_MEMORY; + goto error; + } - free(eword); + result = sendf(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"); - return result; + goto error; } - Curl_setup_transfer(conn, FIRSTSOCKET, -1, FALSE, bytecount, - -1, NULL); /* no upload */ + Curl_xfer_setup1(data, CURL_XFER_RECV, -1, FALSE); } else { @@ -260,19 +300,22 @@ static CURLcode dict_do(struct connectdata *conn, bool *done) if(ppath[i] == ':') ppath[i] = ' '; } - result = Curl_sendf(sockfd, conn, - "CLIENT " LIBCURL_NAME " " LIBCURL_VERSION "\r\n" - "%s\r\n" - "QUIT\r\n", ppath); + result = sendf(data, + "CLIENT " LIBCURL_NAME " " LIBCURL_VERSION "\r\n" + "%s\r\n" + "QUIT\r\n", ppath); if(result) { failf(data, "Failed sending DICT request"); - return result; + goto error; } - Curl_setup_transfer(conn, FIRSTSOCKET, -1, FALSE, bytecount, -1, NULL); + Curl_xfer_setup1(data, CURL_XFER_RECV, -1, FALSE); } } - return CURLE_OK; +error: + free(eword); + free(path); + return result; } -#endif /*CURL_DISABLE_DICT*/ +#endif /* CURL_DISABLE_DICT */ diff --git a/contrib/curl/lib/dict.h b/contrib/curl/lib/dict.h index 12c0f3394d..ba9a92719b 100644 --- a/contrib/curl/lib/dict.h +++ b/contrib/curl/lib/dict.h @@ -7,11 +7,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2009, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -20,6 +20,8 @@ * 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 diff --git a/contrib/curl/lib/dllmain.c b/contrib/curl/lib/dllmain.c new file mode 100644 index 0000000000..41e97b37eb --- /dev/null +++ b/contrib/curl/lib/dllmain.c @@ -0,0 +1,81 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#ifdef USE_OPENSSL +#include +#endif + +/* The fourth-to-last include */ +#ifdef __CYGWIN__ +#define WIN32_LEAN_AND_MEAN +#include +#ifdef _WIN32 +#undef _WIN32 +#endif +#endif + +/* The last 3 #include files should be in this order */ +#include "curl_printf.h" +#include "curl_memory.h" +#include "memdebug.h" + +/* DllMain() must only be defined for Windows and Cygwin DLL builds. */ +#if (defined(_WIN32) || defined(__CYGWIN__)) && !defined(CURL_STATICLIB) + +#if defined(USE_OPENSSL) && \ + !defined(OPENSSL_IS_AWSLC) && \ + !defined(OPENSSL_IS_BORINGSSL) && \ + !defined(LIBRESSL_VERSION_NUMBER) && \ + (OPENSSL_VERSION_NUMBER >= 0x10100000L) +#define PREVENT_OPENSSL_MEMLEAK +#endif + +#ifdef PREVENT_OPENSSL_MEMLEAK +BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved); +BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) +{ + (void)hinstDLL; + (void)lpvReserved; + + switch(fdwReason) { + case DLL_PROCESS_ATTACH: + break; + case DLL_PROCESS_DETACH: + break; + case DLL_THREAD_ATTACH: + break; + case DLL_THREAD_DETACH: + /* Call OPENSSL_thread_stop to prevent a memory leak in case OpenSSL is + linked statically. + https://github.com/curl/curl/issues/12327#issuecomment-1826405944 */ + OPENSSL_thread_stop(); + break; + } + return TRUE; +} +#endif /* OpenSSL */ + +#endif /* DLL build */ diff --git a/contrib/curl/lib/doh.c b/contrib/curl/lib/doh.c new file mode 100644 index 0000000000..cb41f25ebe --- /dev/null +++ b/contrib/curl/lib/doh.c @@ -0,0 +1,1444 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#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" +#include "escape.h" + +#define DNS_CLASS_IN 0x01 + +/* doh_print_buf truncates if the hex string will be more than this */ +#define LOCAL_PB_HEXMAX 400 + +#ifndef CURL_DISABLE_VERBOSE_STRINGS +static const char * const errors[]={ + "", + "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"; +} + +struct curl_trc_feat Curl_doh_trc = { + "DoH", + CURL_LOG_LVL_NONE, +}; +#endif /* !CURL_DISABLE_VERBOSE_STRINGS */ + +/* @unittest 1655 + */ +UNITTEST DOHcode doh_req_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 hostname. + * + * 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 hostname ends with a trailing dot, the corresponding + * QNAME-encoding is one byte longer than the hostname. 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 hostname. + * + * 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; +} + +#if defined(USE_HTTPSRR) && defined(DEBUGBUILD) +static void doh_print_buf(struct Curl_easy *data, + const char *prefix, + unsigned char *buf, size_t len) +{ + unsigned char hexstr[LOCAL_PB_HEXMAX]; + size_t hlen = LOCAL_PB_HEXMAX; + bool truncated = false; + + if(len > (LOCAL_PB_HEXMAX / 2)) + truncated = true; + Curl_hexencode(buf, len, hexstr, hlen); + if(!truncated) + infof(data, "%s: len=%d, val=%s", prefix, (int)len, hexstr); + else + infof(data, "%s: len=%d (truncated)val=%s", prefix, (int)len, hexstr); + return; +} +#endif + +/* called from multi.c when this DoH transfer is complete */ +static int doh_done(struct Curl_easy *doh, CURLcode result) +{ + struct Curl_easy *data; /* the transfer that asked for the DoH probe */ + + data = Curl_multi_get_handle(doh->multi, doh->set.dohfor_mid); + if(!data) { + DEBUGF(infof(doh, "doh_done: xfer for mid=%" FMT_OFF_T + " not found", doh->set.dohfor_mid)); + DEBUGASSERT(0); + } + else { + struct doh_probes *dohp = data->req.doh; + /* one of the DoH request done for the 'data' transfer is now complete! */ + dohp->pending--; + infof(doh, "a DoH request is completed, %u to go", dohp->pending); + if(result) + infof(doh, "DoH request %s", curl_easy_strerror(result)); + + if(!dohp->pending) { + /* DoH completed, run the transfer picking up the results */ + 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 doh_run_probe(struct Curl_easy *data, + struct doh_probe *p, DNStype dnstype, + const char *host, + const char *url, CURLM *multi, + struct curl_slist *headers) +{ + struct Curl_easy *doh = NULL; + CURLcode result = CURLE_OK; + timediff_t timeout_ms; + DOHcode d = doh_req_encode(host, dnstype, p->req_body, sizeof(p->req_body), + &p->req_body_len); + if(d) { + failf(data, "Failed to encode DoH packet [%d]", d); + return CURLE_OUT_OF_MEMORY; + } + + p->dnstype = dnstype; + Curl_dyn_init(&p->resp_body, 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) + goto error; + + /* pass in the struct pointer via a local variable to please coverity and + the gcc typecheck helpers */ + doh->state.internal = true; +#ifndef CURL_DISABLE_VERBOSE_STRINGS + doh->state.feat = &Curl_doh_trc; +#endif + 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, &p->resp_body); + ERROR_CHECK_SETOPT(CURLOPT_POSTFIELDS, p->req_body); + ERROR_CHECK_SETOPT(CURLOPT_POSTFIELDSIZE, (long)p->req_body_len); + 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 DEBUGBUILD + /* 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(Curl_trc_ft_is_verbose(data, &Curl_doh_trc)) + 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_mid = data->mid; /* for which transfer this is done */ + + /* 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; + + p->easy_mid = doh->mid; + return CURLE_OK; + +error: + Curl_close(&doh); + p->easy_mid = -1; + 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; + struct doh_probes *dohp; + struct connectdata *conn = data->conn; + size_t i; +#ifdef USE_HTTPSRR + /* for now, this is only used when ECH is enabled */ +# ifdef USE_ECH + char *qname = NULL; +# endif +#endif + *waitp = FALSE; + (void)hostname; + (void)port; + + DEBUGASSERT(!data->req.doh); + DEBUGASSERT(conn); + + /* start clean, consider allocating this struct on demand */ + dohp = data->req.doh = calloc(1, sizeof(struct doh_probes)); + if(!dohp) + return NULL; + + for(i = 0; i < DOH_SLOT_COUNT; ++i) { + dohp->probe[i].easy_mid = -1; + } + + conn->bits.doh = TRUE; + dohp->host = hostname; + dohp->port = port; + dohp->req_hds = + curl_slist_append(NULL, + "Content-Type: application/dns-message"); + if(!dohp->req_hds) + goto error; + + /* create IPv4 DoH request */ + result = doh_run_probe(data, &dohp->probe[DOH_SLOT_IPV4], + DNS_TYPE_A, hostname, data->set.str[STRING_DOH], + data->multi, dohp->req_hds); + if(result) + goto error; + dohp->pending++; + +#ifdef USE_IPV6 + if((conn->ip_version != CURL_IPRESOLVE_V4) && Curl_ipv6works(data)) { + /* create IPv6 DoH request */ + result = doh_run_probe(data, &dohp->probe[DOH_SLOT_IPV6], + DNS_TYPE_AAAA, hostname, data->set.str[STRING_DOH], + data->multi, dohp->req_hds); + if(result) + goto error; + dohp->pending++; + } +#endif + +#ifdef USE_HTTPSRR + /* + * TODO: Figure out the conditions under which we want to make + * a request for an HTTPS RR when we are not doing ECH. For now, + * making this request breaks a bunch of DoH tests, e.g. test2100, + * where the additional request does not match the pre-cooked data + * files, so there is a bit of work attached to making the request + * in a non-ECH use-case. For the present, we will only make the + * request when ECH is enabled in the build and is being used for + * the curl operation. + */ +# ifdef USE_ECH + if(data->set.tls_ech & CURLECH_ENABLE + || data->set.tls_ech & CURLECH_HARD) { + if(port == 443) + qname = strdup(hostname); + else + qname = aprintf("_%d._https.%s", port, hostname); + if(!qname) + goto error; + result = doh_run_probe(data, &dohp->probe[DOH_SLOT_HTTPS_RR], + DNS_TYPE_HTTPS, qname, data->set.str[STRING_DOH], + data->multi, dohp->req_hds); + Curl_safefree(qname); + if(result) + goto error; + dohp->pending++; + } +# endif +#endif + *waitp = TRUE; /* this never returns synchronously */ + return NULL; + +error: + Curl_doh_cleanup(data); + return NULL; +} + +static DOHcode doh_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 += (unsigned int)(1 + length); + } while(length); + return DOH_OK; +} + +static unsigned short doh_get16bit(const unsigned char *doh, + unsigned int index) +{ + return (unsigned short)((doh[index] << 8) | doh[index + 1]); +} + +static unsigned int doh_get32bit(const unsigned char *doh, unsigned 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 will not be upset */ + return ((unsigned)doh[0] << 24) | ((unsigned)doh[1] << 16) | + ((unsigned)doh[2] << 8) | doh[3]; +} + +static void doh_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++; + } +} + +static void doh_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++; + } +} + +#ifdef USE_HTTPSRR +static DOHcode doh_store_https(const unsigned char *doh, int index, + struct dohentry *d, uint16_t len) +{ + /* silently ignore RRs over the limit */ + if(d->numhttps_rrs < DOH_MAX_HTTPS) { + struct dohhttps_rr *h = &d->https_rrs[d->numhttps_rrs]; + h->val = Curl_memdup(&doh[index], len); + if(!h->val) + return DOH_OUT_OF_MEM; + h->len = len; + d->numhttps_rrs++; + } + return DOH_OK; +} +#endif + +static DOHcode doh_store_cname(const unsigned char *doh, size_t dohlen, + unsigned int index, struct dohentry *d) +{ + struct dynbuf *c; + unsigned int loop = 128; /* a valid DNS name can never loop this much */ + unsigned char length; + + if(d->numcname == DOH_MAX_CNAME) + return DOH_OK; /* skip! */ + + c = &d->cname[d->numcname++]; + do { + if(index >= dohlen) + return DOH_DNS_OUT_OF_RANGE; + length = doh[index]; + if((length & 0xc0) == 0xc0) { + int newpos; + /* name pointer, get the new offset (14 bits) */ + if((index + 1) >= dohlen) + return DOH_DNS_OUT_OF_RANGE; + + /* move to the new index */ + newpos = (length & 0x3f) << 8 | doh[index + 1]; + index = (unsigned int)newpos; + continue; + } + else if(length & 0xc0) + return DOH_DNS_BAD_LABEL; /* bad input */ + else + index++; + + if(length) { + if(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 doh_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 + - HTTPS (TYPE 65): N bytes */ + DOHcode rc; + + switch(type) { + case DNS_TYPE_A: + if(rdlength != 4) + return DOH_DNS_RDATA_LEN; + doh_store_a(doh, index, d); + break; + case DNS_TYPE_AAAA: + if(rdlength != 16) + return DOH_DNS_RDATA_LEN; + doh_store_aaaa(doh, index, d); + break; +#ifdef USE_HTTPSRR + case DNS_TYPE_HTTPS: + rc = doh_store_https(doh, index, d, rdlength); + if(rc) + return rc; + break; +#endif + case DNS_TYPE_CNAME: + rc = doh_store_cname(doh, dohlen, (unsigned int)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_resp_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 = doh_get16bit(doh, 4); + while(qdcount) { + rc = doh_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 = doh_get16bit(doh, 6); + while(ancount) { + unsigned short class; + unsigned int ttl; + + rc = doh_skipqname(doh, dohlen, &index); + if(rc) + return rc; /* bad qname */ + + if(dohlen < (index + 2)) + return DOH_DNS_OUT_OF_RANGE; + + type = doh_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 = doh_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 = doh_get32bit(doh, index); + if(ttl < d->ttl) + d->ttl = ttl; + index += 4; + + if(dohlen < (index + 2)) + return DOH_DNS_OUT_OF_RANGE; + + rdlength = doh_get16bit(doh, index); + index += 2; + if(dohlen < (index + rdlength)) + return DOH_DNS_OUT_OF_RANGE; + + rc = doh_rdata(doh, dohlen, rdlength, type, (int)index, d); + if(rc) + return rc; /* bad doh_rdata */ + index += rdlength; + ancount--; + } + + nscount = doh_get16bit(doh, 8); + while(nscount) { + rc = doh_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 = doh_get16bit(doh, index); + index += 2; + if(dohlen < (index + rdlength)) + return DOH_DNS_OUT_OF_RANGE; + index += rdlength; + nscount--; + } + + arcount = doh_get16bit(doh, 10); + while(arcount) { + rc = doh_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 = doh_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 */ + +#ifdef USE_HTTTPS + if((type != DNS_TYPE_NS) && !d->numcname && !d->numaddr && !d->numhttps_rrs) +#else + if((type != DNS_TYPE_NS) && !d->numcname && !d->numaddr) +#endif + /* nothing stored! */ + return DOH_NO_CONTENT; + + return DOH_OK; /* ok */ +} + +#ifndef CURL_DISABLE_VERBOSE_STRINGS +static void doh_show(struct Curl_easy *data, + const struct dohentry *d) +{ + int i; + infof(data, "[DoH] TTL: %u seconds", d->ttl); + for(i = 0; i < d->numaddr; i++) { + const struct dohaddr *a = &d->addr[i]; + if(a->type == 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; + len = msnprintf(buffer, 128, "[DoH] AAAA: "); + ptr = &buffer[len]; + len = sizeof(buffer) - len; + 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); + } + } +#ifdef USE_HTTPSRR + for(i = 0; i < d->numhttps_rrs; i++) { +# ifdef DEBUGBUILD + doh_print_buf(data, "DoH HTTPS", + d->https_rrs[i].val, d->https_rrs[i].len); +# else + infof(data, "DoH HTTPS RR: length %d", d->https_rrs[i].len); +# endif + } +#endif + for(i = 0; i < d->numcname; i++) { + infof(data, "CNAME: %s", Curl_dyn_ptr(&d->cname[i])); + } +} +#else +#define doh_show(x,y) +#endif + +/* + * doh2ai() + * + * This function returns a pointer to the first element of a newly allocated + * Curl_addrinfo struct linked list filled with the data from a set of DoH + * lookups. Curl_addrinfo is meant to work like the addrinfo struct does for + * 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 USE_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 USE_IPV6 + /* we cannot 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)); +#ifdef __MINGW32__ + addr->sin_family = (short)addrtype; +#else + addr->sin_family = addrtype; +#endif + addr->sin_port = htons((unsigned short)port); + break; + +#ifdef USE_IPV6 + case AF_INET6: + addr6 = (void *)ai->ai_addr; /* storage area for this info */ + DEBUGASSERT(sizeof(struct in6_addr) == sizeof(de->addr[i].ip.v6)); + memcpy(&addr6->sin6_addr, &de->addr[i].ip.v6, sizeof(struct in6_addr)); +#ifdef __MINGW32__ + addr6->sin6_family = (short)addrtype; +#else + addr6->sin6_family = addrtype; +#endif + 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 *doh_type2name(DNStype dnstype) +{ + switch(dnstype) { + case DNS_TYPE_A: + return "A"; + case DNS_TYPE_AAAA: + return "AAAA"; +#ifdef USE_HTTPSRR + case DNS_TYPE_HTTPS: + return "HTTPS"; +#endif + default: + return "unknown"; + } +} +#endif + +UNITTEST void de_cleanup(struct dohentry *d) +{ + int i = 0; + for(i = 0; i < d->numcname; i++) { + Curl_dyn_free(&d->cname[i]); + } +#ifdef USE_HTTPSRR + for(i = 0; i < d->numhttps_rrs; i++) + Curl_safefree(d->https_rrs[i].val); +#endif +} + +#ifdef USE_HTTPSRR + +/* + * @brief decode the DNS name in a binary RRData + * @param buf points to the buffer (in/out) + * @param remaining points to the remaining buffer length (in/out) + * @param dnsname returns the string form name on success + * @return is 1 for success, error otherwise + * + * The encoding here is defined in + * https://tools.ietf.org/html/rfc1035#section-3.1 + * + * The input buffer pointer will be modified so it points to + * just after the end of the DNS name encoding on output. (And + * that is why it is an "unsigned char **" :-) + */ +static CURLcode doh_decode_rdata_name(unsigned char **buf, size_t *remaining, + char **dnsname) +{ + unsigned char *cp = NULL; + int rem = 0; + unsigned char clen = 0; /* chunk len */ + struct dynbuf thename; + + DEBUGASSERT(buf && remaining && dnsname); + if(!buf || !remaining || !dnsname) + return CURLE_OUT_OF_MEMORY; + rem = (int)*remaining; + if(rem <= 0) { + Curl_dyn_free(&thename); + return CURLE_OUT_OF_MEMORY; + } + Curl_dyn_init(&thename, CURL_MAXLEN_host_name); + cp = *buf; + clen = *cp++; + if(clen == 0) { + /* special case - return "." as name */ + if(Curl_dyn_addn(&thename, ".", 1)) + return CURLE_OUT_OF_MEMORY; + } + while(clen) { + if(clen >= rem) { + Curl_dyn_free(&thename); + return CURLE_OUT_OF_MEMORY; + } + if(Curl_dyn_addn(&thename, cp, clen) || + Curl_dyn_addn(&thename, ".", 1)) + return CURLE_TOO_LARGE; + + cp += clen; + rem -= (clen + 1); + if(rem <= 0) { + Curl_dyn_free(&thename); + return CURLE_OUT_OF_MEMORY; + } + clen = *cp++; + } + *buf = cp; + *remaining = rem - 1; + *dnsname = Curl_dyn_ptr(&thename); + return CURLE_OK; +} + +static CURLcode doh_decode_rdata_alpn(unsigned char *rrval, size_t len, + char **alpns) +{ + /* + * spec here is as per draft-ietf-dnsop-svcb-https, section-7.1.1 + * encoding is catenated list of strings each preceded by a one + * octet length + * output is comma-sep list of the strings + * implementations may or may not handle quoting of comma within + * string values, so we might see a comma within the wire format + * version of a string, in which case we will precede that by a + * backslash - same goes for a backslash character, and of course + * we need to use two backslashes in strings when we mean one;-) + */ + int remaining = (int) len; + char *oval; + size_t i; + unsigned char *cp = rrval; + struct dynbuf dval; + + if(!alpns) + return CURLE_OUT_OF_MEMORY; + Curl_dyn_init(&dval, DYN_DOH_RESPONSE); + remaining = (int)len; + cp = rrval; + while(remaining > 0) { + size_t tlen = (size_t) *cp++; + + /* if not 1st time, add comma */ + if(remaining != (int)len && Curl_dyn_addn(&dval, ",", 1)) + goto err; + remaining--; + if(tlen > (size_t)remaining) + goto err; + /* add escape char if needed, clunky but easier to read */ + for(i = 0; i != tlen; i++) { + if('\\' == *cp || ',' == *cp) { + if(Curl_dyn_addn(&dval, "\\", 1)) + goto err; + } + if(Curl_dyn_addn(&dval, cp++, 1)) + goto err; + } + remaining -= (int)tlen; + } + /* this string is always null terminated */ + oval = Curl_dyn_ptr(&dval); + if(!oval) + goto err; + *alpns = oval; + return CURLE_OK; +err: + Curl_dyn_free(&dval); + return CURLE_BAD_CONTENT_ENCODING; +} + +#ifdef DEBUGBUILD +static CURLcode doh_test_alpn_escapes(void) +{ + /* we will use an example from draft-ietf-dnsop-svcb, figure 10 */ + static unsigned char example[] = { + 0x08, /* length 8 */ + 0x66, 0x5c, 0x6f, 0x6f, 0x2c, 0x62, 0x61, 0x72, /* value "f\\oo,bar" */ + 0x02, /* length 2 */ + 0x68, 0x32 /* value "h2" */ + }; + size_t example_len = sizeof(example); + char *aval = NULL; + static const char *expected = "f\\\\oo\\,bar,h2"; + + if(doh_decode_rdata_alpn(example, example_len, &aval) != CURLE_OK) + return CURLE_BAD_CONTENT_ENCODING; + if(strlen(aval) != strlen(expected)) + return CURLE_BAD_CONTENT_ENCODING; + if(memcmp(aval, expected, strlen(aval))) + return CURLE_BAD_CONTENT_ENCODING; + return CURLE_OK; +} +#endif + +static CURLcode doh_resp_decode_httpsrr(unsigned char *rrval, size_t len, + struct Curl_https_rrinfo **hrr) +{ + size_t remaining = len; + unsigned char *cp = rrval; + uint16_t pcode = 0, plen = 0; + struct Curl_https_rrinfo *lhrr = NULL; + char *dnsname = NULL; + +#ifdef DEBUGBUILD + /* a few tests of escaping, should not be here but ok for now */ + if(doh_test_alpn_escapes() != CURLE_OK) + return CURLE_OUT_OF_MEMORY; +#endif + lhrr = calloc(1, sizeof(struct Curl_https_rrinfo)); + if(!lhrr) + return CURLE_OUT_OF_MEMORY; + lhrr->val = Curl_memdup(rrval, len); + if(!lhrr->val) + goto err; + lhrr->len = len; + if(remaining <= 2) + goto err; + lhrr->priority = (uint16_t)((cp[0] << 8) + cp[1]); + cp += 2; + remaining -= (uint16_t)2; + if(doh_decode_rdata_name(&cp, &remaining, &dnsname) != CURLE_OK) + goto err; + lhrr->target = dnsname; + while(remaining >= 4) { + pcode = (uint16_t)((*cp << 8) + (*(cp + 1))); + cp += 2; + plen = (uint16_t)((*cp << 8) + (*(cp + 1))); + cp += 2; + remaining -= 4; + if(pcode == HTTPS_RR_CODE_ALPN) { + if(doh_decode_rdata_alpn(cp, plen, &lhrr->alpns) != CURLE_OK) + goto err; + } + if(pcode == HTTPS_RR_CODE_NO_DEF_ALPN) + lhrr->no_def_alpn = TRUE; + else if(pcode == HTTPS_RR_CODE_IPV4) { + if(!plen) + goto err; + lhrr->ipv4hints = Curl_memdup(cp, plen); + if(!lhrr->ipv4hints) + goto err; + lhrr->ipv4hints_len = (size_t)plen; + } + else if(pcode == HTTPS_RR_CODE_ECH) { + if(!plen) + goto err; + lhrr->echconfiglist = Curl_memdup(cp, plen); + if(!lhrr->echconfiglist) + goto err; + lhrr->echconfiglist_len = (size_t)plen; + } + else if(pcode == HTTPS_RR_CODE_IPV6) { + if(!plen) + goto err; + lhrr->ipv6hints = Curl_memdup(cp, plen); + if(!lhrr->ipv6hints) + goto err; + lhrr->ipv6hints_len = (size_t)plen; + } + if(plen > 0 && plen <= remaining) { + cp += plen; + remaining -= plen; + } + } + DEBUGASSERT(!remaining); + *hrr = lhrr; + return CURLE_OK; +err: + if(lhrr) { + Curl_safefree(lhrr->target); + Curl_safefree(lhrr->echconfiglist); + Curl_safefree(lhrr->val); + Curl_safefree(lhrr->alpns); + Curl_safefree(lhrr); + } + return CURLE_OUT_OF_MEMORY; +} + +# ifdef DEBUGBUILD +static void doh_print_httpsrr(struct Curl_easy *data, + struct Curl_https_rrinfo *hrr) +{ + DEBUGASSERT(hrr); + infof(data, "HTTPS RR: priority %d, target: %s", + hrr->priority, hrr->target); + if(hrr->alpns) + infof(data, "HTTPS RR: alpns %s", hrr->alpns); + else + infof(data, "HTTPS RR: no alpns"); + if(hrr->no_def_alpn) + infof(data, "HTTPS RR: no_def_alpn set"); + else + infof(data, "HTTPS RR: no_def_alpn not set"); + if(hrr->ipv4hints) { + doh_print_buf(data, "HTTPS RR: ipv4hints", + hrr->ipv4hints, hrr->ipv4hints_len); + } + else + infof(data, "HTTPS RR: no ipv4hints"); + if(hrr->echconfiglist) { + doh_print_buf(data, "HTTPS RR: ECHConfigList", + hrr->echconfiglist, hrr->echconfiglist_len); + } + else + infof(data, "HTTPS RR: no ECHConfigList"); + if(hrr->ipv6hints) { + doh_print_buf(data, "HTTPS RR: ipv6hint", + hrr->ipv6hints, hrr->ipv6hints_len); + } + else + infof(data, "HTTPS RR: no ipv6hints"); + return; +} +# endif +#endif + +CURLcode Curl_doh_is_resolved(struct Curl_easy *data, + struct Curl_dns_entry **dnsp) +{ + CURLcode result; + struct doh_probes *dohp = data->req.doh; + *dnsp = NULL; /* defaults to no response */ + if(!dohp) + return CURLE_OUT_OF_MEMORY; + + if(dohp->probe[DOH_SLOT_IPV4].easy_mid < 0 && + dohp->probe[DOH_SLOT_IPV6].easy_mid < 0) { + failf(data, "Could not DoH-resolve: %s", data->state.async.hostname); + return CONN_IS_PROXIED(data->conn) ? CURLE_COULDNT_RESOLVE_PROXY : + CURLE_COULDNT_RESOLVE_HOST; + } + else if(!dohp->pending) { + DOHcode rc[DOH_SLOT_COUNT]; + struct dohentry de; + int slot; + + memset(rc, 0, sizeof(rc)); + /* remove DoH handles from multi handle and close them */ + Curl_doh_close(data); + /* parse the responses, create the struct and return it! */ + de_init(&de); + for(slot = 0; slot < DOH_SLOT_COUNT; slot++) { + struct doh_probe *p = &dohp->probe[slot]; + if(!p->dnstype) + continue; + rc[slot] = doh_resp_decode(Curl_dyn_uptr(&p->resp_body), + Curl_dyn_len(&p->resp_body), + p->dnstype, &de); + Curl_dyn_free(&p->resp_body); +#ifndef CURL_DISABLE_VERBOSE_STRINGS + if(rc[slot]) { + infof(data, "DoH: %s type %s for %s", doh_strerror(rc[slot]), + doh_type2name(p->dnstype), dohp->host); + } +#endif + } /* next slot */ + + result = CURLE_COULDNT_RESOLVE_HOST; /* until we know better */ + if(!rc[DOH_SLOT_IPV4] || !rc[DOH_SLOT_IPV6]) { + /* we have an address, of one kind or other */ + struct Curl_dns_entry *dns; + struct Curl_addrinfo *ai; + + + if(Curl_trc_ft_is_verbose(data, &Curl_doh_trc)) { + infof(data, "[DoH] hostname: %s", dohp->host); + doh_show(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, FALSE); + + if(data->share) + Curl_share_unlock(data, CURL_LOCK_DATA_DNS); + + if(!dns) { + /* returned failure, bail out nicely */ + Curl_freeaddrinfo(ai); + } + else { + data->state.async.dns = dns; + *dnsp = dns; + result = CURLE_OK; /* address resolution OK */ + } + } /* address processing done */ + + /* Now process any build-specific attributes retrieved from DNS */ +#ifdef USE_HTTPSRR + if(de.numhttps_rrs > 0 && result == CURLE_OK && *dnsp) { + struct Curl_https_rrinfo *hrr = NULL; + result = doh_resp_decode_httpsrr(de.https_rrs->val, de.https_rrs->len, + &hrr); + if(result) { + infof(data, "Failed to decode HTTPS RR"); + return result; + } + infof(data, "Some HTTPS RR to process"); +# ifdef DEBUGBUILD + doh_print_httpsrr(data, hrr); +# endif + (*dnsp)->hinfo = hrr; + } +#endif + + /* All done */ + de_cleanup(&de); + Curl_doh_cleanup(data); + return result; + + } /* !dohp->pending */ + + /* else wait for pending DoH transactions to complete */ + return CURLE_OK; +} + +void Curl_doh_close(struct Curl_easy *data) +{ + struct doh_probes *doh = data->req.doh; + if(doh && data->multi) { + struct Curl_easy *probe_data; + curl_off_t mid; + size_t slot; + for(slot = 0; slot < DOH_SLOT_COUNT; slot++) { + mid = doh->probe[slot].easy_mid; + if(mid < 0) + continue; + doh->probe[slot].easy_mid = -1; + /* should have been called before data is removed from multi handle */ + DEBUGASSERT(data->multi); + probe_data = data->multi ? Curl_multi_get_handle(data->multi, mid) : + NULL; + if(!probe_data) { + DEBUGF(infof(data, "Curl_doh_close: xfer for mid=%" + FMT_OFF_T " not found!", + doh->probe[slot].easy_mid)); + continue; + } + /* data->multi might already be reset at this time */ + curl_multi_remove_handle(data->multi, probe_data); + Curl_close(&probe_data); + } + } +} + +void Curl_doh_cleanup(struct Curl_easy *data) +{ + struct doh_probes *doh = data->req.doh; + if(doh) { + Curl_doh_close(data); + curl_slist_free_all(doh->req_hds); + data->req.doh->req_hds = NULL; + Curl_safefree(data->req.doh); + } +} + +#endif /* CURL_DISABLE_DOH */ diff --git a/contrib/curl/lib/doh.h b/contrib/curl/lib/doh.h new file mode 100644 index 0000000000..aae32a6540 --- /dev/null +++ b/contrib/curl/lib/doh.h @@ -0,0 +1,189 @@ +#ifndef HEADER_CURL_DOH_H +#define HEADER_CURL_DOH_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "urldata.h" +#include "curl_addrinfo.h" +#ifdef USE_HTTPSRR +# include +#endif + +#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 */ + DNS_TYPE_HTTPS = 65 +} DNStype; + +/* one of these for each DoH request */ +struct doh_probe { + curl_off_t easy_mid; /* multi id of easy handle doing the lookup */ + DNStype dnstype; + unsigned char req_body[512]; + size_t req_body_len; + struct dynbuf resp_body; +}; + +enum doh_slot_num { + /* Explicit values for first two symbols so as to match hard-coded + * constants in existing code + */ + DOH_SLOT_IPV4 = 0, /* make 'V4' stand out for readability */ + DOH_SLOT_IPV6 = 1, /* 'V6' likewise */ + + /* Space here for (possibly build-specific) additional slot definitions */ +#ifdef USE_HTTPSRR + DOH_SLOT_HTTPS_RR = 2, /* for HTTPS RR */ +#endif + + /* for example */ + /* #ifdef WANT_DOH_FOOBAR_TXT */ + /* DOH_PROBE_SLOT_FOOBAR_TXT, */ + /* #endif */ + + /* AFTER all slot definitions, establish how many we have */ + DOH_SLOT_COUNT +}; + +struct doh_probes { + struct curl_slist *req_hds; + struct doh_probe probe[DOH_SLOT_COUNT]; + unsigned int pending; /* still outstanding probes */ + 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); + +#define DOH_MAX_ADDR 24 +#define DOH_MAX_CNAME 4 +#define DOH_MAX_HTTPS 4 + +struct dohaddr { + int type; + union { + unsigned char v4[4]; /* network byte order */ + unsigned char v6[16]; + } ip; +}; + +#ifdef USE_HTTPSRR + +/* + * These are the code points for DNS wire format SvcParams as + * per draft-ietf-dnsop-svcb-https + * Not all are supported now, and even those that are may need + * more work in future to fully support the spec. + */ +#define HTTPS_RR_CODE_ALPN 0x01 +#define HTTPS_RR_CODE_NO_DEF_ALPN 0x02 +#define HTTPS_RR_CODE_PORT 0x03 +#define HTTPS_RR_CODE_IPV4 0x04 +#define HTTPS_RR_CODE_ECH 0x05 +#define HTTPS_RR_CODE_IPV6 0x06 + +/* + * These may need escaping when found within an ALPN string + * value. + */ +#define COMMA_CHAR ',' +#define BACKSLASH_CHAR '\\' + +struct dohhttps_rr { + uint16_t len; /* raw encoded length */ + unsigned char *val; /* raw encoded octets */ +}; +#endif + +struct dohentry { + struct dynbuf cname[DOH_MAX_CNAME]; + struct dohaddr addr[DOH_MAX_ADDR]; + int numaddr; + unsigned int ttl; + int numcname; +#ifdef USE_HTTPSRR + struct dohhttps_rr https_rrs[DOH_MAX_HTTPS]; + int numhttps_rrs; +#endif +}; + +void Curl_doh_close(struct Curl_easy *data); +void Curl_doh_cleanup(struct Curl_easy *data); + +#ifdef UNITTESTS +UNITTEST DOHcode doh_req_encode(const char *host, + DNStype dnstype, + unsigned char *dnsp, /* buffer */ + size_t len, /* buffer size */ + size_t *olen); /* output length */ +UNITTEST DOHcode doh_resp_decode(const unsigned char *doh, + size_t dohlen, + DNStype dnstype, + struct dohentry *d); + +UNITTEST void de_init(struct dohentry *d); +UNITTEST void de_cleanup(struct dohentry *d); +#endif + +extern struct curl_trc_feat Curl_doh_trc; + +#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/contrib/curl/lib/dotdot.c b/contrib/curl/lib/dotdot.c deleted file mode 100644 index ea7c8a04f6..0000000000 --- a/contrib/curl/lib/dotdot.c +++ /dev/null @@ -1,179 +0,0 @@ -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) 1998 - 2016, Daniel Stenberg, , et al. - * - * This software is licensed as described in the file COPYING, which - * you should have received as part of this distribution. The terms - * are also available at https://curl.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. - * - ***************************************************************************/ - -#include "curl_setup.h" - -#include - -#include "dotdot.h" -#include "curl_memory.h" - -/* The last #include file should be: */ -#include "memdebug.h" - -/* - * "Remove Dot Segments" - * https://tools.ietf.org/html/rfc3986#section-5.2.4 - */ - -/* - * Curl_dedotdotify() - * @unittest: 1395 - * - * This function gets a zero-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 - * - * an allocated dedotdotified output string - */ -char *Curl_dedotdotify(const char *input) -{ - size_t inlen = strlen(input); - char *clone; - size_t clen = inlen; /* the length of the cloned input */ - char *out = malloc(inlen+1); - char *outptr; - char *orgclone; - char *queryp; - if(!out) - return NULL; /* out of memory */ - - /* get a cloned copy of the input */ - clone = strdup(input); - if(!clone) { - free(out); - return NULL; - } - orgclone = clone; - outptr = out; - - if(!*clone) { - /* zero length string, return that */ - free(out); - return clone; - } - - /* - * To handle query-parts properly, we must find it and remove it during the - * dotdot-operation and then append it again at the end to the output - * string. - */ - queryp = strchr(clone, '?'); - if(queryp) - *queryp = 0; - - do { - - /* A. If the input buffer begins with a prefix of "../" or "./", then - remove that prefix from the input buffer; otherwise, */ - - if(!strncmp("./", clone, 2)) { - clone+=2; - clen-=2; - } - else if(!strncmp("../", clone, 3)) { - clone+=3; - clen-=3; - } - - /* 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, */ - else if(!strncmp("/./", clone, 3)) { - clone+=2; - clen-=2; - } - else if(!strcmp("/.", clone)) { - clone[1]='/'; - clone++; - clen-=1; - } - - /* 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("/../", clone, 4)) { - clone+=3; - clen-=3; - /* remove the last segment from the output buffer */ - while(outptr > out) { - outptr--; - if(*outptr == '/') - break; - } - *outptr = 0; /* zero-terminate where it stops */ - } - else if(!strcmp("/..", clone)) { - clone[2]='/'; - clone+=2; - clen-=2; - /* remove the last segment from the output buffer */ - while(outptr > out) { - outptr--; - if(*outptr == '/') - break; - } - *outptr = 0; /* zero-terminate where it stops */ - } - - /* D. if the input buffer consists only of "." or "..", then remove - that from the input buffer; otherwise, */ - - else if(!strcmp(".", clone) || !strcmp("..", clone)) { - *clone=0; - } - - else { - /* 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++ = *clone++; - clen--; - } while(*clone && (*clone != '/')); - *outptr = 0; - } - - } while(*clone); - - if(queryp) { - size_t qlen; - /* There was a query part, append that to the output. The 'clone' string - may now have been altered so we copy from the original input string - from the correct index. */ - size_t oindex = queryp - orgclone; - qlen = strlen(&input[oindex]); - memcpy(outptr, &input[oindex], qlen+1); /* include the ending zero byte */ - } - - free(orgclone); - return out; -} diff --git a/contrib/curl/lib/dynbuf.c b/contrib/curl/lib/dynbuf.c new file mode 100644 index 0000000000..eab07efbf0 --- /dev/null +++ b/contrib/curl/lib/dynbuf.c @@ -0,0 +1,282 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" +#include "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 does not 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 is 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_TOO_LARGE; + } + 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 if(rc == MERR_TOO_LARGE) + return CURLE_TOO_LARGE; + return CURLE_OUT_OF_MEMORY; +#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); + return CURLE_OUT_OF_MEMORY; +#endif +} + +/* + * 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/contrib/curl/lib/dynbuf.h b/contrib/curl/lib/dynbuf.h new file mode 100644 index 0000000000..7dbaab886e --- /dev/null +++ b/contrib/curl/lib/dynbuf.h @@ -0,0 +1,93 @@ +#ifndef HEADER_CURL_DYNBUF_H +#define HEADER_CURL_DYNBUF_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include + +#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 CURL_PRINTF(2, 3); +CURLcode Curl_dyn_vaddf(struct dynbuf *s, const char *fmt, va_list ap) + WARN_UNUSED_RESULT CURL_PRINTF(2, 0); +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/contrib/curl/lib/dynhds.c b/contrib/curl/lib/dynhds.c new file mode 100644 index 0000000000..2c92ca63fe --- /dev/null +++ b/contrib/curl/lib/dynhds.c @@ -0,0 +1,396 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" +#include "dynhds.h" +#include "strcase.h" + +/* The last 3 #include files should be in this order */ +#ifdef USE_NGHTTP2 +#include +#include +#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 UNITTESTS +/* 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/contrib/curl/lib/dynhds.h b/contrib/curl/lib/dynhds.h new file mode 100644 index 0000000000..fb162a30de --- /dev/null +++ b/contrib/curl/lib/dynhds.h @@ -0,0 +1,187 @@ +#ifndef HEADER_CURL_DYNHDS_H +#define HEADER_CURL_DYNHDS_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ +#include "curl_setup.h" + +#include +#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); + +#ifdef UNITTESTS +/* used by unit2602.c */ + +/** + * 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); + +/** + * 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); +#endif + +CURLcode Curl_dynhds_cset(struct dynhds *dynhds, + const char *name, const char *value); + +/** + * 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); + +/** + * Add a single header from an 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 an 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 +#include + +nghttp2_nv *Curl_dynhds_to_nva(struct dynhds *dynhds, size_t *pcount); + +#endif /* USE_NGHTTP2 */ + +#endif /* HEADER_CURL_DYNHDS_H */ diff --git a/contrib/curl/lib/easy.c b/contrib/curl/lib/easy.c index bed94a4444..95442a771b 100644 --- a/contrib/curl/lib/easy.c +++ b/contrib/curl/lib/easy.c @@ -5,11 +5,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2016, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -18,18 +18,12 @@ * 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" -/* - * See comment in curl_memory.h for the explanation of this sanity check. - */ - -#ifdef CURLX_NO_MEMORY_CALLBACKS -#error "libcurl shall not ever be built with CURLX_NO_MEMORY_CALLBACKS defined" -#endif - #ifdef HAVE_NETINET_IN_H #include #endif @@ -61,92 +55,50 @@ #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 "non-ascii.h" +#include "macos.h" #include "warnless.h" -#include "conncache.h" -#include "multiif.h" #include "sigpipe.h" -#include "ssh.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" -void Curl_version_init(void); - -/* win32_cleanup() is for win32 socket cleanup functionality, the opposite - of win32_init() */ -static void win32_cleanup(void) -{ -#ifdef USE_WINSOCK - WSACleanup(); -#endif -#ifdef USE_WINDOWS_SSPI - Curl_sspi_global_cleanup(); -#endif -} - -/* win32_init() performs win32 socket initialization to properly setup the - stack to allow networking */ -static CURLcode win32_init(void) -{ -#ifdef USE_WINSOCK - WORD wVersionRequested; - WSADATA wsaData; - int res; - -#if defined(ENABLE_IPV6) && (USE_WINSOCK < 2) - Error IPV6_requires_winsock2 -#endif - - wVersionRequested = MAKEWORD(USE_WINSOCK, USE_WINSOCK); +/* true globals -- for curl_global_init() and curl_global_cleanup() */ +static unsigned int initialized; +static long easy_init_flags; - res = WSAStartup(wVersionRequested, &wsaData); +#ifdef GLOBAL_INIT_IS_THREADSAFE - if(res != 0) - /* Tell the user that we couldn't find a useable */ - /* winsock.dll. */ - return CURLE_FAILED_INIT; +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) - /* 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. */ +#else - if(LOBYTE(wsaData.wVersion) != LOBYTE(wVersionRequested) || - HIBYTE(wsaData.wVersion) != HIBYTE(wVersionRequested) ) { - /* Tell the user that we couldn't find a useable */ +#define global_init_lock() +#define global_init_unlock() - /* winsock.dll. */ - WSACleanup(); - return CURLE_FAILED_INIT; - } - /* The Windows Sockets DLL is acceptable. Proceed. */ -#elif defined(USE_LWIPSOCK) - lwip_init(); #endif -#ifdef USE_WINDOWS_SSPI - { - CURLcode result = Curl_sspi_global_init(); - if(result) - return result; - } -#endif - - return CURLE_OK; -} - -/* true globals -- for curl_global_init() and curl_global_cleanup() */ -static unsigned int initialized; -static long init_flags; - /* * strdup (and other memory functions) is redefined in complicated * ways, but at this point it must be defined as the system-supplied strdup @@ -155,16 +107,16 @@ static long init_flags; #if defined(_WIN32_WCE) #define system_strdup _strdup #elif !defined(HAVE_STRDUP) -#define system_strdup curlx_strdup +#define system_strdup Curl_strdup #else #define system_strdup strdup #endif -#if defined(_MSC_VER) && defined(_DLL) && !defined(__POCC__) +#if defined(_MSC_VER) && defined(_DLL) +# pragma warning(push) # pragma warning(disable:4232) /* MSVC extension, dllimport identity */ #endif -#ifndef __SYMBIAN32__ /* * If a memory-using function (like curl_getenv) is used before * curl_global_init() is called, we need to have these pointers set already. @@ -174,23 +126,16 @@ 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_callback)_wcsdup; +#if defined(_WIN32) && defined(UNICODE) +curl_wcsdup_callback Curl_cwcsdup = Curl_wcsdup; #endif -#else -/* - * Symbian OS doesn't support initialization to code in writeable static data. - * Initialization will occur in the curl_global_init() call. - */ -curl_malloc_callback Curl_cmalloc; -curl_free_callback Curl_cfree; -curl_realloc_callback Curl_crealloc; -curl_strdup_callback Curl_cstrdup; -curl_calloc_callback Curl_ccalloc; + +#if defined(_MSC_VER) && defined(_DLL) +# pragma warning(pop) #endif -#if defined(_MSC_VER) && defined(_DLL) && !defined(__POCC__) -# pragma warning(default:4232) /* MSVC extension, dllimport identity */ +#ifdef DEBUGBUILD +static char *leakpointer; #endif /** @@ -209,58 +154,59 @@ static CURLcode global_init(long flags, bool memoryfuncs) Curl_crealloc = (curl_realloc_callback)realloc; Curl_cstrdup = (curl_strdup_callback)system_strdup; Curl_ccalloc = (curl_calloc_callback)calloc; -#if defined(WIN32) && defined(UNICODE) +#if defined(_WIN32) && defined(UNICODE) Curl_cwcsdup = (curl_wcsdup_callback)_wcsdup; #endif } - if(flags & CURL_GLOBAL_SSL) - if(!Curl_ssl_init()) { - DEBUGF(fprintf(stderr, "Error: Curl_ssl_init failed\n")); - return CURLE_FAILED_INIT; - } + if(Curl_trc_init()) { + DEBUGF(fprintf(stderr, "Error: Curl_trc_init failed\n")); + goto fail; + } - if(flags & CURL_GLOBAL_WIN32) - if(win32_init()) { - DEBUGF(fprintf(stderr, "Error: win32_init failed\n")); - return CURLE_FAILED_INIT; - } + if(!Curl_ssl_init()) { + DEBUGF(fprintf(stderr, "Error: Curl_ssl_init failed\n")); + goto fail; + } -#ifdef __AMIGA__ - if(!Curl_amiga_init()) { + 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")); - return CURLE_FAILED_INIT; + goto fail; } -#endif -#ifdef NETWARE - if(netware_init()) { - DEBUGF(fprintf(stderr, "Warning: LONG namespace not available\n")); + if(Curl_macos_init()) { + DEBUGF(fprintf(stderr, "Error: Curl_macos_init failed\n")); + goto fail; } -#endif if(Curl_resolver_global_init()) { DEBUGF(fprintf(stderr, "Error: resolver_global_init failed\n")); - return CURLE_FAILED_INIT; + goto fail; } - (void)Curl_ipv6works(); - -#if defined(USE_LIBSSH2) && defined(HAVE_LIBSSH2_INIT) - if(libssh2_init(0)) { - DEBUGF(fprintf(stderr, "Error: libssh2_init failed\n")); - return CURLE_FAILED_INIT; + if(Curl_ssh_init()) { + DEBUGF(fprintf(stderr, "Error: Curl_ssh_init failed\n")); + goto fail; } -#endif - if(flags & CURL_GLOBAL_ACK_EINTR) - Curl_ack_eintr = 1; + easy_init_flags = flags; - init_flags = flags; - - Curl_version_init(); +#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; } @@ -270,7 +216,14 @@ static CURLcode global_init(long flags, bool memoryfuncs) */ CURLcode curl_global_init(long flags) { - return global_init(flags, TRUE); + CURLcode result; + global_init_lock(); + + result = global_init(flags, TRUE); + + global_init_unlock(); + + return result; } /* @@ -281,15 +234,20 @@ 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 + /* Already initialized, do not 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; } @@ -302,38 +260,87 @@ CURLcode curl_global_init_mem(long flags, curl_malloc_callback m, Curl_ccalloc = c; /* Call the actual init function, but without setting */ - return global_init(flags, FALSE); + result = global_init(flags, FALSE); + + global_init_unlock(); + + return result; } /** * curl_global_cleanup() globally cleanups curl, uses the value of - * "init_flags" to determine what needs to be cleaned up and what doesn't. + * "easy_init_flags" to determine what needs to be cleaned up and what does + * not. */ void curl_global_cleanup(void) { - if(!initialized) - return; + global_init_lock(); - if(--initialized) + if(!initialized) { + global_init_unlock(); return; + } - Curl_global_host_cache_dtor(); - - if(init_flags & CURL_GLOBAL_SSL) - Curl_ssl_cleanup(); + if(--initialized) { + global_init_unlock(); + return; + } + Curl_ssl_cleanup(); Curl_resolver_global_cleanup(); - if(init_flags & CURL_GLOBAL_WIN32) - win32_cleanup(); +#ifdef _WIN32 + Curl_win32_cleanup(easy_init_flags); +#endif Curl_amiga_cleanup(); -#if defined(USE_LIBSSH2) && defined(HAVE_LIBSSH2_EXIT) - (void)libssh2_exit(); + Curl_ssh_cleanup(); + +#ifdef DEBUGBUILD + free(leakpointer); #endif - init_flags = 0; + 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; } /* @@ -346,14 +353,18 @@ struct Curl_easy *curl_easy_init(void) struct Curl_easy *data; /* Make sure we inited the global SSL stuff */ + global_init_lock(); + if(!initialized) { - result = curl_global_init(CURL_GLOBAL_DEFAULT); + 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); @@ -365,29 +376,7 @@ struct Curl_easy *curl_easy_init(void) return data; } -/* - * curl_easy_setopt() is the external interface for setting options on an - * easy handle. - */ - -#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_setopt(data, tag, arg); - - va_end(arg); - return result; -} - -#ifdef CURLDEBUG +#ifdef DEBUGBUILD struct socketmonitor { struct socketmonitor *next; /* the next node in the list or NULL */ @@ -402,25 +391,22 @@ struct events { int running_handles; /* store the returned number */ }; +#define DEBUG_EV_POLL 0 + /* 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 */ - +#if DEBUG_EV_POLL + fprintf(stderr, "events_timer: set timeout %ldms\n", timeout_ms); +#endif ev->ms = timeout_ms; ev->msbump = TRUE; return 0; @@ -433,7 +419,7 @@ static int events_timer(struct Curl_multi *multi, /* multi handle */ */ static int poll2cselect(int pollmask) { - int omask=0; + int omask = 0; if(pollmask & POLLIN) omask |= CURL_CSELECT_IN; if(pollmask & POLLOUT) @@ -450,7 +436,7 @@ static int poll2cselect(int pollmask) */ static short socketcb2poll(int pollmask) { - short omask=0; + short omask = 0; if(pollmask & CURL_POLL_IN) omask |= POLLIN; if(pollmask & CURL_POLL_OUT) @@ -473,7 +459,8 @@ static int events_socket(struct Curl_easy *easy, /* easy handle */ { struct events *ev = userp; struct socketmonitor *m; - struct socketmonitor *prev=NULL; + struct socketmonitor *prev = NULL; + bool found = FALSE; #if defined(CURL_DISABLE_VERBOSE_STRINGS) (void) easy; @@ -483,7 +470,7 @@ static int events_socket(struct Curl_easy *easy, /* easy handle */ m = ev->list; while(m) { if(m->socket.fd == s) { - + found = TRUE; if(what == CURL_POLL_REMOVE) { struct socketmonitor *nxt = m->next; /* remove this node from the list of monitored sockets */ @@ -492,28 +479,29 @@ static int events_socket(struct Curl_easy *easy, /* easy handle */ else ev->list = nxt; free(m); - m = nxt; - infof(easy, "socket cb: socket %d REMOVED\n", s); + infof(easy, "socket cb: socket %" FMT_SOCKET_T " 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\n", s, - what&CURL_POLL_IN?"IN":"", - what&CURL_POLL_OUT?"OUT":""); + infof(easy, "socket cb: socket %" FMT_SOCKET_T + " 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(!found) { 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); */ + /* should not happen if our logic is correct, but is no drama. */ + DEBUGF(infof(easy, "socket cb: asked to REMOVE socket %" + FMT_SOCKET_T "but not present!", s)); + DEBUGASSERT(0); } else { m = malloc(sizeof(struct socketmonitor)); @@ -523,9 +511,9 @@ static int events_socket(struct Curl_easy *easy, /* easy handle */ m->socket.events = socketcb2poll(what); m->socket.revents = 0; ev->list = m; - infof(easy, "socket cb: socket %d ADDED as %s%s\n", s, - what&CURL_POLL_IN?"IN":"", - what&CURL_POLL_OUT?"OUT":""); + infof(easy, "socket cb: socket %" FMT_SOCKET_T " ADDED as %s%s", s, + (what&CURL_POLL_IN) ? "IN" : "", + (what&CURL_POLL_OUT) ? "OUT" : ""); } else return CURLE_OUT_OF_MEMORY; @@ -561,7 +549,7 @@ static void events_setup(struct Curl_multi *multi, struct events *ev) static CURLcode wait_or_timeout(struct Curl_multi *multi, struct events *ev) { bool done = FALSE; - CURLMcode mcode; + CURLMcode mcode = CURLM_OK; CURLcode result = CURLE_OK; while(!done) { @@ -569,66 +557,102 @@ static CURLcode wait_or_timeout(struct Curl_multi *multi, struct events *ev) struct socketmonitor *m; struct pollfd *f; struct pollfd fds[4]; - int numfds=0; + int numfds = 0; int pollrc; int i; - struct timeval before; - struct timeval after; + struct curltime before; /* populate the fds[] array */ - for(m = ev->list, f=&fds[0]; m; m = m->next) { + 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); */ +#if DEBUG_EV_POLL + fprintf(stderr, "poll() %d check socket %d\n", numfds, f->fd); +#endif f++; numfds++; } /* get the time stamp to use to figure out how long poll takes */ - before = curlx_tvnow(); - - /* wait for activity or timeout */ - pollrc = Curl_poll(fds, numfds, (int)ev->ms); + before = Curl_now(); - after = curlx_tvnow(); + if(numfds) { + /* wait for activity or timeout */ +#if DEBUG_EV_POLL + fprintf(stderr, "poll(numfds=%d, timeout=%ldms)\n", numfds, ev->ms); +#endif + pollrc = Curl_poll(fds, (unsigned int)numfds, ev->ms); +#if DEBUG_EV_POLL + fprintf(stderr, "poll(numfds=%d, timeout=%ldms) -> %d\n", + numfds, ev->ms, pollrc); +#endif + if(pollrc < 0) + return CURLE_UNRECOVERABLE_POLL; + } + else { +#if DEBUG_EV_POLL + fprintf(stderr, "poll, but no fds, wait timeout=%ldms\n", ev->ms); +#endif + pollrc = 0; + if(ev->ms > 0) + Curl_wait_ms(ev->ms); + } ev->msbump = FALSE; /* reset here */ - if(0 == pollrc) { + 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 if(pollrc > 0) { + else { + /* here pollrc is > 0 */ + struct Curl_llist_node *e = Curl_llist_head(&multi->process); + struct Curl_easy *data; + DEBUGASSERT(e); + data = Curl_node_elem(e); + DEBUGASSERT(data); + /* loop over the monitored sockets to see which ones had activity */ - for(i = 0; i< numfds; i++) { + 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)\n", - fds[i].fd); + + /* sending infof "randomly" to the first easy handle */ + infof(data, "call curl_multi_socket_action(socket " + "%" FMT_SOCKET_T ")", (curl_socket_t)fds[i].fd); mcode = curl_multi_socket_action(multi, fds[i].fd, act, &ev->running_handles); } } - if(!ev->msbump) + + if(!ev->msbump && ev->ms >= 0) { /* If nothing updated the timeout, we decrease it by the spent time. * If it was updated, it has the new timeout time stored already. */ - ev->ms += curlx_tvdiff(after, before); - + timediff_t timediff = Curl_timediff(Curl_now(), before); + if(timediff > 0) { +#if DEBUG_EV_POLL + fprintf(stderr, "poll timeout %ldms not updated, decrease by " + "time spent %ldms\n", ev->ms, (long)timediff); +#endif + if(timediff > ev->ms) + ev->ms = 0; + else + ev->ms -= (long)timediff; + } + } } - else - return CURLE_RECV_ERROR; if(mcode) - return CURLE_URL_MALFORMAT; /* TODO: return a proper error! */ + return CURLE_URL_MALFORMAT; - /* we don't really care about the "msgs_in_queue" value returned in the + /* we do not really care about the "msgs_in_queue" value returned in the second argument */ msg = curl_multi_info_read(multi, &pollrc); if(msg) { @@ -647,15 +671,17 @@ static CURLcode wait_or_timeout(struct Curl_multi *multi, struct events *ev) */ static CURLcode easy_events(struct Curl_multi *multi) { - struct events evs= {2, FALSE, 0, NULL, 0}; + /* 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 = {-1, 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 */ +#else /* DEBUGBUILD */ +/* when not built with debug, this function does not exist */ #define easy_events(x) CURLE_NOT_BUILT_IN #endif @@ -664,44 +690,18 @@ static CURLcode easy_transfer(struct Curl_multi *multi) bool done = FALSE; CURLMcode mcode = CURLM_OK; CURLcode result = CURLE_OK; - struct timeval before; - int without_fds = 0; /* count number of consecutive returns from - curl_multi_wait() without any filedescriptors */ while(!done && !mcode) { int still_running = 0; - int rc; - - before = curlx_tvnow(); - mcode = curl_multi_wait(multi, NULL, 0, 1000, &rc); - - if(!mcode) { - if(!rc) { - struct timeval after = curlx_tvnow(); - - /* If it returns without any filedescriptor instantly, we need to - avoid busy-looping during periods where it has nothing particular - to wait for */ - if(curlx_tvdiff(after, before) <= 10) { - without_fds++; - if(without_fds > 2) { - int sleep_ms = without_fds < 10 ? (1 << (without_fds - 1)) : 1000; - Curl_wait_ms(sleep_ms); - } - } - else - /* it wasn't "instant", restart counter */ - without_fds = 0; - } - else - /* got file descriptor, restart counter */ - without_fds = 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; @@ -713,9 +713,9 @@ static CURLcode easy_transfer(struct Curl_multi *multi) /* 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; + /* The other multi errors should never happen, so return + something suitably generic */ + CURLE_BAD_FUNCTION_ARGUMENT; } return result; @@ -731,9 +731,9 @@ static CURLcode easy_transfer(struct Curl_multi *multi) * 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 + * REALITY: it cannot 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 re-used so that the same pools and + * 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 @@ -749,6 +749,12 @@ static CURLcode easy_perform(struct Curl_easy *data, bool events) if(!data) return CURLE_BAD_FUNCTION_ARGUMENT; + if(data->set.errorbuffer) + /* clear this as early as possible */ + data->set.errorbuffer[0] = 0; + + data->state.os_errno = 0; + if(data->multi) { failf(data, "easy handle already used in multi handle"); return CURLE_FAILED_INIT; @@ -759,34 +765,36 @@ static CURLcode easy_perform(struct Curl_easy *data, bool events) 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); + 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, data->set.maxconnects); + curl_multi_setopt(multi, CURLMOPT_MAXCONNECTS, (long)data->set.maxconnects); + data->multi_easy = NULL; /* pretend it does not exist */ mcode = curl_multi_add_handle(multi, data); if(mcode) { curl_multi_cleanup(multi); if(mcode == CURLM_OUT_OF_MEMORY) return CURLE_OUT_OF_MEMORY; - else - return CURLE_FAILED_INIT; + return CURLE_FAILED_INIT; } - sigpipe_ignore(data, &pipe_st); + /* assign this after curl_multi_add_handle() */ + data->multi_easy = multi; - /* assign this after curl_multi_add_handle() since that function checks for - it and rejects this handle otherwise */ - data->multi = multi; + sigpipe_init(&pipe_st); + sigpipe_apply(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 + /* ignoring the return code is not nice, but atm we cannot really handle a failure here, room for future improvement! */ (void)curl_multi_remove_handle(multi, data); @@ -806,7 +814,7 @@ CURLcode curl_easy_perform(struct Curl_easy *data) return easy_perform(data, FALSE); } -#ifdef CURLDEBUG +#ifdef DEBUGBUILD /* * curl_easy_perform_ev() is the external interface that performs a blocking * transfer using the event-based API internally. @@ -824,14 +832,12 @@ CURLcode curl_easy_perform_ev(struct Curl_easy *data) */ void curl_easy_cleanup(struct Curl_easy *data) { - SIGPIPE_VARIABLE(pipe_st); - - if(!data) - return; - - sigpipe_ignore(data, &pipe_st); - Curl_close(data); - sigpipe_restore(&pipe_st); + if(GOOD_EASY_HANDLE(data)) { + SIGPIPE_VARIABLE(pipe_st); + sigpipe_ignore(data, &pipe_st); + Curl_close(&data); + sigpipe_restore(&pipe_st); + } } /* @@ -854,6 +860,60 @@ CURLcode curl_easy_getinfo(struct Curl_easy *data, CURLINFO info, ...) 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 @@ -862,7 +922,7 @@ CURLcode curl_easy_getinfo(struct Curl_easy *data, CURLINFO info, ...) struct Curl_easy *curl_easy_duphandle(struct Curl_easy *data) { struct Curl_easy *outcurl = calloc(1, sizeof(struct Curl_easy)); - if(NULL == outcurl) + if(!outcurl) goto fail; /* @@ -871,67 +931,113 @@ struct Curl_easy *curl_easy_duphandle(struct Curl_easy *data) * the likeliness of us forgetting to init a buffer here in the future. */ outcurl->set.buffer_size = data->set.buffer_size; - outcurl->state.buffer = malloc(CURL_BUFSIZE(outcurl->set.buffer_size) + 1); - if(!outcurl->state.buffer) - goto fail; - - outcurl->state.headerbuff = malloc(HEADERSIZE); - if(!outcurl->state.headerbuff) - goto fail; - outcurl->state.headersize = HEADERSIZE; /* copy all userdefined values */ - if(Curl_dupset(outcurl, data)) + if(dupset(outcurl, data)) goto fail; - /* the connection cache is setup on demand */ - outcurl->state.conn_cache = NULL; + Curl_dyn_init(&outcurl->state.headerb, CURL_MAX_HTTP_HEADER); - outcurl->state.lastconnect = NULL; + /* the connection pool is setup on demand */ + 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; - if(data->cookies) { +#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(data, - data->cookies->filename, - outcurl->cookies, + outcurl->cookies = Curl_cookie_init(outcurl, NULL, outcurl->cookies, data->set.cookiesession); if(!outcurl->cookies) goto fail; } - /* duplicate all values in 'change' */ - if(data->change.cookielist) { - outcurl->change.cookielist = - Curl_slist_duplicate(data->change.cookielist); - if(!outcurl->change.cookielist) + if(data->state.cookielist) { + outcurl->state.cookielist = Curl_slist_duplicate(data->state.cookielist); + if(!outcurl->state.cookielist) goto fail; } +#endif - if(data->change.url) { - outcurl->change.url = strdup(data->change.url); - if(!outcurl->change.url) + if(data->state.url) { + outcurl->state.url = strdup(data->state.url); + if(!outcurl->state.url) goto fail; - outcurl->change.url_alloc = TRUE; + outcurl->state.url_alloc = TRUE; } - if(data->change.referer) { - outcurl->change.referer = strdup(data->change.referer); - if(!outcurl->change.referer) + if(data->state.referer) { + outcurl->state.referer = strdup(data->state.referer); + if(!outcurl->state.referer) goto fail; - outcurl->change.referer_alloc = TRUE; + 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 + +#ifdef CURLRES_ASYNCH /* Clone the resolver handle, if present, for the new handle */ - if(Curl_resolver_duphandle(&outcurl->state.resolver, - data->state.resolver)) + if(Curl_resolver_duphandle(outcurl, + &outcurl->state.async.resolver, + data->state.async.resolver)) goto fail; +#endif + +#ifdef USE_ARES + { + CURLcode rc; + + rc = Curl_set_dns_servers(outcurl, data->set.str[STRING_DNS_SERVERS]); + if(rc && rc != CURLE_NOT_BUILT_IN) + goto fail; - Curl_convert_setup(outcurl); + rc = Curl_set_dns_interface(outcurl, data->set.str[STRING_DNS_INTERFACE]); + if(rc && rc != CURLE_NOT_BUILT_IN) + goto fail; + rc = Curl_set_dns_local_ip4(outcurl, data->set.str[STRING_DNS_LOCAL_IP4]); + if(rc && rc != CURLE_NOT_BUILT_IN) + goto fail; + + rc = Curl_set_dns_local_ip6(outcurl, data->set.str[STRING_DNS_LOCAL_IP6]); + if(rc && rc != CURLE_NOT_BUILT_IN) + goto fail; + } +#endif /* USE_ARES */ +#ifndef CURL_DISABLE_HTTP + Curl_llist_init(&outcurl->state.httphdrs, NULL); +#endif Curl_initinfo(outcurl); outcurl->magic = CURLEASY_MAGIC_NUMBER; @@ -940,15 +1046,15 @@ struct Curl_easy *curl_easy_duphandle(struct Curl_easy *data) return outcurl; - fail: +fail: if(outcurl) { - curl_slist_free_all(outcurl->change.cookielist); - outcurl->change.cookielist = NULL; - Curl_safefree(outcurl->state.buffer); - Curl_safefree(outcurl->state.headerbuff); - Curl_safefree(outcurl->change.url); - Curl_safefree(outcurl->change.referer); +#ifndef CURL_DISABLE_COOKIES + free(outcurl->cookies); +#endif + Curl_dyn_free(&outcurl->state.headerb); + Curl_altsvc_cleanup(&outcurl->asi); + Curl_hsts_cleanup(&outcurl->hsts); Curl_freeset(outcurl); free(outcurl); } @@ -962,16 +1068,12 @@ struct Curl_easy *curl_easy_duphandle(struct Curl_easy *data) */ void curl_easy_reset(struct Curl_easy *data) { - Curl_safefree(data->state.pathbuffer); - - data->state.path = NULL; - - Curl_free_request_state(data); + Curl_req_hard_reset(&data->req, data); /* zero out UserDefined data: */ Curl_freeset(data); memset(&data->set, 0, sizeof(struct UserDefined)); - (void)Curl_init_userdefined(&data->set); + (void)Curl_init_userdefined(data); /* zero out Progress data: */ memset(&data->progress, 0, sizeof(struct Progress)); @@ -981,6 +1083,15 @@ void curl_easy_reset(struct Curl_easy *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 } /* @@ -992,64 +1103,109 @@ void curl_easy_reset(struct Curl_easy *data) * 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 = &data->req; + struct SingleRequest *k; CURLcode result = CURLE_OK; + int oldstate; + int newstate; + bool recursive = FALSE; + bool keep_changed, unpause_read, not_all_paused; - /* first switch off both pause bits */ - int newstate = k->keepon &~ (KEEP_RECV_PAUSE| KEEP_SEND_PAUSE); - - /* set the new desired pause bits */ - newstate |= ((action & CURLPAUSE_RECV)?KEEP_RECV_PAUSE:0) | - ((action & CURLPAUSE_SEND)?KEEP_SEND_PAUSE:0); + if(!GOOD_EASY_HANDLE(data) || !data->conn) + /* crazy input, do not continue */ + return CURLE_BAD_FUNCTION_ARGUMENT; - /* put it back in the keepon */ + 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); + + keep_changed = ((newstate & (KEEP_RECV_PAUSE| KEEP_SEND_PAUSE)) != oldstate); + not_all_paused = (newstate & (KEEP_RECV_PAUSE|KEEP_SEND_PAUSE)) != + (KEEP_RECV_PAUSE|KEEP_SEND_PAUSE); + unpause_read = ((k->keepon & ~newstate & KEEP_SEND_PAUSE) && + (data->mstate == MSTATE_PERFORMING || + data->mstate == MSTATE_RATELIMITING)); + /* Unpausing writes is detected on the next run in + * transfer.c:Curl_sendrecv(). This is because this may result + * in a transfer error if the application's callbacks fail */ + + /* Set the new keepon state, so it takes effect no matter what error + * may happen afterwards. */ k->keepon = newstate; - if(!(newstate & KEEP_RECV_PAUSE) && data->state.tempwrite) { - /* we have a buffer for sending that we now seem to be able to deliver - since the receive pausing is lifted! */ + /* If not completely pausing both directions now, run again in any case. */ + if(not_all_paused) { + Curl_expire(data, 0, EXPIRE_RUN_NOW); + /* reset the too-slow time keeper */ + data->state.keeps_speed.tv_sec = 0; + /* Simulate socket events on next run for unpaused directions */ + if(!(newstate & KEEP_SEND_PAUSE)) + data->state.select_bits |= CURL_CSELECT_OUT; + if(!(newstate & KEEP_RECV_PAUSE)) + data->state.select_bits |= CURL_CSELECT_IN; + /* On changes, tell application to update its timers. */ + if(keep_changed && data->multi) { + if(Curl_update_timer(data->multi)) { + result = CURLE_ABORTED_BY_CALLBACK; + goto out; + } + } + } - /* get the pointer in local copy since the function may return PAUSE - again and then we'll get a new copy allocted and stored in - the tempwrite variables */ - char *tempwrite = data->state.tempwrite; + if(unpause_read) { + result = Curl_creader_unpause(data); + if(result) + goto out; + } - data->state.tempwrite = NULL; - result = Curl_client_chop_write(data->easy_conn, data->state.tempwritetype, - tempwrite, data->state.tempwritesize); - free(tempwrite); + if(!(k->keepon & KEEP_RECV_PAUSE) && Curl_cwriter_is_paused(data)) { + Curl_conn_ev_data_pause(data, FALSE); + result = Curl_cwriter_unpause(data); } - /* if there's no error and we're not pausing both directions, we want - to have this handle checked soon */ - if(!result && - ((newstate&(KEEP_RECV_PAUSE|KEEP_SEND_PAUSE)) != - (KEEP_RECV_PAUSE|KEEP_SEND_PAUSE)) ) - Curl_expire(data, 0); /* get this handle going again */ +out: + if(!result && !data->state.done && keep_changed) + /* This transfer may have been moved in or out of the bundle, update the + corresponding socket callback, if used */ + result = Curl_updatesocket(data); + + 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 == NULL) + curl_socket_t sfd; + + 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!"); + failf(data, "CONNECT_ONLY is required"); return CURLE_UNSUPPORTED_PROTOCOL; } - *sfd = Curl_getconnectinfo(data, connp); + sfd = Curl_getconnectinfo(data, connp); - if(*sfd == CURL_SOCKET_BAD) { + if(sfd == CURL_SOCKET_BAD) { failf(data, "Failed to get recent socket"); return CURLE_UNSUPPORTED_PROTOCOL; } @@ -1065,53 +1221,111 @@ static CURLcode easy_connection(struct Curl_easy *data, 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; - result = easy_connection(data, &sfd, &c); + if(Curl_is_in_callback(data)) + return CURLE_RECURSIVE_API_CALL; + + result = easy_connection(data, &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(c, sfd, buffer, buflen, &n1); + result = Curl_conn_recv(data, FIRSTSOCKET, buffer, buflen, &n1); if(result) return result; *n = (size_t)n1; + return CURLE_OK; +} + +#ifndef CURL_DISABLE_WEBSOCKETS +CURLcode Curl_connect_only_attach(struct Curl_easy *data) +{ + CURLcode result; + struct connectdata *c = NULL; + + result = easy_connection(data, &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 /* !CURL_DISABLE_WEBSOCKETS */ /* - * Sends data over the connected socket. Use after successful - * curl_easy_perform() with CURLOPT_CONNECT_ONLY option. + * Sends data over the connected socket. + * + * This is the private internal version of curl_easy_send() */ -CURLcode curl_easy_send(struct Curl_easy *data, const void *buffer, - size_t buflen, size_t *n) +CURLcode Curl_senddata(struct Curl_easy *data, const void *buffer, + size_t buflen, size_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); + *n = 0; + result = easy_connection(data, &c); if(result) return result; - *n = 0; - result = Curl_write(c, sfd, buffer, buflen, &n1); + if(!data->conn) + /* on first invoke, the transfer has been detached from the connection and + needs to be reattached */ + Curl_attach_connection(data, c); - if(n1 == -1) - return CURLE_SEND_ERROR; + sigpipe_ignore(data, &pipe_st); + result = Curl_conn_send(data, FIRSTSOCKET, buffer, buflen, FALSE, n); + sigpipe_restore(&pipe_st); - /* detect EAGAIN */ - if(!result && !n1) - return CURLE_AGAIN; + if(result && result != CURLE_AGAIN) + return CURLE_SEND_ERROR; + return result; +} - *n = (size_t)n1; +/* + * 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) +{ + size_t written = 0; + CURLcode result; + if(Curl_is_in_callback(data)) + return CURLE_RECURSIVE_API_CALL; + result = Curl_senddata(data, buffer, buflen, &written); + *n = written; return result; } + +/* + * 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(Curl_is_in_callback(data)) + return CURLE_RECURSIVE_API_CALL; + + /* Use the common function to keep connections alive. */ + return Curl_cpool_upkeep(data); +} diff --git a/contrib/curl/lib/easy_lock.h b/contrib/curl/lib/easy_lock.h new file mode 100644 index 0000000000..4f6764d427 --- /dev/null +++ b/contrib/curl/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, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the 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 +#if defined(HAVE_SCHED_YIELD) +#include +#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 + +#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/contrib/curl/lib/easygetopt.c b/contrib/curl/lib/easygetopt.c new file mode 100644 index 0000000000..86833bf6b9 --- /dev/null +++ b/contrib/curl/lib/easygetopt.c @@ -0,0 +1,98 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ | | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * ___|___/|_| ______| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" +#include "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)) + /* do not 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/contrib/curl/lib/easyif.h b/contrib/curl/lib/easyif.h index f6132cc70d..181ce38f7b 100644 --- a/contrib/curl/lib/easyif.h +++ b/contrib/curl/lib/easyif.h @@ -7,11 +7,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2016, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -20,14 +20,22 @@ * 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 */ -#ifdef CURLDEBUG +CURLcode Curl_senddata(struct Curl_easy *data, const void *buffer, + size_t buflen, size_t *n); + +#ifndef CURL_DISABLE_WEBSOCKETS +CURLcode Curl_connect_only_attach(struct Curl_easy *data); +#endif + +#ifdef DEBUGBUILD CURL_EXTERN CURLcode curl_easy_perform_ev(struct Curl_easy *easy); #endif #endif /* HEADER_CURL_EASYIF_H */ - diff --git a/contrib/curl/lib/easyoptions.c b/contrib/curl/lib/easyoptions.c new file mode 100644 index 0000000000..81091c405a --- /dev/null +++ b/contrib/curl/lib/easyoptions.c @@ -0,0 +1,382 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +/* 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}, + {"ECH", CURLOPT_ECH, CURLOT_STRING, 0}, + {"EGDSOCKET", CURLOPT_EGDSOCKET, CURLOT_STRING, 0}, + {"ENCODING", CURLOPT_ACCEPT_ENCODING, CURLOT_STRING, CURLOT_FLAG_ALIAS}, + {"ERRORBUFFER", CURLOPT_ERRORBUFFER, CURLOT_OBJECT, 0}, + {"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}, + {"SERVER_RESPONSE_TIMEOUT_MS", CURLOPT_SERVER_RESPONSE_TIMEOUT_MS, + 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_KEEPCNT", CURLOPT_TCP_KEEPCNT, 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) != (326 + 1)); +} +#endif diff --git a/contrib/curl/lib/easyoptions.h b/contrib/curl/lib/easyoptions.h new file mode 100644 index 0000000000..24b4cd93ed --- /dev/null +++ b/contrib/curl/lib/easyoptions.h @@ -0,0 +1,37 @@ +#ifndef HEADER_CURL_EASYOPTIONS_H +#define HEADER_CURL_EASYOPTIONS_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +/* should probably go into the public header */ + +#include + +/* generated table with all easy options */ +extern struct curl_easyoption Curl_easyopts[]; + +#ifdef DEBUGBUILD +int Curl_easyopts_check(void); +#endif +#endif diff --git a/contrib/curl/lib/escape.c b/contrib/curl/lib/escape.c index 9c811b8ed1..fda6a3a12a 100644 --- a/contrib/curl/lib/escape.c +++ b/contrib/curl/lib/escape.c @@ -5,11 +5,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2016, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -18,6 +18,8 @@ * 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 @@ -29,7 +31,6 @@ #include "urldata.h" #include "warnless.h" -#include "non-ascii.h" #include "escape.h" #include "strdup.h" /* The last 3 #include files should be in this order */ @@ -37,33 +38,6 @@ #include "curl_memory.h" #include "memdebug.h" -/* Portable character check (remember EBCDIC). Do not use isalnum() because - its behavior is altered by the current locale. - See https://tools.ietf.org/html/rfc3986#section-2.3 -*/ -static bool Curl_isunreserved(unsigned char in) -{ - switch(in) { - case '0': case '1': case '2': case '3': case '4': - case '5': case '6': case '7': case '8': case '9': - case 'a': case 'b': case 'c': case 'd': case 'e': - case 'f': case 'g': case 'h': case 'i': case 'j': - case 'k': case 'l': case 'm': case 'n': case 'o': - case 'p': case 'q': case 'r': case 's': case 't': - case 'u': case 'v': case 'w': case 'x': case 'y': case 'z': - case 'A': case 'B': case 'C': case 'D': case 'E': - case 'F': case 'G': case 'H': case 'I': case 'J': - case 'K': case 'L': case 'M': case 'N': case 'O': - case 'P': case 'Q': case 'R': case 'S': case 'T': - case 'U': case 'V': case 'W': case 'X': case 'Y': case 'Z': - case '-': case '.': case '_': case '~': - return TRUE; - default: - break; - } - return FALSE; -} - /* for ABI-compatibility with previous versions */ char *curl_escape(const char *string, int inlength) { @@ -76,132 +50,121 @@ 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 alloc; - char *ns; - char *testing_ptr = NULL; - unsigned char in; /* we need to treat the characters unsigned */ - size_t newlen; - size_t strindex=0; size_t length; - CURLcode result; + struct dynbuf d; + (void)data; - if(inlength < 0) + if(!string || (inlength < 0)) return NULL; - alloc = (inlength?(size_t)inlength:strlen(string))+1; - newlen = alloc; + length = (inlength ? (size_t)inlength : strlen(string)); + if(!length) + return strdup(""); - ns = malloc(alloc); - if(!ns) - return NULL; + Curl_dyn_init(&d, length * 3 + 1); - length = alloc-1; while(length--) { - in = *string; + /* treat the characters unsigned */ + unsigned char in = (unsigned char)*string++; - if(Curl_isunreserved(in)) - /* just copy this */ - ns[strindex++]=in; + if(ISUNRESERVED(in)) { + /* append this */ + if(Curl_dyn_addn(&d, &in, 1)) + return NULL; + } else { /* encode it */ - newlen += 2; /* the size grows with two, since this'll become a %XX */ - if(newlen > alloc) { - alloc *= 2; - testing_ptr = Curl_saferealloc(ns, alloc); - if(!testing_ptr) - return NULL; - else { - ns = testing_ptr; - } - } - - result = Curl_convert_to_network(data, &in, 1); - if(result) { - /* Curl_convert_to_network calls failf if unsuccessful */ - free(ns); + 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; - } - - snprintf(&ns[strindex], 4, "%%%02X", in); - - strindex+=3; } - string++; } - ns[strindex]=0; /* terminate it */ - return ns; + + 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. * - * Optionally detects control characters (byte codes lower than 32) in the - * data and rejects such data. - * * 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(struct Curl_easy *data, - const char *string, size_t length, + +CURLcode Curl_urldecode(const char *string, size_t length, char **ostring, size_t *olen, - bool reject_ctrl) + enum urlreject ctrl) { - size_t alloc = (length?length:strlen(string))+1; - char *ns = malloc(alloc); - unsigned char in; - size_t strindex=0; - unsigned long hex; - CURLcode result; + 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; - while(--alloc > 0) { - in = *string; + /* store output string */ + *ostring = ns; + + while(alloc) { + unsigned char in = (unsigned char)*string; if(('%' == in) && (alloc > 2) && ISXDIGIT(string[1]) && ISXDIGIT(string[2])) { /* this is two hexadecimal digits following a '%' */ - char hexstr[3]; - char *ptr; - hexstr[0] = string[1]; - hexstr[1] = string[2]; - hexstr[2] = 0; - - hex = strtoul(hexstr, &ptr, 16); + in = (unsigned char)(onehex2dec(string[1]) << 4) | onehex2dec(string[2]); - in = curlx_ultouc(hex); /* this long is never bigger than 255 anyway */ - - result = Curl_convert_from_network(data, &in, 1); - if(result) { - /* Curl_convert_from_network calls failf if unsuccessful */ - free(ns); - return result; - } - - string+=2; - alloc-=2; + string += 3; + alloc -= 3; + } + else { + string++; + alloc--; } - if(reject_ctrl && (in < 0x20)) { - free(ns); + if(((ctrl == REJECT_CTRL) && (in < 0x20)) || + ((ctrl == REJECT_ZERO) && (in == 0))) { + Curl_safefree(*ostring); return CURLE_URL_MALFORMAT; } - ns[strindex++] = in; - string++; + *ns++ = (char)in; } - ns[strindex]=0; /* terminate it */ + *ns = 0; /* terminate it */ if(olen) /* store output size */ - *olen = strindex; - - /* store output string */ - *ostring = ns; + *olen = ns - *ostring; return CURLE_OK; } @@ -211,16 +174,18 @@ CURLcode Curl_urldecode(struct Curl_easy *data, * 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; - if(length >= 0) { - size_t inputlen = length; + (void)data; + if(string && (length >= 0)) { + size_t inputlen = (size_t)length; size_t outputlen; - CURLcode res = Curl_urldecode(data, string, inputlen, &str, &outputlen, - FALSE); + CURLcode res = Curl_urldecode(string, inputlen, &str, &outputlen, + REJECT_NADA); if(res) return NULL; @@ -242,3 +207,29 @@ 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++ = (unsigned char)hex[(*src & 0xF0) >> 4]; + *out++ = (unsigned char)hex[*src & 0x0F]; + ++src; + olen -= 2; + } + *out = 0; + } + else if(olen) + *out = 0; +} diff --git a/contrib/curl/lib/escape.h b/contrib/curl/lib/escape.h index 638666f039..690e417879 100644 --- a/contrib/curl/lib/escape.h +++ b/contrib/curl/lib/escape.h @@ -7,11 +7,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2011, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -20,14 +20,25 @@ * 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. */ -CURLcode Curl_urldecode(struct Curl_easy *data, - const char *string, size_t length, +#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, - bool reject_crlf); + enum urlreject ctrl); -#endif /* HEADER_CURL_ESCAPE_H */ +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/contrib/curl/lib/file.c b/contrib/curl/lib/file.c index e90902c50e..4cd8d0ff83 100644 --- a/contrib/curl/lib/file.c +++ b/contrib/curl/lib/file.c @@ -5,11 +5,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2016, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -18,6 +18,8 @@ * 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" @@ -48,6 +50,14 @@ #include #endif +#ifdef HAVE_SYS_TYPES_H +#include +#endif + +#ifdef HAVE_DIRENT_H +#include +#endif + #include "strtoofft.h" #include "urldata.h" #include @@ -57,18 +67,21 @@ #include "file.h" #include "speedcheck.h" #include "getinfo.h" +#include "multiif.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__) || \ - defined(__SYMBIAN32__) +#if defined(_WIN32) || defined(MSDOS) || defined(__EMX__) #define DOS_FILESYSTEM 1 +#elif defined(__amigaos4__) +#define AMIGA_FILESYSTEM 1 #endif #ifdef OPEN_NEEDS_ARG3 @@ -81,20 +94,22 @@ * Forward declarations. */ -static CURLcode file_do(struct connectdata *, bool *done); -static CURLcode file_done(struct connectdata *conn, +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 connectdata *conn, bool *done); -static CURLcode file_disconnect(struct connectdata *conn, +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 connectdata *conn); +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", /* scheme */ file_setup_connection, /* setup_connection */ file_do, /* do_it */ file_done, /* done */ @@ -107,112 +122,73 @@ const struct Curl_handler Curl_handler_file = { ZERO_NULL, /* domore_getsock */ ZERO_NULL, /* perform_getsock */ file_disconnect, /* disconnect */ - ZERO_NULL, /* readwrite */ + ZERO_NULL, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ + 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 connectdata *conn) +static CURLcode file_setup_connection(struct Curl_easy *data, + struct connectdata *conn) { + (void)conn; /* allocate the FILE specific struct */ - conn->data->req.protop = calloc(1, sizeof(struct FILEPROTO)); - if(!conn->data->req.protop) + data->req.p.file = calloc(1, sizeof(struct FILEPROTO)); + if(!data->req.p.file) return CURLE_OUT_OF_MEMORY; return CURLE_OK; } - /* - Check if this is a range download, and if so, set the internal variables - properly. This code is copied from the FTP implementation and might as - well be factored out. - */ -static CURLcode file_range(struct connectdata *conn) -{ - curl_off_t from, to; - curl_off_t totalsize=-1; - char *ptr; - char *ptr2; - struct Curl_easy *data = conn->data; - - if(data->state.use_range && data->state.range) { - from=curlx_strtoofft(data->state.range, &ptr, 0); - while(*ptr && (ISSPACE(*ptr) || (*ptr=='-'))) - ptr++; - to=curlx_strtoofft(ptr, &ptr2, 0); - if(ptr == ptr2) { - /* we didn't get any digit */ - to=-1; - } - if((-1 == to) && (from>=0)) { - /* X - */ - data->state.resume_from = from; - DEBUGF(infof(data, "RANGE %" CURL_FORMAT_CURL_OFF_T " to end of file\n", - from)); - } - else if(from < 0) { - /* -Y */ - data->req.maxdownload = -from; - data->state.resume_from = from; - DEBUGF(infof(data, "RANGE the last %" CURL_FORMAT_CURL_OFF_T " bytes\n", - -from)); - } - else { - /* X-Y */ - totalsize = to-from; - 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\n", - 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\n", - from, to, data->req.maxdownload)); - } - else - data->req.maxdownload = -1; - 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 + * 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 connectdata *conn, bool *done) +static CURLcode file_connect(struct Curl_easy *data, bool *done) { - struct Curl_easy *data = conn->data; char *real_path; - struct FILEPROTO *file = data->req.protop; + 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; + } - CURLcode result = Curl_urldecode(data, data->state.path, 0, &real_path, - &real_path_len, FALSE); + 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 + /* If the first character is a slash, and there is something that looks like a drive at the beginning of - the path, skip the slash. If we remove the initial + 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 + relative to the current directory which is not 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 + absolute pathname. On Windows, absolute paths start with a drive letter. */ actual_path = real_path; @@ -225,7 +201,7 @@ static CURLcode file_connect(struct connectdata *conn, bool *done) } /* change path separators from '/' to '\\' for DOS, Windows and OS/2 */ - for(i=0; i < real_path_len; ++i) + for(i = 0; i < real_path_len; ++i) if(actual_path[i] == '/') actual_path[i] = '\\'; else if(!actual_path[i]) { /* binary zero */ @@ -242,15 +218,41 @@ static CURLcode file_connect(struct connectdata *conn, bool *done) 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->set.upload && (fd == -1)) { - failf(data, "Couldn't open file %s", data->state.path); - file_done(conn, CURLE_FILE_COULDNT_READ_FILE, FALSE); + 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; @@ -258,10 +260,10 @@ static CURLcode file_connect(struct connectdata *conn, bool *done) return CURLE_OK; } -static CURLcode file_done(struct connectdata *conn, - CURLcode status, bool premature) +static CURLcode file_done(struct Curl_easy *data, + CURLcode status, bool premature) { - struct FILEPROTO *file = conn->data->req.protop; + struct FILEPROTO *file = data->req.p.file; (void)status; /* not used */ (void)premature; /* not used */ @@ -276,21 +278,13 @@ static CURLcode file_done(struct connectdata *conn, return CURLE_OK; } -static CURLcode file_disconnect(struct connectdata *conn, +static CURLcode file_disconnect(struct Curl_easy *data, + struct connectdata *conn, bool dead_connection) { - struct FILEPROTO *file = conn->data->req.protop; (void)dead_connection; /* not used */ - - if(file) { - Curl_safefree(file->freepath); - file->path = NULL; - if(file->fd != -1) - close(file->fd); - file->fd = -1; - } - - return CURLE_OK; + (void)conn; + return file_done(data, CURLE_OK, FALSE); } #ifdef DOS_FILESYSTEM @@ -299,27 +293,24 @@ static CURLcode file_disconnect(struct connectdata *conn, #define DIRSEP '/' #endif -static CURLcode file_upload(struct connectdata *conn) +static CURLcode file_upload(struct Curl_easy *data) { - struct FILEPROTO *file = conn->data->req.protop; + struct FILEPROTO *file = data->req.p.file; const char *dir = strchr(file->path, DIRSEP); int fd; int mode; CURLcode result = CURLE_OK; - struct Curl_easy *data = conn->data; - char *buf = data->state.buffer; - size_t nread; - size_t nwrite; + char *xfer_ulbuf; + size_t xfer_ulblen; curl_off_t bytecount = 0; - struct timeval now = Curl_tvnow(); struct_stat file_stat; - const char *buf2; + const char *sendbuf; + bool eos = FALSE; /* - * Since FILE: doesn't do the full init, we need to provide some extra + * Since FILE: does not do the full init, we need to provide some extra * assignments here. */ - conn->data->req.upload_fromhere = buf; if(!dir) return CURLE_FILE_COULDNT_READ_FILE; /* fix: better error code */ @@ -338,9 +329,9 @@ static CURLcode file_upload(struct connectdata *conn) else mode = MODE_DEFAULT|O_TRUNC; - fd = open(file->path, mode, conn->data->set.new_file_perms); + fd = open(file->path, mode, data->set.new_file_perms); if(fd < 0) { - failf(data, "Can't open %s for writing", file->path); + failf(data, "cannot open %s for writing", file->path); return CURLE_WRITE_ERROR; } @@ -352,43 +343,49 @@ static CURLcode file_upload(struct connectdata *conn) if(data->state.resume_from < 0) { if(fstat(fd, &file_stat)) { close(fd); - failf(data, "Can't get the size of %s", file->path); + failf(data, "cannot get the size of %s", file->path); return CURLE_WRITE_ERROR; } - else - data->state.resume_from = (curl_off_t)file_stat.st_size; + data->state.resume_from = (curl_off_t)file_stat.st_size; } - while(!result) { - int readcount; - result = Curl_fillreadbuffer(conn, BUFSIZE, &readcount); + result = Curl_multi_xfer_ulbuf_borrow(data, &xfer_ulbuf, &xfer_ulblen); + if(result) + goto out; + + while(!result && !eos) { + size_t nread; + ssize_t nwrite; + size_t readcount; + + result = Curl_client_read(data, xfer_ulbuf, xfer_ulblen, &readcount, &eos); if(result) break; - if(readcount <= 0) /* fix questionable compare error. curlvms */ + if(!readcount) break; - nread = (size_t)readcount; + nread = readcount; - /*skip bytes before resume point*/ + /* 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; + sendbuf = xfer_ulbuf; } else { - buf2 = buf + data->state.resume_from; + sendbuf = xfer_ulbuf + data->state.resume_from; nread -= (size_t)data->state.resume_from; data->state.resume_from = 0; } } else - buf2 = buf; + sendbuf = xfer_ulbuf; /* write the data to the target */ - nwrite = write(fd, buf2, nread); - if(nwrite != nread) { + nwrite = write(fd, sendbuf, nread); + if((size_t)nwrite != nread) { result = CURLE_SEND_ERROR; break; } @@ -397,15 +394,17 @@ static CURLcode file_upload(struct connectdata *conn) Curl_pgrsSetUploadCounter(data, bytecount); - if(Curl_pgrsUpdate(conn)) + if(Curl_pgrsUpdate(data)) result = CURLE_ABORTED_BY_CALLBACK; else - result = Curl_speedcheck(data, now); + result = Curl_speedcheck(data, Curl_now()); } - if(!result && Curl_pgrsUpdate(conn)) + if(!result && Curl_pgrsUpdate(data)) result = CURLE_ABORTED_BY_CALLBACK; +out: close(fd); + Curl_multi_xfer_ulbuf_release(data, xfer_ulbuf); return result; } @@ -414,13 +413,13 @@ static CURLcode file_upload(struct connectdata *conn) * 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 + * platforms we support do not 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 connectdata *conn, bool *done) +static CURLcode file_do(struct Curl_easy *data, bool *done) { - /* This implementation ignores the host name in conformance with + /* This implementation ignores the hostname in conformance with RFC 1738. Only local files (reachable via the standard file system) are supported. This means that files on remotely mounted directories (via NFS, Samba, NT sharing) can be accessed through a file:// URL @@ -429,63 +428,57 @@ static CURLcode file_do(struct connectdata *conn, bool *done) 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=0; + curl_off_t expected_size = -1; bool size_known; - bool fstated=FALSE; - ssize_t nread; - struct Curl_easy *data = conn->data; - char *buf = data->state.buffer; - curl_off_t bytecount = 0; + bool fstated = FALSE; int fd; - struct timeval now = Curl_tvnow(); struct FILEPROTO *file; + char *xfer_buf; + size_t xfer_blen; *done = TRUE; /* unconditionally */ - Curl_initinfo(data); - Curl_pgrsStartNow(data); - - if(data->set.upload) - return file_upload(conn); + if(data->state.upload) + return file_upload(data); - file = conn->data->req.protop; + 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)) { - /* we could stat it, then read out the size */ - expected_size = statbuf.st_size; + if(!S_ISDIR(statbuf.st_mode)) + expected_size = statbuf.st_size; /* and store the modification time */ - data->info.filetime = (long)statbuf.st_mtime; + data->info.filetime = statbuf.st_mtime; fstated = TRUE; } - if(fstated && !data->state.range && data->set.timecondition) { - if(!Curl_meets_timecondition(data, (time_t)data->info.filetime)) { - *done = TRUE; - return CURLE_OK; - } - } + if(fstated && !data->state.range && data->set.timecondition && + !Curl_meets_timecondition(data, data->info.filetime)) + return CURLE_OK; - /* If we have selected NOBODY and HEADER, it means that we only want file - information. Which for FILE can't be much more than the file size and - date. */ - if(data->set.opt_no_body && data->set.include_header && fstated) { + if(fstated) { time_t filetime; struct tm buffer; const struct tm *tm = &buffer; - snprintf(buf, CURL_BUFSIZE(data->set.buffer_size), - "Content-Length: %" CURL_FORMAT_CURL_OFF_T "\r\n", expected_size); - result = Curl_client_write(conn, CLIENTWRITE_BOTH, buf, 0); - if(result) - return result; - - result = Curl_client_write(conn, CLIENTWRITE_BOTH, - (char *)"Accept-ranges: bytes\r\n", 0); - if(result) - return result; + char header[80]; + int headerlen; + static const char accept_ranges[]= { "Accept-ranges: bytes\r\n" }; + if(expected_size >= 0) { + headerlen = + msnprintf(header, sizeof(header), "Content-Length: %" FMT_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, sizeof(accept_ranges) - 1); + if(result != CURLE_OK) + return result; + } filetime = (time_t)statbuf.st_mtime; result = Curl_gmtime(filetime, &buffer); @@ -493,48 +486,60 @@ static CURLcode file_do(struct connectdata *conn, bool *done) return result; /* format: "Tue, 15 Nov 1994 12:45:26 GMT" */ - snprintf(buf, BUFSIZE-1, - "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 = Curl_client_write(conn, CLIENTWRITE_BOTH, buf, 0); + headerlen = + msnprintf(header, sizeof(header), + "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 = Curl_client_write(data, CLIENTWRITE_HEADER, header, headerlen); if(!result) - /* set the file size to make it available post transfer */ - Curl_pgrsSetDownloadSize(data, expected_size); - return result; + /* end of headers */ + result = Curl_client_write(data, CLIENTWRITE_HEADER, "\r\n", 2); + 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 CURLE_OK; } /* Check whether file range has been specified */ - file_range(conn); + 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 iff the filesize could be determined */ + * 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."); + failf(data, "cannot get the size of file."); return CURLE_READ_ERROR; } - else - data->state.resume_from += (curl_off_t)statbuf.st_size; + data->state.resume_from += (curl_off_t)statbuf.st_size; } - 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; + if(data->state.resume_from > 0) { + /* We check explicitly if we have a start offset, because + * expected_size may be -1 if we do not 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)) + if(!fstated || (expected_size <= 0)) size_known = FALSE; else size_known = TRUE; @@ -543,55 +548,94 @@ static CURLcode file_do(struct connectdata *conn, bool *done) 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(fstated) + 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)) + if(!S_ISDIR(statbuf.st_mode)) { + if(data->state.resume_from != + lseek(fd, data->state.resume_from, SEEK_SET)) + return CURLE_BAD_DOWNLOAD_RESUME; + } + else { return CURLE_BAD_DOWNLOAD_RESUME; + } } - Curl_pgrsTime(data, TIMER_STARTTRANSFER); + result = Curl_multi_xfer_buf_borrow(data, &xfer_buf, &xfer_blen); + if(result) + goto out; - while(!result) { - /* Don't fill a whole buffer if we want less than all data */ - size_t bytestoread; + if(!S_ISDIR(statbuf.st_mode)) { + while(!result) { + ssize_t nread; + /* Do not fill a whole buffer if we want less than all data */ + size_t bytestoread; - if(size_known) { - bytestoread = - (expected_size < CURL_OFF_T_C(BUFSIZE) - CURL_OFF_T_C(1)) ? - curlx_sotouz(expected_size) : BUFSIZE - 1; - } - else - bytestoread = BUFSIZE-1; + if(size_known) { + bytestoread = (expected_size < (curl_off_t)(xfer_blen-1)) ? + curlx_sotouz(expected_size) : (xfer_blen-1); + } + else + bytestoread = xfer_blen-1; - nread = read(fd, buf, bytestoread); + nread = read(fd, xfer_buf, bytestoread); - if(nread > 0) - buf[nread] = 0; + if(nread > 0) + xfer_buf[nread] = 0; - if(nread <= 0 || (size_known && (expected_size == 0))) - break; + if(nread <= 0 || (size_known && (expected_size == 0))) + break; - bytecount += nread; - if(size_known) - expected_size -= nread; + if(size_known) + expected_size -= nread; - result = Curl_client_write(conn, CLIENTWRITE_BODY, buf, nread); - if(result) - return result; + result = Curl_client_write(data, CLIENTWRITE_BODY, xfer_buf, nread); + if(result) + goto out; - Curl_pgrsSetDownloadCounter(data, bytecount); + if(Curl_pgrsUpdate(data)) + result = CURLE_ABORTED_BY_CALLBACK; + else + result = Curl_speedcheck(data, Curl_now()); + if(result) + goto out; + } + } + else { +#ifdef HAVE_OPENDIR + DIR *dir = opendir(file->path); + struct dirent *entry; - if(Curl_pgrsUpdate(conn)) - result = CURLE_ABORTED_BY_CALLBACK; - else - result = Curl_speedcheck(data, now); + if(!dir) { + result = CURLE_READ_ERROR; + goto out; + } + else { + while((entry = readdir(dir))) { + if(entry->d_name[0] != '.') { + result = Curl_client_write(data, CLIENTWRITE_BODY, + entry->d_name, strlen(entry->d_name)); + if(result) + break; + result = Curl_client_write(data, CLIENTWRITE_BODY, "\n", 1); + if(result) + break; + } + } + closedir(dir); + } +#else + failf(data, "Directory listing not yet implemented on this platform."); + result = CURLE_READ_ERROR; +#endif } - if(Curl_pgrsUpdate(conn)) + + if(Curl_pgrsUpdate(data)) result = CURLE_ABORTED_BY_CALLBACK; +out: + Curl_multi_xfer_buf_release(data, xfer_buf); return result; } diff --git a/contrib/curl/lib/file.h b/contrib/curl/lib/file.h index c12ae0e09e..4565525592 100644 --- a/contrib/curl/lib/file.h +++ b/contrib/curl/lib/file.h @@ -7,11 +7,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2009, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -20,6 +20,8 @@ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY * KIND, either express or implied. * + * SPDX-License-Identifier: curl + * ***************************************************************************/ @@ -38,4 +40,3 @@ extern const struct Curl_handler Curl_handler_file; #endif #endif /* HEADER_CURL_FILE_H */ - diff --git a/contrib/curl/lib/fileinfo.c b/contrib/curl/lib/fileinfo.c index 144c65b1d8..2be3b3239b 100644 --- a/contrib/curl/lib/fileinfo.c +++ b/contrib/curl/lib/fileinfo.c @@ -5,11 +5,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 2010 - 2015, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -18,33 +18,29 @@ * 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 curl_fileinfo *Curl_fileinfo_alloc(void) +struct fileinfo *Curl_fileinfo_alloc(void) { - struct curl_fileinfo *tmp = malloc(sizeof(struct curl_fileinfo)); - if(!tmp) - return NULL; - memset(tmp, 0, sizeof(struct curl_fileinfo)); - return tmp; + return calloc(1, sizeof(struct fileinfo)); } -void Curl_fileinfo_dtor(void *user, void *element) +void Curl_fileinfo_cleanup(struct fileinfo *finfo) { - struct curl_fileinfo *finfo = element; - (void) user; if(!finfo) return; - Curl_safefree(finfo->b_data); - + Curl_dyn_free(&finfo->buf); free(finfo); } +#endif diff --git a/contrib/curl/lib/fileinfo.h b/contrib/curl/lib/fileinfo.h index 5324f1a404..0b3f56d9d4 100644 --- a/contrib/curl/lib/fileinfo.h +++ b/contrib/curl/lib/fileinfo.h @@ -7,11 +7,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 2010, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -20,14 +20,21 @@ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY * KIND, either express or implied. * + * SPDX-License-Identifier: curl + * ***************************************************************************/ #include +#include "llist.h" +#include "dynbuf.h" -struct curl_fileinfo *Curl_fileinfo_alloc(void); - -void Curl_fileinfo_dtor(void *, void *); +struct fileinfo { + struct curl_fileinfo info; + struct Curl_llist_node list; + struct dynbuf buf; +}; -struct curl_fileinfo *Curl_fileinfo_dup(const struct curl_fileinfo *src); +struct fileinfo *Curl_fileinfo_alloc(void); +void Curl_fileinfo_cleanup(struct fileinfo *finfo); #endif /* HEADER_CURL_FILEINFO_H */ diff --git a/contrib/curl/lib/firefox-db2pem.sh b/contrib/curl/lib/firefox-db2pem.sh deleted file mode 100644 index 7d691ff6b9..0000000000 --- a/contrib/curl/lib/firefox-db2pem.sh +++ /dev/null @@ -1,54 +0,0 @@ -#!/bin/sh -# *************************************************************************** -# * _ _ ____ _ -# * Project ___| | | | _ \| | -# * / __| | | | |_) | | -# * | (__| |_| | _ <| |___ -# * \___|\___/|_| \_\_____| -# * -# * Copyright (C) 1998 - 2015, Daniel Stenberg, , et al. -# * -# * This software is licensed as described in the file COPYING, which -# * you should have received as part of this distribution. The terms -# * are also available at https://curl.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. -# * -# *************************************************************************** -# This shell script creates a fresh ca-bundle.crt file for use with libcurl. -# It extracts all ca certs it finds in the local Firefox database and converts -# them all into PEM format. -# -db=`ls -1d $HOME/.mozilla/firefox/*default*` -out=$1 - -if test -z "$out"; then - out="ca-bundle.crt" # use a sensible default -fi - -currentdate=`date` - -cat >$out <> $out - diff --git a/contrib/curl/lib/fopen.c b/contrib/curl/lib/fopen.c new file mode 100644 index 0000000000..7373e08831 --- /dev/null +++ b/contrib/curl/lib/fopen.c @@ -0,0 +1,158 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#if !defined(CURL_DISABLE_COOKIES) || !defined(CURL_DISABLE_ALTSVC) || \ + !defined(CURL_DISABLE_HSTS) + +#ifdef HAVE_FCNTL_H +#include +#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 filename 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 filename 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; +#if (defined(ANDROID) || defined(__ANDROID__)) && \ + (defined(__i386__) || defined(__arm__)) + fd = open(tempstore, O_WRONLY | O_CREAT | O_EXCL, (mode_t)(0600|sb.st_mode)); +#else + fd = open(tempstore, O_WRONLY | O_CREAT | O_EXCL, 0600|sb.st_mode); +#endif + 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/contrib/curl/lib/fopen.h b/contrib/curl/lib/fopen.h new file mode 100644 index 0000000000..e3a919d073 --- /dev/null +++ b/contrib/curl/lib/fopen.h @@ -0,0 +1,30 @@ +#ifndef HEADER_CURL_FOPEN_H +#define HEADER_CURL_FOPEN_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +CURLcode Curl_fopen(struct Curl_easy *data, const char *filename, + FILE **fh, char **tempname); + +#endif diff --git a/contrib/curl/lib/formdata.c b/contrib/curl/lib/formdata.c index c214ba28e9..a0d5a20582 100644 --- a/contrib/curl/lib/formdata.c +++ b/contrib/curl/lib/formdata.c @@ -5,11 +5,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2017, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -18,44 +18,34 @@ * 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 -#ifndef CURL_DISABLE_HTTP +#include "formdata.h" +#if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_FORM_API) #if defined(HAVE_LIBGEN_H) && defined(HAVE_BASENAME) #include #endif #include "urldata.h" /* for struct Curl_easy */ -#include "formdata.h" +#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" -#ifndef HAVE_BASENAME -static char *Curl_basename(char *path); -#define basename(x) Curl_basename((x)) -#endif - -static size_t readfromfile(struct Form *form, char *buffer, size_t size); -static char *formboundary(struct Curl_easy *data); - -/* What kind of Content-Type to use on un-specified files with unrecognized - extensions. */ -#define HTTPPOST_CONTENTTYPE_DEFAULT "application/octet-stream" - -#define FORM_FILE_SEPARATOR ',' -#define FORM_TYPE_SEPARATOR ';' #define HTTPPOST_PTRNAME CURL_HTTPPOST_PTRNAME #define HTTPPOST_FILENAME CURL_HTTPPOST_FILENAME @@ -69,7 +59,7 @@ static char *formboundary(struct Curl_easy *data); * * AddHttpPost() * - * Adds a HttpPost structure to the list, if parent_post is given becomes + * 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. @@ -88,10 +78,15 @@ AddHttpPost(char *name, size_t namelength, 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)(name?(namelength?namelength:strlen(name)):0); + post->namelength = (long)namelength; post->contents = value; post->contentlen = contentslength; post->buffer = buffer; @@ -99,7 +94,7 @@ AddHttpPost(char *name, size_t namelength, post->contenttype = contenttype; post->contentheader = contentHeader; post->showfilename = showfilename; - post->userp = userp, + post->userp = userp; post->flags = flags | CURL_HTTPPOST_LARGE; } else @@ -134,21 +129,19 @@ AddHttpPost(char *name, size_t namelength, * parent_form_info is NULL. * ***************************************************************************/ -static FormInfo * AddFormInfo(char *value, - char *contenttype, - FormInfo *parent_form_info) +static struct FormInfo *AddFormInfo(char *value, + char *contenttype, + struct FormInfo *parent_form_info) { - FormInfo *form_info; + struct FormInfo *form_info; form_info = calloc(1, sizeof(struct FormInfo)); - if(form_info) { - if(value) - form_info->value = value; - if(contenttype) - form_info->contenttype = contenttype; - form_info->flags = HTTPPOST_FILENAME; - } - else + 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' */ @@ -161,60 +154,6 @@ static FormInfo * AddFormInfo(char *value, return form_info; } -/*************************************************************************** - * - * ContentTypeForFilename() - * - * Provides content type for filename if one of the known types (else - * (either the prevtype or the default is returned). - * - * Returns some valid contenttype for filename. - * - ***************************************************************************/ -static const char *ContentTypeForFilename(const char *filename, - const char *prevtype) -{ - const char *contenttype = NULL; - unsigned int i; - /* - * No 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"}, - {".txt", "text/plain"}, - {".html", "text/html"}, - {".xml", "application/xml"} - }; - - if(prevtype) - /* default to the previously set/used! */ - contenttype = prevtype; - else - contenttype = HTTPPOST_CONTENTTYPE_DEFAULT; - - if(filename) { /* in case a NULL was passed in */ - for(i=0; i= strlen(ctts[i].extension)) { - if(strcasecompare(filename + - strlen(filename) - strlen(ctts[i].extension), - ctts[i].extension)) { - contenttype = ctts[i].type; - break; - } - } - } - } - /* we have a contenttype by now */ - return contenttype; -} - /*************************************************************************** * * FormAdd() @@ -258,7 +197,7 @@ static const char *ContentTypeForFilename(const char *filename, * 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 a HttpPost struct cannot be allocated + * 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 * @@ -269,16 +208,16 @@ CURLFORMcode FormAdd(struct curl_httppost **httppost, struct curl_httppost **last_post, va_list params) { - FormInfo *first_form, *current_form, *form = NULL; + 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 */ + 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 + /* This is a state variable, that if TRUE means that we are parsing an + array that we got passed to us. If FALSE we are parsing the input va_list arguments. */ bool array_state = FALSE; @@ -310,8 +249,10 @@ CURLFORMcode FormAdd(struct curl_httppost **httppost, } } else { - /* This is not array-state, get next option */ - option = va_arg(params, CURLformoption); + /* 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; } @@ -319,7 +260,7 @@ CURLFORMcode FormAdd(struct curl_httppost **httppost, switch(option) { case CURLFORM_ARRAY: if(array_state) - /* we don't support an array from within an array */ + /* we do not support an array from within an array */ return_value = CURL_FORMADD_ILLEGAL_ARRAY; else { forms = va_arg(params, struct curl_forms *); @@ -334,20 +275,15 @@ CURLFORMcode FormAdd(struct curl_httppost **httppost, * Set the Name property. */ case CURLFORM_PTRNAME: -#ifdef CURL_DOES_CONVERSIONS - /* Treat CURLFORM_PTR like CURLFORM_COPYNAME so that libcurl will copy - * the data in all cases so that we'll have safe memory for the eventual - * conversion. - */ -#else current_form->flags |= HTTPPOST_PTRNAME; /* fall through */ -#endif + + 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 *); + char *name = array_state ? + array_value : va_arg(params, char *); if(name) current_form->name = name; /* store for the moment */ else @@ -359,20 +295,21 @@ CURLFORMcode FormAdd(struct curl_httppost **httppost, return_value = CURL_FORMADD_OPTION_TWICE; else current_form->namelength = - array_state?(size_t)array_value:(size_t)va_arg(params, long); + 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; /* fall through */ + 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 *); + array_state ? array_value : va_arg(params, char *); if(value) current_form->value = value; /* store for the moment */ else @@ -381,22 +318,23 @@ CURLFORMcode FormAdd(struct curl_httppost **httppost, break; case CURLFORM_CONTENTSLENGTH: current_form->contentslength = - array_state?(size_t)array_value:(size_t)va_arg(params, long); + 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); + array_state ? (curl_off_t)(size_t)array_value : + va_arg(params, curl_off_t); break; - /* Get contents from a given file name */ + /* Get contents from a given filename */ 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 *); + const char *filename = array_state ? + array_value : va_arg(params, char *); if(filename) { current_form->value = strdup(filename); if(!current_form->value) @@ -414,7 +352,7 @@ CURLFORMcode FormAdd(struct curl_httppost **httppost, /* We upload a file */ case CURLFORM_FILE: { - const char *filename = array_state?array_value: + const char *filename = array_state ? array_value : va_arg(params, char *); if(current_form->value) { @@ -464,7 +402,7 @@ CURLFORMcode FormAdd(struct curl_httppost **httppost, return_value = CURL_FORMADD_OPTION_TWICE; else { char *buffer = - array_state?array_value:va_arg(params, char *); + 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 @@ -480,7 +418,7 @@ CURLFORMcode FormAdd(struct curl_httppost **httppost, return_value = CURL_FORMADD_OPTION_TWICE; else current_form->bufferlength = - array_state?(size_t)array_value:(size_t)va_arg(params, long); + array_state ? (size_t)array_value : (size_t)va_arg(params, long); break; case CURLFORM_STREAM: @@ -489,10 +427,10 @@ CURLFORMcode FormAdd(struct curl_httppost **httppost, return_value = CURL_FORMADD_OPTION_TWICE; else { char *userp = - array_state?array_value:va_arg(params, char *); + 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 + current_form->value = userp; /* this is not 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 */ @@ -505,7 +443,7 @@ CURLFORMcode FormAdd(struct curl_httppost **httppost, case CURLFORM_CONTENTTYPE: { const char *contenttype = - array_state?array_value:va_arg(params, char *); + array_state ? array_value : va_arg(params, char *); if(current_form->contenttype) { if(current_form->flags & HTTPPOST_FILENAME) { if(contenttype) { @@ -548,8 +486,8 @@ CURLFORMcode FormAdd(struct curl_httppost **httppost, { /* 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: + struct curl_slist *list = array_state ? + (struct curl_slist *)(void *)array_value : va_arg(params, struct curl_slist *); if(current_form->contentheader) @@ -562,7 +500,7 @@ CURLFORMcode FormAdd(struct curl_httppost **httppost, case CURLFORM_FILENAME: case CURLFORM_BUFFER: { - const char *filename = array_state?array_value: + const char *filename = array_state ? array_value : va_arg(params, char *); if(current_form->showfilename) return_value = CURL_FORMADD_OPTION_TWICE; @@ -584,7 +522,7 @@ CURLFORMcode FormAdd(struct curl_httppost **httppost, 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 */ - FormInfo *ptr; + struct FormInfo *ptr; for(ptr = first_form; ptr != NULL; ptr = ptr->more) { if(ptr->name_alloc) { Curl_safefree(ptr->name); @@ -629,76 +567,91 @@ CURLFORMcode FormAdd(struct curl_httppost **httppost, return_value = CURL_FORMADD_INCOMPLETE; break; } - else { - if(((form->flags & HTTPPOST_FILENAME) || - (form->flags & HTTPPOST_BUFFER)) && - !form->contenttype) { - char *f = form->flags & HTTPPOST_BUFFER? - form->showfilename : form->value; - - /* our contenttype is missing */ - form->contenttype = strdup(ContentTypeForFilename(f, prevtype)); - if(!form->contenttype) { - return_value = CURL_FORMADD_MEMORY; - break; - } - form->contenttype_alloc = TRUE; + 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; } - 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 contains null characters) */ - form->name = Curl_memdup(form->name, form->namelength? - form->namelength: - strlen(form->name)+1); - } - if(!form->name) { - return_value = CURL_FORMADD_MEMORY; + 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; } - form->name_alloc = TRUE; + if(return_value != CURL_FORMADD_OK) + break; + } + if(!(form->flags & HTTPPOST_PTRNAME) && + (form == first_form) ) { + /* Note that there is 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_memdup0(form->name, form->namelength ? + form->namelength : + strlen(form->name)); } - 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; + 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); + 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) { + if(!form->value) { return_value = CURL_FORMADD_MEMORY; break; } - - if(form->contenttype) - prevtype = form->contenttype; + 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 */ - FormInfo *ptr; + struct FormInfo *ptr; for(ptr = form; ptr != NULL; ptr = ptr->more) { if(ptr->name_alloc) { Curl_safefree(ptr->name); @@ -724,7 +677,7 @@ CURLFORMcode FormAdd(struct curl_httppost **httppost, fields given that these have either been deallocated or are owned now by the httppost linked list */ while(first_form) { - FormInfo *ptr = first_form->more; + struct FormInfo *ptr = first_form->more; free(first_form); first_form = ptr; } @@ -750,211 +703,6 @@ CURLFORMcode curl_formadd(struct curl_httppost **httppost, return result; } -#ifdef __VMS -#include -/* - * 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 == NULL) - return 0; - - count = 0; - ret_stat = 1; - while(ret_stat > 0) { - ret_stat = fread(buffer, 1, sizeof(buffer), file); - if(ret_stat != 0) - 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; - } -} - -#endif - -#ifndef __VMS -#define filesize(name, stat_data) (stat_data.st_size) -#else - /* Getting the expected file size needs help on VMS */ -#define filesize(name, stat_data) VmsSpecialSize(name, &stat_data) -#endif - -/* - * AddFormData() adds a chunk of data to the FormData linked list. - * - * size is incremented by the chunk length, unless it is NULL - */ -static CURLcode AddFormData(struct FormData **formp, - enum formtype type, - const void *line, - curl_off_t length, - curl_off_t *size) -{ - struct FormData *newform; - char *alloc2 = NULL; - CURLcode result = CURLE_OK; - if(length < 0 || (size && *size < 0)) - return CURLE_BAD_FUNCTION_ARGUMENT; - - newform = malloc(sizeof(struct FormData)); - if(!newform) - return CURLE_OUT_OF_MEMORY; - newform->next = NULL; - - if(type <= FORM_CONTENT) { - /* we make it easier for plain strings: */ - if(!length) - length = strlen((char *)line); -#if (SIZEOF_SIZE_T < CURL_SIZEOF_CURL_OFF_T) - else if(length >= (curl_off_t)(size_t)-1) { - result = CURLE_BAD_FUNCTION_ARGUMENT; - goto error; - } -#endif - if(type != FORM_DATAMEM) { - newform->line = malloc((size_t)length+1); - if(!newform->line) { - result = CURLE_OUT_OF_MEMORY; - goto error; - } - alloc2 = newform->line; - memcpy(newform->line, line, (size_t)length); - - /* zero terminate for easier debugging */ - newform->line[(size_t)length]=0; - } - else { - newform->line = (char *)line; - type = FORM_DATA; /* in all other aspects this is just FORM_DATA */ - } - newform->length = (size_t)length; - } - else - /* For callbacks and files we don't have any actual data so we just keep a - pointer to whatever this points to */ - newform->line = (char *)line; - - newform->type = type; - - if(size) { - if(type != FORM_FILE) - /* for static content as well as callback data we add the size given - as input argument */ - *size += length; - else { - /* Since this is a file to be uploaded here, add the size of the actual - file */ - if(strcmp("-", newform->line)) { - struct_stat file; - if(!stat(newform->line, &file) && !S_ISDIR(file.st_mode)) - *size += filesize(newform->line, file); - else { - result = CURLE_BAD_FUNCTION_ARGUMENT; - goto error; - } - } - } - } - - if(*formp) { - (*formp)->next = newform; - *formp = newform; - } - else - *formp = newform; - - return CURLE_OK; - error: - if(newform) - free(newform); - if(alloc2) - free(alloc2); - return result; -} - -/* - * AddFormDataf() adds printf()-style formatted data to the formdata chain. - */ - -static CURLcode AddFormDataf(struct FormData **formp, - curl_off_t *size, - const char *fmt, ...) -{ - char *s; - CURLcode result; - va_list ap; - va_start(ap, fmt); - s = curl_mvaprintf(fmt, ap); - va_end(ap); - - if(!s) - return CURLE_OUT_OF_MEMORY; - - result = AddFormData(formp, FORM_DATAMEM, s, 0, size); - if(result) - free(s); - - return result; -} - -/* - * Curl_formclean() is used from http.c, this cleans a built FormData linked - * list - */ -void Curl_formclean(struct FormData **form_ptr) -{ - struct FormData *next, *form; - - form = *form_ptr; - if(!form) - return; - - do { - next=form->next; /* the following form line */ - if(form->type <= FORM_CONTENT) - free(form->line); /* free the line */ - free(form); /* free the struct */ - form = next; - } while(form); /* continue */ - - *form_ptr = NULL; -} - /* * curl_formget() * Serialize a curl_httppost struct. @@ -966,42 +714,30 @@ int curl_formget(struct curl_httppost *form, void *arg, curl_formget_callback append) { CURLcode result; - curl_off_t size; - struct FormData *data, *ptr; + curl_mimepart toppart; - result = Curl_getformdata(NULL, &data, form, NULL, &size); - if(result) - return (int)result; - - for(ptr = data; ptr; ptr = ptr->next) { - if((ptr->type == FORM_FILE) || (ptr->type == FORM_CALLBACK)) { - char buffer[8192]; - size_t nread; - struct Form temp; - - Curl_FormInit(&temp, ptr); - - do { - nread = readfromfile(&temp, buffer, sizeof(buffer)); - if((nread == (size_t) -1) || - (nread > sizeof(buffer)) || - (nread != append(arg, buffer, nread))) { - if(temp.fp) - fclose(temp.fp); - Curl_formclean(&data); - return -1; - } - } while(nread); - } - else { - if(ptr->length != append(arg, ptr->line, ptr->length)) { - Curl_formclean(&data); - return -1; - } + 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_formclean(&data); - return 0; + + Curl_mime_cleanpart(&toppart); + return (int) result; } /* @@ -1017,7 +753,7 @@ void curl_formfree(struct curl_httppost *form) return; do { - next=form->next; /* the following form line */ + next = form->next; /* the following form line */ /* recurse to sub-contents */ curl_formfree(form->more); @@ -1029,124 +765,47 @@ void curl_formfree(struct curl_httppost *form) ) free(form->contents); /* free the contents */ free(form->contenttype); /* free the content type */ - free(form->showfilename); /* free the faked file name */ + free(form->showfilename); /* free the faked filename */ free(form); /* free the struct */ form = next; } while(form); /* continue */ } -#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) +/* Set mime part name, taking care of non null-terminated name string. */ +static CURLcode setname(curl_mimepart *part, const char *name, size_t len) { - /* Ignore all the details above for now and make a quick and simple - implementaion here */ - char *s1; - char *s2; + char *zname; + CURLcode res; - 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; -} -#endif - -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 ! */ + if(!name || !len) + return curl_mime_name(part, name); + zname = Curl_memdup0(name, len); + if(!zname) + return CURLE_OUT_OF_MEMORY; + res = curl_mime_name(part, zname); + free(zname); + return res; } -static CURLcode formdata_add_filename(const struct curl_httppost *file, - struct FormData **form, - curl_off_t *size) +/* wrap call to fseeko so it matches the calling convention of callback */ +static int fseeko_wrapper(void *stream, curl_off_t offset, int whence) { - CURLcode result = CURLE_OK; - char *filename = file->showfilename; - char *filebasename = NULL; - char *filename_escaped = NULL; - - if(!filename) { - filebasename = strippath(file->contents); - if(!filebasename) - return CURLE_OUT_OF_MEMORY; - filename = filebasename; - } - - if(strchr(filename, '\\') || strchr(filename, '"')) { - char *p0, *p1; - - /* filename need be escaped */ - filename_escaped = malloc(strlen(filename)*2+1); - if(!filename_escaped) { - free(filebasename); - return CURLE_OUT_OF_MEMORY; - } - p0 = filename_escaped; - p1 = filename; - while(*p1) { - if(*p1 == '\\' || *p1 == '"') - *p0++ = '\\'; - *p0++ = *p1++; - } - *p0 = '\0'; - filename = filename_escaped; - } - result = AddFormDataf(form, size, - "; filename=\"%s\"", - filename); - free(filename_escaped); - free(filebasename); - return result; +#if defined(HAVE__FSEEKI64) + return _fseeki64(stream, (__int64)offset, whence); +#elif defined(HAVE_FSEEKO) && defined(HAVE_DECL_FSEEKO) + return fseeko(stream, (off_t)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 complete - * (possibly huge) multipart formdata. The input list is in 'post', while the - * output resulting linked lists gets stored in '*finalform'. *sizep will get - * the total size of the whole POST. - * A multipart/form_data content-type is built, unless a custom content-type - * is passed in 'custom_content_type'. + * 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 @@ -1154,432 +813,126 @@ static CURLcode formdata_add_filename(const struct curl_httppost *file, */ CURLcode Curl_getformdata(struct Curl_easy *data, - struct FormData **finalform, + curl_mimepart *finalform, struct curl_httppost *post, - const char *custom_content_type, - curl_off_t *sizep) + curl_read_callback fread_func) { - struct FormData *form = NULL; - struct FormData *firstform; - struct curl_httppost *file; CURLcode result = CURLE_OK; + curl_mime *form = NULL; + curl_mimepart *part; + struct curl_httppost *file; - curl_off_t size = 0; /* support potentially ENORMOUS formposts */ - char *boundary; - char *fileboundary = NULL; - struct curl_slist *curList; - - *finalform = NULL; /* default form is empty */ + Curl_mime_cleanpart(finalform); /* default form is empty */ if(!post) return result; /* no input => no output! */ - boundary = formboundary(data); - if(!boundary) - return CURLE_OUT_OF_MEMORY; - - /* Make the first line of the output */ - result = AddFormDataf(&form, NULL, - "%s; boundary=%s\r\n", - custom_content_type?custom_content_type: - "Content-Type: multipart/form-data", - boundary); - - if(result) { - free(boundary); - return result; - } - /* we DO NOT include that line in the total size of the POST, since it'll be - part of the header! */ - - firstform = form; - - do { - - if(size) { - result = AddFormDataf(&form, &size, "\r\n"); - if(result) - break; - } - - /* boundary */ - result = AddFormDataf(&form, &size, "--%s\r\n", boundary); - if(result) - break; - - /* Maybe later this should be disabled when a custom_content_type is - passed, since Content-Disposition is not meaningful for all multipart - types. - */ - result = AddFormDataf(&form, &size, - "Content-Disposition: form-data; name=\""); - if(result) - break; - - result = AddFormData(&form, FORM_DATA, post->name, post->namelength, - &size); - if(result) - break; + form = curl_mime_init(data); + if(!form) + result = CURLE_OUT_OF_MEMORY; - result = AddFormDataf(&form, &size, "\""); - if(result) - break; + 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) { - /* If used, this is a link to more file names, we must then do - the magic to include several files with the same field name */ - - free(fileboundary); - fileboundary = formboundary(data); - if(!fileboundary) { + part = curl_mime_addpart(form); + if(!part) result = CURLE_OUT_OF_MEMORY; - break; + if(!result) + result = setname(part, post->name, post->namelength); + if(!result) { + multipart = curl_mime_init(data); + if(!multipart) + result = CURLE_OUT_OF_MEMORY; } - - result = AddFormDataf(&form, &size, - "\r\nContent-Type: multipart/mixed;" - " boundary=%s\r\n", - fileboundary); - if(result) - break; + if(!result) + result = curl_mime_subparts(part, multipart); } - file = post; - - do { - - /* If 'showfilename' is set, that is a faked name passed on to us - to use to in the formpost. If that is not set, the actually used - local file name should be added. */ - - if(post->more) { - /* if multiple-file */ - result = AddFormDataf(&form, &size, - "\r\n--%s\r\nContent-Disposition: " - "attachment", - fileboundary); - if(result) - break; - result = formdata_add_filename(file, &form, &size); - if(result) - break; - } - else if(post->flags & (HTTPPOST_FILENAME|HTTPPOST_BUFFER| - HTTPPOST_CALLBACK)) { - /* it should be noted that for the HTTPPOST_FILENAME and - HTTPPOST_CALLBACK cases the ->showfilename struct member is always - assigned at this point */ - if(post->showfilename || (post->flags & HTTPPOST_FILENAME)) { - result = formdata_add_filename(post, &form, &size); - } - - if(result) - break; - } - - if(file->contenttype) { - /* we have a specified type */ - result = AddFormDataf(&form, &size, - "\r\nContent-Type: %s", - file->contenttype); - if(result) - break; - } - - curList = file->contentheader; - while(curList) { - /* Process the additional headers specified for this form */ - result = AddFormDataf(&form, &size, "\r\n%s", curList->data); - if(result) - break; - curList = curList->next; - } - if(result) - break; - - result = AddFormDataf(&form, &size, "\r\n\r\n"); - if(result) - break; + /* 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; - if((post->flags & HTTPPOST_FILENAME) || - (post->flags & HTTPPOST_READFILE)) { - /* we should include the contents from the specified file */ - FILE *fileread; - - fileread = !strcmp("-", file->contents)? - stdin:fopen(file->contents, "rb"); /* binary read for win32 */ - - /* - * VMS: This only allows for stream files on VMS. Stream files are - * OK, as are FIXED & VAR files WITHOUT implied CC For implied CC, - * every record needs to have a \n appended & 1 added to SIZE - */ - - if(fileread) { - if(fileread != stdin) { - /* close the file */ - fclose(fileread); - /* add the file name only - for later reading from this */ - result = AddFormData(&form, FORM_FILE, file->contents, 0, &size); - } - else { - /* When uploading from stdin, we can't know the size of the file, - * thus must read the full file as before. We *could* use chunked - * transfer-encoding, but that only works for HTTP 1.1 and we - * can't be sure we work with such a server. - */ - size_t nread; - char buffer[512]; - while((nread = fread(buffer, 1, sizeof(buffer), fileread)) != 0) { - result = AddFormData(&form, FORM_CONTENT, buffer, nread, &size); - if(result || feof(fileread) || ferror(fileread)) - break; - } + /* 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 will not 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 filename 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 { - if(data) - failf(data, "couldn't open file \"%s\"", file->contents); - *finalform = NULL; - result = CURLE_READ_ERROR; + size_t uclen; + if(!clen) + uclen = CURL_ZERO_TERMINATED; + else + uclen = (size_t)clen; + result = curl_mime_data(part, post->contents, uclen); } } - else if(post->flags & HTTPPOST_BUFFER) - /* include contents of buffer */ - result = AddFormData(&form, FORM_CONTENT, post->buffer, - post->bufferlength, &size); - else if(post->flags & HTTPPOST_CALLBACK) - /* the contents should be read with the callback and the size is set - with the contentslength */ - result = AddFormData(&form, FORM_CALLBACK, post->userp, - post->flags&CURL_HTTPPOST_LARGE? - post->contentlen:post->contentslength, &size); - else - /* include the contents we got */ - result = AddFormData(&form, FORM_CONTENT, post->contents, - post->flags&CURL_HTTPPOST_LARGE? - post->contentlen:post->contentslength, &size); - file = file->more; - } while(file && !result); /* for each specified file for this field */ - - if(result) - break; - if(post->more) { - /* this was a multiple-file inclusion, make a termination file - boundary: */ - result = AddFormDataf(&form, &size, - "\r\n--%s--", - fileboundary); - if(result) - break; + /* Set fake filename. */ + if(!result && post->showfilename) + if(post->more || (post->flags & (HTTPPOST_FILENAME | HTTPPOST_BUFFER | + HTTPPOST_CALLBACK))) + result = curl_mime_filename(part, post->showfilename); } - post = post->next; - } while(post); /* for each field */ - - /* end-boundary for everything */ - if(!result) - result = AddFormDataf(&form, &size, "\r\n--%s--\r\n", boundary); - - if(result) { - Curl_formclean(&firstform); - free(fileboundary); - free(boundary); - return result; } - *sizep = size; - - free(fileboundary); - free(boundary); - - *finalform = firstform; + if(result) + Curl_mime_cleanpart(finalform); return result; } -/* - * Curl_FormInit() inits the struct 'form' points to with the 'formdata' - * and resets the 'sent' counter. - */ -int Curl_FormInit(struct Form *form, struct FormData *formdata) -{ - if(!formdata) - return 1; /* error */ - - form->data = formdata; - form->sent = 0; - form->fp = NULL; - form->fread_func = ZERO_NULL; - - return 0; -} - -#ifndef __VMS -# define fopen_read fopen #else - /* - * 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. - * - */ -# define fopen_read vmsfopenread -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"); - } -} -#endif - -/* - * readfromfile() - * - * The read callback that this function may use can return a value larger than - * 'size' (which then this function returns) that indicates a problem and it - * must be properly dealt with - */ -static size_t readfromfile(struct Form *form, char *buffer, - size_t size) -{ - size_t nread; - bool callback = (form->data->type == FORM_CALLBACK)?TRUE:FALSE; - - if(callback) { - if(form->fread_func == ZERO_NULL) - return 0; - else - nread = form->fread_func(buffer, 1, size, form->data->line); - } - else { - if(!form->fp) { - /* this file hasn't yet been opened */ - form->fp = fopen_read(form->data->line, "rb"); /* b is for binary */ - if(!form->fp) - return (size_t)-1; /* failure */ - } - nread = fread(buffer, 1, size, form->fp); - } - if(!nread) { - /* this is the last chunk from the file, move on */ - if(form->fp) { - fclose(form->fp); - form->fp = NULL; - } - form->data = form->data->next; - } - - return nread; -} - -/* - * Curl_FormReader() is the fread() emulation function that will be used to - * deliver the formdata to the transfer loop and then sent away to the peer. - */ -size_t Curl_FormReader(char *buffer, - size_t size, - size_t nitems, - FILE *mydata) -{ - struct Form *form; - size_t wantedsize; - size_t gotsize = 0; - - form=(struct Form *)mydata; - - wantedsize = size * nitems; - - if(!form->data) - return 0; /* nothing, error, empty */ - - if((form->data->type == FORM_FILE) || - (form->data->type == FORM_CALLBACK)) { - gotsize = readfromfile(form, buffer, wantedsize); - - if(gotsize) - /* If positive or -1, return. If zero, continue! */ - return gotsize; - } - do { - - if((form->data->length - form->sent) > wantedsize - gotsize) { - - memcpy(buffer + gotsize, form->data->line + form->sent, - wantedsize - gotsize); - - form->sent += wantedsize-gotsize; - - return wantedsize; - } - - memcpy(buffer+gotsize, - form->data->line + form->sent, - (form->data->length - form->sent) ); - gotsize += form->data->length - form->sent; - - form->sent = 0; - - form->data = form->data->next; /* advance */ - - } while(form->data && (form->data->type < FORM_CALLBACK)); - /* If we got an empty line and we have more data, we proceed to the next - line immediately to avoid returning zero before we've reached the end. */ - - return gotsize; -} - -/* - * Curl_formpostheader() returns the first line of the formpost, the - * request-header part (which is not part of the request-body like the rest of - * the post). - */ -char *Curl_formpostheader(void *formp, size_t *len) -{ - char *header; - struct Form *form=(struct Form *)formp; - - if(!form->data) - return NULL; /* nothing, ERROR! */ - - header = form->data->line; - *len = form->data->length; - - form->data = form->data->next; /* advance */ - - return header; -} - -/* - * formboundary() creates a suitable boundary string and returns an allocated - * one. - */ -static char *formboundary(struct Curl_easy *data) -{ - /* 24 dashes and 16 hexadecimal digits makes 64 bit (18446744073709551615) - combinations */ - unsigned int rnd[2]; - CURLcode result = Curl_rand(data, &rnd[0], 2); - if(result) - return NULL; - - return aprintf("------------------------%08x%08x", rnd[0], rnd[1]); -} - -#else /* CURL_DISABLE_HTTP */ +/* if disabled */ CURLFORMcode curl_formadd(struct curl_httppost **httppost, struct curl_httppost **last_post, ...) @@ -1601,8 +954,7 @@ int curl_formget(struct curl_httppost *form, void *arg, void curl_formfree(struct curl_httppost *form) { (void)form; - /* does nothing HTTP is disabled */ + /* Nothing to do. */ } - -#endif /* !defined(CURL_DISABLE_HTTP) */ +#endif /* if disabled */ diff --git a/contrib/curl/lib/formdata.h b/contrib/curl/lib/formdata.h index 69629f628c..2ed96ffcf6 100644 --- a/contrib/curl/lib/formdata.h +++ b/contrib/curl/lib/formdata.h @@ -7,11 +7,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2016, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -20,80 +20,40 @@ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY * KIND, either express or implied. * + * SPDX-License-Identifier: curl + * ***************************************************************************/ -enum formtype { - FORM_DATAMEM, /* already allocated FORM_DATA memory */ - FORM_DATA, /* form metadata (convert to network encoding if necessary) */ - FORM_CONTENT, /* form content (never convert) */ - FORM_CALLBACK, /* 'line' points to the custom pointer we pass to the callback - */ - FORM_FILE /* 'line' points to a file name we should read from - to create the form data (never convert) */ -}; +#include "curl_setup.h" -/* plain and simple linked list with lines to send */ -struct FormData { - struct FormData *next; - enum formtype type; - char *line; - size_t length; -}; - -struct Form { - struct FormData *data; /* current form line to send */ - size_t sent; /* number of bytes of the current line that has - already been sent in a previous invoke */ - FILE *fp; /* file to read from */ - curl_read_callback fread_func; /* fread callback pointer */ -}; +#ifndef CURL_DISABLE_FORM_API /* used by FormAdd for temporary storage */ -typedef struct FormInfo { +struct FormInfo { char *name; - bool name_alloc; size_t namelength; char *value; - bool value_alloc; curl_off_t contentslength; char *contenttype; - bool contenttype_alloc; 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 */ - bool showfilename_alloc; + char *showfilename; /* The filename to show. If not set, the actual + filename will be used */ char *userp; /* pointer for the read callback */ struct curl_slist *contentheader; struct FormInfo *more; -} FormInfo; - -int Curl_FormInit(struct Form *form, struct FormData *formdata); + bool name_alloc; + bool value_alloc; + bool contenttype_alloc; + bool showfilename_alloc; +}; CURLcode Curl_getformdata(struct Curl_easy *data, - struct FormData **, + curl_mimepart *, struct curl_httppost *post, - const char *custom_contenttype, - curl_off_t *size); - -/* fread() emulation */ -size_t Curl_FormReader(char *buffer, - size_t size, - size_t nitems, - FILE *mydata); - -/* - * Curl_formpostheader() returns the first line of the formpost, the - * request-header part (which is not part of the request-body like the rest of - * the post). - */ -char *Curl_formpostheader(void *formp, size_t *len); - -char *Curl_FormBoundary(void); - -void Curl_formclean(struct FormData **); + curl_read_callback fread_func); +#endif /* CURL_DISABLE_FORM_API */ -CURLcode Curl_formconvert(struct Curl_easy *, struct FormData *); #endif /* HEADER_CURL_FORMDATA_H */ diff --git a/contrib/curl/lib/ftp.c b/contrib/curl/lib/ftp.c index aa4d5ac2d7..fbc006ec7e 100644 --- a/contrib/curl/lib/ftp.c +++ b/contrib/curl/lib/ftp.c @@ -5,11 +5,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2016, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -18,6 +18,8 @@ * 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" @@ -30,9 +32,6 @@ #ifdef HAVE_ARPA_INET_H #include #endif -#ifdef HAVE_UTSNAME_H -#include -#endif #ifdef HAVE_NETDB_H #include #endif @@ -41,11 +40,6 @@ #include #endif -#if (defined(NETWARE) && defined(__NOVELL_LIBC__)) -#undef in_addr_t -#define in_addr_t unsigned long -#endif - #include #include "urldata.h" #include "sendf.h" @@ -55,14 +49,16 @@ #include "transfer.h" #include "escape.h" #include "http.h" /* for HTTP proxy tunnel stuff */ -#include "socks.h" #include "ftp.h" #include "fileinfo.h" #include "ftplistparser.h" -#include "curl_sec.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" @@ -72,11 +68,11 @@ #include "sockaddr.h" /* required for Curl_sockaddr_storage */ #include "multiif.h" #include "url.h" -#include "strcase.h" #include "speedcheck.h" #include "warnless.h" #include "http_proxy.h" -#include "non-ascii.h" +#include "socks.h" +#include "strdup.h" /* The last 3 #include files should be in this order */ #include "curl_printf.h" #include "curl_memory.h" @@ -89,82 +85,155 @@ #define INET_ADDRSTRLEN 16 #endif +/* 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])) + #ifdef CURL_DISABLE_VERBOSE_STRINGS #define ftp_pasv_verbose(a,b,c,d) Curl_nop_stmt +#define FTP_CSTATE(c) "" +#define FTP_DSTATE(d) "" +#else /* CURL_DISABLE_VERBOSE_STRINGS */ + /* for tracing purposes */ +static const char * const ftp_state_names[]={ + "STOP", + "WAIT220", + "AUTH", + "USER", + "PASS", + "ACCT", + "PBSZ", + "PROT", + "CCC", + "PWD", + "SYST", + "NAMEFMT", + "QUOTE", + "RETR_PREQUOTE", + "STOR_PREQUOTE", + "POSTQUOTE", + "CWD", + "MKD", + "MDTM", + "TYPE", + "LIST_TYPE", + "RETR_TYPE", + "STOR_TYPE", + "SIZE", + "RETR_SIZE", + "STOR_SIZE", + "REST", + "RETR_REST", + "PORT", + "PRET", + "PASV", + "LIST", + "RETR", + "STOR", + "QUIT" +}; +#define FTP_CSTATE(c) ((c)? ftp_state_names[(c)->proto.ftpc.state] : "???") +#define FTP_DSTATE(d) (((d) && (d)->conn)? \ + ftp_state_names[(d)->conn->proto.ftpc.state] : "???") + +#endif /* !CURL_DISABLE_VERBOSE_STRINGS */ + +/* This is the ONLY way to change FTP state! */ +static void _ftp_state(struct Curl_easy *data, + ftpstate newstate +#ifdef DEBUGBUILD + , int lineno #endif + ) +{ + struct connectdata *conn = data->conn; + struct ftp_conn *ftpc = &conn->proto.ftpc; -/* Local API functions */ -#ifndef DEBUGBUILD -static void _state(struct connectdata *conn, - ftpstate newstate); -#define state(x,y) _state(x,y) +#if defined(CURL_DISABLE_VERBOSE_STRINGS) +#ifdef DEBUGBUILD + (void)lineno; +#endif +#else /* CURL_DISABLE_VERBOSE_STRINGS */ + if(ftpc->state != newstate) +#ifdef DEBUGBUILD + CURL_TRC_FTP(data, "[%s] -> [%s] (line %d)", FTP_DSTATE(data), + ftp_state_names[newstate], lineno); #else -static void _state(struct connectdata *conn, - ftpstate newstate, - int lineno); -#define state(x,y) _state(x,y,__LINE__) + CURL_TRC_FTP(data, "[%s] -> [%s]", FTP_DSTATE(data), + ftp_state_names[newstate]); #endif +#endif /* !CURL_DISABLE_VERBOSE_STRINGS */ -static CURLcode ftp_sendquote(struct connectdata *conn, + ftpc->state = newstate; +} + + +/* Local API functions */ +#ifndef DEBUGBUILD +#define ftp_state(x,y) _ftp_state(x,y) +#else /* !DEBUGBUILD */ +#define ftp_state(x,y) _ftp_state(x,y,__LINE__) +#endif /* DEBUGBUILD */ + +static CURLcode ftp_sendquote(struct Curl_easy *data, + struct connectdata *conn, struct curl_slist *quote); -static CURLcode ftp_quit(struct connectdata *conn); -static CURLcode ftp_parse_url_path(struct connectdata *conn); -static CURLcode ftp_regular_transfer(struct connectdata *conn, bool *done); +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 connectdata *conn, - Curl_addrinfo *ai, - char *newhost, /* ascii version */ +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 connectdata *conn); -static CURLcode ftp_state_mdtm(struct connectdata *conn); -static CURLcode ftp_state_quote(struct connectdata *conn, +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 connectdata *conn, +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 connectdata *conn, bool *done); -static CURLcode ftp_done(struct connectdata *conn, +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 connectdata *conn, bool *done); -static CURLcode ftp_disconnect(struct connectdata *conn, bool dead_connection); -static CURLcode ftp_do_more(struct connectdata *conn, int *completed); -static CURLcode ftp_multi_statemach(struct connectdata *conn, bool *done); -static int ftp_getsock(struct connectdata *conn, curl_socket_t *socks, - int numsocks); -static int ftp_domore_getsock(struct connectdata *conn, curl_socket_t *socks, - int numsocks); -static CURLcode ftp_doing(struct connectdata *conn, +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 connectdata * conn); - -static CURLcode init_wc_data(struct connectdata *conn); -static CURLcode wc_statemach(struct connectdata *conn); - +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 connectdata *conn, curl_off_t filesize); - -static CURLcode ftp_readresp(curl_socket_t sockfd, +static CURLcode ftp_state_retr(struct Curl_easy *data, curl_off_t filesize); +static CURLcode ftp_readresp(struct Curl_easy *data, + int sockindex, struct pingpong *pp, int *ftpcode, size_t *size); -static CURLcode ftp_dophase_done(struct connectdata *conn, +static CURLcode ftp_dophase_done(struct Curl_easy *data, bool connected); -/* easy-to-use macro: */ -#define PPSENDF(x,y,z) result = Curl_pp_sendf(x,y,z); \ - if(result) \ - return result - - /* * FTP protocol handler. */ const struct Curl_handler Curl_handler_ftp = { - "FTP", /* scheme */ + "ftp", /* scheme */ ftp_setup_connection, /* setup_connection */ ftp_do, /* do_it */ ftp_done, /* done */ @@ -177,11 +246,16 @@ const struct Curl_handler Curl_handler_ftp = { ftp_domore_getsock, /* domore_getsock */ ZERO_NULL, /* perform_getsock */ ftp_disconnect, /* disconnect */ - ZERO_NULL, /* readwrite */ + ZERO_NULL, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ + ZERO_NULL, /* connection_check */ + ZERO_NULL, /* attach connection */ PORT_FTP, /* defport */ CURLPROTO_FTP, /* protocol */ - PROTOPT_DUAL | PROTOPT_CLOSEACTION | PROTOPT_NEEDSPWD - | PROTOPT_NOURLQUERY /* flags */ + CURLPROTO_FTP, /* family */ + PROTOPT_DUAL | PROTOPT_CLOSEACTION | PROTOPT_NEEDSPWD | + PROTOPT_NOURLQUERY | PROTOPT_PROXY_AS_HTTP | + PROTOPT_WILDCARD /* flags */ }; @@ -191,7 +265,7 @@ const struct Curl_handler Curl_handler_ftp = { */ const struct Curl_handler Curl_handler_ftps = { - "FTPS", /* scheme */ + "ftps", /* scheme */ ftp_setup_connection, /* setup_connection */ ftp_do, /* do_it */ ftp_done, /* done */ @@ -204,75 +278,23 @@ const struct Curl_handler Curl_handler_ftps = { ftp_domore_getsock, /* domore_getsock */ ZERO_NULL, /* perform_getsock */ ftp_disconnect, /* disconnect */ - ZERO_NULL, /* readwrite */ + ZERO_NULL, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ + 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 /* flags */ -}; -#endif - -#ifndef CURL_DISABLE_HTTP -/* - * HTTP-proxyed FTP protocol handler. - */ - -static const struct Curl_handler Curl_handler_ftp_proxy = { - "FTP", /* scheme */ - Curl_http_setup_conn, /* setup_connection */ - Curl_http, /* do_it */ - Curl_http_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 */ - PORT_FTP, /* defport */ - CURLPROTO_HTTP, /* protocol */ - PROTOPT_NONE /* flags */ -}; - - -#ifdef USE_SSL -/* - * HTTP-proxyed FTPS protocol handler. - */ - -static const struct Curl_handler Curl_handler_ftps_proxy = { - "FTPS", /* scheme */ - Curl_http_setup_conn, /* setup_connection */ - Curl_http, /* do_it */ - Curl_http_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 */ - PORT_FTPS, /* defport */ - CURLPROTO_HTTP, /* protocol */ - PROTOPT_NONE /* flags */ + PROTOPT_NEEDSPWD | PROTOPT_NOURLQUERY | PROTOPT_WILDCARD /* flags */ }; #endif -#endif -static void close_secondarysocket(struct connectdata *conn) +static void close_secondarysocket(struct Curl_easy *data) { - if(CURL_SOCKET_BAD != conn->sock[SECONDARYSOCKET]) { - Curl_closesocket(conn, conn->sock[SECONDARYSOCKET]); - conn->sock[SECONDARYSOCKET] = CURL_SOCKET_BAD; - } - conn->bits.tcpconnect[SECONDARYSOCKET] = FALSE; - conn->tunnel_state[SECONDARYSOCKET] = TUNNEL_INIT; + CURL_TRC_FTP(data, "[%s] closing DATA connection", FTP_DSTATE(data)); + Curl_conn_close(data, SECONDARYSOCKET); + Curl_conn_cf_discard_all(data, data->conn, SECONDARYSOCKET); } /* @@ -289,11 +311,11 @@ static void close_secondarysocket(struct connectdata *conn) static void freedirs(struct ftp_conn *ftpc) { - int i; if(ftpc->dirs) { - for(i=0; i < ftpc->dirdepth; i++) { + int i; + for(i = 0; i < ftpc->dirdepth; i++) { free(ftpc->dirs[i]); - ftpc->dirs[i]=NULL; + ftpc->dirs[i] = NULL; } free(ftpc->dirs); ftpc->dirs = NULL; @@ -305,18 +327,96 @@ static void freedirs(struct ftp_conn *ftpc) Curl_safefree(ftpc->newhost); } -/* Returns non-zero if the given string contains CR (\r) or LF (\n), - which are not allowed within RFC 959 . - Note: The input string is in the client's encoding which might - not be ASCII, so escape sequences \r & \n must be used instead - of hex values 0x0d & 0x0a. -*/ -static bool isBadFtpString(const char *string) +#ifdef CURL_PREFER_LF_LINEENDS +/*********************************************************************** + * + * Lineend Conversions + * On ASCII transfers, e.g. directory listings, we might get lines + * ending in '\r\n' and we prefer just '\n'. + * We might also get a lonely '\r' which we convert into a '\n'. + */ +struct ftp_cw_lc_ctx { + struct Curl_cwriter super; + bool newline_pending; +}; + +static CURLcode ftp_cw_lc_write(struct Curl_easy *data, + struct Curl_cwriter *writer, int type, + const char *buf, size_t blen) { - return ((NULL != strchr(string, '\r')) || - (NULL != strchr(string, '\n'))) ? TRUE : FALSE; + static const char nl = '\n'; + struct ftp_cw_lc_ctx *ctx = writer->ctx; + + if(!(type & CLIENTWRITE_BODY) || + data->conn->proto.ftpc.transfertype != 'A') + return Curl_cwriter_write(data, writer->next, type, buf, blen); + + /* ASCII mode BODY data, convert lineends */ + while(blen) { + /* do not pass EOS when writing parts */ + int chunk_type = (type & ~CLIENTWRITE_EOS); + const char *cp; + size_t chunk_len; + CURLcode result; + + if(ctx->newline_pending) { + if(buf[0] != '\n') { + /* previous chunk ended in '\r' and we do not see a '\n' in this one, + * need to write a newline. */ + result = Curl_cwriter_write(data, writer->next, chunk_type, &nl, 1); + if(result) + return result; + } + /* either we just wrote the newline or it is part of the next + * chunk of bytes we write. */ + ctx->newline_pending = FALSE; + } + + cp = memchr(buf, '\r', blen); + if(!cp) + break; + + /* write the bytes before the '\r', excluding the '\r' */ + chunk_len = cp - buf; + if(chunk_len) { + result = Curl_cwriter_write(data, writer->next, chunk_type, + buf, chunk_len); + if(result) + return result; + } + /* skip the '\r', we now have a newline pending */ + buf = cp + 1; + blen = blen - chunk_len - 1; + ctx->newline_pending = TRUE; + } + + /* Any remaining data does not contain a '\r' */ + if(blen) { + DEBUGASSERT(!ctx->newline_pending); + return Curl_cwriter_write(data, writer->next, type, buf, blen); + } + else if(type & CLIENTWRITE_EOS) { + /* EndOfStream, if we have a trailing cr, now is the time to write it */ + if(ctx->newline_pending) { + ctx->newline_pending = FALSE; + return Curl_cwriter_write(data, writer->next, type, &nl, 1); + } + /* Always pass on the EOS type indicator */ + return Curl_cwriter_write(data, writer->next, type, buf, 0); + } + return CURLE_OK; } +static const struct Curl_cwtype ftp_cw_lc = { + "ftp-lineconv", + NULL, + Curl_cwriter_def_init, + ftp_cw_lc_write, + Curl_cwriter_def_close, + sizeof(struct ftp_cw_lc_ctx) +}; + +#endif /* CURL_PREFER_LF_LINEENDS */ /*********************************************************************** * * AcceptServerConnect() @@ -325,48 +425,54 @@ static bool isBadFtpString(const char *string) * called to accept the connection and close the listening socket * */ -static CURLcode AcceptServerConnect(struct connectdata *conn) +static CURLcode AcceptServerConnect(struct Curl_easy *data) { - struct Curl_easy *data = conn->data; + struct connectdata *conn = data->conn; curl_socket_t sock = conn->sock[SECONDARYSOCKET]; curl_socket_t s = CURL_SOCKET_BAD; -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 struct Curl_sockaddr_storage add; #else struct sockaddr_in add; #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); + s = accept(sock, (struct sockaddr *) &add, &size); } - Curl_closesocket(conn, sock); /* close the first socket */ if(CURL_SOCKET_BAD == s) { failf(data, "Error accept()ing server connect"); return CURLE_FTP_PORT_FAILED; } - infof(data, "Connection accepted from server\n"); + 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; - conn->sock[SECONDARYSOCKET] = s; (void)curlx_nonblock(s, TRUE); /* enable non-blocking */ - conn->sock_accepted[SECONDARYSOCKET] = TRUE; + /* Replace any filter on SECONDARY with one listening on this socket */ + result = Curl_conn_tcp_accepted_set(data, conn, SECONDARYSOCKET, &s); + if(result) { + sclose(s); + 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(conn); + close_secondarysocket(data); return CURLE_ABORTED_BY_CALLBACK; } } @@ -384,26 +490,26 @@ static CURLcode AcceptServerConnect(struct connectdata *conn) * Curl_pgrsTime(..., TIMER_STARTACCEPT); * */ -static time_t ftp_timeleft_accept(struct Curl_easy *data) +static timediff_t ftp_timeleft_accept(struct Curl_easy *data) { - time_t timeout_ms = DEFAULT_ACCEPT_TIMEOUT; - time_t other; - struct timeval now; + 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_tvnow(); + now = Curl_now(); /* check if the generic timeout possibly is set shorter */ - other = Curl_timeleft(data, &now, FALSE); + 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_tvdiff(now, data->progress.t_acceptdata); + timeout_ms -= Curl_timediff(now, data->progress.t_acceptdata); if(!timeout_ms) /* avoid returning 0 as that means no timeout! */ return -1; @@ -422,22 +528,23 @@ static time_t ftp_timeleft_accept(struct Curl_easy *data) * connection for a negative response regarding a failure in connecting * */ -static CURLcode ReceivedServerConnect(struct connectdata *conn, bool *received) +static CURLcode ReceivedServerConnect(struct Curl_easy *data, bool *received) { - struct Curl_easy *data = conn->data; + 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; - time_t timeout_ms; + int socketstate = 0; + timediff_t timeout_ms; ssize_t nread; int ftpcode; + bool response = FALSE; *received = FALSE; timeout_ms = ftp_timeleft_accept(data); - infof(data, "Checking for server connect\n"); + 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"); @@ -445,17 +552,21 @@ static CURLcode ReceivedServerConnect(struct connectdata *conn, bool *received) } /* First check whether there is a cached response from server */ - if(pp->cache_size && pp->cache && pp->cache[0] > '3') { + if(Curl_dyn_len(&pp->recvbuf) && (*Curl_dyn_ptr(&pp->recvbuf) > '3')) { /* Data connection could not be established, let's return */ - infof(data, "There is negative response in cache while serv connect\n"); - Curl_GetFTPResponse(&nread, conn, &ftpcode); + 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); + if(pp->overflow) + /* there is pending control data still in the buffer to read */ + response = TRUE; + else + socketstate = Curl_socket_check(ctrl_sock, data_sock, CURL_SOCKET_BAD, 0); /* see if the connection request is already here */ - switch(result) { + switch(socketstate) { case -1: /* error */ /* let's die here */ failf(data, "Error while waiting for server connect"); @@ -463,23 +574,47 @@ static CURLcode ReceivedServerConnect(struct connectdata *conn, bool *received) 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\n"); + if(socketstate & 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\n"); - Curl_GetFTPResponse(&nread, conn, &ftpcode); + else if(socketstate & CURL_CSELECT_IN) + response = TRUE; + break; + } + if(response) { + infof(data, "Ctrl conn has data while waiting for data conn"); + if(pp->overflow > 3) { + char *r = Curl_dyn_ptr(&pp->recvbuf); + + DEBUGASSERT((pp->overflow + pp->nfinal) <= + Curl_dyn_len(&pp->recvbuf)); + /* move over the most recently handled response line */ + r += pp->nfinal; + + if(LASTLINE(r)) { + int status = curlx_sltosi(strtol(r, NULL, 10)); + if(status == 226) { + /* funny timing situation where we get the final message on the + control connection before traffic on the data connection has been + noticed. Leave the 226 in there and use this as a trigger to read + the data socket. */ + infof(data, "Got 226 before data activity"); + *received = TRUE; + return CURLE_OK; + } + } + } - if(ftpcode/100 > 3) - return CURLE_FTP_ACCEPT_FAILED; + (void)Curl_GetFTPResponse(data, &nread, &ftpcode); - return CURLE_WEIRD_SERVER_REPLY; - } + infof(data, "FTP code: %03d", ftpcode); - break; - } /* switch() */ + if(ftpcode/100 > 3) + return CURLE_FTP_ACCEPT_FAILED; + + return CURLE_WEIRD_SERVER_REPLY; + } return CURLE_OK; } @@ -493,44 +628,43 @@ static CURLcode ReceivedServerConnect(struct connectdata *conn, bool *received) * setup transfer parameters and initiate the data transfer. * */ -static CURLcode InitiateTransfer(struct connectdata *conn) +static CURLcode InitiateTransfer(struct Curl_easy *data) { - struct Curl_easy *data = conn->data; - struct FTP *ftp = data->req.protop; CURLcode result = CURLE_OK; + struct connectdata *conn = data->conn; + bool connected; - if(conn->bits.ftp_use_data_ssl) { - /* since we only have a plaintext TCP connection here, we must now - * do the TLS stuff */ - infof(data, "Doing the SSL/TLS handshake on the data stream\n"); - result = Curl_ssl_connect(conn, SECONDARYSOCKET); + CURL_TRC_FTP(data, "InitiateTransfer()"); + if(conn->bits.ftp_use_data_ssl && data->set.ftp_use_port && + !Curl_conn_is_ssl(conn, SECONDARYSOCKET)) { + result = Curl_ssl_cfilter_add(data, conn, SECONDARYSOCKET); 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) { - *(ftp->bytecountp)=0; - - /* When we know we're uploading a specified file, we can get the file + /* When we know we are 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_sndbuf_init(conn->sock[SECONDARYSOCKET]); - Curl_setup_transfer(conn, -1, -1, FALSE, NULL, /* no download */ - SECONDARYSOCKET, ftp->bytecountp); + /* FTP upload, shutdown DATA, ignore shutdown errors, as we rely + * on the server response on the CONTROL connection. */ + Curl_xfer_setup2(data, CURL_XFER_SEND, -1, TRUE, TRUE); } else { - /* FTP download: */ - Curl_setup_transfer(conn, SECONDARYSOCKET, - conn->proto.ftpc.retr_size_saved, FALSE, - ftp->bytecountp, -1, NULL); /* no upload here */ + /* FTP download, shutdown, do not ignore errors */ + Curl_xfer_setup2(data, CURL_XFER_RECV, + conn->proto.ftpc.retr_size_saved, TRUE, FALSE); } conn->proto.ftpc.pp.pending_resp = TRUE; /* expect server response */ - state(conn, FTP_STOP); + ftp_state(data, FTP_STOP); return CURLE_OK; } @@ -539,19 +673,18 @@ static CURLcode InitiateTransfer(struct connectdata *conn) * * AllowServerConnect() * - * When we've issue the PORT command, we have told the server to connect to + * When we have 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 connectdata *conn, bool *connected) +static CURLcode AllowServerConnect(struct Curl_easy *data, bool *connected) { - struct Curl_easy *data = conn->data; - time_t timeout_ms; + timediff_t timeout_ms; CURLcode result = CURLE_OK; *connected = FALSE; - infof(data, "Preparing for accepting server on data port\n"); + infof(data, "Preparing for accepting server on data port"); /* Save the time we start accepting server connect */ Curl_pgrsTime(data, TIMER_STARTACCEPT); @@ -560,47 +693,40 @@ static CURLcode AllowServerConnect(struct connectdata *conn, bool *connected) 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; + result = CURLE_FTP_ACCEPT_TIMEOUT; + goto out; } /* see if the connection request is already here */ - result = ReceivedServerConnect(conn, connected); + result = ReceivedServerConnect(data, connected); if(result) - return result; + goto out; if(*connected) { - result = AcceptServerConnect(conn); + result = AcceptServerConnect(data); if(result) - return result; + goto out; - result = InitiateTransfer(conn); + result = InitiateTransfer(data); if(result) - return result; + goto out; } else { /* Add timeout to multi handle and break out of the loop */ - if(!result && *connected == FALSE) { - if(data->set.accepttimeout > 0) - Curl_expire(data, data->set.accepttimeout); - else - Curl_expire(data, DEFAULT_ACCEPT_TIMEOUT); - } + Curl_expire(data, data->set.accepttimeout ? + data->set.accepttimeout : DEFAULT_ACCEPT_TIMEOUT, + EXPIRE_FTP_ACCEPT); } +out: + CURL_TRC_FTP(data, "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 connectdata *conn, char *line, size_t len, - int *code) +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)) { @@ -611,42 +737,40 @@ static bool ftp_endofresp(struct connectdata *conn, char *line, size_t len, return FALSE; } -static CURLcode ftp_readresp(curl_socket_t sockfd, +static CURLcode ftp_readresp(struct Curl_easy *data, + int sockindex, struct pingpong *pp, int *ftpcode, /* return the ftp-code if done */ size_t *size) /* size of the response */ { - struct connectdata *conn = pp->conn; - struct Curl_easy *data = conn->data; -#ifdef HAVE_GSSAPI - char * const buf = data->state.buffer; -#endif - CURLcode result = CURLE_OK; int code; + CURLcode result = Curl_pp_readresp(data, sockindex, pp, &code, size); - result = Curl_pp_readresp(sockfd, pp, &code, size); - -#if defined(HAVE_GSSAPI) - /* handle the security-oriented responses 6xx ***/ - /* FIXME: some errorchecking perhaps... ***/ - switch(code) { - case 631: - code = Curl_sec_read_msg(conn, buf, PROT_SAFE); - break; - case 632: - code = Curl_sec_read_msg(conn, buf, PROT_PRIVATE); - break; - case 633: - code = Curl_sec_read_msg(conn, buf, PROT_CONFIDENTIAL); - break; - default: - /* normal ftp stuff we pass through! */ - break; +#ifdef HAVE_GSSAPI + { + struct connectdata *conn = data->conn; + char * const buf = Curl_dyn_ptr(&data->conn->proto.ftpc.pp.recvbuf); + + /* 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; + data->info.httpcode = code; if(ftpcode) *ftpcode = code; @@ -659,8 +783,8 @@ static CURLcode ftp_readresp(curl_socket_t sockfd, * This response code can come at any point so having it treated * generically is a good idea. */ - infof(data, "We got a 421 - timeout!\n"); - state(conn, FTP_STOP); + infof(data, "We got a 421 - timeout"); + ftp_state(data, FTP_STOP); return CURLE_OPERATION_TIMEDOUT; } @@ -675,27 +799,27 @@ static CURLcode ftp_readresp(curl_socket_t sockfd, * */ -CURLcode Curl_GetFTPResponse(ssize_t *nreadp, /* return number of bytes read */ - struct connectdata *conn, +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. + * the OpenSSL read() does not 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]; - time_t timeout; /* timeout in milliseconds */ - time_t interval_ms; - struct Curl_easy *data = conn->data; 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; + int cache_skip = 0; + int value_to_be_ignored = 0; + + CURL_TRC_FTP(data, "getFTPResponse start"); if(ftpcode) *ftpcode = 0; /* 0 for errors */ @@ -703,13 +827,14 @@ CURLcode Curl_GetFTPResponse(ssize_t *nreadp, /* return number of bytes read */ /* make the pointer point to something for the rest of this function */ ftpcode = &value_to_be_ignored; - *nreadp=0; + *nreadp = 0; while(!*ftpcode && !result) { /* check and reset timeout value every lap */ - timeout = Curl_pp_state_timeout(pp); + timediff_t timeout = Curl_pp_state_timeout(data, pp, FALSE); + timediff_t interval_ms; - if(timeout <=0) { + if(timeout <= 0) { failf(data, "FTP response timeout"); return CURLE_OPERATION_TIMEDOUT; /* already too little time */ } @@ -725,199 +850,124 @@ CURLcode Curl_GetFTPResponse(ssize_t *nreadp, /* return number of bytes read */ * * 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 + * make sure we do not 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 + * the cache was not good enough to continue we must not just busy-loop * around this function. * */ - if(pp->cache && (cache_skip < 2)) { + if(Curl_dyn_len(&pp->recvbuf) && (cache_skip < 2)) { /* - * There's a cache left since before. We then skipping the wait for + * There is 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(conn, FIRSTSOCKET)) { - switch(SOCKET_READABLE(sockfd, interval_ms)) { - case -1: /* select() error, stop reading */ + else if(!Curl_conn_data_pending(data, FIRSTSOCKET)) { + curl_socket_t wsock = Curl_pp_needs_flush(data, pp) ? + sockfd : CURL_SOCKET_BAD; + int ev = Curl_socket_check(sockfd, CURL_SOCKET_BAD, wsock, interval_ms); + if(ev < 0) { failf(data, "FTP response aborted due to select/poll error: %d", SOCKERRNO); return CURLE_RECV_ERROR; - - case 0: /* timeout */ - if(Curl_pgrsUpdate(conn)) + } + else if(ev == 0) { + if(Curl_pgrsUpdate(data)) return CURLE_ABORTED_BY_CALLBACK; continue; /* just continue in our loop for the timeout duration */ + } + } - default: /* for clarity */ + if(Curl_pp_needs_flush(data, pp)) { + result = Curl_pp_flushsend(data, pp); + if(result) break; - } } - result = ftp_readresp(sockfd, pp, ftpcode, &nread); + + result = ftp_readresp(data, FIRSTSOCKET, pp, ftpcode, &nread); if(result) break; - if(!nread && pp->cache) + if(!nread && Curl_dyn_len(&pp->recvbuf)) /* 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; + cache_skip = 0; *nreadp += nread; - } /* while there's buffer left and loop is requested */ + } /* while there is buffer left and loop is requested */ pp->pending_resp = FALSE; + CURL_TRC_FTP(data, "getFTPResponse -> result=%d, nread=%zd, ftpcode=%d", + result, *nreadp, *ftpcode); 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 _state(struct connectdata *conn, - ftpstate newstate -#ifdef DEBUGBUILD - , int lineno -#endif - ) +static CURLcode ftp_state_user(struct Curl_easy *data, + struct connectdata *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(conn->data, "FTP %p (line %d) state change from %s to %s\n", - (void *)ftpc, lineno, ftp_state_names[ftpc->state], - ftp_state_names[newstate]); -#endif -#endif - - ftpc->state = newstate; -} - -static CURLcode ftp_state_user(struct connectdata *conn) -{ - CURLcode result; - struct FTP *ftp = conn->data->req.protop; - /* send USER */ - PPSENDF(&conn->proto.ftpc.pp, "USER %s", ftp->user?ftp->user:""); - - state(conn, FTP_USER); - conn->data->state.ftp_trying_alternative = FALSE; - - return CURLE_OK; + 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 connectdata *conn) +static CURLcode ftp_state_pwd(struct Curl_easy *data, + struct connectdata *conn) { - CURLcode result; - - /* send PWD to discover our entry point */ - PPSENDF(&conn->proto.ftpc.pp, "%s", "PWD"); - state(conn, FTP_PWD); + CURLcode result = Curl_pp_sendf(data, &conn->proto.ftpc.pp, "%s", "PWD"); + if(!result) + ftp_state(data, FTP_PWD); - return CURLE_OK; + return result; } /* For the FTP "protocol connect" and "doing" phases only */ -static int ftp_getsock(struct connectdata *conn, - curl_socket_t *socks, - int numsocks) +static int ftp_getsock(struct Curl_easy *data, + struct connectdata *conn, + curl_socket_t *socks) { - return Curl_pp_getsock(&conn->proto.ftpc.pp, socks, numsocks); + return Curl_pp_getsock(data, &conn->proto.ftpc.pp, socks); } /* For the FTP "DO_MORE" phase only */ -static int ftp_domore_getsock(struct connectdata *conn, curl_socket_t *socks, - int numsocks) +static int ftp_domore_getsock(struct Curl_easy *data, + struct connectdata *conn, curl_socket_t *socks) { struct ftp_conn *ftpc = &conn->proto.ftpc; - - if(!numsocks) - return GETSOCK_BLANK; + (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. */ + CURL_TRC_FTP(data, "[%s] ftp_domore_getsock()", FTP_DSTATE(data)); if(FTP_STOP == ftpc->state) { - int bits = GETSOCK_READSOCK(0); - - /* if stopped and still in this state, then we're also waiting for a + /* if stopped and still in this state, then we are also waiting for a connect on the secondary connection */ + DEBUGASSERT(conn->sock[SECONDARYSOCKET] != CURL_SOCKET_BAD || + (conn->cfilter[SECONDARYSOCKET] && + !Curl_conn_is_connected(conn, SECONDARYSOCKET))); socks[0] = conn->sock[FIRSTSOCKET]; - - if(!conn->data->set.ftp_use_port) { - int s; - int i; - /* PORT is used to tell the server to connect to us, and during that we - don't do happy eyeballs, but we do if we connect to the server */ - for(s=1, i=0; i<2; i++) { - if(conn->tempsock[i] != CURL_SOCKET_BAD) { - socks[s] = conn->tempsock[i]; - bits |= GETSOCK_WRITESOCK(s++); - } - } - } - else { - socks[1] = conn->sock[SECONDARYSOCKET]; - bits |= GETSOCK_WRITESOCK(1) | GETSOCK_READSOCK(1); - } - - return bits; + /* An unconnected SECONDARY will add its socket by itself + * via its adjust_pollset() */ + return GETSOCK_READSOCK(0); } - else - return Curl_pp_getsock(&conn->proto.ftpc.pp, socks, numsocks); + return Curl_pp_getsock(data, &conn->proto.ftpc.pp, socks); } /* This is called after the FTP_QUOTE state is passed. @@ -926,42 +976,47 @@ static int ftp_domore_getsock(struct connectdata *conn, curl_socket_t *socks, 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 connectdata *conn) +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(conn); + result = ftp_state_mdtm(data); else { - ftpc->count2 = 0; /* count2 counts failed CWDs */ + /* FTPFILE_NOCWD with full path: expect ftpc->cwddone! */ + DEBUGASSERT((data->set.ftp_filemethod != FTPFILE_NOCWD) || + !(ftpc->dirdepth && ftpc->dirs[0][0] == '/')); - /* count3 is set to allow a MKD to fail once. In the case when first 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 = (conn->data->set.ftp_create_missing_dirs==2)?1:0; + ftpc->count2 = 0; /* count2 counts failed CWDs */ - if(conn->bits.reuse && ftpc->entrypath) { - /* This is a re-used connection. Since we change directory to where the + 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->count1 = 0; /* we count this as the first path, then we add one - for all upcoming ones in the ftp->dirs[] array */ - PPSENDF(&conn->proto.ftpc.pp, "CWD %s", ftpc->entrypath); - state(conn, FTP_CWD); + 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->count1 = 1; + ftpc->cwdcount = 1; /* issue the first CWD, the rest is sent when the CWD responses are received... */ - PPSENDF(&conn->proto.ftpc.pp, "CWD %s", ftpc->dirs[ftpc->count1 -1]); - state(conn, FTP_CWD); + 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(conn); + result = ftp_state_mdtm(data); } } } @@ -974,38 +1029,38 @@ typedef enum { DONE } ftpport; -static CURLcode ftp_state_use_port(struct connectdata *conn, +static CURLcode ftp_state_use_port(struct Curl_easy *data, ftpport fcmd) /* start with this */ - { - CURLcode result = CURLE_OK; + CURLcode result = CURLE_FTP_PORT_FAILED; + struct connectdata *conn = data->conn; struct ftp_conn *ftpc = &conn->proto.ftpc; - struct Curl_easy *data=conn->data; - curl_socket_t portsock= CURL_SOCKET_BAD; - char myhost[256] = ""; + curl_socket_t portsock = CURL_SOCKET_BAD; + char myhost[MAX_IPADR_LEN + 1] = ""; struct Curl_sockaddr_storage ss; - Curl_addrinfo *res, *ai; + struct Curl_addrinfo *res, *ai; curl_socklen_t sslen; char hbuf[NI_MAXHOST]; - struct sockaddr *sa=(struct sockaddr *)&ss; + struct sockaddr *sa = (struct sockaddr *)&ss; struct sockaddr_in * const sa4 = (void *)sa; -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 struct sockaddr_in6 * const sa6 = (void *)sa; #endif - char tmp[1024]; static const char mode[][5] = { "EPRT", "PORT" }; - int rc; + enum resolve_t rc; int error; char *host = NULL; char *string_ftpport = data->set.str[STRING_FTPPORT]; - struct Curl_dns_entry *h=NULL; + struct Curl_dns_entry *dns_entry = 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; + size_t addrlen = 0; + char ipstr[50]; /* Step 1, figure out what is requested, * accepted format : @@ -1014,30 +1069,17 @@ static CURLcode ftp_state_use_port(struct connectdata *conn, 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(addrlen+1, 1); - if(!addr) - return CURLE_OUT_OF_MEMORY; - -#ifdef ENABLE_IPV6 +#ifdef USE_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); + char *ip_start = string_ftpport + 1; + ip_end = strchr(ip_start, ']'); + if(ip_end) { + addrlen = ip_end - ip_start; + addr = ip_start; + } } else #endif @@ -1047,30 +1089,29 @@ static CURLcode ftp_state_use_port(struct connectdata *conn, } else { ip_end = strchr(string_ftpport, ':'); + addr = 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) == 1) { + addrlen = ip_end - string_ftpport; +#ifdef USE_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); + addrlen = strlen(string_ftpport); } /* parse the port */ - if(ip_end != NULL) { - port_start = strchr(ip_end, ':'); + if(ip_end) { + char *port_sep = NULL; + char *port_start = strchr(ip_end, ':'); if(port_start) { - port_min = curlx_ultous(strtoul(port_start+1, NULL, 10)); + 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)); @@ -1089,20 +1130,29 @@ static CURLcode ftp_state_use_port(struct connectdata *conn, if(port_min > port_max) port_min = port_max = 0; + if(addrlen) { + DEBUGASSERT(addr); + if(addrlen >= sizeof(ipstr)) + goto out; + memcpy(ipstr, addr, addrlen); + ipstr[addrlen] = 0; - if(*addr != '\0') { /* attempt to get the address of the given interface name */ - switch(Curl_if2ip(conn->ip_addr->ai_family, - Curl_ipv6_scope(conn->ip_addr->ai_addr), - conn->scope_id, addr, hbuf, sizeof(hbuf))) { + switch(Curl_if2ip(conn->remote_addr->family, +#ifdef USE_IPV6 + Curl_ipv6_scope(&conn->remote_addr->curl_sa_addr), + conn->scope_id, +#endif + ipstr, hbuf, sizeof(hbuf))) { case IF2IP_NOT_FOUND: - /* not an interface, use the given string as host name instead */ - host = addr; + /* not an interface, use the given string as hostname instead */ + host = ipstr; break; case IF2IP_AF_NOT_SUPPORTED: - return CURLE_FTP_PORT_FAILED; + goto out; case IF2IP_FOUND: - host = hbuf; /* use the hbuf for host name */ + host = hbuf; /* use the hbuf for hostname */ + break; } } else @@ -1111,68 +1161,65 @@ static CURLcode ftp_state_use_port(struct connectdata *conn, } /* data->set.ftpport */ if(!host) { - /* not an interface and not a host name, get default by extracting + const char *r; + /* not an interface and not a hostname, 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(conn, SOCKERRNO) ); - free(addr); - return CURLE_FTP_PORT_FAILED; + Curl_strerror(SOCKERRNO, buffer, sizeof(buffer))); + goto out; } switch(sa->sa_family) { -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 case AF_INET6: - Curl_inet_ntop(sa->sa_family, &sa6->sin6_addr, hbuf, sizeof(hbuf)); + r = Curl_inet_ntop(sa->sa_family, &sa6->sin6_addr, hbuf, sizeof(hbuf)); break; #endif default: - Curl_inet_ntop(sa->sa_family, &sa4->sin_addr, hbuf, sizeof(hbuf)); + r = Curl_inet_ntop(sa->sa_family, &sa4->sin_addr, hbuf, sizeof(hbuf)); break; } - host = hbuf; /* use this host name */ + if(!r) { + goto out; + } + host = hbuf; /* use this hostname */ possibly_non_local = FALSE; /* we know it is local now */ } /* resolv ip/host to ip */ - rc = Curl_resolv(conn, host, 0, &h); + rc = Curl_resolv(data, host, 0, FALSE, &dns_entry); if(rc == CURLRESOLV_PENDING) - (void)Curl_resolver_wait_resolv(conn, &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) */ + (void)Curl_resolver_wait_resolv(data, &dns_entry); + if(dns_entry) { + res = dns_entry->addr; + } else res = NULL; /* failure! */ - if(res == NULL) { + if(!res) { failf(data, "failed to resolve the address provided to PORT: %s", host); - free(addr); - return CURLE_FTP_PORT_FAILED; + goto out; } - free(addr); host = NULL; /* step 2, create a socket for the requested address */ - - portsock = CURL_SOCKET_BAD; error = 0; for(ai = res; ai; ai = ai->ai_next) { - result = Curl_socket(conn, ai, NULL, &portsock); - if(result) { + if(Curl_socket_open(data, ai, NULL, conn->transport, &portsock)) { error = SOCKERRNO; continue; } break; } if(!ai) { - failf(data, "socket failure: %s", Curl_strerror(conn, error)); - return CURLE_FTP_PORT_FAILED; + failf(data, "socket failure: %s", + Curl_strerror(error, buffer, sizeof(buffer))); + goto out; } + CURL_TRC_FTP(data, "[%s] ftp_state_use_port(), opened socket", + FTP_DSTATE(data)); /* step 3, bind to a suitable local address */ @@ -1182,7 +1229,7 @@ static CURLcode ftp_state_use_port(struct connectdata *conn, for(port = port_min; port <= port_max;) { if(sa->sa_family == AF_INET) sa4->sin_port = htons(port); -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 else sa6->sin6_port = htons(port); #endif @@ -1191,29 +1238,26 @@ static CURLcode ftp_state_use_port(struct connectdata *conn, /* It failed. */ error = SOCKERRNO; if(possibly_non_local && (error == EADDRNOTAVAIL)) { - /* The requested bind address is not local. Use the address used for + /* 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\n", port, - Curl_strerror(conn, error) ); + 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(conn, SOCKERRNO) ); - Curl_closesocket(conn, portsock); - return CURLE_FTP_PORT_FAILED; + Curl_strerror(SOCKERRNO, buffer, sizeof(buffer))); + goto out; } port = port_min; - possibly_non_local = FALSE; /* don't try this again */ + possibly_non_local = FALSE; /* do not try this again */ continue; } - else if(error != EADDRINUSE && error != EACCES) { + if(error != EADDRINUSE && error != EACCES) { failf(data, "bind(port=%hu) failed: %s", port, - Curl_strerror(conn, error) ); - Curl_closesocket(conn, portsock); - return CURLE_FTP_PORT_FAILED; + Curl_strerror(error, buffer, sizeof(buffer))); + goto out; } } else @@ -1222,30 +1266,32 @@ static CURLcode ftp_state_use_port(struct connectdata *conn, port++; } - /* maybe all ports were in use already*/ + /* maybe all ports were in use already */ if(port > port_max) { - failf(data, "bind() failed, we ran out of ports!"); - Curl_closesocket(conn, portsock); - return CURLE_FTP_PORT_FAILED; + 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, (struct sockaddr *)sa, &sslen)) { + if(getsockname(portsock, sa, &sslen)) { failf(data, "getsockname() failed: %s", - Curl_strerror(conn, SOCKERRNO) ); - Curl_closesocket(conn, portsock); - return CURLE_FTP_PORT_FAILED; + Curl_strerror(SOCKERRNO, buffer, sizeof(buffer))); + goto out; } + CURL_TRC_FTP(data, "[%s] ftp_state_use_port(), socket bound to port %d", + FTP_DSTATE(data), port); /* step 4, listen on the socket */ if(listen(portsock, 1)) { - failf(data, "socket failure: %s", Curl_strerror(conn, SOCKERRNO)); - Curl_closesocket(conn, portsock); - return CURLE_FTP_PORT_FAILED; + failf(data, "socket failure: %s", + Curl_strerror(SOCKERRNO, buffer, sizeof(buffer))); + goto out; } + CURL_TRC_FTP(data, "[%s] ftp_state_use_port(), listening on %d", + FTP_DSTATE(data), port); /* step 5, send the proper FTP command */ @@ -1253,13 +1299,19 @@ static CURLcode ftp_state_use_port(struct connectdata *conn, below */ Curl_printable_address(ai, myhost, sizeof(myhost)); -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 if(!conn->bits.ftp_use_eprt && conn->bits.ipv6) /* EPRT is disabled but we are connected to a IPv6 host, so we ignore the request and enable EPRT again! */ conn->bits.ftp_use_eprt = TRUE; #endif + /* 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 */ + for(; fcmd != DONE; fcmd++) { if(!conn->bits.ftp_use_eprt && (EPRT == fcmd)) @@ -1274,7 +1326,7 @@ static CURLcode ftp_state_use_port(struct connectdata *conn, case AF_INET: port = ntohs(sa4->sin_port); break; -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 case AF_INET6: port = ntohs(sa6->sin6_port); break; @@ -1292,45 +1344,39 @@ static CURLcode ftp_state_use_port(struct connectdata *conn, * EPRT |2|1080::8:800:200C:417A|5282| */ - result = Curl_pp_sendf(&ftpc->pp, "%s |%d|%s|%hu|", mode[fcmd], - sa->sa_family == AF_INET?1:2, + 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)); - Curl_closesocket(conn, portsock); - /* don't retry using PORT */ - ftpc->count1 = PORT; - /* bail out */ - state(conn, FTP_STOP); - return result; + goto out; } break; } - else if(PORT == fcmd) { + if(PORT == fcmd) { + /* large enough for [IP address],[num],[num] */ + char target[sizeof(myhost) + 20]; char *source = myhost; - char *dest = tmp; + char *dest = target; /* translate x.x.x.x to x,x,x,x */ - while(source && *source) { + while(*source) { if(*source == '.') - *dest=','; + *dest = ','; else *dest = *source; dest++; source++; } *dest = 0; - snprintf(dest, 20, ",%d,%d", (int)(port>>8), (int)(port&0xff)); + msnprintf(dest, 20, ",%d,%d", (int)(port >> 8), (int)(port & 0xff)); - result = Curl_pp_sendf(&ftpc->pp, "%s %s", mode[fcmd], tmp); + 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)); - Curl_closesocket(conn, portsock); - /* bail out */ - state(conn, FTP_STOP); - return result; + goto out; } break; } @@ -1339,32 +1385,27 @@ static CURLcode ftp_state_use_port(struct connectdata *conn, /* store which command was sent */ ftpc->count1 = fcmd; - close_secondarysocket(conn); - - /* we set the secondary socket variable to this for now, it is only so that - the cleanup function will close it in case we fail before the true - secondary stuff is made */ - conn->sock[SECONDARYSOCKET] = portsock; - - /* this tcpconnect assignment below is a hackish work-around to make the - multi interface with active FTP work - as it will not wait for a - (passive) connect in Curl_is_connected(). + ftp_state(data, FTP_PORT); - The *proper* fix is to make sure that the active connection from the - server is done in a non-blocking way. Currently, it is still BLOCKING. - */ - conn->bits.tcpconnect[SECONDARYSOCKET] = TRUE; - - state(conn, FTP_PORT); +out: + /* If we looked up a dns_entry, now is the time to safely release it */ + if(dns_entry) + Curl_resolv_unlink(data, &dns_entry); + if(result) { + ftp_state(data, FTP_STOP); + } + if(portsock != CURL_SOCKET_BAD) + Curl_socket_close(data, conn, portsock); return result; } -static CURLcode ftp_state_use_pasv(struct connectdata *conn) +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 excecutive summary on what to do: + Here's the executive summary on what to do: PASV is RFC959, expect: 227 Entering Passive Mode (a1,a2,a3,a4,p1,p2) @@ -1387,14 +1428,14 @@ static CURLcode ftp_state_use_pasv(struct connectdata *conn) conn->bits.ftp_use_epsv = TRUE; #endif - modeoff = conn->bits.ftp_use_epsv?0:1; - - PPSENDF(&ftpc->pp, "%s", mode[modeoff]); - - ftpc->count1 = modeoff; - state(conn, FTP_PASV); - infof(conn->data, "Connect data stream passively\n"); + 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; } @@ -1405,94 +1446,97 @@ static CURLcode ftp_state_use_pasv(struct connectdata *conn) * 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 connectdata *conn) +static CURLcode ftp_state_prepare_transfer(struct Curl_easy *data) { CURLcode result = CURLE_OK; - struct FTP *ftp = conn->data->req.protop; - struct Curl_easy *data = conn->data; + struct FTP *ftp = data->req.p.ftp; + struct connectdata *conn = data->conn; - if(ftp->transfer != FTPTRANSFER_BODY) { - /* doesn't transfer any data */ + if(ftp->transfer != PPTRANSFER_BODY) { + /* does not transfer any data */ /* still possibly do PRE QUOTE jobs */ - state(conn, FTP_RETR_PREQUOTE); - result = ftp_state_quote(conn, TRUE, FTP_RETR_PREQUOTE); + 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(conn, EPRT); + 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 */ - if(!conn->proto.ftpc.file) { - PPSENDF(&conn->proto.ftpc.pp, "PRET %s", - data->set.str[STRING_CUSTOMREQUEST]? - data->set.str[STRING_CUSTOMREQUEST]: - (data->set.ftp_list_only?"NLST":"LIST")); - } - else if(data->set.upload) { - PPSENDF(&conn->proto.ftpc.pp, "PRET STOR %s", conn->proto.ftpc.file); - } - else { - PPSENDF(&conn->proto.ftpc.pp, "PRET RETR %s", conn->proto.ftpc.file); - } - state(conn, FTP_PRET); - } - else { - result = ftp_state_use_pasv(conn); + 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 connectdata *conn) +static CURLcode ftp_state_rest(struct Curl_easy *data, + struct connectdata *conn) { CURLcode result = CURLE_OK; - struct FTP *ftp = conn->data->req.protop; + struct FTP *ftp = data->req.p.ftp; struct ftp_conn *ftpc = &conn->proto.ftpc; - if((ftp->transfer != FTPTRANSFER_BODY) && ftpc->file) { + 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 */ - PPSENDF(&conn->proto.ftpc.pp, "REST %d", 0); - - state(conn, FTP_REST); + result = Curl_pp_sendf(data, &ftpc->pp, "REST %d", 0); + if(!result) + ftp_state(data, FTP_REST); } else - result = ftp_state_prepare_transfer(conn); + result = ftp_state_prepare_transfer(data); return result; } -static CURLcode ftp_state_size(struct connectdata *conn) +static CURLcode ftp_state_size(struct Curl_easy *data, + struct connectdata *conn) { CURLcode result = CURLE_OK; - struct FTP *ftp = conn->data->req.protop; + struct FTP *ftp = data->req.p.ftp; struct ftp_conn *ftpc = &conn->proto.ftpc; - if((ftp->transfer == FTPTRANSFER_INFO) && ftpc->file) { + 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 */ - PPSENDF(&ftpc->pp, "SIZE %s", ftpc->file); - - state(conn, FTP_SIZE); + /* we know ftpc->file is a valid pointer to a filename */ + result = Curl_pp_sendf(data, &ftpc->pp, "SIZE %s", ftpc->file); + if(!result) + ftp_state(data, FTP_SIZE); } else - result = ftp_state_rest(conn); + result = ftp_state_rest(data, conn); return result; } -static CURLcode ftp_state_list(struct connectdata *conn) +static CURLcode ftp_state_list(struct Curl_easy *data) { CURLcode result = CURLE_OK; - struct Curl_easy *data = conn->data; + 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 @@ -1500,120 +1544,108 @@ static CURLcode ftp_state_list(struct connectdata *conn) servers either... */ /* - if FTPFILE_NOCWD was specified, we are currently in - the user's home directory, so we should add the path + if FTPFILE_NOCWD was specified, we should add the path as argument for the LIST / NLST / or custom command. Whether the server will support this, is uncertain. The other ftp_filemethods will CWD into dir/dir/ first and then just do LIST (in that case: nothing to do here) */ - char *cmd, *lstArg, *slashPos; - - lstArg = NULL; - if((data->set.ftp_filemethod == FTPFILE_NOCWD) && - data->state.path && - data->state.path[0] && - strchr(data->state.path, '/')) { - - lstArg = strdup(data->state.path); - if(!lstArg) - return CURLE_OUT_OF_MEMORY; + 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; - /* Check if path does not end with /, as then we cut off the file part */ - if(lstArg[strlen(lstArg) - 1] != '/') { + 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; - /* chop off the file part if format is dir/dir/file */ - slashPos = strrchr(lstArg, '/'); - if(slashPos) - *(slashPos+1) = '\0'; + lstArg = rawPath; + lstArg[n] = '\0'; } + else + free(rawPath); } cmd = aprintf("%s%s%s", - data->set.str[STRING_CUSTOMREQUEST]? - data->set.str[STRING_CUSTOMREQUEST]: - (data->set.ftp_list_only?"NLST":"LIST"), - lstArg? " ": "", - lstArg? lstArg: ""); - - if(!cmd) { - free(lstArg); - return CURLE_OUT_OF_MEMORY; - } + data->set.str[STRING_CUSTOMREQUEST] ? + data->set.str[STRING_CUSTOMREQUEST] : + (data->state.list_only ? "NLST" : "LIST"), + lstArg ? " " : "", + lstArg ? lstArg : ""); + free(lstArg); - result = Curl_pp_sendf(&conn->proto.ftpc.pp, "%s", cmd); + if(!cmd) + return CURLE_OUT_OF_MEMORY; - free(lstArg); + result = Curl_pp_sendf(data, &conn->proto.ftpc.pp, "%s", cmd); free(cmd); - if(result) - return result; - - state(conn, FTP_LIST); + if(!result) + ftp_state(data, FTP_LIST); return result; } -static CURLcode ftp_state_retr_prequote(struct connectdata *conn) +static CURLcode ftp_state_retr_prequote(struct Curl_easy *data) { - CURLcode result = CURLE_OK; - - /* We've sent the TYPE, now we must send the list of prequote strings */ - - result = ftp_state_quote(conn, TRUE, FTP_RETR_PREQUOTE); - - return result; + /* We have 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 connectdata *conn) +static CURLcode ftp_state_stor_prequote(struct Curl_easy *data) { - CURLcode result = CURLE_OK; - - /* We've sent the TYPE, now we must send the list of prequote strings */ - - result = ftp_state_quote(conn, TRUE, FTP_STOR_PREQUOTE); - - return result; + /* We have 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 connectdata *conn) +static CURLcode ftp_state_type(struct Curl_easy *data) { CURLcode result = CURLE_OK; - struct FTP *ftp = conn->data->req.protop; - struct Curl_easy *data = conn->data; + 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 + information. Which in FTP cannot be much more than the file size and date. */ - if(data->set.opt_no_body && ftpc->file && - ftp_need_type(conn, data->set.prefer_ascii)) { - /* The SIZE command is _not_ RFC 959 specified, and therefor many servers + 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 = FTPTRANSFER_INFO; + 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(conn, data->set.prefer_ascii, FTP_TYPE); + result = ftp_nb_type(data, conn, data->state.prefer_ascii, FTP_TYPE); if(result) return result; } else - result = ftp_state_size(conn); + 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 connectdata *conn) +static CURLcode ftp_state_mdtm(struct Curl_easy *data) { CURLcode result = CURLE_OK; - struct Curl_easy *data = conn->data; + struct connectdata *conn = data->conn; struct ftp_conn *ftpc = &conn->proto.ftpc; /* Requested time of file or time-depended transfer? */ @@ -1621,123 +1653,128 @@ static CURLcode ftp_state_mdtm(struct connectdata *conn) /* we have requested to get the modified-time of the file, this is a white spot as the MDTM is not mentioned in RFC959 */ - PPSENDF(&ftpc->pp, "MDTM %s", ftpc->file); + result = Curl_pp_sendf(data, &ftpc->pp, "MDTM %s", ftpc->file); - state(conn, FTP_MDTM); + if(!result) + ftp_state(data, FTP_MDTM); } else - result = ftp_state_type(conn); + 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 connectdata *conn, +static CURLcode ftp_state_ul_setup(struct Curl_easy *data, bool sizechecked) { CURLcode result = CURLE_OK; - struct FTP *ftp = conn->data->req.protop; - struct Curl_easy *data = conn->data; + struct connectdata *conn = data->conn; + struct FTP *ftp = data->req.p.ftp; struct ftp_conn *ftpc = &conn->proto.ftpc; - int seekerr = CURL_SEEKFUNC_OK; + 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 */ + /* we are 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 + do not 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 */ - PPSENDF(&ftpc->pp, "SIZE %s", ftpc->file); - state(conn, FTP_STOR_SIZE); + result = Curl_pp_sendf(data, &ftpc->pp, "SIZE %s", ftpc->file); + if(!result) + ftp_state(data, FTP_STOR_SIZE); return result; } /* enable append */ - data->set.ftp_append = TRUE; + append = TRUE; /* Let's read off the proper amount of bytes from the input. */ - if(conn->seek_func) { - seekerr = conn->seek_func(conn->seek_client, data->state.resume_from, - SEEK_SET); + if(data->set.seek_func) { + Curl_set_in_callback(data, true); + seekerr = data->set.seek_func(data->set.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) */ - else { - curl_off_t passed=0; - do { - size_t readthisamountnow = - (data->state.resume_from - passed > CURL_OFF_T_C(BUFSIZE)) ? - BUFSIZE : 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); - } + /* seekerr == CURL_SEEKFUNC_CANTSEEK (cannot seek to offset) */ + do { + char scratch[4*1024]; + size_t readthisamountnow = + (data->state.resume_from - passed > (curl_off_t)sizeof(scratch)) ? + sizeof(scratch) : + curlx_sotouz(data->state.resume_from - passed); + + size_t actuallyread = + data->state.fread_func(scratch, 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) { + if(data->state.infilesize > 0) { data->state.infilesize -= data->state.resume_from; if(data->state.infilesize <= 0) { - infof(data, "File already completely uploaded\n"); + infof(data, "File already completely uploaded"); /* no data to transfer */ - Curl_setup_transfer(conn, -1, -1, FALSE, NULL, -1, NULL); + Curl_xfer_setup_nop(data); - /* Set ->transfer so that we won't get any error in - * ftp_done() because we didn't transfer anything! */ - ftp->transfer = FTPTRANSFER_NONE; + /* Set ->transfer so that we will not get any error in + * ftp_done() because we did not transfer anything! */ + ftp->transfer = PPTRANSFER_NONE; - state(conn, FTP_STOP); + ftp_state(data, FTP_STOP); return CURLE_OK; } } - /* we've passed, proceed as normal */ + /* we have passed, proceed as normal */ } /* resume_from */ - PPSENDF(&ftpc->pp, data->set.ftp_append?"APPE %s":"STOR %s", - ftpc->file); - - state(conn, FTP_STOR); + 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 connectdata *conn, +static CURLcode ftp_state_quote(struct Curl_easy *data, bool init, ftpstate instate) { CURLcode result = CURLE_OK; - struct Curl_easy *data = conn->data; - struct FTP *ftp = data->req.protop; + struct FTP *ftp = data->req.p.ftp; + struct connectdata *conn = data->conn; struct ftp_conn *ftpc = &conn->proto.ftpc; - bool quote=FALSE; + bool quote = FALSE; struct curl_slist *item; switch(instate) { @@ -1757,7 +1794,7 @@ static CURLcode ftp_state_quote(struct connectdata *conn, /* * This state uses: * 'count1' to iterate over the commands to send - * 'count2' to store wether to allow commands to fail + * 'count2' to store whether to allow commands to fail */ if(init) @@ -1769,7 +1806,7 @@ static CURLcode ftp_state_quote(struct connectdata *conn, int i = 0; /* Skip count1 items in the linked list */ - while((i< ftpc->count1) && item) { + while((i < ftpc->count1) && item) { item = item->next; i++; } @@ -1782,8 +1819,10 @@ static CURLcode ftp_state_quote(struct connectdata *conn, else ftpc->count2 = 0; /* failure means cancel operation */ - PPSENDF(&ftpc->pp, "%s", cmd); - state(conn, instate); + result = Curl_pp_sendf(data, &ftpc->pp, "%s", cmd); + if(result) + return result; + ftp_state(data, instate); quote = TRUE; } } @@ -1793,36 +1832,44 @@ static CURLcode ftp_state_quote(struct connectdata *conn, switch(instate) { case FTP_QUOTE: default: - result = ftp_state_cwd(conn); + result = ftp_state_cwd(data, conn); break; case FTP_RETR_PREQUOTE: - if(ftp->transfer != FTPTRANSFER_BODY) - state(conn, FTP_STOP); + 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(conn, ftpc->known_filesize); + result = ftp_state_retr(data, ftpc->known_filesize); } else { - if(data->set.ignorecl) { - /* This code is 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.*/ - PPSENDF(&ftpc->pp, "RETR %s", ftpc->file); - state(conn, FTP_RETR); + 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 do not 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 { - PPSENDF(&ftpc->pp, "SIZE %s", ftpc->file); - state(conn, FTP_RETR_SIZE); + 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(conn, FALSE); + result = ftp_state_ul_setup(data, FALSE); break; case FTP_POSTQUOTE: break; @@ -1834,25 +1881,34 @@ static CURLcode ftp_state_quote(struct connectdata *conn, /* called from ftp_state_pasv_resp to switch to PASV in case of EPSV problems */ -static CURLcode ftp_epsv_disable(struct connectdata *conn) +static CURLcode ftp_epsv_disable(struct Curl_easy *data, + struct connectdata *conn) { CURLcode result = CURLE_OK; - if(conn->bits.ipv6) { - /* We can't disable EPSV when doing IPv6, so this is instead a fail */ - failf(conn->data, "Failed EPSV attempt, exiting\n"); + if(conn->bits.ipv6 +#ifndef CURL_DISABLE_PROXY + && !(conn->bits.tunnel_proxy || conn->bits.socksproxy) +#endif + ) { + /* We cannot disable EPSV when doing IPv6, so this is instead a fail */ + failf(data, "Failed EPSV attempt, exiting"); return CURLE_WEIRD_SERVER_REPLY; } - infof(conn->data, "Failed EPSV attempt. Disabling EPSV\n"); + infof(data, "Failed EPSV attempt. Disabling EPSV"); /* disable it for next transfer */ conn->bits.ftp_use_epsv = FALSE; - conn->data->state.errorbuf = FALSE; /* allow error message to get + Curl_conn_close(data, SECONDARYSOCKET); + Curl_conn_cf_discard_all(data, conn, SECONDARYSOCKET); + data->state.errorbuf = FALSE; /* allow error message to get rewritten */ - PPSENDF(&conn->proto.ftpc.pp, "%s", "PASV"); - conn->proto.ftpc.count1++; - /* remain in/go to the FTP_PASV state */ - state(conn, FTP_PASV); + 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; } @@ -1860,25 +1916,51 @@ static CURLcode ftp_epsv_disable(struct connectdata *conn) 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 + If a proxy tunnel is used, returns the original hostname 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.remote_ip; +} - return conn->ip_addr_str; +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 connectdata *conn, +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_easy *data=conn->data; - struct Curl_dns_entry *addr=NULL; - int rc; + 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 */ + struct pingpong *pp = &ftpc->pp; + char *str = + Curl_dyn_ptr(&pp->recvbuf) + 4; /* start on the first letter */ /* if we come here again, make sure the former name is cleared */ Curl_safefree(ftpc->newhost); @@ -1888,27 +1970,18 @@ static CURLcode ftp_state_pasv_resp(struct connectdata *conn, /* positive EPSV response */ char *ptr = strchr(str, '('); if(ptr) { - unsigned int num; - char separator[4]; + char sep; ptr++; - if(5 == sscanf(ptr, "%c%c%c%u%c", - &separator[0], - &separator[1], - &separator[2], - &num, - &separator[3])) { - const char sep1 = separator[0]; - int i; - - /* The four separators should be identical, or else this is an oddly - formatted reply and we bail out immediately. */ - for(i=1; i<4; i++) { - if(separator[i] != sep1) { - ptr=NULL; /* set to NULL to signal error */ - break; - } - } - if(num > 0xffff) { + /* |||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; } @@ -1920,7 +1993,7 @@ static CURLcode ftp_state_pasv_resp(struct connectdata *conn, } } else - ptr=NULL; + ptr = NULL; } if(!ptr) { failf(data, "Weirdly formatted EPSV reply"); @@ -1930,8 +2003,7 @@ static CURLcode ftp_state_pasv_resp(struct connectdata *conn, else if((ftpc->count1 == 1) && (ftpcode == 227)) { /* positive PASV response */ - int ip[4]; - int port[2]; + unsigned int ip[6]; /* * Scan for a sequence of six comma-separated numbers and use them as @@ -1943,9 +2015,7 @@ static CURLcode ftp_state_pasv_resp(struct connectdata *conn, * "227 Entering passive mode. 127,0,0,1,4,51" */ while(*str) { - if(6 == sscanf(str, "%d,%d,%d,%d,%d,%d", - &ip[0], &ip[1], &ip[2], &ip[3], - &port[0], &port[1])) + if(match_pasv_6nums(str, ip)) break; str++; } @@ -1959,72 +2029,87 @@ static CURLcode ftp_state_pasv_resp(struct connectdata *conn, 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 %d.%d.%d.%d for data connection, re-use %s instead\n", + 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("%d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]); + 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)(((port[0]<<8) + port[1]) & 0xffff); + 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(conn); + 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 + * here. We do not 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(conn, host_name, (int)conn->port, &addr); + rc = Curl_resolv(data, host_name, conn->primary.remote_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(conn, &addr); + (void)Curl_resolver_wait_resolv(data, &addr); - connectport = - (unsigned short)conn->port; /* we connect to the proxy's port */ + /* we connect to the proxy's port */ + connectport = (unsigned short)conn->primary.remote_port; if(!addr) { - failf(data, "Can't resolve proxy host %s:%hu", host_name, connectport); + failf(data, "cannot resolve proxy host %s:%hu", host_name, connectport); return CURLE_COULDNT_RESOLVE_PROXY; } } - else { + else +#endif + { /* normal, direct, ftp connection */ - rc = Curl_resolv(conn, ftpc->newhost, ftpc->newport, &addr); + DEBUGASSERT(ftpc->newhost); + + /* postponed address resolution in case of tcp fastopen */ + if(conn->bits.tcp_fastopen && !conn->bits.reuse && !ftpc->newhost[0]) { + 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(conn, &addr); + (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); + failf(data, "cannot resolve new host %s:%hu", + ftpc->newhost, connectport); return CURLE_FTP_CANT_GET_HOST; } } - conn->bits.tcpconnect[SECONDARYSOCKET] = FALSE; - result = Curl_connecthost(conn, addr); + 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 */ + Curl_resolv_unlink(data, &addr); /* we are done using this address */ if(ftpc->count1 == 0 && ftpcode == 229) - return ftp_epsv_disable(conn); + return ftp_epsv_disable(data, conn); return result; } @@ -2038,23 +2123,26 @@ static CURLcode ftp_state_pasv_resp(struct connectdata *conn, if(data->set.verbose) /* this just dumps information about this second connection */ - ftp_pasv_verbose(conn, addr->addr, ftpc->newhost, connectport); + ftp_pasv_verbose(data, addr->addr, ftpc->newhost, connectport); + + Curl_resolv_unlink(data, &addr); /* we are done using this address */ Curl_safefree(conn->secondaryhostname); - conn->secondaryhostname = strdup(ftpc->newhost); conn->secondary_port = ftpc->newport; + conn->secondaryhostname = strdup(ftpc->newhost); + if(!conn->secondaryhostname) + return CURLE_OUT_OF_MEMORY; - Curl_resolv_unlock(data, addr); /* we're done using this address */ conn->bits.do_more = TRUE; - state(conn, FTP_STOP); /* this phase is completed */ + ftp_state(data, FTP_STOP); /* this phase is completed */ return result; } -static CURLcode ftp_state_port_resp(struct connectdata *conn, +static CURLcode ftp_state_port_resp(struct Curl_easy *data, int ftpcode) { - struct Curl_easy *data = conn->data; + struct connectdata *conn = data->conn; struct ftp_conn *ftpc = &conn->proto.ftpc; ftpport fcmd = (ftpport)ftpc->count1; CURLcode result = CURLE_OK; @@ -2065,7 +2153,7 @@ static CURLcode ftp_state_port_resp(struct connectdata *conn, /* the command failed */ if(EPRT == fcmd) { - infof(data, "disabling EPRT usage\n"); + infof(data, "disabling EPRT usage"); conn->bits.ftp_use_eprt = FALSE; } fcmd++; @@ -2076,23 +2164,72 @@ static CURLcode ftp_state_port_resp(struct connectdata *conn, } else /* try next */ - result = ftp_state_use_port(conn, fcmd); + result = ftp_state_use_port(data, fcmd); } else { - infof(data, "Connect data stream actively\n"); - state(conn, FTP_STOP); /* end of DO phase */ - result = ftp_dophase_done(conn, FALSE); + infof(data, "Connect data stream actively"); + ftp_state(data, FTP_STOP); /* end of DO phase */ + result = ftp_dophase_done(data, FALSE); } return result; } -static CURLcode ftp_state_mdtm_resp(struct connectdata *conn, +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 an 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 Curl_easy *data=conn->data; - struct FTP *ftp = data->req.protop; + struct FTP *ftp = data->req.p.ftp; + struct connectdata *conn = data->conn; struct ftp_conn *ftpc = &conn->proto.ftpc; switch(ftpcode) { @@ -2101,28 +2238,29 @@ static CURLcode ftp_state_mdtm_resp(struct connectdata *conn, /* 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; - char *buf = data->state.buffer; - if(6 == sscanf(buf+4, "%04d%02d%02d%02d%02d%02d", - &year, &month, &day, &hour, &minute, &second)) { + struct pingpong *pp = &ftpc->pp; + char *resp = Curl_dyn_ptr(&pp->recvbuf) + 4; + if(ftp_213_date(resp, &year, &month, &day, &hour, &minute, &second)) { /* we have a time, reformat it */ - time_t secs=time(NULL); - /* using the good old yacc/bison yuck */ - snprintf(buf, CURL_BUFSIZE(conn->data->set.buffer_size), - "%04d%02d%02d %02d:%02d:%02d GMT", - year, month, day, hour, minute, second); + 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 = (long)curl_getdate(buf, &secs); + 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" a HTTP-style header in our output. */ + we "emulate" an HTTP-style header in our output. */ - if(data->set.opt_no_body && + if(data->req.no_body && ftpc->file && data->set.get_filetime && - (data->info.filetime>=0) ) { - time_t filetime = (time_t)data->info.filetime; + (data->info.filetime >= 0) ) { + char headerbuf[128]; + int headerbuflen; + time_t filetime = data->info.filetime; struct tm buffer; const struct tm *tm = &buffer; @@ -2131,16 +2269,17 @@ static CURLcode ftp_state_mdtm_resp(struct connectdata *conn, return result; /* format: "Tue, 15 Nov 1994 12:45:26" */ - snprintf(buf, BUFSIZE-1, - "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 = Curl_client_write(conn, CLIENTWRITE_BOTH, buf, 0); + 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 */ @@ -2148,11 +2287,13 @@ static CURLcode ftp_state_mdtm_resp(struct connectdata *conn, } break; default: - infof(data, "unsupported MDTM reply format\n"); + infof(data, "unsupported MDTM reply format"); break; - case 550: /* "No such file or directory" */ - failf(data, "Given file does not exist"); - result = CURLE_FTP_COULDNT_RETR_FILE; + 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; } @@ -2162,41 +2303,41 @@ static CURLcode ftp_state_mdtm_resp(struct connectdata *conn, case CURL_TIMECOND_IFMODSINCE: default: if(data->info.filetime <= data->set.timevalue) { - infof(data, "The requested document is not new enough\n"); - ftp->transfer = FTPTRANSFER_NONE; /* mark to not transfer data */ + infof(data, "The requested document is not new enough"); + ftp->transfer = PPTRANSFER_NONE; /* mark to not transfer data */ data->info.timecond = TRUE; - state(conn, FTP_STOP); + 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\n"); - ftp->transfer = FTPTRANSFER_NONE; /* mark to not transfer data */ + infof(data, "The requested document is not old enough"); + ftp->transfer = PPTRANSFER_NONE; /* mark to not transfer data */ data->info.timecond = TRUE; - state(conn, FTP_STOP); + ftp_state(data, FTP_STOP); return CURLE_OK; } break; } /* switch */ } else { - infof(data, "Skipping time comparison\n"); + infof(data, "Skipping time comparison"); } } if(!result) - result = ftp_state_type(conn); + result = ftp_state_type(data); return result; } -static CURLcode ftp_state_type_resp(struct connectdata *conn, +static CURLcode ftp_state_type_resp(struct Curl_easy *data, int ftpcode, ftpstate instate) { CURLcode result = CURLE_OK; - struct Curl_easy *data=conn->data; + struct connectdata *conn = data->conn; if(ftpcode/100 != 2) { /* "sasserftpd" and "(u)r(x)bot ftpd" both responds with 226 after a @@ -2206,29 +2347,30 @@ static CURLcode ftp_state_type_resp(struct connectdata *conn, return CURLE_FTP_COULDNT_SET_TYPE; } if(ftpcode != 200) - infof(data, "Got a %03d response code instead of the assumed 200\n", + infof(data, "Got a %03d response code instead of the assumed 200", ftpcode); if(instate == FTP_TYPE) - result = ftp_state_size(conn); + result = ftp_state_size(data, conn); else if(instate == FTP_LIST_TYPE) - result = ftp_state_list(conn); + result = ftp_state_list(data); else if(instate == FTP_RETR_TYPE) - result = ftp_state_retr_prequote(conn); + result = ftp_state_retr_prequote(data); else if(instate == FTP_STOR_TYPE) - result = ftp_state_stor_prequote(conn); + result = ftp_state_stor_prequote(data); return result; } -static CURLcode ftp_state_retr(struct connectdata *conn, - curl_off_t filesize) +static CURLcode ftp_state_retr(struct Curl_easy *data, + curl_off_t filesize) { CURLcode result = CURLE_OK; - struct Curl_easy *data=conn->data; - struct FTP *ftp = data->req.protop; + struct FTP *ftp = data->req.p.ftp; + struct connectdata *conn = data->conn; struct ftp_conn *ftpc = &conn->proto.ftpc; + CURL_TRC_FTP(data, "[%s] ftp_state_retr()", FTP_DSTATE(data)); if(data->set.max_filesize && (filesize > data->set.max_filesize)) { failf(data, "Maximum file size exceeded"); return CURLE_FILESIZE_EXCEEDED; @@ -2239,20 +2381,20 @@ static CURLcode ftp_state_retr(struct connectdata *conn, /* 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\n"); - /* We couldn't get the size and therefore we can't know if there really + infof(data, "ftp server does not support SIZE"); + /* We could not get the size and therefore we cannot 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 + close the connection when we start the connection so it will not 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(data->state.resume_from < 0) { + /* We are 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 ")", + failf(data, "Offset (%" FMT_OFF_T + ") was beyond file size (%" FMT_OFF_T ")", data->state.resume_from, filesize); return CURLE_BAD_DOWNLOAD_RESUME; } @@ -2263,8 +2405,8 @@ static CURLcode ftp_state_retr(struct connectdata *conn, } else { if(filesize < data->state.resume_from) { - failf(data, "Offset (%" CURL_FORMAT_CURL_OFF_T - ") was beyond file size (%" CURL_FORMAT_CURL_OFF_T ")", + failf(data, "Offset (%" FMT_OFF_T + ") was beyond file size (%" FMT_OFF_T ")", data->state.resume_from, filesize); return CURLE_BAD_DOWNLOAD_RESUME; } @@ -2275,72 +2417,101 @@ static CURLcode ftp_state_retr(struct connectdata *conn, if(ftp->downloadsize == 0) { /* no data to transfer */ - Curl_setup_transfer(conn, -1, -1, FALSE, NULL, -1, NULL); - infof(data, "File already completely downloaded\n"); + Curl_xfer_setup_nop(data); + 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 = FTPTRANSFER_NONE; - state(conn, FTP_STOP); + /* Set ->transfer so that we will not get any error in ftp_done() + * because we did not 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 "\n", data->state.resume_from); - - PPSENDF(&ftpc->pp, "REST %" CURL_FORMAT_CURL_OFF_T, - data->state.resume_from); + infof(data, "Instructs server to resume from offset %" FMT_OFF_T, + data->state.resume_from); - state(conn, FTP_RETR_REST); + result = Curl_pp_sendf(data, &ftpc->pp, "REST %" FMT_OFF_T, + data->state.resume_from); + if(!result) + ftp_state(data, FTP_RETR_REST); } else { /* no resume */ - PPSENDF(&ftpc->pp, "RETR %s", ftpc->file); - state(conn, FTP_RETR); + 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 connectdata *conn, +static CURLcode ftp_state_size_resp(struct Curl_easy *data, int ftpcode, ftpstate instate) { CURLcode result = CURLE_OK; - struct Curl_easy *data=conn->data; - curl_off_t filesize; - char *buf = data->state.buffer; + curl_off_t filesize = -1; + char *buf = Curl_dyn_ptr(&data->conn->proto.ftpc.pp.recvbuf); + size_t len = data->conn->proto.ftpc.pp.nfinal; /* get the size from the ascii string: */ - filesize = (ftpcode == 213)?curlx_strtoofft(buf+4, NULL, 0):-1; + 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 = memchr(start, '\r', len); + if(fdigit) { + fdigit--; + if(*fdigit == '\n') + fdigit--; + while(ISDIGIT(fdigit[-1]) && (fdigit > start)) + 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) { - snprintf(buf, CURL_BUFSIZE(data->set.buffer_size), - "Content-Length: %" CURL_FORMAT_CURL_OFF_T "\r\n", filesize); - result = Curl_client_write(conn, CLIENTWRITE_BOTH, buf, 0); + char clbuf[128]; + int clbuflen = msnprintf(clbuf, sizeof(clbuf), + "Content-Length: %" FMT_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(conn); + result = ftp_state_rest(data, data->conn); } else if(instate == FTP_RETR_SIZE) { Curl_pgrsSetDownloadSize(data, filesize); - result = ftp_state_retr(conn, filesize); + result = ftp_state_retr(data, filesize); } else if(instate == FTP_STOR_SIZE) { data->state.resume_from = filesize; - result = ftp_state_ul_setup(conn, TRUE); + result = ftp_state_ul_setup(data, TRUE); } return result; } -static CURLcode ftp_state_rest_resp(struct connectdata *conn, +static CURLcode ftp_state_rest_resp(struct Curl_easy *data, + struct connectdata *conn, int ftpcode, ftpstate instate) { @@ -2353,22 +2524,23 @@ static CURLcode ftp_state_rest_resp(struct connectdata *conn, #ifdef CURL_FTP_HTTPSTYLE_HEAD if(ftpcode == 350) { char buffer[24]= { "Accept-ranges: bytes\r\n" }; - result = Curl_client_write(conn, CLIENTWRITE_BOTH, buffer, 0); + result = client_write_header(data, buffer, strlen(buffer)); if(result) return result; } #endif - result = ftp_state_prepare_transfer(conn); + result = ftp_state_prepare_transfer(data); break; case FTP_RETR_REST: if(ftpcode != 350) { - failf(conn->data, "Couldn't use REST"); + failf(data, "Couldn't use REST"); result = CURLE_FTP_COULDNT_USE_REST; } else { - PPSENDF(&ftpc->pp, "RETR %s", ftpc->file); - state(conn, FTP_RETR); + result = Curl_pp_sendf(data, &ftpc->pp, "RETR %s", ftpc->file); + if(!result) + ftp_state(data, FTP_RETR); } break; } @@ -2376,15 +2548,15 @@ static CURLcode ftp_state_rest_resp(struct connectdata *conn, return result; } -static CURLcode ftp_state_stor_resp(struct connectdata *conn, +static CURLcode ftp_state_stor_resp(struct Curl_easy *data, int ftpcode, ftpstate instate) { CURLcode result = CURLE_OK; - struct Curl_easy *data = conn->data; + struct connectdata *conn = data->conn; - if(ftpcode>=400) { + if(ftpcode >= 400) { failf(data, "Failed FTP upload: %0d", ftpcode); - state(conn, FTP_STOP); + ftp_state(data, FTP_STOP); /* oops, we never close the sockets! */ return CURLE_UPLOAD_FAILED; } @@ -2395,33 +2567,31 @@ static CURLcode ftp_state_stor_resp(struct connectdata *conn, if(data->set.ftp_use_port) { bool connected; - state(conn, FTP_STOP); /* no longer in STOR state */ + ftp_state(data, FTP_STOP); /* no longer in STOR state */ - result = AllowServerConnect(conn, &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\n"); + infof(data, "Data conn was not available immediately"); ftpc->wait_data_conn = TRUE; } return CURLE_OK; } - else - return InitiateTransfer(conn); + return InitiateTransfer(data); } /* for LIST and RETR responses */ -static CURLcode ftp_state_get_resp(struct connectdata *conn, - int ftpcode, - ftpstate instate) +static CURLcode ftp_state_get_resp(struct Curl_easy *data, + int ftpcode, + ftpstate instate) { CURLcode result = CURLE_OK; - struct Curl_easy *data = conn->data; - struct FTP *ftp = data->req.protop; - char *buf = data->state.buffer; + struct FTP *ftp = data->req.p.ftp; + struct connectdata *conn = data->conn; if((ftpcode == 150) || (ftpcode == 125)) { @@ -2442,7 +2612,7 @@ static CURLcode ftp_state_get_resp(struct connectdata *conn, E: 125 Data connection already open; Transfer starting. */ - curl_off_t size=-1; /* default unknown size */ + curl_off_t size = -1; /* default unknown size */ /* @@ -2455,19 +2625,21 @@ static CURLcode ftp_state_get_resp(struct connectdata *conn, */ if((instate != FTP_LIST) && - !data->set.prefer_ascii && + !data->state.prefer_ascii && + !data->set.ignorecl && (ftp->downloadsize < 1)) { /* - * It seems directory listings either don't show the size or very + * It seems directory listings either do not 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; - bytes=strstr(buf, " bytes"); - if(bytes--) { - long in=(long)(bytes-buf); + char *buf = Curl_dyn_ptr(&conn->proto.ftpc.pp.recvbuf); + 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 */ @@ -2475,16 +2647,17 @@ static CURLcode ftp_state_get_resp(struct connectdata *conn, break; /* skip only digits */ if(!ISDIGIT(*bytes)) { - bytes=NULL; + bytes = NULL; break; } /* one more estep backwards */ bytes--; } /* if we have nothing but digits: */ - if(bytes++) { + if(bytes) { + ++bytes; /* get the number! */ - size = curlx_strtoofft(bytes, NULL, 0); + (void)curlx_strtoofft(bytes, NULL, 10, &size); } } } @@ -2493,15 +2666,13 @@ static CURLcode ftp_state_get_resp(struct connectdata *conn, if(size > data->req.maxdownload && data->req.maxdownload > 0) size = data->req.size = data->req.maxdownload; - else if((instate != FTP_LIST) && (data->set.prefer_ascii)) + 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 "\n", - data->req.maxdownload); + infof(data, "Maxdownload = %" FMT_OFF_T, data->req.maxdownload); if(instate != FTP_LIST) - infof(data, "Getting file with size: %" CURL_FORMAT_CURL_OFF_T "\n", - size); + infof(data, "Getting file with size: %" FMT_OFF_T, size); /* FTP download: */ conn->proto.ftpc.state_saved = instate; @@ -2510,30 +2681,30 @@ static CURLcode ftp_state_get_resp(struct connectdata *conn, if(data->set.ftp_use_port) { bool connected; - result = AllowServerConnect(conn, &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\n"); - state(conn, FTP_STOP); + infof(data, "Data conn was not available immediately"); + ftp_state(data, FTP_STOP); ftpc->wait_data_conn = TRUE; } } else - return InitiateTransfer(conn); + return InitiateTransfer(data); } else { if((instate == FTP_LIST) && (ftpcode == 450)) { /* simply no matching files in the dir listing */ - ftp->transfer = FTPTRANSFER_NONE; /* don't download anything */ - state(conn, FTP_STOP); /* this phase is over */ + ftp->transfer = PPTRANSFER_NONE; /* do not 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: + return instate == FTP_RETR && ftpcode == 550 ? + CURLE_REMOTE_FILE_NOT_FOUND : CURLE_FTP_COULDNT_RETR_FILE; } } @@ -2542,11 +2713,12 @@ static CURLcode ftp_state_get_resp(struct connectdata *conn, } /* after USER, PASS and ACCT */ -static CURLcode ftp_state_loggedin(struct connectdata *conn) +static CURLcode ftp_state_loggedin(struct Curl_easy *data) { CURLcode result = CURLE_OK; + struct connectdata *conn = data->conn; - if(conn->ssl[FIRSTSOCKET].use) { + if(conn->bits.ftp_use_control_ssl) { /* PBSZ = PROTECTION BUFFER SIZE. The 'draft-murray-auth-ftp-ssl' (draft 12, page 7) says: @@ -2561,42 +2733,44 @@ static CURLcode ftp_state_loggedin(struct connectdata *conn) parameter of '0' to indicate that no buffering is taking place and the data connection should not be encapsulated. */ - PPSENDF(&conn->proto.ftpc.pp, "PBSZ %d", 0); - state(conn, FTP_PBSZ); + result = Curl_pp_sendf(data, &conn->proto.ftpc.pp, "PBSZ %d", 0); + if(!result) + ftp_state(data, FTP_PBSZ); } else { - result = ftp_state_pwd(conn); + result = ftp_state_pwd(data, conn); } return result; } /* for USER and PASS responses */ -static CURLcode ftp_state_user_resp(struct connectdata *conn, - int ftpcode, - ftpstate instate) +static CURLcode ftp_state_user_resp(struct Curl_easy *data, + int ftpcode) { CURLcode result = CURLE_OK; - struct Curl_easy *data = conn->data; - struct FTP *ftp = data->req.protop; + struct connectdata *conn = data->conn; struct ftp_conn *ftpc = &conn->proto.ftpc; - (void)instate; /* no use for this yet */ /* 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) */ - PPSENDF(&ftpc->pp, "PASS %s", ftp->passwd?ftp->passwd:""); - state(conn, FTP_PASS); + 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(conn); + result = ftp_state_loggedin(data); } else if(ftpcode == 332) { if(data->set.str[STRING_FTP_ACCOUNT]) { - PPSENDF(&ftpc->pp, "ACCT %s", data->set.str[STRING_FTP_ACCOUNT]); - state(conn, FTP_ACCT); + 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"); @@ -2609,14 +2783,16 @@ static CURLcode ftp_state_user_resp(struct connectdata *conn, 530 User ... access denied (the server denies to log the specified user) */ - if(conn->data->set.str[STRING_FTP_ALTERNATIVE_TO_USER] && - !conn->data->state.ftp_trying_alternative) { - /* Ok, USER failed. Let's try the supplied command. */ - PPSENDF(&conn->proto.ftpc.pp, "%s", - conn->data->set.str[STRING_FTP_ALTERNATIVE_TO_USER]); - conn->data->state.ftp_trying_alternative = TRUE; - state(conn, FTP_USER); - result = CURLE_OK; + 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); @@ -2627,37 +2803,35 @@ static CURLcode ftp_state_user_resp(struct connectdata *conn, } /* for ACCT response */ -static CURLcode ftp_state_acct_resp(struct connectdata *conn, +static CURLcode ftp_state_acct_resp(struct Curl_easy *data, int ftpcode) { CURLcode result = CURLE_OK; - struct Curl_easy *data = conn->data; if(ftpcode != 230) { failf(data, "ACCT rejected by server: %03d", ftpcode); result = CURLE_FTP_WEIRD_PASS_REPLY; /* FIX */ } else - result = ftp_state_loggedin(conn); + result = ftp_state_loggedin(data); return result; } -static CURLcode ftp_statemach_act(struct connectdata *conn) +static CURLcode ftp_statemachine(struct Curl_easy *data, + struct connectdata *conn) { CURLcode result; - curl_socket_t sock = conn->sock[FIRSTSOCKET]; - struct Curl_easy *data=conn->data; int ftpcode; struct ftp_conn *ftpc = &conn->proto.ftpc; struct pingpong *pp = &ftpc->pp; - static const char ftpauth[][4] = { "SSL", "TLS" }; + static const char * const ftpauth[] = { "SSL", "TLS" }; size_t nread = 0; if(pp->sendleft) - return Curl_pp_flushsend(pp); + return Curl_pp_flushsend(data, pp); - result = ftp_readresp(sock, pp, &ftpcode, &nread); + result = ftp_readresp(data, FIRSTSOCKET, pp, &ftpcode, &nread); if(result) return result; @@ -2665,9 +2839,12 @@ static CURLcode ftp_statemach_act(struct connectdata *conn) /* we have now received a full FTP server response */ switch(ftpc->state) { case FTP_WAIT220: - if(ftpcode == 230) - /* 230 User logged in - already! */ - return ftp_state_user_resp(conn, ftpcode, ftpc->state); + 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); @@ -2685,21 +2862,19 @@ static CURLcode ftp_statemach_act(struct connectdata *conn) set a valid level */ Curl_sec_request_prot(conn, data->set.str[STRING_KRB_LEVEL]); - if(Curl_sec_login(conn)) - infof(data, "Logging in with password in cleartext!\n"); - else - infof(data, "Authentication successful\n"); + 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->ssl[FIRSTSOCKET].use || - (conn->bits.proxy_ssl_connected[FIRSTSOCKET] && - !conn->proxy_ssl[FIRSTSOCKET].use))) { - /* We don't have a SSL/TLS connection yet, but FTPS is + if(data->set.use_ssl && !conn->bits.ftp_use_control_ssl) { + /* We do not have a SSL/TLS control connection yet, but FTPS is requested. Try a FTPS connection now */ - ftpc->count3=0; + ftpc->count3 = 0; switch(data->set.ftpsslauth) { case CURLFTPAUTH_DEFAULT: case CURLFTPAUTH_SSL: @@ -2713,22 +2888,23 @@ static CURLcode ftp_statemach_act(struct connectdata *conn) default: failf(data, "unsupported parameter to CURLOPT_FTPSSLAUTH: %d", (int)data->set.ftpsslauth); - return CURLE_UNKNOWN_OPTION; /* we don't know what to do */ + return CURLE_UNKNOWN_OPTION; /* we do not know what to do */ } - PPSENDF(&ftpc->pp, "AUTH %s", ftpauth[ftpc->count1]); - state(conn, FTP_AUTH); - } - else { - result = ftp_state_user(conn); - if(result) - return result; + 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->overflow) + return CURLE_WEIRD_SERVER_REPLY; /* Forbid pipelining in response. */ + /* RFC2228 (page 5) says: * * If the server is willing to accept the named security mechanism, @@ -2737,17 +2913,27 @@ static CURLcode ftp_statemach_act(struct connectdata *conn) */ if((ftpcode == 234) || (ftpcode == 334)) { - /* Curl_ssl_connect is BLOCKING */ - result = Curl_ssl_connect(conn, FIRSTSOCKET); + /* 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 */ - result = ftp_state_user(conn); + 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(&ftpc->pp, "AUTH %s", ftpauth[ftpc->count1]); + result = Curl_pp_sendf(data, &ftpc->pp, "AUTH %s", + ftpauth[ftpc->count1]); /* remain in this same state */ } else { @@ -2756,27 +2942,25 @@ static CURLcode ftp_statemach_act(struct connectdata *conn) result = CURLE_USE_SSL_FAILED; else /* ignore the failure and continue */ - result = ftp_state_user(conn); + result = ftp_state_user(data, conn); } - - if(result) - return result; break; case FTP_USER: case FTP_PASS: - result = ftp_state_user_resp(conn, ftpcode, ftpc->state); + result = ftp_state_user_resp(data, ftpcode); break; case FTP_ACCT: - result = ftp_state_acct_resp(conn, ftpcode); + result = ftp_state_acct_resp(data, ftpcode); break; case FTP_PBSZ: - PPSENDF(&ftpc->pp, "PROT %c", - data->set.use_ssl == CURLUSESSL_CONTROL ? 'C' : 'P'); - state(conn, FTP_PROT); - + 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: @@ -2793,43 +2977,40 @@ static CURLcode ftp_statemach_act(struct connectdata *conn) if(data->set.ftp_ccc) { /* CCC - Clear Command Channel */ - PPSENDF(&ftpc->pp, "%s", "CCC"); - state(conn, FTP_CCC); - } - else { - result = ftp_state_pwd(conn); - if(result) - return result; + 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_shutdown(conn, FIRSTSOCKET); + /* This has only been tested on the proftpd server, and the mod_tls + * code sends a close notify alert without waiting for a close notify + * alert in response. Thus we wait for a close notify alert from the + * server, but we do not send one. Let's hope other servers do + * the same... */ + result = Curl_ssl_cfilter_remove(data, FIRSTSOCKET, + (data->set.ftp_ccc == CURLFTPSSL_CCC_ACTIVE)); - if(result) { - failf(conn->data, "Failed to clear the command channel (CCC)"); - return result; - } + if(result) + failf(data, "Failed to clear the command channel (CCC)"); } - - /* Then continue as normal */ - result = ftp_state_pwd(conn); - if(result) - return result; + 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 = CURL_BUFSIZE(data->set.buffer_size); - char *dir; - char *store; - - dir = malloc(nread + 1); - if(!dir) - return CURLE_OUT_OF_MEMORY; + char *ptr = Curl_dyn_ptr(&pp->recvbuf) + 4; /* start on the first + letter */ + bool entry_extracted = FALSE; + struct dynbuf out; + Curl_dyn_init(&out, 1000); /* Reply format is like 257[rubbish]"" and the @@ -2841,100 +3022,98 @@ static CURLcode ftp_statemach_act(struct connectdata *conn) */ /* scan for the first double-quote for non-standard responses */ - while(ptr < &data->state.buffer[buf_size] - && *ptr != '\n' && *ptr != '\0' && *ptr != '"') + while(*ptr != '\n' && *ptr != '\0' && *ptr != '"') ptr++; if('\"' == *ptr) { /* it started good */ - ptr++; - for(store = dir; *ptr;) { + for(ptr++; *ptr; ptr++) { if('\"' == *ptr) { if('\"' == ptr[1]) { /* "quote-doubling" */ - *store = ptr[1]; + result = Curl_dyn_addn(&out, &ptr[1], 1); ptr++; } else { /* end of path */ - *store = '\0'; /* zero terminate */ + if(Curl_dyn_len(&out)) + entry_extracted = TRUE; break; /* get out of this loop */ } } else - *store = *ptr; - store++; - ptr++; + result = Curl_dyn_addn(&out, ptr, 1); + if(result) + return result; } - + } + 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 pathes. In + 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. */ + char *dir = Curl_dyn_ptr(&out); if(!ftpc->server_os && dir[0] != '/') { - - result = Curl_pp_sendf(&ftpc->pp, "%s", "SYST"); + 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'\n", ftpc->entrypath); + infof(data, "Entry path is '%s'", ftpc->entrypath); /* also save it where getinfo can access it: */ data->state.most_recent_ftp_entrypath = ftpc->entrypath; - state(conn, FTP_SYST); + ftp_state(data, FTP_SYST); break; } Curl_safefree(ftpc->entrypath); ftpc->entrypath = dir; /* remember this */ - infof(data, "Entry path is '%s'\n", ftpc->entrypath); + 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\n"); + /* could not get the path */ + Curl_dyn_free(&out); + infof(data, "Failed to figure out path"); } } - state(conn, FTP_STOP); /* we are done with the CONNECT phase! */ - DEBUGF(infof(data, "protocol connect phase DONE\n")); + ftp_state(data, FTP_STOP); /* we are done with the CONNECT phase! */ + CURL_TRC_FTP(data, "[%s] protocol connect phase DONE", FTP_DSTATE(data)); break; case FTP_SYST: if(ftpcode == 215) { - char *ptr=&data->state.buffer[4]; /* start on the first letter */ + char *ptr = Curl_dyn_ptr(&pp->recvbuf) + 4; /* start on the first + letter */ char *os; - char *store; - - os = malloc(nread + 1); - if(!os) - return CURLE_OUT_OF_MEMORY; + char *start; /* Reply format is like 215 */ while(*ptr == ' ') ptr++; - for(store = os; *ptr && *ptr != ' ';) - *store++ = *ptr++; - *store = '\0'; /* zero terminate */ + for(start = ptr; *ptr && *ptr != ' '; ptr++) + ; + os = Curl_memdup0(start, ptr - start); + if(!os) + return CURLE_OUT_OF_MEMORY; /* Check for special servers here. */ - if(strcasecompare(os, "OS/400")) { /* Force OS400 name format 1. */ - result = Curl_pp_sendf(&ftpc->pp, "%s", "SITE NAMEFMT 1"); + result = Curl_pp_sendf(data, &ftpc->pp, "%s", "SITE NAMEFMT 1"); if(result) { free(os); return result; @@ -2942,33 +3121,31 @@ static CURLcode ftp_statemach_act(struct connectdata *conn) /* remember target server OS */ Curl_safefree(ftpc->server_os); ftpc->server_os = os; - state(conn, FTP_NAMEFMT); + ftp_state(data, FTP_NAMEFMT); break; } - else { - /* Nothing special for the target server. */ - /* remember target server OS */ - Curl_safefree(ftpc->server_os); - ftpc->server_os = os; - } + /* 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. */ } - state(conn, FTP_STOP); /* we are done with the CONNECT phase! */ - DEBUGF(infof(data, "protocol connect phase DONE\n")); + ftp_state(data, FTP_STOP); /* we are done with the CONNECT phase! */ + CURL_TRC_FTP(data, "[%s] protocol connect phase DONE", FTP_DSTATE(data)); break; case FTP_NAMEFMT: if(ftpcode == 250) { /* Name format change successful: reload initial path. */ - ftp_state_pwd(conn); + ftp_state_pwd(data, conn); break; } - state(conn, FTP_STOP); /* we are done with the CONNECT phase! */ - DEBUGF(infof(data, "protocol connect phase DONE\n")); + ftp_state(data, FTP_STOP); /* we are done with the CONNECT phase! */ + CURL_TRC_FTP(data, "[%s] protocol connect phase DONE", FTP_DSTATE(data)); break; case FTP_QUOTE: @@ -2977,45 +3154,48 @@ static CURLcode ftp_statemach_act(struct connectdata *conn) case FTP_STOR_PREQUOTE: if((ftpcode >= 400) && !ftpc->count2) { /* failure response code, and not allowed to fail */ - failf(conn->data, "QUOT command failed with %03d", ftpcode); - return CURLE_QUOTE_ERROR; + failf(data, "QUOT command failed with %03d", ftpcode); + result = CURLE_QUOTE_ERROR; } - result = ftp_state_quote(conn, FALSE, ftpc->state); - if(result) - return result; - + else + result = ftp_state_quote(data, FALSE, ftpc->state); break; case FTP_CWD: if(ftpcode/100 != 2) { /* failure to CWD there */ - if(conn->data->set.ftp_create_missing_dirs && - ftpc->count1 && !ftpc->count2) { + if(data->set.ftp_create_missing_dirs && + ftpc->cwdcount && !ftpc->count2) { /* try making it */ ftpc->count2++; /* counter to prevent CWD-MKD loops */ - PPSENDF(&ftpc->pp, "MKD %s", ftpc->dirs[ftpc->count1 - 1]); - state(conn, FTP_MKD); + + /* 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 + ftpc->cwdfail = TRUE; /* do not remember this path as we failed to enter it */ - return CURLE_REMOTE_ACCESS_DENIED; + result = CURLE_REMOTE_ACCESS_DENIED; } } else { /* success */ - ftpc->count2=0; - if(++ftpc->count1 <= ftpc->dirdepth) { + ftpc->count2 = 0; + if(++ftpc->cwdcount <= ftpc->dirdepth) /* send next CWD */ - PPSENDF(&ftpc->pp, "CWD %s", ftpc->dirs[ftpc->count1 - 1]); - } - else { - result = ftp_state_mdtm(conn); - if(result) - return result; - } + result = Curl_pp_sendf(data, &ftpc->pp, "CWD %s", + ftpc->dirs[ftpc->cwdcount - 1]); + else + result = ftp_state_mdtm(data); } break; @@ -3023,33 +3203,36 @@ static CURLcode ftp_statemach_act(struct connectdata *conn) if((ftpcode/100 != 2) && !ftpc->count3--) { /* failure to MKD the dir */ failf(data, "Failed to MKD dir: %03d", ftpcode); - return CURLE_REMOTE_ACCESS_DENIED; + 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]); } - state(conn, FTP_CWD); - /* send CWD */ - PPSENDF(&ftpc->pp, "CWD %s", ftpc->dirs[ftpc->count1 - 1]); break; case FTP_MDTM: - result = ftp_state_mdtm_resp(conn, ftpcode); + 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(conn, ftpcode, ftpc->state); + 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(conn, ftpcode, ftpc->state); + result = ftp_state_size_resp(data, ftpcode, ftpc->state); break; case FTP_REST: case FTP_RETR_REST: - result = ftp_state_rest_resp(conn, ftpcode, ftpc->state); + result = ftp_state_rest_resp(data, conn, ftpcode, ftpc->state); break; case FTP_PRET: @@ -3058,31 +3241,30 @@ static CURLcode ftp_statemach_act(struct connectdata *conn) failf(data, "PRET command not accepted: %03d", ftpcode); return CURLE_FTP_PRET_FAILED; } - result = ftp_state_use_pasv(conn); + result = ftp_state_use_pasv(data, conn); break; case FTP_PASV: - result = ftp_state_pasv_resp(conn, ftpcode); + result = ftp_state_pasv_resp(data, ftpcode); break; case FTP_PORT: - result = ftp_state_port_resp(conn, ftpcode); + result = ftp_state_port_resp(data, ftpcode); break; case FTP_LIST: case FTP_RETR: - result = ftp_state_get_resp(conn, ftpcode, ftpc->state); + result = ftp_state_get_resp(data, ftpcode, ftpc->state); break; case FTP_STOR: - result = ftp_state_stor_resp(conn, ftpcode, ftpc->state); + result = ftp_state_stor_resp(data, ftpcode, ftpc->state); break; case FTP_QUIT: - /* fallthrough, just stop! */ default: /* internal error */ - state(conn, FTP_STOP); + ftp_state(data, FTP_STOP); break; } } /* if(ftpcode) */ @@ -3092,11 +3274,12 @@ static CURLcode ftp_statemach_act(struct connectdata *conn) /* called repeatedly until done from multi.c */ -static CURLcode ftp_multi_statemach(struct connectdata *conn, +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(&ftpc->pp, FALSE); + 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 @@ -3106,14 +3289,15 @@ static CURLcode ftp_multi_statemach(struct connectdata *conn, return result; } -static CURLcode ftp_block_statemach(struct connectdata *conn) +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(pp, TRUE); + result = Curl_pp_statemach(data, pp, TRUE, TRUE /* disconnecting */); if(result) break; } @@ -3129,10 +3313,11 @@ static CURLcode ftp_block_statemach(struct connectdata *conn) * phase is done when this function returns, or FALSE if not. * */ -static CURLcode ftp_connect(struct connectdata *conn, - bool *done) /* see description above */ +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; @@ -3141,25 +3326,23 @@ static CURLcode ftp_connect(struct connectdata *conn, /* We always support persistent connections on ftp */ connkeep(conn, "FTP default"); - pp->response_time = RESP_TIMEOUT; /* set default response time-out */ - pp->statemach_act = ftp_statemach_act; - pp->endofresp = ftp_endofresp; - pp->conn = conn; + PINGPONG_SETUP(pp, ftp_statemachine, ftp_endofresp); if(conn->handler->flags & PROTOPT_SSL) { /* BLOCKING */ - result = Curl_ssl_connect(conn, FIRSTSOCKET); + result = Curl_conn_connect(data, FIRSTSOCKET, TRUE, done); if(result) return result; + conn->bits.ftp_use_control_ssl = TRUE; } - Curl_pp_init(pp); /* init the generic pingpong data */ + Curl_pp_init(pp); /* once per transfer */ /* When we connect, we start in the state where we await the 220 response */ - state(conn, FTP_WAIT220); + ftp_state(data, FTP_WAIT220); - result = ftp_multi_statemach(conn, done); + result = ftp_multi_statemach(data, done); return result; } @@ -3173,18 +3356,18 @@ static CURLcode ftp_connect(struct connectdata *conn, * * Input argument is already checked for validity. */ -static CURLcode ftp_done(struct connectdata *conn, CURLcode status, +static CURLcode ftp_done(struct Curl_easy *data, CURLcode status, bool premature) { - struct Curl_easy *data = conn->data; - struct FTP *ftp = data->req.protop; + 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 *path = NULL; - const char *path_to_use = data->state.path; + char *rawPath = NULL; + size_t pathLen = 0; if(!ftp) return CURLE_OK; @@ -3204,13 +3387,13 @@ static CURLcode ftp_done(struct connectdata *conn, CURLcode status, 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 */ + case CURLE_OK: /* does not 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; @@ -3221,53 +3404,57 @@ static CURLcode ftp_done(struct connectdata *conn, CURLcode status, break; } - /* now store a copy of the directory we are in */ - free(ftpc->prevpath); - - if(data->set.wildcardmatch) { + if(data->state.wildcardmatch) { if(data->set.chunk_end && ftpc->file) { - data->set.chunk_end(data->wildcard.customptr); + 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 "raw" path */ - result = Curl_urldecode(data, path_to_use, 0, &path, NULL, FALSE); + /* 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 { - size_t flen = ftpc->file?strlen(ftpc->file):0; /* file is "raw" already */ - size_t dlen = strlen(path)-flen; - if(!ftpc->cwdfail) { - if(dlen && (data->set.ftp_filemethod != FTPFILE_NOCWD)) { - ftpc->prevpath = path; - if(flen) - /* if 'path' is not the whole string */ - ftpc->prevpath[dlen]=0; /* terminate */ + 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 + /* file is url-decoded */ + pathLen -= ftpc->file ? strlen(ftpc->file) : 0; + + rawPath[pathLen] = '\0'; + ftpc->prevpath = rawPath; } else { - /* we never changed dir */ - ftpc->prevpath=strdup(""); - free(path); + free(rawPath); + ftpc->prevpath = NULL; /* no path */ } - if(ftpc->prevpath) - infof(data, "Remembering we are in dir \"%s\"\n", ftpc->prevpath); - } - else { - ftpc->prevpath = NULL; /* no path */ - free(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 */ + /* shut down the socket to inform the server we are done */ #ifdef _WIN32_WCE shutdown(conn->sock[SECONDARYSOCKET], 2); /* SD_BOTH */ @@ -3276,7 +3463,7 @@ static CURLcode ftp_done(struct connectdata *conn, CURLcode status, if(conn->sock[SECONDARYSOCKET] != CURL_SOCKET_BAD) { if(!result && ftpc->dont_check && data->req.maxdownload > 0) { /* partial download completed */ - result = Curl_pp_sendf(pp, "%s", "ABOR"); + result = Curl_pp_sendf(data, pp, "%s", "ABOR"); if(result) { failf(data, "Failure sending ABOR command: %s", curl_easy_strerror(result)); @@ -3285,18 +3472,10 @@ static CURLcode ftp_done(struct connectdata *conn, CURLcode status, } } - if(conn->ssl[SECONDARYSOCKET].use) { - /* The secondary socket is using SSL so we must close down that part - first before we close the socket for real */ - Curl_ssl_close(conn, SECONDARYSOCKET); - - /* Note that we keep "use" set to TRUE since that (next) connection is - still requested to use SSL */ - } - close_secondarysocket(conn); + close_secondarysocket(data); } - if(!result && (ftp->transfer == FTPTRANSFER_BODY) && ftpc->ctl_valid && + 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, @@ -3304,12 +3483,12 @@ static CURLcode ftp_done(struct connectdata *conn, CURLcode status, * data has been transferred. This happens when doing through NATs etc that * abandon old silent connections. */ - long old_time = pp->response_time; + timediff_t old_time = pp->response_time; pp->response_time = 60*1000; /* give it only a minute for now */ - pp->response = Curl_tvnow(); /* timeout relative now */ + pp->response = Curl_now(); /* timeout relative now */ - result = Curl_GetFTPResponse(&nread, conn, &ftpcode); + result = Curl_GetFTPResponse(data, &nread, &ftpcode); pp->response_time = old_time; /* set this back to previous value */ @@ -3319,22 +3498,33 @@ static CURLcode ftp_done(struct connectdata *conn, CURLcode status, connclose(conn, "Timeout or similar in FTP DONE operation"); /* close */ } - if(result) + 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\n"); + 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. */ - if((ftpcode != 226) && (ftpcode != 250)) { + 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; } } } @@ -3343,49 +3533,42 @@ static CURLcode ftp_done(struct connectdata *conn, CURLcode status, /* the response code from the transfer showed an error already so no use checking further */ ; - else if(data->set.upload) { + else if(data->state.upload) { if((-1 != data->state.infilesize) && - (data->state.infilesize != *ftp->bytecountp) && + (data->state.infilesize != data->req.writebytecount) && !data->set.crlf && - (ftp->transfer == FTPTRANSFER_BODY)) { - failf(data, "Uploaded unaligned file size (%" CURL_FORMAT_CURL_OFF_T - " out of %" CURL_FORMAT_CURL_OFF_T " bytes)", - *ftp->bytecountp, data->state.infilesize); + (ftp->transfer == PPTRANSFER_BODY)) { + failf(data, "Uploaded unaligned file size (%" FMT_OFF_T + " out of %" FMT_OFF_T " bytes)", + data->req.writebytecount, data->state.infilesize); result = CURLE_PARTIAL_FILE; } } else { if((-1 != data->req.size) && - (data->req.size != *ftp->bytecountp) && -#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) != - *ftp->bytecountp) && -#endif /* CURL_DO_LINEEND_CONV */ - (data->req.maxdownload != *ftp->bytecountp)) { - failf(data, "Received only partial file: %" CURL_FORMAT_CURL_OFF_T - " bytes", *ftp->bytecountp); + (data->req.size != data->req.bytecount) && + (data->req.maxdownload != data->req.bytecount)) { + failf(data, "Received only partial file: %" FMT_OFF_T " bytes", + data->req.bytecount); result = CURLE_PARTIAL_FILE; } else if(!ftpc->dont_check && - !*ftp->bytecountp && - (data->req.size>0)) { - failf(data, "No data was received!"); + !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 = FTPTRANSFER_BODY; + ftp->transfer = PPTRANSFER_BODY; ftpc->dont_check = FALSE; /* Send any post-transfer QUOTE strings? */ if(!status && !result && !premature && data->set.postquote) - result = ftp_sendquote(conn, data->set.postquote); - + result = ftp_sendquote(data, conn, data->set.postquote); + CURL_TRC_FTP(data, "[%s] done, result=%d", FTP_DSTATE(data), result); + Curl_safefree(ftp->pathalloc); return result; } @@ -3400,41 +3583,42 @@ static CURLcode ftp_done(struct connectdata *conn, CURLcode status, */ static -CURLcode ftp_sendquote(struct connectdata *conn, struct curl_slist *quote) +CURLcode ftp_sendquote(struct Curl_easy *data, + struct connectdata *conn, struct curl_slist *quote) { struct curl_slist *item; - ssize_t nread; - int ftpcode; - CURLcode result; 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 reponds. */ + is successful, whatever the server responds. */ if(cmd[0] == '*') { cmd++; acceptfail = TRUE; } - PPSENDF(&conn->proto.ftpc.pp, "%s", cmd); - - pp->response = Curl_tvnow(); /* timeout relative now */ - - result = Curl_GetFTPResponse(&nread, conn, &ftpcode); + 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(conn->data, "QUOT string not accepted: %s", cmd); + failf(data, "QUOT string not accepted: %s", cmd); return CURLE_QUOTE_ERROR; } } @@ -3454,7 +3638,7 @@ CURLcode ftp_sendquote(struct connectdata *conn, struct curl_slist *quote) static int ftp_need_type(struct connectdata *conn, bool ascii_wanted) { - return conn->proto.ftpc.transfertype != (ascii_wanted?'A':'I'); + return conn->proto.ftpc.transfertype != (ascii_wanted ? 'A' : 'I'); } /*********************************************************************** @@ -3465,24 +3649,27 @@ static int ftp_need_type(struct connectdata *conn, * sets one of them. * If the transfer type is not sent, simulate on OK response in newstate */ -static CURLcode ftp_nb_type(struct connectdata *conn, +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'); + char want = (char)(ascii ? 'A' : 'I'); if(ftpc->transfertype == want) { - state(conn, newstate); - return ftp_state_type_resp(conn, 200, newstate); + ftp_state(data, newstate); + return ftp_state_type_resp(data, 200, newstate); } - PPSENDF(&ftpc->pp, "TYPE %c", want); - state(conn, 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 CURLE_OK; + /* keep track of our current transfer type */ + ftpc->transfertype = want; + } + return result; } /*************************************************************************** @@ -3490,78 +3677,23 @@ static CURLcode ftp_nb_type(struct connectdata *conn, * 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 + * when we have 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 connectdata *conn, - Curl_addrinfo *ai, - char *newhost, /* ascii version */ +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(conn->data, "Connecting to %s (%s) port %d\n", newhost, buf, port); + infof(data, "Connecting to %s (%s) port %d", newhost, buf, port); } #endif -/* - Check if this is a range download, and if so, set the internal variables - properly. - */ - -static CURLcode ftp_range(struct connectdata *conn) -{ - curl_off_t from, to; - char *ptr; - char *ptr2; - struct Curl_easy *data = conn->data; - struct ftp_conn *ftpc = &conn->proto.ftpc; - - if(data->state.use_range && data->state.range) { - from=curlx_strtoofft(data->state.range, &ptr, 0); - while(*ptr && (ISSPACE(*ptr) || (*ptr=='-'))) - ptr++; - to=curlx_strtoofft(ptr, &ptr2, 0); - if(ptr == ptr2) { - /* we didn't get any digit */ - to=-1; - } - if((-1 == to) && (from>=0)) { - /* X - */ - data->state.resume_from = from; - DEBUGF(infof(conn->data, "FTP RANGE %" CURL_FORMAT_CURL_OFF_T - " to end of file\n", from)); - } - else if(from < 0) { - /* -Y */ - data->req.maxdownload = -from; - data->state.resume_from = from; - DEBUGF(infof(conn->data, "FTP RANGE the last %" CURL_FORMAT_CURL_OFF_T - " bytes\n", -from)); - } - else { - /* X-Y */ - data->req.maxdownload = (to-from)+1; /* include last byte */ - data->state.resume_from = from; - DEBUGF(infof(conn->data, "FTP RANGE from %" CURL_FORMAT_CURL_OFF_T - " getting %" CURL_FORMAT_CURL_OFF_T " bytes\n", - from, data->req.maxdownload)); - } - DEBUGF(infof(conn->data, "range-download from %" CURL_FORMAT_CURL_OFF_T - " to %" CURL_FORMAT_CURL_OFF_T ", totally %" - CURL_FORMAT_CURL_OFF_T " bytes\n", - from, to, data->req.maxdownload)); - ftpc->dont_check = TRUE; /* dont check for successful transfer */ - } - else - data->req.maxdownload = -1; - return CURLE_OK; -} - - /* * ftp_do_more() * @@ -3573,91 +3705,75 @@ static CURLcode ftp_range(struct connectdata *conn) * EPSV). */ -static CURLcode ftp_do_more(struct connectdata *conn, int *completep) +static CURLcode ftp_do_more(struct Curl_easy *data, int *completep) { - struct Curl_easy *data=conn->data; + 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() */ - struct FTP *ftp = data->req.protop; - - /* if the second connection isn't done yet, wait for it */ - if(!conn->bits.tcpconnect[SECONDARYSOCKET]) { - if(conn->tunnel_state[SECONDARYSOCKET] == TUNNEL_CONNECT) { - /* As we're in TUNNEL_CONNECT state now, we know the proxy name and port - aren't used so we blank their arguments. TODO: make this nicer */ - result = Curl_proxyCONNECT(conn, SECONDARYSOCKET, NULL, 0, FALSE); - - return result; - } - - result = Curl_is_connected(conn, SECONDARYSOCKET, &connected); - - /* Ready to do more? */ - if(connected) { - DEBUGF(infof(data, "DO-MORE connected phase starts\n")); - } - else { + /* 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 is not 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(conn); + return ftp_epsv_disable(data, conn); } return result; } } - result = Curl_proxy_connect(conn, SECONDARYSOCKET); - if(result) - return result; - - if(CONNECT_SECONDARYSOCKET_PROXY_SSL()) - return result; - - if(conn->bits.tunnel_proxy && conn->bits.httpproxy && - conn->tunnel_state[SECONDARYSOCKET] != TUNNEL_COMPLETE) - 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 intial commands. + /* already in a state so skip the initial commands. They are only done to kickstart the do_more state */ - result = ftp_multi_statemach(conn, &complete); + 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 + /* if we got an error or if we do not wait for a data connection return immediately */ - if(result || (ftpc->wait_data_conn != TRUE)) + if(result || !ftpc->wait_data_conn) return result; - if(ftpc->wait_data_conn) - /* 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 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 are not actually complete */ + *completep = 0; } - if(ftp->transfer <= FTPTRANSFER_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(ftp->transfer <= PPTRANSFER_INFO) { + /* a transfer is about to take place, or if not a filename was given so we + will do a SIZE on it later and then we need the right TYPE first */ - if(ftpc->wait_data_conn == TRUE) { + if(ftpc->wait_data_conn) { bool serv_conned; - result = ReceivedServerConnect(conn, &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(conn); + result = AcceptServerConnect(data); ftpc->wait_data_conn = FALSE; if(!result) - result = InitiateTransfer(conn); + result = InitiateTransfer(data); if(result) return result; @@ -3666,61 +3782,62 @@ static CURLcode ftp_do_more(struct connectdata *conn, int *completep) connected back to us */ } } - else if(data->set.upload) { - result = ftp_nb_type(conn, data->set.prefer_ascii, FTP_STOR_TYPE); + 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(conn, &complete); - if(ftpc->wait_data_conn) - /* 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; - else - *completep = (int)complete; + result = ftp_multi_statemach(data, &complete); + *completep = (int)complete; } else { /* download */ ftp->downloadsize = -1; /* unknown as of yet */ - result = ftp_range(conn); + result = Curl_range(data); + + if(result == CURLE_OK && data->req.maxdownload >= 0) { + /* Do not check for successful transfer */ + ftpc->dont_check = TRUE; + } + if(result) ; - else if(data->set.ftp_list_only || !ftpc->file) { + 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 == FTPTRANSFER_BODY) { - result = ftp_nb_type(conn, TRUE, FTP_LIST_TYPE); + 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(conn, data->set.prefer_ascii, FTP_RETR_TYPE); + result = ftp_nb_type(data, conn, data->state.prefer_ascii, + FTP_RETR_TYPE); if(result) return result; } - result = ftp_multi_statemach(conn, &complete); + result = ftp_multi_statemach(data, &complete); *completep = (int)complete; } return result; } - if(!result && (ftp->transfer != FTPTRANSFER_BODY)) - /* no data to transfer. FIX: it feels like a kludge to have this here - too! */ - Curl_setup_transfer(conn, -1, -1, FALSE, NULL, -1, NULL); + /* no data to transfer */ + Curl_xfer_setup_nop(data); 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\n", (int)result)); + CURL_TRC_FTP(data, "[%s] DO-MORE phase ends with %d", FTP_DSTATE(data), + (int)result); } return result; @@ -3737,71 +3854,74 @@ static CURLcode ftp_do_more(struct connectdata *conn, int *completep) */ static -CURLcode ftp_perform(struct connectdata *conn, +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; + CURLcode result = CURLE_OK; - DEBUGF(infof(conn->data, "DO phase starts\n")); + CURL_TRC_FTP(data, "[%s] DO phase starts", FTP_DSTATE(data)); - if(conn->data->set.opt_no_body) { + if(data->req.no_body) { /* requested no body means no transfer... */ - struct FTP *ftp = conn->data->req.protop; - ftp->transfer = FTPTRANSFER_INFO; + 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(conn, TRUE, FTP_QUOTE); + result = ftp_state_quote(data, TRUE, FTP_QUOTE); if(result) return result; /* run the state-machine */ - result = ftp_multi_statemach(conn, dophase_done); + result = ftp_multi_statemach(data, dophase_done); - *connected = conn->bits.tcpconnect[SECONDARYSOCKET]; + *connected = Curl_conn_is_connected(data->conn, SECONDARYSOCKET); - infof(conn->data, "ftp_perform ends with SECONDARY: %d\n", *connected); + if(*connected) + infof(data, "[FTP] [%s] perform, DATA connection established", + FTP_DSTATE(data)); + else + CURL_TRC_FTP(data, "[%s] perform, awaiting DATA connect", + FTP_DSTATE(data)); if(*dophase_done) - DEBUGF(infof(conn->data, "DO phase is complete1\n")); + CURL_TRC_FTP(data, "[%s] DO phase is complete1", FTP_DSTATE(data)); return result; } static void wc_data_dtor(void *ptr) { - struct ftp_wc_tmpdata *tmp = ptr; - if(tmp) - Curl_ftp_parselist_data_free(&tmp->parser); - free(tmp); + struct ftp_wc *ftpwc = ptr; + if(ftpwc && ftpwc->parser) + Curl_ftp_parselist_data_free(&ftpwc->parser); + free(ftpwc); } -static CURLcode init_wc_data(struct connectdata *conn) +static CURLcode init_wc_data(struct Curl_easy *data) { char *last_slash; - char *path = conn->data->state.path; - struct WildcardData *wildcard = &(conn->data->wildcard); + struct FTP *ftp = data->req.p.ftp; + char *path = ftp->path; + struct WildcardData *wildcard = data->wildcard; CURLcode result = CURLE_OK; - struct ftp_wc_tmpdata *ftp_tmp; + struct ftp_wc *ftpwc = NULL; - last_slash = strrchr(conn->data->state.path, '/'); + last_slash = strrchr(ftp->path, '/'); if(last_slash) { last_slash++; if(last_slash[0] == '\0') { wildcard->state = CURLWC_CLEAN; - result = ftp_parse_url_path(conn); - return result; - } - else { - wildcard->pattern = strdup(last_slash); - if(!wildcard->pattern) - return CURLE_OUT_OF_MEMORY; - last_slash[0] = '\0'; /* cut file from path */ + return ftp_parse_url_path(data); } + 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]) { @@ -3812,184 +3932,200 @@ static CURLcode init_wc_data(struct connectdata *conn) } else { /* only list */ wildcard->state = CURLWC_CLEAN; - result = ftp_parse_url_path(conn); - return result; + return ftp_parse_url_path(data); } } /* program continues only if URL is not ending with slash, allocate needed resources for wildcard transfer */ - /* allocate ftp protocol specific temporary wildcard data */ - ftp_tmp = calloc(1, sizeof(struct ftp_wc_tmpdata)); - if(!ftp_tmp) { - Curl_safefree(wildcard->pattern); - return CURLE_OUT_OF_MEMORY; + /* 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 */ - ftp_tmp->parser = Curl_ftp_parselist_data_alloc(); - if(!ftp_tmp->parser) { - Curl_safefree(wildcard->pattern); - free(ftp_tmp); - return CURLE_OUT_OF_MEMORY; + ftpwc->parser = Curl_ftp_parselist_data_alloc(); + if(!ftpwc->parser) { + result = CURLE_OUT_OF_MEMORY; + goto fail; } - wildcard->tmp = ftp_tmp; /* put it to the WildcardData tmp pointer */ - wildcard->tmp_dtor = wc_data_dtor; + wildcard->ftpwc = ftpwc; /* put it to the WildcardData tmp pointer */ + wildcard->dtor = wc_data_dtor; /* wildcard does not support NOCWD option (assert it?) */ - if(conn->data->set.ftp_filemethod == FTPFILE_NOCWD) - conn->data->set.ftp_filemethod = FTPFILE_MULTICWD; + if(data->set.ftp_filemethod == FTPFILE_NOCWD) + data->set.ftp_filemethod = FTPFILE_MULTICWD; - /* try to parse ftp url */ - result = ftp_parse_url_path(conn); + /* try to parse ftp URL */ + result = ftp_parse_url_path(data); if(result) { - Curl_safefree(wildcard->pattern); - wildcard->tmp_dtor(wildcard->tmp); - wildcard->tmp_dtor = ZERO_NULL; - wildcard->tmp = NULL; - return result; + goto fail; } - wildcard->path = strdup(conn->data->state.path); + wildcard->path = strdup(ftp->path); if(!wildcard->path) { - Curl_safefree(wildcard->pattern); - wildcard->tmp_dtor(wildcard->tmp); - wildcard->tmp_dtor = ZERO_NULL; - wildcard->tmp = NULL; - return CURLE_OUT_OF_MEMORY; + result = CURLE_OUT_OF_MEMORY; + goto fail; } /* backup old write_function */ - ftp_tmp->backup.write_function = conn->data->set.fwrite_func; + ftpwc->backup.write_function = data->set.fwrite_func; /* parsing write function */ - conn->data->set.fwrite_func = Curl_ftp_parselist; + data->set.fwrite_func = Curl_ftp_parselist; /* backup old file descriptor */ - ftp_tmp->backup.file_descriptor = conn->data->set.out; - /* let the writefunc callback know what curl pointer is working with */ - conn->data->set.out = conn; + ftpwc->backup.file_descriptor = data->set.out; + /* let the writefunc callback know the transfer */ + data->set.out = data; - infof(conn->data, "Wildcard - Parsing started\n"); + 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; } -/* This is called recursively */ -static CURLcode wc_statemach(struct connectdata *conn) +static CURLcode wc_statemach(struct Curl_easy *data) { - struct WildcardData * const wildcard = &(conn->data->wildcard); + struct WildcardData * const wildcard = data->wildcard; + struct connectdata *conn = data->conn; CURLcode result = CURLE_OK; - switch(wildcard->state) { - case CURLWC_INIT: - result = init_wc_data(conn); - if(wildcard->state == CURLWC_CLEAN) - /* only listing! */ - break; - else + 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; - break; + 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_tmpdata *ftp_tmp = wildcard->tmp; - conn->data->set.fwrite_func = ftp_tmp->backup.write_function; - conn->data->set.out = ftp_tmp->backup.file_descriptor; - ftp_tmp->backup.write_function = ZERO_NULL; - ftp_tmp->backup.file_descriptor = NULL; - wildcard->state = CURLWC_DOWNLOADING; - - if(Curl_ftp_parselist_geterror(ftp_tmp->parser)) { - /* error found in LIST parsing */ - wildcard->state = CURLWC_CLEAN; - return wc_statemach(conn); - } - else if(wildcard->filelist->size == 0) { - /* no corresponding file */ - wildcard->state = CURLWC_CLEAN; - return CURLE_REMOTE_FILE_NOT_FOUND; + 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(Curl_llist_count(&wildcard->filelist) == 0) { + /* no corresponding file */ + wildcard->state = CURLWC_CLEAN; + return CURLE_REMOTE_FILE_NOT_FOUND; + } + continue; } - return wc_statemach(conn); - } - 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; + case CURLWC_DOWNLOADING: { + /* filelist has at least one file, lets get first one */ + struct ftp_conn *ftpc = &conn->proto.ftpc; + struct Curl_llist_node *head = Curl_llist_head(&wildcard->filelist); + struct curl_fileinfo *finfo = Curl_node_elem(head); + 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; + char *tmp_path = aprintf("%s%s", wildcard->path, finfo->filename); + if(!tmp_path) + return CURLE_OUT_OF_MEMORY; - /* switch default "state.pathbuffer" and tmp_path, good to see - ftp_parse_url_path function to understand this trick */ - Curl_safefree(conn->data->state.pathbuffer); - conn->data->state.pathbuffer = tmp_path; - conn->data->state.path = tmp_path; - - infof(conn->data, "Wildcard - START of \"%s\"\n", finfo->filename); - if(conn->data->set.chunk_bgn) { - long userresponse = conn->data->set.chunk_bgn( - finfo, wildcard->customptr, (int)wildcard->filelist->size); - switch(userresponse) { - case CURL_CHUNK_BGN_FUNC_SKIP: - infof(conn->data, "Wildcard - \"%s\" skipped by user\n", - finfo->filename); + /* 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)Curl_llist_count(&wildcard->filelist)); + 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; - return wc_statemach(conn); - case CURL_CHUNK_BGN_FUNC_FAIL: - return CURLE_CHUNK_FAILED; + continue; } - } - if(finfo->filetype != CURLFILETYPE_FILE) { - wildcard->state = CURLWC_SKIP; - return wc_statemach(conn); - } + if(finfo->flags & CURLFINFOFLAG_KNOWN_SIZE) + ftpc->known_filesize = finfo->size; + + result = ftp_parse_url_path(data); + if(result) + return result; - if(finfo->flags & CURLFINFOFLAG_KNOWN_SIZE) - ftpc->known_filesize = finfo->size; + /* we do not need the Curl_fileinfo of first file anymore */ + Curl_node_remove(Curl_llist_head(&wildcard->filelist)); - result = ftp_parse_url_path(conn); - if(result) + if(Curl_llist_count(&wildcard->filelist) == 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; + } - /* 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; + 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_node_remove(Curl_llist_head(&wildcard->filelist)); + wildcard->state = (Curl_llist_count(&wildcard->filelist) == 0) ? + CURLWC_CLEAN : CURLWC_DOWNLOADING; + continue; } - } break; - - case CURLWC_SKIP: { - if(conn->data->set.chunk_end) - conn->data->set.chunk_end(conn->data->wildcard.customptr); - Curl_llist_remove(wildcard->filelist, wildcard->filelist->head, NULL); - wildcard->state = (wildcard->filelist->size == 0) ? - CURLWC_CLEAN : CURLWC_DOWNLOADING; - return wc_statemach(conn); - } - case CURLWC_CLEAN: { - struct ftp_wc_tmpdata *ftp_tmp = wildcard->tmp; - result = CURLE_OK; - if(ftp_tmp) - result = Curl_ftp_parselist_geterror(ftp_tmp->parser); + 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; - } break; + wildcard->state = result ? CURLWC_ERROR : CURLWC_DONE; + return result; + } - case CURLWC_DONE: - case CURLWC_ERROR: - break; + case CURLWC_DONE: + case CURLWC_ERROR: + case CURLWC_CLEAR: + if(wildcard->dtor) { + wildcard->dtor(wildcard->ftpwc); + wildcard->ftpwc = NULL; + } + return result; + } } - - return result; + /* UNREACHABLE */ } /*********************************************************************** @@ -4001,18 +4137,37 @@ static CURLcode wc_statemach(struct connectdata *conn) * * The input argument is already checked for validity. */ -static CURLcode ftp_do(struct connectdata *conn, bool *done) +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(conn->data->set.wildcardmatch) { - result = wc_statemach(conn); - if(conn->data->wildcard.state == CURLWC_SKIP || - conn->data->wildcard.state == CURLWC_DONE) { +#ifdef CURL_PREFER_LF_LINEENDS + { + /* FTP data may need conversion. */ + struct Curl_cwriter *ftp_lc_writer; + + result = Curl_cwriter_create(&ftp_lc_writer, data, &ftp_cw_lc, + CURL_CW_CONTENT_DECODE); + if(result) + return result; + + result = Curl_cwriter_add(data, ftp_lc_writer); + if(result) { + Curl_cwriter_free(data, ftp_lc_writer); + return result; + } + } +#endif /* CURL_PREFER_LF_LINEENDS */ + + 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; } @@ -4020,68 +4175,12 @@ static CURLcode ftp_do(struct connectdata *conn, bool *done) return result; } else { /* no wildcard FSM needed */ - result = ftp_parse_url_path(conn); + result = ftp_parse_url_path(data); if(result) return result; } - result = ftp_regular_transfer(conn, done); - - return result; -} - - -CURLcode Curl_ftpsend(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 - enum protection_level data_sec = conn->data_prot; -#endif - - write_len = strlen(cmd); - if(write_len > (sizeof(s) -3)) - return CURLE_BAD_FUNCTION_ARGUMENT; - - strcpy(&s[write_len], "\r\n"); /* append a trailing CRLF */ - write_len +=2; - - bytes_written=0; - - result = Curl_convert_to_network(conn->data, s, write_len); - /* Curl_convert_to_network calls failf if unsuccessful */ - if(result) - return result; - - for(;;) { -#ifdef HAVE_GSSAPI - conn->data_prot = PROT_CMD; -#endif - result = Curl_write(conn, conn->sock[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; - - if(conn->data->set.verbose) - Curl_debug(conn->data, CURLINFO_HEADER_OUT, - sptr, (size_t)bytes_written, conn); - - if(bytes_written != (ssize_t)write_len) { - write_len -= bytes_written; - sptr += bytes_written; - } - else - break; - } + result = ftp_regular_transfer(data, done); return result; } @@ -4096,24 +4195,24 @@ CURLcode Curl_ftpsend(struct connectdata *conn, const char *cmd) * connection. * */ -static CURLcode ftp_quit(struct connectdata *conn) +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(&conn->proto.ftpc.pp, "%s", "QUIT"); + result = Curl_pp_sendf(data, &conn->proto.ftpc.pp, "%s", "QUIT"); if(result) { - failf(conn->data, "Failure sending QUIT command: %s", + 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 */ - state(conn, FTP_STOP); + ftp_state(data, FTP_STOP); return result; } - state(conn, FTP_QUIT); + ftp_state(data, FTP_QUIT); - result = ftp_block_statemach(conn); + result = ftp_block_statemach(data, conn); } return result; @@ -4126,48 +4225,48 @@ static CURLcode ftp_quit(struct connectdata *conn) * Disconnect from an FTP server. Cleanup protocol-specific per-connection * resources. BLOCKING. */ -static CURLcode ftp_disconnect(struct connectdata *conn, bool dead_connection) +static CURLcode ftp_disconnect(struct Curl_easy *data, + struct connectdata *conn, + bool dead_connection) { - struct ftp_conn *ftpc= &conn->proto.ftpc; + 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 + ftp_quit() will check the state of ftp->ctl_valid. If it is ok it will try to send the QUIT command, otherwise it will 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(conn); /* ignore errors on the QUIT */ + (void)ftp_quit(data, conn); /* ignore errors on the QUIT */ if(ftpc->entrypath) { - struct Curl_easy *data = conn->data; if(data->state.most_recent_ftp_entrypath == ftpc->entrypath) { data->state.most_recent_ftp_entrypath = NULL; } - free(ftpc->entrypath); - ftpc->entrypath = NULL; + Curl_safefree(ftpc->entrypath); } freedirs(ftpc); - free(ftpc->prevpath); - ftpc->prevpath = NULL; - free(ftpc->server_os); - ftpc->server_os = NULL; - + Curl_safefree(ftpc->account); + Curl_safefree(ftpc->alternative_to_user); + Curl_safefree(ftpc->prevpath); + Curl_safefree(ftpc->server_os); Curl_pp_disconnect(pp); - -#ifdef HAVE_GSSAPI Curl_sec_end(conn); -#endif - return CURLE_OK; } +#ifdef _MSC_VER +/* warning C4706: assignment within conditional expression */ +#pragma warning(disable:4706) +#endif + /*********************************************************************** * * ftp_parse_url_path() @@ -4176,217 +4275,174 @@ static CURLcode ftp_disconnect(struct connectdata *conn, bool dead_connection) * */ static -CURLcode ftp_parse_url_path(struct connectdata *conn) +CURLcode ftp_parse_url_path(struct Curl_easy *data) { - struct Curl_easy *data = conn->data; /* the ftp struct is already inited in ftp_connect() */ - struct FTP *ftp = data->req.protop; + struct FTP *ftp = data->req.p.ftp; + struct connectdata *conn = data->conn; struct ftp_conn *ftpc = &conn->proto.ftpc; - const char *slash_pos; /* position of the first '/' char in curpos */ - const char *path_to_use = data->state.path; - const char *cur_pos; - const char *filename = NULL; - - cur_pos = path_to_use; /* current position in path. point at the begin of - next path component */ + 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; - switch(data->set.ftp_filemethod) { - case FTPFILE_NOCWD: - /* fastest, but less standard-compliant */ - - /* - The best time to check whether the path is a file or directory is right - here. so: + /* 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; + } - the first condition in the if() right here, is there just in case - someone decides to set path to NULL one day - */ - if(path_to_use[0] && - (path_to_use[strlen(path_to_use) - 1] != '/') ) - filename = path_to_use; /* 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; + switch(data->set.ftp_filemethod) { + case FTPFILE_NOCWD: /* fastest, but less standard-compliant */ - case FTPFILE_SINGLECWD: - /* get the last slash */ - if(!path_to_use[0]) { - /* no dir, no file */ - ftpc->dirdepth = 0; + 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; - } - slash_pos=strrchr(cur_pos, '/'); - if(slash_pos || !*cur_pos) { - size_t dirlen = slash_pos-cur_pos; - CURLcode result; - ftpc->dirs = calloc(1, sizeof(ftpc->dirs[0])); - if(!ftpc->dirs) - return CURLE_OUT_OF_MEMORY; + 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; + } - if(!dirlen) - dirlen++; + ftpc->dirs[0] = Curl_memdup0(rawPath, dirlen); + if(!ftpc->dirs[0]) { + free(rawPath); + return CURLE_OUT_OF_MEMORY; + } - result = Curl_urldecode(conn->data, slash_pos ? cur_pos : "/", - slash_pos ? dirlen : 1, - &ftpc->dirs[0], NULL, - FALSE); - if(result) { - freedirs(ftpc); - return result; + ftpc->dirdepth = 1; /* we consider it to be a single dir */ + fileName = slashPos + 1; /* rest is filename */ } - ftpc->dirdepth = 1; /* we consider it to be a single dir */ - filename = slash_pos ? slash_pos+1 : cur_pos; /* rest is file name */ - } - else - filename = cur_pos; /* this is a file name only */ - break; + else + fileName = rawPath; /* filename only (or empty) */ + break; - default: /* allow pretty much anything */ - case FTPFILE_MULTICWD: - ftpc->dirdepth = 0; - ftpc->diralloc = 5; /* default dir depth to allocate */ - ftpc->dirs = calloc(ftpc->diralloc, sizeof(ftpc->dirs[0])); - if(!ftpc->dirs) - return CURLE_OUT_OF_MEMORY; + 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 have a special case for listing the root dir only */ - if(!strcmp(path_to_use, "/")) { - cur_pos++; /* make it point to the zero byte */ - ftpc->dirs[0] = strdup("/"); - ftpc->dirdepth++; - } - else { - /* parse the URL path into separate path components */ - while((slash_pos = strchr(cur_pos, '/')) != NULL) { - /* 1 or 0 pointer offset to indicate absolute directory */ - ssize_t absolute_dir = ((cur_pos - data->state.path > 0) && - (ftpc->dirdepth == 0))?1:0; - - /* seek out the next path component */ - if(slash_pos-cur_pos) { /* we skip empty path components, like "x//y" since the FTP command - CWD requires a parameter and a non-existent parameter a) doesn't + CWD requires a parameter and a non-existent parameter a) does not work on many servers and b) has no effect on the others. */ - size_t len = slash_pos - cur_pos + absolute_dir; - CURLcode result = - Curl_urldecode(conn->data, cur_pos - absolute_dir, len, - &ftpc->dirs[ftpc->dirdepth], NULL, - TRUE); - if(result) { - free(ftpc->dirs[ftpc->dirdepth]); - freedirs(ftpc); - return result; - } - } - else { - cur_pos = slash_pos + 1; /* jump to the rest of the string */ - if(!ftpc->dirdepth) { - /* path starts with a slash, add that as a directory */ - ftpc->dirs[ftpc->dirdepth] = strdup("/"); - if(!ftpc->dirs[ftpc->dirdepth++]) { /* run out of memory ... */ - failf(data, "no memory"); - freedirs(ftpc); + if(compLen > 0) { + char *comp = Curl_memdup0(curPos, compLen); + if(!comp) { + free(rawPath); return CURLE_OUT_OF_MEMORY; } + ftpc->dirs[ftpc->dirdepth++] = comp; } - continue; - } - - cur_pos = slash_pos + 1; /* jump to the rest of the string */ - if(++ftpc->dirdepth >= ftpc->diralloc) { - /* enlarge array */ - char **bigger; - ftpc->diralloc *= 2; /* double the size each time */ - bigger = realloc(ftpc->dirs, ftpc->diralloc * sizeof(ftpc->dirs[0])); - if(!bigger) { - freedirs(ftpc); - return CURLE_OUT_OF_MEMORY; - } - ftpc->dirs = bigger; + curPos = slashPos + 1; } } + DEBUGASSERT((size_t)ftpc->dirdepth <= dirAlloc); + fileName = curPos; /* the rest is the filename (or empty) */ } - filename = cur_pos; /* the rest is the file name */ break; } /* switch */ - if(filename && *filename) { - CURLcode result = - Curl_urldecode(conn->data, filename, 0, &ftpc->file, NULL, TRUE); - - if(result) { - freedirs(ftpc); - return result; - } - } + if(fileName && *fileName) + ftpc->file = strdup(fileName); else - ftpc->file=NULL; /* instead of point to a zero byte, we make it a NULL - pointer */ + ftpc->file = NULL; /* instead of point to a zero byte, + we make it a NULL pointer */ - if(data->set.upload && !ftpc->file && (ftp->transfer == FTPTRANSFER_BODY)) { - /* We need a file name when uploading. Return error! */ - failf(data, "Uploading to a URL without a file name!"); + if(data->state.upload && !ftpc->file && (ftp->transfer == PPTRANSFER_BODY)) { + /* We need a filename when uploading. Return error! */ + failf(data, "Uploading to a URL without a filename"); + free(rawPath); return CURLE_URL_MALFORMAT; } ftpc->cwddone = FALSE; /* default to not done */ - if(ftpc->prevpath) { - /* prevpath is "raw" so we convert the input path before we compare the - strings */ - size_t dlen; - char *path; - CURLcode result = - Curl_urldecode(conn->data, data->state.path, 0, &path, &dlen, FALSE); - if(result) { - freedirs(ftpc); - return result; - } + 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; - dlen -= ftpc->file?strlen(ftpc->file):0; - if((dlen == strlen(ftpc->prevpath)) && - !strncmp(path, ftpc->prevpath, dlen)) { - infof(data, "Request has same path as previous transfer\n"); - ftpc->cwddone = TRUE; + if((strlen(oldPath) == n) && !strncmp(rawPath, oldPath, n)) { + infof(data, "Request has same path as previous transfer"); + ftpc->cwddone = TRUE; + } } - free(path); } + free(rawPath); return CURLE_OK; } /* call this when the DO phase has completed */ -static CURLcode ftp_dophase_done(struct connectdata *conn, - bool connected) +static CURLcode ftp_dophase_done(struct Curl_easy *data, bool connected) { - struct FTP *ftp = conn->data->req.protop; + 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(conn, &completed); + CURLcode result = ftp_do_more(data, &completed); if(result) { - close_secondarysocket(conn); + close_secondarysocket(data); return result; } } - if(ftp->transfer != FTPTRANSFER_BODY) + if(ftp->transfer != PPTRANSFER_BODY) /* no data to transfer */ - Curl_setup_transfer(conn, -1, -1, FALSE, NULL, -1, NULL); + Curl_xfer_setup_nop(data); else if(!connected) - /* since we didn't connect now, we want do_more to get called */ + /* since we did not connect now, we want do_more to get called */ conn->bits.do_more = TRUE; ftpc->ctl_valid = TRUE; /* seems good */ @@ -4395,17 +4451,17 @@ static CURLcode ftp_dophase_done(struct connectdata *conn, } /* called from multi.c while DOing */ -static CURLcode ftp_doing(struct connectdata *conn, +static CURLcode ftp_doing(struct Curl_easy *data, bool *dophase_done) { - CURLcode result = ftp_multi_statemach(conn, dophase_done); + CURLcode result = ftp_multi_statemach(data, dophase_done); if(result) - DEBUGF(infof(conn->data, "DO phase failed\n")); + CURL_TRC_FTP(data, "[%s] DO phase failed", FTP_DSTATE(data)); else if(*dophase_done) { - result = ftp_dophase_done(conn, FALSE /* not connected */); + result = ftp_dophase_done(data, FALSE /* not connected */); - DEBUGF(infof(conn->data, "DO phase is complete2\n")); + CURL_TRC_FTP(data, "[%s] DO phase is complete2", FTP_DSTATE(data)); } return result; } @@ -4423,12 +4479,12 @@ static CURLcode ftp_doing(struct connectdata *conn, * ftp_done() function without finding any major problem. */ static -CURLcode ftp_regular_transfer(struct connectdata *conn, +CURLcode ftp_regular_transfer(struct Curl_easy *data, bool *dophase_done) { - CURLcode result=CURLE_OK; - bool connected=FALSE; - struct Curl_easy *data = conn->data; + 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 */ @@ -4439,7 +4495,7 @@ CURLcode ftp_regular_transfer(struct connectdata *conn, ftpc->ctl_valid = TRUE; /* starts good */ - result = ftp_perform(conn, + result = ftp_perform(data, &connected, /* have we connected after PASV/PORT */ dophase_done); /* all commands in the DO-phase done? */ @@ -4449,7 +4505,7 @@ CURLcode ftp_regular_transfer(struct connectdata *conn, /* the DO phase has not completed yet */ return CURLE_OK; - result = ftp_dophase_done(conn, connected); + result = ftp_dophase_done(data, connected); if(result) return result; @@ -4460,90 +4516,77 @@ CURLcode ftp_regular_transfer(struct connectdata *conn, return result; } -static CURLcode ftp_setup_connection(struct connectdata *conn) +static CURLcode ftp_setup_connection(struct Curl_easy *data, + struct connectdata *conn) { - struct Curl_easy *data = conn->data; char *type; - char command; struct FTP *ftp; + CURLcode result = CURLE_OK; + struct ftp_conn *ftpc = &conn->proto.ftpc; - if(conn->bits.httpproxy && !data->set.tunnel_thru_httpproxy) { - /* Unless we have asked to tunnel ftp operations through the proxy, we - switch and use HTTP operations only */ -#ifndef CURL_DISABLE_HTTP - if(conn->handler == &Curl_handler_ftp) - conn->handler = &Curl_handler_ftp_proxy; - else { -#ifdef USE_SSL - conn->handler = &Curl_handler_ftps_proxy; -#else - failf(data, "FTPS not supported!"); - return CURLE_UNSUPPORTED_PROTOCOL; -#endif + 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; } - /* set it up as a HTTP connection instead */ - return conn->handler->setup_connection(conn); -#else - failf(data, "FTP over http proxy requires HTTP support built-in!"); - return CURLE_UNSUPPORTED_PROTOCOL; -#endif } + 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; - conn->data->req.protop = ftp = malloc(sizeof(struct FTP)); - if(NULL == ftp) - return CURLE_OUT_OF_MEMORY; - - data->state.path++; /* don't include the initial slash */ - data->state.slash_removed = TRUE; /* we've skipped the slash */ + ftp->path = &data->state.up.path[1]; /* do not include the initial slash */ /* FTP URLs support an extension like ";type=" that - * we'll try to get now! */ - type = strstr(data->state.path, ";type="); + * we will 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]); - conn->bits.type_set = TRUE; switch(command) { case 'A': /* ASCII mode */ - data->set.prefer_ascii = TRUE; + data->state.prefer_ascii = TRUE; break; case 'D': /* directory mode */ - data->set.ftp_list_only = TRUE; + data->state.list_only = TRUE; break; case 'I': /* binary mode */ default: /* switch off ASCII */ - data->set.prefer_ascii = FALSE; + data->state.prefer_ascii = FALSE; break; } } /* get some initial data into the ftp struct */ - ftp->bytecountp = &conn->data->req.bytecount; - ftp->transfer = FTPTRANSFER_BODY; + 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; - /* No need to duplicate user+password, the connectdata struct won't change - during a session, but we re-init them here since on subsequent inits - since the conn struct may have changed or been replaced. - */ - ftp->user = conn->user; - ftp->passwd = conn->passwd; - if(isBadFtpString(ftp->user)) - return CURLE_URL_MALFORMAT; - if(isBadFtpString(ftp->passwd)) - return CURLE_URL_MALFORMAT; - - conn->proto.ftpc.known_filesize = -1; /* unknown size for now */ - - return CURLE_OK; + CURL_TRC_FTP(data, "[%s] setup connection -> %d", FTP_CSTATE(conn), result); + return result; } #endif /* CURL_DISABLE_FTP */ diff --git a/contrib/curl/lib/ftp.h b/contrib/curl/lib/ftp.h index 3bbf26206e..3d0af01587 100644 --- a/contrib/curl/lib/ftp.h +++ b/contrib/curl/lib/ftp.h @@ -7,11 +7,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2016, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -20,8 +20,12 @@ * 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 @@ -31,15 +35,14 @@ extern const struct Curl_handler Curl_handler_ftp; extern const struct Curl_handler Curl_handler_ftps; #endif -CURLcode Curl_ftpsend(struct connectdata *, const char *cmd); -CURLcode Curl_GetFTPResponse(ssize_t *nread, struct connectdata *conn, +CURLcode Curl_GetFTPResponse(struct Curl_easy *data, ssize_t *nread, int *ftpcode); #endif /* CURL_DISABLE_FTP */ /**************************************************************************** * FTP unique setup ***************************************************************************/ -typedef enum { +enum { FTP_STOP, /* do nothing state, stops the state machine */ FTP_WAIT220, /* waiting for the initial 220 response immediately after a connect */ @@ -58,7 +61,7 @@ typedef enum { FTP_STOR_PREQUOTE, FTP_POSTQUOTE, FTP_CWD, /* change dir */ - FTP_MKD, /* if the dir didn't exist */ + FTP_MKD, /* if the dir did not 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 */ @@ -77,11 +80,12 @@ typedef enum { FTP_STOR, /* generic state for STOR and APPE */ FTP_QUIT, FTP_LAST /* never used */ -} ftpstate; +}; +typedef unsigned char ftpstate; /* use the enum values */ struct ftp_parselist_data; /* defined later in ftplistparser.c */ -struct ftp_wc_tmpdata { +struct ftp_wc { struct ftp_parselist_data *parser; struct { @@ -102,9 +106,8 @@ typedef enum { perhaps the Curl_easy is changed between the times the connection is used. */ struct FTP { - curl_off_t *bytecountp; - char *user; /* user name string */ - char *passwd; /* password string */ + 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 */ @@ -117,41 +120,46 @@ struct FTP { 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 filename (or path) */ char **dirs; /* realloc()ed array for path components */ - int dirdepth; /* number of entries used in the 'dirs' array */ - int diralloc; /* number of entries allocated for the 'dirs' array */ - char *file; /* decoded file */ - bool 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. */ - bool 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() */ - bool cwddone; /* if it has been determined that the proper CWD combo - already has been done */ - bool cwdfail; /* set TRUE if a CWD command fails, as then we must prevent - caching the current directory */ - bool wait_data_conn; /* this is set TRUE if data connection is waited */ - char *prevpath; /* conn->path from the previous transfer */ + 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) */ - 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 */ - ftpstate state; /* always use ftp.c:state() to change state! */ - ftpstate state_saved; /* transfer type saved to be reloaded after - data connection is established */ 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 */ - /* newhost is the (allocated) IP addr or host name to connect the data + 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 hostname to connect the data connection to */ - char *newhost; /* this is the pair to connect the DATA... */ - unsigned short newport; /* 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 */ diff --git a/contrib/curl/lib/ftplistparser.c b/contrib/curl/lib/ftplistparser.c index f94b31b9c5..448f3a43ab 100644 --- a/contrib/curl/lib/ftplistparser.c +++ b/contrib/curl/lib/ftplistparser.c @@ -5,11 +5,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2016, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -18,6 +18,8 @@ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY * KIND, either express or implied. * + * SPDX-License-Identifier: curl + * ***************************************************************************/ /** @@ -49,12 +51,10 @@ #include "ftplistparser.h" #include "curl_fnmatch.h" #include "curl_memory.h" +#include "multiif.h" /* The last #include file should be: */ #include "memdebug.h" -/* allocs buffer which will contain one line of LIST command response */ -#define FTP_BUFFER_ALLOCSIZE 160 - typedef enum { PL_UNIX_TOTALSIZE = 0, PL_UNIX_FILETYPE, @@ -165,7 +165,7 @@ struct ftp_parselist_data { } state; CURLcode error; - struct curl_fileinfo *file_data; + struct fileinfo *file_data; unsigned int item_length; size_t item_offset; struct { @@ -178,16 +178,56 @@ struct ftp_parselist_data { } 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 **pl_data) +void Curl_ftp_parselist_data_free(struct ftp_parselist_data **parserp) { - free(*pl_data); - *pl_data = NULL; + struct ftp_parselist_data *parser = *parserp; + if(parser) + Curl_fileinfo_cleanup(parser->file_data); + free(parser); + *parserp = NULL; } @@ -199,9 +239,9 @@ CURLcode Curl_ftp_parselist_geterror(struct ftp_parselist_data *pl_data) #define FTP_LP_MALFORMATED_PERM 0x01000000 -static int ftp_pl_get_permission(const char *str) +static unsigned int ftp_pl_get_permission(const char *str) { - int permissions = 0; + unsigned int permissions = 0; /* USER */ if(str[0] == 'r') permissions |= 1 << 8; @@ -264,28 +304,19 @@ static int ftp_pl_get_permission(const char *str) return permissions; } -static void PL_ERROR(struct connectdata *conn, CURLcode err) -{ - struct ftp_wc_tmpdata *tmpdata = conn->data->wildcard.tmp; - struct ftp_parselist_data *parser = tmpdata->parser; - if(parser->file_data) - Curl_fileinfo_dtor(NULL, parser->file_data); - parser->file_data = NULL; - parser->error = err; -} - -static CURLcode ftp_pl_insert_finfo(struct connectdata *conn, - struct curl_fileinfo *finfo) +static CURLcode ftp_pl_insert_finfo(struct Curl_easy *data, + struct fileinfo *infop) { curl_fnmatch_callback compare; - struct WildcardData *wc = &conn->data->wildcard; - struct ftp_wc_tmpdata *tmpdata = wc->tmp; - struct curl_llist *llist = wc->filelist; - struct ftp_parselist_data *parser = tmpdata->parser; + 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; - /* move finfo pointers to b_data */ - char *str = finfo->b_data; + /* 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; @@ -298,12 +329,13 @@ static CURLcode ftp_pl_insert_finfo(struct connectdata *conn, str + parser->offsets.user : NULL; /* get correct fnmatch callback */ - compare = conn->data->set.fnmatch; + compare = data->set.fnmatch; if(!compare) compare = Curl_fnmatch; /* filter pattern-corresponding filenames */ - if(compare(conn->data->set.fnmatch_data, wc->pattern, + 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 && @@ -314,87 +346,73 @@ static CURLcode ftp_pl_insert_finfo(struct connectdata *conn, else { add = FALSE; } + Curl_set_in_callback(data, false); if(add) { - if(!Curl_llist_insert_next(llist, llist->tail, finfo)) { - Curl_fileinfo_dtor(NULL, finfo); - tmpdata->parser->file_data = NULL; - return CURLE_OUT_OF_MEMORY; - } + Curl_llist_append(llist, finfo, &infop->list); } else { - Curl_fileinfo_dtor(NULL, finfo); + Curl_fileinfo_cleanup(infop); } - tmpdata->parser->file_data = NULL; + 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 connectdata *conn = (struct connectdata *)connptr; - struct ftp_wc_tmpdata *tmpdata = conn->data->wildcard.tmp; - struct ftp_parselist_data *parser = tmpdata->parser; - struct curl_fileinfo *finfo; - unsigned long i = 0; + 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 hadled later + * 3. (last) call => is skipped RIGHT HERE and the error is handled later * in wc_statemach() */ - return bufflen; + goto fail; } if(parser->os_type == OS_TYPE_UNKNOWN && bufflen > 0) { /* considering info about FILE response format */ - parser->os_type = (buffer[0] >= '0' && buffer[0] <= '9') ? - OS_TYPE_WIN_NT : OS_TYPE_UNIX; + 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; - return bufflen; - } - parser->file_data->b_data = malloc(FTP_BUFFER_ALLOCSIZE); - if(!parser->file_data->b_data) { - PL_ERROR(conn, CURLE_OUT_OF_MEMORY); - return bufflen; + goto fail; } - parser->file_data->b_size = FTP_BUFFER_ALLOCSIZE; parser->item_offset = 0; parser->item_length = 0; + Curl_dyn_init(&parser->file_data->buf, MAX_FTPLIST_BUFFER); } - finfo = parser->file_data; - finfo->b_data[finfo->b_used++] = c; + infop = parser->file_data; + finfo = &infop->info; - if(finfo->b_used >= finfo->b_size - 1) { - /* if it is important, extend buffer space for file data */ - char *tmp = realloc(finfo->b_data, - finfo->b_size + FTP_BUFFER_ALLOCSIZE); - if(tmp) { - finfo->b_size += FTP_BUFFER_ALLOCSIZE; - finfo->b_data = tmp; - } - else { - Curl_fileinfo_dtor(NULL, parser->file_data); - parser->file_data = NULL; - parser->error = CURLE_OUT_OF_MEMORY; - PL_ERROR(conn, CURLE_OUT_OF_MEMORY); - return bufflen; - } + 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: @@ -409,38 +427,36 @@ size_t Curl_ftp_parselist(char *buffer, size_t size, size_t nmemb, else { parser->state.UNIX.main = PL_UNIX_FILETYPE; /* start FSM again not considering size of directory */ - finfo->b_used = 0; - i--; + Curl_dyn_reset(&infop->buf); + continue; } break; case PL_UNIX_TOTALSIZE_READING: parser->item_length++; if(c == '\r') { parser->item_length--; - finfo->b_used--; + Curl_dyn_setlen(&infop->buf, --len); } else if(c == '\n') { - finfo->b_data[parser->item_length - 1] = 0; - if(strncmp("total ", finfo->b_data, 6) == 0) { - char *endptr = finfo->b_data+6; - /* here we can deal with directory size, pass the leading white - spaces and then the digits */ - while(ISSPACE(*endptr)) + 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 != 0) { - PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST); - return bufflen; - } - else { - parser->state.UNIX.main = PL_UNIX_FILETYPE; - finfo->b_used = 0; + if(*endptr) { + parser->error = CURLE_FTP_BAD_FILE_LIST; + goto fail; } + parser->state.UNIX.main = PL_UNIX_FILETYPE; + Curl_dyn_reset(&infop->buf); } else { - PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST); - return bufflen; + parser->error = CURLE_FTP_BAD_FILE_LIST; + goto fail; } } break; @@ -473,8 +489,8 @@ size_t Curl_ftp_parselist(char *buffer, size_t size, size_t nmemb, finfo->filetype = CURLFILETYPE_DOOR; break; default: - PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST); - return bufflen; + parser->error = CURLE_FTP_BAD_FILE_LIST; + goto fail; } parser->state.UNIX.main = PL_UNIX_PERMISSION; parser->item_length = 0; @@ -484,24 +500,24 @@ size_t Curl_ftp_parselist(char *buffer, size_t size, size_t nmemb, parser->item_length++; if(parser->item_length <= 9) { if(!strchr("rwx-tTsS", c)) { - PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST); - return bufflen; + parser->error = CURLE_FTP_BAD_FILE_LIST; + goto fail; } } else if(parser->item_length == 10) { unsigned int perm; if(c != ' ') { - PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST); - return bufflen; + parser->error = CURLE_FTP_BAD_FILE_LIST; + goto fail; } - finfo->b_data[10] = 0; /* terminate permissions */ - perm = ftp_pl_get_permission(finfo->b_data + parser->item_offset); + mem[10] = 0; /* terminate permissions */ + perm = ftp_pl_get_permission(mem + parser->item_offset); if(perm & FTP_LP_MALFORMATED_PERM) { - PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST); - return bufflen; + parser->error = CURLE_FTP_BAD_FILE_LIST; + goto fail; } - parser->file_data->flags |= CURLFINFOFLAG_KNOWN_PERM; - parser->file_data->perm = perm; + parser->file_data->info.flags |= CURLFINFOFLAG_KNOWN_PERM; + parser->file_data->info.perm = perm; parser->offsets.perm = parser->item_offset; parser->item_length = 0; @@ -513,14 +529,14 @@ size_t Curl_ftp_parselist(char *buffer, size_t size, size_t nmemb, switch(parser->state.UNIX.sub.hlinks) { case PL_UNIX_HLINKS_PRESPACE: if(c != ' ') { - if(c >= '0' && c <= '9') { - parser->item_offset = finfo->b_used - 1; + if(ISDIGIT(c)) { + parser->item_offset = len - 1; parser->item_length = 1; parser->state.UNIX.sub.hlinks = PL_UNIX_HLINKS_NUMBER; } else { - PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST); - return bufflen; + parser->error = CURLE_FTP_BAD_FILE_LIST; + goto fail; } } break; @@ -529,20 +545,20 @@ size_t Curl_ftp_parselist(char *buffer, size_t size, size_t nmemb, if(c == ' ') { char *p; long int hlinks; - finfo->b_data[parser->item_offset + parser->item_length - 1] = 0; - hlinks = strtol(finfo->b_data + parser->item_offset, &p, 10); + 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->flags |= CURLFINFOFLAG_KNOWN_HLINKCOUNT; - parser->file_data->hardlinks = hlinks; + 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(c < '0' || c > '9') { - PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST); - return bufflen; + else if(!ISDIGIT(c)) { + parser->error = CURLE_FTP_BAD_FILE_LIST; + goto fail; } break; } @@ -551,7 +567,7 @@ size_t Curl_ftp_parselist(char *buffer, size_t size, size_t nmemb, switch(parser->state.UNIX.sub.user) { case PL_UNIX_USER_PRESPACE: if(c != ' ') { - parser->item_offset = finfo->b_used - 1; + parser->item_offset = len - 1; parser->item_length = 1; parser->state.UNIX.sub.user = PL_UNIX_USER_PARSING; } @@ -559,7 +575,7 @@ size_t Curl_ftp_parselist(char *buffer, size_t size, size_t nmemb, case PL_UNIX_USER_PARSING: parser->item_length++; if(c == ' ') { - finfo->b_data[parser->item_offset + parser->item_length - 1] = 0; + 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; @@ -573,7 +589,7 @@ size_t Curl_ftp_parselist(char *buffer, size_t size, size_t nmemb, switch(parser->state.UNIX.sub.group) { case PL_UNIX_GROUP_PRESPACE: if(c != ' ') { - parser->item_offset = finfo->b_used - 1; + parser->item_offset = len - 1; parser->item_length = 1; parser->state.UNIX.sub.group = PL_UNIX_GROUP_NAME; } @@ -581,7 +597,7 @@ size_t Curl_ftp_parselist(char *buffer, size_t size, size_t nmemb, case PL_UNIX_GROUP_NAME: parser->item_length++; if(c == ' ') { - finfo->b_data[parser->item_offset + parser->item_length - 1] = 0; + 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; @@ -595,14 +611,14 @@ size_t Curl_ftp_parselist(char *buffer, size_t size, size_t nmemb, switch(parser->state.UNIX.sub.size) { case PL_UNIX_SIZE_PRESPACE: if(c != ' ') { - if(c >= '0' && c <= '9') { - parser->item_offset = finfo->b_used - 1; + if(ISDIGIT(c)) { + parser->item_offset = len - 1; parser->item_length = 1; parser->state.UNIX.sub.size = PL_UNIX_SIZE_NUMBER; } else { - PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST); - return bufflen; + parser->error = CURLE_FTP_BAD_FILE_LIST; + goto fail; } } break; @@ -611,21 +627,23 @@ size_t Curl_ftp_parselist(char *buffer, size_t size, size_t nmemb, if(c == ' ') { char *p; curl_off_t fsize; - finfo->b_data[parser->item_offset + parser->item_length - 1] = 0; - fsize = curlx_strtoofft(finfo->b_data+parser->item_offset, &p, 10); - if(p[0] == '\0' && fsize != CURL_OFF_T_MAX && - fsize != CURL_OFF_T_MIN) { - parser->file_data->flags |= CURLFINFOFLAG_KNOWN_SIZE; - parser->file_data->size = 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; } - 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)) { - PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST); - return bufflen; + parser->error = CURLE_FTP_BAD_FILE_LIST; + goto fail; } break; } @@ -635,13 +653,13 @@ size_t Curl_ftp_parselist(char *buffer, size_t size, size_t nmemb, case PL_UNIX_TIME_PREPART1: if(c != ' ') { if(ISALNUM(c)) { - parser->item_offset = finfo->b_used -1; + parser->item_offset = len -1; parser->item_length = 1; parser->state.UNIX.sub.time = PL_UNIX_TIME_PART1; } else { - PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST); - return bufflen; + parser->error = CURLE_FTP_BAD_FILE_LIST; + goto fail; } } break; @@ -651,8 +669,8 @@ size_t Curl_ftp_parselist(char *buffer, size_t size, size_t nmemb, parser->state.UNIX.sub.time = PL_UNIX_TIME_PREPART2; } else if(!ISALNUM(c) && c != '.') { - PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST); - return bufflen; + parser->error = CURLE_FTP_BAD_FILE_LIST; + goto fail; } break; case PL_UNIX_TIME_PREPART2: @@ -662,8 +680,8 @@ size_t Curl_ftp_parselist(char *buffer, size_t size, size_t nmemb, parser->state.UNIX.sub.time = PL_UNIX_TIME_PART2; } else { - PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST); - return bufflen; + parser->error = CURLE_FTP_BAD_FILE_LIST; + goto fail; } } break; @@ -673,8 +691,8 @@ size_t Curl_ftp_parselist(char *buffer, size_t size, size_t nmemb, parser->state.UNIX.sub.time = PL_UNIX_TIME_PREPART3; } else if(!ISALNUM(c) && c != '.') { - PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST); - return bufflen; + parser->error = CURLE_FTP_BAD_FILE_LIST; + goto fail; } break; case PL_UNIX_TIME_PREPART3: @@ -684,18 +702,18 @@ size_t Curl_ftp_parselist(char *buffer, size_t size, size_t nmemb, parser->state.UNIX.sub.time = PL_UNIX_TIME_PART3; } else { - PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST); - return bufflen; + parser->error = CURLE_FTP_BAD_FILE_LIST; + goto fail; } } break; case PL_UNIX_TIME_PART3: parser->item_length++; if(c == ' ') { - finfo->b_data[parser->item_offset + parser->item_length -1] = 0; + mem[parser->item_offset + parser->item_length -1] = 0; parser->offsets.time = parser->item_offset; /* - if(ftp_pl_gettime(parser, finfo->b_data + parser->item_offset)) { + if(ftp_pl_gettime(parser, finfo->mem + parser->item_offset)) { parser->file_data->flags |= CURLFINFOFLAG_KNOWN_TIME; } */ @@ -709,8 +727,8 @@ size_t Curl_ftp_parselist(char *buffer, size_t size, size_t nmemb, } } else if(!ISALNUM(c) && c != '.' && c != ':') { - PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST); - return bufflen; + parser->error = CURLE_FTP_BAD_FILE_LIST; + goto fail; } break; } @@ -719,7 +737,7 @@ size_t Curl_ftp_parselist(char *buffer, size_t size, size_t nmemb, switch(parser->state.UNIX.sub.filename) { case PL_UNIX_FILENAME_PRESPACE: if(c != ' ') { - parser->item_offset = finfo->b_used - 1; + parser->item_offset = len - 1; parser->item_length = 1; parser->state.UNIX.sub.filename = PL_UNIX_FILENAME_NAME; } @@ -730,30 +748,30 @@ size_t Curl_ftp_parselist(char *buffer, size_t size, size_t nmemb, parser->state.UNIX.sub.filename = PL_UNIX_FILENAME_WINDOWSEOL; } else if(c == '\n') { - finfo->b_data[parser->item_offset + parser->item_length - 1] = 0; + 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(conn, finfo); + result = ftp_pl_insert_finfo(data, infop); if(result) { - PL_ERROR(conn, result); - return bufflen; + parser->error = result; + goto fail; } } break; case PL_UNIX_FILENAME_WINDOWSEOL: if(c == '\n') { - finfo->b_data[parser->item_offset + parser->item_length - 1] = 0; + 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(conn, finfo); + result = ftp_pl_insert_finfo(data, infop); if(result) { - PL_ERROR(conn, result); - return bufflen; + parser->error = result; + goto fail; } } else { - PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST); - return bufflen; + parser->error = CURLE_FTP_BAD_FILE_LIST; + goto fail; } break; } @@ -762,7 +780,7 @@ size_t Curl_ftp_parselist(char *buffer, size_t size, size_t nmemb, switch(parser->state.UNIX.sub.symlink) { case PL_UNIX_SYMLINK_PRESPACE: if(c != ' ') { - parser->item_offset = finfo->b_used - 1; + parser->item_offset = len - 1; parser->item_length = 1; parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_NAME; } @@ -773,8 +791,8 @@ size_t Curl_ftp_parselist(char *buffer, size_t size, size_t nmemb, parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_PRETARGET1; } else if(c == '\r' || c == '\n') { - PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST); - return bufflen; + parser->error = CURLE_FTP_BAD_FILE_LIST; + goto fail; } break; case PL_UNIX_SYMLINK_PRETARGET1: @@ -783,8 +801,8 @@ size_t Curl_ftp_parselist(char *buffer, size_t size, size_t nmemb, parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_PRETARGET2; } else if(c == '\r' || c == '\n') { - PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST); - return bufflen; + parser->error = CURLE_FTP_BAD_FILE_LIST; + goto fail; } else { parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_NAME; @@ -796,8 +814,8 @@ size_t Curl_ftp_parselist(char *buffer, size_t size, size_t nmemb, parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_PRETARGET3; } else if(c == '\r' || c == '\n') { - PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST); - return bufflen; + parser->error = CURLE_FTP_BAD_FILE_LIST; + goto fail; } else { parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_NAME; @@ -808,14 +826,14 @@ size_t Curl_ftp_parselist(char *buffer, size_t size, size_t nmemb, if(c == ' ') { parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_PRETARGET4; /* now place where is symlink following */ - finfo->b_data[parser->item_offset + parser->item_length - 4] = 0; + 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') { - PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST); - return bufflen; + parser->error = CURLE_FTP_BAD_FILE_LIST; + goto fail; } else { parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_NAME; @@ -824,12 +842,12 @@ size_t Curl_ftp_parselist(char *buffer, size_t size, size_t nmemb, case PL_UNIX_SYMLINK_PRETARGET4: if(c != '\r' && c != '\n') { parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_TARGET; - parser->item_offset = finfo->b_used - 1; + parser->item_offset = len - 1; parser->item_length = 1; } else { - PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST); - return bufflen; + parser->error = CURLE_FTP_BAD_FILE_LIST; + goto fail; } break; case PL_UNIX_SYMLINK_TARGET: @@ -838,30 +856,30 @@ size_t Curl_ftp_parselist(char *buffer, size_t size, size_t nmemb, parser->state.UNIX.sub.symlink = PL_UNIX_SYMLINK_WINDOWSEOL; } else if(c == '\n') { - finfo->b_data[parser->item_offset + parser->item_length - 1] = 0; + mem[parser->item_offset + parser->item_length - 1] = 0; parser->offsets.symlink_target = parser->item_offset; - result = ftp_pl_insert_finfo(conn, finfo); + result = ftp_pl_insert_finfo(data, infop); if(result) { - PL_ERROR(conn, result); - return bufflen; + parser->error = result; + goto fail; } parser->state.UNIX.main = PL_UNIX_FILETYPE; } break; case PL_UNIX_SYMLINK_WINDOWSEOL: if(c == '\n') { - finfo->b_data[parser->item_offset + parser->item_length - 1] = 0; + mem[parser->item_offset + parser->item_length - 1] = 0; parser->offsets.symlink_target = parser->item_offset; - result = ftp_pl_insert_finfo(conn, finfo); + result = ftp_pl_insert_finfo(data, infop); if(result) { - PL_ERROR(conn, result); - return bufflen; + parser->error = result; + goto fail; } parser->state.UNIX.main = PL_UNIX_FILETYPE; } else { - PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST); - return bufflen; + parser->error = CURLE_FTP_BAD_FILE_LIST; + goto fail; } break; } @@ -874,8 +892,8 @@ size_t Curl_ftp_parselist(char *buffer, size_t size, size_t nmemb, parser->item_length++; if(parser->item_length < 9) { if(!strchr("0123456789-", c)) { /* only simple control */ - PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST); - return bufflen; + parser->error = CURLE_FTP_BAD_FILE_LIST; + goto fail; } } else if(parser->item_length == 9) { @@ -884,34 +902,34 @@ size_t Curl_ftp_parselist(char *buffer, size_t size, size_t nmemb, parser->state.NT.sub.time = PL_WINNT_TIME_PRESPACE; } else { - PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST); - return bufflen; + parser->error = CURLE_FTP_BAD_FILE_LIST; + goto fail; } } else { - PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST); - return bufflen; + 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(!ISSPACE(c)) { + 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; - finfo->b_data[parser->item_offset + parser->item_length -1] = 0; + 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)) { - PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST); - return bufflen; + parser->error = CURLE_FTP_BAD_FILE_LIST; + goto fail; } break; } @@ -919,11 +937,8 @@ size_t Curl_ftp_parselist(char *buffer, size_t size, size_t nmemb, case PL_WINNT_DIRORSIZE: switch(parser->state.NT.sub.dirorsize) { case PL_WINNT_DIRORSIZE_PRESPACE: - if(c == ' ') { - - } - else { - parser->item_offset = finfo->b_used - 1; + if(c != ' ') { + parser->item_offset = len - 1; parser->item_length = 1; parser->state.NT.sub.dirorsize = PL_WINNT_DIRORSIZE_CONTENT; } @@ -931,34 +946,24 @@ size_t Curl_ftp_parselist(char *buffer, size_t size, size_t nmemb, case PL_WINNT_DIRORSIZE_CONTENT: parser->item_length ++; if(c == ' ') { - finfo->b_data[parser->item_offset + parser->item_length - 1] = 0; - if(strcmp("", finfo->b_data + parser->item_offset) == 0) { + mem[parser->item_offset + parser->item_length - 1] = 0; + if(strcmp("", mem + parser->item_offset) == 0) { finfo->filetype = CURLFILETYPE_DIRECTORY; finfo->size = 0; } else { char *endptr; - finfo->size = curlx_strtoofft(finfo->b_data + - parser->item_offset, - &endptr, 10); - if(!*endptr) { - if(finfo->size == CURL_OFF_T_MAX || - finfo->size == CURL_OFF_T_MIN) { - if(errno == ERANGE) { - PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST); - return bufflen; - } - } - } - else { - PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST); - return bufflen; + 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->filetype = CURLFILETYPE_FILE; + parser->file_data->info.filetype = CURLFILETYPE_FILE; } - parser->file_data->flags |= CURLFINFOFLAG_KNOWN_SIZE; + 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; @@ -970,7 +975,7 @@ size_t Curl_ftp_parselist(char *buffer, size_t size, size_t nmemb, switch(parser->state.NT.sub.filename) { case PL_WINNT_FILENAME_PRESPACE: if(c != ' ') { - parser->item_offset = finfo->b_used -1; + parser->item_offset = len -1; parser->item_length = 1; parser->state.NT.sub.filename = PL_WINNT_FILENAME_CONTENT; } @@ -979,16 +984,15 @@ size_t Curl_ftp_parselist(char *buffer, size_t size, size_t nmemb, parser->item_length++; if(c == '\r') { parser->state.NT.sub.filename = PL_WINNT_FILENAME_WINEOL; - finfo->b_data[finfo->b_used - 1] = 0; + mem[len - 1] = 0; } else if(c == '\n') { parser->offsets.filename = parser->item_offset; - finfo->b_data[finfo->b_used - 1] = 0; - parser->offsets.filename = parser->item_offset; - result = ftp_pl_insert_finfo(conn, finfo); + mem[len - 1] = 0; + result = ftp_pl_insert_finfo(data, infop); if(result) { - PL_ERROR(conn, result); - return bufflen; + parser->error = result; + goto fail; } parser->state.NT.main = PL_WINNT_DATE; parser->state.NT.sub.filename = PL_WINNT_FILENAME_PRESPACE; @@ -997,17 +1001,17 @@ size_t Curl_ftp_parselist(char *buffer, size_t size, size_t nmemb, case PL_WINNT_FILENAME_WINEOL: if(c == '\n') { parser->offsets.filename = parser->item_offset; - result = ftp_pl_insert_finfo(conn, finfo); + result = ftp_pl_insert_finfo(data, infop); if(result) { - PL_ERROR(conn, result); - return bufflen; + parser->error = result; + goto fail; } parser->state.NT.main = PL_WINNT_DATE; parser->state.NT.sub.filename = PL_WINNT_FILENAME_PRESPACE; } else { - PL_ERROR(conn, CURLE_FTP_BAD_FILE_LIST); - return bufflen; + parser->error = CURLE_FTP_BAD_FILE_LIST; + goto fail; } break; } @@ -1015,13 +1019,23 @@ size_t Curl_ftp_parselist(char *buffer, size_t size, size_t nmemb, } break; default: - return bufflen + 1; + 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 bufflen; + return retsize; } #endif /* CURL_DISABLE_FTP */ diff --git a/contrib/curl/lib/ftplistparser.h b/contrib/curl/lib/ftplistparser.h index 8128887c0b..5ba1f6a97d 100644 --- a/contrib/curl/lib/ftplistparser.h +++ b/contrib/curl/lib/ftplistparser.h @@ -7,11 +7,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2012, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -20,6 +20,8 @@ * 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" @@ -37,5 +39,39 @@ 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/contrib/curl/lib/functypes.h b/contrib/curl/lib/functypes.h new file mode 100644 index 0000000000..ea66d3281d --- /dev/null +++ b/contrib/curl/lib/functypes.h @@ -0,0 +1,115 @@ +#ifndef HEADER_CURL_FUNCTYPES_H +#define HEADER_CURL_FUNCTYPES_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +/* 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/contrib/curl/lib/getenv.c b/contrib/curl/lib/getenv.c index 89d181de3c..63eaeda0f2 100644 --- a/contrib/curl/lib/getenv.c +++ b/contrib/curl/lib/getenv.c @@ -5,11 +5,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2012, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -18,6 +18,8 @@ * 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" @@ -27,24 +29,48 @@ #include "memdebug.h" -static -char *GetEnv(const char *variable) +static char *GetEnv(const char *variable) { -#if defined(_WIN32_WCE) || defined(CURL_WINDOWS_APP) +#if defined(_WIN32_WCE) || defined(CURL_WINDOWS_UWP) || \ + defined(__ORBIS__) || defined(__PROSPERO__) /* PlayStation 4 and 5 */ (void)variable; return NULL; -#else -#ifdef WIN32 - char env[MAX_PATH]; /* MAX_PATH is from windef.h */ - char *temp = getenv(variable); - env[0] = '\0'; - if(temp != NULL) - ExpandEnvironmentStringsA(temp, env, sizeof(env)); - return (env[0] != '\0')?strdup(env):NULL; +#elif defined(_WIN32) + /* This uses Windows API instead of C runtime getenv() to get the environment + variable since some changes are not 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 is possible for rc to be 0 if the variable was found but empty. + Since getenv does not 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 + return (env && env[0]) ? strdup(env) : NULL; #endif } diff --git a/contrib/curl/lib/getinfo.c b/contrib/curl/lib/getinfo.c index a1ce5058e8..43924c97dd 100644 --- a/contrib/curl/lib/getinfo.c +++ b/contrib/curl/lib/getinfo.c @@ -5,11 +5,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2016, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -18,6 +18,8 @@ * 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" @@ -51,9 +53,11 @@ CURLcode Curl_initinfo(struct Curl_easy *data) pro->t_connect = 0; pro->t_appconnect = 0; pro->t_pretransfer = 0; + pro->t_posttransfer = 0; pro->t_starttransfer = 0; pro->timespent = 0; pro->t_redirect = 0; + pro->is_t_startransfer_set = false; info->httpcode = 0; info->httpproxycode = 0; @@ -73,10 +77,10 @@ CURLcode Curl_initinfo(struct Curl_easy *data) 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; + memset(&info->primary, 0, sizeof(info->primary)); + info->primary.remote_port = -1; + info->primary.local_port = -1; + info->retry_after = 0; info->conn_scheme = 0; info->conn_protocol = 0; @@ -84,7 +88,6 @@ CURLcode Curl_initinfo(struct Curl_easy *data) #ifdef USE_SSL Curl_ssl_free_certinfo(data); #endif - return CURLE_OK; } @@ -93,7 +96,37 @@ static CURLcode getinfo_char(struct Curl_easy *data, CURLINFO info, { switch(info) { case CURLINFO_EFFECTIVE_URL: - *param_charp = data->change.url?data->change.url:(char *)""; + *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; @@ -114,22 +147,43 @@ static CURLcode getinfo_char(struct Curl_easy *data, CURLINFO info, 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; + *param_charp = data->info.primary.remote_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; + *param_charp = data->info.primary.local_ip; break; case CURLINFO_RTSP_SESSION_ID: +#ifndef CURL_DISABLE_RTSP *param_charp = data->set.str[STRING_RTSP_SESSION_ID]; +#else + *param_charp = NULL; +#endif break; case CURLINFO_SCHEME: *param_charp = data->info.conn_scheme; 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; } @@ -147,6 +201,33 @@ static CURLcode getinfo_long(struct Curl_easy *data, CURLINFO info, long *to_long; } lptr; +#ifdef DEBUGBUILD + char *timestr = getenv("CURL_TIME"); + if(timestr) { + unsigned long val = strtoul(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 = strtoul(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; @@ -155,22 +236,31 @@ static CURLcode getinfo_long(struct Curl_easy *data, CURLINFO info, *param_longp = data->info.httpproxycode; break; case CURLINFO_FILETIME: - *param_longp = data->info.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 = data->info.header_size; + *param_longp = (long)data->info.header_size; break; case CURLINFO_REQUEST_SIZE: - *param_longp = data->info.request_size; + *param_longp = (long)data->info.request_size; break; case CURLINFO_SSL_VERIFYRESULT: *param_longp = data->set.ssl.certverifyresult; break; case CURLINFO_PROXY_SSL_VERIFYRESULT: +#ifndef CURL_DISABLE_PROXY *param_longp = data->set.proxy_ssl.certverifyresult; +#else + *param_longp = 0; +#endif break; case CURLINFO_REDIRECT_COUNT: - *param_longp = data->set.followlocation; + *param_longp = data->state.followlocation; break; case CURLINFO_HTTPAUTH_AVAIL: lptr.to_long = param_longp; @@ -189,8 +279,8 @@ static CURLcode getinfo_long(struct Curl_easy *data, CURLINFO info, 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 */ + /* 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 @@ -200,16 +290,23 @@ static CURLcode getinfo_long(struct Curl_easy *data, CURLINFO info, break; case CURLINFO_PRIMARY_PORT: /* Return the (remote) port of the most recent (primary) connection */ - *param_longp = data->info.conn_primary_port; + *param_longp = data->info.primary.remote_port; break; case CURLINFO_LOCAL_PORT: /* Return the local port of the most recent (primary) connection */ - *param_longp = data->info.conn_local_port; + *param_longp = data->info.primary.local_port; + break; + case CURLINFO_PROXY_ERROR: + *param_longp = (long)data->info.pxcode; break; case CURLINFO_CONDITION_UNMET: - /* return if the condition prevented the document to get transferred */ - *param_longp = data->info.timecond ? 1L : 0L; + 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; @@ -219,6 +316,13 @@ static CURLcode getinfo_long(struct Curl_easy *data, CURLINFO info, case CURLINFO_RTSP_CSEQ_RECV: *param_longp = data->state.rtsp_CSeq_recv; break; +#else + case CURLINFO_RTSP_CLIENT_CSEQ: + case CURLINFO_RTSP_SERVER_CSEQ: + case CURLINFO_RTSP_CSEQ_RECV: + *param_longp = 0; + break; +#endif case CURLINFO_HTTP_VERSION: switch(data->info.httpversion) { case 10: @@ -230,15 +334,121 @@ static CURLcode getinfo_long(struct Curl_easy *data, CURLINFO info, 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; + *param_longp = (long)data->info.conn_protocol; + break; + case CURLINFO_USED_PROXY: + *param_longp = +#ifdef CURL_DISABLE_PROXY + 0 +#else + data->info.used_proxy +#endif + ; 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 = strtoul(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_POSTTRANSFER_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.ul.cur_size; + break; + case CURLINFO_SIZE_DOWNLOAD_T: + *param_offt = data->progress.dl.cur_size; + break; + case CURLINFO_SPEED_DOWNLOAD_T: + *param_offt = data->progress.dl.speed; + break; + case CURLINFO_SPEED_UPLOAD_T: + *param_offt = data->progress.ul.speed; + break; + case CURLINFO_CONTENT_LENGTH_DOWNLOAD_T: + *param_offt = (data->progress.flags & PGRS_DL_SIZE_KNOWN) ? + data->progress.dl.total_size : -1; + break; + case CURLINFO_CONTENT_LENGTH_UPLOAD_T: + *param_offt = (data->progress.flags & PGRS_UL_SIZE_KNOWN) ? + data->progress.ul.total_size : -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_POSTTRANSFER_TIME_T: + *param_offt = data->progress.t_posttransfer; + break; + case CURLINFO_STARTTRANSFER_TIME_T: + *param_offt = data->progress.t_starttransfer; + break; + case CURLINFO_QUEUE_TIME_T: + *param_offt = data->progress.t_postqueue; + 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; } @@ -249,47 +459,68 @@ static CURLcode getinfo_long(struct Curl_easy *data, CURLINFO info, 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 = strtoul(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 = data->progress.timespent; + *param_doublep = DOUBLE_SECS(data->progress.timespent); break; case CURLINFO_NAMELOOKUP_TIME: - *param_doublep = data->progress.t_nslookup; + *param_doublep = DOUBLE_SECS(data->progress.t_nslookup); break; case CURLINFO_CONNECT_TIME: - *param_doublep = data->progress.t_connect; + *param_doublep = DOUBLE_SECS(data->progress.t_connect); break; case CURLINFO_APPCONNECT_TIME: - *param_doublep = data->progress.t_appconnect; + *param_doublep = DOUBLE_SECS(data->progress.t_appconnect); break; case CURLINFO_PRETRANSFER_TIME: - *param_doublep = data->progress.t_pretransfer; + *param_doublep = DOUBLE_SECS(data->progress.t_pretransfer); break; case CURLINFO_STARTTRANSFER_TIME: - *param_doublep = data->progress.t_starttransfer; + *param_doublep = DOUBLE_SECS(data->progress.t_starttransfer); break; case CURLINFO_SIZE_UPLOAD: - *param_doublep = (double)data->progress.uploaded; + *param_doublep = (double)data->progress.ul.cur_size; break; case CURLINFO_SIZE_DOWNLOAD: - *param_doublep = (double)data->progress.downloaded; + *param_doublep = (double)data->progress.dl.cur_size; break; case CURLINFO_SPEED_DOWNLOAD: - *param_doublep = (double)data->progress.dlspeed; + *param_doublep = (double)data->progress.dl.speed; break; case CURLINFO_SPEED_UPLOAD: - *param_doublep = (double)data->progress.ulspeed; + *param_doublep = (double)data->progress.ul.speed; break; case CURLINFO_CONTENT_LENGTH_DOWNLOAD: - *param_doublep = (data->progress.flags & PGRS_DL_SIZE_KNOWN)? - (double)data->progress.size_dl:-1; + *param_doublep = (data->progress.flags & PGRS_DL_SIZE_KNOWN) ? + (double)data->progress.dl.total_size : -1; break; case CURLINFO_CONTENT_LENGTH_UPLOAD: - *param_doublep = (data->progress.flags & PGRS_UL_SIZE_KNOWN)? - (double)data->progress.size_ul:-1; + *param_doublep = (data->progress.flags & PGRS_UL_SIZE_KNOWN) ? + (double)data->progress.ul.total_size : -1; break; case CURLINFO_REDIRECT_TIME: - *param_doublep = data->progress.t_redirect; + *param_doublep = DOUBLE_SECS(data->progress.t_redirect); break; default: @@ -326,46 +557,19 @@ static CURLcode getinfo_slist(struct Curl_easy *data, CURLINFO info, struct curl_tlssessioninfo **tsip = (struct curl_tlssessioninfo **) param_slistp; struct curl_tlssessioninfo *tsi = &data->tsi; - struct connectdata *conn = data->easy_conn; +#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) { - unsigned int i; - for(i = 0; i < (sizeof(conn->ssl) / sizeof(conn->ssl[0])); ++i) { - if(conn->ssl[i].use) { -#if defined(USE_AXTLS) - tsi->internals = (void *)conn->ssl[i].ssl; -#elif defined(USE_CYASSL) - tsi->internals = (void *)conn->ssl[i].handle; -#elif defined(USE_DARWINSSL) - tsi->internals = (void *)conn->ssl[i].ssl_ctx; -#elif defined(USE_GNUTLS) - tsi->internals = (void *)conn->ssl[i].session; -#elif defined(USE_GSKIT) - tsi->internals = (void *)conn->ssl[i].handle; -#elif defined(USE_MBEDTLS) - tsi->internals = (void *)&conn->ssl[i].ssl; -#elif defined(USE_NSS) - tsi->internals = (void *)conn->ssl[i].handle; -#elif defined(USE_OPENSSL) - /* Legacy: CURLINFO_TLS_SESSION must return an SSL_CTX pointer. */ - tsi->internals = ((info == CURLINFO_TLS_SESSION) ? - (void *)conn->ssl[i].ctx : - (void *)conn->ssl[i].handle); -#elif defined(USE_POLARSSL) - tsi->internals = (void *)&conn->ssl[i].ssl; -#elif defined(USE_SCHANNEL) - tsi->internals = (void *)&conn->ssl[i].ctxt->ctxt_handle; -#elif defined(USE_SSL) -#error "SSL backend specific information missing for CURLINFO_TLS_SSL_PTR" -#endif - break; - } - } + tsi->internals = Curl_ssl_get_internals(data, FIRSTSOCKET, info, 0); } +#endif } break; default: @@ -394,6 +598,7 @@ 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; @@ -401,7 +606,7 @@ CURLcode Curl_getinfo(struct Curl_easy *data, CURLINFO info, ...) CURLcode result = CURLE_UNKNOWN_OPTION; if(!data) - return result; + return CURLE_BAD_FUNCTION_ARGUMENT; va_start(arg, info); @@ -422,6 +627,11 @@ CURLcode Curl_getinfo(struct Curl_easy *data, CURLINFO info, ...) 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) diff --git a/contrib/curl/lib/getinfo.h b/contrib/curl/lib/getinfo.h index aecf717f75..56bb440b43 100644 --- a/contrib/curl/lib/getinfo.h +++ b/contrib/curl/lib/getinfo.h @@ -7,11 +7,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2010, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -20,6 +20,8 @@ * 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); diff --git a/contrib/curl/lib/gopher.c b/contrib/curl/lib/gopher.c index a073d0ba27..051e6e7ab5 100644 --- a/contrib/curl/lib/gopher.c +++ b/contrib/curl/lib/gopher.c @@ -5,11 +5,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2016, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -18,6 +18,8 @@ * 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" @@ -28,12 +30,17 @@ #include #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" @@ -42,7 +49,11 @@ * Forward declarations. */ -static CURLcode gopher_do(struct connectdata *conn, bool *done); +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. @@ -51,7 +62,7 @@ static CURLcode gopher_do(struct connectdata *conn, bool *done); */ const struct Curl_handler Curl_handler_gopher = { - "GOPHER", /* scheme */ + "gopher", /* scheme */ ZERO_NULL, /* setup_connection */ gopher_do, /* do_it */ ZERO_NULL, /* done */ @@ -64,61 +75,121 @@ const struct Curl_handler Curl_handler_gopher = { ZERO_NULL, /* domore_getsock */ ZERO_NULL, /* perform_getsock */ ZERO_NULL, /* disconnect */ - ZERO_NULL, /* readwrite */ + ZERO_NULL, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ + ZERO_NULL, /* connection_check */ + ZERO_NULL, /* attach connection */ PORT_GOPHER, /* defport */ CURLPROTO_GOPHER, /* protocol */ + CURLPROTO_GOPHER, /* family */ PROTOPT_NONE /* flags */ }; -static CURLcode gopher_do(struct connectdata *conn, bool *done) +#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, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ + 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) { - CURLcode result=CURLE_OK; - struct Curl_easy *data=conn->data; - curl_socket_t sockfd = conn->sock[FIRSTSOCKET]; + (void)data; + (void)done; + return CURLE_OK; +} + +static CURLcode gopher_connecting(struct Curl_easy *data, bool *done) +{ + struct connectdata *conn = data->conn; + CURLcode result; - curl_off_t *bytecount = &data->req.bytecount; - char *path = data->state.path; - char *sel; + 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; - ssize_t amount, k; - size_t len; + timediff_t timeout_ms; + ssize_t k; + size_t amount, 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(path) <= 2) { + if(strlen(gopherpath) <= 2) { sel = (char *)""; - len = (int)strlen(sel); + len = strlen(sel); + free(gopherpath); } else { char *newp; - size_t j, i; /* Otherwise, drop / and the first character (i.e., item type) ... */ - newp = path; - newp+=2; - - /* ... then turn ? into TAB for search servers, Veronica, etc. ... */ - j = strlen(newp); - for(i=0; i, et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -20,10 +20,15 @@ * 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/contrib/curl/lib/hash.c b/contrib/curl/lib/hash.c index 065504235d..1910ac5dc4 100644 --- a/contrib/curl/lib/hash.c +++ b/contrib/curl/lib/hash.c @@ -5,11 +5,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2016, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -18,6 +18,8 @@ * 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" @@ -31,16 +33,21 @@ /* The last #include file should be: */ #include "memdebug.h" +/* random patterns for API verification */ +#define HASHINIT 0x7017e781 +#define ITERINIT 0x5FEDCBA9 + 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; - - Curl_safefree(e->key); + 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); + if(e->dtor) + e->dtor(e->key, e->key_len, e->ptr); + else + h->dtor(e->ptr); e->ptr = NULL; } @@ -55,133 +62,124 @@ hash_element_dtor(void *user, void *element) * @unittest: 1602 * @unittest: 1603 */ -int -Curl_hash_init(struct curl_hash *h, - int slots, +void +Curl_hash_init(struct Curl_hash *h, + size_t slots, hash_function hfunc, comp_function comparator, - curl_hash_dtor dtor) + Curl_hash_dtor dtor) { - int i; - - if(!slots || !hfunc || !comparator ||!dtor) { - return 1; /* failure */ - } + 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; - - h->table = malloc(slots * sizeof(struct curl_llist *)); - if(h->table) { - for(i = 0; i < slots; ++i) { - h->table[i] = Curl_llist_alloc((curl_llist_dtor) hash_element_dtor); - if(!h->table[i]) { - while(i--) { - Curl_llist_destroy(h->table[i], NULL); - h->table[i] = NULL; - } - free(h->table); - h->table = NULL; - h->slots = 0; - return 1; /* failure */ - } - } - return 0; /* fine */ - } - else { - h->slots = 0; - return 1; /* failure */ - } +#ifdef DEBUGBUILD + h->init = HASHINIT; +#endif } -static struct curl_hash_element * -mk_hash_element(const void *key, size_t key_len, const void *p) +static struct Curl_hash_element * +mk_hash_element(const void *key, size_t key_len, const void *p, + Curl_hash_elem_dtor dtor) { - struct curl_hash_element *he = malloc(sizeof(struct curl_hash_element)); - + /* 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) { - void *dupkey = malloc(key_len); - if(dupkey) { - /* copy the key */ - memcpy(dupkey, key, key_len); - - he->key = dupkey; - he->key_len = key_len; - he->ptr = (void *) p; - } - else { - /* failed to duplicate the key, free memory and fail */ - free(he); - he = NULL; - } + /* copy the key */ + memcpy(he->key, key, key_len); + he->key_len = key_len; + he->ptr = (void *) p; + he->dtor = dtor; } return he; } -#define FETCH_LIST(x,y,z) x->table[x->hash_func(y, z, x->slots)] +#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. - * - * @unittest: 1305 - * @unittest: 1602 - * @unittest: 1603 - */ -void * -Curl_hash_add(struct curl_hash *h, void *key, size_t key_len, void *p) +void *Curl_hash_add2(struct Curl_hash *h, void *key, size_t key_len, void *p, + Curl_hash_elem_dtor dtor) { - struct curl_hash_element *he; - struct curl_llist_element *le; - struct curl_llist *l = FETCH_LIST(h, key, key_len); + struct Curl_hash_element *he; + struct Curl_llist_node *le; + struct Curl_llist *l; + + DEBUGASSERT(h); + DEBUGASSERT(h->slots); + DEBUGASSERT(h->init == HASHINIT); + if(!h->table) { + size_t 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); + } - for(le = l->head; le; le = le->next) { - he = (struct curl_hash_element *) le->ptr; + l = FETCH_LIST(h, key, key_len); + + for(le = Curl_llist_head(l); le; le = Curl_node_next(le)) { + he = (struct Curl_hash_element *) Curl_node_elem(le); if(h->comp_func(he->key, he->key_len, key, key_len)) { - Curl_llist_remove(l, le, (void *)h); + Curl_node_uremove(le, (void *)h); --h->size; break; } } - he = mk_hash_element(key, key_len, p); + he = mk_hash_element(key, key_len, p, dtor); if(he) { - if(Curl_llist_insert_next(l, l->tail, he)) { - ++h->size; - return p; /* return the new entry */ - } - /* - * Couldn't insert it, destroy the 'he' element and the key again. We - * don't call hash_element_dtor() since that would also call the - * "destructor" for the actual data 'p'. When we fail, we shall not touch - * that data. - */ - free(he->key); - free(he); + Curl_llist_append(l, he, &he->list); + ++h->size; + return p; /* return the new entry */ } return NULL; /* failure */ } +/* 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 is not 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) +{ + return Curl_hash_add2(h, key, key_len, p, NULL); +} + /* 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) +int Curl_hash_delete(struct Curl_hash *h, void *key, size_t key_len) { - struct curl_llist_element *le; - struct curl_hash_element *he; - struct curl_llist *l = FETCH_LIST(h, key, key_len); + DEBUGASSERT(h); + DEBUGASSERT(h->slots); + DEBUGASSERT(h->init == HASHINIT); + if(h->table) { + struct Curl_llist_node *le; + struct Curl_llist *l = FETCH_LIST(h, key, key_len); - for(le = l->head; le; le = le->next) { - 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; + for(le = Curl_llist_head(l); le; le = Curl_node_next(le)) { + struct Curl_hash_element *he = Curl_node_elem(le); + if(h->comp_func(he->key, he->key_len, key, key_len)) { + Curl_node_uremove(le, (void *) h); + --h->size; + return 0; + } } } return 1; @@ -192,16 +190,17 @@ int Curl_hash_delete(struct curl_hash *h, void *key, size_t key_len) * @unittest: 1603 */ void * -Curl_hash_pick(struct curl_hash *h, void *key, size_t key_len) +Curl_hash_pick(struct Curl_hash *h, void *key, size_t key_len) { - struct curl_llist_element *le; - struct curl_hash_element *he; - struct curl_llist *l; - - if(h) { + DEBUGASSERT(h); + DEBUGASSERT(h->init == HASHINIT); + if(h->table) { + struct Curl_llist_node *le; + struct Curl_llist *l; + DEBUGASSERT(h->slots); l = FETCH_LIST(h, key, key_len); - for(le = l->head; le; le = le->next) { - he = le->ptr; + for(le = Curl_llist_head(l); le; le = Curl_node_next(le)) { + struct Curl_hash_element *he = Curl_node_elem(le); if(h->comp_func(he->key, he->key_len, key, key_len)) { return he->ptr; } @@ -211,25 +210,6 @@ Curl_hash_pick(struct curl_hash *h, void *key, size_t key_len) return NULL; } -#if defined(DEBUGBUILD) && defined(AGGRESIVE_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. * @@ -238,16 +218,16 @@ Curl_hash_apply(curl_hash *h, void *user, * @unittest: 1603 */ void -Curl_hash_destroy(struct curl_hash *h) +Curl_hash_destroy(struct Curl_hash *h) { - int i; - - for(i = 0; i < h->slots; ++i) { - Curl_llist_destroy(h->table[i], (void *) h); - h->table[i] = NULL; + DEBUGASSERT(h->init == HASHINIT); + if(h->table) { + size_t i; + for(i = 0; i < h->slots; ++i) { + Curl_llist_destroy(&h->table[i], (void *) h); + } + Curl_safefree(h->table); } - - Curl_safefree(h->table); h->size = 0; h->slots = 0; } @@ -257,33 +237,38 @@ Curl_hash_destroy(struct curl_hash *h) * @unittest: 1602 */ void -Curl_hash_clean(struct curl_hash *h) +Curl_hash_clean(struct Curl_hash *h) { Curl_hash_clean_with_criterium(h, NULL, NULL); } +size_t Curl_hash_count(struct Curl_hash *h) +{ + DEBUGASSERT(h->init == HASHINIT); + return h->size; +} + /* Cleans all entries that pass the comp function criteria. */ void -Curl_hash_clean_with_criterium(struct curl_hash *h, void *user, +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; + size_t i; - if(!h) + if(!h || !h->table) return; + DEBUGASSERT(h->init == HASHINIT); for(i = 0; i < h->slots; ++i) { - list = h->table[i]; - le = list->head; /* get first list entry */ + struct Curl_llist *list = &h->table[i]; + struct Curl_llist_node *le = + Curl_llist_head(list); /* get first list entry */ while(le) { - struct curl_hash_element *he = le->ptr; - lnext = le->next; + struct Curl_hash_element *he = Curl_node_elem(le); + struct Curl_llist_node *lnext = Curl_node_next(le); /* ask the callback function if we shall remove this entry or not */ - if(comp == NULL || comp(user, he->ptr)) { - Curl_llist_remove(list, le, (void *) h); + if(!comp || comp(user, he->ptr)) { + Curl_node_uremove(le, (void *) h); --h->size; /* one less entry in the hash now */ } le = lnext; @@ -295,11 +280,12 @@ 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; - unsigned long h = 5381; + size_t h = 5381; while(key_str < end) { + size_t j = (size_t)*key_str++; h += h << 5; - h ^= (unsigned long) *key_str++; + h ^= j; } return (h % slots_num); @@ -314,52 +300,57 @@ size_t Curl_str_key_compare(void *k1, size_t key1_len, return 0; } -void Curl_hash_start_iterate(struct curl_hash *hash, - struct curl_hash_iterator *iter) +void Curl_hash_start_iterate(struct Curl_hash *hash, + struct Curl_hash_iterator *iter) { + DEBUGASSERT(hash->init == HASHINIT); iter->hash = hash; iter->slot_index = 0; iter->current_element = NULL; +#ifdef DEBUGBUILD + iter->init = ITERINIT; +#endif } -struct curl_hash_element * -Curl_hash_next_element(struct curl_hash_iterator *iter) +struct Curl_hash_element * +Curl_hash_next_element(struct Curl_hash_iterator *iter) { - int i; - struct curl_hash *h = iter->hash; + struct Curl_hash *h; + DEBUGASSERT(iter->init == ITERINIT); + 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; + iter->current_element = Curl_node_next(iter->current_element); /* If we have reached the end of the list, find the next one */ if(!iter->current_element) { - 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; + size_t i; + for(i = iter->slot_index; i < h->slots; i++) { + if(Curl_llist_head(&h->table[i])) { + iter->current_element = Curl_llist_head(&h->table[i]); + iter->slot_index = i + 1; break; } } } if(iter->current_element) { - struct curl_hash_element *he = iter->current_element->ptr; + struct Curl_hash_element *he = Curl_node_elem(iter->current_element); return he; } - else { - iter->current_element = NULL; - return NULL; - } + return NULL; } #if 0 /* useful function for debugging hashes and their contents */ -void Curl_hash_print(struct curl_hash *h, +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; + struct Curl_hash_iterator iter; + struct Curl_hash_element *he; + size_t last_index = ~0; if(!h) return; @@ -372,7 +363,7 @@ void Curl_hash_print(struct curl_hash *h, while(he) { if(iter.slot_index != last_index) { fprintf(stderr, "index %d:", iter.slot_index); - if(last_index >= 0) { + if(last_index != ~0) { fprintf(stderr, "\n"); } last_index = iter.slot_index; @@ -388,3 +379,25 @@ void Curl_hash_print(struct curl_hash *h, fprintf(stderr, "\n"); } #endif + +void Curl_hash_offt_init(struct Curl_hash *h, + size_t slots, + Curl_hash_dtor dtor) +{ + Curl_hash_init(h, slots, Curl_hash_str, Curl_str_key_compare, dtor); +} + +void *Curl_hash_offt_set(struct Curl_hash *h, curl_off_t id, void *elem) +{ + return Curl_hash_add(h, &id, sizeof(id), elem); +} + +int Curl_hash_offt_remove(struct Curl_hash *h, curl_off_t id) +{ + return Curl_hash_delete(h, &id, sizeof(id)); +} + +void *Curl_hash_offt_get(struct Curl_hash *h, curl_off_t id) +{ + return Curl_hash_pick(h, &id, sizeof(id)); +} diff --git a/contrib/curl/lib/hash.h b/contrib/curl/lib/hash.h index a5a6cac720..b160395024 100644 --- a/contrib/curl/lib/hash.h +++ b/contrib/curl/lib/hash.h @@ -7,11 +7,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2016, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -20,6 +20,8 @@ * 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" @@ -41,59 +43,81 @@ typedef size_t (*comp_function) (void *key1, void *key2, size_t key2_len); -typedef void (*curl_hash_dtor)(void *); +typedef void (*Curl_hash_dtor)(void *); -struct curl_hash { - struct curl_llist **table; +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; + Curl_hash_dtor dtor; + size_t slots; size_t size; +#ifdef DEBUGBUILD + int init; +#endif }; -struct curl_hash_element { +typedef void (*Curl_hash_elem_dtor)(void *key, size_t key_len, void *p); + +struct Curl_hash_element { + struct Curl_llist_node list; void *ptr; - char *key; + Curl_hash_elem_dtor dtor; size_t key_len; +#ifdef DEBUGBUILD + int init; +#endif + 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; +struct Curl_hash_iterator { + struct Curl_hash *hash; + size_t slot_index; + struct Curl_llist_node *current_element; +#ifdef DEBUGBUILD + int init; +#endif }; -int 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)); -int Curl_hash_count(struct curl_hash *h); -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, +void Curl_hash_init(struct Curl_hash *h, + size_t 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); +void *Curl_hash_add2(struct Curl_hash *h, void *key, size_t key_len, void *p, + Curl_hash_elem_dtor dtor); +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_destroy(struct Curl_hash *h); +size_t Curl_hash_count(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_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 Curl_hash_print(struct Curl_hash *h, void (*func)(void *)); +/* Hash for `curl_off_t` as key */ +void Curl_hash_offt_init(struct Curl_hash *h, size_t slots, + Curl_hash_dtor dtor); + +void *Curl_hash_offt_set(struct Curl_hash *h, curl_off_t id, void *elem); +int Curl_hash_offt_remove(struct Curl_hash *h, curl_off_t id); +void *Curl_hash_offt_get(struct Curl_hash *h, curl_off_t id); + #endif /* HEADER_CURL_HASH_H */ diff --git a/contrib/curl/lib/headers.c b/contrib/curl/lib/headers.c new file mode 100644 index 0000000000..2985e1e185 --- /dev/null +++ b/contrib/curl/lib/headers.c @@ -0,0 +1,449 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#include "urldata.h" +#include "strdup.h" +#include "strcase.h" +#include "sendf.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_node *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 = (unsigned int)(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_node *e; + struct Curl_llist_node *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 = Curl_llist_head(&data->state.httphdrs); e; e = Curl_node_next(e)) { + hs = Curl_node_elem(e); + 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 = Curl_llist_head(&data->state.httphdrs); e; e = Curl_node_next(e)) { + hs = Curl_node_elem(e); + if(strcasecompare(hs->name, name) && + (hs->type & type) && + (hs->request == request) && + (match++ == nameindex)) { + e_pick = e; + break; + } + } + if(!e) /* this should not 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_node *pick; + struct Curl_llist_node *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 = Curl_node_next(pick); + } + else + pick = Curl_llist_head(&data->state.httphdrs); + + if(pick) { + /* make sure it is the next header of the desired type */ + do { + hs = Curl_node_elem(pick); + if((hs->type & type) && (hs->request == request)) + break; + pick = Curl_node_next(pick); + } while(pick); + } + + if(!pick) + /* no more headers available */ + return NULL; + + hs = Curl_node_elem(pick); + + /* count number of occurrences of this name within the mask and figure out + the index for the currently selected entry */ + for(e = Curl_llist_head(&data->state.httphdrs); e; e = Curl_node_next(e)) { + struct Curl_header_store *check = Curl_node_elem(e); + if(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_node_remove(&hs->node); + + /* 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_append(&data->state.httphdrs, 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) + /* neither CR nor LF as terminator is not a valid header */ + return CURLE_WEIRD_SERVER_REPLY; + } + hlen = end - header; + + 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 { + /* cannot 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) { + hs->name = name; + hs->value = value; + hs->type = type; + hs->request = data->state.requests; + + /* insert this node into the list of headers */ + Curl_llist_append(&data->state.httphdrs, hs, &hs->node); + data->state.prevhead = hs; + } + else + free(hs); + return result; +} + +/* + * Curl_headers_reset(). Reset the headers subsystem. + */ +static void headers_reset(struct Curl_easy *data) +{ + Curl_llist_init(&data->state.httphdrs, NULL); + data->state.prevhead = NULL; +} + +struct hds_cw_collect_ctx { + struct Curl_cwriter super; +}; + +static CURLcode hds_cw_collect_write(struct Curl_easy *data, + struct Curl_cwriter *writer, int type, + const char *buf, size_t blen) +{ + if((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, buf, htype); + CURL_TRC_WRITE(data, "header_collect pushed(type=%x, len=%zu) -> %d", + htype, blen, result); + if(result) + return result; + } + return Curl_cwriter_write(data, writer->next, type, buf, blen); +} + +static const struct Curl_cwtype hds_cw_collect = { + "hds-collect", + NULL, + Curl_cwriter_def_init, + hds_cw_collect_write, + Curl_cwriter_def_close, + sizeof(struct hds_cw_collect_ctx) +}; + +CURLcode Curl_headers_init(struct Curl_easy *data) +{ + struct Curl_cwriter *writer; + CURLcode result; + + if(data->conn && (data->conn->handler->protocol & PROTO_FAMILY_HTTP)) { + /* avoid installing it twice */ + if(Curl_cwriter_get_by_name(data, hds_cw_collect.name)) + return CURLE_OK; + + result = Curl_cwriter_create(&writer, data, &hds_cw_collect, + CURL_CW_PROTOCOL); + if(result) + return result; + + result = Curl_cwriter_add(data, writer); + if(result) { + Curl_cwriter_free(data, writer); + return result; + } + } + return CURLE_OK; +} + +/* + * Curl_headers_cleanup(). Free all stored headers and associated memory. + */ +CURLcode Curl_headers_cleanup(struct Curl_easy *data) +{ + struct Curl_llist_node *e; + struct Curl_llist_node *n; + + for(e = Curl_llist_head(&data->state.httphdrs); e; e = n) { + struct Curl_header_store *hs = Curl_node_elem(e); + n = Curl_node_next(e); + free(hs); + } + headers_reset(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/contrib/curl/lib/headers.h b/contrib/curl/lib/headers.h new file mode 100644 index 0000000000..e11fe9804e --- /dev/null +++ b/contrib/curl/lib/headers.h @@ -0,0 +1,62 @@ +#ifndef HEADER_CURL_HEADER_H +#define HEADER_CURL_HEADER_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ +#include "curl_setup.h" + +#if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_HEADERS_API) + +struct Curl_header_store { + struct Curl_llist_node 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 */ +}; + +/* + * Initialize header collecting for a transfer. + * Will add a client writer that catches CLIENTWRITE_HEADER writes. + */ +CURLcode Curl_headers_init(struct Curl_easy *data); + +/* + * 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_init(x) CURLE_OK +#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/contrib/curl/lib/hmac.c b/contrib/curl/lib/hmac.c index dae95054b0..90f37f0bff 100644 --- a/contrib/curl/lib/hmac.c +++ b/contrib/curl/lib/hmac.c @@ -5,11 +5,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2016, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -18,18 +18,22 @@ * 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" -#ifndef CURL_DISABLE_CRYPTO_AUTH +#if (defined(USE_CURL_NTLM_CORE) && !defined(USE_WINDOWS_SSPI)) \ + || !defined(CURL_DISABLE_AWS) || !defined(CURL_DISABLE_DIGEST_AUTH) #include #include "curl_hmac.h" #include "curl_memory.h" +#include "warnless.h" /* The last #include file should be: */ #include "memdebug.h" @@ -38,8 +42,8 @@ * 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 initialisation. + * and computing procedures are setup dynamically at HMAC computation context + * initialization. */ static const unsigned char hmac_ipad = 0x36; @@ -47,18 +51,18 @@ static const unsigned char hmac_opad = 0x5C; -HMAC_context * -Curl_HMAC_init(const HMAC_params * hashparams, +struct HMAC_context * +Curl_HMAC_init(const struct HMAC_params *hashparams, const unsigned char *key, unsigned int keylen) { size_t i; - HMAC_context *ctxt; + struct HMAC_context *ctxt; unsigned char *hkey; unsigned char b; /* Create HMAC context. */ - i = sizeof *ctxt + 2 * hashparams->hmac_ctxtsize + + i = sizeof(*ctxt) + 2 * hashparams->hmac_ctxtsize + hashparams->hmac_resultlen; ctxt = malloc(i); @@ -100,7 +104,7 @@ Curl_HMAC_init(const HMAC_params * hashparams, return ctxt; } -int Curl_HMAC_update(HMAC_context * ctxt, +int Curl_HMAC_update(struct HMAC_context *ctxt, const unsigned char *data, unsigned int len) { @@ -110,9 +114,9 @@ int Curl_HMAC_update(HMAC_context * ctxt, } -int Curl_HMAC_final(HMAC_context *ctxt, unsigned char *result) +int Curl_HMAC_final(struct HMAC_context *ctxt, unsigned char *result) { - const HMAC_params * hashparams = ctxt->hmac_hash; + const struct HMAC_params *hashparams = ctxt->hmac_hash; /* Do not get result if called with a null parameter: only release storage. */ @@ -129,4 +133,41 @@ int Curl_HMAC_final(HMAC_context *ctxt, unsigned char *result) return 0; } -#endif /* CURL_DISABLE_CRYPTO_AUTH */ +/* + * 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/contrib/curl/lib/hostasyn.c b/contrib/curl/lib/hostasyn.c index 28bdf7a48a..4d6a8e8596 100644 --- a/contrib/curl/lib/hostasyn.c +++ b/contrib/curl/lib/hostasyn.c @@ -5,11 +5,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2015, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -18,10 +18,17 @@ * 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 #endif @@ -36,26 +43,16 @@ #include #endif -#ifdef HAVE_PROCESS_H -#include -#endif - #include "urldata.h" #include "sendf.h" #include "hostip.h" #include "hash.h" #include "share.h" -#include "strerror.h" #include "url.h" #include "curl_memory.h" /* The last #include file should be: */ #include "memdebug.h" -/*********************************************************************** - * Only for builds using asynchronous name resolves - **********************************************************************/ -#ifdef CURLRES_ASYNCH - /* * Curl_addrinfo_callback() gets called by ares, gethostbyname_thread() * or getaddrinfo_thread() when we got the name resolved (or not!). @@ -66,88 +63,61 @@ * * The storage operation locks and unlocks the DNS cache. */ -CURLcode Curl_addrinfo_callback(struct connectdata *conn, +CURLcode Curl_addrinfo_callback(struct Curl_easy *data, int status, struct Curl_addrinfo *ai) { struct Curl_dns_entry *dns = NULL; CURLcode result = CURLE_OK; - conn->async.status = status; + data->state.async.status = status; if(CURL_ASYNC_SUCCESS == status) { if(ai) { - struct Curl_easy *data = conn->data; - if(data->share) Curl_share_lock(data, CURL_LOCK_DATA_DNS, CURL_LOCK_ACCESS_SINGLE); dns = Curl_cache_addr(data, ai, - conn->async.hostname, - conn->async.port); + data->state.async.hostname, 0, + data->state.async.port, FALSE); + 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; } - - if(data->share) - Curl_share_unlock(data, CURL_LOCK_DATA_DNS); } else { result = CURLE_OUT_OF_MEMORY; } } - conn->async.dns = dns; + data->state.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->async.done = TRUE; + data->state.async.done = TRUE; /* IPv4: The input hostent struct will be freed by ares when we return from this function */ return result; } -/* 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_async_resolved(struct connectdata *conn, - bool *protocol_done) -{ - CURLcode result; - - if(conn->async.dns) { - conn->dns_entry = conn->async.dns; - conn->async.dns = NULL; - } - - result = Curl_setup_conn(conn, protocol_done); - - if(result) - /* We're not allowed to return failure with memory left allocated - in the connectdata struct, free those here */ - Curl_disconnect(conn, FALSE); /* close the connection */ - - 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 */ -Curl_addrinfo *Curl_getaddrinfo(struct connectdata *conn, - const char *hostname, - int port, - int *waitp) +struct Curl_addrinfo *Curl_getaddrinfo(struct Curl_easy *data, + const char *hostname, + int port, + int *waitp) { - return Curl_resolver_getaddrinfo(conn, hostname, port, waitp); + return Curl_resolver_getaddrinfo(data, hostname, port, waitp); } #endif /* CURLRES_ASYNCH */ diff --git a/contrib/curl/lib/hostcheck.c b/contrib/curl/lib/hostcheck.c deleted file mode 100644 index cbd0893601..0000000000 --- a/contrib/curl/lib/hostcheck.c +++ /dev/null @@ -1,150 +0,0 @@ -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) 1998 - 2016, Daniel Stenberg, , et al. - * - * This software is licensed as described in the file COPYING, which - * you should have received as part of this distribution. The terms - * are also available at https://curl.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. - * - ***************************************************************************/ - -#include "curl_setup.h" - -#if defined(USE_OPENSSL) \ - || defined(USE_AXTLS) \ - || defined(USE_GSKIT) \ - || (defined(USE_SCHANNEL) && defined(_WIN32_WCE)) -/* these backends use functions from this file */ - -#ifdef HAVE_NETINET_IN_H -#include -#endif - -#include "hostcheck.h" -#include "strcase.h" -#include "inet_pton.h" - -#include "curl_memory.h" -/* The last #include file should be: */ -#include "memdebug.h" - -/* - * 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://tools.ietf.org/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. - * - * NOTE: hostmatch() gets called with copied buffers so that it can modify the - * contents at will. - */ - -static int hostmatch(char *hostname, char *pattern) -{ - const char *pattern_label_end, *pattern_wildcard, *hostname_label_end; - int wildcard_enabled; - size_t prefixlen, suffixlen; - struct in_addr ignored; -#ifdef ENABLE_IPV6 - struct sockaddr_in6 si6; -#endif - - /* normalize pattern and hostname by stripping off trailing dots */ - size_t len = strlen(hostname); - if(hostname[len-1]=='.') - hostname[len-1]=0; - len = strlen(pattern); - if(pattern[len-1]=='.') - pattern[len-1]=0; - - pattern_wildcard = strchr(pattern, '*'); - if(pattern_wildcard == NULL) - return strcasecompare(pattern, hostname) ? - CURL_HOST_MATCH : CURL_HOST_NOMATCH; - - /* detect IP address as hostname and fail the match if so */ - if(Curl_inet_pton(AF_INET, hostname, &ignored) > 0) - return CURL_HOST_NOMATCH; -#ifdef ENABLE_IPV6 - else if(Curl_inet_pton(AF_INET6, hostname, &si6.sin6_addr) > 0) - return CURL_HOST_NOMATCH; -#endif - - /* We require at least 2 dots in pattern to avoid too wide wildcard - match. */ - wildcard_enabled = 1; - pattern_label_end = strchr(pattern, '.'); - if(pattern_label_end == NULL || strchr(pattern_label_end+1, '.') == NULL || - pattern_wildcard > pattern_label_end || - strncasecompare(pattern, "xn--", 4)) { - wildcard_enabled = 0; - } - if(!wildcard_enabled) - return strcasecompare(pattern, hostname) ? - CURL_HOST_MATCH : CURL_HOST_NOMATCH; - - hostname_label_end = strchr(hostname, '.'); - if(hostname_label_end == NULL || - !strcasecompare(pattern_label_end, hostname_label_end)) - return CURL_HOST_NOMATCH; - - /* The wildcard must match at least one character, so the left-most - label of the hostname is at least as large as the left-most label - of the pattern. */ - if(hostname_label_end - hostname < pattern_label_end - pattern) - return CURL_HOST_NOMATCH; - - prefixlen = pattern_wildcard - pattern; - suffixlen = pattern_label_end - (pattern_wildcard+1); - return strncasecompare(pattern, hostname, prefixlen) && - strncasecompare(pattern_wildcard+1, hostname_label_end - suffixlen, - suffixlen) ? - CURL_HOST_MATCH : CURL_HOST_NOMATCH; -} - -int Curl_cert_hostcheck(const char *match_pattern, const char *hostname) -{ - char *matchp; - char *hostp; - int res = 0; - if(!match_pattern || !*match_pattern || - !hostname || !*hostname) /* sanity check */ - ; - else { - matchp = strdup(match_pattern); - if(matchp) { - hostp = strdup(hostname); - if(hostp) { - if(hostmatch(hostp, matchp) == CURL_HOST_MATCH) - res= 1; - free(hostp); - } - free(matchp); - } - } - - return res; -} - -#endif /* OPENSSL, AXTLS, GSKIT or schannel+wince */ diff --git a/contrib/curl/lib/hostip.c b/contrib/curl/lib/hostip.c index fa4bad9824..a94a00d845 100644 --- a/contrib/curl/lib/hostip.c +++ b/contrib/curl/lib/hostip.c @@ -5,11 +5,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2016, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -18,6 +18,8 @@ * 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" @@ -25,6 +27,9 @@ #ifdef HAVE_NETINET_IN_H #include #endif +#ifdef HAVE_NETINET_IN6_H +#include +#endif #ifdef HAVE_NETDB_H #include #endif @@ -36,37 +41,41 @@ #include #endif -#ifdef HAVE_SETJMP_H #include -#endif -#ifdef HAVE_SIGNAL_H #include -#endif - -#ifdef HAVE_PROCESS_H -#include -#endif #include "urldata.h" #include "sendf.h" #include "hostip.h" #include "hash.h" +#include "rand.h" #include "share.h" -#include "strerror.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) +#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 * ================== @@ -75,8 +84,8 @@ * 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 + * that. The host may not be able to resolve IPv6, but we do not really have to + * take that into account. Hosts that are not IPv6-enabled have CURLRES_IPV4 * defined. * * CURLRES_ARES - is defined if libcurl is built to use c-ares for @@ -106,114 +115,70 @@ * CURLRES_* defines based on the config*.h and curl_setup.h defines. */ -/* These two symbols are for the global DNS cache */ -static struct curl_hash hostname_cache; -static int host_cache_initialized; - -static void freednsentry(void *freethis); - -/* - * 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) -{ - int rc = 0; - if(!host_cache_initialized) { - rc = Curl_hash_init(&hostname_cache, 7, Curl_hash_str, - Curl_str_key_compare, freednsentry); - if(!rc) - host_cache_initialized = 1; - } - return rc?NULL:&hostname_cache; -} - -/* - * Destroy and cleanup the global DNS cache - */ -void Curl_global_host_cache_dtor(void) -{ - if(host_cache_initialized) { - Curl_hash_destroy(&hostname_cache); - host_cache_initialized = 0; - } -} +static void hostcache_unlink_entry(void *entry); -/* - * Return # of adresses in a Curl_addrinfo struct - */ -int Curl_num_addresses(const Curl_addrinfo *addr) -{ - int i = 0; - while(addr) { - addr = addr->ai_next; - i++; - } - return i; -} +#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() returns a printable version of the 1st address + * 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, it returns NULL. + * If the conversion fails, the target buffer is empty. */ -const char * -Curl_printable_address(const Curl_addrinfo *ai, char *buf, size_t bufsize) +void Curl_printable_address(const struct Curl_addrinfo *ai, char *buf, + size_t bufsize) { - const struct sockaddr_in *sa4; - const struct in_addr *ipaddr4; -#ifdef ENABLE_IPV6 - const struct sockaddr_in6 *sa6; - const struct in6_addr *ipaddr6; -#endif + DEBUGASSERT(bufsize); + buf[0] = 0; switch(ai->ai_family) { - case AF_INET: - sa4 = (const void *)ai->ai_addr; - ipaddr4 = &sa4->sin_addr; - return Curl_inet_ntop(ai->ai_family, (const void *)ipaddr4, buf, - bufsize); -#ifdef ENABLE_IPV6 - case AF_INET6: - sa6 = (const void *)ai->ai_addr; - ipaddr6 = &sa6->sin6_addr; - return Curl_inet_ntop(ai->ai_family, (const void *)ipaddr6, buf, - bufsize); -#endif - default: - break; + 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 USE_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; } - return NULL; } /* - * Return a hostcache id string for the provided host + port, to be used by - * the DNS caching. + * 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 char * -create_hostcache_id(const char *name, int port) +static size_t +create_hostcache_id(const char *name, + size_t nlen, /* 0 or actual name length */ + int port, char *ptr, size_t buflen) { - /* create and return the new allocated entry */ - char *id = aprintf("%s:%d", name, port); - char *ptr = id; - if(ptr) { - /* lower case the name part */ - while(*ptr && (*ptr != ':')) { - *ptr = (char)TOLOWER(*ptr); - ptr++; - } - } - return id; + size_t len = nlen ? nlen : strlen(name); + DEBUGASSERT(buflen >= MAX_HOSTCACHE_LEN); + if(len > (buflen - 7)) + len = buflen - 7; + /* store and lower case the name */ + Curl_strntolower(ptr, name, len); + return msnprintf(&ptr[len], 7, ":%u", port) + len; } struct hostcache_prune_data { - long cache_timeout; time_t now; + time_t oldest; /* oldest time in cache not pruned. */ + int max_age_sec; }; /* @@ -224,30 +189,42 @@ struct hostcache_prune_data { * cache. */ static int -hostcache_timestamp_remove(void *datap, void *hc) +hostcache_entry_is_stale(void *datap, void *hc) { - struct hostcache_prune_data *data = + struct hostcache_prune_data *prune = (struct hostcache_prune_data *) datap; - struct Curl_dns_entry *c = (struct Curl_dns_entry *) hc; - - return (0 != c->timestamp) - && (data->now - c->timestamp >= data->cache_timeout); + struct Curl_dns_entry *dns = (struct Curl_dns_entry *) hc; + + if(dns->timestamp) { + /* age in seconds */ + time_t age = prune->now - dns->timestamp; + if(age >= prune->max_age_sec) + 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 void -hostcache_prune(struct curl_hash *hostcache, long cache_timeout, time_t now) +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.max_age_sec = cache_timeout; user.now = now; + user.oldest = 0; Curl_hash_clean_with_criterium(hostcache, (void *) &user, - hostcache_timestamp_remove); + hostcache_entry_is_stale); + + return user.oldest; } /* @@ -257,72 +234,107 @@ hostcache_prune(struct curl_hash *hostcache, long cache_timeout, time_t now) 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->set.dns_cache_timeout == -1) || !data->dns.hostcache) - /* cache forever means never prune, and NULL hostcache means - we can't do it */ + if(!data->dns.hostcache) + /* NULL hostcache means we cannot do it */ return; if(data->share) Curl_share_lock(data, CURL_LOCK_DATA_DNS, CURL_LOCK_ACCESS_SINGLE); - time(&now); + now = time(NULL); - /* Remove outdated and unused entries from the hostcache */ - hostcache_prune(data->dns.hostcache, - data->set.dns_cache_timeout, - 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 && + (Curl_hash_count(data->dns.hostcache) > MAX_DNS_CACHE_SIZE)); if(data->share) Curl_share_unlock(data, CURL_LOCK_DATA_DNS); } -#ifdef HAVE_SIGSETJMP +#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. */ -sigjmp_buf curl_jmpenv; +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 connectdata *conn, - const char *hostname, - int port) +static struct Curl_dns_entry *fetch_addr(struct Curl_easy *data, + const char *hostname, + int port) { - char *entry_id = NULL; struct Curl_dns_entry *dns = NULL; - size_t entry_len; - struct Curl_easy *data = conn->data; + char entry_id[MAX_HOSTCACHE_LEN]; /* Create an entry id, based upon the hostname and port */ - entry_id = create_hostcache_id(hostname, port); - /* If we can't create the entry id, fail */ - if(!entry_id) - return dns; + size_t entry_len = create_hostcache_id(hostname, 0, port, + entry_id, sizeof(entry_id)); + + /* See if it is already in our dns cache */ + dns = Curl_hash_pick(data->dns.hostcache, entry_id, entry_len + 1); - entry_len = strlen(entry_id); + /* 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 its already in our dns cache */ - dns = Curl_hash_pick(data->dns.hostcache, entry_id, entry_len+1); + /* See if it is 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)) { + 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.now = time(NULL); + user.max_age_sec = data->set.dns_cache_timeout; + user.oldest = 0; - if(hostcache_timestamp_remove(&user, dns)) { - infof(data, "Hostname in DNS cache was stale, zapped\n"); + if(hostcache_entry_is_stale(&user, dns)) { + infof(data, "Hostname in DNS cache was stale, zapped"); dns = NULL; /* the memory deallocation is being handled by the hash */ - Curl_hash_delete(data->dns.hostcache, entry_id, entry_len+1); + Curl_hash_delete(data->dns.hostcache, entry_id, entry_len + 1); } } - /* free the allocated entry_id again */ - free(entry_id); + /* 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 does not 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; } @@ -337,24 +349,23 @@ fetch_addr(struct connectdata *conn, * * 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! + * The returned data *MUST* be "released" with Curl_resolv_unlink() after + * use, or we will leak memory! */ struct Curl_dns_entry * -Curl_fetch_addr(struct connectdata *conn, +Curl_fetch_addr(struct Curl_easy *data, const char *hostname, int port) { - struct Curl_easy *data = conn->data; struct Curl_dns_entry *dns = NULL; if(data->share) Curl_share_lock(data, CURL_LOCK_DATA_DNS, CURL_LOCK_ACCESS_SINGLE); - dns = fetch_addr(conn, hostname, port); + dns = fetch_addr(data, hostname, port); if(dns) - dns->inuse++; /* we use it! */ + dns->refcount++; /* we use it! */ if(data->share) Curl_share_unlock(data, CURL_LOCK_DATA_DNS); @@ -362,6 +373,88 @@ Curl_fetch_addr(struct connectdata *conn, 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] % (unsigned int)(i + 1)]; + nodes[rnd[i] % (unsigned int)(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. * @@ -373,66 +466,214 @@ Curl_fetch_addr(struct connectdata *conn, */ struct Curl_dns_entry * Curl_cache_addr(struct Curl_easy *data, - Curl_addrinfo *addr, + struct Curl_addrinfo *addr, const char *hostname, - int port) + size_t hostlen, /* length or zero */ + int port, + bool permanent) { - char *entry_id; + char entry_id[MAX_HOSTCACHE_LEN]; size_t entry_len; struct Curl_dns_entry *dns; struct Curl_dns_entry *dns2; - /* Create an entry id, based upon the hostname and port */ - entry_id = create_hostcache_id(hostname, port); - /* If we can't create the entry id, fail */ - if(!entry_id) - return NULL; - entry_len = strlen(entry_id); +#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)); + dns = calloc(1, sizeof(struct Curl_dns_entry) + hostlen); if(!dns) { - free(entry_id); return NULL; } - dns->inuse = 1; /* the cache has the first reference */ + /* Create an entry id, based upon the hostname and port */ + entry_len = create_hostcache_id(hostname, hostlen, port, + entry_id, sizeof(entry_id)); + + dns->refcount = 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 CURLOPT_RESOLVE entry */ + if(permanent) + dns->timestamp = 0; /* an entry that never goes stale */ + else { + dns->timestamp = time(NULL); + if(dns->timestamp == 0) + dns->timestamp = 1; + } + 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, + dns2 = Curl_hash_add(data->dns.hostcache, entry_id, entry_len + 1, (void *)dns); if(!dns2) { free(dns); - free(entry_id); return NULL; } dns = dns2; - dns->inuse++; /* mark entry as in-use */ + dns->refcount++; /* mark entry as in-use */ + return dns; +} - /* free the allocated entry_id */ - free(entry_id); +#ifdef USE_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; - return dns; + sa6.sin6_family = AF_INET6; + sa6.sin6_port = htons(port16); + sa6.sin6_flowinfo = 0; + sa6.sin6_scope_id = 0; + + (void)Curl_inet_pton(AF_INET6, "::1", ipv6); + 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 USE_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 does not 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 cannot get/use one */ + ipv6_works = 0; + else { + ipv6_works = 1; + sclose(s); + } + return (ipv6_works > 0) ? TRUE : FALSE; + } +} +#endif /* USE_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 USE_IPV6 + struct in6_addr in6; +#endif + if(Curl_inet_pton(AF_INET, hostname, &in) > 0 +#ifdef USE_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 + * is provided). This function might return immediately if we are 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. - * - * In debug mode, we specifically test for an interface name "LocalHost" - * and resolve "localhost" instead as a means to permit test cases - * to connect to a local test server with any host name. + * function is used. You MUST call Curl_resolv_unlink() later (when you are + * done using this struct) to decrease the reference counter again. * * Return codes: * @@ -441,26 +682,39 @@ Curl_cache_addr(struct Curl_easy *data, * CURLRESOLV_PENDING (1) = waiting for response, no pointer */ -int Curl_resolv(struct connectdata *conn, - const char *hostname, - int port, - struct Curl_dns_entry **entry) +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; - struct Curl_easy *data = conn->data; CURLcode result; - int rc = CURLRESOLV_ERROR; /* default to failure */ - + 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(conn, hostname, port); + dns = fetch_addr(data, hostname, port); if(dns) { - infof(data, "Hostname %s was found in DNS cache\n", hostname); - dns->inuse++; /* we use it! */ + infof(data, "Hostname %s was found in DNS cache", hostname); + dns->refcount++; /* we use it! */ rc = CURLRESOLV_RESOLVED; } @@ -470,31 +724,103 @@ int Curl_resolv(struct connectdata *conn, if(!dns) { /* The entry was not in the cache. Resolve it to IP address */ - Curl_addrinfo *addr; - int respwait; - - /* Check what IP specifics the app has requested and if we can provide it. - * If not, bail out. */ - if(!Curl_ipvalid(conn)) - return CURLRESOLV_ERROR; + 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 - /* 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(conn, -#ifdef DEBUGBUILD - (data->set.str[STRING_DEVICE] - && !strcmp(data->set.str[STRING_DEVICE], - "LocalHost"))?"localhost": + /* 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 + data->state.async.resolver, +#else + NULL, #endif - hostname, port, &respwait); + 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); + if(!addr) + return CURLRESOLV_ERROR; + } +#ifdef USE_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 */ + addr = Curl_ip2addr(AF_INET6, &in6, hostname, port); + if(!addr) + return CURLRESOLV_ERROR; + } + } +#endif /* USE_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 USE_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 /* USE_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_resolver_is_resolved(conn, &dns); + /* First, check that we have not received the info by now */ + result = Curl_resolv_check(data, &dns); if(result) /* error detected */ return CURLRESOLV_ERROR; if(dns) @@ -508,7 +834,7 @@ int Curl_resolv(struct connectdata *conn, 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, port); + dns = Curl_cache_addr(data, addr, hostname, 0, port, FALSE); if(data->share) Curl_share_unlock(data, CURL_LOCK_DATA_DNS); @@ -516,8 +842,10 @@ int Curl_resolv(struct connectdata *conn, if(!dns) /* returned failure, bail out nicely */ Curl_freeaddrinfo(addr); - else + else { rc = CURLRESOLV_RESOLVED; + show_resolve_info(data, dns); + } } } @@ -529,27 +857,25 @@ int Curl_resolv(struct connectdata *conn, #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 + * execution. This effectively causes the remainder of the application to run * within a signal handler which is nonportable and could lead to problems. */ -static -RETSIGTYPE alarmfunc(int sig) +CURL_NORETURN static +void alarmfunc(int sig) { - /* this is for "-ansi -Wall -pedantic" to stop complaining! (rabe) */ (void)sig; siglongjmp(curl_jmpenv, 1); - return; } #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 + * timeout. This function might return immediately if we are 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. + * function is used. You MUST call Curl_resolv_unlink() later (when you are + * done using this struct) to decrease the reference 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 @@ -564,16 +890,16 @@ RETSIGTYPE alarmfunc(int sig) * CURLRESOLV_PENDING (1) = waiting for response, no pointer */ -int Curl_resolv_timeout(struct connectdata *conn, - const char *hostname, - int port, - struct Curl_dns_entry **entry, - time_t timeoutms) +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; /* wether old sigact has been saved */ + volatile bool keep_copysig = FALSE; /* whether old sigact has been saved */ struct sigaction sigact; #else #ifdef HAVE_SIGNAL @@ -582,9 +908,8 @@ int Curl_resolv_timeout(struct connectdata *conn, #endif /* HAVE_SIGACTION */ volatile long timeout; volatile unsigned int prev_alarm = 0; - struct Curl_easy *data = conn->data; #endif /* USE_ALARM_TIMEOUT */ - int rc; + enum resolve_t rc; *entry = NULL; @@ -597,11 +922,11 @@ int Curl_resolv_timeout(struct connectdata *conn, /* Ignore the timeout when signals are disabled */ timeout = 0; else - timeout = timeoutms; + timeout = (timeoutms > LONG_MAX) ? LONG_MAX : (long)timeoutms; if(!timeout) /* USE_ALARM_TIMEOUT defined, but no timeout actually requested */ - return Curl_resolv(conn, hostname, port, entry); + return Curl_resolv(data, hostname, port, TRUE, entry); if(timeout < 1000) { /* The alarm() function only provides integer second resolution, so if @@ -615,8 +940,10 @@ int Curl_resolv_timeout(struct connectdata *conn, 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 + as otherwise we would 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"); @@ -634,7 +961,7 @@ int Curl_resolv_timeout(struct connectdata *conn, 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 behaviour! */ + /* HP-UX does not have SA_RESTART but defaults to that behavior! */ sigact.sa_flags &= ~SA_RESTART; #endif /* now set the new struct */ @@ -654,7 +981,7 @@ int Curl_resolv_timeout(struct connectdata *conn, #else #ifndef CURLRES_ASYNCH if(timeoutms) - infof(conn->data, "timeout on name lookup is not supported\n"); + infof(data, "timeout on name lookup is not supported"); #else (void)timeoutms; /* timeoutms not used with an async resolver */ #endif @@ -663,7 +990,7 @@ int Curl_resolv_timeout(struct connectdata *conn, /* Perform the actual name resolution. This might be interrupted by an * alarm if it takes too long. */ - rc = Curl_resolv(conn, hostname, port, entry); + rc = Curl_resolv(data, hostname, port, TRUE, entry); #ifdef USE_ALARM_TIMEOUT clean_up: @@ -685,24 +1012,27 @@ int Curl_resolv_timeout(struct connectdata *conn, #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 */ - unsigned long elapsed_ms = Curl_tvdiff(Curl_tvnow(), conn->created); + 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 = prev_alarm - elapsed_ms/1000; + 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 + will not, 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!"); + failf(data, "Previous alarm fired off"); } else alarm((unsigned int)alarm_set); @@ -713,18 +1043,20 @@ int Curl_resolv_timeout(struct connectdata *conn, } /* - * 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. + * Curl_resolv_unlink() releases a reference to the given cached DNS entry. + * When the reference count reaches 0, the entry is destroyed. It is important + * that only one unlink 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) +void Curl_resolv_unlink(struct Curl_easy *data, struct Curl_dns_entry **pdns) { + struct Curl_dns_entry *dns = *pdns; + *pdns = NULL; if(data && data->share) Curl_share_lock(data, CURL_LOCK_DATA_DNS, CURL_LOCK_ACCESS_SINGLE); - freednsentry(dns); + hostcache_unlink_entry(dns); if(data && data->share) Curl_share_unlock(data, CURL_LOCK_DATA_DNS); @@ -733,25 +1065,42 @@ void Curl_resolv_unlock(struct Curl_easy *data, struct Curl_dns_entry *dns) /* * File-internal: release cache dns entry reference, free if inuse drops to 0 */ -static void freednsentry(void *freethis) +static void hostcache_unlink_entry(void *entry) { - struct Curl_dns_entry *dns = (struct Curl_dns_entry *) freethis; - DEBUGASSERT(dns && (dns->inuse>0)); + struct Curl_dns_entry *dns = (struct Curl_dns_entry *) entry; + DEBUGASSERT(dns && (dns->refcount > 0)); - dns->inuse--; - if(dns->inuse == 0) { + dns->refcount--; + if(dns->refcount == 0) { Curl_freeaddrinfo(dns->addr); +#ifdef USE_HTTPSRR + if(dns->hinfo) { + if(dns->hinfo->target) + free(dns->hinfo->target); + if(dns->hinfo->alpns) + free(dns->hinfo->alpns); + if(dns->hinfo->ipv4hints) + free(dns->hinfo->ipv4hints); + if(dns->hinfo->echconfiglist) + free(dns->hinfo->echconfiglist); + if(dns->hinfo->ipv6hints) + free(dns->hinfo->ipv6hints); + if(dns->hinfo->val) + free(dns->hinfo->val); + free(dns->hinfo); + } +#endif free(dns); } } /* - * Curl_mk_dnscache() inits a new DNS cache and returns success/failure. + * Curl_init_dnscache() inits a new DNS cache. */ -int Curl_mk_dnscache(struct curl_hash *hash) +void Curl_init_dnscache(struct Curl_hash *hash, size_t size) { - return Curl_hash_init(hash, 7, Curl_hash_str, Curl_str_key_compare, - freednsentry); + Curl_hash_init(hash, size, Curl_hash_str, Curl_str_key_compare, + hostcache_unlink_entry); } /* @@ -762,7 +1111,7 @@ int Curl_mk_dnscache(struct curl_hash *hash) */ void Curl_hostcache_clean(struct Curl_easy *data, - struct curl_hash *hash) + struct Curl_hash *hash) { if(data && data->share) Curl_share_lock(data, CURL_LOCK_DATA_DNS, CURL_LOCK_ACCESS_SINGLE); @@ -777,108 +1126,361 @@ void Curl_hostcache_clean(struct Curl_easy *data, CURLcode Curl_loadhostpairs(struct Curl_easy *data) { struct curl_slist *hostp; - char hostname[256]; - char address[256]; - int port; + char *host_end; + + /* Default is no wildcard found */ + data->state.wildcard_resolve = false; - for(hostp = data->change.resolve; hostp; hostp = hostp->next) { + for(hostp = data->state.resolve; hostp; hostp = hostp->next) { + char entry_id[MAX_HOSTCACHE_LEN]; if(!hostp->data) continue; if(hostp->data[0] == '-') { - char *entry_id; + unsigned long num = 0; size_t entry_len; - - if(2 != sscanf(hostp->data + 1, "%255[^:]:%d", hostname, &port)) { - infof(data, "Couldn't parse CURLOPT_RESOLVE removal entry '%s'!\n", + 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_id = create_hostcache_id(hostname, port); - /* If we can't create the entry id, fail */ - if(!entry_id) { - return CURLE_OUT_OF_MEMORY; - } - - entry_len = strlen(entry_id); - + 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); + /* delete entry, ignore if it did not exist */ + Curl_hash_delete(data->dns.hostcache, entry_id, entry_len + 1); if(data->share) Curl_share_unlock(data, CURL_LOCK_DATA_DNS); - - /* free the allocated entry_id again */ - free(entry_id); } else { struct Curl_dns_entry *dns; - Curl_addrinfo *addr; - char *entry_id; + struct Curl_addrinfo *head = NULL, *tail = NULL; size_t entry_len; - - if(3 != sscanf(hostp->data, "%255[^:]:%d:%255s", hostname, &port, - address)) { - infof(data, "Couldn't parse CURLOPT_RESOLVE entry '%s'!\n", - hostp->data); - continue; + 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 - addr = Curl_str2addr(address, port); - if(!addr) { - infof(data, "Address in '%s' found illegal!\n", hostp->data); - continue; + 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 USE_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; + } } - /* Create an entry id, based upon the hostname and port */ - entry_id = create_hostcache_id(hostname, port); - /* If we can't create the entry id, fail */ - if(!entry_id) { - Curl_freeaddrinfo(addr); - return CURLE_OUT_OF_MEMORY; + 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; } - entry_len = strlen(entry_id); + /* 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 its already in our dns cache */ - dns = Curl_hash_pick(data->dns.hostcache, entry_id, entry_len+1); - - /* free the allocated entry_id again */ - free(entry_id); + /* See if it is 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); + } - if(!dns) { - /* if not in the cache already, put this host in the cache */ - dns = Curl_cache_addr(data, addr, hostname, port); - if(dns) { - dns->timestamp = 0; /* mark as added by CURLOPT_RESOLVE */ - /* release the returned reference; the cache itself will keep the - * entry alive: */ - dns->inuse--; - } + /* put this new host in the cache */ + dns = Curl_cache_addr(data, head, host_begin, hlen, port, permanent); + if(dns) { + /* release the returned reference; the cache itself will keep the + * entry alive: */ + dns->refcount--; } - else - /* this is a duplicate, free it again */ - Curl_freeaddrinfo(addr); if(data->share) Curl_share_unlock(data, CURL_LOCK_DATA_DNS); if(!dns) { - Curl_freeaddrinfo(addr); + Curl_freeaddrinfo(head); return CURLE_OUT_OF_MEMORY; } - infof(data, "Added %s:%d:%s to DNS cache\n", - hostname, port, address); +#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->change.resolve = NULL; /* dealt with now */ + 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, cannot 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(data->state.async.dns) { + conn->dns_entry = data->state.async.dns; + data->state.async.dns = NULL; + } +#endif + + result = Curl_setup_conn(data, protocol_done); + + if(result) { + Curl_detach_connection(data); + Curl_cpool_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) +{ + const char *host_or_proxy; + CURLcode result; + +#ifndef CURL_DISABLE_PROXY + struct connectdata *conn = data->conn; + 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, + data->state.async.hostname); + + return result; +} +#endif /* USE_CURL_ASYNC */ diff --git a/contrib/curl/lib/hostip.h b/contrib/curl/lib/hostip.h index 1dc4079fef..b1c5ecb2e1 100644 --- a/contrib/curl/lib/hostip.h +++ b/contrib/curl/lib/hostip.h @@ -7,11 +7,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2016, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -20,20 +20,20 @@ * 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" -#ifdef HAVE_SETJMP_H #include -#endif -#ifdef NETWARE -#undef in_addr_t -#define in_addr_t unsigned long +#ifdef USE_HTTPSRR +# include #endif /* Allocate enough memory to hold the full name information structs and @@ -58,51 +58,95 @@ struct connectdata; * 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. + * Returns a struct Curl_hash pointer on success, NULL on failure. */ -struct curl_hash *Curl_global_host_cache_init(void); -void Curl_global_host_cache_dtor(void); +struct Curl_hash *Curl_global_host_cache_init(void); + +#ifdef USE_HTTPSRR + +#define CURL_MAXLEN_host_name 253 + +struct Curl_https_rrinfo { + size_t len; /* raw encoded length */ + unsigned char *val; /* raw encoded octets */ + /* + * fields from HTTPS RR, with the mandatory fields + * first (priority, target), then the others in the + * order of the keytag numbers defined at + * https://datatracker.ietf.org/doc/html/rfc9460#section-14.3.2 + */ + uint16_t priority; + char *target; + char *alpns; /* keytag = 1 */ + bool no_def_alpn; /* keytag = 2 */ + /* + * we do not support ports (keytag = 3) as we do not support + * port-switching yet + */ + unsigned char *ipv4hints; /* keytag = 4 */ + size_t ipv4hints_len; + unsigned char *echconfiglist; /* keytag = 5 */ + size_t echconfiglist_len; + unsigned char *ipv6hints; /* keytag = 6 */ + size_t ipv6hints_len; +}; +#endif struct Curl_dns_entry { - Curl_addrinfo *addr; - /* timestamp == 0 -- CURLOPT_RESOLVE entry, doesn't timeout */ + struct Curl_addrinfo *addr; +#ifdef USE_HTTPSRR + struct Curl_https_rrinfo *hinfo; +#endif + /* timestamp == 0 -- permanent CURLOPT_RESOLVE entry (does not time out) */ time_t timestamp; - /* use-counter, use Curl_resolv_unlock to release reference */ - long inuse; + /* reference counter, entry is freed on reaching 0 */ + size_t refcount; + /* 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! + * The returned data *MUST* be "released" with Curl_resolv_unlink() after + * use, or we will leak memory! */ /* return codes */ -#define CURLRESOLV_TIMEDOUT -2 -#define CURLRESOLV_ERROR -1 -#define CURLRESOLV_RESOLVED 0 -#define CURLRESOLV_PENDING 1 -int Curl_resolv(struct connectdata *conn, const char *hostname, - int port, struct Curl_dns_entry **dnsentry); -int Curl_resolv_timeout(struct connectdata *conn, const char *hostname, - int port, struct Curl_dns_entry **dnsentry, - time_t timeoutms); - -#ifdef CURLRES_IPV6 +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 USE_IPV6 /* * Curl_ipv6works() returns TRUE if IPv6 seems to work. */ -bool Curl_ipv6works(void); +bool Curl_ipv6works(struct Curl_easy *data); #else -#define Curl_ipv6works() FALSE +#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 connectdata *conn); +bool Curl_ipvalid(struct Curl_easy *data, struct connectdata *conn); /* @@ -111,46 +155,26 @@ bool Curl_ipvalid(struct connectdata *conn); * name resolve layers (selected at build-time). They all take this same set * of arguments */ -Curl_addrinfo *Curl_getaddrinfo(struct connectdata *conn, - const char *hostname, - int port, - int *waitp); - +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); -/* for debugging purposes only: */ -void Curl_scan_cache_used(void *user, void *ptr); +/* unlink a dns entry, potentially shared with a cache */ +void Curl_resolv_unlink(struct Curl_easy *data, + struct Curl_dns_entry **pdns); -/* init a new dns cache and return success */ -int Curl_mk_dnscache(struct curl_hash *hash); +/* init a new dns cache */ +void Curl_init_dnscache(struct Curl_hash *hash, size_t hashsize); /* prune old entries from the DNS cache */ void Curl_hostcache_prune(struct Curl_easy *data); -/* Return # of adresses in a Curl_addrinfo struct */ -int Curl_num_addresses(const Curl_addrinfo *addr); - -#if defined(CURLDEBUG) && defined(HAVE_GETNAMEINFO) -int curl_dogetnameinfo(GETNAMEINFO_QUAL_ARG1 GETNAMEINFO_TYPE_ARG1 sa, - GETNAMEINFO_TYPE_ARG2 salen, - char *host, GETNAMEINFO_TYPE_ARG46 hostlen, - char *serv, GETNAMEINFO_TYPE_ARG46 servlen, - GETNAMEINFO_TYPE_ARG7 flags, - int line, const char *source); -#endif - /* IPv4 threadsafe resolve function used for synch and asynch builds */ -Curl_addrinfo *Curl_ipv4_resolve_r(const char *hostname, int port); +struct Curl_addrinfo *Curl_ipv4_resolve_r(const char *hostname, int port); -CURLcode Curl_async_resolved(struct connectdata *conn, - bool *protocol_connect); - -#ifndef CURLRES_ASYNCH -#define Curl_async_resolved(x,y) CURLE_OK -#endif +CURLcode Curl_once_resolved(struct Curl_easy *data, bool *protocol_connect); /* * Curl_addrinfo_callback() is used when we build with any asynch specialty. @@ -158,38 +182,40 @@ CURLcode Curl_async_resolved(struct connectdata *conn, * status is CURL_ASYNC_SUCCESS. Twiddles fields in conn to indicate async * request completed whether successful or failed. */ -CURLcode Curl_addrinfo_callback(struct connectdata *conn, +CURLcode Curl_addrinfo_callback(struct Curl_easy *data, int status, - Curl_addrinfo *ai); + 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. */ -const char *Curl_printable_address(const Curl_addrinfo *ip, - char *buf, size_t bufsize); +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! + * The returned data *MUST* be "released" with Curl_resolv_unlink() after + * use, or we will leak memory! */ struct Curl_dns_entry * -Curl_fetch_addr(struct connectdata *conn, +Curl_fetch_addr(struct Curl_easy *data, const char *hostname, int port); + /* * Curl_cache_addr() stores a 'Curl_addrinfo' struct in the DNS cache. - * + * @param permanent iff TRUE, entry will never become stale * Returns the Curl_dns_entry entry pointer or NULL if the storage failed. */ struct Curl_dns_entry * -Curl_cache_addr(struct Curl_easy *data, Curl_addrinfo *addr, - const char *hostname, int port); +Curl_cache_addr(struct Curl_easy *data, struct Curl_addrinfo *addr, + const char *hostname, size_t hostlen, int port, + bool permanent); #ifndef INADDR_NONE #define CURL_INADDR_NONE (in_addr_t) ~0 @@ -197,15 +223,6 @@ Curl_cache_addr(struct Curl_easy *data, Curl_addrinfo *addr, #define CURL_INADDR_NONE INADDR_NONE #endif -#ifdef HAVE_SIGSETJMP -/* Forward-declaration of variable defined in hostip.c. 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. - */ -extern sigjmp_buf curl_jmpenv; -#endif - /* * Function provided by the resolver backend to set DNS servers to use. */ @@ -235,16 +252,16 @@ CURLcode Curl_set_dns_local_ip6(struct Curl_easy *data, /* * Clean off entries from the cache */ -void Curl_hostcache_clean(struct Curl_easy *data, struct curl_hash *hash); - -/* - * Destroy the hostcache of this handle. - */ -void Curl_hostcache_destroy(struct Curl_easy *data); +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/contrib/curl/lib/hostip4.c b/contrib/curl/lib/hostip4.c index e459328ac3..3bfea48d4f 100644 --- a/contrib/curl/lib/hostip4.c +++ b/contrib/curl/lib/hostip4.c @@ -5,11 +5,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2016, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -18,10 +18,17 @@ * 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 #endif @@ -36,35 +43,26 @@ #include #endif -#ifdef HAVE_PROCESS_H -#include -#endif - #include "urldata.h" #include "sendf.h" #include "hostip.h" #include "hash.h" #include "share.h" -#include "strerror.h" #include "url.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" -/*********************************************************************** - * Only for plain IPv4 builds - **********************************************************************/ -#ifdef CURLRES_IPV4 /* plain IPv4 code coming up */ /* * Curl_ipvalid() checks what CURL_IPRESOLVE_* requirements that might've * been set and returns TRUE if they are OK. */ -bool Curl_ipvalid(struct connectdata *conn) +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 */ + /* An IPv6 address was requested and we cannot get/use one */ return FALSE; return TRUE; /* OK, proceed */ @@ -84,33 +82,34 @@ bool Curl_ipvalid(struct connectdata *conn) * 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 + * has the corresponding rules. This is primarily on *nix. Note that some Unix * flavours have thread-safe versions of the plain gethostbyname() etc. * */ -Curl_addrinfo *Curl_getaddrinfo(struct connectdata *conn, - const char *hostname, - int port, - int *waitp) +struct Curl_addrinfo *Curl_getaddrinfo(struct Curl_easy *data, + const char *hostname, + int port, + int *waitp) { - Curl_addrinfo *ai = NULL; + struct Curl_addrinfo *ai = NULL; #ifdef CURL_DISABLE_VERBOSE_STRINGS - (void)conn; + (void)data; #endif *waitp = 0; /* synchronous response only */ ai = Curl_ipv4_resolve_r(hostname, port); if(!ai) - infof(conn->data, "Curl_ipv4_resolve_r failed for %s\n", hostname); + 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) +#if defined(CURLRES_IPV4) && \ + !defined(CURLRES_ARES) && !defined(CURLRES_AMIGA) /* * Curl_ipv4_resolve_r() - ipv4 threadsafe resolver function. @@ -119,36 +118,31 @@ Curl_addrinfo *Curl_getaddrinfo(struct connectdata *conn, * implying that only threadsafe code and function calls may be used. * */ -Curl_addrinfo *Curl_ipv4_resolve_r(const char *hostname, - int port) +struct Curl_addrinfo *Curl_ipv4_resolve_r(const char *hostname, + int port) { -#if !defined(HAVE_GETADDRINFO_THREADSAFE) && defined(HAVE_GETHOSTBYNAME_R_3) +#if !(defined(HAVE_GETADDRINFO) && defined(HAVE_GETADDRINFO_THREADSAFE)) && \ + defined(HAVE_GETHOSTBYNAME_R_3) int res; #endif - Curl_addrinfo *ai = NULL; + struct Curl_addrinfo *ai = NULL; struct hostent *h = NULL; - struct in_addr in; struct hostent *buf = NULL; - if(Curl_inet_pton(AF_INET, hostname, &in) > 0) - /* This is a dotted IP address 123.123.123.123-style */ - return Curl_ip2addr(AF_INET, &in, hostname, port); - -#if defined(HAVE_GETADDRINFO_THREADSAFE) - else { - 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) { - snprintf(sbuf, sizeof(sbuf), "%d", port); - sbufptr = sbuf; - } +#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); + (void)Curl_getaddrinfo_ex(hostname, sbufptr, &hints, &ai); #elif defined(HAVE_GETHOSTBYNAME_R) /* @@ -156,144 +150,143 @@ Curl_addrinfo *Curl_ipv4_resolve_r(const char *hostname, * Since there are three different versions of it, the following code is * somewhat #ifdef-ridden. */ - else { - 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. - */ + 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. - */ + /* 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 + 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. - */ + /* 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 would not be such a big problem if older versions would not + * sometimes return EAGAIN on a common failure case. Alas, we cannot + * 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 cannot + * explain, is that tests performed on glibc 2.2.4-34 and 2.2.4-32 + * (shipped/upgraded by Redhat 7.2) do not 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 */ + 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. + /* AIX, Digital UNIX/Tru64, HP-UX 10, more? */ + + /* For AIX 4.3 or later, we do not 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 have found out that it is not 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. */ - 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_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. + res = gethostbyname_r(hostname, + (struct hostent *)buf, + (struct hostent_data *)((char *)buf + + sizeof(struct hostent))); + h_errnop = SOCKERRNO; /* we do not 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 do not know how big buffer this particular lookup required, + * we cannot 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 { - h = gethostbyname((void *)hostname); -#endif /* HAVE_GETADDRINFO_THREADSAFE || HAVE_GETHOSTBYNAME_R */ } + 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 do not 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); @@ -304,4 +297,5 @@ Curl_addrinfo *Curl_ipv4_resolve_r(const char *hostname, return ai; } -#endif /* defined(CURLRES_IPV4) && !defined(CURLRES_ARES) */ +#endif /* defined(CURLRES_IPV4) && !defined(CURLRES_ARES) && + !defined(CURLRES_AMIGA) */ diff --git a/contrib/curl/lib/hostip6.c b/contrib/curl/lib/hostip6.c index 4ebfc2dcfa..c16ddfe58d 100644 --- a/contrib/curl/lib/hostip6.c +++ b/contrib/curl/lib/hostip6.c @@ -5,11 +5,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2016, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -18,10 +18,17 @@ * 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 #endif @@ -36,16 +43,11 @@ #include #endif -#ifdef HAVE_PROCESS_H -#include -#endif - #include "urldata.h" #include "sendf.h" #include "hostip.h" #include "hash.h" #include "share.h" -#include "strerror.h" #include "url.h" #include "inet_pton.h" #include "connect.h" @@ -54,75 +56,14 @@ #include "curl_memory.h" #include "memdebug.h" -/*********************************************************************** - * Only for IPv6-enabled builds - **********************************************************************/ -#ifdef CURLRES_IPV6 - -#if defined(CURLDEBUG) && defined(HAVE_GETNAMEINFO) -/* These are 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 - */ - -/* - * For CURLRES_ARS, this should be written using ares_gethostbyaddr() - * (ignoring the fact c-ares doesn't return 'serv'). - */ - -int curl_dogetnameinfo(GETNAMEINFO_QUAL_ARG1 GETNAMEINFO_TYPE_ARG1 sa, - GETNAMEINFO_TYPE_ARG2 salen, - char *host, GETNAMEINFO_TYPE_ARG46 hostlen, - char *serv, GETNAMEINFO_TYPE_ARG46 servlen, - GETNAMEINFO_TYPE_ARG7 flags, - int line, const char *source) -{ - int res = (getnameinfo)(sa, salen, - host, hostlen, - serv, servlen, - flags); - if(0 == res) - /* success */ - curl_memlog("GETNAME %s:%d getnameinfo()\n", - source, line); - else - curl_memlog("GETNAME %s:%d getnameinfo() failed = %d\n", - source, line, res); - return res; -} -#endif /* defined(CURLDEBUG) && defined(HAVE_GETNAMEINFO) */ - -/* - * Curl_ipv6works() returns TRUE if IPv6 seems to work. - */ -bool Curl_ipv6works(void) -{ - /* 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 re-use */ - static int ipv6_works = -1; - if(-1 == ipv6_works) { - /* 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; - Curl_closesocket(NULL, s); - } - } - return (ipv6_works>0)?TRUE:FALSE; -} - /* * Curl_ipvalid() checks what CURL_IPRESOLVE_* requirements that might've * been set and returns TRUE if they are OK. */ -bool Curl_ipvalid(struct connectdata *conn) +bool Curl_ipvalid(struct Curl_easy *data, struct connectdata *conn) { if(conn->ip_version == CURL_IPRESOLVE_V6) - return Curl_ipv6works(); + return Curl_ipv6works(data); return TRUE; } @@ -130,22 +71,19 @@ bool Curl_ipvalid(struct connectdata *conn) #if defined(CURLRES_SYNCH) #ifdef DEBUG_ADDRINFO -static void dump_addrinfo(struct connectdata *conn, const Curl_addrinfo *ai) +static void dump_addrinfo(const struct Curl_addrinfo *ai) { printf("dump_addrinfo:\n"); for(; ai; ai = ai->ai_next) { - char buf[INET6_ADDRSTRLEN]; - + char buf[INET6_ADDRSTRLEN]; printf(" fam %2d, CNAME %s, ", ai->ai_family, ai->ai_canonname ? ai->ai_canonname : ""); - if(Curl_printable_address(ai, buf, sizeof(buf))) - printf("%s\n", buf); - else - printf("failed; %s\n", Curl_strerror(conn, SOCKERRNO)); + Curl_printable_address(ai, buf, sizeof(buf)); + printf("%s\n", buf); } } #else -#define dump_addrinfo(x,y) Curl_nop_stmt +#define dump_addrinfo(x) Curl_nop_stmt #endif /* @@ -153,55 +91,40 @@ static void dump_addrinfo(struct connectdata *conn, const Curl_addrinfo *ai) * non-ares version). * * Returns name information about the given hostname and port number. If - * successful, the 'addrinfo' is returned and the forth argument will point to - * memory we need to free after use. That memory *MUST* be freed with + * 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. */ -Curl_addrinfo *Curl_getaddrinfo(struct connectdata *conn, - const char *hostname, - int port, - int *waitp) +struct Curl_addrinfo *Curl_getaddrinfo(struct Curl_easy *data, + const char *hostname, + int port, + int *waitp) { struct addrinfo hints; - Curl_addrinfo *res; + struct Curl_addrinfo *res; int error; char sbuf[12]; char *sbufptr = NULL; #ifndef USE_RESOLVE_ON_IPS char addrbuf[128]; #endif - int pf; -#if !defined(CURL_DISABLE_VERBOSE_STRINGS) - struct Curl_easy *data = conn->data; -#endif + int pf = PF_INET; *waitp = 0; /* synchronous response only */ - /* Check if a limited name resolve has been requested */ - switch(conn->ip_version) { - case CURL_IPRESOLVE_V4: - pf = PF_INET; - break; - case CURL_IPRESOLVE_V6: - pf = PF_INET6; - break; - default: + if((data->conn->ip_version != CURL_IPRESOLVE_V4) && Curl_ipv6works(data)) + /* The stack seems to be IPv6-enabled */ pf = PF_UNSPEC; - break; - } - - if((pf != PF_INET) && !Curl_ipv6works()) - /* The stack seems to be a non-IPv6 one */ - pf = PF_INET; memset(&hints, 0, sizeof(hints)); hints.ai_family = pf; - hints.ai_socktype = conn->socktype; + 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. + * an IPv4 address on iOS and macOS. */ if((1 == Curl_inet_pton(AF_INET, hostname, addrbuf)) || (1 == Curl_inet_pton(AF_INET6, hostname, addrbuf))) { @@ -211,13 +134,13 @@ Curl_addrinfo *Curl_getaddrinfo(struct connectdata *conn, #endif if(port) { - snprintf(sbuf, sizeof(sbuf), "%d", port); - sbufptr=sbuf; + 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\n", hostname, port); + infof(data, "getaddrinfo(3) failed for %s:%d", hostname, port); return NULL; } @@ -225,7 +148,7 @@ Curl_addrinfo *Curl_getaddrinfo(struct connectdata *conn, Curl_addrinfo_set_port(res, port); } - dump_addrinfo(conn, res); + dump_addrinfo(res); return res; } diff --git a/contrib/curl/lib/hostsyn.c b/contrib/curl/lib/hostsyn.c index 1a95263c62..ca8b0758c4 100644 --- a/contrib/curl/lib/hostsyn.c +++ b/contrib/curl/lib/hostsyn.c @@ -5,11 +5,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2015, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -18,10 +18,17 @@ * 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 #endif @@ -36,26 +43,16 @@ #include #endif -#ifdef HAVE_PROCESS_H -#include -#endif - #include "urldata.h" #include "sendf.h" #include "hostip.h" #include "hash.h" #include "share.h" -#include "strerror.h" #include "url.h" #include "curl_memory.h" /* The last #include file should be: */ #include "memdebug.h" -/*********************************************************************** - * Only for builds using synchronous name resolves - **********************************************************************/ -#ifdef CURLRES_SYNCH - /* * Function provided by the resolver backend to set DNS servers to use. */ diff --git a/contrib/curl/lib/hsts.c b/contrib/curl/lib/hsts.c new file mode 100644 index 0000000000..a5c216f6de --- /dev/null +++ b/contrib/curl/lib/hsts.c @@ -0,0 +1,576 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ +/* + * 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 +#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" + +#if defined(DEBUGBUILD) || defined(UNITTESTS) +/* 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_node *e; + struct Curl_llist_node *n; + for(e = Curl_llist_head(&h->list); e; e = n) { + struct stsentry *sts = Curl_node_elem(e); + n = Curl_node_next(e); + hsts_free(sts); + } + free(h->filename); + free(h); + *hp = NULL; + } +} + +static CURLcode hsts_create(struct hsts *h, + const char *hostname, + bool subdomains, + curl_off_t expires) +{ + size_t hlen; + DEBUGASSERT(h); + DEBUGASSERT(hostname); + + hlen = strlen(hostname); + if(hlen && (hostname[hlen - 1] == '.')) + /* strip off any trailing dot */ + --hlen; + if(hlen) { + char *duphost; + struct stsentry *sts = calloc(1, sizeof(struct stsentry)); + if(!sts) + return CURLE_OUT_OF_MEMORY; + + duphost = Curl_memdup0(hostname, hlen); + if(!duphost) { + free(sts); + return CURLE_OUT_OF_MEMORY; + } + + sts->host = duphost; + sts->expires = expires; + sts->includeSubDomains = subdomains; + Curl_llist_append(&h->list, 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_node_remove(&sts->node); + 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 hostname 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_node *e; + struct Curl_llist_node *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 = Curl_llist_head(&h->list); e; e = n) { + struct stsentry *sts = Curl_node_elem(e); + n = Curl_node_next(e); + if(sts->expires <= now) { + /* remove expired entries */ + Curl_node_remove(&sts->node); + 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_node *e; + struct Curl_llist_node *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 filename */ + 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 = Curl_llist_head(&h->list); e; e = n) { + struct stsentry *sts = Curl_node_elem(e); + n = Curl_node_next(e); + 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 is a write callback */ + struct curl_index i; /* count */ + i.total = Curl_llist_count(&h->list); + i.index = 0; + for(e = Curl_llist_head(&h->list); e; e = n) { + struct stsentry *sts = Curl_node_elem(e); + bool stop; + n = Curl_node_next(e); + 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 hostname, 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; + DEBUGASSERT(e.name[0]); + 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; + FILE *fp; + + /* we need a private copy of the filename 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) { + struct dynbuf buf; + Curl_dyn_init(&buf, MAX_HSTS_LINE); + while(Curl_get_line(&buf, fp)) { + char *lineptr = Curl_dyn_ptr(&buf); + while(*lineptr && ISBLANK(*lineptr)) + lineptr++; + /* + * Skip empty or commented lines, since we know the line will have a + * trailing newline from Curl_get_line we can treat length 1 as empty. + */ + if((*lineptr == '#') || strlen(lineptr) <= 1) + continue; + + hsts_add(h, lineptr); + } + Curl_dyn_free(&buf); /* free the line buffer */ + fclose(fp); + } + return result; +} + +/* + * 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/contrib/curl/lib/hsts.h b/contrib/curl/lib/hsts.h new file mode 100644 index 0000000000..1c544f97bd --- /dev/null +++ b/contrib/curl/lib/hsts.h @@ -0,0 +1,69 @@ +#ifndef HEADER_CURL_HSTS_H +#define HEADER_CURL_HSTS_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ +#include "curl_setup.h" + +#if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_HSTS) +#include +#include "llist.h" + +#if defined(DEBUGBUILD) || defined(UNITTESTS) +extern time_t deltatime; +#endif + +struct stsentry { + struct Curl_llist_node 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 hostnames. */ +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/contrib/curl/lib/http.c b/contrib/curl/lib/http.c index 8db86cd84d..b0a397915b 100644 --- a/contrib/curl/lib/http.c +++ b/contrib/curl/lib/http.c @@ -5,11 +5,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2016, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -18,6 +18,8 @@ * 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" @@ -45,24 +47,32 @@ #include #endif +#ifdef USE_HYPER +#include +#endif + #include "urldata.h" #include #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 "headers.h" #include "select.h" #include "parsedate.h" /* for the week day and month names */ #include "strtoofft.h" @@ -71,12 +81,15 @@ #include "content_encoding.h" #include "http_proxy.h" #include "warnless.h" -#include "non-ascii.h" -#include "conncache.h" -#include "pipeline.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" @@ -87,25 +100,16 @@ * Forward declarations. */ -static int http_getsock_do(struct connectdata *conn, - curl_socket_t *socks, - int numsocks); -static int http_should_fail(struct connectdata *conn); - -#ifdef USE_SSL -static CURLcode https_connecting(struct connectdata *conn, bool *done); -static int https_getsock(struct connectdata *conn, - curl_socket_t *socks, - int numsocks); -#else -#define https_connecting(x,y) CURLE_COULDNT_CONNECT -#endif +static bool http_should_fail(struct Curl_easy *data, int httpcode); +static bool http_exp100_is_waiting(struct Curl_easy *data); +static CURLcode http_exp100_add_reader(struct Curl_easy *data); +static void http_exp100_send_anyway(struct Curl_easy *data); /* * HTTP handler interface. */ const struct Curl_handler Curl_handler_http = { - "HTTP", /* scheme */ + "http", /* scheme */ Curl_http_setup_conn, /* setup_connection */ Curl_http, /* do_it */ Curl_http_done, /* done */ @@ -114,14 +118,19 @@ const struct Curl_handler Curl_handler_http = { ZERO_NULL, /* connecting */ ZERO_NULL, /* doing */ ZERO_NULL, /* proto_getsock */ - http_getsock_do, /* doing_getsock */ + Curl_http_getsock_do, /* doing_getsock */ ZERO_NULL, /* domore_getsock */ ZERO_NULL, /* perform_getsock */ ZERO_NULL, /* disconnect */ - ZERO_NULL, /* readwrite */ + Curl_http_write_resp, /* write_resp */ + Curl_http_write_resp_hd, /* write_resp_hd */ + ZERO_NULL, /* connection_check */ + ZERO_NULL, /* attach connection */ PORT_HTTP, /* defport */ CURLPROTO_HTTP, /* protocol */ - PROTOPT_CREDSPERREQUEST /* flags */ + CURLPROTO_HTTP, /* family */ + PROTOPT_CREDSPERREQUEST | /* flags */ + PROTOPT_USERPWDCTRL }; #ifdef USE_SSL @@ -129,91 +138,79 @@ const struct Curl_handler Curl_handler_http = { * HTTPS handler interface. */ const struct Curl_handler Curl_handler_https = { - "HTTPS", /* scheme */ + "https", /* scheme */ Curl_http_setup_conn, /* setup_connection */ Curl_http, /* do_it */ Curl_http_done, /* done */ ZERO_NULL, /* do_more */ Curl_http_connect, /* connect_it */ - https_connecting, /* connecting */ + NULL, /* connecting */ ZERO_NULL, /* doing */ - https_getsock, /* proto_getsock */ - http_getsock_do, /* doing_getsock */ + NULL, /* proto_getsock */ + Curl_http_getsock_do, /* doing_getsock */ ZERO_NULL, /* domore_getsock */ ZERO_NULL, /* perform_getsock */ ZERO_NULL, /* disconnect */ - ZERO_NULL, /* readwrite */ + Curl_http_write_resp, /* write_resp */ + Curl_http_write_resp_hd, /* write_resp_hd */ + ZERO_NULL, /* connection_check */ + ZERO_NULL, /* attach connection */ PORT_HTTPS, /* defport */ CURLPROTO_HTTPS, /* protocol */ - PROTOPT_SSL | PROTOPT_CREDSPERREQUEST | PROTOPT_ALPN_NPN /* flags */ + CURLPROTO_HTTP, /* family */ + PROTOPT_SSL | PROTOPT_CREDSPERREQUEST | PROTOPT_ALPN | /* flags */ + PROTOPT_USERPWDCTRL }; + #endif -CURLcode Curl_http_setup_conn(struct connectdata *conn) +CURLcode Curl_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(conn->data->req.protop == NULL); - - http = calloc(1, sizeof(struct HTTP)); - if(!http) - return CURLE_OUT_OF_MEMORY; - - conn->data->req.protop = http; - - Curl_http2_setup_conn(conn); - Curl_http2_setup_req(conn->data); - - return CURLE_OK; -} - -/* - * checkheaders() checks the linked list of custom HTTP headers for a - * particular header (prefix). - * - * Returns a pointer to the first matching header or NULL if none matched. - */ -char *Curl_checkheaders(const struct connectdata *conn, - const char *thisheader) -{ - struct curl_slist *head; - size_t thislen = strlen(thisheader); - struct Curl_easy *data = conn->data; + connkeep(conn, "HTTP default"); - for(head = data->set.headers;head; head=head->next) { - if(strncasecompare(head->data, thisheader, thislen)) - return head->data; + if(data->state.httpwant == CURL_HTTP_VERSION_3ONLY) { + CURLcode result = Curl_conn_may_http3(data, conn); + if(result) + return result; } - return NULL; + return CURLE_OK; } +#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 instead of the Curl_easy simply - * to know if this is a proxy request or not, as it then might check a - * different header 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(const struct connectdata *conn, - const char *thisheader) +char *Curl_checkProxyheaders(struct Curl_easy *data, + const struct connectdata *conn, + const char *thisheader, + const size_t thislen) { struct curl_slist *head; - size_t thislen = strlen(thisheader); - struct Curl_easy *data = conn->data; 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)) + 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 @@ -225,11 +222,8 @@ char *Curl_copy_header_value(const char *header) { const char *start; const char *end; - char *value; size_t len; - DEBUGASSERT(header); - /* Find the end of the header name */ while(*header && (*header != ':')) ++header; @@ -243,8 +237,6 @@ char *Curl_copy_header_value(const char *header) while(*start && ISSPACE(*start)) start++; - /* data is in the host encoding so - use '\r' and '\n' instead of 0x0d and 0x0a */ end = strchr(start, '\r'); if(!end) end = strchr(start, '\n'); @@ -260,90 +252,139 @@ char *Curl_copy_header_value(const char *header) /* get length of the type */ len = end - start + 1; - value = malloc(len + 1); - if(!value) - return NULL; - - memcpy(value, start, len); - value[len] = 0; /* zero terminate */ - - return value; + return Curl_memdup0(start, len); } +#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 connectdata *conn, bool proxy) +static CURLcode http_output_basic(struct Curl_easy *data, bool proxy) { size_t size = 0; char *authorization = NULL; - struct Curl_easy *data = conn->data; 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) { - userp = &conn->allocptr.proxyuserpwd; - user = conn->http_proxy.user; - pwd = conn->http_proxy.passwd; +#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 = &conn->allocptr.userpwd; - user = conn->user; - pwd = conn->passwd; + userp = &data->state.aptr.userpwd; + user = data->state.aptr.user; + pwd = data->state.aptr.passwd; } - snprintf(data->state.buffer, CURL_BUFSIZE(data->set.buffer_size), - "%s:%s", user, pwd); + out = aprintf("%s:%s", user ? user : "", pwd ? pwd : ""); + if(!out) + return CURLE_OUT_OF_MEMORY; - result = Curl_base64_encode(data, - data->state.buffer, strlen(data->state.buffer), - &authorization, &size); + result = Curl_base64_encode(out, strlen(out), &authorization, &size); if(result) - return result; + goto fail; - if(!authorization) - return CURLE_REMOTE_ACCESS_DENIED; + 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) - return CURLE_OUT_OF_MEMORY; + if(!*userp) { + result = CURLE_OUT_OF_MEMORY; + goto fail; + } - return CURLE_OK; +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) +static bool pickoneauth(struct auth *pick, unsigned long mask) { bool picked; /* only deal with authentication we want */ - unsigned long avail = pick->avail & pick->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; @@ -354,126 +395,87 @@ static bool pickoneauth(struct auth *pick) } /* - * Curl_http_perhapsrewind() + * 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 - * } - * } - * } + * The current request needs to be done again - maybe due to a follow + * or authentication negotiation. Check if: + * 1) a rewind of the data sent to the server is necessary + * 2) the current transfer should continue or be stopped early */ -static CURLcode http_perhapsrewind(struct connectdata *conn) +static CURLcode http_perhapsrewind(struct Curl_easy *data, + struct connectdata *conn) { - struct Curl_easy *data = conn->data; - struct HTTP *http = data->req.protop; - 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->set.httpreq) { - case HTTPREQ_GET: - case HTTPREQ_HEAD: - return CURLE_OK; - default: - break; - } - - bytessent = http->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->set.httpreq) { - case HTTPREQ_POST: - if(data->state.infilesize != -1) - expectsend = data->state.infilesize; - else if(data->set.postfields) - expectsend = (curl_off_t)strlen(data->set.postfields); - break; - case HTTPREQ_PUT: - if(data->state.infilesize != -1) - expectsend = data->state.infilesize; - break; - case HTTPREQ_POST_FORM: - expectsend = http->postsize; - break; - default: - break; - } + curl_off_t bytessent = data->req.writebytecount; + curl_off_t expectsend = Curl_creader_total_length(data); + curl_off_t upload_remain = (expectsend >= 0) ? (expectsend - bytessent) : -1; + bool little_upload_remains = (upload_remain >= 0 && upload_remain < 2000); + bool needs_rewind = Curl_creader_needs_rewind(data); + /* By default, we would like to abort the transfer when little or unknown + * amount remains. This may be overridden by authentications further + * below! */ + bool abort_upload = (!data->req.upload_done && !little_upload_remains); + const char *ongoing_auth = NULL; + + /* We need a rewind before uploading client read data again. The + * checks below just influence of the upload is to be continued + * or aborted early. + * This depends on how much remains to be sent and in what state + * the authentication is. Some auth schemes such as NTLM do not work + * for a new connection. */ + if(needs_rewind) { + infof(data, "Need to rewind upload for next request"); + Curl_creader_set_rewind(data, TRUE); } - conn->bits.rewindaftersend = FALSE; /* default */ + if(conn->bits.close) + /* If we already decided to close this connection, we cannot veto. */ + return CURLE_OK; - if((expectsend == -1) || (expectsend > bytessent)) { + if(abort_upload) { + /* We'd like to abort the upload - but should we? */ #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->ntlm.state != NTLMSTATE_NONE) || - (conn->proxyntlm.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->bits.rewindaftersend = TRUE; - infof(data, "Rewind stream after send\n"); - } - - return CURLE_OK; + (data->state.authhost.picked == CURLAUTH_NTLM)) { + ongoing_auth = "NTML"; + if((conn->http_ntlm_state != NTLMSTATE_NONE) || + (conn->proxy_ntlm_state != NTLMSTATE_NONE)) { + /* The NTLM-negotiation has started, keep on sending. + * Need to do further work on same connection */ + abort_upload = FALSE; + } + } +#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)) { + ongoing_auth = "NEGOTIATE"; + if((conn->http_negotiate_state != GSS_AUTHNONE) || + (conn->proxy_negotiate_state != GSS_AUTHNONE)) { + /* The NEGOTIATE-negotiation has started, keep on sending. + * Need to do further work on same connection */ + abort_upload = FALSE; } - - 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\n", - (curl_off_t)(expectsend - bytessent)); } #endif + } - /* This is not NTLM or many bytes left to send: close */ + if(abort_upload) { + if(upload_remain >= 0) + infof(data, "%s%sclose instead of sending %" FMT_OFF_T " more bytes", + ongoing_auth ? ongoing_auth : "", + ongoing_auth ? " send, " : "", + upload_remain); + else + infof(data, "%s%sclose instead of sending unknown amount " + "of more bytes", + ongoing_auth ? ongoing_auth : "", + ongoing_auth ? " send, " : ""); + /* We decided to abort the ongoing transfer */ streamclose(conn, "Mid-auth HTTP and much data left to send"); - 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 */ + /* FIXME: questionable manipulation here, can we do this differently? */ + data->req.size = 0; /* do not download any more than 0 bytes */ } - - if(bytessent) - /* we rewind now at once since if we already sent something */ - return Curl_readrewind(conn); - return CURLE_OK; } @@ -484,68 +486,77 @@ static CURLcode http_perhapsrewind(struct connectdata *conn) * picked. */ -CURLcode Curl_http_auth_act(struct connectdata *conn) +CURLcode Curl_http_auth_act(struct Curl_easy *data) { - struct Curl_easy *data = conn->data; + struct connectdata *conn = data->conn; bool pickhost = FALSE; bool pickproxy = FALSE; CURLcode result = CURLE_OK; + unsigned long authmask = ~0ul; - if(100 <= data->req.httpcode && 199 >= data->req.httpcode) + 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; + return data->set.http_fail_on_error ? CURLE_HTTP_RETURNED_ERROR : CURLE_OK; - if(conn->bits.user_passwd && + 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); + (data->req.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); + (data->req.authneg && data->req.httpcode < 300))) { + pickproxy = pickoneauth(&data->state.authproxy, + authmask & ~CURLAUTH_BEARER); if(!pickproxy) data->state.authproblem = TRUE; } +#endif if(pickhost || pickproxy) { + 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->change.url); /* clone URL */ + data->req.newurl = strdup(data->state.url); /* clone URL */ if(!data->req.newurl) return CURLE_OUT_OF_MEMORY; - - if((data->set.httpreq != HTTPREQ_GET) && - (data->set.httpreq != HTTPREQ_HEAD) && - !conn->bits.rewindaftersend) { - result = http_perhapsrewind(conn); - if(result) - return result; - } } else if((data->req.httpcode < 300) && (!data->state.authhost.done) && - conn->bits.authneg) { + data->req.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->set.httpreq != HTTPREQ_GET) && - (data->set.httpreq != HTTPREQ_HEAD)) { - data->req.newurl = strdup(data->change.url); /* clone URL */ + we did not 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(conn)) { + if(http_should_fail(data, data->req.httpcode)) { failf(data, "The requested URL returned error: %d", data->req.httpcode); result = CURLE_HTTP_RETURNED_ERROR; @@ -554,12 +565,14 @@ CURLcode Curl_http_auth_act(struct connectdata *conn) 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 connectdata *conn, +output_auth_headers(struct Curl_easy *data, + struct connectdata *conn, struct auth *authstatus, const char *request, const char *path, @@ -567,54 +580,43 @@ output_auth_headers(struct connectdata *conn, { const char *auth = NULL; CURLcode result = CURLE_OK; -#if !defined(CURL_DISABLE_VERBOSE_STRINGS) || defined(USE_SPNEGO) - struct Curl_easy *data = conn->data; -#endif -#ifdef USE_SPNEGO - struct negotiatedata *negdata = proxy ? - &data->state.proxyneg : &data->state.negotiate; -#endif + (void)conn; -#ifdef CURL_DISABLE_CRYPTO_AUTH +#ifdef CURL_DISABLE_DIGEST_AUTH (void)request; (void)path; #endif - -#ifdef USE_SPNEGO - negdata->state = GSS_AUTHNONE; - if((authstatus->picked == CURLAUTH_NEGOTIATE) && - negdata->context && !GSS_ERROR(negdata->status)) { - auth = "Negotiate"; - result = Curl_output_negotiate(conn, proxy); +#ifndef CURL_DISABLE_AWS + if(authstatus->picked == CURLAUTH_AWS_SIGV4) { + auth = "AWS_SIGV4"; + result = Curl_output_aws_sigv4(data, proxy); if(result) return result; - authstatus->done = TRUE; - negdata->state = GSS_AUTHSENT; } else #endif -#ifdef USE_NTLM - if(authstatus->picked == CURLAUTH_NTLM) { - auth = "NTLM"; - result = Curl_output_ntlm(conn, proxy); +#ifdef USE_SPNEGO + if(authstatus->picked == CURLAUTH_NEGOTIATE) { + auth = "Negotiate"; + result = Curl_output_negotiate(data, conn, 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(conn, proxy); +#ifdef USE_NTLM + if(authstatus->picked == CURLAUTH_NTLM) { + auth = "NTLM"; + result = Curl_output_ntlm(data, proxy); if(result) return result; } else #endif -#ifndef CURL_DISABLE_CRYPTO_AUTH +#ifndef CURL_DISABLE_DIGEST_AUTH if(authstatus->picked == CURLAUTH_DIGEST) { auth = "Digest"; - result = Curl_output_digest(conn, + result = Curl_output_digest(data, proxy, (const unsigned char *)request, (const unsigned char *)path); @@ -623,14 +625,34 @@ output_auth_headers(struct connectdata *conn, } else #endif +#ifndef CURL_DISABLE_BASIC_AUTH if(authstatus->picked == CURLAUTH_BASIC) { /* Basic */ - if((proxy && conn->bits.proxy_user_passwd && - !Curl_checkProxyheaders(conn, "Proxy-authorization:")) || - (!proxy && conn->bits.user_passwd && - !Curl_checkheaders(conn, "Authorization:"))) { + 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(conn, proxy); + 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; } @@ -639,43 +661,54 @@ output_auth_headers(struct connectdata *conn, functions work that way */ authstatus->done = TRUE; } +#endif if(auth) { - infof(data, "%s auth using %s with user '%s'\n", +#ifndef CURL_DISABLE_PROXY + infof(data, "%s auth using %s with user '%s'", proxy ? "Proxy" : "Server", auth, - proxy ? (conn->http_proxy.user ? conn->http_proxy.user : "") : - (conn->user ? conn->user : "")); - authstatus->multi = (!authstatus->done) ? TRUE : FALSE; + 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->multi = FALSE; + authstatus->multipass = FALSE; - return CURLE_OK; + return result; } /** * Curl_http_output_auth() setups the authentication headers for the * host/proxy and the correct authentication - * method. conn->data->state.authdone is set to TRUE when authentication is + * 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 + * @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 connectdata *conn, +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 Curl_easy *data = conn->data; struct auth *authhost; struct auth *authproxy; @@ -684,8 +717,16 @@ Curl_http_output_auth(struct connectdata *conn, authhost = &data->state.authhost; authproxy = &data->state.authproxy; - if((conn->bits.httpproxy && conn->bits.proxy_user_passwd) || - conn->bits.user_passwd) + 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; @@ -696,20 +737,20 @@ Curl_http_output_auth(struct connectdata *conn, 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. */ + want one, and if this is one single bit it will 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. */ + and if this is one single bit it will 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 == proxytunnel)) { - result = output_auth_headers(conn, authproxy, request, path, TRUE); + (conn->bits.tunnel_proxy == (bit)proxytunnel)) { + result = output_auth_headers(data, conn, authproxy, request, path, TRUE); if(result) return result; } @@ -717,43 +758,87 @@ Curl_http_output_auth(struct connectdata *conn, #else (void)proxytunnel; #endif /* CURL_DISABLE_PROXY */ - /* we have no proxy so let's pretend we're done authenticating + /* we have no proxy so let's pretend we are 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, we do some weirdo checks here */ - if(!data->state.this_is_a_follow || - conn->bits.netrc || - !data->state.first_host || - data->set.http_disable_hostname_check_before_authentication || - strcasecompare(data->state.first_host, conn->host.name)) { - result = output_auth_headers(conn, authhost, request, path, FALSE); - } + /* 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". */ + data->req.authneg = TRUE; + } + else + data->req.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 connectdata *conn, bool proxy, +CURLcode Curl_http_input_auth(struct Curl_easy *data, bool proxy, const char *auth) /* the first non-space */ { /* * This resource requires authentication */ - struct Curl_easy *data = conn->data; - + struct connectdata *conn = data->conn; #ifdef USE_SPNEGO - struct negotiatedata *negdata = proxy? - &data->state.proxyneg:&data->state.negotiate; + 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; @@ -765,6 +850,11 @@ CURLcode Curl_http_input_auth(struct connectdata *conn, bool proxy, 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 @@ -784,28 +874,25 @@ CURLcode Curl_http_input_auth(struct connectdata *conn, bool proxy, while(*auth) { #ifdef USE_SPNEGO - if(checkprefix("Negotiate", auth)) { + 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) { - if(negdata->state == GSS_AUTHSENT || - negdata->state == GSS_AUTHNONE) { - CURLcode result = Curl_input_negotiate(conn, proxy, auth); - if(!result) { - DEBUGASSERT(!data->req.newurl); - data->req.newurl = strdup(data->change.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 */ - negdata->state = GSS_AUTHRECV; - } - else - data->state.authproblem = TRUE; + 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; } } } @@ -813,45 +900,20 @@ CURLcode Curl_http_input_auth(struct connectdata *conn, bool proxy, #endif #ifdef USE_NTLM /* NTLM support requires the SSL crypto libs */ - if(checkprefix("NTLM", auth)) { + if(checkprefix("NTLM", auth) && is_valid_auth_separator(auth[4])) { if((authp->avail & CURLAUTH_NTLM) || - (authp->avail & CURLAUTH_NTLM_WB) || Curl_auth_is_ntlm_supported()) { *availp |= CURLAUTH_NTLM; authp->avail |= CURLAUTH_NTLM; - if(authp->picked == CURLAUTH_NTLM || - authp->picked == CURLAUTH_NTLM_WB) { + if(authp->picked == CURLAUTH_NTLM) { /* NTLM authentication is picked and activated */ - CURLcode result = Curl_input_ntlm(conn, proxy, auth); + 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; - - /* Get the challenge-message which will be passed to - * ntlm_auth for generating the type 3 message later */ - while(*auth && ISSPACE(*auth)) - auth++; - if(checkprefix("NTLM", auth)) { - auth += strlen("NTLM"); - while(*auth && ISSPACE(*auth)) - auth++; - if(*auth) { - conn->challenge_header = strdup(auth); - if(!conn->challenge_header) - return CURLE_OUT_OF_MEMORY; - } - } - } -#endif } else { - infof(data, "Authentication problem. Ignoring this.\n"); + infof(data, "Authentication problem. Ignoring this."); data->state.authproblem = TRUE; } } @@ -859,10 +921,10 @@ CURLcode Curl_http_input_auth(struct connectdata *conn, bool proxy, } else #endif -#ifndef CURL_DISABLE_CRYPTO_AUTH - if(checkprefix("Digest", auth)) { +#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.\n"); + infof(data, "Ignoring duplicate digest auth header."); else if(Curl_auth_is_digest_supported()) { CURLcode result; @@ -870,35 +932,62 @@ CURLcode Curl_http_input_auth(struct connectdata *conn, bool proxy, 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 + * authentication is not 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(conn, proxy, auth); + result = Curl_input_digest(data, proxy, auth); if(result) { - infof(data, "Authentication problem. Ignoring this.\n"); + infof(data, "Authentication problem. Ignoring this."); data->state.authproblem = TRUE; } } } else #endif - if(checkprefix("Basic", auth)) { +#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 + anyway, which basically means our name+password is not valid. */ authp->avail = CURLAUTH_NONE; - infof(data, "Authentication problem. Ignoring this.\n"); + 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 is not 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 */ + if(*auth == ',') /* if we are on a comma, skip it */ auth++; while(*auth && ISSPACE(*auth)) auth++; @@ -908,45 +997,45 @@ CURLcode Curl_http_input_auth(struct connectdata *conn, bool proxy, } /** - * http_should_fail() determines whether an HTTP response has gotten us + * http_should_fail() determines whether an HTTP response code has gotten us * into an error state or not. * - * @param conn all information about the current connection - * - * @retval 0 communications should continue + * @retval FALSE communications should continue * - * @retval 1 communications should not continue + * @retval TRUE communications should not continue */ -static int http_should_fail(struct connectdata *conn) +static bool http_should_fail(struct Curl_easy *data, int httpcode) { - struct Curl_easy *data; - int httpcode; - - DEBUGASSERT(conn); - data = conn->data; DEBUGASSERT(data); - - httpcode = data->req.httpcode; + DEBUGASSERT(data->conn); /* - ** If we haven't been asked to fail on error, - ** don't fail. + ** If we have not been asked to fail on error, + ** do not fail. */ if(!data->set.http_fail_on_error) - return 0; + return FALSE; /* ** Any code < 400 is never terminal. */ if(httpcode < 400) - return 0; + 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 + ** Any code >= 400 that is not 401 or 407 is always ** a terminal error */ if((httpcode != 401) && (httpcode != 407)) - return 1; + return TRUE; /* ** All we have left to deal with is 401 and 407 @@ -954,520 +1043,133 @@ static int http_should_fail(struct connectdata *conn) 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. + ** 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 have been to asked to authenticate a + ** particular stage, and we have done it, we are OK. If we are already + ** completely authenticated, it is not OK to get another 401 or 407. ** - ** It is possible for authentication to go stale such that - ** the client needs to reauthenticate. Once that info is - ** available, use it here. + ** 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. + ** Either we are not authenticating, or we are supposed to be authenticating + ** something else. This is an error. */ - if((httpcode == 401) && !conn->bits.user_passwd) + if((httpcode == 401) && !data->state.aptr.user) return TRUE; - if((httpcode == 407) && !conn->bits.proxy_user_passwd) +#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. + * Curl_compareheader() * - * Returns the amount of bytes it filled the buffer with. + * Returns TRUE if 'headerline' contains the 'header' with given 'content'. + * Pass headers WITH the colon. */ -static size_t readmoredata(char *buffer, - size_t size, - size_t nitems, - void *userp) +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 */ { - struct connectdata *conn = (struct connectdata *)userp; - struct HTTP *http = conn->data->req.protop; - size_t fullsize = size * nitems; - - if(!http->postsize) - /* nothing to return */ - return 0; + /* 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." */ - /* make sure that a HTTP request is never sent away chunked! */ - conn->data->req.forbidchunk = (http->sending == HTTPSEND_REQUEST)?TRUE:FALSE; + size_t len; + const char *start; + const char *end; + DEBUGASSERT(hlen); + DEBUGASSERT(clen); + DEBUGASSERT(header); + DEBUGASSERT(content); - if(http->postsize <= (curl_off_t)fullsize) { - memcpy(buffer, http->postdata, (size_t)http->postsize); - fullsize = (size_t)http->postsize; + if(!strncasecompare(headerline, header, hlen)) + return FALSE; /* does not start with header */ - if(http->backup.postsize) { - /* move backup data into focus and continue on that */ - http->postdata = http->backup.postdata; - http->postsize = http->backup.postsize; - conn->data->state.fread_func = http->backup.fread_func; - conn->data->state.in = http->backup.fread_in; + /* pass the header */ + start = &headerline[hlen]; - http->sending++; /* move one step up */ + /* pass all whitespace */ + while(*start && ISSPACE(*start)) + start++; - http->backup.postsize=0; - } - else - http->postsize = 0; + /* find the end of the header line */ + end = strchr(start, '\r'); /* lines end with CRLF */ + if(!end) { + /* in case there is a non-standard compliant line here */ + end = strchr(start, '\n'); - return fullsize; + if(!end) + /* hm, there is no line ending here, use the zero byte! */ + end = strchr(start, '\0'); } - memcpy(buffer, http->postdata, fullsize); - http->postdata += fullsize; - http->postsize -= fullsize; - - return fullsize; -} + len = end-start; /* length of the content part of the input line */ -/* ------------------------------------------------------------------------- */ -/* add_buffer functions */ + /* find the content string in the rest of the line */ + for(; len >= clen; len--, start++) { + if(strncasecompare(start, content, clen)) + return TRUE; /* match! */ + } -/* - * Curl_add_buffer_init() sets up and returns a fine buffer struct - */ -Curl_send_buffer *Curl_add_buffer_init(void) -{ - return calloc(1, sizeof(Curl_send_buffer)); + return FALSE; /* no match */ } /* - * Curl_add_buffer_free() frees all associated resources. + * Curl_http_connect() performs HTTP stuff to do at connect-time, called from + * the generic Curl_connect(). */ -void Curl_add_buffer_free(Curl_send_buffer *buff) +CURLcode Curl_http_connect(struct Curl_easy *data, bool *done) { - if(buff) /* deal with NULL input */ - free(buff->buffer); - free(buff); -} - -/* - * Curl_add_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_add_buffer_send(Curl_send_buffer *in, - struct connectdata *conn, - - /* add the number of sent bytes to this - counter */ - long *bytes_written, - - /* how much of the buffer contains body data */ - size_t included_body_bytes, - int socketindex) - -{ - ssize_t amount; - CURLcode result; - char *ptr; - size_t size; - struct HTTP *http = conn->data->req.protop; - size_t sendsize; - curl_socket_t sockfd; - size_t headersize; - - DEBUGASSERT(socketindex <= SECONDARYSOCKET); - - sockfd = conn->sock[socketindex]; - - /* 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 = in->buffer; - size = in->size_used; - - headersize = size - included_body_bytes; /* the initial part that isn't body - is header */ - - DEBUGASSERT(size > included_body_bytes); - - result = Curl_convert_to_network(conn->data, ptr, headersize); - /* Curl_convert_to_network calls failf if unsuccessful */ - if(result) { - /* conversion failed, free memory and return to the caller */ - Curl_add_buffer_free(in); - return result; - } - - if((conn->handler->flags & PROTOPT_SSL || - conn->http_proxy.proxytype == CURLPROXY_HTTPS) - && conn->httpversion != 20) { - /* We never send more than CURL_MAX_WRITE_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. - */ - - sendsize = (size > CURL_MAX_WRITE_SIZE) ? CURL_MAX_WRITE_SIZE : 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. - */ - memcpy(conn->data->state.uploadbuffer, ptr, sendsize); - ptr = conn->data->state.uploadbuffer; - } - else - sendsize = size; - - result = Curl_write(conn, sockfd, 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; - - if(conn->data->set.verbose) { - /* this data _may_ contain binary stuff */ - Curl_debug(conn->data, CURLINFO_HEADER_OUT, ptr, headlen, conn); - if(bodylen) { - /* there was body data sent beyond the initial header part, pass that - on to the debug callback too */ - Curl_debug(conn->data, CURLINFO_DATA_OUT, - ptr+headlen, bodylen, conn); - } - } - - /* '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 */ - http->writebytecount += bodylen; - - 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 = in->buffer + amount; - - /* backup the currently set pointers */ - http->backup.fread_func = conn->data->state.fread_func; - http->backup.fread_in = conn->data->state.in; - http->backup.postdata = http->postdata; - http->backup.postsize = http->postsize; - - /* set the new pointers for the request-sending */ - conn->data->state.fread_func = (curl_read_callback)readmoredata; - conn->data->state.in = (void *)conn; - http->postdata = ptr; - http->postsize = (curl_off_t)size; - - http->send_buffer = in; - 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; - else - Curl_pipeline_leave_write(conn); - } - } - Curl_add_buffer_free(in); - - return result; -} - - -/* - * add_bufferf() add the formatted input to the buffer. - */ -CURLcode Curl_add_bufferf(Curl_send_buffer *in, const char *fmt, ...) -{ - char *s; - va_list ap; - va_start(ap, fmt); - s = vaprintf(fmt, ap); /* this allocs a new string to append */ - va_end(ap); - - if(s) { - CURLcode result = Curl_add_buffer(in, s, strlen(s)); - free(s); - return result; - } - /* If we failed, we cleanup the whole buffer and return error */ - free(in->buffer); - free(in); - return CURLE_OUT_OF_MEMORY; -} - -/* - * add_buffer() appends a memory chunk to the existing buffer - */ -CURLcode Curl_add_buffer(Curl_send_buffer *in, const void *inptr, size_t size) -{ - char *new_rb; - size_t new_size; - - if(~size < in->size_used) { - /* If resulting used size of send buffer would wrap size_t, cleanup - the whole buffer and return error. Otherwise the required buffer - size will fit into a single allocatable memory chunk */ - Curl_safefree(in->buffer); - free(in); - return CURLE_OUT_OF_MEMORY; - } - - if(!in->buffer || - ((in->size_used + size) > (in->size_max - 1))) { - - /* If current buffer size isn't enough to hold the result, use a - buffer size that doubles the required size. If this new size - would wrap size_t, then just use the largest possible one */ - - if((size > (size_t)-1 / 2) || (in->size_used > (size_t)-1 / 2) || - (~(size * 2) < (in->size_used * 2))) - new_size = (size_t)-1; - else - new_size = (in->size_used+size) * 2; - - if(in->buffer) - /* we have a buffer, enlarge the existing one */ - new_rb = Curl_saferealloc(in->buffer, new_size); - else - /* create a new buffer */ - new_rb = malloc(new_size); - - if(!new_rb) { - /* If we failed, we cleanup the whole buffer and return error */ - free(in); - return CURLE_OUT_OF_MEMORY; - } - - in->buffer = new_rb; - in->size_max = new_size; - } - memcpy(&in->buffer[in->size_used], inptr, size); - - in->size_used += size; - - return CURLE_OK; -} - -/* 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 char *content) /* content string to find */ -{ - /* 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 hlen = strlen(header); - size_t clen; - size_t len; - const char *start; - const char *end; - - if(!strncasecompare(headerline, header, hlen)) - return FALSE; /* doesn't start with header */ - - /* pass the header */ - start = &headerline[hlen]; - - /* pass all white spaces */ - 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 */ - clen = strlen(content); /* length of the word to find */ - - /* 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 connectdata *conn, bool *done) -{ - CURLcode result; - - /* We default to persistent connections. We set this already in this connect - function to make the re-use checks properly be able to check this bit. */ - connkeep(conn, "HTTP default"); - - /* the CONNECT procedure might not have been completed */ - result = Curl_proxy_connect(conn, FIRSTSOCKET); - if(result) - return result; - - if(CONNECT_FIRSTSOCKET_PROXY_SSL()) - return CURLE_OK; /* wait for HTTPS proxy SSL initialization to complete */ - - if(conn->tunnel_state[FIRSTSOCKET] == TUNNEL_CONNECT) - /* nothing else to do except wait right now - we're not done here. */ - return CURLE_OK; - - if(conn->given->flags & PROTOPT_SSL) { - /* perform SSL initialization */ - result = https_connecting(conn, done); - if(result) - return result; - } - else - *done = TRUE; - - return CURLE_OK; + 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 + interface and then we are always _sending_ a request and thus we wait for the single socket to become writable only */ -static int http_getsock_do(struct connectdata *conn, - curl_socket_t *socks, - int numsocks) +int Curl_http_getsock_do(struct Curl_easy *data, + struct connectdata *conn, + curl_socket_t *socks) { /* write mode */ - (void)numsocks; /* unused, we trust it to be at least 1 */ - socks[0] = conn->sock[FIRSTSOCKET]; + (void)conn; + socks[0] = Curl_conn_get_socket(data, FIRSTSOCKET); return GETSOCK_WRITESOCK(0); } -#ifdef USE_SSL -static CURLcode https_connecting(struct connectdata *conn, bool *done) -{ - CURLcode result; - DEBUGASSERT((conn) && (conn->handler->flags & PROTOPT_SSL)); - - /* perform SSL initialization for this socket */ - result = Curl_ssl_connect_nonblocking(conn, FIRSTSOCKET, done); - if(result) - connclose(conn, "Failed HTTPS connection"); - - return result; -} - -static int https_getsock(struct connectdata *conn, - curl_socket_t *socks, - int numsocks) -{ - if(conn->handler->flags & PROTOPT_SSL) - return Curl_ssl_getsock(conn, socks, numsocks); - return GETSOCK_BLANK; -} -#endif /* USE_SSL */ - /* * Curl_http_done() gets called after a single HTTP request has been * performed. */ -CURLcode Curl_http_done(struct connectdata *conn, +CURLcode Curl_http_done(struct Curl_easy *data, CURLcode status, bool premature) { - struct Curl_easy *data = conn->data; - struct HTTP *http = data->req.protop; - - Curl_unencode_cleanup(conn); - -#ifdef USE_SPNEGO - if(data->state.proxyneg.state == GSS_AUTHSENT || - data->state.negotiate.state == GSS_AUTHSENT) { - /* add forbid re-use if http-code != 401/407 as a WA only needed for - * 401/407 that signal auth failure (empty) otherwise state will be RECV - * with current code. - * Do not close CONNECT_ONLY connections. */ - if((data->req.httpcode != 401) && (data->req.httpcode != 407) && - !data->set.connect_only) - streamclose(conn, "Negotiate transfer completed"); - Curl_cleanup_negotiate(data); - } -#endif - - /* 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; - - if(http->send_buffer) { - Curl_add_buffer_free(http->send_buffer); - http->send_buffer = NULL; /* clear the pointer */ - } - - Curl_http2_done(conn, premature); + struct connectdata *conn = data->conn; - if(HTTPREQ_POST_FORM == data->set.httpreq) { - data->req.bytecount = http->readbytecount + http->writebytecount; + /* Clear multipass flag. If authentication is not 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; - Curl_formclean(&http->sendit); /* Now free that whole lot */ - if(http->form.fp) { - /* a file being uploaded was left opened, close it! */ - fclose(http->form.fp); - http->form.fp = NULL; - } - } - else if(HTTPREQ_PUT == data->set.httpreq) - data->req.bytecount = http->readbytecount + http->writebytecount; + Curl_dyn_reset(&data->state.headerb); + Curl_hyper_done(data); if(status) return status; @@ -1476,13 +1178,15 @@ CURLcode Curl_http_done(struct connectdata *conn, entire operation is complete */ !conn->bits.retry && !data->set.connect_only && - (http->readbytecount + + (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 + /* If this connection is not simply closed to be retried, AND nothing was + read from the HTTP server (that counts), this cannot 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; } @@ -1498,61 +1202,32 @@ CURLcode Curl_http_done(struct connectdata *conn, * - if any server previously contacted to handle this request only supports * 1.0. */ -static bool use_http_1_1plus(const struct Curl_easy *data, - const struct connectdata *conn) +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->set.httpversion == CURL_HTTP_VERSION_1_0) && + if((data->state.httpwant == CURL_HTTP_VERSION_1_0) && (conn->httpversion <= 10)) return FALSE; - return ((data->set.httpversion == CURL_HTTP_VERSION_NONE) || - (data->set.httpversion >= CURL_HTTP_VERSION_1_1)); + 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) { -#ifdef USE_NGHTTP2 - if(conn->proto.httpc.h2) + if(Curl_conn_is_http3(data, conn, FIRSTSOCKET)) + return "3"; + if(Curl_conn_is_http2(data, conn, FIRSTSOCKET)) return "2"; -#endif - - if(use_http_1_1plus(data, conn)) + if(Curl_use_http_1_1plus(data, conn)) return "1.1"; return "1.0"; } - -/* check and possibly add an Expect: header */ -static CURLcode expect100(struct Curl_easy *data, - struct connectdata *conn, - Curl_send_buffer *req_buffer) -{ - CURLcode result = CURLE_OK; - const char *ptr; - data->state.expect100header = FALSE; /* default to false unless it is set - to TRUE below */ - if(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) */ - ptr = Curl_checkheaders(conn, "Expect:"); - if(ptr) { - data->state.expect100header = - Curl_compareheader(ptr, "Expect:", "100-continue"); - } - else { - result = Curl_add_bufferf(req_buffer, - "Expect: 100-continue\r\n"); - if(!result) - data->state.expect100header = TRUE; - } - } - - return result; -} +#endif enum proxy_use { HEADER_SERVER, /* direct to server */ @@ -1560,24 +1235,34 @@ enum proxy_use { HEADER_CONNECT /* sending CONNECT to a proxy */ }; -CURLcode Curl_add_custom_headers(struct connectdata *conn, - bool is_connect, - Curl_send_buffer *req_buffer) +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 */ - struct Curl_easy *data = conn->data; + 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; + proxy = conn->bits.httpproxy && !conn->bits.tunnel_proxy ? + HEADER_PROXY : HEADER_SERVER; switch(proxy) { case HEADER_SERVER: @@ -1597,93 +1282,279 @@ CURLcode Curl_add_custom_headers(struct connectdata *conn, 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) { + 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) { - /* we require a colon for this to be a true header */ - + name = headers->data; + namelen = ptr - headers->data; ptr++; /* pass the colon */ while(*ptr && ISSPACE(*ptr)) ptr++; - if(*ptr) { - /* only send this if the contents was non-blank */ - - if(conn->allocptr.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:", headers->data)) - ; - else if(data->set.httpreq == HTTPREQ_POST_FORM && - /* this header (extended by formdata.c) is sent later */ - checkprefix("Content-Type:", headers->data)) - ; - 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", headers->data)) - ; - else if(conn->allocptr.te && - /* when asking for Transfer-Encoding, don't pass on a custom - Connection: */ - checkprefix("Connection", headers->data)) - ; - else if((conn->httpversion == 20) && - checkprefix("Transfer-Encoding:", headers->data)) - /* HTTP/2 doesn't support chunked requests */ - ; - else { - CURLcode result = Curl_add_bufferf(req_buffer, "%s\r\n", - headers->data); - if(result) - return result; - } + value = ptr; + valuelen = strlen(value); + } + else { + /* quirk #1, suppress this header */ + continue; } } else { ptr = strchr(headers->data, ';'); - if(ptr) { - - ptr++; /* pass the semicolon */ - while(*ptr && ISSPACE(*ptr)) - ptr++; - if(*ptr) { - /* this may be used for something else in the future */ - } - else { - if(*(--ptr) == ';') { - CURLcode result; + if(!ptr) { + /* neither : nor ; in provided header value. We seem + * to ignore this silently */ + continue; + } - /* send no-value custom header if terminated by semicolon */ - *ptr = ':'; - result = Curl_add_bufferf(req_buffer, "%s\r\n", - headers->data); - if(result) - return result; - } - } + 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; } } - headers = headers->next; - } - } - - return CURLE_OK; -} -CURLcode Curl_add_timecondition(struct Curl_easy *data, - Curl_send_buffer *req_buffer) -{ - const struct tm *tm; - char *buf = data->state.buffer; + DEBUGASSERT(name && value); + if(data->state.aptr.host && + /* a Host: header was sent already, do not 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(data->req.authneg && + /* while doing auth neg, do not 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, do not 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 does not 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, do not 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(data->req.authneg && + /* while doing auth neg, do not 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, do not pass on a custom + Connection: */ + checkprefix("Connection:", compare)) + ; + else if((conn->httpversion >= 20) && + checkprefix("Transfer-Encoding:", compare)) + /* HTTP/2 does not 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 */ @@ -1696,142 +1567,89 @@ CURLcode Curl_add_timecondition(struct Curl_easy *data, } tm = &keeptime; - /* 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" */ - snprintf(buf, BUFSIZE-1, - "%s, %02d %s %4d %02d:%02d:%02d GMT", - 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); - switch(data->set.timecondition) { default: - break; + DEBUGF(infof(data, "invalid time condition")); + return CURLE_BAD_FUNCTION_ARGUMENT; + case CURL_TIMECOND_IFMODSINCE: - result = Curl_add_bufferf(req_buffer, - "If-Modified-Since: %s\r\n", buf); + condp = "If-Modified-Since"; + len = 17; break; case CURL_TIMECOND_IFUNMODSINCE: - result = Curl_add_bufferf(req_buffer, - "If-Unmodified-Since: %s\r\n", buf); + condp = "If-Unmodified-Since"; + len = 19; break; case CURL_TIMECOND_LASTMOD: - result = Curl_add_bufferf(req_buffer, - "Last-Modified: %s\r\n", buf); + condp = "Last-Modified"; + len = 13; break; } - return result; -} - -/* - * Curl_http() gets called from the generic multi_do() function when a HTTP - * request is to be performed. This creates and sends a properly constructed - * HTTP request. - */ -CURLcode Curl_http(struct connectdata *conn, bool *done) -{ - struct Curl_easy *data = conn->data; - CURLcode result = CURLE_OK; - struct HTTP *http; - const char *ppath = data->state.path; - bool paste_ftp_userpwd = FALSE; - char ftp_typecode[sizeof("/;type=?")] = ""; - const char *host = conn->host.name; - const char *te = ""; /* transfer-encoding */ - const char *ptr; - const char *request; - Curl_HttpReq httpreq = data->set.httpreq; -#if !defined(CURL_DISABLE_COOKIES) - char *addcookies = NULL; -#endif - curl_off_t included_body = 0; - const char *httpstring; - Curl_send_buffer *req_buffer; - curl_off_t postsize = 0; /* curl_off_t to handle large file sizes */ - int seekerr = CURL_SEEKFUNC_OK; - - /* 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; - - if(conn->httpversion < 20) { /* unless the connection is re-used and already - http2 */ - switch(conn->negnpn) { - case CURL_HTTP_VERSION_2: - conn->httpversion = 20; /* we know we're on HTTP/2 now */ - - result = Curl_http2_switched(conn, NULL, 0); - if(result) - return result; - break; - case CURL_HTTP_VERSION_1_1: - /* continue with HTTP/1.1 when explicitly requested */ - break; - default: - /* Check if user wants to use HTTP/2 with clear TCP*/ -#ifdef USE_NGHTTP2 - if(conn->data->set.httpversion == - CURL_HTTP_VERSION_2_PRIOR_KNOWLEDGE) { - DEBUGF(infof(data, "HTTP/2 over clean TCP\n")); - conn->httpversion = 20; - - result = Curl_http2_switched(conn, NULL, 0); - if(result) - return result; - } -#endif - break; - } - } - else { - /* prepare for a http2 request */ - result = Curl_http2_setup(conn); - if(result) - return result; + if(Curl_checkheaders(data, condp, len)) { + /* A custom header was specified; it will be sent instead. */ + return CURLE_OK; } - http = data->req.protop; - - if(!data->state.this_is_a_follow) { - /* Free to avoid leaking memory on multiple requests*/ - free(data->state.first_host); + /* 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). + */ - data->state.first_host = strdup(conn->host.name); - if(!data->state.first_host) - return CURLE_OUT_OF_MEMORY; + /* 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 - data->state.first_remote_port = conn->remote_port; - } - http->writebytecount = http->readbytecount = 0; + 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->set.upload) { + 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->set.opt_no_body) + if(data->req.no_body) request = "HEAD"; else { - DEBUGASSERT((httpreq > HTTPREQ_NONE) && (httpreq < HTTPREQ_LAST)); + 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: @@ -1847,236 +1665,188 @@ CURLcode Curl_http(struct connectdata *conn, bool *done) } } } + *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(conn, "User-Agent:")) { - free(conn->allocptr.uagent); - conn->allocptr.uagent=NULL; + if(Curl_checkheaders(data, STRCONST("User-Agent"))) { + free(data->state.aptr.uagent); + data->state.aptr.uagent = NULL; } + return CURLE_OK; +} - /* setup the authentication headers */ - result = Curl_http_output_auth(conn, request, ppath, FALSE); - if(result) - return result; - if((data->state.authhost.multi || data->state.authproxy.multi) && - (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; +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); - Curl_safefree(conn->allocptr.ref); - if(data->change.referer && !Curl_checkheaders(conn, "Referer:")) { - conn->allocptr.ref = aprintf("Referer: %s\r\n", data->change.referer); - if(!conn->allocptr.ref) + 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; } - else - conn->allocptr.ref = NULL; + 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(data->set.str[STRING_COOKIE] && !Curl_checkheaders(conn, "Cookie:")) - addcookies = data->set.str[STRING_COOKIE]; + /* If we have a given custom Host: header, we extract the hostname in + order to possibly use it for cookie reasons later on. We only allow the + 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 hostname + 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(!Curl_checkheaders(conn, "Accept-Encoding:") && - data->set.str[STRING_ENCODING]) { - Curl_safefree(conn->allocptr.accept_encoding); - conn->allocptr.accept_encoding = - aprintf("Accept-Encoding: %s\r\n", data->set.str[STRING_ENCODING]); - if(!conn->allocptr.accept_encoding) - return CURLE_OUT_OF_MEMORY; + if(!strcasecompare("Host:", ptr)) { + aptr->host = aprintf("Host:%s\r\n", &ptr[5]); + if(!aptr->host) + return CURLE_OUT_OF_MEMORY; + } } else { - Curl_safefree(conn->allocptr.accept_encoding); - conn->allocptr.accept_encoding = NULL; - } - -#ifdef HAVE_LIBZ - /* we only consider transfer-encoding magic if libz support is built-in */ + /* When building Host: headers, we must put the hostname within + [brackets] if the hostname is a plain IPv6-address. RFC2732-style. */ + const char *host = conn->host.name; - if(!Curl_checkheaders(conn, "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/hers own TE: header we don't do this magic - but then assume that the user will handle it all! */ - char *cptr = Curl_checkheaders(conn, "Connection:"); -#define TE_HEADER "TE: gzip\r\n" - - Curl_safefree(conn->allocptr.te); - - /* Create the (updated) Connection: header */ - conn->allocptr.te = cptr? aprintf("%s, TE\r\n" TE_HEADER, cptr): - strdup("Connection: TE\r\n" TE_HEADER); + 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 do not 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(!conn->allocptr.te) + if(!aptr->host) + /* without Host: we cannot make a nice request */ return CURLE_OUT_OF_MEMORY; } -#endif + return CURLE_OK; +} - ptr = Curl_checkheaders(conn, "Transfer-Encoding:"); - if(ptr) { - /* Some kind of TE is requested, check if 'chunked' is chosen */ - data->req.upload_chunky = - Curl_compareheader(ptr, "Transfer-Encoding:", "chunked"); - } - else { - if((conn->handler->protocol&PROTO_FAMILY_HTTP) && - data->set.upload && - (data->state.infilesize == -1)) { - if(conn->bits.authneg) - /* don't enable chunked during auth neg */ - ; - else if(use_http_1_1plus(data, conn)) { - /* 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; - } +/* + * 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->req.upload_chunky) - te = "Transfer-Encoding: chunked\r\n"; + if(data->set.str[STRING_TARGET]) { + path = data->set.str[STRING_TARGET]; + query = NULL; } - Curl_safefree(conn->allocptr.host); +#ifndef CURL_DISABLE_PROXY + if(conn->bits.httpproxy && !conn->bits.tunnel_proxy) { + /* Using a proxy but does not tunnel through it */ - ptr = Curl_checkheaders(conn, "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) + /* The path sent to the proxy is in fact the entire URL. But if the remote + host is a IDN-name, we must make sure that the request we produce only + uses the encoded hostname! */ + + /* and no fragment part */ + CURLUcode uc; + char *url; + CURLU *h = curl_url_dup(data->state.uh); + if(!h) 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 */ - int startsearch = 0; - 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 { - char *colon = strchr(cookiehost + startsearch, ':'); - if(colon) - *colon = 0; /* The host must not include an embedded port number */ + + 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; } - Curl_safefree(conn->allocptr.cookiehost); - conn->allocptr.cookiehost = cookiehost; } -#endif + uc = curl_url_set(h, CURLUPART_FRAGMENT, NULL, 0); + if(uc) { + curl_url_cleanup(h); + return CURLE_OUT_OF_MEMORY; + } - if(strcmp("Host:", ptr)) { - conn->allocptr.host = aprintf("%s\r\n", ptr); - if(!conn->allocptr.host) + if(strcasecompare("http", data->state.up.scheme)) { + /* when getting HTTP, we do not 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; + } } - else - /* when clearing the header */ - conn->allocptr.host = NULL; - } - else { - /* When building Host: headers, we must put the host name within - [brackets] if the host name is a plain IPv6-address. RFC2732-style. */ - - if(((conn->given->protocol&CURLPROTO_HTTPS) && - (conn->remote_port == PORT_HTTPS)) || - ((conn->given->protocol&CURLPROTO_HTTP) && - (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 */ - conn->allocptr.host = aprintf("Host: %s%s%s\r\n", - conn->bits.ipv6_ip?"[":"", - host, - conn->bits.ipv6_ip?"]":""); - else - conn->allocptr.host = aprintf("Host: %s%s%s:%hu\r\n", - conn->bits.ipv6_ip?"[":"", - host, - conn->bits.ipv6_ip?"]":"", - conn->remote_port); - - if(!conn->allocptr.host) - /* without Host: we can't make a nice request */ + /* Extract the URL to use in the request. */ + uc = curl_url_get(h, CURLUPART_URL, &url, CURLU_NO_DEFAULT_PORT); + if(uc) { + curl_url_cleanup(h); return CURLE_OUT_OF_MEMORY; - } + } -#ifndef CURL_DISABLE_PROXY - if(conn->bits.httpproxy && !conn->bits.tunnel_proxy) { - /* Using a proxy but does not tunnel through it */ + curl_url_cleanup(h); - /* 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! */ - if(conn->host.dispname != conn->host.name) { - char *url = data->change.url; - ptr = strstr(url, conn->host.dispname); - if(ptr) { - /* This is where the display name starts in the URL, now replace this - part with the encoded name. TODO: This method of replacing the host - name is rather crude as I believe there's a slight risk that the - user has entered a user name or password that contain the host name - string. */ - size_t currlen = strlen(conn->host.dispname); - size_t newlen = strlen(conn->host.name); - size_t urllen = strlen(url); - - char *newurl; - - newurl = malloc(urllen + newlen - currlen + 1); - if(newurl) { - /* copy the part before the host name */ - memcpy(newurl, url, ptr - url); - /* append the new host name instead of the old */ - memcpy(newurl + (ptr - url), conn->host.name, newlen); - /* append the piece after the host name */ - memcpy(newurl + newlen + (ptr - url), - ptr + currlen, /* copy the trailing zero byte too */ - urllen - (ptr-url) - currlen + 1); - if(data->change.url_alloc) { - Curl_safefree(data->change.url); - data->change.url_alloc = FALSE; - } - data->change.url = newurl; - data->change.url_alloc = TRUE; - } - else - return CURLE_OUT_OF_MEMORY; - } - } - ppath = data->change.url; - if(checkprefix("ftp://", ppath)) { + /* 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= if not present */ - char *type = strstr(ppath, ";type="); + char *type = strstr(path, ";type="); if(type && type[6] && type[7] == 0) { switch(Curl_raw_toupper(type[6])) { case 'A': @@ -2088,37 +1858,171 @@ CURLcode Curl_http(struct connectdata *conn, bool *done) } } if(!type) { - char *p = ftp_typecode; - /* avoid sending invalid URLs like ftp://example.com;type=i if the - * user specified ftp://example.com without the slash */ - if(!*data->state.path && ppath[strlen(ppath) - 1] != '/') { - *p++ = '/'; - } - snprintf(p, sizeof(ftp_typecode) - 1, ";type=%c", - data->set.prefer_ascii ? 'a' : 'i'); + result = Curl_dyn_addf(r, ";type=%c", + data->state.prefer_ascii ? 'a' : 'i'); + if(result) + return result; } } - if(conn->bits.user_passwd && !conn->bits.userpwd_in_url) - paste_ftp_userpwd = TRUE; } } -#endif /* CURL_DISABLE_PROXY */ - if(HTTPREQ_POST_FORM == httpreq) { - /* we must build the whole post sequence first, so that we have a size of - the whole transfer before we start to send it */ - result = Curl_getformdata(data, &http->sendit, data->set.httppost, - Curl_checkheaders(conn, "Content-Type:"), - &http->postsize); + 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; +} + +#if !defined(CURL_DISABLE_MIME) || !defined(CURL_DISABLE_FORM_API) +static CURLcode set_post_reader(struct Curl_easy *data, Curl_HttpReq httpreq) +{ + CURLcode result; + + switch(httpreq) { +#ifndef CURL_DISABLE_MIME + case HTTPREQ_POST_MIME: + data->state.mimepost = &data->set.mimepost; + break; +#endif +#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; + break; } - http->p_accept = Curl_checkheaders(conn, "Accept:")?NULL:"Accept: */*\r\n"; + switch(httpreq) { + case HTTPREQ_POST_FORM: + case HTTPREQ_POST_MIME: + /* This is form posting using mime data. */ +#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); + if(result) + return result; + curl_mime_headers(data->state.mimepost, NULL, 0); + result = Curl_creader_set_mime(data, data->state.mimepost); + if(result) + return result; + } + else +#endif + { + result = Curl_creader_set_null(data); + } + data->state.infilesize = Curl_creader_total_length(data); + return result; + + default: + return Curl_creader_set_null(data); + } + /* never reached */ +} +#endif + +static CURLcode set_reader(struct Curl_easy *data, Curl_HttpReq httpreq) +{ + CURLcode result = CURLE_OK; + curl_off_t postsize = data->state.infilesize; + + DEBUGASSERT(data->conn); + + if(data->req.authneg) { + return Curl_creader_set_null(data); + } + + switch(httpreq) { + case HTTPREQ_PUT: /* Let's PUT the data to the server! */ + return postsize ? Curl_creader_set_fread(data, postsize) : + Curl_creader_set_null(data); + +#if !defined(CURL_DISABLE_MIME) || !defined(CURL_DISABLE_FORM_API) + case HTTPREQ_POST_FORM: + case HTTPREQ_POST_MIME: + return set_post_reader(data, httpreq); +#endif + + case HTTPREQ_POST: + /* this is the simple POST, using x-www-form-urlencoded style */ + /* the size of the post body */ + if(!postsize) { + result = Curl_creader_set_null(data); + } + else if(data->set.postfields) { + if(postsize > 0) + result = Curl_creader_set_buf(data, data->set.postfields, + (size_t)postsize); + else + result = Curl_creader_set_null(data); + } + else { + /* we read the bytes from the callback. In case "chunked" encoding + * is forced by the application, we disregard `postsize`. This is + * a backward compatibility decision to earlier versions where + * chunking disregarded this. See issue #13229. */ + bool chunked = FALSE; + char *ptr = Curl_checkheaders(data, STRCONST("Transfer-Encoding")); + if(ptr) { + /* Some kind of TE is requested, check if 'chunked' is chosen */ + chunked = Curl_compareheader(ptr, STRCONST("Transfer-Encoding:"), + STRCONST("chunked")); + } + result = Curl_creader_set_fread(data, chunked ? -1 : postsize); + } + return result; - if(( (HTTPREQ_POST == httpreq) || - (HTTPREQ_POST_FORM == httpreq) || - (HTTPREQ_PUT == httpreq) ) && + default: + /* HTTP GET/HEAD download, has no body, needs no Content-Length */ + data->state.infilesize = 0; + return Curl_creader_set_null(data); + } + /* not reached */ +} + +static CURLcode http_resume(struct Curl_easy *data, 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 @@ -2126,1366 +2030,865 @@ CURLcode Curl_http(struct connectdata *conn, bool *done) * 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! + * We do not support this now. Bail out! */ data->state.resume_from = 0; } - if(data->state.resume_from && !data->state.this_is_a_follow) { - /* do we still game? */ - - /* Now, let's read off the proper amount of bytes from the - input. */ - if(conn->seek_func) { - seekerr = conn->seek_func(conn->seek_client, data->state.resume_from, - SEEK_SET); - } - - if(seekerr != CURL_SEEKFUNC_OK) { - 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) */ - else { - curl_off_t passed=0; - do { - size_t readthisamountnow = - (data->state.resume_from - passed > CURL_OFF_T_C(BUFSIZE)) ? - BUFSIZE : 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; - } + if(data->state.resume_from && !data->req.authneg) { + /* only act on the first request */ + CURLcode result; + result = Curl_creader_resume_from(data, data->state.resume_from); + if(result) { + failf(data, "Unable to resume from offset %" FMT_OFF_T, + data->state.resume_from); + return result; } - /* we've passed, proceed as normal */ } } - 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(conn, "Range:")) { - /* if a line like this was already allocated, free the previous one */ - free(conn->allocptr.rangeline); - conn->allocptr.rangeline = aprintf("Range: bytes=%s\r\n", - data->state.range); - } - else if((httpreq != HTTPREQ_GET) && - !Curl_checkheaders(conn, "Content-Range:")) { + return CURLE_OK; +} - /* if a line like this was already allocated, free the previous one */ - free(conn->allocptr.rangeline); +CURLcode Curl_http_req_set_reader(struct Curl_easy *data, + Curl_HttpReq httpreq, + const char **tep) +{ + CURLcode result = CURLE_OK; + const char *ptr; - 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) */ - conn->allocptr.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); + result = set_reader(data, httpreq); + if(result) + return result; - } - 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; - conn->allocptr.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); + result = http_resume(data, httpreq); + if(result) + return result; + + 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")); + if(data->req.upload_chunky && + Curl_use_http_1_1plus(data, data->conn) && + (data->conn->httpversion >= 20)) { + infof(data, "suppressing chunked transfer encoding on connection " + "using HTTP version 2 or higher"); + data->req.upload_chunky = FALSE; + } + } + else { + curl_off_t req_clen = Curl_creader_total_length(data); + + if(req_clen < 0) { + /* indeterminate request content length */ + if(Curl_use_http_1_1plus(data, data->conn)) { + /* On HTTP/1.1, enable chunked, on HTTP/2 and later we do not + * need it */ + data->req.upload_chunky = (data->conn->httpversion < 20); } else { - /* Range was selected and then we just pass the incoming range and - append total size */ - conn->allocptr.rangeline = - aprintf("Content-Range: bytes %s/%" CURL_FORMAT_CURL_OFF_T "\r\n", - data->state.range, data->state.infilesize); + failf(data, "Chunky upload is not supported by HTTP 1.0"); + return CURLE_UPLOAD_FAILED; } - if(!conn->allocptr.rangeline) - return CURLE_OUT_OF_MEMORY; } + else { + /* else, no chunky upload */ + data->req.upload_chunky = FALSE; + } + + if(data->req.upload_chunky) + *tep = "Transfer-Encoding: chunked\r\n"; } + return result; +} - httpstring = get_http_string(data, conn); +static CURLcode addexpect(struct Curl_easy *data, struct dynbuf *r, + bool *announced_exp100) +{ + CURLcode result; + char *ptr; - /* initialize a dynamic send-buffer */ - req_buffer = Curl_add_buffer_init(); + *announced_exp100 = FALSE; + /* Avoid Expect: 100-continue if Upgrade: is used */ + if(data->req.upgr101 != UPGR101_INIT) + return CURLE_OK; - if(!req_buffer) - return CURLE_OUT_OF_MEMORY; + /* For really small puts we do not 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. */ + ptr = Curl_checkheaders(data, STRCONST("Expect")); + if(ptr) { + *announced_exp100 = + Curl_compareheader(ptr, STRCONST("Expect:"), STRCONST("100-continue")); + } + else if(!data->state.disableexpect && + Curl_use_http_1_1plus(data, data->conn) && + (data->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) */ + curl_off_t client_len = Curl_creader_client_length(data); + if(client_len > EXPECT_100_THRESHOLD || client_len < 0) { + result = Curl_dyn_addn(r, STRCONST("Expect: 100-continue\r\n")); + if(result) + return result; + *announced_exp100 = TRUE; + } + } + return CURLE_OK; +} - /* add the main request stuff */ - /* GET/HEAD/POST/PUT */ - result = Curl_add_bufferf(req_buffer, "%s ", request); - if(result) - return result; +CURLcode Curl_http_req_complete(struct Curl_easy *data, + struct dynbuf *r, Curl_HttpReq httpreq) +{ + CURLcode result = CURLE_OK; + curl_off_t req_clen; + bool announced_exp100 = FALSE; - /* url */ - if(paste_ftp_userpwd) - result = Curl_add_bufferf(req_buffer, "ftp://%s:%s@%s", - conn->user, conn->passwd, - ppath + sizeof("ftp://") - 1); - else - result = Curl_add_buffer(req_buffer, ppath, strlen(ppath)); - if(result) - return result; + DEBUGASSERT(data->conn); +#ifndef USE_HYPER + if(data->req.upload_chunky) { + result = Curl_httpchunk_add_reader(data); + if(result) + return result; + } +#endif - result = - Curl_add_bufferf(req_buffer, - "%s" /* ftp typecode (;type=x) */ - " 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 */ - - ftp_typecode, - httpstring, - (conn->allocptr.host?conn->allocptr.host:""), - conn->allocptr.proxyuserpwd? - conn->allocptr.proxyuserpwd:"", - conn->allocptr.userpwd?conn->allocptr.userpwd:"", - (data->state.use_range && conn->allocptr.rangeline)? - conn->allocptr.rangeline:"", - (data->set.str[STRING_USERAGENT] && - *data->set.str[STRING_USERAGENT] && - conn->allocptr.uagent)? - conn->allocptr.uagent:"", - http->p_accept?http->p_accept:"", - conn->allocptr.te?conn->allocptr.te:"", - (data->set.str[STRING_ENCODING] && - *data->set.str[STRING_ENCODING] && - conn->allocptr.accept_encoding)? - conn->allocptr.accept_encoding:"", - (data->change.referer && conn->allocptr.ref)? - conn->allocptr.ref:"" /* Referer: */, - (conn->bits.httpproxy && - !conn->bits.tunnel_proxy && - !Curl_checkProxyheaders(conn, "Proxy-Connection:"))? - "Proxy-Connection: Keep-Alive\r\n":"", - te - ); + /* Get the request body length that has been set up */ + req_clen = Curl_creader_total_length(data); + switch(httpreq) { + case HTTPREQ_PUT: + case HTTPREQ_POST: +#if !defined(CURL_DISABLE_MIME) || !defined(CURL_DISABLE_FORM_API) + case HTTPREQ_POST_FORM: + case HTTPREQ_POST_MIME: +#endif + /* We only set Content-Length and allow a custom Content-Length if + we do not upload data chunked, as RFC2616 forbids us to set both + kinds of headers (Transfer-Encoding: chunked and Content-Length). + We do not override a custom "Content-Length" header, but during + authentication negotiation that header is suppressed. + */ + if(req_clen >= 0 && !data->req.upload_chunky && + (data->req.authneg || + !Curl_checkheaders(data, STRCONST("Content-Length")))) { + /* we allow replacing this header if not during auth negotiation, + although it is not very wise to actually set your own */ + result = Curl_dyn_addf(r, "Content-Length: %" FMT_OFF_T "\r\n", + req_clen); + } + if(result) + goto out; - /* clear userpwd to avoid re-using credentials from re-used connections */ - Curl_safefree(conn->allocptr.userpwd); +#ifndef CURL_DISABLE_MIME + /* Output mime-generated headers. */ + if(data->state.mimepost && + ((httpreq == HTTPREQ_POST_FORM) || (httpreq == HTTPREQ_POST_MIME))) { + struct curl_slist *hdr; - /* - * Free proxyuserpwd for Negotiate/NTLM. Cannot reuse as it is associated - * with the connection and shouldn't be repeated over it either. - */ - switch(data->state.authproxy.picked) { - case CURLAUTH_NEGOTIATE: - case CURLAUTH_NTLM: - case CURLAUTH_NTLM_WB: - Curl_safefree(conn->allocptr.proxyuserpwd); + for(hdr = data->state.mimepost->curlheaders; hdr; hdr = hdr->next) { + result = Curl_dyn_addf(r, "%s\r\n", hdr->data); + if(result) + goto out; + } + } +#endif + if(httpreq == HTTPREQ_POST) { + if(!Curl_checkheaders(data, STRCONST("Content-Type"))) { + result = Curl_dyn_addn(r, STRCONST("Content-Type: application/" + "x-www-form-urlencoded\r\n")); + if(result) + goto out; + } + } + result = addexpect(data, r, &announced_exp100); + if(result) + goto out; + break; + default: break; } - if(result) - return result; + /* end of headers */ + result = Curl_dyn_addn(r, STRCONST("\r\n")); + if(!result) { + Curl_pgrsSetUploadSize(data, req_clen); + if(announced_exp100) + result = http_exp100_add_reader(data); + } - if(!(conn->handler->flags&PROTOPT_SSL) && - conn->httpversion != 20 && - (data->set.httpversion == 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_buffer, conn); - if(result) - return result; +out: + if(!result) { + /* setup variables for the upcoming transfer */ + Curl_xfer_setup1(data, CURL_XFER_SENDRECV, -1, TRUE); } + return result; +} #if !defined(CURL_DISABLE_COOKIES) - if(data->cookies || addcookies) { - struct Cookie *co=NULL; /* no cookies from start */ - int count=0; - if(data->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->cookies, - conn->allocptr.cookiehost? - conn->allocptr.cookiehost:host, - data->state.path, - (conn->handler->protocol&CURLPROTO_HTTPS)? - TRUE:FALSE); + 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; + 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) { - if(0 == count) { - result = Curl_add_bufferf(req_buffer, "Cookie: "); + size_t add; + if(!count) { + result = Curl_dyn_addn(r, STRCONST("Cookie: ")); if(result) break; } - result = Curl_add_bufferf(req_buffer, - "%s%s=%s", count?"; ":"", - co->name, co->value); + 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) { + if(addcookies && !result && !linecap) { if(!count) - result = Curl_add_bufferf(req_buffer, "Cookie: "); + result = Curl_dyn_addn(r, STRCONST("Cookie: ")); if(!result) { - result = Curl_add_bufferf(req_buffer, "%s%s", count?"; ":"", - addcookies); + result = Curl_dyn_addf(r, "%s%s", count ? "; " : "", addcookies); count++; } } if(count && !result) - result = Curl_add_buffer(req_buffer, "\r\n", 2); + result = Curl_dyn_addn(r, STRCONST("\r\n")); if(result) return result; } + return result; +} #endif - result = Curl_add_timecondition(data, req_buffer); - if(result) - return result; - - result = Curl_add_custom_headers(conn, FALSE, req_buffer); - if(result) - return result; - - http->postdata = NULL; /* nothing to post at this point */ - Curl_pgrsSetUploadSize(data, -1); /* upload size is unknown atm */ +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 are 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"))) { + curl_off_t req_clen = Curl_creader_total_length(data); + /* if a line like this was already allocated, free the previous one */ + free(data->state.aptr.rangeline); - /* If 'authdone' is FALSE, we must not set the write socket index to the - Curl_transfer() call below, as we're not ready to actually upload any - data yet. */ + if(data->set.set_resume_from < 0) { + /* Upload resume was asked for, but we do not know the size of the + remote part so we tell the server (and act accordingly) that we + upload the whole file (again) */ + data->state.aptr.rangeline = + aprintf("Content-Range: bytes 0-%" FMT_OFF_T "/%" FMT_OFF_T "\r\n", + req_clen - 1, req_clen); - switch(httpreq) { + } + else if(data->state.resume_from) { + /* This is because "resume" was selected */ + /* TODO: not sure if we want to send this header during authentication + * negotiation, but test1084 checks for it. In which case we have a + * "null" client reader installed that gives an unexpected length. */ + curl_off_t total_len = data->req.authneg ? + data->state.infilesize : + (data->state.resume_from + req_clen); + data->state.aptr.rangeline = + aprintf("Content-Range: bytes %s%" FMT_OFF_T "/%" FMT_OFF_T "\r\n", + data->state.range, total_len-1, total_len); + } + 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/%" FMT_OFF_T "\r\n", + data->state.range, req_clen); + } + if(!data->state.aptr.rangeline) + return CURLE_OUT_OF_MEMORY; + } + } + return CURLE_OK; +} - case HTTPREQ_POST_FORM: - if(!http->sendit || conn->bits.authneg) { - /* nothing to post! */ - result = Curl_add_bufferf(req_buffer, "Content-Length: 0\r\n\r\n"); - if(result) - return result; +CURLcode Curl_http_firstwrite(struct Curl_easy *data) +{ + struct connectdata *conn = data->conn; + struct SingleRequest *k = &data->req; - result = Curl_add_buffer_send(req_buffer, conn, - &data->info.request_size, 0, FIRSTSOCKET); - if(result) - failf(data, "Failed sending POST request"); - else - /* setup variables for the upcoming transfer */ - Curl_setup_transfer(conn, FIRSTSOCKET, -1, TRUE, &http->readbytecount, - -1, NULL); - break; + if(data->req.newurl) { + if(conn->bits.close) { + /* Abort after the headers if "follow Location" is set + and we are set to close anyway. */ + k->keepon &= ~KEEP_RECV; + k->done = TRUE; + return CURLE_OK; } - - if(Curl_FormInit(&http->form, http->sendit)) { - failf(data, "Internal HTTP POST error!"); - return CURLE_HTTP_POST_ERROR; + /* 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 + does not allow resume from here. */ + infof(data, "The entire document is already downloaded"); + streamclose(conn, "already downloaded"); + /* Abort download */ + k->keepon &= ~KEEP_RECV; + k->done = TRUE; + return CURLE_OK; } - /* Get the currently set callback function pointer and store that in the - form struct since we might want the actual user-provided callback later - on. The data->set.fread_func pointer itself will be changed for the - multipart case to the function that returns a multipart formatted - stream. */ - http->form.fread_func = data->state.fread_func; - - /* Set the read function to read from the generated form data */ - data->state.fread_func = (curl_read_callback)Curl_FormReader; - data->state.in = &http->form; - - http->sending = HTTPSEND_BODY; + /* we wanted to resume a download, although the server does not seem to + * support this and we did this with a GET (if it was not a GET we did a + * POST or PUT resume) */ + failf(data, "HTTP server does not seem to support " + "byte ranges. Cannot resume."); + return CURLE_RANGE_ERROR; + } - if(!data->req.upload_chunky && - !Curl_checkheaders(conn, "Content-Length:")) { - /* only add Content-Length if not uploading chunked */ - result = Curl_add_bufferf(req_buffer, - "Content-Length: %" CURL_FORMAT_CURL_OFF_T - "\r\n", http->postsize); - if(result) - return result; + 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)) { + k->done = TRUE; + /* We are 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 */ - result = expect100(data, conn, req_buffer); - if(result) - return result; + 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 do not 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" - /* Get Content-Type: line from Curl_formpostheader. - */ - char *contentType; - size_t linelength=0; - contentType = Curl_formpostheader((void *)&http->form, - &linelength); - if(!contentType) { - failf(data, "Could not get Content-Type header line!"); - return CURLE_HTTP_POST_ERROR; - } + Curl_safefree(data->state.aptr.te); - result = Curl_add_buffer(req_buffer, contentType, linelength); - if(result) - return result; + if(cptr) { + cptr = Curl_copy_header_value(cptr); + if(!cptr) + return CURLE_OUT_OF_MEMORY; } - /* make the request end in a true CRLF */ - result = Curl_add_buffer(req_buffer, "\r\n", 2); - if(result) - return result; - - /* set upload size to the progress meter */ - Curl_pgrsSetUploadSize(data, http->postsize); + /* Create the (updated) Connection: header */ + data->state.aptr.te = aprintf("Connection: %s%sTE\r\n" TE_HEADER, + cptr ? cptr : "", (cptr && *cptr) ? ", ":""); - /* fire away the whole request to the server */ - result = Curl_add_buffer_send(req_buffer, conn, - &data->info.request_size, 0, FIRSTSOCKET); - if(result) - failf(data, "Failed sending POST request"); - else - /* setup variables for the upcoming transfer */ - Curl_setup_transfer(conn, FIRSTSOCKET, -1, TRUE, - &http->readbytecount, FIRSTSOCKET, - &http->writebytecount); + free(cptr); + if(!data->state.aptr.te) + return CURLE_OUT_OF_MEMORY; + } + return CURLE_OK; +} +#endif - if(result) { - Curl_formclean(&http->sendit); /* free that whole lot */ - return result; - } +#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; + 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 */ - /* convert the form data */ - result = Curl_convert_form(data, http->sendit); - if(result) { - Curl_formclean(&http->sendit); /* free that whole lot */ - return result; - } + /* 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 HTTPREQ_PUT: /* Let's PUT the data to the server! */ - - if(conn->bits.authneg) - postsize = 0; - else - postsize = data->state.infilesize; - - if((postsize != -1) && !data->req.upload_chunky && - (conn->bits.authneg || !Curl_checkheaders(conn, "Content-Length:"))) { - /* only add Content-Length if not uploading chunked */ - result = Curl_add_bufferf(req_buffer, - "Content-Length: %" CURL_FORMAT_CURL_OFF_T - "\r\n", postsize); + 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; + goto fail; } - - if(postsize != 0) { - result = expect100(data, conn, req_buffer); + 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; + goto fail; } + break; + } - result = Curl_add_buffer(req_buffer, "\r\n", 2); /* end of headers */ - if(result) - return result; - - /* set the upload size to the progress meter */ - Curl_pgrsSetUploadSize(data, postsize); + /* Add collecting of headers written to client. For a new connection, + * we might have done that already, but reuse + * or multiplex needs it here as well. */ + result = Curl_headers_init(data); + if(result) + goto fail; - /* this sends the buffer and frees all the buffer resources */ - result = Curl_add_buffer_send(req_buffer, conn, - &data->info.request_size, 0, FIRSTSOCKET); - if(result) - failf(data, "Failed sending PUT request"); - else - /* prepare for transfer */ - Curl_setup_transfer(conn, FIRSTSOCKET, -1, TRUE, - &http->readbytecount, postsize?FIRSTSOCKET:-1, - postsize?&http->writebytecount:NULL); - if(result) - return result; - break; + result = Curl_http_host(data, conn); + if(result) + goto fail; - case HTTPREQ_POST: - /* this is the simple POST, using x-www-form-urlencoded style */ + result = Curl_http_useragent(data); + if(result) + goto fail; - if(conn->bits.authneg) - postsize = 0; - else { - /* figure out the size of the postfields */ - postsize = (data->state.infilesize != -1)? - data->state.infilesize: - (data->set.postfields? (curl_off_t)strlen(data->set.postfields):-1); - } + Curl_http_method(data, conn, &request, &httpreq); - /* 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((postsize != -1) && !data->req.upload_chunky && - (conn->bits.authneg || !Curl_checkheaders(conn, "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_add_bufferf(req_buffer, - "Content-Length: %" CURL_FORMAT_CURL_OFF_T - "\r\n", postsize); - if(result) - return result; + /* 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) + goto fail; + } - if(!Curl_checkheaders(conn, "Content-Type:")) { - result = Curl_add_bufferf(req_buffer, - "Content-Type: application/" - "x-www-form-urlencoded\r\n"); - 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; + } - /* For really small posts 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. */ - ptr = Curl_checkheaders(conn, "Expect:"); - if(ptr) { - data->state.expect100header = - Curl_compareheader(ptr, "Expect:", "100-continue"); - } - else if(postsize > TINY_INITIAL_POST_SIZE || postsize < 0) { - result = expect100(data, conn, req_buffer); - if(result) - return result; - } - else - data->state.expect100header = FALSE; + 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); - if(data->set.postfields) { +#ifdef HAVE_LIBZ + /* we only consider transfer-encoding magic if libz support is built-in */ + result = Curl_transferencode(data); + if(result) + goto fail; +#endif - /* In HTTP2, we send request body in DATA frame regardless of - its size. */ - if(conn->httpversion != 20 && - !data->state.expect100header && - (postsize < MAX_INITIAL_POST_SIZE)) { - /* if we don't use expect: 100 AND - postsize is less than MAX_INITIAL_POST_SIZE + result = Curl_http_req_set_reader(data, httpreq, &te); + if(result) + goto fail; - 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. */ + p_accept = Curl_checkheaders(data, + STRCONST("Accept")) ? NULL : "Accept: */*\r\n"; - result = Curl_add_buffer(req_buffer, "\r\n", 2); /* end of headers! */ - if(result) - return result; + result = Curl_http_range(data, httpreq); + if(result) + goto fail; - if(!data->req.upload_chunky) { - /* We're not sending it 'chunked', append it to the request - already now to reduce the number if send() calls */ - result = Curl_add_buffer(req_buffer, data->set.postfields, - (size_t)postsize); - included_body = postsize; - } - else { - if(postsize) { - /* Append the POST data chunky-style */ - result = Curl_add_bufferf(req_buffer, "%x\r\n", (int)postsize); - if(!result) { - result = Curl_add_buffer(req_buffer, data->set.postfields, - (size_t)postsize); - if(!result) - result = Curl_add_buffer(req_buffer, "\r\n", 2); - included_body = postsize + 2; - } - } - if(!result) - result = Curl_add_buffer(req_buffer, "\x30\x0d\x0a\x0d\x0a", 5); - /* 0 CR LF CR LF */ - included_body += 5; - } - if(result) - return result; - /* Make sure the progress information is accurate */ - Curl_pgrsSetUploadSize(data, postsize); - } - else { - /* A huge POST coming up, do data separate from the request */ - http->postsize = postsize; - http->postdata = data->set.postfields; + httpstring = get_http_string(data, conn); - http->sending = HTTPSEND_BODY; + /* initialize a dynamic send-buffer */ + Curl_dyn_init(&req, DYN_HTTP_REQUEST); - data->state.fread_func = (curl_read_callback)readmoredata; - data->state.in = (void *)conn; + /* make sure the header buffer is reset - if there are leftovers from a + previous transfer */ + Curl_dyn_reset(&data->state.headerb); - /* set the upload size to the progress meter */ - Curl_pgrsSetUploadSize(data, http->postsize); + /* 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); + goto fail; + } - result = Curl_add_buffer(req_buffer, "\r\n", 2); /* end of headers! */ - if(result) - 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; } - else { - result = Curl_add_buffer(req_buffer, "\r\n", 2); /* end of headers! */ - if(result) - return result; + } +#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 : ""), +#ifndef CURL_DISABLE_PROXY + data->state.aptr.proxyuserpwd ? + data->state.aptr.proxyuserpwd : "", +#else + "", +#endif + data->state.aptr.userpwd ? data->state.aptr.userpwd : "", + (data->state.use_range && data->state.aptr.rangeline) ? + data->state.aptr.rangeline : "", + (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: */, +#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 : "" + ); - 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_add_buffer(req_buffer, - "\x30\x0d\x0a\x0d\x0a", 5); - /* 0 CR LF CR LF */ - if(result) - return result; - } + /* clear userpwd and proxyuserpwd to avoid reusing old credentials + * from reused connections */ + Curl_safefree(data->state.aptr.userpwd); +#ifndef CURL_DISABLE_PROXY + Curl_safefree(data->state.aptr.proxyuserpwd); +#endif + free(altused); - else if(data->state.infilesize) { - /* set the upload size to the progress meter */ - Curl_pgrsSetUploadSize(data, postsize?postsize:-1); + if(result) { + Curl_dyn_free(&req); + goto fail; + } - /* 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; - http->postsize = postsize; - } - } + 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 is not done + over SSL */ + result = Curl_http2_request_upgrade(&req, data); + if(result) { + Curl_dyn_free(&req); + return result; } - /* issue the request */ - result = Curl_add_buffer_send(req_buffer, conn, &data->info.request_size, - (size_t)included_body, FIRSTSOCKET); + } - if(result) - failf(data, "Failed sending HTTP POST request"); - else - Curl_setup_transfer(conn, FIRSTSOCKET, -1, TRUE, - &http->readbytecount, http->postdata?FIRSTSOCKET:-1, - http->postdata?&http->writebytecount:NULL); - break; - - default: - result = Curl_add_buffer(req_buffer, "\r\n", 2); - if(result) - return result; - - /* issue the request */ - result = Curl_add_buffer_send(req_buffer, conn, - &data->info.request_size, 0, FIRSTSOCKET); + result = Curl_http_cookies(data, conn, &req); +#ifndef CURL_DISABLE_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) - failf(data, "Failed sending HTTP request"); - else - /* HTTP GET/HEAD download: */ - Curl_setup_transfer(conn, FIRSTSOCKET, -1, TRUE, &http->readbytecount, - http->postdata?FIRSTSOCKET:-1, - http->postdata?&http->writebytecount:NULL); + if(!result) { + /* req_send takes ownership of the 'req' memory on success */ + result = Curl_http_req_complete(data, &req, httpreq); + if(!result) + result = Curl_req_send(data, &req); } + Curl_dyn_free(&req); if(result) - return result; - - if(http->writebytecount) { - /* if a request-body has been sent off, we make sure this progress is noted - properly */ - Curl_pgrsSetUploadCounter(data, http->writebytecount); - if(Curl_pgrsUpdate(conn)) - result = CURLE_ABORTED_BY_CALLBACK; - - if(http->writebytecount >= 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\n", - http->writebytecount, postsize); - data->req.upload_done = TRUE; - data->req.keepon &= ~KEEP_SEND; /* we're done writing */ - data->req.exp100 = EXP100_SEND_DATA; /* already sent */ - } - } + goto fail; - if((conn->httpversion == 20) && data->req.upload_chunky) + 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; +fail: + if(CURLE_TOO_LARGE == result) + failf(data, "HTTP request too large"); 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 bool +static statusline checkhttpprefix(struct Curl_easy *data, - const char *s) + const char *s, size_t len) { struct curl_slist *head = data->set.http200aliases; - bool rc = FALSE; -#ifdef CURL_DOES_CONVERSIONS - /* convert from the network encoding using a scratch area */ - char *scratch = strdup(s); - if(NULL == scratch) { - failf(data, "Failed to allocate memory for conversion!"); - return FALSE; /* can't return CURLE_OUT_OF_MEMORY so return FALSE */ - } - if(CURLE_OK != Curl_convert_from_network(data, scratch, strlen(s)+1)) { - /* Curl_convert_from_network calls failf if unsuccessful */ - free(scratch); - return FALSE; /* can't return CURLE_foobar so return FALSE */ - } - s = scratch; -#endif /* CURL_DOES_CONVERSIONS */ + statusline rc = STATUS_BAD; + statusline onmatch = len >= 5 ? STATUS_DONE : STATUS_UNKNOWN; while(head) { - if(checkprefix(head->data, s)) { - rc = TRUE; + if(checkprefixmax(head->data, s, len)) { + rc = onmatch; break; } head = head->next; } - if(!rc && (checkprefix("HTTP/", s))) - rc = TRUE; + if((rc != STATUS_DONE) && (checkprefixmax("HTTP/", s, len))) + rc = onmatch; -#ifdef CURL_DOES_CONVERSIONS - free(scratch); -#endif /* CURL_DOES_CONVERSIONS */ return rc; } #ifndef CURL_DISABLE_RTSP -static bool +static statusline checkrtspprefix(struct Curl_easy *data, - const char *s) + const char *s, size_t len) { - -#ifdef CURL_DOES_CONVERSIONS - /* convert from the network encoding using a scratch area */ - char *scratch = strdup(s); - if(NULL == scratch) { - failf(data, "Failed to allocate memory for conversion!"); - return FALSE; /* can't return CURLE_OUT_OF_MEMORY so return FALSE */ - } - if(CURLE_OK != Curl_convert_from_network(data, scratch, strlen(s)+1)) { - /* Curl_convert_from_network calls failf if unsuccessful */ - free(scratch); - return FALSE; /* can't return CURLE_foobar so return FALSE */ - } - s = scratch; -#else + statusline result = STATUS_BAD; + statusline onmatch = len >= 5 ? STATUS_DONE : STATUS_UNKNOWN; (void)data; /* unused */ -#endif /* CURL_DOES_CONVERSIONS */ - if(checkprefix("RTSP/", s)) - return TRUE; - else - return FALSE; + if(checkprefixmax("RTSP/", s, len)) + result = onmatch; + + return result; } #endif /* CURL_DISABLE_RTSP */ -static bool +static statusline checkprotoprefix(struct Curl_easy *data, struct connectdata *conn, - const char *s) + const char *s, size_t len) { #ifndef CURL_DISABLE_RTSP if(conn->handler->protocol & CURLPROTO_RTSP) - return checkrtspprefix(data, s); + return checkrtspprefix(data, s, len); #else (void)conn; #endif /* CURL_DISABLE_RTSP */ - return checkhttpprefix(data, s); -} - -/* - * header_append() copies a chunk of data to the end of the already received - * header. We make sure that the full string fit in the allocated header - * buffer, or else we enlarge it. - */ -static CURLcode header_append(struct Curl_easy *data, - struct SingleRequest *k, - size_t length) -{ - if(k->hbuflen + length >= data->state.headersize) { - /* We enlarge the header buffer as it is too small */ - char *newbuff; - size_t hbufp_index; - size_t newsize; - - if(k->hbuflen + length > CURL_MAX_HTTP_HEADER) { - /* The reason to have a max limit for this is to avoid the risk of a bad - server feeding libcurl with a never-ending header that will cause - reallocs infinitely */ - failf(data, "Avoided giant realloc for header (max is %d)!", - CURL_MAX_HTTP_HEADER); - return CURLE_OUT_OF_MEMORY; - } - - newsize=CURLMAX((k->hbuflen+ length)*3/2, data->state.headersize*2); - hbufp_index = k->hbufp - data->state.headerbuff; - newbuff = realloc(data->state.headerbuff, newsize); - if(!newbuff) { - failf(data, "Failed to alloc memory for big header!"); - return CURLE_OUT_OF_MEMORY; - } - data->state.headersize=newsize; - data->state.headerbuff = newbuff; - k->hbufp = data->state.headerbuff + hbufp_index; - } - memcpy(k->hbufp, k->str_start, length); - k->hbufp += length; - k->hbuflen += length; - *k->hbufp = 0; - - return CURLE_OK; + return checkhttpprefix(data, s, len); } -static void print_http_error(struct Curl_easy *data) -{ - struct SingleRequest *k = &data->req; - char *beg = k->p; - - /* make sure that data->req.p points to the HTTP status line */ - if(!strncmp(beg, "HTTP", 4)) { - - /* skip to HTTP status code */ - beg = strchr(beg, ' '); - if(beg && *++beg) { - - /* find trailing CR */ - char end_char = '\r'; - char *end = strchr(beg, end_char); - if(!end) { - /* try to find LF (workaround for non-compliant HTTP servers) */ - end_char = '\n'; - end = strchr(beg, end_char); - } - - if(end) { - /* temporarily replace CR or LF by NUL and print the error message */ - *end = '\0'; - failf(data, "The requested URL returned error: %s", beg); +/* HTTP header has field name `n` (a string constant) */ +#define HD_IS(hd, hdlen, n) \ + (((hdlen) >= (sizeof(n)-1)) && curl_strnequal((n), (hd), (sizeof(n)-1))) - /* restore the previously replaced CR or LF */ - *end = end_char; - return; - } - } - } +#define HD_VAL(hd, hdlen, n) \ + ((((hdlen) >= (sizeof(n)-1)) && \ + curl_strnequal((n), (hd), (sizeof(n)-1)))? (hd + (sizeof(n)-1)) : NULL) - /* fall-back to printing the HTTP status code only */ - failf(data, "The requested URL returned error: %d", k->httpcode); -} +/* HTTP header has field name `n` (a string constant) and contains `v` + * (a string constant) in its value(s) */ +#define HD_IS_AND_SAYS(hd, hdlen, n, v) \ + (HD_IS(hd, hdlen, n) && \ + ((hdlen) > ((sizeof(n)-1) + (sizeof(v)-1))) && \ + Curl_compareheader(hd, STRCONST(n), STRCONST(v))) /* - * Read any HTTP header lines from the server and pass them to the client app. + * Curl_http_header() parses a single response header. */ -CURLcode Curl_http_readwrite_headers(struct Curl_easy *data, - struct connectdata *conn, - ssize_t *nread, - bool *stop_reading) +CURLcode Curl_http_header(struct Curl_easy *data, + const char *hd, size_t hdlen) { + struct connectdata *conn = data->conn; CURLcode result; struct SingleRequest *k = &data->req; - - /* header line within buffer loop */ - do { - size_t rest_length; - size_t full_length; - int writetype; - - /* str_start is start of line within buf */ - k->str_start = k->str; - - /* data is in network encoding so use 0x0a instead of '\n' */ - k->end_ptr = memchr(k->str_start, 0x0a, *nread); - - if(!k->end_ptr) { - /* Not a complete header line within buffer, append the data to - the end of the headerbuff. */ - result = header_append(data, k, *nread); - if(result) - return result; - - if(!k->headerline && (k->hbuflen>5)) { - /* make a first check that this looks like a protocol header */ - if(!checkprotoprefix(data, conn, data->state.headerbuff)) { - /* this is not the beginning of a protocol first header line */ - k->header = FALSE; - k->badheader = HEADER_ALLBAD; - break; - } - } - - break; /* read more and try again */ - } - - /* decrease the size of the remaining (supposed) header line */ - rest_length = (k->end_ptr - k->str)+1; - *nread -= (ssize_t)rest_length; - - k->str = k->end_ptr + 1; /* move past new line */ - - full_length = k->str - k->str_start; - - result = header_append(data, k, full_length); - if(result) - return result; - - k->end_ptr = k->hbufp; - k->p = data->state.headerbuff; - - /**** - * We now have a FULL header line that p points to - *****/ - - if(!k->headerline) { - /* the first read header */ - if((k->hbuflen>5) && - !checkprotoprefix(data, conn, data->state.headerbuff)) { - /* this is not the beginning of a protocol first header line */ - k->header = FALSE; - if(*nread) - /* since there's more, this is a partial bad header */ - k->badheader = HEADER_PARTHEADER; - else { - /* this was all we read so it's all a bad header */ - k->badheader = HEADER_ALLBAD; - *nread = (ssize_t)rest_length; - } - break; - } - } - - /* headers are in network encoding so - use 0x0a and 0x0d instead of '\n' and '\r' */ - if((0x0a == *k->p) || (0x0d == *k->p)) { - size_t headerlen; - /* Zero-length header line means end of headers! */ - -#ifdef CURL_DOES_CONVERSIONS - if(0x0d == *k->p) { - *k->p = '\r'; /* replace with CR in host encoding */ - k->p++; /* pass the CR byte */ - } - if(0x0a == *k->p) { - *k->p = '\n'; /* replace with LF in host encoding */ - k->p++; /* pass the LF byte */ - } + const char *v; + + switch(hd[0]) { + case 'a': + case 'A': +#ifndef CURL_DISABLE_ALTSVC + v = (data->asi && + ((data->conn->handler->flags & PROTOPT_SSL) || +#ifdef DEBUGBUILD + /* allow debug builds to circumvent the HTTPS restriction */ + getenv("CURL_ALTSVC_HTTP") #else - if('\r' == *k->p) - k->p++; /* pass the \r byte */ - if('\n' == *k->p) - k->p++; /* pass the \n byte */ -#endif /* CURL_DOES_CONVERSIONS */ - - if(100 <= k->httpcode && 199 >= k->httpcode) { - /* "A user agent MAY ignore unexpected 1xx status responses." */ - switch(k->httpcode) { - case 100: - /* - * We have made a 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; - } - break; - case 101: - /* Switching Protocols */ - if(k->upgr101 == UPGR101_REQUESTED) { - /* Switching to HTTP/2 */ - infof(data, "Received 101\n"); - 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_switched(conn, k->str, *nread); - if(result) - return result; - *nread = 0; - } - else { - /* Switching to another protocol (e.g. WebSocket) */ - 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 { - 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->set.httpreq != HTTPREQ_HEAD) { - /* On HTTP 1.1, when connection is not to get closed, but no - Content-Length nor Content-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\n"); - streamclose(conn, "HTTP: No end-of-message indicator"); - } - } - - /* 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->ntlm.state == NTLMSTATE_TYPE2)) || - ((data->req.httpcode == 407) && - (conn->proxyntlm.state == NTLMSTATE_TYPE2)))) { - infof(data, "Connection closure while negotiating auth (HTTP 1.0?)\n"); - data->state.authproblem = TRUE; - } -#endif - - /* - * When all the headers have been parsed, see if we should give - * up and return an error. - */ - if(http_should_fail(conn)) { - failf(data, "The requested URL returned error: %d", - k->httpcode); - return CURLE_HTTP_RETURNED_ERROR; - } - - /* now, only output this if the header AND body are requested: - */ - writetype = CLIENTWRITE_HEADER; - if(data->set.include_header) - writetype |= CLIENTWRITE_BODY; - - headerlen = k->p - data->state.headerbuff; - - result = Curl_client_write(conn, writetype, - data->state.headerbuff, - headerlen); - if(result) - return result; - - data->info.header_size += (long)headerlen; - data->req.headerbytecount += (long)headerlen; - - 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(conn); - - if(result) - return result; - - if(k->httpcode >= 300) { - if((!conn->bits.authneg) && !conn->bits.close && - !conn->bits.rewindaftersend) { - /* - * 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. - * - * rewindaftersend indicates that something has told libcurl to - * continue sending even if it gets discarded - */ - - switch(data->set.httpreq) { - case HTTPREQ_PUT: - case HTTPREQ_POST: - case HTTPREQ_POST_FORM: - /* 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. - */ - if(!k->upload_done) { - if(data->set.http_keep_sending_on_error) { - infof(data, "HTTP error before end of send, keep sending\n"); - 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\n"); - streamclose(conn, "Stop sending data before everything sent"); - k->upload_done = TRUE; - k->keepon &= ~KEEP_SEND; /* don't send */ - if(data->state.expect100header) - k->exp100 = EXP100_FAILED; - } - } - break; - - default: /* default label present to avoid compiler warnings */ - break; - } - } - - if(conn->bits.rewindaftersend) { - /* We rewind after a complete send, so thus we continue - sending now */ - infof(data, "Keep sending data to get tossed away!\n"); - 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->set.opt_no_body) - *stop_reading = 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 */ - *stop_reading = TRUE; + 0 #endif - else { - /* If we know the expected size of this document, we set the - maximum download size to the size of the expected - document or else, we won't know when to stop reading! - - Note that we set the download maximum even if we read a - "Connection: close" header, to make sure that - "Content-Length: 0" still prevents us from attempting to - read the (missing) response-body. - */ - /* According to RFC2616 section 4.4, we MUST ignore - Content-Length: headers if we are now receiving data - using chunked Transfer-Encoding. - */ - if(k->chunk) - k->maxdownload = k->size = -1; - } - if(-1 != k->size) { - /* We do this operation even if no_body is true, since this - data might be retrieved later with curl_easy_getinfo() - and its CURLINFO_CONTENT_LENGTH_DOWNLOAD option. */ - - Curl_pgrsSetDownloadSize(data, k->size); - k->maxdownload = k->size; - } - - /* If max download size is *zero* (nothing) we already - have nothing and can safely return ok now! */ - if(0 == k->maxdownload) - *stop_reading = TRUE; - - if(*stop_reading) { - /* we make sure that this socket isn't read more now */ - k->keepon &= ~KEEP_RECV; - } - - if(data->set.verbose) - Curl_debug(data, CURLINFO_HEADER_IN, - k->str_start, headerlen, conn); - break; /* exit header line loop */ - } - - /* We continue reading headers, so reset the line-based - header parsing variables hbufp && hbuflen */ - k->hbufp = data->state.headerbuff; - k->hbuflen = 0; - continue; + )) ? HD_VAL(hd, hdlen, "Alt-Svc:") : NULL; + if(v) { + /* the ALPN of the current request */ + enum alpnid id = (conn->httpversion == 30) ? ALPN_h3 : + (conn->httpversion == 20) ? ALPN_h2 : ALPN_h1; + return Curl_altsvc_parse(data, data->asi, v, id, conn->host.name, + curlx_uitous((unsigned int)conn->remote_port)); } +#endif + break; + case 'c': + case 'C': + /* Check for Content-Length: header lines to get size */ + v = (!k->http_bodyless && !data->set.ignorecl) ? + HD_VAL(hd, hdlen, "Content-Length:") : NULL; + if(v) { + curl_off_t contentlength; + CURLofft offt = curlx_strtoofft(v, NULL, 10, &contentlength); - /* - * Checks for special headers coming up. - */ - - 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! */ - int httpversion_major; - int rtspversion_major; - int nc = 0; -#ifdef CURL_DOES_CONVERSIONS -#define HEADER1 scratch -#define SCRATCHSIZE 21 - CURLcode res; - char scratch[SCRATCHSIZE+1]; /* "HTTP/major.minor 123" */ - /* We can't really convert this yet because we - don't know if it's the 1st header line or the body. - So we do a partial conversion into a scratch area, - leaving the data at k->p as-is. - */ - strncpy(&scratch[0], k->p, SCRATCHSIZE); - scratch[SCRATCHSIZE] = 0; /* null terminate */ - res = Curl_convert_from_network(data, - &scratch[0], - SCRATCHSIZE); - if(res) - /* Curl_convert_from_network calls failf if unsuccessful */ - return res; -#else -#define HEADER1 k->p /* no conversion needed, just use k->p */ -#endif /* CURL_DOES_CONVERSIONS */ - - if(conn->handler->protocol & PROTO_FAMILY_HTTP) { - /* - * https://tools.ietf.org/html/rfc7230#section-3.1.2 - * - * The reponse code is always a three-digit number in HTTP as the spec - * says. We try to allow any number here, but we cannot make - * guarantees on future behaviors since it isn't within the protocol. - */ - nc = sscanf(HEADER1, - " HTTP/%d.%d %d", - &httpversion_major, - &conn->httpversion, - &k->httpcode); - - if(nc == 1 && httpversion_major == 2 && - 1 == sscanf(HEADER1, " HTTP/2 %d", &k->httpcode)) { - conn->httpversion = 0; - nc = 3; - } - - if(nc==3) { - conn->httpversion += 10 * httpversion_major; - - if(k->upgr101 == UPGR101_RECEIVED) { - /* supposedly upgraded to http2 now */ - if(conn->httpversion != 20) - infof(data, "Lying server, not serving HTTP/2\n"); - } - } - else { - /* this is the real world, not a Nirvana - NCSA 1.5.x returns this crap when asked for HTTP/1.1 - */ - nc=sscanf(HEADER1, " HTTP %3d", &k->httpcode); - conn->httpversion = 10; - - /* If user has set option HTTP200ALIASES, - compare header line against list of aliases - */ - if(!nc) { - if(checkhttpprefix(data, k->p)) { - nc = 1; - k->httpcode = 200; - conn->httpversion = 10; - } - } - } - } - else if(conn->handler->protocol & CURLPROTO_RTSP) { - nc = sscanf(HEADER1, - " RTSP/%d.%d %3d", - &rtspversion_major, - &conn->rtspversion, - &k->httpcode); - if(nc==3) { - conn->rtspversion += 10 * rtspversion_major; - conn->httpversion = 11; /* For us, RTSP acts like HTTP 1.1 */ - } - else { - /* TODO: do we care about the other cases here? */ - nc = 0; - } + if(offt == CURL_OFFT_OK) { + k->size = contentlength; + k->maxdownload = k->size; } - - if(nc) { - 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->set.http_fail_on_error && (k->httpcode >= 400) && - ((k->httpcode != 401) || !conn->bits.user_passwd) && - ((k->httpcode != 407) || !conn->bits.proxy_user_passwd) ) { - - if(data->state.resume_from && - (data->set.httpreq==HTTPREQ_GET) && - (k->httpcode == 416)) { - /* "Requested Range Not Satisfiable", just proceed and - pretend this is no error */ - } - else { - /* serious error, go home! */ - print_http_error(data); - return CURLE_HTTP_RETURNED_ERROR; - } - } - - 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\n"); - connclose(conn, "HTTP/1.0 close after body"); - } - else if(conn->httpversion == 20 || - (k->upgr101 == UPGR101_REQUESTED && k->httpcode == 101)) { - DEBUGF(infof(data, "HTTP/2 found, allow multiplexing\n")); - - /* HTTP/2 cannot blacklist 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 - server supports pipelining. */ - DEBUGF(infof(data, - "HTTP 1.1 or later with persistent connection, " - "pipelining supported\n")); - /* Activate pipelining if needed */ - if(conn->bundle) { - if(!Curl_pipeline_site_blacklisted(data, conn)) - conn->bundle->multiuse = BUNDLE_PIPELINING; - } - } - - switch(k->httpcode) { - 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. */ - /* FALLTHROUGH */ - 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; - k->size=0; - k->maxdownload=0; - k->ignorecl = TRUE; /* ignore Content-Length headers */ - break; - default: - /* nothing */ - break; + 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 { - k->header = FALSE; /* this is not a header line */ - break; + /* negative or just rubbish - bad HTTP */ + failf(data, "Invalid Content-Length: value"); + return CURLE_WEIRD_SERVER_REPLY; } + return CURLE_OK; } - - result = Curl_convert_from_network(data, k->p, strlen(k->p)); - /* Curl_convert_from_network calls failf if unsuccessful */ - if(result) - return result; - - /* Check for Content-Length: header lines to get size */ - if(!k->ignorecl && !data->set.ignorecl && - checkprefix("Content-Length:", k->p)) { - curl_off_t contentlength = curlx_strtoofft(k->p+15, NULL, 10); - if(data->set.max_filesize && - contentlength > data->set.max_filesize) { - failf(data, "Maximum file size exceeded"); - return CURLE_FILESIZE_EXCEEDED; - } - if(contentlength >= 0) { - k->size = contentlength; - k->maxdownload = k->size; - /* we set the progress download size already at this point - just to make it easier for apps/callbacks to extract this - info as soon as possible */ - Curl_pgrsSetDownloadSize(data, k->size); - } - else { - /* Negative Content-Length is really odd, and we know it - happens for example when older Apache servers send large - files */ - streamclose(conn, "negative content-length"); - infof(data, "Negative content-length: %" CURL_FORMAT_CURL_OFF_T - ", closing after transfer\n", contentlength); - } + v = (!k->http_bodyless && data->set.str[STRING_ENCODING]) ? + HD_VAL(hd, hdlen, "Content-Encoding:") : NULL; + if(v) { + /* + * 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 + */ + return Curl_build_unencoding_stack(data, v, FALSE); } /* check for Content-Type: header lines to get the MIME-type */ - else if(checkprefix("Content-Type:", k->p)) { - char *contenttype = Curl_copy_header_value(k->p); + v = HD_VAL(hd, hdlen, "Content-Type:"); + if(v) { + char *contenttype = Curl_copy_header_value(hd); if(!contenttype) return CURLE_OUT_OF_MEMORY; if(!*contenttype) @@ -3495,56 +2898,9 @@ CURLcode Curl_http_readwrite_headers(struct Curl_easy *data, Curl_safefree(data->info.contenttype); data->info.contenttype = contenttype; } + return CURLE_OK; } - else if(checkprefix("Server:", k->p)) { - if(conn->httpversion < 20) { - /* only do this for non-h2 servers */ - char *server_name = Curl_copy_header_value(k->p); - - /* Turn off pipelining if the server version is blacklisted */ - if(conn->bundle && (conn->bundle->multiuse == BUNDLE_PIPELINING)) { - if(Curl_pipeline_server_blacklisted(data, server_name)) - conn->bundle->multiuse = BUNDLE_NO_MULTIUSE; - } - free(server_name); - } - } - else if((conn->httpversion == 10) && - conn->bits.httpproxy && - Curl_compareheader(k->p, - "Proxy-Connection:", "keep-alive")) { - /* - * When a 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!\n"); - } - else if((conn->httpversion == 11) && - conn->bits.httpproxy && - Curl_compareheader(k->p, - "Proxy-Connection:", "close")) { - /* - * We get a 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!\n"); - } - else if((conn->httpversion == 10) && - Curl_compareheader(k->p, "Connection:", "keep-alive")) { - /* - * A 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!\n"); - } - else if(Curl_compareheader(k->p, "Connection:", "close")) { + if(HD_IS_AND_SAYS(hd, hdlen, "Connection:", "close")) { /* * [RFC 2616, section 8.1.2.1] * "Connection: close" is HTTP/1.1 language and means that @@ -3552,164 +2908,67 @@ CURLcode Curl_http_readwrite_headers(struct Curl_easy *data, * served. */ streamclose(conn, "Connection: close used"); + return CURLE_OK; } - else if(checkprefix("Transfer-Encoding:", k->p)) { - /* One or more encodings. We check for chunked and/or a compression - algorithm. */ + if((conn->httpversion == 10) && + HD_IS_AND_SAYS(hd, hdlen, "Connection:", "keep-alive")) { /* - * [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. */ - - char *start; - - /* Find the first non-space letter */ - start = k->p + 18; - - for(;;) { - /* skip whitespaces and commas */ - while(*start && (ISSPACE(*start) || (*start == ','))) - start++; - - if(checkprefix("chunked", start)) { - k->chunk = TRUE; /* chunks coming our way */ + * 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"); + return CURLE_OK; + } + v = !k->http_bodyless ? HD_VAL(hd, hdlen, "Content-Range:") : NULL; + if(v) { + /* Content-Range: bytes [num]- + Content-Range: bytes: [num]- + Content-Range: [num]- + Content-Range: [asterisk]/[total] - /* init our chunky engine */ - Curl_httpchunk_init(conn); + 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. + */ - start += 7; - } + const char *ptr = v; - if(k->auto_decoding) - /* TODO: we only support the first mentioned compression for now */ - break; + /* Move forward until first digit or asterisk */ + while(*ptr && !ISDIGIT(*ptr) && *ptr != '*') + ptr++; - if(checkprefix("identity", start)) { - k->auto_decoding = IDENTITY; - start += 8; + /* 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(checkprefix("deflate", start)) { - k->auto_decoding = DEFLATE; - start += 7; - } - else if(checkprefix("gzip", start)) { - k->auto_decoding = GZIP; - start += 4; - } - else if(checkprefix("x-gzip", start)) { - k->auto_decoding = GZIP; - start += 6; - } - else - /* unknown! */ - break; - - } - - } - else if(checkprefix("Content-Encoding:", k->p) && - 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 - */ - char *start; - - /* Find the first non-space letter */ - start = k->p + 17; - while(*start && ISSPACE(*start)) - start++; - - /* Record the content-encoding for later use */ - if(checkprefix("identity", start)) - k->auto_decoding = IDENTITY; - else if(checkprefix("deflate", start)) - k->auto_decoding = DEFLATE; - else if(checkprefix("gzip", start) - || checkprefix("x-gzip", start)) - k->auto_decoding = GZIP; - } - else if(checkprefix("Content-Range:", k->p)) { - /* 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 forth means the requested range was unsatisfied. - */ - - char *ptr = k->p + 14; - - /* Move forward until first digit or asterisk */ - while(*ptr && !ISDIGIT(*ptr) && *ptr != '*') - ptr++; - - /* if it truly stopped on a digit */ - if(ISDIGIT(*ptr)) { - k->offset = curlx_strtoofft(ptr, NULL, 10); - - if(data->state.resume_from == k->offset) - /* we asked for a resume and we got it */ - k->content_range = TRUE; } - else + else if(k->httpcode < 300) data->state.resume_from = 0; /* get everything */ } -#if !defined(CURL_DISABLE_COOKIES) - else if(data->cookies && - checkprefix("Set-Cookie:", k->p)) { - Curl_share_lock(data, CURL_LOCK_DATA_COOKIE, - CURL_LOCK_ACCESS_SINGLE); - Curl_cookie_add(data, - data->cookies, TRUE, k->p+11, - /* If there is a custom-set Host: name, use it - here, or else use real peer host name. */ - conn->allocptr.cookiehost? - conn->allocptr.cookiehost:conn->host.name, - data->state.path); - Curl_share_unlock(data, CURL_LOCK_DATA_COOKIE); - } -#endif - else if(checkprefix("Last-Modified:", k->p) && - (data->set.timecondition || data->set.get_filetime) ) { - time_t secs=time(NULL); - k->timeofdoc = curl_getdate(k->p+strlen("Last-Modified:"), - &secs); + break; + case 'l': + case 'L': + v = (!k->http_bodyless && + (data->set.timecondition || data->set.get_filetime)) ? + HD_VAL(hd, hdlen, "Last-Modified:") : NULL; + if(v) { + k->timeofdoc = Curl_getdate_capped(v); if(data->set.get_filetime) - data->info.filetime = (long)k->timeofdoc; - } - else if((checkprefix("WWW-Authenticate:", k->p) && - (401 == k->httpcode)) || - (checkprefix("Proxy-authenticate:", k->p) && - (407 == k->httpcode))) { - - bool proxy = (k->httpcode == 407) ? TRUE : FALSE; - char *auth = Curl_copy_header_value(k->p); - if(!auth) - return CURLE_OUT_OF_MEMORY; - - result = Curl_http_input_auth(conn, proxy, auth); - - free(auth); - - if(result) - return result; + data->info.filetime = k->timeofdoc; + return CURLE_OK; } - else if((k->httpcode >= 300 && k->httpcode < 400) && - checkprefix("Location:", k->p) && + if((k->httpcode >= 300 && k->httpcode < 400) && + HD_IS(hd, hdlen, "Location:") && !data->req.location) { /* this is the URL that the server advises us to use instead */ - char *location = Curl_copy_header_value(k->p); + char *location = Curl_copy_header_value(hd); if(!location) return CURLE_OUT_OF_MEMORY; if(!*location) @@ -3726,48 +2985,1564 @@ CURLcode Curl_http_readwrite_headers(struct Curl_easy *data, /* some cases of POST and PUT etc needs to rewind the data stream at this point */ - result = http_perhapsrewind(conn); + result = http_perhapsrewind(data, conn); if(result) return result; + + /* mark the next request as a followed location: */ + data->state.this_is_a_follow = TRUE; } } } - else if(conn->handler->protocol & CURLPROTO_RTSP) { - result = Curl_rtsp_parseheader(conn, k->p); + break; + case 'p': + case 'P': +#ifndef CURL_DISABLE_PROXY + v = HD_VAL(hd, hdlen, "Proxy-Connection:"); + if(v) { + if((conn->httpversion == 10) && conn->bits.httpproxy && + HD_IS_AND_SAYS(hd, hdlen, "Proxy-Connection:", "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"); /* do not close */ + infof(data, "HTTP/1.0 proxy connection set to keep alive"); + } + else if((conn->httpversion == 11) && conn->bits.httpproxy && + HD_IS_AND_SAYS(hd, hdlen, "Proxy-Connection:", "close")) { + /* + * We get an HTTP/1.1 response from a proxy and it says it will + * close down after this transfer. + */ + connclose(conn, "Proxy-Connection: asked to close after done"); + infof(data, "HTTP/1.1 proxy connection set close"); + } + return CURLE_OK; + } +#endif + if((407 == k->httpcode) && HD_IS(hd, hdlen, "Proxy-authenticate:")) { + char *auth = Curl_copy_header_value(hd); + if(!auth) + return CURLE_OUT_OF_MEMORY; + result = Curl_http_input_auth(data, TRUE, auth); + free(auth); + return result; + } +#ifdef USE_SPNEGO + if(HD_IS(hd, hdlen, "Persistent-Auth:")) { + struct negotiatedata *negdata = &conn->negotiate; + struct auth *authp = &data->state.authhost; + if(authp->picked == CURLAUTH_NEGOTIATE) { + char *persistentauth = Curl_copy_header_value(hd); + 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 + break; + case 'r': + case 'R': + v = HD_VAL(hd, hdlen, "Retry-After:"); + if(v) { + /* 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(v, NULL, 10, &retry_after); + if(!retry_after) { + time_t date = Curl_getdate_capped(v); + 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 */ + return CURLE_OK; + } + break; + case 's': + case 'S': +#if !defined(CURL_DISABLE_COOKIES) + v = (data->cookies && data->state.cookie_engine) ? + HD_VAL(hd, hdlen, "Set-Cookie:") : NULL; + if(v) { + /* If there is a custom-set Host: name, use it here, or else use + * real peer hostname. */ + 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, v, host, + data->state.up.path, secure_context); + Curl_share_unlock(data, CURL_LOCK_DATA_COOKIE); + return CURLE_OK; + } +#endif +#ifndef CURL_DISABLE_HSTS + /* If enabled, the header is incoming and this is over HTTPS */ + v = (data->hsts && + ((conn->handler->flags & PROTOPT_SSL) || +#ifdef DEBUGBUILD + /* allow debug builds to circumvent the HTTPS restriction */ + getenv("CURL_HSTS_HTTP") +#else + 0 +#endif + ) + ) ? HD_VAL(hd, hdlen, "Strict-Transport-Security:") : NULL; + if(v) { + CURLcode check = + Curl_hsts_parse(data->hsts, conn->host.name, v); + if(check) + infof(data, "Illegal STS header skipped"); +#ifdef DEBUGBUILD + else + infof(data, "Parsed STS header fine (%zu entries)", + Curl_llist_count(&data->hsts->list)); +#endif + } +#endif + break; + case 't': + case 'T': + /* RFC 9112, ch. 6.1 + * "Transfer-Encoding MAY be sent in a response to a HEAD request or + * in a 304 (Not Modified) response (Section 15.4.5 of [HTTP]) to a + * GET request, neither of which includes a message body, to indicate + * that the origin server would have applied a transfer coding to the + * message body if the request had been an unconditional GET." + * + * Read: in these cases the 'Transfer-Encoding' does not apply + * to any data following the response headers. Do not add any decoders. + */ + v = (!k->http_bodyless && + (data->state.httpreq != HTTPREQ_HEAD) && + (k->httpcode != 304)) ? + HD_VAL(hd, hdlen, "Transfer-Encoding:") : NULL; + if(v) { + /* One or more encodings. We check for chunked and/or a compression + algorithm. */ + result = Curl_build_unencoding_stack(data, v, TRUE); if(result) return result; + if(!k->chunk && data->set.http_transfer_encoding) { + /* if this is not chunked, only close can signal the end of this + * transfer as Content-Length is said not to be trusted for + * transfer-encoding! */ + connclose(conn, "HTTP/1.1 transfer-encoding without chunks"); + k->ignore_cl = TRUE; + } + return CURLE_OK; + } + v = HD_VAL(hd, hdlen, "Trailer:"); + if(v) { + data->req.resp_trailer = TRUE; + return CURLE_OK; + } + break; + case 'w': + case 'W': + if((401 == k->httpcode) && HD_IS(hd, hdlen, "WWW-Authenticate:")) { + char *auth = Curl_copy_header_value(hd); + if(!auth) + return CURLE_OUT_OF_MEMORY; + result = Curl_http_input_auth(data, FALSE, auth); + free(auth); + return result; } + break; + } - /* - * End of header-checks. Write them to the client. - */ + if(conn->handler->protocol & CURLPROTO_RTSP) { + result = Curl_rtsp_parseheader(data, hd); + if(result) + return result; + } + return CURLE_OK; +} - writetype = CLIENTWRITE_HEADER; - if(data->set.include_header) - writetype |= CLIENTWRITE_BODY; +/* + * 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; - if(data->set.verbose) - Curl_debug(data, CURLINFO_HEADER_IN, - k->p, (size_t)k->hbuflen, conn); + switch(k->httpversion) { + case 10: + case 11: +#ifdef USE_HTTP2 + case 20: +#endif +#ifdef USE_HTTP3 + case 30: +#endif + /* no major version switch mid-connection */ + if(conn->httpversion && + (k->httpversion/10 != conn->httpversion/10)) { + failf(data, "Version mismatch (from HTTP/%u to HTTP/%u)", + conn->httpversion/10, k->httpversion/10); + return CURLE_UNSUPPORTED_PROTOCOL; + } + break; + default: + failf(data, "Unsupported HTTP version (%u.%d) in response", + k->httpversion/10, k->httpversion%10); + return CURLE_UNSUPPORTED_PROTOCOL; + } - result = Curl_client_write(conn, writetype, k->p, k->hbuflen); - if(result) - return result; + data->info.httpcode = k->httpcode; + data->info.httpversion = k->httpversion; + conn->httpversion = (unsigned char)k->httpversion; - data->info.header_size += (long)k->hbuflen; - data->req.headerbytecount += (long)k->hbuflen; + if(!data->state.httpversion || data->state.httpversion > k->httpversion) + /* store the lowest server version we encounter */ + data->state.httpversion = (unsigned char)k->httpversion; - /* reset hbufp pointer && hbuflen */ - k->hbufp = data->state.headerbuff; - k->hbuflen = 0; + /* + * This code executes as part of processing the header. As a + * result, it is 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. */ } - while(*k->str); /* header line within buffer */ - /* 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. */ + if(k->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(k->httpversion == 20 || + (k->upgr101 == UPGR101_H2 && k->httpcode == 101)) { + DEBUGF(infof(data, "HTTP/2 found, allow multiplexing")); + } + + 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->ignorebody && + (k->size > data->set.max_filesize)) { + failf(data, "Maximum file size exceeded"); + return CURLE_FILESIZE_EXCEEDED; + } + if(k->ignorebody) + infof(data, "setting size while ignoring"); + Curl_pgrsSetDownloadSize(data, k->size); + k->maxdownload = k->size; + } + return CURLE_OK; +} +static CURLcode verify_header(struct Curl_easy *data, + const char *hd, size_t hdlen) +{ + struct SingleRequest *k = &data->req; + char *ptr = memchr(hd, 0x00, hdlen); + 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(((hd[0] == ' ') || (hd[0] == '\t')) && k->headerline > 2) + /* line folding, cannot happen on line 2 */ + ; + else { + ptr = memchr(hd, ':', hdlen); + 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; } +static CURLcode http_write_header(struct Curl_easy *data, + const char *hd, size_t hdlen) +{ + CURLcode result; + int writetype; + + /* now, only output this if the header AND body are requested: + */ + Curl_debug(data, CURLINFO_HEADER_IN, (char *)hd, hdlen); + + writetype = CLIENTWRITE_HEADER | + ((data->req.httpcode/100 == 1) ? CLIENTWRITE_1XX : 0); + + result = Curl_client_write(data, writetype, hd, hdlen); + if(result) + return result; + + result = Curl_bump_headersize(data, hdlen, FALSE); + if(result) + return result; + + data->req.deductheadercount = (100 <= data->req.httpcode && + 199 >= data->req.httpcode) ? + data->req.headerbytecount : 0; + return result; +} + +static CURLcode http_on_response(struct Curl_easy *data, + const char *last_hd, size_t last_hd_len, + const char *buf, size_t blen, + size_t *pconsumed) +{ + struct connectdata *conn = data->conn; + CURLcode result = CURLE_OK; + struct SingleRequest *k = &data->req; + + (void)buf; /* not used without HTTP2 enabled */ + *pconsumed = 0; + + if(k->upgr101 == UPGR101_RECEIVED) { + /* supposedly upgraded to http2 now */ + if(conn->httpversion != 20) + infof(data, "Lying server, not serving HTTP/2"); + } + + if(k->httpcode < 200 && last_hd) { + /* Intermediate responses might trigger processing of more + * responses, write the last header to the client before + * proceeding. */ + result = http_write_header(data, last_hd, last_hd_len); + last_hd = NULL; /* handled it */ + if(result) + goto out; + } + + if(k->httpcode < 100) { + failf(data, "Unsupported response code in HTTP response"); + result = CURLE_UNSUPPORTED_PROTOCOL; + goto out; + } + else if(k->httpcode < 200) { + /* "A user agent MAY ignore unexpected 1xx status responses." + * By default, we expect to get more responses after this one. */ + k->header = TRUE; + k->headerline = 0; /* restart the header line counter */ + + switch(k->httpcode) { + case 100: + /* + * We have made an HTTP PUT or POST and this is 1.1-lingo + * that tells us that the server is OK with this and ready + * to receive the data. + */ + Curl_http_exp100_got100(data); + break; + case 101: + /* Switching Protocols only allowed from HTTP/1.1 */ + + if(conn->httpversion != 11) { + /* invalid for other HTTP versions */ + failf(data, "unexpected 101 response code"); + result = CURLE_WEIRD_SERVER_REPLY; + goto out; + } + if(k->upgr101 == UPGR101_H2) { + /* Switching to HTTP/2, where we will get more responses */ + infof(data, "Received 101, Switching to HTTP/2"); + k->upgr101 = UPGR101_RECEIVED; + data->conn->bits.asks_multiplex = FALSE; + /* We expect more response from HTTP/2 later */ + k->header = TRUE; + k->headerline = 0; /* restart the header line counter */ + /* Any remaining `buf` bytes are already HTTP/2 and passed to + * be processed. */ + result = Curl_http2_upgrade(data, conn, FIRSTSOCKET, buf, blen); + if(result) + goto out; + *pconsumed += blen; + } +#ifndef CURL_DISABLE_WEBSOCKETS + else if(k->upgr101 == UPGR101_WS) { + /* verify the response. Any passed `buf` bytes are already in + * WebSockets format and taken in by the protocol handler. */ + result = Curl_ws_accept(data, buf, blen); + if(result) + goto out; + *pconsumed += blen; /* ws accept handled the data */ + k->header = FALSE; /* we will not get more responses */ + if(data->set.connect_only) + k->keepon &= ~KEEP_RECV; /* read no more content */ + } +#endif + else { + /* We silently accept this as the final response. + * TODO: this looks, uhm, wrong. What are we switching to if we + * did not ask for an Upgrade? Maybe the application provided an + * `Upgrade: xxx` header? */ + k->header = FALSE; + } + break; + default: + /* The server may send us other 1xx responses, like informative + * 103. This have no influence on request processing and we expect + * to receive a final response eventually. */ + break; + } + goto out; + } + + /* k->httpcode >= 200, final response */ + k->header = FALSE; + + if(k->upgr101 == UPGR101_H2) { + /* A requested upgrade was denied, poke the multi handle to possibly + allow a pending pipewait to continue */ + data->conn->bits.asks_multiplex = FALSE; + Curl_multi_connchanged(data->multi); + } + + if((k->size == -1) && !k->chunk && !conn->bits.close && + (conn->httpversion == 11) && + !(conn->handler->protocol & CURLPROTO_RTSP) && + data->state.httpreq != HTTPREQ_HEAD) { + /* On HTTP 1.1, when connection is not to get closed, but no + Content-Length nor Transfer-Encoding chunked have been + received, according to RFC2616 section 4.4 point 5, we + assume that the server will close the connection to + signal the end of the document. */ + infof(data, "no chunk, no close, no size. Assume close to " + "signal end"); + streamclose(conn, "HTTP: No end-of-message indicator"); + } + + /* At this point we have some idea about the fate of the connection. + 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 + +#ifndef CURL_DISABLE_WEBSOCKETS + /* All >=200 HTTP status codes are errors when wanting WebSockets */ + if(data->req.upgr101 == UPGR101_WS) { + failf(data, "Refused WebSockets upgrade: %d", k->httpcode); + result = CURLE_HTTP_RETURNED_ERROR; + goto out; + } +#endif + + /* Check if this response means the transfer errored. */ + if(http_should_fail(data, data->req.httpcode)) { + failf(data, "The requested URL returned error: %d", + k->httpcode); + result = CURLE_HTTP_RETURNED_ERROR; + goto out; + } + + /* 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) + goto out; + + if(k->httpcode >= 300) { + if((!data->req.authneg) && !conn->bits.close && + !Curl_creader_will_rewind(data)) { + /* + * 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. + * + */ + + 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 have read the entire response. + */ + if(!Curl_req_done_sending(data)) { + if((k->httpcode == 417) && Curl_http_exp100_is_selected(data)) { + /* 417 Expectation Failed - try again without the Expect + header */ + if(!k->writebytecount && http_exp100_is_waiting(data)) { + 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) + goto out; + } + data->state.disableexpect = TRUE; + DEBUGASSERT(!data->req.newurl); + data->req.newurl = strdup(data->state.url); + Curl_req_abort_sending(data); + } + else if(data->set.http_keep_sending_on_error) { + infof(data, "HTTP error before end of send, keep sending"); + http_exp100_send_anyway(data); + } + else { + infof(data, "HTTP error before end of send, stop sending"); + streamclose(conn, "Stop sending data before everything sent"); + result = Curl_req_abort_sending(data); + if(result) + goto out; + } + } + break; + + default: /* default label present to avoid compiler warnings */ + break; + } + } + + if(Curl_creader_will_rewind(data) && !Curl_req_done_sending(data)) { + /* We rewind before next send, continue sending now */ + infof(data, "Keep sending data to get tossed away"); + k->keepon |= KEEP_SEND; + } + + } + + /* If we requested a "no body", this is a good time to get + * out and return home. + */ + if(data->req.no_body) + k->download_done = TRUE; + + /* If max download size is *zero* (nothing) we already have + nothing and can safely return ok now! But for HTTP/2, we would + like to call http2_handle_stream_close to properly close a + stream. In order to do this, we keep reading until we + close the stream. */ + if(0 == k->maxdownload + && !Curl_conn_is_http2(data, conn, FIRSTSOCKET) + && !Curl_conn_is_http3(data, conn, FIRSTSOCKET)) + k->download_done = TRUE; + + /* final response without error, prepare to receive the body */ + result = Curl_http_firstwrite(data); + + if(!result) + /* This is the last response that we get for the current request. + * Check on the body size and determine if the response is complete. + */ + result = Curl_http_size(data); + +out: + if(last_hd) { + /* if not written yet, write it now */ + CURLcode r2 = http_write_header(data, last_hd, last_hd_len); + if(!result) + result = r2; + } + return result; +} + +static CURLcode http_rw_hd(struct Curl_easy *data, + const char *hd, size_t hdlen, + const char *buf_remain, size_t blen, + size_t *pconsumed) +{ + CURLcode result = CURLE_OK; + struct SingleRequest *k = &data->req; + int writetype; + + *pconsumed = 0; + if((0x0a == *hd) || (0x0d == *hd)) { + /* Empty header line means end of headers! */ + struct dynbuf last_header; + size_t consumed; + + Curl_dyn_init(&last_header, hdlen + 1); + result = Curl_dyn_addn(&last_header, hd, hdlen); + if(result) + return result; + + /* analyze the response to find out what to do. */ + /* Caveat: we clear anything in the header brigade, because a + * response might switch HTTP version which may call use recursively. + * Not nice, but that is currently the way of things. */ + Curl_dyn_reset(&data->state.headerb); + result = http_on_response(data, Curl_dyn_ptr(&last_header), + Curl_dyn_len(&last_header), + buf_remain, blen, &consumed); + *pconsumed += consumed; + Curl_dyn_free(&last_header); + return result; + } + + /* + * Checks for special headers coming up. + */ + + writetype = CLIENTWRITE_HEADER; + if(!k->headerline++) { + /* This is the first header, it MUST be the error code line + or else we consider this to be the body right away! */ + bool fine_statusline = FALSE; + + k->httpversion = 0; /* Do not know yet */ + if(data->conn->handler->protocol & PROTO_FAMILY_HTTP) { + /* + * https://datatracker.ietf.org/doc/html/rfc7230#section-3.1.2 + * + * The response code is always a three-digit number in HTTP as the spec + * says. We allow any three-digit number here, but we cannot make + * guarantees on future behaviors since it is not within the protocol. + */ + const char *p = hd; + + while(*p && ISBLANK(*p)) + p++; + if(!strncmp(p, "HTTP/", 5)) { + p += 5; + switch(*p) { + case '1': + p++; + if((p[0] == '.') && (p[1] == '0' || p[1] == '1')) { + if(ISBLANK(p[2])) { + k->httpversion = 10 + (p[1] - '0'); + p += 3; + if(ISDIGIT(p[0]) && ISDIGIT(p[1]) && ISDIGIT(p[2])) { + k->httpcode = (p[0] - '0') * 100 + (p[1] - '0') * 10 + + (p[2] - '0'); + p += 3; + if(ISSPACE(*p)) + fine_statusline = TRUE; + } + } + } + if(!fine_statusline) { + failf(data, "Unsupported HTTP/1 subversion in response"); + return CURLE_UNSUPPORTED_PROTOCOL; + } + break; + case '2': + case '3': + if(!ISBLANK(p[1])) + break; + k->httpversion = (*p - '0') * 10; + p += 2; + if(ISDIGIT(p[0]) && ISDIGIT(p[1]) && ISDIGIT(p[2])) { + k->httpcode = (p[0] - '0') * 100 + (p[1] - '0') * 10 + + (p[2] - '0'); + p += 3; + if(!ISSPACE(*p)) + break; + fine_statusline = TRUE; + } + break; + default: /* unsupported */ + failf(data, "Unsupported HTTP version in response"); + return CURLE_UNSUPPORTED_PROTOCOL; + } + } + + if(!fine_statusline) { + /* If user has set option HTTP200ALIASES, + compare header line against list of aliases + */ + statusline check = checkhttpprefix(data, hd, hdlen); + if(check == STATUS_DONE) { + fine_statusline = TRUE; + k->httpcode = 200; + k->httpversion = 10; + } + } + } + else if(data->conn->handler->protocol & CURLPROTO_RTSP) { + const char *p = hd; + while(*p && ISBLANK(*p)) + p++; + if(!strncmp(p, "RTSP/", 5)) { + p += 5; + if(ISDIGIT(*p)) { + p++; + if((p[0] == '.') && ISDIGIT(p[1])) { + if(ISBLANK(p[2])) { + p += 3; + if(ISDIGIT(p[0]) && ISDIGIT(p[1]) && ISDIGIT(p[2])) { + k->httpcode = (p[0] - '0') * 100 + (p[1] - '0') * 10 + + (p[2] - '0'); + p += 3; + if(ISSPACE(*p)) { + fine_statusline = TRUE; + k->httpversion = 11; /* RTSP acts like HTTP 1.1 */ + } + } + } + } + } + if(!fine_statusline) + return CURLE_WEIRD_SERVER_REPLY; + } + } + + if(fine_statusline) { + result = Curl_http_statusline(data, data->conn); + if(result) + return result; + writetype |= CLIENTWRITE_STATUS; + } + else { + k->header = FALSE; /* this is not a header line */ + return CURLE_WEIRD_SERVER_REPLY; + } + } + + result = verify_header(data, hd, hdlen); + if(result) + return result; + + result = Curl_http_header(data, hd, hdlen); + if(result) + return result; + + /* + * Taken in one (more) header. Write it to the client. + */ + Curl_debug(data, CURLINFO_HEADER_IN, (char *)hd, hdlen); + + if(k->httpcode/100 == 1) + writetype |= CLIENTWRITE_1XX; + result = Curl_client_write(data, writetype, hd, hdlen); + if(result) + return result; + + result = Curl_bump_headersize(data, hdlen, FALSE); + if(result) + return result; + + return CURLE_OK; +} + +/* + * Read any HTTP header lines from the server and pass them to the client app. + */ +static CURLcode http_parse_headers(struct Curl_easy *data, + const char *buf, size_t blen, + size_t *pconsumed) +{ + struct connectdata *conn = data->conn; + CURLcode result = CURLE_OK; + struct SingleRequest *k = &data->req; + char *end_ptr; + bool leftover_body = FALSE; + + /* header line within buffer loop */ + *pconsumed = 0; + while(blen && k->header) { + size_t consumed; + + end_ptr = memchr(buf, '\n', blen); + if(!end_ptr) { + /* Not a complete header line within buffer, append the data to + the end of the headerbuff. */ + 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; + streamclose(conn, "bad HTTP: No end-of-message indicator"); + if(conn->httpversion >= 10) { + failf(data, "Invalid status line"); + return CURLE_WEIRD_SERVER_REPLY; + } + if(!data->set.http09_allowed) { + failf(data, "Received HTTP/0.9 when not allowed"); + return CURLE_UNSUPPORTED_PROTOCOL; + } + leftover_body = TRUE; + goto out; + } + } + goto out; /* read more and try again */ + } + + /* decrease the size of the remaining (supposed) header line */ + consumed = (end_ptr - buf) + 1; + result = Curl_dyn_addn(&data->state.headerb, buf, consumed); + if(result) + return result; + blen -= consumed; + buf += consumed; + *pconsumed += consumed; + + /**** + * 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(conn->httpversion >= 10) { + failf(data, "Invalid status line"); + return CURLE_WEIRD_SERVER_REPLY; + } + if(!data->set.http09_allowed) { + failf(data, "Received HTTP/0.9 when not allowed"); + return CURLE_UNSUPPORTED_PROTOCOL; + } + k->header = FALSE; + leftover_body = TRUE; + goto out; + } + } + + result = http_rw_hd(data, Curl_dyn_ptr(&data->state.headerb), + Curl_dyn_len(&data->state.headerb), + buf, blen, &consumed); + /* We are done with this line. We reset because response + * processing might switch to HTTP/2 and that might call us + * directly again. */ + Curl_dyn_reset(&data->state.headerb); + if(consumed) { + blen -= consumed; + buf += consumed; + *pconsumed += consumed; + } + if(result) + return result; + } + + /* 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: + if(!k->header && !leftover_body) { + Curl_dyn_free(&data->state.headerb); + } + return CURLE_OK; +} + +CURLcode Curl_http_write_resp_hd(struct Curl_easy *data, + const char *hd, size_t hdlen, + bool is_eos) +{ + CURLcode result; + size_t consumed; + char tmp = 0; + + result = http_rw_hd(data, hd, hdlen, &tmp, 0, &consumed); + if(!result && is_eos) { + result = Curl_client_write(data, (CLIENTWRITE_BODY|CLIENTWRITE_EOS), + &tmp, 0); + } + return result; +} + +/* + * HTTP protocol `write_resp` implementation. Will parse headers + * when not done yet and otherwise return without consuming data. + */ +CURLcode Curl_http_write_resp_hds(struct Curl_easy *data, + const char *buf, size_t blen, + size_t *pconsumed) +{ + if(!data->req.header) { + *pconsumed = 0; + return CURLE_OK; + } + else { + CURLcode result; + + result = http_parse_headers(data, buf, blen, pconsumed); + if(!result && !data->req.header) { + if(!data->req.no_body && Curl_dyn_len(&data->state.headerb)) { + /* leftover from parsing something that turned out not + * to be a header, only happens if we allow for + * HTTP/0.9 like responses */ + result = Curl_client_write(data, CLIENTWRITE_BODY, + Curl_dyn_ptr(&data->state.headerb), + Curl_dyn_len(&data->state.headerb)); + } + Curl_dyn_free(&data->state.headerb); + } + return result; + } +} + +CURLcode Curl_http_write_resp(struct Curl_easy *data, + const char *buf, size_t blen, + bool is_eos) +{ + CURLcode result; + size_t consumed; + int flags; + + result = Curl_http_write_resp_hds(data, buf, blen, &consumed); + if(result || data->req.done) + goto out; + + DEBUGASSERT(consumed <= blen); + blen -= consumed; + buf += consumed; + /* either all was consumed in header parsing, or we have data left + * and are done with headers, e.g. it is BODY data */ + DEBUGASSERT(!blen || !data->req.header); + if(!data->req.header && (blen || is_eos)) { + /* BODY data after header been parsed, write and consume */ + flags = CLIENTWRITE_BODY; + if(is_eos) + flags |= CLIENTWRITE_EOS; + result = Curl_client_write(data, flags, (char *)buf, blen); + } +out: + return result; +} + +/* 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_memdup0(scheme, s_len); + if(!req->scheme) + goto out; + } + if(authority) { + req->authority = Curl_memdup0(authority, a_len); + if(!req->authority) + goto out; + } + if(path) { + req->path = Curl_memdup0(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); + } +} + +struct cr_exp100_ctx { + struct Curl_creader super; + struct curltime start; /* time started waiting */ + enum expect100 state; +}; + +/* Expect: 100-continue client reader, blocking uploads */ + +static void http_exp100_continue(struct Curl_easy *data, + struct Curl_creader *reader) +{ + struct cr_exp100_ctx *ctx = reader->ctx; + if(ctx->state > EXP100_SEND_DATA) { + ctx->state = EXP100_SEND_DATA; + data->req.keepon |= KEEP_SEND; + data->req.keepon &= ~KEEP_SEND_TIMED; + Curl_expire_done(data, EXPIRE_100_TIMEOUT); + } +} + +static CURLcode cr_exp100_read(struct Curl_easy *data, + struct Curl_creader *reader, + char *buf, size_t blen, + size_t *nread, bool *eos) +{ + struct cr_exp100_ctx *ctx = reader->ctx; + timediff_t ms; + + switch(ctx->state) { + case EXP100_SENDING_REQUEST: + if(!Curl_req_sendbuf_empty(data)) { + /* The initial request data has not been fully sent yet. Do + * not start the timer yet. */ + DEBUGF(infof(data, "cr_exp100_read, request not full sent yet")); + *nread = 0; + *eos = FALSE; + return CURLE_OK; + } + /* We are now waiting for a reply from the server or + * a timeout on our side IFF the request has been fully sent. */ + DEBUGF(infof(data, "cr_exp100_read, start AWAITING_CONTINUE, " + "timeout %ldms", data->set.expect_100_timeout)); + ctx->state = EXP100_AWAITING_CONTINUE; + ctx->start = Curl_now(); + Curl_expire(data, data->set.expect_100_timeout, EXPIRE_100_TIMEOUT); + data->req.keepon &= ~KEEP_SEND; + data->req.keepon |= KEEP_SEND_TIMED; + *nread = 0; + *eos = FALSE; + return CURLE_OK; + case EXP100_FAILED: + DEBUGF(infof(data, "cr_exp100_read, expectation failed, error")); + *nread = 0; + *eos = FALSE; + return CURLE_READ_ERROR; + case EXP100_AWAITING_CONTINUE: + ms = Curl_timediff(Curl_now(), ctx->start); + if(ms < data->set.expect_100_timeout) { + DEBUGF(infof(data, "cr_exp100_read, AWAITING_CONTINUE, not expired")); + data->req.keepon &= ~KEEP_SEND; + data->req.keepon |= KEEP_SEND_TIMED; + *nread = 0; + *eos = FALSE; + return CURLE_OK; + } + /* we have waited long enough, continue anyway */ + http_exp100_continue(data, reader); + infof(data, "Done waiting for 100-continue"); + FALLTHROUGH(); + default: + DEBUGF(infof(data, "cr_exp100_read, pass through")); + return Curl_creader_read(data, reader->next, buf, blen, nread, eos); + } +} + +static void cr_exp100_done(struct Curl_easy *data, + struct Curl_creader *reader, int premature) +{ + struct cr_exp100_ctx *ctx = reader->ctx; + ctx->state = premature ? EXP100_FAILED : EXP100_SEND_DATA; + data->req.keepon &= ~KEEP_SEND_TIMED; + Curl_expire_done(data, EXPIRE_100_TIMEOUT); +} + +static const struct Curl_crtype cr_exp100 = { + "cr-exp100", + Curl_creader_def_init, + cr_exp100_read, + Curl_creader_def_close, + Curl_creader_def_needs_rewind, + Curl_creader_def_total_length, + Curl_creader_def_resume_from, + Curl_creader_def_rewind, + Curl_creader_def_unpause, + Curl_creader_def_is_paused, + cr_exp100_done, + sizeof(struct cr_exp100_ctx) +}; + +static CURLcode http_exp100_add_reader(struct Curl_easy *data) +{ + struct Curl_creader *reader = NULL; + CURLcode result; + + result = Curl_creader_create(&reader, data, &cr_exp100, + CURL_CR_PROTOCOL); + if(!result) + result = Curl_creader_add(data, reader); + if(!result) { + struct cr_exp100_ctx *ctx = reader->ctx; + ctx->state = EXP100_SENDING_REQUEST; + } + + if(result && reader) + Curl_creader_free(data, reader); + return result; +} + +void Curl_http_exp100_got100(struct Curl_easy *data) +{ + struct Curl_creader *r = Curl_creader_get_by_type(data, &cr_exp100); + if(r) + http_exp100_continue(data, r); +} + +static bool http_exp100_is_waiting(struct Curl_easy *data) +{ + struct Curl_creader *r = Curl_creader_get_by_type(data, &cr_exp100); + if(r) { + struct cr_exp100_ctx *ctx = r->ctx; + return (ctx->state == EXP100_AWAITING_CONTINUE); + } + return FALSE; +} + +static void http_exp100_send_anyway(struct Curl_easy *data) +{ + struct Curl_creader *r = Curl_creader_get_by_type(data, &cr_exp100); + if(r) + http_exp100_continue(data, r); +} + +bool Curl_http_exp100_is_selected(struct Curl_easy *data) +{ + struct Curl_creader *r = Curl_creader_get_by_type(data, &cr_exp100); + return r ? TRUE : FALSE; +} + #endif /* CURL_DISABLE_HTTP */ diff --git a/contrib/curl/lib/http.h b/contrib/curl/lib/http.h index 7ce4bd9a0f..bb5974d94d 100644 --- a/contrib/curl/lib/http.h +++ b/contrib/curl/lib/http.h @@ -7,11 +7,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2015, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -20,13 +20,32 @@ * 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 +#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 -#ifdef USE_NGHTTP2 -#include +#if defined(USE_HTTP3) +#include #endif extern const struct Curl_handler Curl_handler_http; @@ -35,69 +54,99 @@ extern const struct Curl_handler Curl_handler_http; extern const struct Curl_handler Curl_handler_https; #endif +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 char *content); /* content string to find */ + 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_checkheaders(const struct connectdata *conn, - const char *thisheader); char *Curl_copy_header_value(const char *header); -char *Curl_checkProxyheaders(const struct connectdata *conn, - const char *thisheader); -/* ------------------------------------------------------------------------- */ -/* - * The add_buffer series of functions are used to build one large memory chunk - * from repeated function invokes. Used so that the entire HTTP request can - * be sent in one go. - */ -struct Curl_send_buffer { - char *buffer; - size_t size_max; - size_t size_used; -}; -typedef struct Curl_send_buffer Curl_send_buffer; - -Curl_send_buffer *Curl_add_buffer_init(void); -void Curl_add_buffer_free(Curl_send_buffer *buff); -CURLcode Curl_add_bufferf(Curl_send_buffer *in, const char *fmt, ...); -CURLcode Curl_add_buffer(Curl_send_buffer *in, const void *inptr, size_t size); -CURLcode Curl_add_buffer_send(Curl_send_buffer *in, - struct connectdata *conn, - long *bytes_written, - size_t included_body_bytes, - int socketindex); +char *Curl_checkProxyheaders(struct Curl_easy *data, + const struct connectdata *conn, + const char *thisheader, + const size_t thislen); CURLcode Curl_add_timecondition(struct Curl_easy *data, - Curl_send_buffer *buf); -CURLcode Curl_add_custom_headers(struct connectdata *conn, +#ifndef USE_HYPER + struct dynbuf *req +#else + void *headers +#endif + ); +CURLcode Curl_add_custom_headers(struct Curl_easy *data, bool is_connect, - Curl_send_buffer *req_buffer); +#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); + +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, + const char *hd, size_t hdlen); +CURLcode Curl_transferencode(struct Curl_easy *data); +CURLcode Curl_http_req_set_reader(struct Curl_easy *data, + Curl_HttpReq httpreq, + const char **tep); +CURLcode Curl_http_req_complete(struct Curl_easy *data, + 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_range(struct Curl_easy *data, + Curl_HttpReq httpreq); +CURLcode Curl_http_firstwrite(struct Curl_easy *data); /* protocol-specific functions set up to be called by the main engine */ -CURLcode Curl_http(struct connectdata *conn, bool *done); -CURLcode Curl_http_done(struct connectdata *, CURLcode, bool premature); -CURLcode Curl_http_connect(struct connectdata *conn, bool *done); -CURLcode Curl_http_setup_conn(struct connectdata *conn); - -/* The following functions are defined in http_chunks.c */ -void Curl_httpchunk_init(struct connectdata *conn); -CHUNKcode Curl_httpchunk_read(struct connectdata *conn, char *datap, - ssize_t length, ssize_t *wrote); +CURLcode Curl_http_setup_conn(struct Curl_easy *data, + struct connectdata *conn); +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); +int Curl_http_getsock_do(struct Curl_easy *data, struct connectdata *conn, + curl_socket_t *socks); +CURLcode Curl_http_write_resp(struct Curl_easy *data, + const char *buf, size_t blen, + bool is_eos); +CURLcode Curl_http_write_resp_hd(struct Curl_easy *data, + const char *hd, size_t hdlen, + bool is_eos); /* These functions are in http.c */ -void Curl_http_auth_stage(struct Curl_easy *data, int stage); -CURLcode Curl_http_input_auth(struct connectdata *conn, bool proxy, +CURLcode Curl_http_input_auth(struct Curl_easy *data, bool proxy, const char *auth); -CURLcode Curl_http_auth_act(struct connectdata *conn); -CURLcode Curl_http_perhapsrewind(struct connectdata *conn); +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 */ +#define CURLAUTH_PICKNONE (1<<30) /* do not 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 @@ -106,7 +155,7 @@ CURLcode Curl_http_perhapsrewind(struct connectdata *conn); 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 uncondtionally sent and thus it may not be larger than can + 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. @@ -115,132 +164,45 @@ CURLcode Curl_http_perhapsrewind(struct connectdata *conn); #define MAX_INITIAL_POST_SIZE (64*1024) #endif -#ifndef TINY_INITIAL_POST_SIZE -#define TINY_INITIAL_POST_SIZE 1024 +/* 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) + +bool Curl_http_exp100_is_selected(struct Curl_easy *data); +void Curl_http_exp100_got100(struct Curl_easy *data); + #endif /* CURL_DISABLE_HTTP */ /**************************************************************************** * HTTP unique setup ***************************************************************************/ -struct HTTP { - struct FormData *sendit; - curl_off_t postsize; /* off_t to handle large file sizes */ - const char *postdata; - - const char *p_pragma; /* Pragma: string */ - const char *p_accept; /* Accept: string */ - curl_off_t readbytecount; - curl_off_t writebytecount; - - /* For FORM posting */ - struct Form form; - - 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; - } backup; - - enum { - HTTPSEND_NADA, /* init */ - HTTPSEND_REQUEST, /* sending a request */ - HTTPSEND_BODY, /* sending body */ - HTTPSEND_LAST /* never use this */ - } sending; - - void *send_buffer; /* used if the request couldn't be sent in one chunk, - points to an allocated send_buffer struct */ - -#ifdef USE_NGHTTP2 - /*********** for HTTP/2 we store stream-local data here *************/ - int32_t stream_id; /* stream we are interested in */ - - bool bodystarted; - /* We store non-final and final response headers here, per-stream */ - Curl_send_buffer *header_recvbuf; - size_t nread_header_recvbuf; /* number of bytes in header_recvbuf fed into - upper layer */ - Curl_send_buffer *trailer_recvbuf; - int status_code; /* HTTP status code */ - const uint8_t *pausedata; /* pointer to data received in on_data_chunk */ - size_t pauselen; /* the number of bytes left in data */ - bool closed; /* TRUE on HTTP2 stream close */ - bool close_handled; /* TRUE if stream closure is handled by libcurl */ - uint32_t error_code; /* HTTP/2 error code */ - - char *mem; /* points to a buffer in memory to store received data */ - size_t len; /* size of the buffer 'mem' points to */ - size_t memlen; /* size of data copied to mem */ - - const uint8_t *upload_mem; /* points to a buffer to read from */ - size_t upload_len; /* size of the buffer 'upload_mem' points to */ - curl_off_t upload_left; /* number of 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 */ -#endif -}; - -typedef int (*sending)(void); /* Curl_send */ -typedef int (*recving)(void); /* Curl_recv */ -#ifdef USE_NGHTTP2 -/* h2 settings for this connection */ -struct h2settings { - uint32_t max_concurrent_streams; - bool enable_push; -}; -#endif - - -struct http_conn { -#ifdef USE_NGHTTP2 -#define H2_BINSETTINGS_LEN 80 - nghttp2_session *h2; - uint8_t binsettings[H2_BINSETTINGS_LEN]; - size_t binlen; /* length of the binsettings data */ - sending send_underlying; /* underlying send Curl_send callback */ - recving recv_underlying; /* underlying recv Curl_recv callback */ - char *inbuf; /* buffer to receive data from underlying socket */ - size_t inbuflen; /* number of bytes filled in inbuf */ - size_t nread_inbuf; /* number of bytes read from in inbuf */ - /* We need separate buffer for transmission and reception because we - may call nghttp2_session_send() after the - nghttp2_session_mem_recv() but mem buffer is still not full. In - this case, we wrongly sends the content of mem buffer if we share - them for both cases. */ - int32_t pause_stream_id; /* stream ID which paused - nghttp2_session_mem_recv */ - size_t drain_total; /* sum of all stream's UrlState.drain */ - - /* this is a hash of all individual streams (Curl_easy structs) */ - struct h2settings settings; - - /* list of settings that will be sent */ - nghttp2_settings_entry local_settings[3]; - size_t local_settings_num; -#else - int unused; /* prevent a compiler warning */ -#endif -}; +CURLcode Curl_http_size(struct Curl_easy *data); -CURLcode Curl_http_readwrite_headers(struct Curl_easy *data, - struct connectdata *conn, - ssize_t *nread, - bool *stop_reading); +CURLcode Curl_http_write_resp_hds(struct Curl_easy *data, + 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. conn->data->state.authdone is set to TRUE when authentication is + * 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" @@ -248,11 +210,87 @@ CURLcode Curl_http_readwrite_headers(struct Curl_easy *data, * @returns CURLcode */ CURLcode -Curl_http_output_auth(struct connectdata *conn, +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 */ -#endif /* HEADER_CURL_HTTP_H */ +/* 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 an 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 an 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/contrib/curl/lib/http1.c b/contrib/curl/lib/http1.c new file mode 100644 index 0000000000..f135b2072a --- /dev/null +++ b/contrib/curl/lib/http1.c @@ -0,0 +1,346 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#ifndef CURL_DISABLE_HTTP + +#include "urldata.h" +#include +#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)) { + unsigned 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/contrib/curl/lib/http1.h b/contrib/curl/lib/http1.h new file mode 100644 index 0000000000..2de302f1f6 --- /dev/null +++ b/contrib/curl/lib/http1.h @@ -0,0 +1,63 @@ +#ifndef HEADER_CURL_HTTP1_H +#define HEADER_CURL_HTTP1_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#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/contrib/curl/lib/http2.c b/contrib/curl/lib/http2.c index af69c72245..2c024ca55e 100644 --- a/contrib/curl/lib/http2.c +++ b/contrib/curl/lib/http2.c @@ -5,11 +5,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2017, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -18,44 +18,46 @@ * 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 #include #include "urldata.h" +#include "bufq.h" +#include "hash.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 "conncache.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" -#define MIN(x,y) ((x)<(y)?(x):(y)) - -#if (NGHTTP2_VERSION_NUM < 0x010000) +#if (NGHTTP2_VERSION_NUM < 0x010c00) #error too old nghttp2 version, upgrade! #endif -#if (NGHTTP2_VERSION_NUM > 0x010800) -#define NGHTTP2_HAS_HTTP2_STRERROR 1 -#endif - -#if (NGHTTP2_VERSION_NUM >= 0x010900) -/* nghttp2_session_callbacks_set_error_callback is present in nghttp2 1.9.0 or - later */ -#define NGHTTP2_HAS_ERROR_CALLBACK 1 -#else +#ifdef CURL_DISABLE_VERBOSE_STRINGS #define nghttp2_session_callbacks_set_error_callback(x,y) #endif @@ -63,201 +65,709 @@ #define NGHTTP2_HAS_SET_LOCAL_WINDOW_SIZE 1 #endif -#define HTTP2_HUGE_WINDOW_SIZE (1 << 30) -/* - * Curl_http2_init_state() is called when the easy handle is created and - * allows for HTTP/2 specific init of state. - */ -void Curl_http2_init_state(struct UrlState *state) +/* buffer dimensioning: + * use 16K as chunk size, as that fits H2 DATA frames well */ +#define H2_CHUNK_SIZE (16 * 1024) +/* connection window size */ +#define H2_CONN_WINDOW_SIZE (10 * 1024 * 1024) +/* on receiving from TLS, we prep for holding a full stream window */ +#define H2_NW_RECV_CHUNKS (H2_CONN_WINDOW_SIZE / H2_CHUNK_SIZE) +/* on send into TLS, we just want to accumulate small frames */ +#define H2_NW_SEND_CHUNKS 1 +/* this is how much we want "in flight" for a stream, unthrottled */ +#define H2_STREAM_WINDOW_SIZE_MAX (10 * 1024 * 1024) +/* this is how much we want "in flight" for a stream, initially, IFF + * nghttp2 allows us to tweak the local window size. */ +#if NGHTTP2_HAS_SET_LOCAL_WINDOW_SIZE +#define H2_STREAM_WINDOW_SIZE_INITIAL (64 * 1024) +#else +#define H2_STREAM_WINDOW_SIZE_INITIAL H2_STREAM_WINDOW_SIZE_MAX +#endif +/* 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_CONN_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. 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_MAX) + +#define H2_SETTINGS_IV_LEN 3 +#define H2_BINSETTINGS_LEN 80 + +static size_t populate_settings(nghttp2_settings_entry *iv, + struct Curl_easy *data) { - state->stream_weight = NGHTTP2_DEFAULT_WEIGHT; + 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_INITIAL; + + iv[2].settings_id = NGHTTP2_SETTINGS_ENABLE_PUSH; + iv[2].value = data->multi->push_cb != NULL; + + return 3; } -/* - * Curl_http2_init_userset() is called when the easy handle is created and - * allows for HTTP/2 specific user-set fields. +static ssize_t populate_binsettings(uint8_t *binsettings, + struct Curl_easy *data) +{ + nghttp2_settings_entry iv[H2_SETTINGS_IV_LEN]; + size_t 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; + /* 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 */ + struct dynbuf scratch; /* scratch buffer for temp use */ + + struct Curl_hash streams; /* hash of `data->mid` to `h2_stream_ctx` */ + size_t drain_total; /* sum of all stream's UrlState drain */ + uint32_t max_concurrent_streams; + uint32_t goaway_error; /* goaway error code from server */ + int32_t remote_max_sid; /* max id processed by server */ + int32_t local_max_sid; /* max id processed by us */ + BIT(initialized); + BIT(via_h1_upgrade); + BIT(conn_closed); + BIT(rcvd_goaway); + BIT(sent_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 h2_stream_hash_free(void *stream); + +static void cf_h2_ctx_init(struct cf_h2_ctx *ctx, bool via_h1_upgrade) +{ + Curl_bufcp_init(&ctx->stream_bufcp, H2_CHUNK_SIZE, H2_STREAM_POOL_SPARES); + Curl_bufq_initp(&ctx->inbufq, &ctx->stream_bufcp, H2_NW_RECV_CHUNKS, 0); + Curl_bufq_initp(&ctx->outbufq, &ctx->stream_bufcp, H2_NW_SEND_CHUNKS, 0); + Curl_dyn_init(&ctx->scratch, CURL_MAX_HTTP_HEADER); + Curl_hash_offt_init(&ctx->streams, 63, h2_stream_hash_free); + ctx->remote_max_sid = 2147483647; + ctx->via_h1_upgrade = via_h1_upgrade; + ctx->initialized = TRUE; +} + +static void cf_h2_ctx_free(struct cf_h2_ctx *ctx) +{ + if(ctx && ctx->initialized) { + Curl_bufq_free(&ctx->inbufq); + Curl_bufq_free(&ctx->outbufq); + Curl_bufcp_free(&ctx->stream_bufcp); + Curl_dyn_free(&ctx->scratch); + Curl_hash_clean(&ctx->streams); + Curl_hash_destroy(&ctx->streams); + memset(ctx, 0, sizeof(*ctx)); + } + free(ctx); +} + +static void cf_h2_ctx_close(struct cf_h2_ctx *ctx) +{ + if(ctx->h2) { + nghttp2_session_del(ctx->h2); + } +} + +static CURLcode h2_progress_egress(struct Curl_cfilter *cf, + struct Curl_easy *data); + +/** + * All about the H2 internals of a stream */ -void Curl_http2_init_userset(struct UserDefined *set) +struct h2_stream_ctx { + 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 */ + curl_off_t nrcvd_data; /* number of DATA bytes received */ + + 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 */ + CURLcode xfer_result; /* Result of writing out response */ + int32_t local_window_size; /* the local recv window size */ + int32_t id; /* HTTP/2 protocol identifier for stream */ + BIT(resp_hds_complete); /* we have a complete, final response */ + BIT(closed); /* TRUE on stream close */ + BIT(reset); /* TRUE on stream reset */ + BIT(close_handled); /* TRUE if stream closure is handled by libcurl */ + BIT(bodystarted); + BIT(body_eos); /* the complete body has been added to `sendbuf` and + * is being/has been processed from there. */ +}; + +#define H2_STREAM_CTX(ctx,data) ((struct h2_stream_ctx *)(\ + data? Curl_hash_offt_get(&(ctx)->streams, (data)->mid) : NULL)) + +static struct h2_stream_ctx *h2_stream_ctx_create(struct cf_h2_ctx *ctx) +{ + struct h2_stream_ctx *stream; + + (void)ctx; + stream = calloc(1, sizeof(*stream)); + if(!stream) + return NULL; + + stream->id = -1; + Curl_bufq_initp(&stream->sendbuf, &ctx->stream_bufcp, + H2_STREAM_SEND_CHUNKS, BUFQ_OPT_NONE); + Curl_h1_req_parse_init(&stream->h1, H1_PARSE_DEFAULT_MAX_LINE_LEN); + Curl_dynhds_init(&stream->resp_trailers, 0, DYN_HTTP_REQUEST); + stream->resp_hds_len = 0; + stream->bodystarted = FALSE; + stream->status_code = -1; + stream->closed = FALSE; + stream->close_handled = FALSE; + stream->error = NGHTTP2_NO_ERROR; + stream->local_window_size = H2_STREAM_WINDOW_SIZE_INITIAL; + stream->nrcvd_data = 0; + return stream; +} + +static void free_push_headers(struct h2_stream_ctx *stream) { - set->stream_weight = NGHTTP2_DEFAULT_WEIGHT; + size_t i; + for(i = 0; i < stream->push_headers_used; i++) + free(stream->push_headers[i]); + Curl_safefree(stream->push_headers); + stream->push_headers_used = 0; } -static int http2_perform_getsock(const struct connectdata *conn, - curl_socket_t *sock, /* points to - numsocks - number of - sockets */ - int numsocks) +static void h2_stream_ctx_free(struct h2_stream_ctx *stream) { - const struct http_conn *c = &conn->proto.httpc; - int bitmap = GETSOCK_BLANK; - (void)numsocks; + Curl_bufq_free(&stream->sendbuf); + Curl_h1_req_parse_free(&stream->h1); + Curl_dynhds_free(&stream->resp_trailers); + free_push_headers(stream); + free(stream); +} - /* TODO We should check underlying socket state if it is SSL socket - because of renegotiation. */ - sock[0] = conn->sock[FIRSTSOCKET]; +static void h2_stream_hash_free(void *stream) +{ + DEBUGASSERT(stream); + h2_stream_ctx_free((struct h2_stream_ctx *)stream); +} - /* in a HTTP/2 connection we can basically always get a frame so we should - always be ready for one */ - bitmap |= GETSOCK_READSOCK(FIRSTSOCKET); +#ifdef NGHTTP2_HAS_SET_LOCAL_WINDOW_SIZE +static int32_t cf_h2_get_desired_local_win(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + (void)cf; + if(data->set.max_recv_speed && data->set.max_recv_speed < INT32_MAX) { + /* The transfer should only receive `max_recv_speed` bytes per second. + * We restrict the stream's local window size, so that the server cannot + * send us "too much" at a time. + * This gets less precise the higher the latency. */ + return (int32_t)data->set.max_recv_speed; + } + return H2_STREAM_WINDOW_SIZE_MAX; +} - if(nghttp2_session_want_write(c->h2)) - bitmap |= GETSOCK_WRITESOCK(FIRSTSOCKET); +static CURLcode cf_h2_update_local_win(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct h2_stream_ctx *stream, + bool paused) +{ + struct cf_h2_ctx *ctx = cf->ctx; + int32_t dwsize; + int rv; - return bitmap; + dwsize = paused ? 0 : cf_h2_get_desired_local_win(cf, data); + if(dwsize != stream->local_window_size) { + int32_t wsize = nghttp2_session_get_stream_effective_local_window_size( + ctx->h2, stream->id); + if(dwsize > wsize) { + rv = nghttp2_submit_window_update(ctx->h2, NGHTTP2_FLAG_NONE, + stream->id, dwsize - wsize); + if(rv) { + failf(data, "[%d] nghttp2_submit_window_update() failed: " + "%s(%d)", stream->id, nghttp2_strerror(rv), rv); + return CURLE_HTTP2; + } + stream->local_window_size = dwsize; + CURL_TRC_CF(data, cf, "[%d] local window update by %d", + stream->id, dwsize - wsize); + } + else { + rv = nghttp2_session_set_local_window_size(ctx->h2, NGHTTP2_FLAG_NONE, + stream->id, dwsize); + if(rv) { + failf(data, "[%d] nghttp2_session_set_local_window_size() failed: " + "%s(%d)", stream->id, nghttp2_strerror(rv), rv); + return CURLE_HTTP2; + } + stream->local_window_size = dwsize; + CURL_TRC_CF(data, cf, "[%d] local window size now %d", + stream->id, dwsize); + } + } + return CURLE_OK; } -static int http2_getsock(struct connectdata *conn, - curl_socket_t *sock, /* points to numsocks - number of sockets */ - int numsocks) +#else /* NGHTTP2_HAS_SET_LOCAL_WINDOW_SIZE */ + +static CURLcode cf_h2_update_local_win(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct h2_stream_ctx *stream, + bool paused) { - return http2_perform_getsock(conn, sock, numsocks); + (void)cf; + (void)data; + (void)stream; + (void)paused; + return CURLE_OK; } +#endif /* !NGHTTP2_HAS_SET_LOCAL_WINDOW_SIZE */ /* - * http2_stream_free() free HTTP2 stream related data + * Mark this transfer to get "drained". */ -static void http2_stream_free(struct HTTP *http) +static void drain_stream(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct h2_stream_ctx *stream) { - if(http) { - Curl_add_buffer_free(http->header_recvbuf); - http->header_recvbuf = NULL; /* clear the pointer */ - Curl_add_buffer_free(http->trailer_recvbuf); - http->trailer_recvbuf = NULL; /* clear the pointer */ - for(; http->push_headers_used > 0; --http->push_headers_used) { - free(http->push_headers[http->push_headers_used - 1]); - } - free(http->push_headers); - http->push_headers = NULL; + unsigned char bits; + + (void)cf; + bits = CURL_CSELECT_IN; + if(!stream->closed && + (!stream->body_eos || !Curl_bufq_is_empty(&stream->sendbuf))) + bits |= CURL_CSELECT_OUT; + if(stream->closed || (data->state.select_bits != bits)) { + CURL_TRC_CF(data, cf, "[%d] DRAIN select_bits=%x", + stream->id, bits); + data->state.select_bits = bits; + Curl_expire(data, 0, EXPIRE_RUN_NOW); } } -static CURLcode http2_disconnect(struct connectdata *conn, - bool dead_connection) +static CURLcode http2_data_setup(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct h2_stream_ctx **pstream) { - struct http_conn *c = &conn->proto.httpc; - (void)dead_connection; + struct cf_h2_ctx *ctx = cf->ctx; + struct h2_stream_ctx *stream; - DEBUGF(infof(conn->data, "HTTP/2 DISCONNECT starts now\n")); + (void)cf; + DEBUGASSERT(data); + stream = H2_STREAM_CTX(ctx, data); + if(stream) { + *pstream = stream; + return CURLE_OK; + } - nghttp2_session_del(c->h2); - Curl_safefree(c->inbuf); - http2_stream_free(conn->data->req.protop); + stream = h2_stream_ctx_create(ctx); + if(!stream) + return CURLE_OUT_OF_MEMORY; - DEBUGF(infof(conn->data, "HTTP/2 DISCONNECT done\n")); + if(!Curl_hash_offt_set(&ctx->streams, data->mid, stream)) { + h2_stream_ctx_free(stream); + return CURLE_OUT_OF_MEMORY; + } + *pstream = stream; return CURLE_OK; } -/* called from Curl_http_setup_conn */ -void Curl_http2_setup_req(struct Curl_easy *data) +static void http2_data_done(struct Curl_cfilter *cf, struct Curl_easy *data) +{ + struct cf_h2_ctx *ctx = cf->ctx; + struct h2_stream_ctx *stream = H2_STREAM_CTX(ctx, data); + + DEBUGASSERT(ctx); + if(!stream || !ctx->initialized) + return; + + if(ctx->h2) { + bool flush_egress = FALSE; + /* returns error if stream not known, which is fine here */ + (void)nghttp2_session_set_stream_user_data(ctx->h2, stream->id, NULL); + + if(!stream->closed && stream->id > 0) { + /* RST_STREAM */ + CURL_TRC_CF(data, cf, "[%d] premature DATA_DONE, RST stream", + stream->id); + stream->closed = TRUE; + stream->reset = TRUE; + nghttp2_submit_rst_stream(ctx->h2, NGHTTP2_FLAG_NONE, + stream->id, NGHTTP2_STREAM_CLOSED); + flush_egress = TRUE; + } + + if(flush_egress) + nghttp2_session_send(ctx->h2); + } + + Curl_hash_offt_remove(&ctx->streams, data->mid); +} + +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, FALSE, 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); +#if !defined(CURL_DISABLE_VERBOSE_STRINGS) +static int error_callback(nghttp2_session *session, const char *msg, + size_t len, void *userp); +#endif +static CURLcode cf_h2_ctx_open(struct Curl_cfilter *cf, + struct Curl_easy *data) { - struct HTTP *http = data->req.protop; + struct cf_h2_ctx *ctx = cf->ctx; + struct h2_stream_ctx *stream; + CURLcode result = CURLE_OUT_OF_MEMORY; + int rc; + nghttp2_session_callbacks *cbs = NULL; + + DEBUGASSERT(!ctx->h2); + DEBUGASSERT(ctx->initialized); + + 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); +#if !defined(CURL_DISABLE_VERBOSE_STRINGS) + nghttp2_session_callbacks_set_error_callback(cbs, error_callback); +#endif + + /* 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(ctx->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, (size_t)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]; + size_t 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; + } - http->nread_header_recvbuf = 0; - http->bodystarted = FALSE; - http->status_code = -1; - http->pausedata = NULL; - http->pauselen = 0; - http->error_code = NGHTTP2_NO_ERROR; - http->closed = FALSE; - http->close_handled = FALSE; - http->mem = data->state.buffer; - http->len = BUFSIZE; - http->memlen = 0; + /* all set, traffic will be send on connect */ + result = CURLE_OK; + CURL_TRC_CF(data, cf, "[0] created h2 session%s", + ctx->via_h1_upgrade ? " (via h1 upgrade)" : ""); + +out: + if(cbs) + nghttp2_session_callbacks_del(cbs); + return result; } -/* called from Curl_http_setup_conn */ -void Curl_http2_setup_conn(struct connectdata *conn) +/* + * Returns nonzero if current HTTP/2 session should be closed. + */ +static int should_close_session(struct cf_h2_ctx *ctx) { - conn->proto.httpc.settings.max_concurrent_streams = - DEFAULT_MAX_CONCURRENT_STREAMS; + return ctx->drain_total == 0 && !nghttp2_session_want_read(ctx->h2) && + !nghttp2_session_want_write(ctx->h2); } /* - * HTTP2 handler interface. This isn't added to the general list of protocols - * but will be used at run-time when the protocol is dynamically switched from - * HTTP to HTTP2. + * 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. */ -const struct Curl_handler Curl_handler_http2 = { - "HTTP", /* scheme */ - ZERO_NULL, /* setup_connection */ - Curl_http, /* do_it */ - Curl_http_done, /* done */ - ZERO_NULL, /* do_more */ - ZERO_NULL, /* connect_it */ - ZERO_NULL, /* connecting */ - ZERO_NULL, /* doing */ - http2_getsock, /* proto_getsock */ - http2_getsock, /* doing_getsock */ - ZERO_NULL, /* domore_getsock */ - http2_perform_getsock, /* perform_getsock */ - http2_disconnect, /* disconnect */ - ZERO_NULL, /* readwrite */ - PORT_HTTP, /* defport */ - CURLPROTO_HTTP, /* protocol */ - PROTOPT_STREAM /* flags */ -}; +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; -const struct Curl_handler Curl_handler_http2_ssl = { - "HTTPS", /* scheme */ - ZERO_NULL, /* setup_connection */ - Curl_http, /* do_it */ - Curl_http_done, /* done */ - ZERO_NULL, /* do_more */ - ZERO_NULL, /* connect_it */ - ZERO_NULL, /* connecting */ - ZERO_NULL, /* doing */ - http2_getsock, /* proto_getsock */ - http2_getsock, /* doing_getsock */ - ZERO_NULL, /* domore_getsock */ - http2_perform_getsock, /* perform_getsock */ - http2_disconnect, /* disconnect */ - ZERO_NULL, /* readwrite */ - PORT_HTTP, /* defport */ - CURLPROTO_HTTPS, /* protocol */ - PROTOPT_SSL | PROTOPT_STREAM /* flags */ -}; + 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 have sent off a request and the connection is + not in use by any other transfer, there should not 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, Prefix with a space. Return - * total length written. + * Store nghttp2 version info in this buffer. */ -int Curl_http2_ver(char *p, size_t len) +void Curl_http2_ver(char *p, size_t len) { nghttp2_info *h2 = nghttp2_version(0); - return snprintf(p, len, " nghttp2/%s", h2->version_str); -} - -/* HTTP/2 error code to name based on the Error Code Registry. -https://tools.ietf.org/html/rfc7540#page-77 -nghttp2_error_code enums are identical. -*/ -const char *Curl_http2_strerror(uint32_t err) -{ -#ifndef NGHTTP2_HAS_HTTP2_STRERROR - const char *str[] = { - "NO_ERROR", /* 0x0 */ - "PROTOCOL_ERROR", /* 0x1 */ - "INTERNAL_ERROR", /* 0x2 */ - "FLOW_CONTROL_ERROR", /* 0x3 */ - "SETTINGS_TIMEOUT", /* 0x4 */ - "STREAM_CLOSED", /* 0x5 */ - "FRAME_SIZE_ERROR", /* 0x6 */ - "REFUSED_STREAM", /* 0x7 */ - "CANCEL", /* 0x8 */ - "COMPRESSION_ERROR", /* 0x9 */ - "CONNECT_ERROR", /* 0xA */ - "ENHANCE_YOUR_CALM", /* 0xB */ - "INADEQUATE_SECURITY", /* 0xC */ - "HTTP_1_1_REQUIRED" /* 0xD */ - }; - return (err < sizeof str / sizeof str[0]) ? str[err] : "unknown"; -#else - return nghttp2_http2_strerror(err); -#endif + (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; } /* @@ -266,33 +776,35 @@ const char *Curl_http2_strerror(uint32_t err) * written. See the documentation of nghttp2_send_callback for the details. */ static ssize_t send_callback(nghttp2_session *h2, - const uint8_t *data, size_t length, int flags, + const uint8_t *buf, size_t blen, int flags, void *userp) { - struct connectdata *conn = (struct connectdata *)userp; - struct http_conn *c = &conn->proto.httpc; - ssize_t written; + 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); - written = ((Curl_send*)c->send_underlying)(conn, FIRSTSOCKET, - data, length, &result); - - if(result == CURLE_AGAIN) { - return NGHTTP2_ERR_WOULDBLOCK; - } - - if(written == -1) { - failf(conn->data, "Failed sending HTTP2 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(!written) + if(!nwritten) { + ctx->nw_out_blocked = 1; return NGHTTP2_ERR_WOULDBLOCK; - - return written; + } + return nwritten; } @@ -300,6 +812,7 @@ static ssize_t send_callback(nghttp2_session *h2, the struct are hidden from the user. */ struct curl_pushheaders { struct Curl_easy *data; + struct h2_stream_ctx *stream; const nghttp2_push_promise *frame; }; @@ -313,9 +826,8 @@ char *curl_pushheader_bynum(struct curl_pushheaders *h, size_t num) if(!h || !GOOD_EASY_HANDLE(h->data)) return NULL; else { - struct HTTP *stream = h->data->req.protop; - if(num < stream->push_headers_used) - return stream->push_headers[num]; + if(h->stream && num < h->stream->push_headers_used) + return h->stream->push_headers[num]; } return NULL; } @@ -325,6 +837,9 @@ char *curl_pushheader_bynum(struct curl_pushheaders *h, size_t num) */ char *curl_pushheader_byname(struct curl_pushheaders *h, const char *header) { + struct h2_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 @@ -334,209 +849,289 @@ char *curl_pushheader_byname(struct curl_pushheaders *h, const char *header) if(!h || !GOOD_EASY_HANDLE(h->data) || !header || !header[0] || !strcmp(header, ":") || strchr(header + 1, ':')) return NULL; - else { - struct HTTP *stream = h->data->req.protop; - size_t len = strlen(header); - size_t i; - for(i=0; ipush_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]; - } + + stream = h->stream; + 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 *duphandle(struct Curl_easy *data) +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); - second = NULL; + struct h2_stream_ctx *second_stream; + 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; } - else { - second->req.protop = http; - http->header_recvbuf = Curl_add_buffer_init(); - if(!http->header_recvbuf) { - free(http); - (void)Curl_close(second); - second = NULL; - } - else { - Curl_http2_setup_req(second); - second->state.stream_weight = data->state.stream_weight; - } + } + + v = curl_pushheader_byname(hp, HTTP_PSEUDO_AUTHORITY); + if(v) { + uc = Curl_url_set_authority(u, v); + if(uc) { + rc = 2; + goto fail; } } - return second; + + 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) +{ + http2_data_done(cf, newhandle); + (void)Curl_close(&newhandle); +} -static int push_promise(struct Curl_easy *data, - struct connectdata *conn, +static int push_promise(struct Curl_cfilter *cf, + struct Curl_easy *data, const nghttp2_push_promise *frame) { - int rv; - DEBUGF(infof(data, "PUSH_PROMISE received, stream %u!\n", - frame->promised_stream_id)); + 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 HTTP *stream; - struct HTTP *newstream; + struct h2_stream_ctx *stream; + struct h2_stream_ctx *newstream; struct curl_pushheaders heads; CURLMcode rc; - struct http_conn *httpc; - size_t i; + CURLcode result; /* clone the parent */ - struct Curl_easy *newhandle = duphandle(data); + struct Curl_easy *newhandle = h2_duphandle(cf, data); if(!newhandle) { - infof(data, "failed to duplicate handle\n"); - rv = 1; /* FAIL HARD */ + infof(data, "failed to duplicate handle"); + rv = CURL_PUSH_DENY; /* FAIL HARD */ goto fail; } - heads.data = data; - heads.frame = frame; /* ask the application */ - DEBUGF(infof(data, "Got PUSH_PROMISE, ask application!\n")); + CURL_TRC_CF(data, cf, "Got PUSH_PROMISE, ask application"); - stream = data->req.protop; + stream = H2_STREAM_CTX(ctx, data); if(!stream) { - failf(data, "Internal NULL stream!\n"); - rv = 1; + failf(data, "Internal NULL stream"); + discard_newhandle(cf, newhandle); + rv = CURL_PUSH_DENY; + goto fail; + } + + heads.data = data; + heads.stream = stream; + heads.frame = frame; + + 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; ipush_headers_used; i++) - free(stream->push_headers[i]); - free(stream->push_headers); - stream->push_headers = NULL; - stream->push_headers_used = 0; + free_push_headers(stream); if(rv) { + DEBUGASSERT((rv > CURL_PUSH_OK) && (rv <= CURL_PUSH_ERROROUT)); /* denied, kill off the new handle again */ - http2_stream_free(newhandle->req.protop); - (void)Curl_close(newhandle); + discard_newhandle(cf, newhandle); goto fail; } - newstream = newhandle->req.protop; - newstream->stream_id = frame->promised_stream_id; + 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, conn); + rc = Curl_multi_add_perform(data->multi, newhandle, cf->conn); if(rc) { - infof(data, "failed to add handle to multi\n"); - http2_stream_free(newhandle->req.protop); - Curl_close(newhandle); - rv = 1; + 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; } - httpc = &conn->proto.httpc; - nghttp2_session_set_stream_user_data(httpc->h2, - frame->promised_stream_id, newhandle); + /* success, remember max stream id processed */ + if(newstream->id > ctx->local_max_sid) + ctx->local_max_sid = newstream->id; } else { - DEBUGF(infof(data, "Got PUSH_PROMISE, ignore it!\n")); - rv = 1; + CURL_TRC_CF(data, cf, "Got PUSH_PROMISE, ignore it"); + rv = CURL_PUSH_DENY; } - fail: +fail: return rv; } -static int on_frame_recv(nghttp2_session *session, const nghttp2_frame *frame, - void *userp) +static void h2_xfer_write_resp_hd(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct h2_stream_ctx *stream, + const char *buf, size_t blen, bool eos) { - struct connectdata *conn = (struct connectdata *)userp; - struct http_conn *httpc = &conn->proto.httpc; - struct Curl_easy *data_s = NULL; - struct HTTP *stream = NULL; - static int lastStream = -1; - int rv; - size_t left, ncopy; - int32_t stream_id = frame->hd.stream_id; - if(!stream_id) { - /* stream ID zero is for connection-oriented stuff */ - if(frame->hd.type == NGHTTP2_SETTINGS) { - uint32_t max_conn = httpc->settings.max_concurrent_streams; - DEBUGF(infof(conn->data, "Got SETTINGS\n")); - httpc->settings.max_concurrent_streams = - nghttp2_session_get_remote_settings( - session, NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS); - httpc->settings.enable_push = - nghttp2_session_get_remote_settings( - session, NGHTTP2_SETTINGS_ENABLE_PUSH); - DEBUGF(infof(conn->data, "MAX_CONCURRENT_STREAMS == %d\n", - httpc->settings.max_concurrent_streams)); - DEBUGF(infof(conn->data, "ENABLE_PUSH == %s\n", - httpc->settings.enable_push?"TRUE":"false")); - if(max_conn != httpc->settings.max_concurrent_streams) { - /* only signal change if the value actually changed */ - infof(conn->data, - "Connection state changed (MAX_CONCURRENT_STREAMS updated)!\n"); - Curl_multi_connchanged(conn->data->multi); - } - } - return 0; - } - data_s = nghttp2_session_get_stream_user_data(session, stream_id); - if(lastStream != stream_id) { - lastStream = stream_id; + /* If we already encountered an error, skip further writes */ + if(!stream->xfer_result) { + stream->xfer_result = Curl_xfer_write_resp_hd(data, buf, blen, eos); + if(!stream->xfer_result && !eos) + stream->xfer_result = cf_h2_update_local_win(cf, data, stream, FALSE); + if(stream->xfer_result) + CURL_TRC_CF(data, cf, "[%d] error %d writing %zu bytes of headers", + stream->id, stream->xfer_result, blen); } - if(!data_s) { - DEBUGF(infof(conn->data, - "No Curl_easy associated with stream: %x\n", - stream_id)); - return 0; +} + +static void h2_xfer_write_resp(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct h2_stream_ctx *stream, + const char *buf, size_t blen, bool eos) +{ + + /* If we already encountered an error, skip further writes */ + if(!stream->xfer_result) + stream->xfer_result = Curl_xfer_write_resp(data, buf, blen, eos); + if(!stream->xfer_result && !eos) + stream->xfer_result = cf_h2_update_local_win(cf, data, stream, FALSE); + /* If the transfer write is errored, we do not want any more data */ + if(stream->xfer_result) { + struct cf_h2_ctx *ctx = cf->ctx; + CURL_TRC_CF(data, cf, "[%d] error %d writing %zu bytes of data, " + "RST-ing stream", + stream->id, stream->xfer_result, blen); + nghttp2_submit_rst_stream(ctx->h2, 0, stream->id, + (uint32_t)NGHTTP2_ERR_CALLBACK_FAILURE); } +} + +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 h2_stream_ctx *stream = H2_STREAM_CTX(ctx, data); + int32_t stream_id = frame->hd.stream_id; + int rv; - stream = data_s->req.protop; if(!stream) { - DEBUGF(infof(conn->data, "No proto pointer for stream: %x\n", - stream_id)); - return NGHTTP2_ERR_CALLBACK_FAILURE; + CURL_TRC_CF(data, cf, "[%d] No stream_ctx set", stream_id); + return CURLE_FAILED_INIT; } - DEBUGF(infof(data_s, "on_frame_recv() header %x stream %x\n", - frame->hd.type, stream_id)); - switch(frame->hd.type) { case NGHTTP2_DATA: - /* If body started on this stream, then receiving DATA is illegal. */ + CURL_TRC_CF(data, cf, "[%d] DATA, window=%d/%d", + stream_id, + 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(session, NGHTTP2_FLAG_NONE, + rv = nghttp2_submit_rst_stream(ctx->h2, NGHTTP2_FLAG_NONE, stream_id, NGHTTP2_PROTOCOL_ERROR); if(nghttp2_is_fatal(rv)) { - return NGHTTP2_ERR_CALLBACK_FAILURE; + return CURLE_RECV_ERROR; } } + if(frame->hd.flags & NGHTTP2_FLAG_END_STREAM) { + drain_stream(cf, data, stream); + } break; case NGHTTP2_HEADERS: if(stream->bodystarted) { - /* Only valid HEADERS after body started is trailer HEADERS. We + /* 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 */ - DEBUGASSERT(stream->status_code != -1); + 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) { @@ -544,209 +1139,314 @@ static int on_frame_recv(nghttp2_session *session, const nghttp2_frame *frame, stream->status_code = -1; } - Curl_add_buffer(stream->header_recvbuf, "\r\n", 2); - - left = stream->header_recvbuf->size_used - stream->nread_header_recvbuf; - ncopy = MIN(stream->len, left); - - memcpy(&stream->mem[stream->memlen], - stream->header_recvbuf->buffer + stream->nread_header_recvbuf, - ncopy); - stream->nread_header_recvbuf += ncopy; - - DEBUGF(infof(data_s, "Store %zu bytes headers from stream %u at %p\n", - ncopy, stream_id, stream->mem)); + h2_xfer_write_resp_hd(cf, data, stream, STRCONST("\r\n"), stream->closed); - stream->len -= ncopy; - stream->memlen += ncopy; - - data_s->state.drain++; - httpc->drain_total++; - { - /* get the pointer from userp again since it was re-assigned above */ - struct connectdata *conn_s = (struct connectdata *)userp; - - /* if we receive data for another handle, wake that up */ - if(conn_s->data != data_s) - Curl_expire(data_s, 0); + if(stream->status_code / 100 != 1) { + stream->resp_hds_complete = TRUE; } + drain_stream(cf, data, stream); break; case NGHTTP2_PUSH_PROMISE: - rv = push_promise(data_s, conn, &frame->push_promise); + rv = push_promise(cf, data, &frame->push_promise); if(rv) { /* deny! */ - rv = nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, + 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 rv; + 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; + } + drain_stream(cf, data, stream); + break; + case NGHTTP2_WINDOW_UPDATE: + if(CURL_WANT_SEND(data) && Curl_bufq_is_empty(&stream->sendbuf)) { + /* need more data, force processing of transfer */ + drain_stream(cf, data, stream); + } + else if(!Curl_bufq_is_empty(&stream->sendbuf)) { + /* resume the potentially suspended stream */ + rv = nghttp2_session_resume_data(ctx->h2, stream->id); + if(nghttp2_is_fatal(rv)) + return CURLE_SEND_ERROR; + } + break; default: - DEBUGF(infof(conn->data, "Got frame type %x for stream %u!\n", - frame->hd.type, stream_id)); break; } - return 0; + return CURLE_OK; } -static int on_invalid_frame_recv(nghttp2_session *session, - const nghttp2_frame *frame, - int lib_error_code, void *userp) +#ifndef CURL_DISABLE_VERBOSE_STRINGS +static int fr_print(const nghttp2_frame *frame, char *buffer, size_t blen) { - struct Curl_easy *data_s = NULL; - (void)userp; + 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); + } +} - data_s = nghttp2_session_get_stream_user_data(session, frame->hd.stream_id); - if(data_s) { - DEBUGF(infof(data_s, - "on_invalid_frame_recv() was called, error=%d:%s\n", - lib_error_code, nghttp2_strerror(lib_error_code))); +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 h2_stream_ctx *stream = H2_STREAM_CTX(ctx, data); + if(stream) + drain_stream(cf, data, stream); + } + } + break; + } + case NGHTTP2_GOAWAY: + ctx->rcvd_goaway = TRUE; + ctx->goaway_error = frame->goaway.error_code; + ctx->remote_max_sid = frame->goaway.last_stream_id; + if(data) { + infof(data, "received GOAWAY, error=%u, last_stream=%u", + ctx->goaway_error, ctx->remote_max_sid); + 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 *data, size_t len, void *userp) + const uint8_t *mem, size_t len, void *userp) { - struct HTTP *stream; + struct Curl_cfilter *cf = userp; + struct cf_h2_ctx *ctx = cf->ctx; + struct h2_stream_ctx *stream; struct Curl_easy *data_s; - size_t nread; - struct connectdata *conn = (struct connectdata *)userp; - (void)session; (void)flags; - (void)data; 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, this is an - internal error more than anything else! */ - return NGHTTP2_ERR_CALLBACK_FAILURE; + 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 = data_s->req.protop; + stream = H2_STREAM_CTX(ctx, data_s); if(!stream) return NGHTTP2_ERR_CALLBACK_FAILURE; - nread = MIN(stream->len, len); - memcpy(&stream->mem[stream->memlen], data, nread); - - stream->len -= nread; - stream->memlen += nread; - - data_s->state.drain++; - conn->proto.httpc.drain_total++; - - /* if we receive data for another handle, wake that up */ - if(conn->data != data_s) - Curl_expire(data_s, 0); - - DEBUGF(infof(data_s, "%zu data received for stream %u " - "(%zu left in buffer %p, total %zu)\n", - nread, stream_id, - stream->len, stream->mem, - stream->memlen)); - - if(nread < len) { - stream->pausedata = data + nread; - stream->pauselen = len - nread; - DEBUGF(infof(data_s, "NGHTTP2_ERR_PAUSE - %zu bytes out of buffer" - ", stream %u\n", - len - nread, stream_id)); - data_s->easy_conn->proto.httpc.pause_stream_id = stream_id; - - return NGHTTP2_ERR_PAUSE; - } - - /* pause execution of nghttp2 if we received data for another handle - in order to process them first. */ - if(conn->data != data_s) { - data_s->easy_conn->proto.httpc.pause_stream_id = stream_id; - - return NGHTTP2_ERR_PAUSE; - } + h2_xfer_write_resp(cf, data_s, stream, (char *)mem, len, FALSE); + nghttp2_session_consume(ctx->h2, stream_id, len); + stream->nrcvd_data += (curl_off_t)len; return 0; } -static int before_frame_send(nghttp2_session *session, - const nghttp2_frame *frame, - void *userp) +static int on_stream_close(nghttp2_session *session, int32_t stream_id, + uint32_t error_code, void *userp) { - struct Curl_easy *data_s; - (void)userp; + struct Curl_cfilter *cf = userp; + struct cf_h2_ctx *ctx = cf->ctx; + struct Curl_easy *data_s, *call_data = CF_DATA_CURRENT(cf); + struct h2_stream_ctx *stream; + int rv; + (void)session; - data_s = nghttp2_session_get_stream_user_data(session, frame->hd.stream_id); - if(data_s) { - DEBUGF(infof(data_s, "before_frame_send() was called\n")); + DEBUGASSERT(call_data); + /* stream id 0 is the connection, do not look there for streams. */ + data_s = stream_id ? + nghttp2_session_get_stream_user_data(session, stream_id) : NULL; + if(!data_s) { + CURL_TRC_CF(call_data, cf, + "[%d] on_stream_close, no easy set on stream", stream_id); + return 0; } - - return 0; -} -static int on_frame_send(nghttp2_session *session, - const nghttp2_frame *frame, - void *userp) -{ - struct Curl_easy *data_s; - (void)userp; - - data_s = nghttp2_session_get_stream_user_data(session, frame->hd.stream_id); - if(data_s) { - DEBUGF(infof(data_s, "on_frame_send() was called, length = %zd\n", - frame->hd.length)); + if(!GOOD_EASY_HANDLE(data_s)) { + /* nghttp2 still has an easy registered for the stream which has + * been freed be libcurl. This points to a code path that does not + * trigger DONE or DETACH events as it must. */ + CURL_TRC_CF(call_data, cf, + "[%d] on_stream_close, not a GOOD easy on stream", stream_id); + (void)nghttp2_session_set_stream_user_data(session, stream_id, 0); + return NGHTTP2_ERR_CALLBACK_FAILURE; + } + stream = H2_STREAM_CTX(ctx, data_s); + if(!stream) { + CURL_TRC_CF(data_s, cf, + "[%d] on_stream_close, GOOD easy but no stream", stream_id); + return NGHTTP2_ERR_CALLBACK_FAILURE; } - return 0; -} -static int on_frame_not_send(nghttp2_session *session, - const nghttp2_frame *frame, - int lib_error_code, void *userp) -{ - struct Curl_easy *data_s; - (void)userp; - data_s = nghttp2_session_get_stream_user_data(session, frame->hd.stream_id); - if(data_s) { - DEBUGF(infof(data_s, - "on_frame_not_send() was called, lib_error_code = %d\n", - lib_error_code)); + stream->closed = TRUE; + stream->error = error_code; + if(stream->error) { + stream->reset = TRUE; } - return 0; -} -static int on_stream_close(nghttp2_session *session, int32_t stream_id, - uint32_t error_code, void *userp) -{ - struct Curl_easy *data_s; - struct HTTP *stream; - struct connectdata *conn = (struct connectdata *)userp; - (void)session; - (void)stream_id; - - 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) { - /* We could get stream ID not in the hash. For example, if we - decided to reject stream (e.g., PUSH_PROMISE). */ - return 0; - } - DEBUGF(infof(data_s, "on_stream_close(), %s (err %d), stream %u\n", - Curl_http2_strerror(error_code), error_code, stream_id)); - stream = data_s->req.protop; - if(!stream) - return NGHTTP2_ERR_CALLBACK_FAILURE; - stream->error_code = error_code; - stream->closed = TRUE; - data_s->state.drain++; - conn->proto.httpc.drain_total++; + 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 the entry from the hash as the stream is now gone */ - nghttp2_session_set_stream_user_data(session, stream_id, 0); - DEBUGF(infof(data_s, "Removed stream %u hash!\n", stream_id)); + /* 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; } @@ -754,66 +1454,29 @@ static int on_stream_close(nghttp2_session *session, int32_t stream_id, static int on_begin_headers(nghttp2_session *session, const nghttp2_frame *frame, void *userp) { - struct HTTP *stream; + struct Curl_cfilter *cf = userp; + struct cf_h2_ctx *ctx = cf->ctx; + struct h2_stream_ctx *stream; struct Curl_easy *data_s = NULL; - (void)userp; + (void)cf; data_s = nghttp2_session_get_stream_user_data(session, frame->hd.stream_id); if(!data_s) { return 0; } - DEBUGF(infof(data_s, "on_begin_headers() was called\n")); - if(frame->hd.type != NGHTTP2_HEADERS) { return 0; } - stream = data_s->req.protop; + stream = H2_STREAM_CTX(ctx, data_s); if(!stream || !stream->bodystarted) { return 0; } - /* This is trailer HEADERS started. Allocate buffer for them. */ - DEBUGF(infof(data_s, "trailer field started\n")); - - assert(stream->trailer_recvbuf == NULL); - - stream->trailer_recvbuf = Curl_add_buffer_init(); - if(!stream->trailer_recvbuf) { - return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; - } - return 0; } -/* Decode HTTP status code. Returns -1 if no valid status code was - decoded. */ -static int decode_status_code(const uint8_t *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; -} - /* 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, @@ -821,10 +1484,12 @@ static int on_header(nghttp2_session *session, const nghttp2_frame *frame, uint8_t flags, void *userp) { - struct HTTP *stream; + struct Curl_cfilter *cf = userp; + struct cf_h2_ctx *ctx = cf->ctx; + struct h2_stream_ctx *stream; struct Curl_easy *data_s; int32_t stream_id = frame->hd.stream_id; - struct connectdata *conn = (struct connectdata *)userp; + CURLcode result; (void)flags; DEBUGASSERT(stream_id); /* should never be a zero stream ID here */ @@ -836,9 +1501,9 @@ static int on_header(nghttp2_session *session, const nghttp2_frame *frame, internal error more than anything else! */ return NGHTTP2_ERR_CALLBACK_FAILURE; - stream = data_s->req.protop; + stream = H2_STREAM_CTX(ctx, data_s); if(!stream) { - failf(data_s, "Internal NULL stream! 5\n"); + failf(data_s, "Internal NULL stream"); return NGHTTP2_ERR_CALLBACK_FAILURE; } @@ -847,21 +1512,54 @@ static int on_header(nghttp2_session *session, const nghttp2_frame *frame, 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_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"); + free_push_headers(stream); + return NGHTTP2_ERR_CALLBACK_FAILURE; + } stream->push_headers_alloc *= 2; - headp = Curl_saferealloc(stream->push_headers, - stream->push_headers_alloc * sizeof(char *)); + headp = realloc(stream->push_headers, + stream->push_headers_alloc * sizeof(char *)); if(!headp) { - stream->push_headers = NULL; - return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; + free_push_headers(stream); + return NGHTTP2_ERR_CALLBACK_FAILURE; } stream->push_headers = headp; } @@ -872,1342 +1570,1374 @@ static int on_header(nghttp2_session *session, const nghttp2_frame *frame, } if(stream->bodystarted) { - /* This is trailer fields. */ - /* 3 is for ":" and "\r\n". */ - uint32_t n = (uint32_t)(namelen + valuelen + 3); - - DEBUGF(infof(data_s, "h2 trailer: %.*s: %.*s\n", namelen, name, valuelen, - value)); - - Curl_add_buffer(stream->trailer_recvbuf, &n, sizeof(n)); - Curl_add_buffer(stream->trailer_recvbuf, name, namelen); - Curl_add_buffer(stream->trailer_recvbuf, ": ", 2); - Curl_add_buffer(stream->trailer_recvbuf, value, valuelen); - Curl_add_buffer(stream->trailer_recvbuf, "\r\n\0", 3); + /* 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(":status") - 1 && - memcmp(":status", name, namelen) == 0) { - /* nghttp2 guarantees :status is received first and only once, and - value is 3 digits status code, and decode_status_code always - succeeds. */ - stream->status_code = decode_status_code(value, valuelen); - DEBUGASSERT(stream->status_code != -1); - - Curl_add_buffer(stream->header_recvbuf, "HTTP/2 ", 7); - Curl_add_buffer(stream->header_recvbuf, value, valuelen); - /* the space character after the status code is mandatory */ - Curl_add_buffer(stream->header_recvbuf, " \r\n", 3); + 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; + Curl_dyn_reset(&ctx->scratch); + result = Curl_dyn_addn(&ctx->scratch, STRCONST("HTTP/2 ")); + if(!result) + result = Curl_dyn_addn(&ctx->scratch, value, valuelen); + if(!result) + result = Curl_dyn_addn(&ctx->scratch, STRCONST(" \r\n")); + if(!result) + h2_xfer_write_resp_hd(cf, data_s, stream, Curl_dyn_ptr(&ctx->scratch), + Curl_dyn_len(&ctx->scratch), FALSE); + if(result) + return NGHTTP2_ERR_CALLBACK_FAILURE; /* if we receive data for another handle, wake that up */ - if(conn->data != data_s) - Curl_expire(data_s, 0); + if(CF_DATA_CURRENT(cf) != data_s) + Curl_expire(data_s, 0, EXPIRE_RUN_NOW); - DEBUGF(infof(data_s, "h2 status: HTTP/2 %03d (easy %p)\n", - stream->status_code, data_s)); + 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 a HTTP1-style header */ - Curl_add_buffer(stream->header_recvbuf, name, namelen); - Curl_add_buffer(stream->header_recvbuf, ": ", 2); - Curl_add_buffer(stream->header_recvbuf, value, valuelen); - Curl_add_buffer(stream->header_recvbuf, "\r\n", 2); + /* convert to an HTTP1-style header */ + Curl_dyn_reset(&ctx->scratch); + result = Curl_dyn_addn(&ctx->scratch, (const char *)name, namelen); + if(!result) + result = Curl_dyn_addn(&ctx->scratch, STRCONST(": ")); + if(!result) + result = Curl_dyn_addn(&ctx->scratch, (const char *)value, valuelen); + if(!result) + result = Curl_dyn_addn(&ctx->scratch, STRCONST("\r\n")); + if(!result) + h2_xfer_write_resp_hd(cf, data_s, stream, Curl_dyn_ptr(&ctx->scratch), + Curl_dyn_len(&ctx->scratch), FALSE); + if(result) + return NGHTTP2_ERR_CALLBACK_FAILURE; /* if we receive data for another handle, wake that up */ - if(conn->data != data_s) - Curl_expire(data_s, 0); + if(CF_DATA_CURRENT(cf) != data_s) + Curl_expire(data_s, 0, EXPIRE_RUN_NOW); - DEBUGF(infof(data_s, "h2 header: %.*s: %.*s\n", namelen, name, valuelen, - value)); + 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 data_source_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) +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 cf_h2_ctx *ctx = cf->ctx; struct Curl_easy *data_s; - struct HTTP *stream = NULL; - size_t nread; + struct h2_stream_ctx *stream = NULL; + CURLcode result; + ssize_t nread; (void)source; - (void)userp; - - 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 = data_s->req.protop; - if(!stream) - return NGHTTP2_ERR_CALLBACK_FAILURE; - } - else + (void)cf; + if(!stream_id) return NGHTTP2_ERR_INVALID_ARGUMENT; - nread = MIN(stream->upload_len, length); - if(nread > 0) { - memcpy(buf, stream->upload_mem, nread); - stream->upload_mem += nread; - stream->upload_len -= nread; - if(data_s->state.infilesize != -1) - stream->upload_left -= nread; - } + /* 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; - if(stream->upload_left == 0) - *data_flags = NGHTTP2_DATA_FLAG_EOF; - else if(nread == 0) - return NGHTTP2_ERR_DEFERRED; + stream = H2_STREAM_CTX(ctx, data_s); + if(!stream) + return NGHTTP2_ERR_CALLBACK_FAILURE; - DEBUGF(infof(data_s, "data_source_read_callback: " - "returns %zu bytes stream %u\n", - nread, stream_id)); + nread = Curl_bufq_read(&stream->sendbuf, buf, length, &result); + if(nread < 0) { + if(result != CURLE_AGAIN) + return NGHTTP2_ERR_CALLBACK_FAILURE; + nread = 0; + } - return nread; -} + CURL_TRC_CF(data_s, cf, "[%d] req_body_read(len=%zu) eos=%d -> %zd, %d", + stream_id, length, stream->body_eos, nread, result); -#define H2_BUFSIZE 32768 + if(stream->body_eos && Curl_bufq_is_empty(&stream->sendbuf)) { + *data_flags = NGHTTP2_DATA_FLAG_EOF; + return nread; + } + return (nread == 0) ? NGHTTP2_ERR_DEFERRED : nread; +} -#ifdef NGHTTP2_HAS_ERROR_CALLBACK +#if !defined(CURL_DISABLE_VERBOSE_STRINGS) static int error_callback(nghttp2_session *session, const char *msg, size_t len, void *userp) { - struct connectdata *conn = (struct connectdata *)userp; + struct Curl_cfilter *cf = userp; + struct Curl_easy *data = CF_DATA_CURRENT(cf); (void)session; - infof(conn->data, "http2 error: %.*s\n", len, msg); + failf(data, "%.*s", (int)len, msg); return 0; } #endif -static void populate_settings(struct connectdata *conn, - struct http_conn *httpc) +/* + * Append headers to ask for an HTTP1.1 to HTTP2 upgrade. + */ +CURLcode Curl_http2_request_upgrade(struct dynbuf *req, + struct Curl_easy *data) { - nghttp2_settings_entry *iv = httpc->local_settings; + 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; + } - iv[0].settings_id = NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS; - iv[0].value = 100; + result = Curl_base64url_encode((const char *)binsettings, (size_t)binlen, + &base64, &blen); + if(result) { + Curl_dyn_free(req); + return result; + } - iv[1].settings_id = NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE; - iv[1].value = HTTP2_HUGE_WINDOW_SIZE; + 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); - iv[2].settings_id = NGHTTP2_SETTINGS_ENABLE_PUSH; - iv[2].value = conn->data->multi->push_cb != NULL; + k->upgr101 = UPGR101_H2; + data->conn->bits.asks_multiplex = TRUE; - httpc->local_settings_num = 3; + return result; } -void Curl_http2_done(struct connectdata *conn, bool premature) +static ssize_t http2_handle_stream_close(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct h2_stream_ctx *stream, + CURLcode *err) { - struct Curl_easy *data = conn->data; - struct HTTP *http = data->req.protop; - struct http_conn *httpc = &conn->proto.httpc; - - if(http->header_recvbuf) { - DEBUGF(infof(data, "free header_recvbuf!!\n")); - Curl_add_buffer_free(http->header_recvbuf); - http->header_recvbuf = NULL; /* clear the pointer */ - Curl_add_buffer_free(http->trailer_recvbuf); - http->trailer_recvbuf = NULL; /* clear the pointer */ - if(http->push_headers) { - /* if they weren't used and then freed before */ - for(; http->push_headers_used > 0; --http->push_headers_used) { - free(http->push_headers[http->push_headers_used - 1]); - } - free(http->push_headers); - http->push_headers = NULL; - } + 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"); /* do not use this anymore */ + data->state.refused_stream = TRUE; + *err = CURLE_RECV_ERROR; /* trigger Curl_retry_request() later */ + return -1; } - - if(premature) { - /* RST_STREAM */ - nghttp2_submit_rst_stream(httpc->h2, NGHTTP2_FLAG_NONE, http->stream_id, - NGHTTP2_STREAM_CLOSED); - if(http->stream_id == httpc->pause_stream_id) { - infof(data, "stopped the pause stream!\n"); - httpc->pause_stream_id = 0; + else if(stream->error != NGHTTP2_NO_ERROR) { + if(stream->resp_hds_complete && data->req.no_body) { + CURL_TRC_CF(data, cf, "[%d] error after response headers, but we did " + "not want a body anyway, ignore: %s (err %u)", + stream->id, nghttp2_http2_strerror(stream->error), + stream->error); + stream->close_handled = TRUE; + *err = CURLE_OK; + goto out; } + failf(data, "HTTP/2 stream %u was not closed cleanly: %s (err %u)", + stream->id, nghttp2_http2_strerror(stream->error), + stream->error); + *err = CURLE_HTTP2_STREAM; + return -1; } - if(http->stream_id) { - nghttp2_session_set_stream_user_data(httpc->h2, http->stream_id, 0); - http->stream_id = 0; + else if(stream->reset) { + failf(data, "HTTP/2 stream %u was reset", stream->id); + *err = data->req.bytecount ? CURLE_PARTIAL_FILE : CURLE_HTTP2; + return -1; } -} - -/* - * Initialize nghttp2 for a Curl connection - */ -CURLcode Curl_http2_init(struct connectdata *conn) -{ - if(!conn->proto.httpc.h2) { - int rc; - nghttp2_session_callbacks *callbacks; - - conn->proto.httpc.inbuf = malloc(H2_BUFSIZE); - if(conn->proto.httpc.inbuf == NULL) - return CURLE_OUT_OF_MEMORY; - rc = nghttp2_session_callbacks_new(&callbacks); + 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(rc) { - failf(conn->data, "Couldn't initialize nghttp2 callbacks!"); - return CURLE_OUT_OF_MEMORY; /* most likely at least */ - } - - /* nghttp2_send_callback */ - nghttp2_session_callbacks_set_send_callback(callbacks, send_callback); - /* nghttp2_on_frame_recv_callback */ - nghttp2_session_callbacks_set_on_frame_recv_callback - (callbacks, on_frame_recv); - /* nghttp2_on_invalid_frame_recv_callback */ - nghttp2_session_callbacks_set_on_invalid_frame_recv_callback - (callbacks, on_invalid_frame_recv); - /* nghttp2_on_data_chunk_recv_callback */ - nghttp2_session_callbacks_set_on_data_chunk_recv_callback - (callbacks, on_data_chunk_recv); - /* nghttp2_before_frame_send_callback */ - nghttp2_session_callbacks_set_before_frame_send_callback - (callbacks, before_frame_send); - /* nghttp2_on_frame_send_callback */ - nghttp2_session_callbacks_set_on_frame_send_callback - (callbacks, on_frame_send); - /* nghttp2_on_frame_not_send_callback */ - nghttp2_session_callbacks_set_on_frame_not_send_callback - (callbacks, on_frame_not_send); - /* nghttp2_on_stream_close_callback */ - nghttp2_session_callbacks_set_on_stream_close_callback - (callbacks, on_stream_close); - /* nghttp2_on_begin_headers_callback */ - nghttp2_session_callbacks_set_on_begin_headers_callback - (callbacks, on_begin_headers); - /* nghttp2_on_header_callback */ - nghttp2_session_callbacks_set_on_header_callback(callbacks, on_header); - - nghttp2_session_callbacks_set_error_callback(callbacks, error_callback); - - /* The nghttp2 session is not yet setup, do it */ - rc = nghttp2_session_client_new(&conn->proto.httpc.h2, callbacks, conn); - - nghttp2_session_callbacks_del(callbacks); + if(Curl_dynhds_count(&stream->resp_trailers)) { + struct dynhds_entry *e; + struct dynbuf dbuf; + size_t i; - if(rc) { - failf(conn->data, "Couldn't initialize nghttp2!"); - return CURLE_OUT_OF_MEMORY; /* most likely at least */ + *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; } - return CURLE_OK; + + 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; } /* - * Append headers to ask for a HTTP1.1 to HTTP2 upgrade. + * 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. */ -CURLcode Curl_http2_request_upgrade(Curl_send_buffer *req, - struct connectdata *conn) + +static void h2_pri_spec(struct cf_h2_ctx *ctx, + struct Curl_easy *data, + nghttp2_priority_spec *pri_spec) { - CURLcode result; - ssize_t binlen; - char *base64; - size_t blen; - struct SingleRequest *k = &conn->data->req; - uint8_t *binsettings = conn->proto.httpc.binsettings; - struct http_conn *httpc = &conn->proto.httpc; - - populate_settings(conn, httpc); - - /* this returns number of bytes it wrote */ - binlen = nghttp2_pack_settings_payload(binsettings, H2_BINSETTINGS_LEN, - httpc->local_settings, - httpc->local_settings_num); - if(!binlen) { - failf(conn->data, "nghttp2 unexpectedly failed on pack_settings_payload"); - return CURLE_FAILED_INIT; - } - conn->proto.httpc.binlen = binlen; + struct Curl_data_priority *prio = &data->set.priority; + struct h2_stream_ctx *depstream = H2_STREAM_CTX(ctx, prio->parent); + int32_t depstream_id = depstream ? depstream->id : 0; + nghttp2_priority_spec_init(pri_spec, depstream_id, + sweight_wanted(data), + data->set.priority.exclusive); + data->state.priority = *prio; +} - result = Curl_base64url_encode(conn->data, (const char *)binsettings, binlen, - &base64, &blen); - if(result) - return result; +/* + * Check if there is 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 h2_stream_ctx *stream = H2_STREAM_CTX(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; - result = Curl_add_bufferf(req, - "Connection: Upgrade, HTTP2-Settings\r\n" - "Upgrade: %s\r\n" - "HTTP2-Settings: %s\r\n", - NGHTTP2_CLEARTEXT_PROTO_VERSION_ID, base64); - free(base64); + h2_pri_spec(ctx, data, &pri_spec); + CURL_TRC_CF(data, cf, "[%d] Queuing PRIORITY", stream->id); + DEBUGASSERT(stream->id != -1); + rv = nghttp2_submit_priority(ctx->h2, NGHTTP2_FLAG_NONE, + stream->id, &pri_spec); + if(rv) + goto out; + } - k->upgr101 = UPGR101_REQUESTED; + ctx->nw_out_blocked = 0; + while(!rv && !ctx->nw_out_blocked && nghttp2_session_want_write(ctx->h2)) + rv = nghttp2_session_send(ctx->h2); - return result; +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); } -/* - * Returns nonzero if current HTTP/2 session should be closed. - */ -static int should_close_session(struct http_conn *httpc) +static ssize_t stream_recv(struct Curl_cfilter *cf, struct Curl_easy *data, + struct h2_stream_ctx *stream, + char *buf, size_t len, CURLcode *err) { - return httpc->drain_total == 0 && !nghttp2_session_want_read(httpc->h2) && - !nghttp2_session_want_write(httpc->h2); -} + struct cf_h2_ctx *ctx = cf->ctx; + ssize_t nread = -1; -static int h2_session_send(struct Curl_easy *data, - nghttp2_session *h2); + (void)buf; + *err = CURLE_AGAIN; + if(stream->xfer_result) { + CURL_TRC_CF(data, cf, "[%d] xfer write failed", stream->id); + *err = stream->xfer_result; + nread = -1; + } + else if(stream->closed) { + CURL_TRC_CF(data, cf, "[%d] returning CLOSE", stream->id); + nread = http2_handle_stream_close(cf, data, stream, err); + } + else if(stream->reset || + (ctx->conn_closed && Curl_bufq_is_empty(&ctx->inbufq)) || + (ctx->rcvd_goaway && ctx->remote_max_sid < stream->id)) { + CURL_TRC_CF(data, cf, "[%d] returning ERR", stream->id); + *err = data->req.bytecount ? CURLE_PARTIAL_FILE : CURLE_HTTP2; + nread = -1; + } + + 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; +} -/* - * h2_process_pending_input() processes pending input left in - * httpc->inbuf. Then, call h2_session_send() to send pending data. - * 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_easy *data, - struct http_conn *httpc, - CURLcode *err) +static CURLcode h2_progress_ingress(struct Curl_cfilter *cf, + struct Curl_easy *data, + size_t data_max_bytes) { + struct cf_h2_ctx *ctx = cf->ctx; + struct h2_stream_ctx *stream; + CURLcode result = CURLE_OK; ssize_t nread; - char *inbuf; - ssize_t rv; - - nread = httpc->inbuflen - httpc->nread_inbuf; - inbuf = httpc->inbuf + httpc->nread_inbuf; - rv = nghttp2_session_mem_recv(httpc->h2, (const uint8_t *)inbuf, nread); - if(rv < 0) { - failf(data, - "h2_process_pending_input: nghttp2_session_mem_recv() returned " - "%d:%s\n", rv, nghttp2_strerror((int)rv)); - *err = CURLE_RECV_ERROR; - return -1; - } + /* 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(ctx, data); + if(stream && (stream->closed || !data_max_bytes)) { + /* We would like to abort here and stop processing, so that + * the transfer loop can handle the data/close here. However, + * this may leave data in underlying buffers that will not + * be consumed. */ + if(!cf->next || !cf->next->cft->has_data_pending(cf->next, data)) + drain_stream(cf, data, stream); + break; + } - if(nread == rv) { - DEBUGF(infof(data, - "h2_process_pending_input: All data in connection buffer " - "processed\n")); - httpc->inbuflen = 0; - httpc->nread_inbuf = 0; - } - else { - httpc->nread_inbuf += rv; - DEBUGF(infof(data, - "h2_process_pending_input: %zu bytes left in connection " - "buffer\n", - httpc->inbuflen - httpc->nread_inbuf)); - } + nread = Curl_bufq_sipn(&ctx->inbufq, 0, 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); + data_max_bytes = (data_max_bytes > (size_t)nread) ? + (data_max_bytes - (size_t)nread) : 0; + } - rv = h2_session_send(data, httpc->h2); - if(rv != 0) { - *err = CURLE_SEND_ERROR; - return -1; + if(h2_process_pending_input(cf, data, &result)) + return result; + CURL_TRC_CF(data, cf, "[0] progress ingress: inbufg=%zu", + Curl_bufq_len(&ctx->inbufq)); } - if(should_close_session(httpc)) { - DEBUGF(infof(data, - "h2_process_pending_input: nothing to do in this session\n")); - *err = CURLE_HTTP2; - return -1; + if(ctx->conn_closed && Curl_bufq_is_empty(&ctx->inbufq)) { + connclose(cf->conn, "GOAWAY received"); } - return 0; + CURL_TRC_CF(data, cf, "[0] progress ingress: done"); + return CURLE_OK; } -/* - * Called from transfer.c:done_sending when we stop uploading. - */ -CURLcode Curl_http2_done_sending(struct connectdata *conn) +static ssize_t cf_h2_recv(struct Curl_cfilter *cf, struct Curl_easy *data, + char *buf, size_t len, CURLcode *err) { - CURLcode result = CURLE_OK; + struct cf_h2_ctx *ctx = cf->ctx; + struct h2_stream_ctx *stream = H2_STREAM_CTX(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, "http/2 recv on a transfer never opened " + "or already cleared, mid=%" FMT_OFF_T, data->mid); + *err = CURLE_HTTP2; + return -1; + } - if((conn->handler == &Curl_handler_http2_ssl) || - (conn->handler == &Curl_handler_http2)) { - /* make sure this is only attempted for HTTP/2 transfers */ + CF_DATA_SAVE(save, cf, data); - struct HTTP *stream = conn->data->req.protop; + nread = stream_recv(cf, data, stream, buf, len, err); + if(nread < 0 && *err != CURLE_AGAIN) + goto out; - if(stream->upload_left) { - /* If the stream still thinks there's data left to upload. */ - struct http_conn *httpc = &conn->proto.httpc; - nghttp2_session *h2 = httpc->h2; + if(nread < 0) { + *err = h2_progress_ingress(cf, data, len); + if(*err) + goto out; - stream->upload_left = 0; /* DONE! */ + nread = stream_recv(cf, data, stream, buf, len, err); + } - /* resume sending here to trigger the callback to get called again so - that it can signal EOF to nghttp2 */ - (void)nghttp2_session_resume_data(h2, stream->stream_id); + 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); + } + } - (void)h2_process_pending_input(conn->data, httpc, &result); + if(stream->closed) { + CURL_TRC_CF(data, cf, "[%d] DRAIN closed stream", stream->id); + drain_stream(cf, data, stream); } } - return result; -} +out: + result = h2_progress_egress(cf, data); + if(result == CURLE_AGAIN) { + /* pending data to send, need to be called again. Ideally, we + * monitor the socket for POLLOUT, but when not SENDING + * any more, we force processing of the transfer. */ + if(!CURL_WANT_SEND(data)) + drain_stream(cf, data, stream); + } + else if(result) { + *err = result; + nread = -1; + } + CURL_TRC_CF(data, cf, "[%d] cf_recv(len=%zu) -> %zd %d, " + "window=%d/%d, connection %d/%d", + stream->id, len, nread, *err, + 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 http2_handle_stream_close(struct connectdata *conn, - struct Curl_easy *data, - struct HTTP *stream, CURLcode *err) +static ssize_t cf_h2_body_send(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct h2_stream_ctx *stream, + const void *buf, size_t blen, bool eos, + CURLcode *err) { - char *trailer_pos, *trailer_end; - CURLcode result; - struct http_conn *httpc = &conn->proto.httpc; + struct cf_h2_ctx *ctx = cf->ctx; + ssize_t nwritten; - if(httpc->pause_stream_id == stream->stream_id) { - httpc->pause_stream_id = 0; + 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, "[%d] discarding data" + "on closed stream with response", stream->id); + if(eos) + stream->body_eos = TRUE; + *err = CURLE_OK; + return (ssize_t)blen; + } + /* Server closed before we got a response, this is an error */ + infof(data, "stream %u closed", stream->id); + *err = CURLE_SEND_ERROR; + return -1; } - DEBUGASSERT(httpc->drain_total >= data->state.drain); - httpc->drain_total -= data->state.drain; - data->state.drain = 0; + nwritten = Curl_bufq_write(&stream->sendbuf, buf, blen, err); + if(nwritten < 0) + return -1; - if(httpc->pause_stream_id == 0) { - if(h2_process_pending_input(data, httpc, err) != 0) { + if(eos && (blen == (size_t)nwritten)) + stream->body_eos = TRUE; + + if(eos || !Curl_bufq_is_empty(&stream->sendbuf)) { + /* resume the potentially suspended stream */ + int rv = nghttp2_session_resume_data(ctx->h2, stream->id); + if(nghttp2_is_fatal(rv)) { + *err = CURLE_SEND_ERROR; return -1; } } + return nwritten; +} - DEBUGASSERT(data->state.drain == 0); +static ssize_t h2_submit(struct h2_stream_ctx **pstream, + struct Curl_cfilter *cf, struct Curl_easy *data, + const void *buf, size_t len, + bool eos, CURLcode *err) +{ + struct cf_h2_ctx *ctx = cf->ctx; + struct h2_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; - /* Reset to FALSE to prevent infinite loop in readwrite_data function. */ - stream->closed = FALSE; - if(stream->error_code != NGHTTP2_NO_ERROR) { - failf(data, "HTTP/2 stream %u was not closed cleanly: %s (err %d)", - stream->stream_id, Curl_http2_strerror(stream->error_code), - stream->error_code); - *err = CURLE_HTTP2_STREAM; - return -1; + Curl_dynhds_init(&h2_headers, 0, DYN_HTTP_REQUEST); + + *err = http2_data_setup(cf, data, &stream); + if(*err) { + nwritten = -1; + goto out; } - if(!stream->bodystarted) { - failf(data, "HTTP/2 stream %u was closed cleanly, but before getting " - " all response header fields, teated as error", - stream->stream_id); - *err = CURLE_HTTP2_STREAM; - return -1; + 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); - if(stream->trailer_recvbuf && stream->trailer_recvbuf->buffer) { - trailer_pos = stream->trailer_recvbuf->buffer; - trailer_end = trailer_pos + stream->trailer_recvbuf->size_used; + nva = Curl_dynhds_to_nva(&h2_headers, &nheader); + if(!nva) { + *err = CURLE_OUT_OF_MEMORY; + nwritten = -1; + goto out; + } - for(; trailer_pos < trailer_end;) { - uint32_t n; - memcpy(&n, trailer_pos, sizeof(n)); - trailer_pos += sizeof(n); + h2_pri_spec(ctx, data, &pri_spec); + if(!nghttp2_session_check_request_allowed(ctx->h2)) + CURL_TRC_CF(data, cf, "send request NOT allowed (via nghttp2)"); - result = Curl_client_write(conn, CLIENTWRITE_HEADER, trailer_pos, n); - if(result) { - *err = result; - return -1; - } + switch(data->state.httpreq) { + case HTTPREQ_POST: + case HTTPREQ_POST_FORM: + case HTTPREQ_POST_MIME: + case HTTPREQ_PUT: + 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_id = nghttp2_submit_request(ctx->h2, &pri_spec, nva, nheader, + NULL, data); + } - trailer_pos += n + 1; - } + 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; } - stream->close_handled = TRUE; +#define MAX_ACC 60000 /* <64KB to account for some overhead */ + if(Curl_trc_is_verbose(data)) { + size_t acc = 0; - DEBUGF(infof(data, "http2_recv returns 0, http2_handle_stream_close\n")); - return 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; -/* - * 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. - */ + infof(data, "[HTTP/2] [%d] [%.*s: %.*s]", stream_id, + (int)nva[i].namelen, nva[i].name, + (int)nva[i].valuelen, nva[i].value); + } -static void h2_pri_spec(struct Curl_easy *data, - nghttp2_priority_spec *pri_spec) -{ - struct HTTP *depstream = (data->set.stream_depends_on? - data->set.stream_depends_on->req.protop:NULL); - int32_t depstream_id = depstream? depstream->stream_id:0; - nghttp2_priority_spec_init(pri_spec, depstream_id, data->set.stream_weight, - data->set.stream_depends_e); - data->state.stream_weight = data->set.stream_weight; - data->state.stream_depends_e = data->set.stream_depends_e; - data->state.stream_depends_on = data->set.stream_depends_on; -} + 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); + } + } -/* - * h2_session_send() checks if there's been an update in the priority / - * dependency settings and if so it submits a PRIORITY frame with the updated - * info. - */ -static int h2_session_send(struct Curl_easy *data, - nghttp2_session *h2) -{ - struct HTTP *stream = data->req.protop; - if((data->set.stream_weight != data->state.stream_weight) || - (data->set.stream_depends_e != data->state.stream_depends_e) || - (data->set.stream_depends_on != data->state.stream_depends_on) ) { - /* send new weight and/or dependency */ - nghttp2_priority_spec pri_spec; - int rv; + stream->id = stream_id; - h2_pri_spec(data, &pri_spec); + body = (const char *)buf + nwritten; + bodylen = len - nwritten; - DEBUGF(infof(data, "Queuing PRIORITY on stream %u (easy %p)\n", - stream->stream_id, data)); - rv = nghttp2_submit_priority(h2, NGHTTP2_FLAG_NONE, stream->stream_id, - &pri_spec); - if(rv) - return rv; + if(bodylen || eos) { + ssize_t n = cf_h2_body_send(cf, data, stream, body, bodylen, eos, err); + if(n >= 0) + nwritten += n; + else if(*err == CURLE_AGAIN) + *err = CURLE_OK; + else if(*err != CURLE_AGAIN) { + *err = CURLE_SEND_ERROR; + nwritten = -1; + goto out; + } } - return nghttp2_session_send(h2); +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 http2_recv(struct connectdata *conn, int sockindex, - char *mem, size_t len, CURLcode *err) +static ssize_t cf_h2_send(struct Curl_cfilter *cf, struct Curl_easy *data, + const void *buf, size_t len, bool eos, + CURLcode *err) { - CURLcode result = CURLE_OK; - ssize_t rv; - ssize_t nread; - struct http_conn *httpc = &conn->proto.httpc; - struct Curl_easy *data = conn->data; - struct HTTP *stream = data->req.protop; - - (void)sockindex; /* we always do HTTP2 on sockindex 0 */ + struct cf_h2_ctx *ctx = cf->ctx; + struct h2_stream_ctx *stream = H2_STREAM_CTX(ctx, data); + struct cf_call_data save; + ssize_t nwritten; + CURLcode result; - if(should_close_session(httpc)) { - DEBUGF(infof(data, - "http2_recv: nothing to do in this session\n")); - *err = CURLE_HTTP2; - return -1; - } + CF_DATA_SAVE(save, cf, data); - /* Nullify here because we call nghttp2_session_send() and they - might refer to the old buffer. */ - stream->upload_mem = NULL; - stream->upload_len = 0; - - /* - * At this point 'stream' is just in the Curl_easy the connection - * identifies as its owner at this time. - */ - - if(stream->bodystarted && - stream->nread_header_recvbuf < stream->header_recvbuf->size_used) { - /* If there is body data pending for this stream to return, do that */ - size_t left = - stream->header_recvbuf->size_used - stream->nread_header_recvbuf; - size_t ncopy = MIN(len, left); - memcpy(mem, stream->header_recvbuf->buffer + stream->nread_header_recvbuf, - ncopy); - stream->nread_header_recvbuf += ncopy; - - DEBUGF(infof(data, "http2_recv: Got %d bytes from header_recvbuf\n", - (int)ncopy)); - return ncopy; - } - - DEBUGF(infof(data, "http2_recv: easy %p (stream %u)\n", - data, stream->stream_id)); - - if((data->state.drain) && stream->memlen) { - DEBUGF(infof(data, "http2_recv: DRAIN %zu bytes stream %u!! (%p => %p)\n", - stream->memlen, stream->stream_id, - stream->mem, mem)); - if(mem != stream->mem) { - /* if we didn't get the same buffer this time, we must move the data to - the beginning */ - memmove(mem, stream->mem, stream->memlen); - stream->len = len - stream->memlen; - stream->mem = mem; - } - if(httpc->pause_stream_id == stream->stream_id && !stream->pausedata) { - /* We have paused nghttp2, but we have no pause data (see - on_data_chunk_recv). */ - httpc->pause_stream_id = 0; - if(h2_process_pending_input(data, httpc, &result) != 0) { - *err = result; - return -1; - } + if(!stream || stream->id == -1) { + nwritten = h2_submit(&stream, cf, data, buf, len, eos, err); + if(nwritten < 0) { + goto out; } - } - else if(stream->pausedata) { - DEBUGASSERT(httpc->pause_stream_id == stream->stream_id); - nread = MIN(len, stream->pauselen); - memcpy(mem, stream->pausedata, nread); - - stream->pausedata += nread; - stream->pauselen -= nread; - - infof(data, "%zu data bytes written\n", nread); - if(stream->pauselen == 0) { - DEBUGF(infof(data, "Unpaused by stream %u\n", stream->stream_id)); - assert(httpc->pause_stream_id == stream->stream_id); - httpc->pause_stream_id = 0; - - stream->pausedata = NULL; - stream->pauselen = 0; - - /* When NGHTTP2_ERR_PAUSE is returned from - data_source_read_callback, we might not process DATA frame - fully. Calling nghttp2_session_mem_recv() again will - continue to process DATA frame, but if there is no incoming - frames, then we have to call it again with 0-length data. - Without this, on_stream_close callback will not be called, - and stream could be hanged. */ - if(h2_process_pending_input(data, httpc, &result) != 0) { - *err = result; - return -1; - } + DEBUGASSERT(stream); + } + else if(stream->body_eos) { + /* We already wrote this, but CURLE_AGAINed the call due to not + * being able to flush stream->sendbuf. Make a 0-length write + * to trigger flushing again. + * If this works, we report to have written `len` bytes. */ + DEBUGASSERT(eos); + nwritten = cf_h2_body_send(cf, data, stream, buf, 0, eos, err); + CURL_TRC_CF(data, cf, "[%d] cf_body_send last CHUNK -> %zd, %d, eos=%d", + stream->id, nwritten, *err, eos); + if(nwritten < 0) { + goto out; } - DEBUGF(infof(data, "http2_recv: returns unpaused %zd bytes on stream %u\n", - nread, stream->stream_id)); - return nread; - } - else if(httpc->pause_stream_id) { - /* If a stream paused nghttp2_session_mem_recv previously, and has - not processed all data, it still refers to the buffer in - nghttp2_session. If we call nghttp2_session_mem_recv(), we may - overwrite that buffer. To avoid that situation, just return - here with CURLE_AGAIN. This could be busy loop since data in - socket is not read. But it seems that usually streams are - notified with its drain property, and socket is read again - quickly. */ - DEBUGF(infof(data, "stream %x is paused, pause id: %x\n", - stream->stream_id, httpc->pause_stream_id)); - *err = CURLE_AGAIN; - return -1; + nwritten = len; } else { - char *inbuf; - /* remember where to store incoming data for this stream and how big the - buffer is */ - stream->mem = mem; - stream->len = len; - stream->memlen = 0; - - if(httpc->inbuflen == 0) { - nread = ((Curl_recv *)httpc->recv_underlying)( - conn, FIRSTSOCKET, httpc->inbuf, H2_BUFSIZE, &result); - - if(nread == -1) { - if(result != CURLE_AGAIN) - failf(data, "Failed receiving HTTP2 data"); - else if(stream->closed) - /* received when the stream was already closed! */ - return http2_handle_stream_close(conn, data, stream, err); - - *err = result; - return -1; - } - - if(nread == 0) { - failf(data, "Unexpected EOF"); - *err = CURLE_RECV_ERROR; - return -1; - } - - DEBUGF(infof(data, "nread=%zd\n", nread)); + nwritten = cf_h2_body_send(cf, data, stream, buf, len, eos, err); + CURL_TRC_CF(data, cf, "[%d] cf_body_send(len=%zu) -> %zd, %d, eos=%d", + stream->id, len, nwritten, *err, eos); + } - httpc->inbuflen = nread; - inbuf = httpc->inbuf; - } - else { - nread = httpc->inbuflen - httpc->nread_inbuf; - inbuf = httpc->inbuf + httpc->nread_inbuf; + /* 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); - DEBUGF(infof(data, "Use data left in connection buffer, nread=%zd\n", - nread)); - } - rv = nghttp2_session_mem_recv(httpc->h2, (const uint8_t *)inbuf, nread); + /* 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) { + infof(data, "stream %u closed", stream->id); + *err = CURLE_SEND_ERROR; + nwritten = -1; + goto out; + } + else if(result && (result != CURLE_AGAIN)) { + *err = result; + nwritten = -1; + goto out; + } - if(nghttp2_is_fatal((int)rv)) { - failf(data, "nghttp2_session_mem_recv() returned %d:%s\n", - rv, nghttp2_strerror((int)rv)); - *err = CURLE_RECV_ERROR; - return 0; - } - DEBUGF(infof(data, "nghttp2_session_mem_recv() returns %zd\n", rv)); - if(nread == rv) { - DEBUGF(infof(data, "All data in connection buffer processed\n")); - httpc->inbuflen = 0; - httpc->nread_inbuf = 0; + 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 { - httpc->nread_inbuf += rv; - DEBUGF(infof(data, "%zu bytes left in connection buffer\n", - httpc->inbuflen - httpc->nread_inbuf)); - } - /* Always send pending frames in nghttp2 session, because - nghttp2_session_mem_recv() may queue new frame */ - rv = h2_session_send(data, httpc->h2); - if(rv != 0) { - *err = CURLE_SEND_ERROR; - return 0; - } - - if(should_close_session(httpc)) { - DEBUGF(infof(data, "http2_recv: nothing to do in this session\n")); + CURL_TRC_CF(data, cf, "send: nothing to do in this session"); *err = CURLE_HTTP2; - return -1; + nwritten = -1; } } - if(stream->memlen) { - ssize_t retlen = stream->memlen; - DEBUGF(infof(data, "http2_recv: returns %zd for stream %u\n", - retlen, stream->stream_id)); - stream->memlen = 0; - if(httpc->pause_stream_id == stream->stream_id) { - /* data for this stream is returned now, but this stream caused a pause - already so we need it called again asap */ - DEBUGF(infof(data, "Data returned for PAUSED stream %u\n", - stream->stream_id)); - } - else if(!stream->closed) { - DEBUGASSERT(httpc->drain_total >= data->state.drain); - httpc->drain_total -= data->state.drain; - data->state.drain = 0; /* this stream is hereby drained */ - } - - return retlen; - } - /* If stream is closed, return 0 to signal the http routine to close - the connection */ - if(stream->closed) { - return http2_handle_stream_close(conn, data, stream, err); - } - *err = CURLE_AGAIN; - DEBUGF(infof(data, "http2_recv returns AGAIN for stream %u\n", - stream->stream_id)); - return -1; -} - -/* Index where :authority header field will appear in request header - field list. */ -#define AUTHORITY_DST_IDX 3 - -#define HEADER_OVERFLOW(x) \ - (x.namelen > (uint16_t)-1 || x.valuelen > (uint16_t)-1 - x.namelen) - -/* - * Check header memory for the token "trailers". - * Parse the tokens as separated by comma and surrounded by whitespace. - * Returns TRUE if found or FALSE if not. - */ -static bool contains_trailers(const char *p, size_t len) -{ - const char *end = p + len; - for(;;) { - for(; p != end && (*p == ' ' || *p == '\t'); ++p) - ; - if(p == end || (size_t)(end - p) < sizeof("trailers") - 1) - return FALSE; - if(strncasecompare("trailers", p, sizeof("trailers") - 1)) { - p += sizeof("trailers") - 1; - for(; p != end && (*p == ' ' || *p == '\t'); ++p) - ; - if(p == end || *p == ',') - return TRUE; - } - /* skip to next token */ - for(; p != end && *p != ','; ++p) - ; - if(p == end) - return FALSE; - ++p; - } -} - -typedef enum { - /* Send header to server */ - HEADERINST_FORWARD, - /* Don't send header to server */ - HEADERINST_IGNORE, - /* Discard header, and replace it with "te: trailers" */ - HEADERINST_TE_TRAILERS -} header_instruction; - -/* Decides how to treat given header field. */ -static header_instruction inspect_header(const char *name, size_t namelen, - const char *value, size_t valuelen) { - switch(namelen) { - case 2: - if(!strncasecompare("te", name, namelen)) - return HEADERINST_FORWARD; - - return contains_trailers(value, valuelen) ? - HEADERINST_TE_TRAILERS : HEADERINST_IGNORE; - case 7: - return strncasecompare("upgrade", name, namelen) ? - HEADERINST_IGNORE : HEADERINST_FORWARD; - case 10: - return (strncasecompare("connection", name, namelen) || - strncasecompare("keep-alive", name, namelen)) ? - HEADERINST_IGNORE : HEADERINST_FORWARD; - case 16: - return strncasecompare("proxy-connection", name, namelen) ? - HEADERINST_IGNORE : HEADERINST_FORWARD; - case 17: - return strncasecompare("transfer-encoding", name, namelen) ? - HEADERINST_IGNORE : HEADERINST_FORWARD; - default: - return HEADERINST_FORWARD; +out: + if(stream) { + CURL_TRC_CF(data, cf, "[%d] cf_send(len=%zu) -> %zd, %d, " + "eos=%d, h2 windows %d-%d (stream-conn), " + "buffers %zu-%zu (stream-conn)", + stream->id, len, nwritten, *err, + stream->body_eos, + 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 ssize_t http2_send(struct connectdata *conn, int sockindex, - const void *mem, size_t len, CURLcode *err) +static CURLcode cf_h2_flush(struct Curl_cfilter *cf, + struct Curl_easy *data) { - /* - * BIG TODO: Currently, we send request in this function, but this - * function is also used to send request body. It would be nice to - * add dedicated function for request. - */ - int rv; - struct http_conn *httpc = &conn->proto.httpc; - struct HTTP *stream = conn->data->req.protop; - nghttp2_nv *nva = NULL; - size_t nheader; - size_t i; - size_t authority_idx; - char *hdbuf = (char *)mem; - char *end, *line_end; - nghttp2_data_provider data_prd; - int32_t stream_id; - nghttp2_session *h2 = httpc->h2; - nghttp2_priority_spec pri_spec; - - (void)sockindex; - - DEBUGF(infof(conn->data, "http2_send len=%zu\n", len)); + struct cf_h2_ctx *ctx = cf->ctx; + struct h2_stream_ctx *stream = H2_STREAM_CTX(ctx, data); + struct cf_call_data save; + CURLcode result = CURLE_OK; - if(stream->stream_id != -1) { - if(stream->close_handled) { - infof(conn->data, "stream %d closed\n", stream->stream_id); - *err = CURLE_HTTP2_STREAM; - return -1; - } - else if(stream->closed) { - return http2_handle_stream_close(conn, conn->data, stream, err); - } - /* If stream_id != -1, we have dispatched request HEADERS, and now - are going to send or sending request body in DATA frame */ - stream->upload_mem = mem; - stream->upload_len = len; - nghttp2_session_resume_data(h2, stream->stream_id); - rv = h2_session_send(conn->data, h2); + CF_DATA_SAVE(save, cf, data); + if(stream && !Curl_bufq_is_empty(&stream->sendbuf)) { + /* resume the potentially suspended stream */ + int rv = nghttp2_session_resume_data(ctx->h2, stream->id); if(nghttp2_is_fatal(rv)) { - *err = CURLE_SEND_ERROR; - return -1; - } - len -= stream->upload_len; - - /* Nullify here because we call nghttp2_session_send() and they - might refer to the old buffer. */ - stream->upload_mem = NULL; - stream->upload_len = 0; - - if(should_close_session(httpc)) { - DEBUGF(infof(conn->data, "http2_send: nothing to do in this session\n")); - *err = CURLE_HTTP2; - return -1; + result = CURLE_SEND_ERROR; + goto out; } + } - if(stream->upload_left) { - /* we are sure that we have more data to send here. Calling the - following API will make nghttp2_session_want_write() return - nonzero if remote window allows it, which then libcurl checks - socket is writable or not. See http2_perform_getsock(). */ - nghttp2_session_resume_data(h2, stream->stream_id); - } + result = h2_progress_egress(cf, data); - DEBUGF(infof(conn->data, "http2_send returns %zu for stream %u\n", len, - stream->stream_id)); - return len; +out: + if(stream) { + CURL_TRC_CF(data, cf, "[%d] flush -> %d, " + "h2 windows %d-%d (stream-conn), " + "buffers %zu-%zu (stream-conn)", + stream->id, result, + 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)); } - - /* Calculate number of headers contained in [mem, mem + len) */ - /* Here, we assume the curl http code generate *correct* HTTP header - field block */ - nheader = 0; - for(i = 1; i < len; ++i) { - if(hdbuf[i] == '\n' && hdbuf[i - 1] == '\r') { - ++nheader; - ++i; - } + else { + CURL_TRC_CF(data, cf, "flush -> %d, " + "connection-window=%d, nw_send_buffer(%zu)", + result, nghttp2_session_get_remote_window_size(ctx->h2), + Curl_bufq_len(&ctx->outbufq)); } - if(nheader < 2) - goto fail; + CF_DATA_RESTORE(cf, save); + return result; +} - /* We counted additional 2 \r\n in the first and last line. We need 3 - new headers: :method, :path and :scheme. Therefore we need one - more space. */ - nheader += 1; - nva = malloc(sizeof(nghttp2_nv) * nheader); - if(nva == NULL) { - *err = CURLE_OUT_OF_MEMORY; - return -1; +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; + struct cf_call_data save; + curl_socket_t sock; + bool want_recv, want_send; + + if(!ctx->h2) + return; + + sock = Curl_conn_cf_get_socket(cf, data); + Curl_pollset_check(data, ps, sock, &want_recv, &want_send); + if(want_recv || want_send) { + struct h2_stream_ctx *stream = H2_STREAM_CTX(ctx, data); + bool c_exhaust, s_exhaust; + + CF_DATA_SAVE(save, cf, data); + c_exhaust = want_send && !nghttp2_session_get_remote_window_size(ctx->h2); + s_exhaust = want_send && 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_bufq_is_empty(&ctx->outbufq); + + Curl_pollset_set(data, ps, sock, want_recv, want_send); + CF_DATA_RESTORE(cf, save); + } + else if(ctx->sent_goaway && !cf->shutdown) { + /* shutdown in progress */ + CF_DATA_SAVE(save, cf, data); + want_send = nghttp2_session_want_write(ctx->h2) || + !Curl_bufq_is_empty(&ctx->outbufq); + want_recv = nghttp2_session_want_read(ctx->h2); + Curl_pollset_set(data, ps, sock, want_recv, want_send); + CF_DATA_RESTORE(cf, save); } +} - /* Extract :method, :path from request line */ - line_end = strstr(hdbuf, "\r\n"); +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; - /* Method does not contain spaces */ - end = memchr(hdbuf, ' ', line_end - hdbuf); - if(!end || end == hdbuf) - goto fail; - nva[0].name = (unsigned char *)":method"; - nva[0].namelen = strlen((char *)nva[0].name); - nva[0].value = (unsigned char *)hdbuf; - nva[0].valuelen = (size_t)(end - hdbuf); - nva[0].flags = NGHTTP2_NV_FLAG_NONE; - if(HEADER_OVERFLOW(nva[0])) { - failf(conn->data, "Failed sending HTTP request: Header overflow"); - goto fail; + if(cf->connected) { + *done = TRUE; + return CURLE_OK; } - hdbuf = end + 1; - - /* Path may contain spaces so scan backwards */ - end = NULL; - for(i = (size_t)(line_end - hdbuf); i; --i) { - if(hdbuf[i - 1] == ' ') { - end = &hdbuf[i - 1]; - break; - } - } - if(!end || end == hdbuf) - goto fail; - nva[1].name = (unsigned char *)":path"; - nva[1].namelen = strlen((char *)nva[1].name); - nva[1].value = (unsigned char *)hdbuf; - nva[1].valuelen = (size_t)(end - hdbuf); - nva[1].flags = NGHTTP2_NV_FLAG_NONE; - if(HEADER_OVERFLOW(nva[1])) { - failf(conn->data, "Failed sending HTTP request: Header overflow"); - goto fail; + /* Connect the lower filters first */ + if(!cf->next->connected) { + result = Curl_conn_cf_connect(cf->next, data, blocking, done); + if(result || !*done) + return result; } - hdbuf = end + 1; + *done = FALSE; - end = line_end; - nva[2].name = (unsigned char *)":scheme"; - nva[2].namelen = strlen((char *)nva[2].name); - if(conn->handler->flags & PROTOPT_SSL) - nva[2].value = (unsigned char *)"https"; - else - nva[2].value = (unsigned char *)"http"; - nva[2].valuelen = strlen((char *)nva[2].value); - nva[2].flags = NGHTTP2_NV_FLAG_NONE; - if(HEADER_OVERFLOW(nva[2])) { - failf(conn->data, "Failed sending HTTP request: Header overflow"); - goto fail; + CF_DATA_SAVE(save, cf, data); + DEBUGASSERT(ctx->initialized); + if(!ctx->h2) { + result = cf_h2_ctx_open(cf, data); + if(result) + goto out; } - authority_idx = 0; - i = 3; - while(i < nheader) { - size_t hlen; - - hdbuf = line_end + 2; + result = h2_progress_ingress(cf, data, H2_CHUNK_SIZE); + 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; +} - line_end = strstr(hdbuf, "\r\n"); - if(line_end == hdbuf) - goto fail; +static void cf_h2_close(struct Curl_cfilter *cf, struct Curl_easy *data) +{ + struct cf_h2_ctx *ctx = cf->ctx; - /* header continuation lines are not supported */ - if(*hdbuf == ' ' || *hdbuf == '\t') - goto fail; + if(ctx) { + struct cf_call_data save; - for(end = hdbuf; end < line_end && *end != ':'; ++end) - ; - if(end == hdbuf || end == line_end) - goto fail; - hlen = end - hdbuf; + CF_DATA_SAVE(save, cf, data); + cf_h2_ctx_close(ctx); + CF_DATA_RESTORE(cf, save); + cf->connected = FALSE; + } + if(cf->next) + cf->next->cft->do_close(cf->next, data); +} - if(hlen == 4 && strncasecompare("host", hdbuf, 4)) { - authority_idx = i; - nva[i].name = (unsigned char *)":authority"; - nva[i].namelen = strlen((char *)nva[i].name); - } - else { - nva[i].name = (unsigned char *)hdbuf; - nva[i].namelen = (size_t)(end - hdbuf); - } - hdbuf = end + 1; - while(*hdbuf == ' ' || *hdbuf == '\t') - ++hdbuf; - end = line_end; - - switch(inspect_header((const char *)nva[i].name, nva[i].namelen, hdbuf, - end - hdbuf)) { - case HEADERINST_IGNORE: - /* skip header fields prohibited by HTTP/2 specification. */ - --nheader; - continue; - case HEADERINST_TE_TRAILERS: - nva[i].value = (uint8_t*)"trailers"; - nva[i].valuelen = sizeof("trailers") - 1; - break; - default: - nva[i].value = (unsigned char *)hdbuf; - nva[i].valuelen = (size_t)(end - hdbuf); - } +static void cf_h2_destroy(struct Curl_cfilter *cf, struct Curl_easy *data) +{ + struct cf_h2_ctx *ctx = cf->ctx; - nva[i].flags = NGHTTP2_NV_FLAG_NONE; - if(HEADER_OVERFLOW(nva[i])) { - failf(conn->data, "Failed sending HTTP request: Header overflow"); - goto fail; - } - ++i; + (void)data; + if(ctx) { + cf_h2_ctx_free(ctx); + cf->ctx = NULL; } +} - /* :authority must come before non-pseudo header fields */ - if(authority_idx != 0 && authority_idx != AUTHORITY_DST_IDX) { - nghttp2_nv authority = nva[authority_idx]; - for(i = authority_idx; i > AUTHORITY_DST_IDX; --i) { - nva[i] = nva[i - 1]; - } - nva[i] = authority; - } +static CURLcode cf_h2_shutdown(struct Curl_cfilter *cf, + struct Curl_easy *data, bool *done) +{ + struct cf_h2_ctx *ctx = cf->ctx; + struct cf_call_data save; + CURLcode result; + int rv; - /* Warn stream may be rejected if cumulative length of headers is too large. - It appears nghttp2 will not send a header frame larger than 64KB. */ -#define MAX_ACC 60000 /* <64KB to account for some overhead */ - { - size_t acc = 0; + if(!cf->connected || !ctx->h2 || cf->shutdown || ctx->conn_closed) { + *done = TRUE; + return CURLE_OK; + } - for(i = 0; i < nheader; ++i) { - acc += nva[i].namelen + nva[i].valuelen; + CF_DATA_SAVE(save, cf, data); - DEBUGF(infof(conn->data, "h2 header: %.*s:%.*s\n", - nva[i].namelen, nva[i].name, - nva[i].valuelen, nva[i].value)); + if(!ctx->sent_goaway) { + rv = nghttp2_submit_goaway(ctx->h2, NGHTTP2_FLAG_NONE, + ctx->local_max_sid, 0, + (const uint8_t *)"shutdown", + sizeof("shutdown")); + if(rv) { + failf(data, "nghttp2_submit_goaway() failed: %s(%d)", + nghttp2_strerror(rv), rv); + result = CURLE_SEND_ERROR; + goto out; } + ctx->sent_goaway = TRUE; + } + /* GOAWAY submitted, process egress and ingress until nghttp2 is done. */ + result = CURLE_OK; + if(nghttp2_session_want_write(ctx->h2) || + !Curl_bufq_is_empty(&ctx->outbufq)) + result = h2_progress_egress(cf, data); + if(!result && nghttp2_session_want_read(ctx->h2)) + result = h2_progress_ingress(cf, data, 0); + + if(result == CURLE_AGAIN) + result = CURLE_OK; + + *done = (ctx->conn_closed || + (!result && !nghttp2_session_want_write(ctx->h2) && + !nghttp2_session_want_read(ctx->h2) && + Curl_bufq_is_empty(&ctx->outbufq))); + +out: + CF_DATA_RESTORE(cf, save); + cf->shutdown = (result || *done); + return result; +} - if(acc > MAX_ACC) { - infof(conn->data, "http2_send: Warning: The cumulative length of all " - "headers exceeds %zu bytes and that could cause the " - "stream to be rejected.\n", MAX_ACC); +static CURLcode http2_data_pause(struct Curl_cfilter *cf, + struct Curl_easy *data, + bool pause) +{ + struct cf_h2_ctx *ctx = cf->ctx; + struct h2_stream_ctx *stream = H2_STREAM_CTX(ctx, data); + + DEBUGASSERT(data); + if(ctx && ctx->h2 && stream) { + CURLcode result = cf_h2_update_local_win(cf, data, stream, pause); + if(result) + return result; + + /* 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); } + CURL_TRC_CF(data, cf, "[%d] stream now %spaused", stream->id, + pause ? "" : "un"); } + return CURLE_OK; +} - h2_pri_spec(conn->data, &pri_spec); +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; - switch(conn->data->set.httpreq) { - case HTTPREQ_POST: - case HTTPREQ_POST_FORM: - case HTTPREQ_PUT: - if(conn->data->state.infilesize != -1) - stream->upload_left = conn->data->state.infilesize; - else - /* data sending without specifying the data amount up front */ - stream->upload_left = -1; /* unknown, but not zero */ + (void)arg2; - data_prd.read_callback = data_source_read_callback; - data_prd.source.ptr = NULL; - stream_id = nghttp2_submit_request(h2, &pri_spec, nva, nheader, - &data_prd, conn->data); + 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_FLUSH: + result = cf_h2_flush(cf, data); + break; + case CF_CTRL_DATA_DETACH: + http2_data_done(cf, data); + break; + case CF_CTRL_DATA_DONE: + http2_data_done(cf, data); break; default: - stream_id = nghttp2_submit_request(h2, &pri_spec, nva, nheader, - NULL, conn->data); + break; } + CF_DATA_RESTORE(cf, save); + return result; +} - Curl_safefree(nva); +static bool cf_h2_data_pending(struct Curl_cfilter *cf, + const struct Curl_easy *data) +{ + struct cf_h2_ctx *ctx = cf->ctx; + struct h2_stream_ctx *stream = H2_STREAM_CTX(ctx, data); - if(stream_id < 0) { - DEBUGF(infof(conn->data, "http2_send() send error\n")); - *err = CURLE_SEND_ERROR; - return -1; - } + if(ctx && (!Curl_bufq_is_empty(&ctx->inbufq) + || (stream && !Curl_bufq_is_empty(&stream->sendbuf)))) + return TRUE; + return cf->next ? cf->next->cft->has_data_pending(cf->next, data) : FALSE; +} - infof(conn->data, "Using Stream ID: %x (easy handle %p)\n", - stream_id, conn->data); - stream->stream_id = stream_id; +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; - /* this does not call h2_session_send() since there can not have been any - * priority upodate since the nghttp2_submit_request() call above */ - rv = nghttp2_session_send(h2); + 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; +} - if(rv != 0) { - *err = CURLE_SEND_ERROR; - return -1; - } +static CURLcode cf_h2_keep_alive(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + CURLcode result; + struct cf_call_data save; - if(should_close_session(httpc)) { - DEBUGF(infof(conn->data, "http2_send: nothing to do in this session\n")); - *err = CURLE_HTTP2; - return -1; - } + CF_DATA_SAVE(save, cf, data); + result = http2_send_ping(cf, data); + CF_DATA_RESTORE(cf, save); + return result; +} - if(stream->stream_id != -1) { - /* If whole HEADERS frame was sent off to the underlying socket, - the nghttp2 library calls data_source_read_callback. But only - it found that no data available, so it deferred the DATA - transmission. Which means that nghttp2_session_want_write() - returns 0 on http2_perform_getsock(), which results that no - writable socket check is performed. To workaround this, we - issue nghttp2_session_resume_data() here to bring back DATA - transmission from deferred state. */ - nghttp2_session_resume_data(h2, stream->stream_id); +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; + case CF_QUERY_STREAM_ERROR: { + struct h2_stream_ctx *stream = H2_STREAM_CTX(ctx, data); + *pres1 = stream ? (int)stream->error : 0; + return CURLE_OK; + } + case CF_QUERY_NEED_FLUSH: { + struct h2_stream_ctx *stream = H2_STREAM_CTX(ctx, data); + if(!Curl_bufq_is_empty(&ctx->outbufq) || + (stream && !Curl_bufq_is_empty(&stream->sendbuf))) { + *pres1 = TRUE; + return CURLE_OK; + } + break; } - - return len; - -fail: - free(nva); - *err = CURLE_SEND_ERROR; - return -1; + default: + break; + } + return cf->next ? + cf->next->cft->query(cf->next, data, query, pres1, pres2) : + CURLE_UNKNOWN_OPTION; } -CURLcode Curl_http2_setup(struct connectdata *conn) -{ - CURLcode result; - struct http_conn *httpc = &conn->proto.httpc; - struct HTTP *stream = conn->data->req.protop; +struct Curl_cftype Curl_cft_nghttp2 = { + "HTTP/2", + CF_TYPE_MULTIPLEX, + CURL_LOG_LVL_NONE, + cf_h2_destroy, + cf_h2_connect, + cf_h2_close, + cf_h2_shutdown, + Curl_cf_def_get_host, + cf_h2_adjust_pollset, + cf_h2_data_pending, + cf_h2_send, + cf_h2_recv, + cf_h2_cntrl, + cf_h2_is_alive, + cf_h2_keep_alive, + cf_h2_query, +}; - stream->stream_id = -1; +static CURLcode http2_cfilter_add(struct Curl_cfilter **pcf, + struct Curl_easy *data, + struct connectdata *conn, + int sockindex, + bool via_h1_upgrade) +{ + struct Curl_cfilter *cf = NULL; + struct cf_h2_ctx *ctx; + CURLcode result = CURLE_OUT_OF_MEMORY; - if(!stream->header_recvbuf) - stream->header_recvbuf = Curl_add_buffer_init(); + DEBUGASSERT(data->conn); + ctx = calloc(1, sizeof(*ctx)); + if(!ctx) + goto out; + cf_h2_ctx_init(ctx, via_h1_upgrade); - if((conn->handler == &Curl_handler_http2_ssl) || - (conn->handler == &Curl_handler_http2)) - return CURLE_OK; /* already done */ + result = Curl_cf_create(&cf, &Curl_cft_nghttp2, ctx); + if(result) + goto out; - if(conn->handler->flags & PROTOPT_SSL) - conn->handler = &Curl_handler_http2_ssl; - else - conn->handler = &Curl_handler_http2; + ctx = NULL; + Curl_conn_cf_add(data, conn, sockindex, cf); - result = Curl_http2_init(conn); +out: if(result) - return result; - - infof(conn->data, "Using HTTP2, server supports multi-use\n"); - stream->upload_left = 0; - stream->upload_mem = NULL; - stream->upload_len = 0; + cf_h2_ctx_free(ctx); + *pcf = result ? NULL : cf; + return result; +} - httpc->inbuflen = 0; - httpc->nread_inbuf = 0; +static CURLcode http2_cfilter_insert_after(struct Curl_cfilter *cf, + struct Curl_easy *data, + bool via_h1_upgrade) +{ + struct Curl_cfilter *cf_h2 = NULL; + struct cf_h2_ctx *ctx; + CURLcode result = CURLE_OUT_OF_MEMORY; - httpc->pause_stream_id = 0; - httpc->drain_total = 0; + (void)data; + ctx = calloc(1, sizeof(*ctx)); + if(!ctx) + goto out; + cf_h2_ctx_init(ctx, via_h1_upgrade); - conn->bits.multiplex = TRUE; /* at least potentially multiplexed */ - conn->httpversion = 20; - conn->bundle->multiuse = BUNDLE_MULTIPLEX; + result = Curl_cf_create(&cf_h2, &Curl_cft_nghttp2, ctx); + if(result) + goto out; - infof(conn->data, "Connection state changed (HTTP/2 confirmed)\n"); - Curl_multi_connchanged(conn->data->multi); + ctx = NULL; + Curl_conn_cf_insert_after(cf, cf_h2); - return CURLE_OK; +out: + if(result) + cf_h2_ctx_free(ctx); + return result; } -CURLcode Curl_http2_switched(struct connectdata *conn, - const char *mem, size_t nread) +static bool Curl_cf_is_http2(struct Curl_cfilter *cf, + const struct Curl_easy *data) { - CURLcode result; - struct http_conn *httpc = &conn->proto.httpc; - int rv; - ssize_t nproc; - struct Curl_easy *data = conn->data; - struct HTTP *stream = conn->data->req.protop; - - result = Curl_http2_setup(conn); - if(result) - return result; - - httpc->recv_underlying = (recving)conn->recv[FIRSTSOCKET]; - httpc->send_underlying = (sending)conn->send[FIRSTSOCKET]; - conn->recv[FIRSTSOCKET] = http2_recv; - conn->send[FIRSTSOCKET] = http2_send; + (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; +} - if(conn->data->req.upgr101 == UPGR101_RECEIVED) { - /* stream 1 is opened implicitly on upgrade */ - stream->stream_id = 1; - /* queue SETTINGS frame (again) */ - rv = nghttp2_session_upgrade(httpc->h2, httpc->binsettings, - httpc->binlen, NULL); - if(rv != 0) { - failf(data, "nghttp2_session_upgrade() failed: %s(%d)", - nghttp2_strerror(rv), rv); - return CURLE_HTTP2; - } +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; +} - nghttp2_session_set_stream_user_data(httpc->h2, - stream->stream_id, - conn->data); - } - else { - populate_settings(conn, httpc); - - /* stream ID is unknown at this point */ - stream->stream_id = -1; - rv = nghttp2_submit_settings(httpc->h2, NGHTTP2_FLAG_NONE, - httpc->local_settings, - httpc->local_settings_num); - if(rv != 0) { - failf(data, "nghttp2_submit_settings() failed: %s(%d)", - nghttp2_strerror(rv), rv); - return CURLE_HTTP2; +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 do not support HTTP/2 proxies yet. Also it is debatable + whether or not this setting should apply to HTTP/2 proxies. */ + infof(data, "Ignoring HTTP/2 prior knowledge due to proxy"); + return FALSE; } - } - -#ifdef NGHTTP2_HAS_SET_LOCAL_WINDOW_SIZE - rv = nghttp2_session_set_local_window_size(httpc->h2, NGHTTP2_FLAG_NONE, 0, - HTTP2_HUGE_WINDOW_SIZE); - if(rv != 0) { - failf(data, "nghttp2_session_set_local_window_size() failed: %s(%d)", - nghttp2_strerror(rv), rv); - return CURLE_HTTP2; - } #endif - - /* we are going to copy mem to httpc->inbuf. This is required since - mem is part of buffer pointed by stream->mem, and callbacks - called by nghttp2_session_mem_recv() will write stream specific - data into stream->mem, overwriting data already there. */ - if(H2_BUFSIZE < nread) { - failf(data, "connection buffer size is too small to store data following " - "HTTP Upgrade response header: buflen=%zu, datalen=%zu", - H2_BUFSIZE, nread); - return CURLE_HTTP2; - } - - infof(conn->data, "Copying HTTP/2 data in stream buffer to connection buffer" - " after upgrade: len=%zu\n", - nread); - - if(nread) - memcpy(httpc->inbuf, mem, nread); - httpc->inbuflen = nread; - - nproc = nghttp2_session_mem_recv(httpc->h2, (const uint8_t *)httpc->inbuf, - httpc->inbuflen); - - if(nghttp2_is_fatal((int)nproc)) { - failf(data, "nghttp2_session_mem_recv() failed: %s(%d)", - nghttp2_strerror((int)nproc), (int)nproc); - return CURLE_HTTP2; + return TRUE; } + return FALSE; +} - DEBUGF(infof(data, "nghttp2_session_mem_recv() returns %zd\n", nproc)); +CURLcode Curl_http2_switch(struct Curl_easy *data, + struct connectdata *conn, int sockindex) +{ + struct Curl_cfilter *cf; + CURLcode result; - if((ssize_t)nread == nproc) { - httpc->inbuflen = 0; - httpc->nread_inbuf = 0; - } - else { - httpc->nread_inbuf += nproc; - } + DEBUGASSERT(!Curl_conn_is_http2(data, conn, sockindex)); - /* Try to send some frames since we may read SETTINGS already. */ - rv = h2_session_send(data, httpc->h2); + result = http2_cfilter_add(&cf, data, conn, sockindex, FALSE); + if(result) + return result; + CURL_TRC_CF(data, cf, "switching connection to HTTP/2"); - if(rv != 0) { - failf(data, "nghttp2_session_send() failed: %s(%d)", - nghttp2_strerror(rv), rv); - return CURLE_HTTP2; - } + conn->httpversion = 20; /* we know we are on HTTP/2 now */ + conn->bits.multiplex = TRUE; /* at least potentially multiplexed */ + Curl_multi_connchanged(data->multi); - if(should_close_session(httpc)) { - DEBUGF(infof(data, - "nghttp2_session_send(): nothing to do in this session\n")); - return CURLE_HTTP2; + if(cf->next) { + bool done; + return Curl_conn_cf_connect(cf, data, FALSE, &done); } - return CURLE_OK; } -void Curl_http2_add_child(struct Curl_easy *parent, struct Curl_easy *child, - bool exclusive) +CURLcode Curl_http2_switch_at(struct Curl_cfilter *cf, struct Curl_easy *data) { - struct Curl_http2_dep **tail; - struct Curl_http2_dep *dep = calloc(1, sizeof(struct Curl_http2_dep)); - dep->data = child; + struct Curl_cfilter *cf_h2; + CURLcode result; - if(parent->set.stream_dependents && exclusive) { - struct Curl_http2_dep *node = parent->set.stream_dependents; - while(node) { - node->data->set.stream_depends_on = child; - node = node->next; - } + DEBUGASSERT(!Curl_cf_is_http2(cf, data)); - tail = &child->set.stream_dependents; - while(*tail) - tail = &(*tail)->next; + result = http2_cfilter_insert_after(cf, data, FALSE); + if(result) + return result; - DEBUGASSERT(!*tail); - *tail = parent->set.stream_dependents; - parent->set.stream_dependents = 0; - } + cf_h2 = cf->next; + cf->conn->httpversion = 20; /* we know we are on HTTP/2 now */ + cf->conn->bits.multiplex = TRUE; /* at least potentially multiplexed */ + Curl_multi_connchanged(data->multi); - tail = &parent->set.stream_dependents; - while(*tail) { - (*tail)->data->set.stream_depends_e = FALSE; - tail = &(*tail)->next; + if(cf_h2->next) { + bool done; + return Curl_conn_cf_connect(cf_h2, data, FALSE, &done); } - - DEBUGASSERT(!*tail); - *tail = dep; - - child->set.stream_depends_on = parent; - child->set.stream_depends_e = exclusive; + return CURLE_OK; } -void Curl_http2_remove_child(struct Curl_easy *parent, struct Curl_easy *child) +CURLcode Curl_http2_upgrade(struct Curl_easy *data, + struct connectdata *conn, int sockindex, + const char *mem, size_t nread) { - struct Curl_http2_dep *last = 0; - struct Curl_http2_dep *data = parent->set.stream_dependents; - DEBUGASSERT(child->set.stream_depends_on == parent); + struct Curl_cfilter *cf; + struct cf_h2_ctx *ctx; + CURLcode result; - while(data && data->data != child) { - last = data; - data = data->next; - } + DEBUGASSERT(!Curl_conn_is_http2(data, conn, sockindex)); + DEBUGASSERT(data->req.upgr101 == UPGR101_RECEIVED); - DEBUGASSERT(data); + result = http2_cfilter_add(&cf, data, conn, sockindex, TRUE); + if(result) + return result; + CURL_TRC_CF(data, cf, "upgrading connection to HTTP/2"); - if(data) { - if(last) { - last->next = data->next; + DEBUGASSERT(cf->cft == &Curl_cft_nghttp2); + ctx = cf->ctx; + + 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; } - else { - parent->set.stream_dependents = data->next; + 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; } - free(data); + infof(data, "Copied HTTP/2 data in stream buffer to connection buffer" + " after upgrade: len=%zu", nread); } - child->set.stream_depends_on = 0; - child->set.stream_depends_e = FALSE; + conn->httpversion = 20; /* we know we are on HTTP/2 now */ + conn->bits.multiplex = TRUE; /* at least potentially multiplexed */ + Curl_multi_connchanged(data->multi); + + if(cf->next) { + bool done; + return Curl_conn_cf_connect(cf, data, FALSE, &done); + } + return CURLE_OK; } -void Curl_http2_cleanup_dependencies(struct Curl_easy *data) +/* 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) { - while(data->set.stream_dependents) { - struct Curl_easy *tmp = data->set.stream_dependents->data; - Curl_http2_remove_child(data, tmp); - if(data->set.stream_depends_on) - Curl_http2_add_child(data->set.stream_depends_on, tmp, FALSE); + if(Curl_conn_is_http2(data, data->conn, FIRSTSOCKET)) { + int err = Curl_conn_get_stream_error(data, data->conn, FIRSTSOCKET); + return (err == NGHTTP2_HTTP_1_1_REQUIRED); } - - if(data->set.stream_depends_on) - Curl_http2_remove_child(data->set.stream_depends_on, data); + return FALSE; } #else /* !USE_NGHTTP2 */ /* Satisfy external references even if http2 is not compiled in. */ - -#define CURL_DISABLE_TYPECHECK #include char *curl_pushheader_bynum(struct curl_pushheaders *h, size_t num) diff --git a/contrib/curl/lib/http2.h b/contrib/curl/lib/http2.h index f405b3aebd..80e183480a 100644 --- a/contrib/curl/lib/http2.h +++ b/contrib/curl/lib/http2.h @@ -7,11 +7,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2016, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -20,6 +20,8 @@ * 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" @@ -29,51 +31,47 @@ /* value for MAX_CONCURRENT_STREAMS we use until we get an updated setting from the peer */ -#define DEFAULT_MAX_CONCURRENT_STREAMS 13 +#define DEFAULT_MAX_CONCURRENT_STREAMS 100 /* - * Store nghttp2 version info in this buffer, Prefix with a space. Return - * total length written. + * Store nghttp2 version info in this buffer. */ -int Curl_http2_ver(char *p, size_t len); +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); -const char *Curl_http2_strerror(uint32_t err); +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; -CURLcode Curl_http2_init(struct connectdata *conn); -void Curl_http2_init_state(struct UrlState *state); -void Curl_http2_init_userset(struct UserDefined *set); -CURLcode Curl_http2_send_request(struct connectdata *conn); -CURLcode Curl_http2_request_upgrade(Curl_send_buffer *req, - struct connectdata *conn); -CURLcode Curl_http2_setup(struct connectdata *conn); -CURLcode Curl_http2_switched(struct connectdata *conn, - const char *data, size_t nread); -/* called from Curl_http_setup_conn */ -void Curl_http2_setup_conn(struct connectdata *conn); -void Curl_http2_setup_req(struct Curl_easy *data); -void Curl_http2_done(struct connectdata *conn, bool premature); -CURLcode Curl_http2_done_sending(struct connectdata *conn); -void Curl_http2_add_child(struct Curl_easy *parent, struct Curl_easy *child, - bool exclusive); -void Curl_http2_remove_child(struct Curl_easy *parent, - struct Curl_easy *child); -void Curl_http2_cleanup_dependencies(struct Curl_easy *data); #else /* USE_NGHTTP2 */ -#define Curl_http2_init(x) CURLE_UNSUPPORTED_PROTOCOL -#define Curl_http2_send_request(x) CURLE_UNSUPPORTED_PROTOCOL + +#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_setup(x) CURLE_UNSUPPORTED_PROTOCOL -#define Curl_http2_switched(x,y,z) CURLE_UNSUPPORTED_PROTOCOL -#define Curl_http2_setup_conn(x) -#define Curl_http2_setup_req(x) -#define Curl_http2_init_state(x) -#define Curl_http2_init_userset(x) -#define Curl_http2_done(x,y) -#define Curl_http2_done_sending(x) -#define Curl_http2_add_child(x, y, z) -#define Curl_http2_remove_child(x, y) -#define Curl_http2_cleanup_dependencies(x) +#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/contrib/curl/lib/http_aws_sigv4.c b/contrib/curl/lib/http_aws_sigv4.c new file mode 100644 index 0000000000..bba3067955 --- /dev/null +++ b/contrib/curl/lib/http_aws_sigv4.c @@ -0,0 +1,880 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.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 + +/* 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 * CURL_SHA256_DIGEST_LENGTH + 1) + +static void sha256_to_hex(char *dst, unsigned char *sha) +{ + Curl_hexencode(sha, CURL_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) + +/* alphabetically compare two headers by their name, expecting + headers to use ':' at this point */ +static int compare_header_names(const char *a, const char *b) +{ + const char *colon_a; + const char *colon_b; + size_t len_a; + size_t len_b; + size_t min_len; + int cmp; + + colon_a = strchr(a, ':'); + colon_b = strchr(b, ':'); + + DEBUGASSERT(colon_a); + DEBUGASSERT(colon_b); + + len_a = colon_a ? (size_t)(colon_a - a) : strlen(a); + len_b = colon_b ? (size_t)(colon_b - b) : strlen(b); + + min_len = (len_a < len_b) ? len_a : len_b; + + cmp = strncmp(a, b, min_len); + + /* return the shorter of the two if one is shorter */ + if(!cmp) + return (int)(len_a - len_b); + + return cmp; +} + +/* 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"))) { + 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 = aprintf("%s: %s\r\n", date_hdr_key, timestamp); + } + else { + char *value; + char *endp; + value = strchr(*date_header, ':'); + if(!value) { + *date_header = NULL; + goto fail; + } + ++value; + while(ISBLANK(*value)) + ++value; + endp = value; + while(*endp && ISALNUM(*endp)) + ++endp; + /* 16 bytes => "19700101T000000Z" */ + if((endp - value) == TIMESTAMP_SIZE - 1) { + memcpy(timestamp, value, TIMESTAMP_SIZE - 1); + timestamp[TIMESTAMP_SIZE - 1] = 0; + } + else + /* bad timestamp length */ + timestamp[0] = 0; + *date_header = NULL; + } + + /* alpha-sort by header name in a case sensitive manner */ + do { + again = 0; + for(l = head; l; l = l->next) { + struct curl_slist *next = l->next; + + if(next && compare_header_names(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 + +/** + * found_equals have a double meaning, + * detect if an equal have been found when called from canon_query, + * and mark that this function is called to compute the path, + * if found_equals is NULL. + */ +static CURLcode canon_string(const char *q, size_t len, + struct dynbuf *dq, bool *found_equals) +{ + CURLcode result = CURLE_OK; + + for(; 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 '%': + /* 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: { + const char hex[] = "0123456789ABCDEF"; + char out[3]={'%'}; + + if(!found_equals) { + /* if found_equals is NULL assuming, been in path */ + if(*q == '/') { + /* allowed as if */ + result = Curl_dyn_addn(dq, q, 1); + break; + } + } + else { + /* allowed as-is */ + if(*q == '=') { + result = Curl_dyn_addn(dq, q, 1); + *found_equals = true; + break; + } + } + /* URL encode */ + out[1] = hex[((unsigned char)*q) >> 4]; + out[2] = hex[*q & 0xf]; + result = Curl_dyn_addn(dq, out, 3); + break; + } + } + } + } + return result; +} + + +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++) { + const char *q = ap->p; + bool found_equals = false; + if(!ap->len) + continue; + result = canon_string(q, ap->len, dq, &found_equals); + 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; + struct dynbuf canonical_path; + 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[CURL_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[CURL_SHA256_DIGEST_LENGTH] = {0}; + unsigned char sign1[CURL_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); + Curl_dyn_init(&canonical_path, 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 cannot 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; + } + memcpy(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; + } + memcpy(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 + clock = time(NULL); + } +#else + clock = time(NULL); +#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 = canon_string(data->state.up.path, strlen(data->state.up.path), + &canonical_path, NULL); + if(result) + goto fail; + result = CURLE_OUT_OF_MEMORY; + + canonical_request = + aprintf("%s\n" /* HTTPRequestMethod */ + "%s\n" /* CanonicalURI */ + "%s\n" /* CanonicalQueryString */ + "%s\n" /* CanonicalHeaders */ + "%s\n" /* SignedHeaders */ + "%.*s", /* HashedRequestPayload in hex */ + method, + Curl_dyn_ptr(&canonical_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 = aprintf("%s4_request", provider0); + if(!request_type) + goto fail; + + credential_scope = aprintf("%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 = aprintf("%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 = aprintf("%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 = aprintf("Authorization: %s4-HMAC-SHA256 " + "Credential=%s/%s, " + "SignedHeaders=%s, " + "Signature=%s\r\n" + /* + * date_header is added here, only if it was not + * 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_path); + 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/contrib/curl/lib/curl_ntlm_wb.h b/contrib/curl/lib/http_aws_sigv4.h similarity index 63% rename from contrib/curl/lib/curl_ntlm_wb.h rename to contrib/curl/lib/http_aws_sigv4.h index aba3d469c3..57cc5706eb 100644 --- a/contrib/curl/lib/curl_ntlm_wb.h +++ b/contrib/curl/lib/http_aws_sigv4.h @@ -1,5 +1,5 @@ -#ifndef HEADER_CURL_NTLM_WB_H -#define HEADER_CURL_NTLM_WB_H +#ifndef HEADER_CURL_HTTP_AWS_SIGV4_H +#define HEADER_CURL_HTTP_AWS_SIGV4_H /*************************************************************************** * _ _ ____ _ * Project ___| | | | _ \| | @@ -7,7 +7,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2014, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms @@ -20,19 +20,12 @@ * 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 creating ntlm header output by delegating challenge/response - to Samba's winbind daemon helper ntlm_auth */ -CURLcode Curl_output_ntlm_wb(struct connectdata *conn, bool proxy); - -void Curl_ntlm_wb_cleanup(struct connectdata *conn); - -#endif /* !CURL_DISABLE_HTTP && USE_NTLM && NTLM_WB_ENABLED */ +/* this is for creating aws_sigv4 header output */ +CURLcode Curl_output_aws_sigv4(struct Curl_easy *data, bool proxy); -#endif /* HEADER_CURL_NTLM_WB_H */ +#endif /* HEADER_CURL_HTTP_AWS_SIGV4_H */ diff --git a/contrib/curl/lib/http_chunks.c b/contrib/curl/lib/http_chunks.c index 1bdf6974c1..c228eb4f9b 100644 --- a/contrib/curl/lib/http_chunks.c +++ b/contrib/curl/lib/http_chunks.c @@ -5,11 +5,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2016, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -18,6 +18,8 @@ * 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" @@ -25,11 +27,13 @@ #ifndef CURL_DISABLE_HTTP #include "urldata.h" /* it includes http_chunks.h */ +#include "curl_printf.h" +#include "curl_trc.h" #include "sendf.h" /* for the client write stuff */ - +#include "dynbuf.h" #include "content_encoding.h" #include "http.h" -#include "non-ascii.h" /* for Curl_convert_to_network prototype */ +#include "multiif.h" #include "strtoofft.h" #include "warnless.h" @@ -74,167 +78,156 @@ */ -/* Check for an ASCII hex digit. - We avoid the use of isxdigit to accommodate non-ASCII hosts. */ -static bool Curl_isxdigit(char digit) +void Curl_httpchunk_init(struct Curl_easy *data, struct Curl_chunker *ch, + bool ignore_body) { - return ( (digit >= 0x30 && digit <= 0x39) /* 0-9 */ - || (digit >= 0x41 && digit <= 0x46) /* A-F */ - || (digit >= 0x61 && digit <= 0x66) /* a-f */) ? TRUE : FALSE; + (void)data; + ch->hexindex = 0; /* start at 0 */ + ch->state = CHUNK_HEX; /* we get hex first! */ + ch->last_code = CHUNKE_OK; + Curl_dyn_init(&ch->trailer, DYN_H1_TRAILER); + ch->ignore_body = ignore_body; } -void Curl_httpchunk_init(struct connectdata *conn) +void Curl_httpchunk_reset(struct Curl_easy *data, struct Curl_chunker *ch, + bool ignore_body) { - struct Curl_chunker *chunk = &conn->chunk; - chunk->hexindex=0; /* start at 0 */ - chunk->dataleft=0; /* no data left yet! */ - chunk->state = CHUNK_HEX; /* we get hex first! */ + (void)data; + ch->hexindex = 0; /* start at 0 */ + ch->state = CHUNK_HEX; /* we get hex first! */ + ch->last_code = CHUNKE_OK; + Curl_dyn_reset(&ch->trailer); + ch->ignore_body = ignore_body; } -/* - * 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 connectdata *conn, - char *datap, - ssize_t datalen, - ssize_t *wrotep) +void Curl_httpchunk_free(struct Curl_easy *data, struct Curl_chunker *ch) +{ + (void)data; + Curl_dyn_free(&ch->trailer); +} + +bool Curl_httpchunk_is_done(struct Curl_easy *data, struct Curl_chunker *ch) +{ + (void)data; + return ch->state == CHUNK_DONE; +} + +static CURLcode httpchunk_readwrite(struct Curl_easy *data, + struct Curl_chunker *ch, + struct Curl_cwriter *cw_next, + const char *buf, size_t blen, + size_t *pconsumed) { - CURLcode result=CURLE_OK; - struct Curl_easy *data = conn->data; - struct Curl_chunker *ch = &conn->chunk; - struct SingleRequest *k = &data->req; + CURLcode result = CURLE_OK; size_t piece; - curl_off_t length = (curl_off_t)datalen; - size_t *wrote = (size_t *)wrotep; - *wrote = 0; /* nothing's written yet */ + *pconsumed = 0; /* nothing's written yet */ + /* first check terminal states that will not progress anywhere */ + if(ch->state == CHUNK_DONE) + return CURLE_OK; + if(ch->state == CHUNK_FAILED) + return CURLE_RECV_ERROR; /* 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(conn, CLIENTWRITE_BODY, datap, datalen); - if(result) - return CHUNKE_WRITE_ERROR; + chunk read process, to properly calculate the content length */ + if(data->set.http_te_skip && !ch->ignore_body) { + if(cw_next) + result = Curl_cwriter_write(data, cw_next, CLIENTWRITE_BODY, buf, blen); + else + result = Curl_client_write(data, CLIENTWRITE_BODY, (char *)buf, blen); + if(result) { + ch->state = CHUNK_FAILED; + ch->last_code = CHUNKE_PASSTHRU_ERROR; + return result; + } } - while(length) { + while(blen) { switch(ch->state) { case CHUNK_HEX: - if(Curl_isxdigit(*datap)) { - if(ch->hexindex < MAXNUM_SIZE) { - ch->hexbuffer[ch->hexindex] = *datap; - datap++; - length--; - ch->hexindex++; - } - else { - return CHUNKE_TOO_LONG_HEX; /* longer hex than we support */ + if(ISXDIGIT(*buf)) { + if(ch->hexindex >= CHUNK_MAXNUM_LEN) { + failf(data, "chunk hex-length longer than %d", CHUNK_MAXNUM_LEN); + ch->state = CHUNK_FAILED; + ch->last_code = CHUNKE_TOO_LONG_HEX; /* longer than we support */ + return CURLE_RECV_ERROR; } + ch->hexbuffer[ch->hexindex++] = *buf; + buf++; + blen--; + (*pconsumed)++; } else { - char *endptr; - if(0 == ch->hexindex) + if(0 == ch->hexindex) { /* This is illegal data, we received junk where we expected a hexadecimal digit. */ - return CHUNKE_ILLEGAL_HEX; - - /* length and datap are unmodified */ - ch->hexbuffer[ch->hexindex]=0; - - /* convert to host encoding before calling strtoul */ - result = Curl_convert_from_network(conn->data, ch->hexbuffer, - ch->hexindex); - if(result) { - /* Curl_convert_from_network calls failf if unsuccessful */ - /* Treat it as a bad hex character */ - return CHUNKE_ILLEGAL_HEX; + failf(data, "chunk hex-length char not a hex digit: 0x%x", *buf); + ch->state = CHUNK_FAILED; + ch->last_code = CHUNKE_ILLEGAL_HEX; + return CURLE_RECV_ERROR; } - ch->datasize=curlx_strtoofft(ch->hexbuffer, &endptr, 16); - if((ch->datasize == CURL_OFF_T_MAX) && (errno == ERANGE)) - /* overflow is an error */ - return CHUNKE_ILLEGAL_HEX; + /* blen and buf are unmodified */ + ch->hexbuffer[ch->hexindex] = 0; + if(curlx_strtoofft(ch->hexbuffer, NULL, 16, &ch->datasize)) { + failf(data, "chunk hex-length not valid: '%s'", ch->hexbuffer); + ch->state = CHUNK_FAILED; + ch->last_code = CHUNKE_ILLEGAL_HEX; + return CURLE_RECV_ERROR; + } ch->state = CHUNK_LF; /* now wait for the CRLF */ } break; case CHUNK_LF: /* waiting for the LF after a chunk size */ - if(*datap == 0x0a) { - /* we're now expecting data to come, unless size was zero! */ + if(*buf == 0x0a) { + /* we are now expecting data to come, unless size was zero! */ if(0 == ch->datasize) { ch->state = CHUNK_TRAILER; /* now check for trailers */ - conn->trlPos=0; } - else + else { ch->state = CHUNK_DATA; + CURL_TRC_WRITE(data, "http_chunked, chunk start of %" + FMT_OFF_T " bytes", ch->datasize); + } } - datap++; - length--; + buf++; + blen--; + (*pconsumed)++; break; case CHUNK_DATA: - /* We expect 'datasize' of data. We have 'length' right now, it can be + /* We expect 'datasize' of data. We have 'blen' right now, it can be more or less than 'datasize'. Get the smallest piece. */ - piece = curlx_sotouz((ch->datasize >= length)?length:ch->datasize); + piece = blen; + if(ch->datasize < (curl_off_t)blen) + piece = curlx_sotouz(ch->datasize); /* Write the data portion available */ -#ifdef HAVE_LIBZ - switch(conn->data->set.http_ce_skip? - IDENTITY : data->req.auto_decoding) { - case IDENTITY: -#endif - if(!k->ignorebody) { - if(!data->set.http_te_skip) - result = Curl_client_write(conn, CLIENTWRITE_BODY, datap, - piece); - else - result = CURLE_OK; + if(!data->set.http_te_skip && !ch->ignore_body) { + if(cw_next) + result = Curl_cwriter_write(data, cw_next, CLIENTWRITE_BODY, + buf, piece); + else + result = Curl_client_write(data, CLIENTWRITE_BODY, + (char *)buf, piece); + if(result) { + ch->state = CHUNK_FAILED; + ch->last_code = CHUNKE_PASSTHRU_ERROR; + return result; } -#ifdef HAVE_LIBZ - break; - - case DEFLATE: - /* update data->req.keep.str to point to the chunk data. */ - data->req.str = datap; - result = Curl_unencode_deflate_write(conn, &data->req, - (ssize_t)piece); - break; - - case GZIP: - /* update data->req.keep.str to point to the chunk data. */ - data->req.str = datap; - result = Curl_unencode_gzip_write(conn, &data->req, - (ssize_t)piece); - break; - - default: - failf(conn->data, - "Unrecognized content encoding type. " - "libcurl understands `identity', `deflate' and `gzip' " - "content encodings."); - return CHUNKE_BAD_ENCODING; } -#endif - - if(result) - return CHUNKE_WRITE_ERROR; - - *wrote += piece; + *pconsumed += piece; ch->datasize -= piece; /* decrease amount left to expect */ - datap += piece; /* move read pointer forward */ - length -= piece; /* decrease space left in this round */ + buf += piece; /* move read pointer forward */ + blen -= piece; /* decrease space left in this round */ + CURL_TRC_WRITE(data, "http_chunked, write %zu body bytes, %" + FMT_OFF_T " bytes in chunk remain", + piece, ch->datasize); if(0 == ch->datasize) /* end of data this round, we now expect a trailing CRLF */ @@ -242,123 +235,140 @@ CHUNKcode Curl_httpchunk_read(struct connectdata *conn, break; case CHUNK_POSTLF: - if(*datap == 0x0a) { + if(*buf == 0x0a) { /* The last one before we go back to hex state and start all over. */ - Curl_httpchunk_init(conn); /* sets state back to CHUNK_HEX */ + Curl_httpchunk_reset(data, ch, ch->ignore_body); + } + else if(*buf != 0x0d) { + ch->state = CHUNK_FAILED; + ch->last_code = CHUNKE_BAD_CHUNK; + return CURLE_RECV_ERROR; } - else if(*datap != 0x0d) - return CHUNKE_BAD_CHUNK; - datap++; - length--; + buf++; + blen--; + (*pconsumed)++; break; case CHUNK_TRAILER: - if((*datap == 0x0d) || (*datap == 0x0a)) { + if((*buf == 0x0d) || (*buf == 0x0a)) { + char *tr = Curl_dyn_ptr(&ch->trailer); /* this is the end of a trailer, but if the trailer was zero bytes there was no trailer and we move on */ - if(conn->trlPos) { - /* we allocate trailer with 3 bytes extra room to fit this */ - conn->trailer[conn->trlPos++]=0x0d; - conn->trailer[conn->trlPos++]=0x0a; - conn->trailer[conn->trlPos]=0; - - /* Convert to host encoding before calling Curl_client_write */ - result = Curl_convert_from_network(conn->data, conn->trailer, - conn->trlPos); - if(result) - /* Curl_convert_from_network calls failf if unsuccessful */ - /* Treat it as a bad chunk */ - return CHUNKE_BAD_CHUNK; - + if(tr) { + size_t trlen; + result = Curl_dyn_addn(&ch->trailer, (char *)STRCONST("\x0d\x0a")); + if(result) { + ch->state = CHUNK_FAILED; + ch->last_code = CHUNKE_OUT_OF_MEMORY; + return result; + } + tr = Curl_dyn_ptr(&ch->trailer); + trlen = Curl_dyn_len(&ch->trailer); if(!data->set.http_te_skip) { - result = Curl_client_write(conn, CLIENTWRITE_HEADER, - conn->trailer, conn->trlPos); - if(result) - return CHUNKE_WRITE_ERROR; + if(cw_next) + result = Curl_cwriter_write(data, cw_next, + CLIENTWRITE_HEADER| + CLIENTWRITE_TRAILER, + tr, trlen); + else + result = Curl_client_write(data, + CLIENTWRITE_HEADER| + CLIENTWRITE_TRAILER, + tr, trlen); + if(result) { + ch->state = CHUNK_FAILED; + ch->last_code = CHUNKE_PASSTHRU_ERROR; + return result; + } } - conn->trlPos=0; + Curl_dyn_reset(&ch->trailer); ch->state = CHUNK_TRAILER_CR; - if(*datap == 0x0a) + if(*buf == 0x0a) /* already on the LF */ break; } else { - /* no trailer, we're on the final CRLF pair */ + /* no trailer, we are on the final CRLF pair */ ch->state = CHUNK_TRAILER_POSTCR; - break; /* don't advance the pointer */ + break; /* do not advance the pointer */ } } else { - /* conn->trailer is assumed to be freed in url.c on a - connection basis */ - if(conn->trlPos >= conn->trlMax) { - /* we always allocate three extra bytes, just because when the full - header has been received we append CRLF\0 */ - char *ptr; - if(conn->trlMax) { - conn->trlMax *= 2; - ptr = realloc(conn->trailer, conn->trlMax + 3); - } - else { - conn->trlMax=128; - ptr = malloc(conn->trlMax + 3); - } - if(!ptr) - return CHUNKE_OUT_OF_MEMORY; - conn->trailer = ptr; + result = Curl_dyn_addn(&ch->trailer, buf, 1); + if(result) { + ch->state = CHUNK_FAILED; + ch->last_code = CHUNKE_OUT_OF_MEMORY; + return result; } - conn->trailer[conn->trlPos++]=*datap; } - datap++; - length--; + buf++; + blen--; + (*pconsumed)++; break; case CHUNK_TRAILER_CR: - if(*datap == 0x0a) { + if(*buf == 0x0a) { ch->state = CHUNK_TRAILER_POSTCR; - datap++; - length--; + buf++; + blen--; + (*pconsumed)++; + } + else { + ch->state = CHUNK_FAILED; + ch->last_code = CHUNKE_BAD_CHUNK; + return CURLE_RECV_ERROR; } - 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((*datap != 0x0d) && (*datap != 0x0a)) { + if((*buf != 0x0d) && (*buf != 0x0a)) { /* not a CR then it must be another header in the trailer */ ch->state = CHUNK_TRAILER; break; } - if(*datap == 0x0d) { + if(*buf == 0x0d) { /* skip if CR */ - datap++; - length--; + buf++; + blen--; + (*pconsumed)++; } /* now wait for the final LF */ ch->state = CHUNK_STOP; break; case CHUNK_STOP: - if(*datap == 0x0a) { - length--; - + if(*buf == 0x0a) { + blen--; + (*pconsumed)++; /* Record the length of any data left in the end of the buffer - even if there's no more chunks to read */ - ch->dataleft = curlx_sotouz(length); - - return CHUNKE_STOP; /* return stop */ + even if there is no more chunks to read */ + ch->datasize = blen; + ch->state = CHUNK_DONE; + CURL_TRC_WRITE(data, "http_chunk, response complete"); + return CURLE_OK; + } + else { + ch->state = CHUNK_FAILED; + ch->last_code = CHUNKE_BAD_CHUNK; + CURL_TRC_WRITE(data, "http_chunk error, expected 0x0a, seeing 0x%ux", + (unsigned int)*buf); + return CURLE_RECV_ERROR; } - else - return CHUNKE_BAD_CHUNK; + case CHUNK_DONE: + return CURLE_OK; + + case CHUNK_FAILED: + return CURLE_RECV_ERROR; } + } - return CHUNKE_OK; + return CURLE_OK; } -const char *Curl_chunked_strerror(CHUNKcode code) +static const char *Curl_chunked_strerror(CHUNKcode code) { switch(code) { default: @@ -369,8 +379,8 @@ const char *Curl_chunked_strerror(CHUNKcode code) return "Illegal or missing hexadecimal sequence"; case CHUNKE_BAD_CHUNK: return "Malformed encoding found"; - case CHUNKE_WRITE_ERROR: - return "Write error"; + case CHUNKE_PASSTHRU_ERROR: + return "Error writing data to client"; case CHUNKE_BAD_ENCODING: return "Bad content-encoding found"; case CHUNKE_OUT_OF_MEMORY: @@ -378,4 +388,295 @@ const char *Curl_chunked_strerror(CHUNKcode code) } } +CURLcode Curl_httpchunk_read(struct Curl_easy *data, + struct Curl_chunker *ch, + char *buf, size_t blen, + size_t *pconsumed) +{ + return httpchunk_readwrite(data, ch, NULL, buf, blen, pconsumed); +} + +struct chunked_writer { + struct Curl_cwriter super; + struct Curl_chunker ch; +}; + +static CURLcode cw_chunked_init(struct Curl_easy *data, + struct Curl_cwriter *writer) +{ + struct chunked_writer *ctx = writer->ctx; + + data->req.chunk = TRUE; /* chunks coming our way. */ + Curl_httpchunk_init(data, &ctx->ch, FALSE); + return CURLE_OK; +} + +static void cw_chunked_close(struct Curl_easy *data, + struct Curl_cwriter *writer) +{ + struct chunked_writer *ctx = writer->ctx; + Curl_httpchunk_free(data, &ctx->ch); +} + +static CURLcode cw_chunked_write(struct Curl_easy *data, + struct Curl_cwriter *writer, int type, + const char *buf, size_t blen) +{ + struct chunked_writer *ctx = writer->ctx; + CURLcode result; + size_t consumed; + + if(!(type & CLIENTWRITE_BODY)) + return Curl_cwriter_write(data, writer->next, type, buf, blen); + + consumed = 0; + result = httpchunk_readwrite(data, &ctx->ch, writer->next, buf, blen, + &consumed); + + if(result) { + if(CHUNKE_PASSTHRU_ERROR == ctx->ch.last_code) { + failf(data, "Failed reading the chunked-encoded stream"); + } + else { + failf(data, "%s in chunked-encoding", + Curl_chunked_strerror(ctx->ch.last_code)); + } + return result; + } + + blen -= consumed; + if(CHUNK_DONE == ctx->ch.state) { + /* chunks read successfully, download is complete */ + data->req.download_done = TRUE; + if(blen) { + infof(data, "Leftovers after chunking: %zu bytes", blen); + } + } + else if((type & CLIENTWRITE_EOS) && !data->req.no_body) { + failf(data, "transfer closed with outstanding read data remaining"); + return CURLE_PARTIAL_FILE; + } + + return CURLE_OK; +} + +/* HTTP chunked Transfer-Encoding decoder */ +const struct Curl_cwtype Curl_httpchunk_unencoder = { + "chunked", + NULL, + cw_chunked_init, + cw_chunked_write, + cw_chunked_close, + sizeof(struct chunked_writer) +}; + +/* max length of an HTTP chunk that we want to generate */ +#define CURL_CHUNKED_MINLEN (1024) +#define CURL_CHUNKED_MAXLEN (64 * 1024) + +struct chunked_reader { + struct Curl_creader super; + struct bufq chunkbuf; + BIT(read_eos); /* we read an EOS from the next reader */ + BIT(eos); /* we have returned an EOS */ +}; + +static CURLcode cr_chunked_init(struct Curl_easy *data, + struct Curl_creader *reader) +{ + struct chunked_reader *ctx = reader->ctx; + (void)data; + Curl_bufq_init2(&ctx->chunkbuf, CURL_CHUNKED_MAXLEN, 2, BUFQ_OPT_SOFT_LIMIT); + return CURLE_OK; +} + +static void cr_chunked_close(struct Curl_easy *data, + struct Curl_creader *reader) +{ + struct chunked_reader *ctx = reader->ctx; + (void)data; + Curl_bufq_free(&ctx->chunkbuf); +} + +static CURLcode add_last_chunk(struct Curl_easy *data, + struct Curl_creader *reader) +{ + struct chunked_reader *ctx = reader->ctx; + struct curl_slist *trailers = NULL, *tr; + CURLcode result; + size_t n; + int rc; + + if(!data->set.trailer_callback) { + CURL_TRC_READ(data, "http_chunk, added last, empty chunk"); + return Curl_bufq_cwrite(&ctx->chunkbuf, STRCONST("0\r\n\r\n"), &n); + } + + result = Curl_bufq_cwrite(&ctx->chunkbuf, STRCONST("0\r\n"), &n); + if(result) + goto out; + + Curl_set_in_callback(data, true); + rc = data->set.trailer_callback(&trailers, data->set.trailer_data); + Curl_set_in_callback(data, false); + + if(rc != CURL_TRAILERFUNC_OK) { + failf(data, "operation aborted by trailing headers callback"); + result = CURLE_ABORTED_BY_CALLBACK; + goto out; + } + + for(tr = trailers; tr; tr = tr->next) { + /* only add correctly formatted trailers */ + char *ptr = strchr(tr->data, ':'); + if(!ptr || *(ptr + 1) != ' ') { + infof(data, "Malformatted trailing header, skipping trailer"); + continue; + } + + result = Curl_bufq_cwrite(&ctx->chunkbuf, tr->data, + strlen(tr->data), &n); + if(!result) + result = Curl_bufq_cwrite(&ctx->chunkbuf, STRCONST("\r\n"), &n); + if(result) + goto out; + } + + result = Curl_bufq_cwrite(&ctx->chunkbuf, STRCONST("\r\n"), &n); + +out: + curl_slist_free_all(trailers); + CURL_TRC_READ(data, "http_chunk, added last chunk with trailers " + "from client -> %d", result); + return result; +} + +static CURLcode add_chunk(struct Curl_easy *data, + struct Curl_creader *reader, + char *buf, size_t blen) +{ + struct chunked_reader *ctx = reader->ctx; + CURLcode result; + char tmp[CURL_CHUNKED_MINLEN]; + size_t nread; + bool eos; + + DEBUGASSERT(!ctx->read_eos); + blen = CURLMIN(blen, CURL_CHUNKED_MAXLEN); /* respect our buffer pref */ + if(blen < sizeof(tmp)) { + /* small read, make a chunk of decent size */ + buf = tmp; + blen = sizeof(tmp); + } + else { + /* larger read, make a chunk that will fit when read back */ + blen -= (8 + 2 + 2); /* deduct max overhead, 8 hex + 2*crlf */ + } + + result = Curl_creader_read(data, reader->next, buf, blen, &nread, &eos); + if(result) + return result; + if(eos) + ctx->read_eos = TRUE; + + if(nread) { + /* actually got bytes, wrap them into the chunkbuf */ + char hd[11] = ""; + int hdlen; + size_t n; + + hdlen = msnprintf(hd, sizeof(hd), "%zx\r\n", nread); + if(hdlen <= 0) + return CURLE_READ_ERROR; + /* On a soft-limited bufq, we do not need to check that all was written */ + result = Curl_bufq_cwrite(&ctx->chunkbuf, hd, hdlen, &n); + if(!result) + result = Curl_bufq_cwrite(&ctx->chunkbuf, buf, nread, &n); + if(!result) + result = Curl_bufq_cwrite(&ctx->chunkbuf, "\r\n", 2, &n); + CURL_TRC_READ(data, "http_chunk, made chunk of %zu bytes -> %d", + nread, result); + if(result) + return result; + } + + if(ctx->read_eos) + return add_last_chunk(data, reader); + return CURLE_OK; +} + +static CURLcode cr_chunked_read(struct Curl_easy *data, + struct Curl_creader *reader, + char *buf, size_t blen, + size_t *pnread, bool *peos) +{ + struct chunked_reader *ctx = reader->ctx; + CURLcode result = CURLE_READ_ERROR; + + *pnread = 0; + *peos = ctx->eos; + + if(!ctx->eos) { + if(!ctx->read_eos && Curl_bufq_is_empty(&ctx->chunkbuf)) { + /* Still getting data form the next reader, buffer is empty */ + result = add_chunk(data, reader, buf, blen); + if(result) + return result; + } + + if(!Curl_bufq_is_empty(&ctx->chunkbuf)) { + result = Curl_bufq_cread(&ctx->chunkbuf, buf, blen, pnread); + if(!result && ctx->read_eos && Curl_bufq_is_empty(&ctx->chunkbuf)) { + /* no more data, read all, done. */ + ctx->eos = TRUE; + *peos = TRUE; + } + return result; + } + } + /* We may get here, because we are done or because callbacks paused */ + DEBUGASSERT(ctx->eos || !ctx->read_eos); + return CURLE_OK; +} + +static curl_off_t cr_chunked_total_length(struct Curl_easy *data, + struct Curl_creader *reader) +{ + /* this reader changes length depending on input */ + (void)data; + (void)reader; + return -1; +} + +/* HTTP chunked Transfer-Encoding encoder */ +const struct Curl_crtype Curl_httpchunk_encoder = { + "chunked", + cr_chunked_init, + cr_chunked_read, + cr_chunked_close, + Curl_creader_def_needs_rewind, + cr_chunked_total_length, + Curl_creader_def_resume_from, + Curl_creader_def_rewind, + Curl_creader_def_unpause, + Curl_creader_def_is_paused, + Curl_creader_def_done, + sizeof(struct chunked_reader) +}; + +CURLcode Curl_httpchunk_add_reader(struct Curl_easy *data) +{ + struct Curl_creader *reader = NULL; + CURLcode result; + + result = Curl_creader_create(&reader, data, &Curl_httpchunk_encoder, + CURL_CR_TRANSFER_ENCODE); + if(!result) + result = Curl_creader_add(data, reader); + + if(result && reader) + Curl_creader_free(data, reader); + return result; +} + #endif /* CURL_DISABLE_HTTP */ diff --git a/contrib/curl/lib/http_chunks.h b/contrib/curl/lib/http_chunks.h index 3a8b4ddf37..34951ea0f4 100644 --- a/contrib/curl/lib/http_chunks.h +++ b/contrib/curl/lib/http_chunks.h @@ -7,11 +7,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2014, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -20,16 +20,25 @@ * 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_HTTP + +#include "dynbuf.h" + +struct connectdata; + /* * The longest possible hexadecimal number we support in a chunked transfer. - * Weird enough, RFC2616 doesn't set a maximum size! Since we use strtoul() - * to convert it, we "only" support 2^32 bytes chunk data. + * 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 MAXNUM_SIZE 16 +#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 + /* await and buffer all hexadecimal digits until we get one that is not a hexadecimal digit. When done, we go CHUNK_LF */ CHUNK_HEX, @@ -45,9 +54,9 @@ typedef enum { big deal. */ CHUNK_POSTLF, - /* Used to mark that we're out of the game. NOTE: that there's a 'dataleft' - field in the struct that will tell how many bytes that were not passed to - the client in the end of the last buffer! */ + /* Used to mark that we are out of the game. NOTE: that there is 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 @@ -62,30 +71,75 @@ typedef enum { 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 + CHUNK_TRAILER_POSTCR, + + /* Successfully de-chunked everything */ + CHUNK_DONE, + + /* Failed on seeing a bad or not correctly terminated chunk */ + CHUNK_FAILED } ChunkyState; typedef enum { - CHUNKE_STOP = -1, CHUNKE_OK = 0, CHUNKE_TOO_LONG_HEX = 1, CHUNKE_ILLEGAL_HEX, CHUNKE_BAD_CHUNK, - CHUNKE_WRITE_ERROR, CHUNKE_BAD_ENCODING, CHUNKE_OUT_OF_MEMORY, - CHUNKE_LAST + CHUNKE_PASSTHRU_ERROR /* Curl_httpchunk_read() returns a CURLcode to use */ } CHUNKcode; -const char *Curl_chunked_strerror(CHUNKcode code); - struct Curl_chunker { - char hexbuffer[ MAXNUM_SIZE + 1]; - int hexindex; - ChunkyState state; curl_off_t datasize; - size_t dataleft; /* untouched data amount at the end of the last buffer */ + ChunkyState state; + CHUNKcode last_code; + struct dynbuf trailer; /* for chunked-encoded trailer */ + unsigned char hexindex; + char hexbuffer[CHUNK_MAXNUM_LEN + 1]; /* +1 for null-terminator */ + BIT(ignore_body); /* never write response body data */ }; -#endif /* HEADER_CURL_HTTP_CHUNKS_H */ +/* The following functions are defined in http_chunks.c */ +void Curl_httpchunk_init(struct Curl_easy *data, struct Curl_chunker *ch, + bool ignore_body); +void Curl_httpchunk_free(struct Curl_easy *data, struct Curl_chunker *ch); +void Curl_httpchunk_reset(struct Curl_easy *data, struct Curl_chunker *ch, + bool ignore_body); +/* + * Read BODY bytes in HTTP/1.1 chunked encoding from `buf` and return + * the amount of bytes consumed. The actual response bytes and trailer + * headers are written out to the client. + * On success, this will consume all bytes up to the end of the response, + * e.g. the last chunk, has been processed. + * @param data the transfer involved + * @param ch the chunker instance keeping state across calls + * @param buf the response data + * @param blen amount of bytes in `buf` + * @param pconsumed on successful return, the number of bytes in `buf` + * consumed + * + * This function always uses ASCII hex values to accommodate non-ASCII hosts. + * For example, 0x0d and 0x0a are used instead of '\r' and '\n'. + */ +CURLcode Curl_httpchunk_read(struct Curl_easy *data, struct Curl_chunker *ch, + char *buf, size_t blen, size_t *pconsumed); + +/** + * @return TRUE iff chunked decoded has finished successfully. + */ +bool Curl_httpchunk_is_done(struct Curl_easy *data, struct Curl_chunker *ch); + +extern const struct Curl_cwtype Curl_httpchunk_unencoder; + +extern const struct Curl_crtype Curl_httpchunk_encoder; + +/** + * Add a transfer-encoding "chunked" reader to the transfers reader stack + */ +CURLcode Curl_httpchunk_add_reader(struct Curl_easy *data); + +#endif /* !CURL_DISABLE_HTTP */ + +#endif /* HEADER_CURL_HTTP_CHUNKS_H */ diff --git a/contrib/curl/lib/http_digest.c b/contrib/curl/lib/http_digest.c index e2d865b0af..2db3125a8e 100644 --- a/contrib/curl/lib/http_digest.c +++ b/contrib/curl/lib/http_digest.c @@ -5,11 +5,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2016, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -18,16 +18,19 @@ * 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_CRYPTO_AUTH) +#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" @@ -40,13 +43,11 @@ Proxy-Authenticate: Digest realm="testrealm", nonce="1053604598" */ -CURLcode Curl_input_digest(struct connectdata *conn, +CURLcode Curl_input_digest(struct Curl_easy *data, bool proxy, const char *header) /* rest of the *-authenticate: header */ { - struct Curl_easy *data = conn->data; - /* Point to the correct struct with this */ struct digestdata *digest; @@ -57,23 +58,22 @@ CURLcode Curl_input_digest(struct connectdata *conn, digest = &data->state.digest; } - if(!checkprefix("Digest", header)) + if(!checkprefix("Digest", header) || !ISBLANK(header[6])) return CURLE_BAD_CONTENT_ENCODING; header += strlen("Digest"); - while(*header && ISSPACE(*header)) + while(*header && ISBLANK(*header)) header++; return Curl_auth_decode_digest_http_message(header, digest); } -CURLcode Curl_output_digest(struct connectdata *conn, +CURLcode Curl_output_digest(struct Curl_easy *data, bool proxy, const unsigned char *request, const unsigned char *uripath) { CURLcode result; - struct Curl_easy *data = conn->data; unsigned char *path = NULL; char *tmp = NULL; char *response; @@ -81,7 +81,7 @@ CURLcode Curl_output_digest(struct connectdata *conn, 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 a HTTP proxy */ + server, which is for a plain host or for an HTTP proxy */ char **allocuserpwd; /* Point to the name and password for this */ @@ -93,17 +93,21 @@ CURLcode Curl_output_digest(struct connectdata *conn, struct auth *authp; if(proxy) { +#ifdef CURL_DISABLE_PROXY + return CURLE_NOT_BUILT_IN; +#else digest = &data->state.proxydigest; - allocuserpwd = &conn->allocptr.proxyuserpwd; - userp = conn->http_proxy.user; - passwdp = conn->http_proxy.passwd; + 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 = &conn->allocptr.userpwd; - userp = conn->user; - passwdp = conn->passwd; + allocuserpwd = &data->state.aptr.userpwd; + userp = data->state.aptr.user; + passwdp = data->state.aptr.passwd; authp = &data->state.authhost; } @@ -144,7 +148,8 @@ CURLcode Curl_output_digest(struct connectdata *conn, tmp = strchr((char *)uripath, '?'); if(tmp) { size_t urilen = tmp - (char *)uripath; - path = (unsigned char *) aprintf("%.*s", urilen, uripath); + /* typecast is fine here since the value is always less than 32 bits */ + path = (unsigned char *) aprintf("%.*s", (int)urilen, uripath); } } if(!tmp) @@ -171,7 +176,7 @@ CURLcode Curl_output_digest(struct connectdata *conn, return CURLE_OK; } -void Curl_digest_cleanup(struct Curl_easy *data) +void Curl_http_auth_cleanup_digest(struct Curl_easy *data) { Curl_auth_digest_cleanup(&data->state.digest); Curl_auth_digest_cleanup(&data->state.proxydigest); diff --git a/contrib/curl/lib/http_digest.h b/contrib/curl/lib/http_digest.h index fd225c7c1b..5f797310fd 100644 --- a/contrib/curl/lib/http_digest.h +++ b/contrib/curl/lib/http_digest.h @@ -7,11 +7,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2014, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -20,23 +20,25 @@ * 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 connectdata *conn, +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 connectdata *conn, +CURLcode Curl_output_digest(struct Curl_easy *data, bool proxy, const unsigned char *request, const unsigned char *uripath); -#if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_CRYPTO_AUTH) -void Curl_digest_cleanup(struct Curl_easy *data); -#else -#define Curl_digest_cleanup(x) Curl_nop_stmt -#endif +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/contrib/curl/lib/http_negotiate.c b/contrib/curl/lib/http_negotiate.c index 51375e81d1..5dda475057 100644 --- a/contrib/curl/lib/http_negotiate.c +++ b/contrib/curl/lib/http_negotiate.c @@ -5,11 +5,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2016, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -18,6 +18,8 @@ * 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" @@ -28,17 +30,17 @@ #include "sendf.h" #include "http_negotiate.h" #include "vauth/vauth.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" -CURLcode Curl_input_negotiate(struct connectdata *conn, bool proxy, - const char *header) +CURLcode Curl_input_negotiate(struct Curl_easy *data, struct connectdata *conn, + bool proxy, const char *header) { CURLcode result; - struct Curl_easy *data = conn->data; size_t len; /* Point to the username, password, service and host */ @@ -49,14 +51,20 @@ CURLcode Curl_input_negotiate(struct connectdata *conn, bool proxy, /* 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 = &data->state.proxyneg; + neg_ctx = &conn->proxyneg; + state = conn->proxy_negotiate_state; +#else + return CURLE_NOT_BUILT_IN; +#endif } else { userp = conn->user; @@ -64,7 +72,8 @@ CURLcode Curl_input_negotiate(struct connectdata *conn, bool proxy, service = data->set.str[STRING_SERVICE_NAME] ? data->set.str[STRING_SERVICE_NAME] : "HTTP"; host = conn->host.name; - neg_ctx = &data->state.negotiate; + neg_ctx = &conn->negotiate; + state = conn->http_negotiate_state; } /* Not set means empty */ @@ -76,63 +85,172 @@ CURLcode Curl_input_negotiate(struct connectdata *conn, bool proxy, /* Obtain the input token, if any */ header += strlen("Negotiate"); - while(*header && ISSPACE(*header)) + while(*header && ISBLANK(*header)) header++; len = strlen(header); + neg_ctx->havenegdata = len != 0; if(!len) { - /* Is this the first call in a new negotiation? */ - if(neg_ctx->context) { - /* The server rejected our authentication and hasn't suppled any more + 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 has not supplied any more negotiation mechanisms */ + Curl_http_auth_cleanup_negotiate(conn); return CURLE_LOGIN_DENIED; } } - /* Initilise the security context and decode our challenge */ + /* 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 + /* Check if the connection is using SSL and get the channel binding data */ +#if defined(USE_SSL) && defined(HAVE_GSSAPI) + if(conn->handler->flags & PROTOPT_SSL) { + Curl_dyn_init(&neg_ctx->channel_binding_data, SSL_CB_MAX_SIZE); + result = Curl_ssl_get_channel_binding( + data, FIRSTSOCKET, &neg_ctx->channel_binding_data); + if(result) { + Curl_http_auth_cleanup_negotiate(conn); + return result; + } + } +#endif + + /* Initialize the security context and decode our challenge */ result = Curl_auth_decode_spnego_message(data, userp, passwdp, service, host, header, neg_ctx); +#if defined(USE_SSL) && defined(HAVE_GSSAPI) + Curl_dyn_free(&neg_ctx->channel_binding_data); +#endif + if(result) - Curl_auth_spnego_cleanup(neg_ctx); + Curl_http_auth_cleanup_negotiate(conn); return result; } -CURLcode Curl_output_negotiate(struct connectdata *conn, bool proxy) +CURLcode Curl_output_negotiate(struct Curl_easy *data, + struct connectdata *conn, bool proxy) { - struct negotiatedata *neg_ctx = proxy ? &conn->data->state.proxyneg : - &conn->data->state.negotiate; + struct negotiatedata *neg_ctx; + struct auth *authp; + curlnegotiate *state; char *base64 = NULL; size_t len = 0; char *userp; CURLcode result; - result = Curl_auth_create_spnego_message(conn->data, neg_ctx, &base64, &len); - if(result) - return result; - - userp = aprintf("%sAuthorization: Negotiate %s\r\n", proxy ? "Proxy-" : "", - base64); - if(proxy) { - Curl_safefree(conn->allocptr.proxyuserpwd); - conn->allocptr.proxyuserpwd = userp; +#ifndef CURL_DISABLE_PROXY + neg_ctx = &conn->proxyneg; + authp = &data->state.authproxy; + state = &conn->proxy_negotiate_state; +#else + return CURLE_NOT_BUILT_IN; +#endif } else { - Curl_safefree(conn->allocptr.userpwd); - conn->allocptr.userpwd = userp; + neg_ctx = &conn->negotiate; + authp = &data->state.authhost; + state = &conn->http_negotiate_state; } - free(base64); + authp->done = FALSE; - return (userp == NULL) ? CURLE_OUT_OF_MEMORY : CURLE_OK; + 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) { +#ifndef CURL_DISABLE_PROXY + Curl_safefree(data->state.aptr.proxyuserpwd); + data->state.aptr.proxyuserpwd = userp; +#endif + } + 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, + * do not send a header in future requests */ + authp->done = TRUE; + } + + neg_ctx->havenegdata = FALSE; + + return CURLE_OK; } -void Curl_cleanup_negotiate(struct Curl_easy *data) +void Curl_http_auth_cleanup_negotiate(struct connectdata *conn) { - Curl_auth_spnego_cleanup(&data->state.negotiate); - Curl_auth_spnego_cleanup(&data->state.proxyneg); + 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/contrib/curl/lib/http_negotiate.h b/contrib/curl/lib/http_negotiate.h index c64e548251..76d8356132 100644 --- a/contrib/curl/lib/http_negotiate.h +++ b/contrib/curl/lib/http_negotiate.h @@ -7,11 +7,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2015, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -20,19 +20,24 @@ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY * KIND, either express or implied. * + * SPDX-License-Identifier: curl + * ***************************************************************************/ -#ifdef USE_SPNEGO +#if !defined(CURL_DISABLE_HTTP) && defined(USE_SPNEGO) /* this is for Negotiate header input */ -CURLcode Curl_input_negotiate(struct connectdata *conn, bool proxy, - const char *header); +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 connectdata *conn, bool proxy); +CURLcode Curl_output_negotiate(struct Curl_easy *data, + struct connectdata *conn, bool proxy); -void Curl_cleanup_negotiate(struct Curl_easy *data); +void Curl_http_auth_cleanup_negotiate(struct connectdata *conn); -#endif /* USE_SPNEGO */ +#else /* !CURL_DISABLE_HTTP && USE_SPNEGO */ +#define Curl_http_auth_cleanup_negotiate(x) +#endif #endif /* HEADER_CURL_HTTP_NEGOTIATE_H */ diff --git a/contrib/curl/lib/http_ntlm.c b/contrib/curl/lib/http_ntlm.c index 8a78bd2930..49230bc1bd 100644 --- a/contrib/curl/lib/http_ntlm.c +++ b/contrib/curl/lib/http_ntlm.c @@ -5,11 +5,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2016, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -18,6 +18,8 @@ * 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" @@ -27,7 +29,7 @@ /* * NTLM details: * - * https://davenport.sourceforge.io/ntlm.html + * https://davenport.sourceforge.net/ntlm.html * https://www.innovation.ch/java/ntlm.html */ @@ -37,13 +39,14 @@ #include "sendf.h" #include "strcase.h" #include "http_ntlm.h" -#include "curl_ntlm_wb.h" +#include "curl_ntlm_core.h" +#include "curl_base64.h" #include "vauth/vauth.h" #include "url.h" -#if defined(USE_NSS) -#include "vtls/nssg.h" -#elif defined(USE_WINDOWS_SSPI) +/* 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 @@ -58,16 +61,19 @@ # define DEBUG_OUT(x) Curl_nop_stmt #endif -CURLcode Curl_input_ntlm(struct connectdata *conn, +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"); @@ -76,29 +82,40 @@ CURLcode Curl_input_ntlm(struct connectdata *conn, header++; if(*header) { - result = Curl_auth_decode_ntlm_type2_message(conn->data, header, ntlm); + 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; - ntlm->state = NTLMSTATE_TYPE2; /* We got a type-2 message */ + *state = NTLMSTATE_TYPE2; /* We got a type-2 message */ } else { - if(ntlm->state == NTLMSTATE_LAST) { - infof(conn->data, "NTLM auth restarted\n"); - Curl_http_ntlm_cleanup(conn); + if(*state == NTLMSTATE_LAST) { + infof(data, "NTLM auth restarted"); + Curl_http_auth_cleanup_ntlm(conn); } - else if(ntlm->state == NTLMSTATE_TYPE3) { - infof(conn->data, "NTLM handshake rejected\n"); - Curl_http_ntlm_cleanup(conn); - ntlm->state = NTLMSTATE_NONE; + 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(ntlm->state >= NTLMSTATE_TYPE1) { - infof(conn->data, "NTLM handshake failure (internal error)\n"); + else if(*state >= NTLMSTATE_TYPE1) { + infof(data, "NTLM handshake failure (internal error)"); return CURLE_REMOTE_ACCESS_DENIED; } - ntlm->state = NTLMSTATE_TYPE1; /* We should send away a type-1 */ + *state = NTLMSTATE_TYPE1; /* We should send away a type-1 */ } } @@ -106,47 +123,59 @@ CURLcode Curl_input_ntlm(struct connectdata *conn, } /* - * This is for creating ntlm header output + * This is for creating NTLM header output */ -CURLcode Curl_output_ntlm(struct connectdata *conn, bool proxy) +CURLcode Curl_output_ntlm(struct Curl_easy *data, bool proxy) { char *base64 = NULL; size_t len = 0; - CURLcode result; + 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 a HTTP proxy */ + server, which is for a plain host or for an HTTP proxy */ char **allocuserpwd; - /* point to the name and password for this */ + /* 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(conn->data); - -#ifdef USE_NSS - if(CURLE_OK != Curl_nss_force_init(conn->data)) - return CURLE_OUT_OF_MEMORY; -#endif + DEBUGASSERT(data); if(proxy) { - allocuserpwd = &conn->allocptr.proxyuserpwd; - userp = conn->http_proxy.user; - passwdp = conn->http_proxy.passwd; +#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; - authp = &conn->data->state.authproxy; + state = &conn->proxy_ntlm_state; + authp = &data->state.authproxy; +#else + return CURLE_NOT_BUILT_IN; +#endif } else { - allocuserpwd = &conn->allocptr.userpwd; - userp = conn->user; - passwdp = conn->passwd; + 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; - authp = &conn->data->state.authhost; + state = &conn->http_ntlm_state; + authp = &data->state.authhost; } authp->done = FALSE; @@ -158,81 +187,84 @@ CURLcode Curl_output_ntlm(struct connectdata *conn, bool proxy) passwdp = ""; #ifdef USE_WINDOWS_SSPI - if(s_hSecDll == NULL) { + if(!Curl_hSecDll) { /* not thread safe and leaks - use curl_global_init() to avoid */ CURLcode err = Curl_sspi_global_init(); - if(s_hSecDll == NULL) + if(!Curl_hSecDll) return err; } +#ifdef SECPKG_ATTR_ENDPOINT_BINDINGS + ntlm->sslContext = conn->sslContext; #endif +#endif + + Curl_bufref_init(&ntlmmsg); - switch(ntlm->state) { + /* connection is already authenticated, do not 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(userp, passwdp, ntlm, &base64, - &len); - if(result) - return result; - - if(base64) { - free(*allocuserpwd); - *allocuserpwd = aprintf("%sAuthorization: NTLM %s\r\n", - proxy ? "Proxy-" : "", - base64); - free(base64); - if(!*allocuserpwd) - return CURLE_OUT_OF_MEMORY; - - DEBUG_OUT(fprintf(stderr, "**** Header %s\n ", *allocuserpwd)); + 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(conn->data, userp, passwdp, - ntlm, &base64, &len); - if(result) - return result; - - if(base64) { - free(*allocuserpwd); - *allocuserpwd = aprintf("%sAuthorization: NTLM %s\r\n", - proxy ? "Proxy-" : "", - base64); - free(base64); - if(!*allocuserpwd) - return CURLE_OUT_OF_MEMORY; - - DEBUG_OUT(fprintf(stderr, "**** %s\n ", *allocuserpwd)); - - ntlm->state = NTLMSTATE_TYPE3; /* we send a type-3 */ - authp->done = TRUE; + 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_TYPE3: - /* connection is already authenticated, - * don't send a header in future requests */ - ntlm->state = NTLMSTATE_LAST; - /* fall-through */ case NTLMSTATE_LAST: Curl_safefree(*allocuserpwd); authp->done = TRUE; break; } + Curl_bufref_free(&ntlmmsg); - return CURLE_OK; + return result; } -void Curl_http_ntlm_cleanup(struct connectdata *conn) +void Curl_http_auth_cleanup_ntlm(struct connectdata *conn) { - Curl_auth_ntlm_cleanup(&conn->ntlm); - Curl_auth_ntlm_cleanup(&conn->proxyntlm); - -#if defined(NTLM_WB_ENABLED) - Curl_ntlm_wb_cleanup(conn); -#endif + Curl_auth_cleanup_ntlm(&conn->ntlm); + Curl_auth_cleanup_ntlm(&conn->proxyntlm); } #endif /* !CURL_DISABLE_HTTP && USE_NTLM */ diff --git a/contrib/curl/lib/http_ntlm.h b/contrib/curl/lib/http_ntlm.h index d186bbe370..c1cf05701f 100644 --- a/contrib/curl/lib/http_ntlm.h +++ b/contrib/curl/lib/http_ntlm.h @@ -1,5 +1,5 @@ -#ifndef HEADER_CURL_NTLM_H -#define HEADER_CURL_NTLM_H +#ifndef HEADER_CURL_HTTP_NTLM_H +#define HEADER_CURL_HTTP_NTLM_H /*************************************************************************** * _ _ ____ _ * Project ___| | | | _ \| | @@ -7,11 +7,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2014, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -20,21 +20,25 @@ * 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 connectdata *conn, bool proxy, +/* 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 connectdata *conn, bool proxy); +/* this is for creating NTLM header output */ +CURLcode Curl_output_ntlm(struct Curl_easy *data, bool proxy); -void Curl_http_ntlm_cleanup(struct connectdata *conn); +void Curl_http_auth_cleanup_ntlm(struct connectdata *conn); -#endif /* !CURL_DISABLE_HTTP && USE_NTLM */ +#else /* !CURL_DISABLE_HTTP && USE_NTLM */ +#define Curl_http_auth_cleanup_ntlm(x) +#endif -#endif /* HEADER_CURL_NTLM_H */ +#endif /* HEADER_CURL_HTTP_NTLM_H */ diff --git a/contrib/curl/lib/http_proxy.c b/contrib/curl/lib/http_proxy.c index 7fde11dbb8..1ec08d0d09 100644 --- a/contrib/curl/lib/http_proxy.c +++ b/contrib/curl/lib/http_proxy.c @@ -5,11 +5,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2017, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -18,626 +18,320 @@ * 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 "http_proxy.h" + +#if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_PROXY) -#include "urldata.h" #include -#include "http_proxy.h" +#ifdef USE_HYPER +#include +#endif #include "sendf.h" #include "http.h" #include "url.h" #include "select.h" #include "progress.h" -#include "non-ascii.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" -/* - * Perform SSL initialization for HTTPS proxy. Sets - * proxy_ssl_connected connection bit when complete. Can be - * called multiple times. - */ -static CURLcode https_proxy_connect(struct connectdata *conn, int sockindex) + +CURLcode Curl_http_proxy_get_destination(struct Curl_cfilter *cf, + const char **phostname, + int *pport, bool *pipv6_ip) { -#ifdef USE_SSL - CURLcode result = CURLE_OK; - DEBUGASSERT(conn->http_proxy.proxytype == CURLPROXY_HTTPS); - if(!conn->bits.proxy_ssl_connected[sockindex]) { - /* perform SSL initialization for this socket */ - result = - Curl_ssl_connect_nonblocking(conn, sockindex, - &conn->bits.proxy_ssl_connected[sockindex]); - if(result) - conn->bits.close = TRUE; /* a failed connection is marked for closure to - prevent (bad) re-use or similar */ - } - return result; -#else - (void) conn; - (void) sockindex; - return CURLE_NOT_BUILT_IN; -#endif + 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_proxy_connect(struct connectdata *conn, int sockindex) +CURLcode Curl_http_proxy_create_CONNECT(struct httpreq **preq, + struct Curl_cfilter *cf, + struct Curl_easy *data, + int http_version_major) { - if(conn->http_proxy.proxytype == CURLPROXY_HTTPS) { - const CURLcode result = https_proxy_connect(conn, sockindex); + 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) - return result; - if(!conn->bits.proxy_ssl_connected[sockindex]) - return result; /* wait for HTTPS proxy SSL initialization to complete */ + goto out; } - if(conn->bits.tunnel_proxy && conn->bits.httpproxy) { -#ifndef CURL_DISABLE_PROXY - /* for [protocol] tunneled through HTTP proxy */ - struct HTTP http_proxy; - void *prot_save; - const char *hostname; - int remote_port; - CURLcode result; - - /* BLOCKING */ - /* We want "seamless" operations through HTTP proxy tunnel */ - - /* Curl_proxyCONNECT is based on a pointer to a struct HTTP at the - * member conn->proto.http; we want [protocol] through HTTP and we have - * to change the member temporarily for connecting to the HTTP - * proxy. After Curl_proxyCONNECT we have to set back the member to the - * original pointer - * - * This function might be called several times in the multi interface case - * if the proxy's CONNECT response is not instant. - */ - prot_save = conn->data->req.protop; - memset(&http_proxy, 0, sizeof(http_proxy)); - conn->data->req.protop = &http_proxy; - connkeep(conn, "HTTP proxy CONNECT"); + if(data->state.aptr.proxyuserpwd) { + result = Curl_dynhds_h1_cadd_line(&req->headers, + data->state.aptr.proxyuserpwd); + if(result) + goto out; + } - /* for the secondary socket (FTP), use the "connect to host" - * but ignore the "connect to port" (use the secondary port) - */ + if(!Curl_checkProxyheaders(data, cf->conn, STRCONST("User-Agent")) && + data->set.str[STRING_USERAGENT] && *data->set.str[STRING_USERAGENT]) { + result = Curl_dynhds_cadd(&req->headers, "User-Agent", + data->set.str[STRING_USERAGENT]); + if(result) + goto out; + } - if(conn->bits.conn_to_host) - hostname = conn->conn_to_host.name; - else if(sockindex == SECONDARYSOCKET) - hostname = conn->secondaryhostname; - else - hostname = conn->host.name; - - if(sockindex == SECONDARYSOCKET) - remote_port = conn->secondary_port; - else if(conn->bits.conn_to_port) - remote_port = conn->conn_to_port; - else - remote_port = conn->remote_port; - result = Curl_proxyCONNECT(conn, sockindex, hostname, - remote_port, FALSE); - conn->data->req.protop = prot_save; - if(CURLE_OK != result) - return result; - Curl_safefree(conn->allocptr.proxyuserpwd); -#else - return CURLE_NOT_BUILT_IN; -#endif + 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; } - /* no HTTP tunnel proxy, just return */ - return CURLE_OK; + + 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; } -/* - * Curl_proxyCONNECT() requires that we're connected to a HTTP proxy. This - * function will issue the necessary commands to get a seamless tunnel through - * this proxy. After that, the socket can be used just as a normal socket. - * - * 'blocking' set to TRUE means that this function will do the entire CONNECT - * + response in a blocking fashion. Should be avoided! - */ - -CURLcode Curl_proxyCONNECT(struct connectdata *conn, - int sockindex, - const char *hostname, - int remote_port, - bool blocking) + +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) { - int subversion=0; - struct Curl_easy *data=conn->data; - struct SingleRequest *k = &data->req; + struct cf_proxy_ctx *ctx = cf->ctx; CURLcode result; - curl_socket_t tunnelsocket = conn->sock[sockindex]; - curl_off_t cl=0; - bool closeConnection = FALSE; - bool chunked_encoding = FALSE; - time_t check; - -#define SELECT_OK 0 -#define SELECT_ERROR 1 -#define SELECT_TIMEOUT 2 - int error = SELECT_OK; - - if(conn->tunnel_state[sockindex] == TUNNEL_COMPLETE) - return CURLE_OK; /* CONNECT is already completed */ - - conn->bits.proxy_connect_closed = FALSE; - - do { - if(TUNNEL_INIT == conn->tunnel_state[sockindex]) { - /* BEGIN CONNECT PHASE */ - char *host_port; - Curl_send_buffer *req_buffer; - - infof(data, "Establish HTTP proxy tunnel to %s:%hu\n", - hostname, remote_port); - - /* 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. */ - free(data->req.newurl); - data->req.newurl = NULL; - - /* initialize a dynamic send-buffer */ - req_buffer = Curl_add_buffer_init(); - - if(!req_buffer) - return CURLE_OUT_OF_MEMORY; - - host_port = aprintf("%s:%hu", hostname, remote_port); - if(!host_port) { - Curl_add_buffer_free(req_buffer); - return CURLE_OUT_OF_MEMORY; - } - /* Setup the proxy-authorization header, if any */ - result = Curl_http_output_auth(conn, "CONNECT", host_port, TRUE); - - free(host_port); - - if(!result) { - char *host = NULL; - const char *proxyconn=""; - const char *useragent=""; - const char *http = (conn->http_proxy.proxytype == CURLPROXY_HTTP_1_0) ? - "1.0" : "1.1"; - bool ipv6_ip = conn->bits.ipv6_ip; - char *hostheader; - - /* the hostname may be different */ - if(hostname != conn->host.name) - ipv6_ip = (strchr(hostname, ':') != NULL); - hostheader= /* host:port with IPv6 support */ - aprintf("%s%s%s:%hu", ipv6_ip?"[":"", hostname, ipv6_ip?"]":"", - remote_port); - if(!hostheader) { - Curl_add_buffer_free(req_buffer); - return CURLE_OUT_OF_MEMORY; - } - - if(!Curl_checkProxyheaders(conn, "Host:")) { - host = aprintf("Host: %s\r\n", hostheader); - if(!host) { - free(hostheader); - Curl_add_buffer_free(req_buffer); - return CURLE_OUT_OF_MEMORY; - } - } - if(!Curl_checkProxyheaders(conn, "Proxy-Connection:")) - proxyconn = "Proxy-Connection: Keep-Alive\r\n"; - - if(!Curl_checkProxyheaders(conn, "User-Agent:") && - data->set.str[STRING_USERAGENT]) - useragent = conn->allocptr.uagent; - - result = - Curl_add_bufferf(req_buffer, - "CONNECT %s HTTP/%s\r\n" - "%s" /* Host: */ - "%s" /* Proxy-Authorization */ - "%s" /* User-Agent */ - "%s", /* Proxy-Connection */ - hostheader, - http, - host?host:"", - conn->allocptr.proxyuserpwd? - conn->allocptr.proxyuserpwd:"", - useragent, - proxyconn); - - if(host) - free(host); - free(hostheader); - - if(!result) - result = Curl_add_custom_headers(conn, TRUE, req_buffer); - - if(!result) - /* CRLF terminate the request */ - result = Curl_add_bufferf(req_buffer, "\r\n"); - - if(!result) { - /* Send the connect request to the proxy */ - /* BLOCKING */ - result = - Curl_add_buffer_send(req_buffer, conn, - &data->info.request_size, 0, sockindex); - } - req_buffer = NULL; - if(result) - failf(data, "Failed sending CONNECT to proxy"); - } + if(cf->connected) { + *done = TRUE; + return CURLE_OK; + } - Curl_add_buffer_free(req_buffer); + 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) - return result; - - conn->tunnel_state[sockindex] = TUNNEL_CONNECT; - } /* END CONNECT PHASE */ - - check = Curl_timeleft(data, NULL, TRUE); - if(check <= 0) { - failf(data, "Proxy CONNECT aborted due to timeout"); - return CURLE_RECV_ERROR; - } - - if(!blocking) { - if(!Curl_conn_data_pending(conn, sockindex)) - /* return so we'll be called again polling-style */ - return CURLE_OK; - else { - DEBUGF(infof(data, - "Read response immediately from proxy CONNECT\n")); - } + 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; } - /* at this point, the tunnel_connecting phase is over. */ - - { /* READING RESPONSE PHASE */ - size_t nread; /* total size read */ - int perline; /* count bytes per line */ - int keepon=TRUE; - ssize_t gotbytes; - char *ptr; - char *line_start; - - ptr = data->state.buffer; - line_start = ptr; - - nread = 0; - perline = 0; - - while(nread < BUFSIZE && keepon && !error) { - int writetype; - - if(Curl_pgrsUpdate(conn)) - return CURLE_ABORTED_BY_CALLBACK; - - if(ptr >= &data->state.buffer[BUFSIZE]) { - failf(data, "CONNECT response too large!"); - return CURLE_RECV_ERROR; - } - - check = Curl_timeleft(data, NULL, TRUE); - if(check <= 0) { - failf(data, "Proxy CONNECT aborted due to timeout"); - error = SELECT_TIMEOUT; /* already too little time */ - break; - } - - /* 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(conn, tunnelsocket, ptr, 1, &gotbytes); - if(result == CURLE_AGAIN) { - if(SOCKET_READABLE(tunnelsocket, check<1000L?check:1000) == -1) { - error = SELECT_ERROR; - failf(data, "Proxy CONNECT aborted due to select/poll error"); - break; - } - continue; - } - else if(result) { - keepon = FALSE; - break; - } - else if(gotbytes <= 0) { - if(data->set.proxyauth && data->state.authproxy.avail) { - /* proxy auth was requested and there was proxy auth available, - then deem this as "mere" proxy disconnect */ - conn->bits.proxy_connect_closed = TRUE; - infof(data, "Proxy CONNECT connection closed\n"); - } - else { - error = SELECT_ERROR; - failf(data, "Proxy CONNECT aborted"); - } - keepon = FALSE; - break; - } - - /* We got a byte of data */ - nread++; - - if(keepon > TRUE) { - /* This means we are currently ignoring a response-body */ - - nread = 0; /* make next read start over in the read buffer */ - ptr = data->state.buffer; - if(cl) { - /* A Content-Length based body: simply count down the counter - and make sure to break out of the loop when we're done! */ - cl--; - if(cl <= 0) { - keepon = FALSE; - 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; - ssize_t tookcareof = 0; - - /* now parse the chunked piece of data so that we can - properly tell when the stream ends */ - r = Curl_httpchunk_read(conn, ptr, 1, &tookcareof); - if(r == CHUNKE_STOP) { - /* we're done reading chunks! */ - infof(data, "chunk reading DONE\n"); - keepon = FALSE; - /* we did the full CONNECT treatment, go COMPLETE */ - conn->tunnel_state[sockindex] = TUNNEL_COMPLETE; - } - } - continue; - } - - perline++; /* amount of bytes in this line so far */ - - /* if this is not the end of a header line then continue */ - if(*ptr != 0x0a) { - ptr++; - continue; - } - - /* convert from the network encoding */ - result = Curl_convert_from_network(data, line_start, perline); - /* Curl_convert_from_network calls failf if unsuccessful */ - if(result) - return result; - - /* output debug if that is requested */ - if(data->set.verbose) - Curl_debug(data, CURLINFO_HEADER_IN, - line_start, (size_t)perline, conn); - - /* send the header to the callback */ - writetype = CLIENTWRITE_HEADER; - if(data->set.include_header) - writetype |= CLIENTWRITE_BODY; - - result = Curl_client_write(conn, writetype, line_start, perline); - - data->info.header_size += (long)perline; - data->req.headerbytecount += (long)perline; - - 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' == line_start[0]) || - ('\n' == line_start[0])) { - /* end of response-headers from the proxy */ - nread = 0; /* make next read start over in the read - buffer */ - ptr = data->state.buffer; - 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 */ - keepon = 2; - - if(cl) { - infof(data, "Ignore %" CURL_FORMAT_CURL_OFF_T - " bytes of response-body\n", cl); - } - else if(chunked_encoding) { - CHUNKcode r; - - infof(data, "Ignore chunked response-body\n"); - - /* 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(line_start[1] == '\n') { - /* this can only be a LF if the letter at index 0 - was a CR */ - line_start++; - } - - /* now parse the chunked piece of data so that we can - properly tell when the stream ends */ - r = Curl_httpchunk_read(conn, line_start + 1, 1, &gotbytes); - if(r == CHUNKE_STOP) { - /* we're done reading chunks! */ - infof(data, "chunk reading DONE\n"); - keepon = FALSE; - /* we did the full CONNECT treatment, go to - COMPLETE */ - conn->tunnel_state[sockindex] = TUNNEL_COMPLETE; - } - } - 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 */ - keepon = FALSE; - } - } - else - keepon = FALSE; - /* we did the full CONNECT treatment, go to COMPLETE */ - conn->tunnel_state[sockindex] = TUNNEL_COMPLETE; - continue; - } - - line_start[perline] = 0; /* zero terminate the buffer */ - if((checkprefix("WWW-Authenticate:", line_start) && - (401 == k->httpcode)) || - (checkprefix("Proxy-authenticate:", line_start) && - (407 == k->httpcode))) { - - bool proxy = (k->httpcode == 407) ? TRUE : FALSE; - char *auth = Curl_copy_header_value(line_start); - if(!auth) - return CURLE_OUT_OF_MEMORY; - - result = Curl_http_input_auth(conn, proxy, auth); - - free(auth); - - if(result) - return result; - } - else if(checkprefix("Content-Length:", line_start)) { - if(k->httpcode/100 == 2) { - /* A server MUST NOT send any Transfer-Encoding or - Content-Length header fields in a 2xx (Successful) - response to CONNECT. (RFC 7231 section 4.3.6) */ - failf(data, "Content-Length: in %03d response", - k->httpcode); - return CURLE_RECV_ERROR; - } - - cl = curlx_strtoofft(line_start + - strlen("Content-Length:"), NULL, 10); - } - else if(Curl_compareheader(line_start, "Connection:", "close")) - closeConnection = TRUE; - else if(Curl_compareheader(line_start, - "Transfer-Encoding:", - "chunked")) { - if(k->httpcode/100 == 2) { - /* A server MUST NOT send any Transfer-Encoding or - Content-Length header fields in a 2xx (Successful) - response to CONNECT. (RFC 7231 section 4.3.6) */ - failf(data, "Transfer-Encoding: in %03d response", k->httpcode); - return CURLE_RECV_ERROR; - } - infof(data, "CONNECT responded chunked\n"); - chunked_encoding = TRUE; - /* init our chunky engine */ - Curl_httpchunk_init(conn); - } - else if(Curl_compareheader(line_start, "Proxy-Connection:", "close")) - closeConnection = TRUE; - else if(2 == sscanf(line_start, "HTTP/1.%d %d", - &subversion, - &k->httpcode)) { - /* store the HTTP code from the proxy */ - data->info.httpproxycode = k->httpcode; - } - - perline = 0; /* line starts over here */ - ptr = data->state.buffer; - line_start = ptr; - } /* while there's buffer left and loop is requested */ - - if(Curl_pgrsUpdate(conn)) - return CURLE_ABORTED_BY_CALLBACK; - - if(error) - return CURLE_RECV_ERROR; - - if(data->info.httpproxycode != 200) { - /* Deal with the possibly already received authenticate - headers. 'newurl' is set to a new URL if we must loop. */ - result = Curl_http_auth_act(conn); - if(result) - return result; - - if(conn->bits.close) - /* the connection has been marked for closure, most likely in the - Curl_http_auth_act() function and thus we can kill it at once - below */ - closeConnection = TRUE; - } + 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; + } - if(closeConnection && data->req.newurl) { - /* Connection closed by server. Don't use it anymore */ - Curl_closesocket(conn, conn->sock[sockindex]); - conn->sock[sockindex] = CURL_SOCKET_BAD; - break; - } - } /* END READING RESPONSE PHASE */ - - /* If we are supposed to continue and request a new URL, which basically - * means the HTTP authentication is still going on so if the tunnel - * is complete we start over in INIT state */ - if(data->req.newurl && - (TUNNEL_COMPLETE == conn->tunnel_state[sockindex])) { - conn->tunnel_state[sockindex] = TUNNEL_INIT; - infof(data, "TUNNEL_STATE switched to: %d\n", - conn->tunnel_state[sockindex]); - } +out: + if(!result) { + cf->connected = TRUE; + *done = TRUE; + } + return result; +} - } while(data->req.newurl); +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); + } +} - if(200 != data->req.httpcode) { - if(closeConnection && data->req.newurl) { - conn->bits.proxy_connect_closed = TRUE; - infof(data, "Connect me again please\n"); - } - else { - free(data->req.newurl); - data->req.newurl = NULL; - /* failure, close this connection to avoid re-use */ - streamclose(conn, "proxy CONNECT failure"); - Curl_closesocket(conn, conn->sock[sockindex]); - conn->sock[sockindex] = CURL_SOCKET_BAD; - } +static void http_proxy_cf_destroy(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct cf_proxy_ctx *ctx = cf->ctx; - /* to back to init state */ - conn->tunnel_state[sockindex] = TUNNEL_INIT; + (void)data; + CURL_TRC_CF(data, cf, "destroy"); + free(ctx); +} - if(conn->bits.proxy_connect_closed) - /* this is not an error, just part of the connection negotiation */ - return CURLE_OK; - else { - failf(data, "Received HTTP code %d from proxy after CONNECT", - data->req.httpcode); - return CURLE_RECV_ERROR; +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); +} - conn->tunnel_state[sockindex] = TUNNEL_COMPLETE; - - /* 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(conn->allocptr.proxyuserpwd); - conn->allocptr.proxyuserpwd = NULL; - data->state.authproxy.done = TRUE; +struct Curl_cftype Curl_cft_http_proxy = { + "HTTP-PROXY", + CF_TYPE_IP_CONNECT|CF_TYPE_PROXY, + 0, + http_proxy_cf_destroy, + http_proxy_cf_connect, + http_proxy_cf_close, + Curl_cf_def_shutdown, + Curl_cf_http_proxy_get_host, + Curl_cf_def_adjust_pollset, + Curl_cf_def_data_pending, + 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; - infof(data, "Proxy replied OK to CONNECT request\n"); - data->req.ignorebody = FALSE; /* put it (back) to non-ignore state */ - conn->bits.rewindaftersend = FALSE; /* make sure this isn't set for the - document request */ - return CURLE_OK; + (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_PROXY */ + +#endif /* ! CURL_DISABLE_HTTP && !CURL_DISABLE_PROXY */ diff --git a/contrib/curl/lib/http_proxy.h b/contrib/curl/lib/http_proxy.h index d1f5a7c83f..2b5f7ae706 100644 --- a/contrib/curl/lib/http_proxy.h +++ b/contrib/curl/lib/http_proxy.h @@ -7,11 +7,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2015, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -20,23 +20,42 @@ * 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) -/* ftp can use this as well */ -CURLcode Curl_proxyCONNECT(struct connectdata *conn, - int tunnelsocket, - const char *hostname, int remote_port, - bool blocking); + +#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) -CURLcode Curl_proxy_connect(struct connectdata *conn, int sockindex); +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 */ -#else -#define Curl_proxyCONNECT(x,y,z,w,v) CURLE_NOT_BUILT_IN -#define Curl_proxy_connect(x,y) CURLE_OK -#endif +#define IS_HTTPS_PROXY(t) (((t) == CURLPROXY_HTTPS) || \ + ((t) == CURLPROXY_HTTPS2)) #endif /* HEADER_CURL_HTTP_PROXY_H */ diff --git a/contrib/curl/lib/idn.c b/contrib/curl/lib/idn.c new file mode 100644 index 0000000000..ed20cdc16b --- /dev/null +++ b/contrib/curl/lib/idn.c @@ -0,0 +1,378 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + + /* + * 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 + +#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" + +/* for macOS and iOS targets */ +#if defined(USE_APPLE_IDN) +#include +#include +#include + +#define MAX_HOST_LENGTH 512 + +static CURLcode iconv_to_utf8(const char *in, size_t inlen, + char **out, size_t *outlen) +{ + iconv_t cd = iconv_open("UTF-8", nl_langinfo(CODESET)); + if(cd != (iconv_t)-1) { + size_t iconv_outlen = *outlen; + char *iconv_in = (char *)in; + size_t iconv_inlen = inlen; + size_t iconv_result = iconv(cd, &iconv_in, &iconv_inlen, + out, &iconv_outlen); + *outlen -= iconv_outlen; + iconv_close(cd); + if(iconv_result == (size_t)-1) { + if(errno == ENOMEM) + return CURLE_OUT_OF_MEMORY; + else + return CURLE_URL_MALFORMAT; + } + + return CURLE_OK; + } + else { + if(errno == ENOMEM) + return CURLE_OUT_OF_MEMORY; + else + return CURLE_FAILED_INIT; + } +} + +static CURLcode mac_idn_to_ascii(const char *in, char **out) +{ + size_t inlen = strlen(in); + if(inlen < MAX_HOST_LENGTH) { + char iconv_buffer[MAX_HOST_LENGTH] = {0}; + char *iconv_outptr = iconv_buffer; + size_t iconv_outlen = sizeof(iconv_buffer); + CURLcode iconv_result = iconv_to_utf8(in, inlen, + &iconv_outptr, &iconv_outlen); + if(!iconv_result) { + UErrorCode err = U_ZERO_ERROR; + UIDNA* idna = uidna_openUTS46( + UIDNA_CHECK_BIDI|UIDNA_NONTRANSITIONAL_TO_ASCII, &err); + if(!U_FAILURE(err)) { + UIDNAInfo info = UIDNA_INFO_INITIALIZER; + char buffer[MAX_HOST_LENGTH] = {0}; + (void)uidna_nameToASCII_UTF8(idna, iconv_buffer, (int)iconv_outlen, + buffer, sizeof(buffer) - 1, &info, &err); + uidna_close(idna); + if(!U_FAILURE(err) && !info.errors) { + *out = strdup(buffer); + if(*out) + return CURLE_OK; + else + return CURLE_OUT_OF_MEMORY; + } + } + } + else + return iconv_result; + } + return CURLE_URL_MALFORMAT; +} + +static CURLcode mac_ascii_to_idn(const char *in, char **out) +{ + size_t inlen = strlen(in); + if(inlen < MAX_HOST_LENGTH) { + UErrorCode err = U_ZERO_ERROR; + UIDNA* idna = uidna_openUTS46( + UIDNA_CHECK_BIDI|UIDNA_NONTRANSITIONAL_TO_UNICODE, &err); + if(!U_FAILURE(err)) { + UIDNAInfo info = UIDNA_INFO_INITIALIZER; + char buffer[MAX_HOST_LENGTH] = {0}; + (void)uidna_nameToUnicodeUTF8(idna, in, -1, buffer, + sizeof(buffer) - 1, &info, &err); + uidna_close(idna); + if(!U_FAILURE(err)) { + *out = strdup(buffer); + if(*out) + return CURLE_OK; + else + return CURLE_OUT_OF_MEMORY; + } + } + } + return CURLE_URL_MALFORMAT; +} +#endif + +#ifdef USE_WIN32_IDN +/* using Windows kernel32 and normaliz libraries. */ + +#if (!defined(_WIN32_WINNT) || _WIN32_WINNT < 0x600) && \ + (!defined(WINVER) || WINVER < 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 hostname 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); +#elif defined(USE_APPLE_IDN) + result = mac_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; +#elif defined(USE_APPLE_IDN) + CURLcode result = mac_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) +{ + Curl_safefree(host->encalloc); +} + +#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 hostname */ + 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 = Curl_idn_decode(host->name, &decoded); + if(result) + return result; + /* successful */ + host->name = host->encalloc = decoded; + } +#endif + return CURLE_OK; +} diff --git a/contrib/curl/lib/idn.h b/contrib/curl/lib/idn.h new file mode 100644 index 0000000000..2bdce8927f --- /dev/null +++ b/contrib/curl/lib/idn.h @@ -0,0 +1,39 @@ +#ifndef HEADER_CURL_IDN_H +#define HEADER_CURL_IDN_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +bool Curl_is_ASCII_name(const char *hostname); +CURLcode Curl_idnconvert_hostname(struct hostname *host); +#if defined(USE_LIBIDN2) || defined(USE_WIN32_IDN) || defined(USE_APPLE_IDN) +#define USE_IDN +void Curl_free_idnconverted_hostname(struct hostname *host); +CURLcode Curl_idn_decode(const char *input, char **output); +CURLcode Curl_idn_encode(const char *input, char **output); + +#else +#define Curl_free_idnconverted_hostname(x) +#define Curl_idn_decode(x) NULL +#endif +#endif /* HEADER_CURL_IDN_H */ diff --git a/contrib/curl/lib/idn_win32.c b/contrib/curl/lib/idn_win32.c deleted file mode 100644 index 8dc300b36d..0000000000 --- a/contrib/curl/lib/idn_win32.c +++ /dev/null @@ -1,111 +0,0 @@ -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) 1998 - 2016, Daniel Stenberg, , et al. - * - * This software is licensed as described in the file COPYING, which - * you should have received as part of this distribution. The terms - * are also available at https://curl.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. - * - ***************************************************************************/ - - /* - * IDN conversions using Windows kernel32 and normaliz libraries. - */ - -#include "curl_setup.h" - -#ifdef USE_WIN32_IDN - -#include "curl_multibyte.h" -#include "curl_memory.h" -#include "warnless.h" - - /* The last #include file should be: */ -#include "memdebug.h" - -#ifdef WANT_IDN_PROTOTYPES -# if defined(_SAL_VERSION) -WINNORMALIZEAPI int WINAPI -IdnToAscii(_In_ DWORD dwFlags, - _In_reads_(cchUnicodeChar) LPCWSTR lpUnicodeCharStr, - _In_ int cchUnicodeChar, - _Out_writes_opt_(cchASCIIChar) LPWSTR lpASCIICharStr, - _In_ int cchASCIIChar); -WINNORMALIZEAPI int WINAPI -IdnToUnicode(_In_ DWORD dwFlags, - _In_reads_(cchASCIIChar) LPCWSTR lpASCIICharStr, - _In_ int cchASCIIChar, - _Out_writes_opt_(cchUnicodeChar) LPWSTR lpUnicodeCharStr, - _In_ int cchUnicodeChar); -# else -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 -#endif - -#define IDN_MAX_LENGTH 255 - -bool curl_win32_idn_to_ascii(const char *in, char **out); -bool curl_win32_ascii_to_idn(const char *in, char **out); - -bool curl_win32_idn_to_ascii(const char *in, char **out) -{ - bool success = FALSE; - - wchar_t *in_w = Curl_convert_UTF8_to_wchar(in); - if(in_w) { - wchar_t punycode[IDN_MAX_LENGTH]; - int chars = IdnToAscii(0, in_w, -1, punycode, IDN_MAX_LENGTH); - free(in_w); - if(chars) { - *out = Curl_convert_wchar_to_UTF8(punycode); - if(*out) - success = TRUE; - } - } - - return success; -} - -bool curl_win32_ascii_to_idn(const char *in, char **out) -{ - bool success = FALSE; - - wchar_t *in_w = Curl_convert_UTF8_to_wchar(in); - if(in_w) { - size_t in_len = wcslen(in_w) + 1; - wchar_t unicode[IDN_MAX_LENGTH]; - int chars = IdnToUnicode(0, in_w, curlx_uztosi(in_len), - unicode, IDN_MAX_LENGTH); - free(in_w); - if(chars) { - *out = Curl_convert_wchar_to_UTF8(unicode); - if(*out) - success = TRUE; - } - } - - return success; -} - -#endif /* USE_WIN32_IDN */ diff --git a/contrib/curl/lib/if2ip.c b/contrib/curl/lib/if2ip.c index d876615ea1..55afd553d6 100644 --- a/contrib/curl/lib/if2ip.c +++ b/contrib/curl/lib/if2ip.c @@ -5,11 +5,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2016, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -18,6 +18,8 @@ * 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" @@ -60,17 +62,17 @@ /* ------------------------------------------------------------------ */ +#ifdef USE_IPV6 /* Return the scope of the given address. */ unsigned int Curl_ipv6_scope(const struct sockaddr *sa) { -#ifndef ENABLE_IPV6 - (void) sa; -#else 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; @@ -86,68 +88,52 @@ unsigned int Curl_ipv6_scope(const struct sockaddr *sa) break; } } -#endif - return IPV6_SCOPE_GLOBAL; } +#endif +#ifndef CURL_DISABLE_BINDLOCAL #if defined(HAVE_GETIFADDRS) -bool Curl_if_is_interface_name(const char *interf) -{ - bool result = FALSE; - - struct ifaddrs *iface, *head; - - if(getifaddrs(&head) >= 0) { - for(iface=head; iface != NULL; iface=iface->ifa_next) { - if(strcasecompare(iface->ifa_name, interf)) { - result = TRUE; - break; - } - } - freeifaddrs(head); - } - return result; -} - -if2ip_result_t Curl_if2ip(int af, unsigned int remote_scope, - unsigned int remote_scope_id, const char *interf, - char *buf, int buf_size) +if2ip_result_t Curl_if2ip(int af, +#ifdef USE_IPV6 + unsigned int remote_scope, + unsigned int local_scope_id, +#endif + const char *interf, + char *buf, size_t buf_size) { struct ifaddrs *iface, *head; if2ip_result_t res = IF2IP_NOT_FOUND; -#ifndef ENABLE_IPV6 - (void) remote_scope; - -#ifndef HAVE_SOCKADDR_IN6_SIN6_SCOPE_ID - (void) remote_scope_id; -#endif - +#if defined(USE_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 != NULL) { + 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; - char *ip; + const char *ip; char scope[12] = ""; char ipstr[64]; -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 if(af == AF_INET6) { +#ifdef HAVE_SOCKADDR_IN6_SIN6_SCOPE_ID unsigned int scopeid = 0; +#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; + /* 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; } @@ -159,23 +145,24 @@ if2ip_result_t Curl_if2ip(int af, unsigned int remote_scope, ->sin6_scope_id; /* If given, scope id should match. */ - if(remote_scope_id && scopeid != remote_scope_id) { + if(local_scope_id && scopeid != local_scope_id) { if(res == IF2IP_NOT_FOUND) res = IF2IP_AF_NOT_SUPPORTED; continue; } -#endif + if(scopeid) - snprintf(scope, sizeof(scope), "%%%u", scopeid); + msnprintf(scope, sizeof(scope), "%%%u", scopeid); +#endif } else #endif addr = - &((struct sockaddr_in *)(void *)iface->ifa_addr)->sin_addr; + &((struct sockaddr_in *)(void *)iface->ifa_addr)->sin_addr; res = IF2IP_FOUND; - ip = (char *) Curl_inet_ntop(af, addr, ipstr, sizeof(ipstr)); - snprintf(buf, buf_size, "%s%s", ip, scope); + ip = Curl_inet_ntop(af, addr, ipstr, sizeof(ipstr)); + msnprintf(buf, buf_size, "%s%s", ip, scope); break; } } @@ -194,27 +181,25 @@ if2ip_result_t Curl_if2ip(int af, unsigned int remote_scope, #elif defined(HAVE_IOCTL_SIOCGIFADDR) -bool Curl_if_is_interface_name(const char *interf) -{ - /* This is here just to support the old interfaces */ - char buf[256]; - - return (Curl_if2ip(AF_INET, 0 /* unused */, 0, interf, buf, sizeof(buf)) == - IF2IP_NOT_FOUND) ? FALSE : TRUE; -} - -if2ip_result_t Curl_if2ip(int af, unsigned int remote_scope, - unsigned int remote_scope_id, const char *interf, - char *buf, int buf_size) +if2ip_result_t Curl_if2ip(int af, +#ifdef USE_IPV6 + unsigned int remote_scope, + unsigned int local_scope_id, +#endif + const char *interf, + char *buf, size_t buf_size) { struct ifreq req; struct in_addr in; struct sockaddr_in *s; curl_socket_t dummy; size_t len; + const char *r; +#ifdef USE_IPV6 (void)remote_scope; - (void)remote_scope_id; + (void)local_scope_id; +#endif if(!interf || (af != AF_INET)) return IF2IP_NOT_FOUND; @@ -228,10 +213,18 @@ if2ip_result_t Curl_if2ip(int af, unsigned int remote_scope, return IF2IP_NOT_FOUND; memset(&req, 0, sizeof(req)); - memcpy(req.ifr_name, interf, len+1); + memcpy(req.ifr_name, interf, len + 1); req.ifr_addr.sa_family = AF_INET; +#if defined(__GNUC__) && defined(_AIX) +/* Suppress warning inside system headers */ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wshift-sign-overflow" +#endif if(ioctl(dummy, SIOCGIFADDR, &req) < 0) { +#if defined(__GNUC__) && defined(_AIX) +#pragma GCC diagnostic pop +#endif 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 @@ -239,30 +232,31 @@ if2ip_result_t Curl_if2ip(int af, unsigned int remote_scope, return IF2IP_NOT_FOUND; } - s = (struct sockaddr_in *)&req.ifr_addr; + s = (struct sockaddr_in *)(void *)&req.ifr_addr; memcpy(&in, &s->sin_addr, sizeof(in)); - Curl_inet_ntop(s->sin_family, &in, buf, buf_size); + r = Curl_inet_ntop(s->sin_family, &in, buf, buf_size); sclose(dummy); + if(!r) + return IF2IP_NOT_FOUND; return IF2IP_FOUND; } #else -bool Curl_if_is_interface_name(const char *interf) -{ - (void) interf; - - return FALSE; -} - -if2ip_result_t Curl_if2ip(int af, unsigned int remote_scope, - unsigned int remote_scope_id, const char *interf, - char *buf, int buf_size) +if2ip_result_t Curl_if2ip(int af, +#ifdef USE_IPV6 + unsigned int remote_scope, + unsigned int local_scope_id, +#endif + const char *interf, + char *buf, size_t buf_size) { (void) af; +#ifdef USE_IPV6 (void) remote_scope; - (void) remote_scope_id; + (void) local_scope_id; +#endif (void) interf; (void) buf; (void) buf_size; @@ -270,3 +264,5 @@ if2ip_result_t Curl_if2ip(int af, unsigned int remote_scope, } #endif + +#endif /* CURL_DISABLE_BINDLOCAL */ diff --git a/contrib/curl/lib/if2ip.h b/contrib/curl/lib/if2ip.h index f3a7ff0b2f..f4b2f4c15d 100644 --- a/contrib/curl/lib/if2ip.h +++ b/contrib/curl/lib/if2ip.h @@ -7,11 +7,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2014, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -20,6 +20,8 @@ * 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" @@ -27,11 +29,14 @@ #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_NODELOCAL 3 /* Loopback. */ +#define IPV6_SCOPE_UNIQUELOCAL 3 /* Unique local */ +#define IPV6_SCOPE_NODELOCAL 4 /* Loopback. */ +#ifdef USE_IPV6 unsigned int Curl_ipv6_scope(const struct sockaddr *sa); - -bool Curl_if_is_interface_name(const char *interf); +#else +#define Curl_ipv6_scope(x) 0 +#endif typedef enum { IF2IP_NOT_FOUND = 0, /* Interface not found */ @@ -39,9 +44,13 @@ typedef enum { IF2IP_FOUND = 2 /* The address has been stored in "buf" */ } if2ip_result_t; -if2ip_result_t Curl_if2ip(int af, unsigned int remote_scope, - unsigned int remote_scope_id, const char *interf, - char *buf, int buf_size); +if2ip_result_t Curl_if2ip(int af, +#ifdef USE_IPV6 + unsigned int remote_scope, + unsigned int local_scope_id, +#endif + const char *interf, + char *buf, size_t buf_size); #ifdef __INTERIX diff --git a/contrib/curl/lib/imap.c b/contrib/curl/lib/imap.c index 44d350be21..4979a18edc 100644 --- a/contrib/curl/lib/imap.c +++ b/contrib/curl/lib/imap.c @@ -5,11 +5,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2017, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -18,6 +18,8 @@ * 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 @@ -28,6 +30,7 @@ * 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 * ***************************************************************************/ @@ -42,9 +45,6 @@ #ifdef HAVE_ARPA_INET_H #include #endif -#ifdef HAVE_UTSNAME_H -#include -#endif #ifdef HAVE_NETDB_H #include #endif @@ -53,11 +53,6 @@ #include #endif -#if (defined(NETWARE) && defined(__NOVELL_LIBC__)) -#undef in_addr_t -#define in_addr_t unsigned long -#endif - #include #include "urldata.h" #include "sendf.h" @@ -68,17 +63,19 @@ #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 "strerror.h" #include "select.h" #include "multiif.h" #include "url.h" -#include "strcase.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" @@ -86,35 +83,41 @@ #include "memdebug.h" /* Local API functions */ -static CURLcode imap_regular_transfer(struct connectdata *conn, bool *done); -static CURLcode imap_do(struct connectdata *conn, bool *done); -static CURLcode imap_done(struct connectdata *conn, CURLcode status, +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 connectdata *conn, bool *done); -static CURLcode imap_disconnect(struct connectdata *conn, bool dead); -static CURLcode imap_multi_statemach(struct connectdata *conn, bool *done); -static int imap_getsock(struct connectdata *conn, curl_socket_t *socks, - int numsocks); -static CURLcode imap_doing(struct connectdata *conn, bool *dophase_done); -static CURLcode imap_setup_connection(struct connectdata *conn); +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 connectdata *conn, const char *fmt, ...); +static CURLcode imap_sendf(struct Curl_easy *data, const char *fmt, ...) + CURL_PRINTF(2, 3); static CURLcode imap_parse_url_options(struct connectdata *conn); -static CURLcode imap_parse_url_path(struct connectdata *conn); -static CURLcode imap_parse_custom_request(struct connectdata *conn); -static CURLcode imap_perform_authenticate(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 char *initresp); -static CURLcode imap_continue_authenticate(struct connectdata *conn, - const char *resp); -static void imap_get_message(char *buffer, char **outptr); + 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", /* scheme */ imap_setup_connection, /* setup_connection */ imap_do, /* do_it */ imap_done, /* done */ @@ -127,9 +130,13 @@ const struct Curl_handler Curl_handler_imap = { ZERO_NULL, /* domore_getsock */ ZERO_NULL, /* perform_getsock */ imap_disconnect, /* disconnect */ - ZERO_NULL, /* readwrite */ + ZERO_NULL, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ + ZERO_NULL, /* connection_check */ + ZERO_NULL, /* attach connection */ PORT_IMAP, /* defport */ CURLPROTO_IMAP, /* protocol */ + CURLPROTO_IMAP, /* family */ PROTOPT_CLOSEACTION| /* flags */ PROTOPT_URLOPTIONS }; @@ -140,7 +147,7 @@ const struct Curl_handler Curl_handler_imap = { */ const struct Curl_handler Curl_handler_imaps = { - "IMAPS", /* scheme */ + "imaps", /* scheme */ imap_setup_connection, /* setup_connection */ imap_do, /* do_it */ imap_done, /* done */ @@ -153,74 +160,34 @@ const struct Curl_handler Curl_handler_imaps = { ZERO_NULL, /* domore_getsock */ ZERO_NULL, /* perform_getsock */ imap_disconnect, /* disconnect */ - ZERO_NULL, /* readwrite */ + ZERO_NULL, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ + ZERO_NULL, /* connection_check */ + ZERO_NULL, /* attach connection */ PORT_IMAPS, /* defport */ CURLPROTO_IMAPS, /* protocol */ - PROTOPT_CLOSEACTION | PROTOPT_SSL /* flags */ + CURLPROTO_IMAP, /* family */ + PROTOPT_CLOSEACTION | PROTOPT_SSL | /* flags */ + PROTOPT_URLOPTIONS }; #endif -#ifndef CURL_DISABLE_HTTP -/* - * HTTP-proxyed IMAP protocol handler. - */ - -static const struct Curl_handler Curl_handler_imap_proxy = { - "IMAP", /* scheme */ - Curl_http_setup_conn, /* setup_connection */ - Curl_http, /* do_it */ - Curl_http_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 */ - PORT_IMAP, /* defport */ - CURLPROTO_HTTP, /* protocol */ - PROTOPT_NONE /* flags */ -}; - -#ifdef USE_SSL -/* - * HTTP-proxyed IMAPS protocol handler. - */ - -static const struct Curl_handler Curl_handler_imaps_proxy = { - "IMAPS", /* scheme */ - Curl_http_setup_conn, /* setup_connection */ - Curl_http, /* do_it */ - Curl_http_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 */ - PORT_IMAPS, /* defport */ - CURLPROTO_HTTP, /* protocol */ - PROTOPT_NONE /* flags */ -}; -#endif -#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 */ - '+', /* Code received when continuation is expected */ - 'O', /* Code to receive upon authentication success */ - 0, /* Maximum initial response length (no max) */ imap_perform_authenticate, /* Send authentication command */ imap_continue_authenticate, /* Send authentication continuation */ - imap_get_message /* Get SASL response message */ + 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 */ }; @@ -231,7 +198,7 @@ static void imap_to_imaps(struct connectdata *conn) conn->handler = &Curl_handler_imaps; /* Set the connection's upgraded to TLS flag */ - conn->tls_upgraded = TRUE; + conn->bits.tls_upgraded = TRUE; } #else #define imap_to_imaps(x) Curl_nop_stmt @@ -285,10 +252,10 @@ static bool imap_matchresp(const char *line, size_t len, const char *cmd) * 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 connectdata *conn, char *line, size_t len, - int *resp) +static bool imap_endofresp(struct Curl_easy *data, struct connectdata *conn, + char *line, size_t len, int *resp) { - struct IMAP *imap = conn->data->req.protop; + 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); @@ -299,15 +266,11 @@ static bool imap_endofresp(struct connectdata *conn, char *line, size_t len, len -= id_len + 1; if(len >= 2 && !memcmp(line, "OK", 2)) - *resp = 'O'; - else if(len >= 2 && !memcmp(line, "NO", 2)) - *resp = 'N'; - else if(len >= 3 && !memcmp(line, "BAD", 3)) - *resp = 'B'; - else { - failf(conn->data, "Bad tagged response"); - *resp = -1; - } + *resp = IMAP_RESP_OK; + else if(len >= 7 && !memcmp(line, "PREAUTH", 7)) + *resp = IMAP_RESP_PREAUTH; + else + *resp = IMAP_RESP_NOT_OK; return TRUE; } @@ -324,15 +287,16 @@ static bool imap_endofresp(struct connectdata *conn, char *line, size_t len, case IMAP_LIST: if((!imap->custom && !imap_matchresp(line, len, "LIST")) || (imap->custom && !imap_matchresp(line, len, imap->custom) && - (strcmp(imap->custom, "STORE") || + (!strcasecompare(imap->custom, "STORE") || !imap_matchresp(line, len, "FETCH")) && - strcmp(imap->custom, "SELECT") && - strcmp(imap->custom, "EXAMINE") && - strcmp(imap->custom, "SEARCH") && - strcmp(imap->custom, "EXPUNGE") && - strcmp(imap->custom, "LSUB") && - strcmp(imap->custom, "UID") && - strcmp(imap->custom, "NOOP"))) + !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; @@ -363,8 +327,8 @@ static bool imap_endofresp(struct connectdata *conn, char *line, size_t len, /* 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 e-mail servers ignore this and only send a single + instead. */ - if(imap && !imap->custom && ((len == 3 && !memcmp("+", line, 1)) || + 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 */ @@ -374,7 +338,7 @@ static bool imap_endofresp(struct connectdata *conn, char *line, size_t len, break; default: - failf(conn->data, "Unexpected continuation response"); + failf(data, "Unexpected continuation response"); *resp = -1; break; } @@ -391,38 +355,43 @@ static bool imap_endofresp(struct connectdata *conn, char *line, size_t len, * * Gets the authentication message from the response buffer. */ -static void imap_get_message(char *buffer, char **outptr) +static CURLcode imap_get_message(struct Curl_easy *data, struct bufref *out) { - size_t len = 0; - char *message = NULL; - - /* Find the start of the message */ - for(message = buffer + 2; *message == ' ' || *message == '\t'; message++) - ; - - /* Find the end of the message */ - for(len = strlen(message); len--;) - if(message[len] != '\r' && message[len] != '\n' && message[len] != ' ' && - message[len] != '\t') - break; + char *message = Curl_dyn_ptr(&data->conn->proto.imapc.pp.recvbuf); + size_t len = data->conn->proto.imapc.pp.nfinal; + + 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 */ - if(++len) { - message[len] = '\0'; + /* 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); - *outptr = message; + return CURLE_OK; } /*********************************************************************** * - * state() + * imap_state() * * This is the ONLY way to change IMAP state! */ -static void state(struct connectdata *conn, imapstate newstate) +static void imap_state(struct Curl_easy *data, imapstate newstate) { - struct imap_conn *imapc = &conn->proto.imapc; + struct imap_conn *imapc = &data->conn->proto.imapc; #if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS) /* for debug purposes */ static const char * const names[]={ @@ -445,7 +414,7 @@ static void state(struct connectdata *conn, imapstate newstate) }; if(imapc->state != newstate) - infof(conn->data, "IMAP %p state change from %s to %s\n", + infof(data, "IMAP %p state change from %s to %s", (void *)imapc, names[imapc->state], names[newstate]); #endif @@ -459,20 +428,20 @@ static void state(struct connectdata *conn, imapstate newstate) * Sends the CAPABILITY command in order to obtain a list of server side * supported capabilities. */ -static CURLcode imap_perform_capability(struct connectdata *conn) +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(conn, "CAPABILITY"); + result = imap_sendf(data, "CAPABILITY"); if(!result) - state(conn, IMAP_CAPABILITY); + imap_state(data, IMAP_CAPABILITY); return result; } @@ -483,15 +452,13 @@ static CURLcode imap_perform_capability(struct connectdata *conn) * * Sends the STARTTLS command to start the upgrade to TLS. */ -static CURLcode imap_perform_starttls(struct connectdata *conn) +static CURLcode imap_perform_starttls(struct Curl_easy *data) { - CURLcode result = CURLE_OK; - /* Send the STARTTLS command */ - result = imap_sendf(conn, "STARTTLS"); + CURLcode result = imap_sendf(data, "STARTTLS"); if(!result) - state(conn, IMAP_STARTTLS); + imap_state(data, IMAP_STARTTLS); return result; } @@ -502,24 +469,32 @@ static CURLcode imap_perform_starttls(struct connectdata *conn) * * Performs the upgrade to TLS. */ -static CURLcode imap_perform_upgrade_tls(struct connectdata *conn) +static CURLcode imap_perform_upgrade_tls(struct Curl_easy *data, + struct connectdata *conn) { - CURLcode result = CURLE_OK; + /* Start the SSL connection */ struct imap_conn *imapc = &conn->proto.imapc; + CURLcode result; + bool ssldone = FALSE; - /* Start the SSL connection */ - result = Curl_ssl_connect_nonblocking(conn, FIRSTSOCKET, &imapc->ssldone); + 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) - state(conn, IMAP_UPGRADETLS); + imap_state(data, IMAP_UPGRADETLS); if(imapc->ssldone) { imap_to_imaps(conn); - result = imap_perform_capability(conn); + result = imap_perform_capability(data, conn); } } - +out: return result; } @@ -529,16 +504,17 @@ static CURLcode imap_perform_upgrade_tls(struct connectdata *conn) * * Sends a clear text LOGIN command to authenticate with. */ -static CURLcode imap_perform_login(struct connectdata *conn) +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(!conn->bits.user_passwd) { - state(conn, IMAP_STOP); + connect phase if we do not */ + if(!data->state.aptr.user) { + imap_state(data, IMAP_STOP); return result; } @@ -548,14 +524,14 @@ static CURLcode imap_perform_login(struct connectdata *conn) passwd = imap_atom(conn->passwd, false); /* Send the LOGIN command */ - result = imap_sendf(conn, "LOGIN %s %s", user ? user : "", + result = imap_sendf(data, "LOGIN %s %s", user ? user : "", passwd ? passwd : ""); free(user); free(passwd); if(!result) - state(conn, IMAP_LOGIN); + imap_state(data, IMAP_LOGIN); return result; } @@ -567,19 +543,20 @@ static CURLcode imap_perform_login(struct connectdata *conn) * Sends an AUTHENTICATE command allowing the client to login with the given * SASL authentication mechanism. */ -static CURLcode imap_perform_authenticate(struct connectdata *conn, +static CURLcode imap_perform_authenticate(struct Curl_easy *data, const char *mech, - const char *initresp) + const struct bufref *initresp) { CURLcode result = CURLE_OK; + const char *ir = (const char *) Curl_bufref_ptr(initresp); - if(initresp) { + if(ir) { /* Send the AUTHENTICATE command with the initial response */ - result = imap_sendf(conn, "AUTHENTICATE %s %s", mech, initresp); + result = imap_sendf(data, "AUTHENTICATE %s %s", mech, ir); } else { /* Send the AUTHENTICATE command */ - result = imap_sendf(conn, "AUTHENTICATE %s", mech); + result = imap_sendf(data, "AUTHENTICATE %s", mech); } return result; @@ -589,14 +566,34 @@ static CURLcode imap_perform_authenticate(struct connectdata *conn, * * imap_continue_authenticate() * - * Sends SASL continuation data or cancellation. + * Sends SASL continuation data. */ -static CURLcode imap_continue_authenticate(struct connectdata *conn, - const char *resp) +static CURLcode imap_continue_authenticate(struct Curl_easy *data, + const char *mech, + const struct bufref *resp) { - struct imap_conn *imapc = &conn->proto.imapc; + struct imap_conn *imapc = &data->conn->proto.imapc; - return Curl_pp_sendf(&imapc->pp, "%s", resp); + (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, "*"); } /*********************************************************************** @@ -607,31 +604,33 @@ static CURLcode imap_continue_authenticate(struct connectdata *conn, * 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 connectdata *conn) +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 we have enough data to authenticate with and end the - connect phase if we don't */ - if(!Curl_sasl_can_authenticate(&imapc->sasl, conn)) { - state(conn, IMAP_STOP); + /* Check if already authenticated OR if there is enough data to authenticate + with and end the connect phase if we do not */ + 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, conn, imapc->ir_supported, &progress); + result = Curl_sasl_start(&imapc->sasl, data, imapc->ir_supported, &progress); if(!result) { if(progress == SASL_INPROGRESS) - state(conn, IMAP_AUTHENTICATE); + imap_state(data, IMAP_AUTHENTICATE); else if(!imapc->login_disabled && (imapc->preftype & IMAP_TYPE_CLEARTEXT)) /* Perform clear text authentication */ - result = imap_perform_login(conn); + result = imap_perform_login(data, conn); else { /* Other mechanisms not supported */ - infof(conn->data, "No known authentication mechanisms supported!\n"); + infof(data, "No known authentication mechanisms supported"); result = CURLE_LOGIN_DENIED; } } @@ -645,31 +644,30 @@ static CURLcode imap_perform_authentication(struct connectdata *conn) * * Sends a LIST command or an alternative custom request. */ -static CURLcode imap_perform_list(struct connectdata *conn) +static CURLcode imap_perform_list(struct Curl_easy *data) { CURLcode result = CURLE_OK; - struct Curl_easy *data = conn->data; - struct IMAP *imap = data->req.protop; - char *mailbox; + struct IMAP *imap = data->req.p.imap; if(imap->custom) /* Send the custom request */ - result = imap_sendf(conn, "%s%s", imap->custom, + 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 */ - mailbox = imap->mailbox ? imap_atom(imap->mailbox, true) : strdup(""); + char *mailbox = imap->mailbox ? imap_atom(imap->mailbox, true) + : strdup(""); if(!mailbox) return CURLE_OUT_OF_MEMORY; /* Send the LIST command */ - result = imap_sendf(conn, "LIST \"%s\" *", mailbox); + result = imap_sendf(data, "LIST \"%s\" *", mailbox); free(mailbox); } if(!result) - state(conn, IMAP_LIST); + imap_state(data, IMAP_LIST); return result; } @@ -680,11 +678,11 @@ static CURLcode imap_perform_list(struct connectdata *conn) * * Sends a SELECT command to ask the server to change the selected mailbox. */ -static CURLcode imap_perform_select(struct connectdata *conn) +static CURLcode imap_perform_select(struct Curl_easy *data) { CURLcode result = CURLE_OK; - struct Curl_easy *data = conn->data; - struct IMAP *imap = data->req.protop; + struct connectdata *conn = data->conn; + struct IMAP *imap = data->req.p.imap; struct imap_conn *imapc = &conn->proto.imapc; char *mailbox; @@ -694,7 +692,7 @@ static CURLcode imap_perform_select(struct connectdata *conn) /* Check we have a mailbox */ if(!imap->mailbox) { - failf(conn->data, "Cannot SELECT without a mailbox."); + failf(data, "Cannot SELECT without a mailbox."); return CURLE_URL_MALFORMAT; } @@ -704,12 +702,12 @@ static CURLcode imap_perform_select(struct connectdata *conn) return CURLE_OUT_OF_MEMORY; /* Send the SELECT command */ - result = imap_sendf(conn, "SELECT %s", mailbox); + result = imap_sendf(data, "SELECT %s", mailbox); free(mailbox); if(!result) - state(conn, IMAP_SELECT); + imap_state(data, IMAP_SELECT); return result; } @@ -720,30 +718,38 @@ static CURLcode imap_perform_select(struct connectdata *conn) * * Sends a FETCH command to initiate the download of a message. */ -static CURLcode imap_perform_fetch(struct connectdata *conn) +static CURLcode imap_perform_fetch(struct Curl_easy *data) { CURLcode result = CURLE_OK; - struct IMAP *imap = conn->data->req.protop; - + struct IMAP *imap = data->req.p.imap; /* Check we have a UID */ - if(!imap->uid) { - failf(conn->data, "Cannot FETCH without 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; } - - /* Send the FETCH command */ - if(imap->partial) - result = imap_sendf(conn, "FETCH %s BODY[%s]<%s>", - imap->uid, - imap->section ? imap->section : "", - imap->partial); - else - result = imap_sendf(conn, "FETCH %s BODY[%s]", - imap->uid, - imap->section ? imap->section : ""); - if(!result) - state(conn, IMAP_FETCH); + imap_state(data, IMAP_FETCH); return result; } @@ -754,21 +760,51 @@ static CURLcode imap_perform_fetch(struct connectdata *conn) * * Sends an APPEND command to initiate the upload of a message. */ -static CURLcode imap_perform_append(struct connectdata *conn) +static CURLcode imap_perform_append(struct Curl_easy *data) { CURLcode result = CURLE_OK; - struct IMAP *imap = conn->data->req.protop; + struct IMAP *imap = data->req.p.imap; char *mailbox; /* Check we have a mailbox */ if(!imap->mailbox) { - failf(conn->data, "Cannot APPEND without a mailbox."); + failf(data, "Cannot APPEND without a mailbox."); return CURLE_URL_MALFORMAT; } +#ifndef CURL_DISABLE_MIME + /* Prepare the mime data if some. */ + if(data->set.mimepost.kind != MIMEKIND_NONE) { + /* Use the whole structure as data. */ + data->set.mimepost.flags &= ~(unsigned int)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"); + + if(!result) + result = Curl_creader_set_mime(data, &data->set.mimepost); + if(result) + return result; + data->state.infilesize = Curl_creader_client_length(data); + } + else +#endif + { + result = Curl_creader_set_fread(data, data->state.infilesize); + if(result) + return result; + } + /* Check we know the size of the upload */ - if(conn->data->state.infilesize < 0) { - failf(conn->data, "Cannot APPEND with unknown input file size\n"); + if(data->state.infilesize < 0) { + failf(data, "Cannot APPEND with unknown input file size"); return CURLE_UPLOAD_FAILED; } @@ -778,13 +814,13 @@ static CURLcode imap_perform_append(struct connectdata *conn) return CURLE_OUT_OF_MEMORY; /* Send the APPEND command */ - result = imap_sendf(conn, "APPEND %s (\\Seen) {%" CURL_FORMAT_CURL_OFF_T "}", - mailbox, conn->data->state.infilesize); + result = imap_sendf(data, "APPEND %s (\\Seen) {%" FMT_OFF_T "}", + mailbox, data->state.infilesize); free(mailbox); if(!result) - state(conn, IMAP_APPEND); + imap_state(data, IMAP_APPEND); return result; } @@ -795,22 +831,22 @@ static CURLcode imap_perform_append(struct connectdata *conn) * * Sends a SEARCH command. */ -static CURLcode imap_perform_search(struct connectdata *conn) +static CURLcode imap_perform_search(struct Curl_easy *data) { CURLcode result = CURLE_OK; - struct IMAP *imap = conn->data->req.protop; + struct IMAP *imap = data->req.p.imap; /* Check we have a query string */ if(!imap->query) { - failf(conn->data, "Cannot SEARCH without a query string."); + failf(data, "Cannot SEARCH without a query string."); return CURLE_URL_MALFORMAT; } /* Send the SEARCH command */ - result = imap_sendf(conn, "SEARCH %s", imap->query); + result = imap_sendf(data, "SEARCH %s", imap->query); if(!result) - state(conn, IMAP_SEARCH); + imap_state(data, IMAP_SEARCH); return result; } @@ -821,49 +857,48 @@ static CURLcode imap_perform_search(struct connectdata *conn) * * Performs the logout action prior to sclose() being called. */ -static CURLcode imap_perform_logout(struct connectdata *conn) +static CURLcode imap_perform_logout(struct Curl_easy *data) { - CURLcode result = CURLE_OK; - /* Send the LOGOUT command */ - result = imap_sendf(conn, "LOGOUT"); + CURLcode result = imap_sendf(data, "LOGOUT"); if(!result) - state(conn, IMAP_LOGOUT); + imap_state(data, IMAP_LOGOUT); return result; } /* For the initial server greeting */ -static CURLcode imap_state_servergreet_resp(struct connectdata *conn, +static CURLcode imap_state_servergreet_resp(struct Curl_easy *data, int imapcode, imapstate instate) { - CURLcode result = CURLE_OK; - struct Curl_easy *data = conn->data; - + struct connectdata *conn = data->conn; (void)instate; /* no use for this yet */ - if(imapcode != 'O') { + 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"); - result = CURLE_WEIRD_SERVER_REPLY; + return CURLE_WEIRD_SERVER_REPLY; } - else - result = imap_perform_capability(conn); - return result; + return imap_perform_capability(data, conn); } /* For CAPABILITY responses */ -static CURLcode imap_state_capability_resp(struct connectdata *conn, +static CURLcode imap_state_capability_resp(struct Curl_easy *data, int imapcode, imapstate instate) { CURLcode result = CURLE_OK; - struct Curl_easy *data = conn->data; + struct connectdata *conn = data->conn; struct imap_conn *imapc = &conn->proto.imapc; - const char *line = data->state.buffer; - size_t wordlen; + const char *line = Curl_dyn_ptr(&imapc->pp.recvbuf); (void)instate; /* no use for this yet */ @@ -873,6 +908,7 @@ static CURLcode imap_state_capability_resp(struct connectdata *conn, /* Loop through the data line */ for(;;) { + size_t wordlen; while(*line && (*line == ' ' || *line == '\t' || *line == '\r' || *line == '\n')) { @@ -904,7 +940,7 @@ static CURLcode imap_state_capability_resp(struct connectdata *conn, /* Do we have a SASL based authentication mechanism? */ else if(wordlen > 5 && !memcmp(line, "AUTH=", 5)) { size_t llen; - unsigned int mechbit; + unsigned short mechbit; line += 5; wordlen -= 5; @@ -918,75 +954,75 @@ static CURLcode imap_state_capability_resp(struct connectdata *conn, line += wordlen; } } - else if(imapcode == 'O') { - if(data->set.use_ssl && !conn->ssl[FIRSTSOCKET].use) { - /* We don't have a SSL/TLS connection yet, but SSL is requested */ - if(imapc->tls_supported) - /* Switch to TLS connection now */ - result = imap_perform_starttls(conn); - else if(data->set.use_ssl == CURLUSESSL_TRY) - /* Fallback and carry on with authentication */ - result = imap_perform_authentication(conn); - else { - failf(data, "STARTTLS not supported."); - result = CURLE_USE_SSL_FAILED; - } + 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(conn); } else - result = imap_perform_authentication(conn); + result = imap_perform_authentication(data, conn); return result; } /* For STARTTLS responses */ -static CURLcode imap_state_starttls_resp(struct connectdata *conn, +static CURLcode imap_state_starttls_resp(struct Curl_easy *data, int imapcode, imapstate instate) { CURLcode result = CURLE_OK; - struct Curl_easy *data = conn->data; + struct connectdata *conn = data->conn; (void)instate; /* no use for this yet */ - if(imapcode != 'O') { + /* Pipelining in response is forbidden. */ + if(data->conn->proto.imapc.pp.overflow) + 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(conn); + result = imap_perform_authentication(data, conn); } else - result = imap_perform_upgrade_tls(conn); + result = imap_perform_upgrade_tls(data, conn); return result; } /* For SASL authentication responses */ -static CURLcode imap_state_auth_resp(struct connectdata *conn, +static CURLcode imap_state_auth_resp(struct Curl_easy *data, + struct connectdata *conn, int imapcode, imapstate instate) { CURLcode result = CURLE_OK; - struct Curl_easy *data = conn->data; struct imap_conn *imapc = &conn->proto.imapc; saslprogress progress; (void)instate; /* no use for this yet */ - result = Curl_sasl_continue(&imapc->sasl, conn, imapcode, &progress); + result = Curl_sasl_continue(&imapc->sasl, data, imapcode, &progress); if(!result) switch(progress) { case SASL_DONE: - state(conn, IMAP_STOP); /* Authenticated */ + 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(conn); + result = imap_perform_login(data, conn); else { failf(data, "Authentication cancelled"); result = CURLE_LOGIN_DENIED; @@ -1000,89 +1036,95 @@ static CURLcode imap_state_auth_resp(struct connectdata *conn, } /* For LOGIN responses */ -static CURLcode imap_state_login_resp(struct connectdata *conn, +static CURLcode imap_state_login_resp(struct Curl_easy *data, int imapcode, imapstate instate) { CURLcode result = CURLE_OK; - struct Curl_easy *data = conn->data; - (void)instate; /* no use for this yet */ - if(imapcode != 'O') { + if(imapcode != IMAP_RESP_OK) { failf(data, "Access denied. %c", imapcode); result = CURLE_LOGIN_DENIED; } else /* End of connect phase */ - state(conn, IMAP_STOP); + imap_state(data, IMAP_STOP); return result; } /* For LIST and SEARCH responses */ -static CURLcode imap_state_listsearch_resp(struct connectdata *conn, +static CURLcode imap_state_listsearch_resp(struct Curl_easy *data, int imapcode, imapstate instate) { CURLcode result = CURLE_OK; - char *line = conn->data->state.buffer; - size_t len = strlen(line); + char *line = Curl_dyn_ptr(&data->conn->proto.imapc.pp.recvbuf); + size_t len = data->conn->proto.imapc.pp.nfinal; (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(conn, CLIENTWRITE_BODY, line, len + 1); - line[len] = '\0'; - } - else if(imapcode != 'O') - result = CURLE_QUOTE_ERROR; /* TODO: Fix error code */ + if(imapcode == '*') + result = Curl_client_write(data, CLIENTWRITE_BODY, line, len); + else if(imapcode != IMAP_RESP_OK) + result = CURLE_QUOTE_ERROR; else /* End of DO phase */ - state(conn, IMAP_STOP); + imap_state(data, IMAP_STOP); return result; } /* For SELECT responses */ -static CURLcode imap_state_select_resp(struct connectdata *conn, int imapcode, +static CURLcode imap_state_select_resp(struct Curl_easy *data, int imapcode, imapstate instate) { CURLcode result = CURLE_OK; - struct Curl_easy *data = conn->data; - struct IMAP *imap = conn->data->req.protop; + 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; - char tmp[20]; + const char *line = Curl_dyn_ptr(&data->conn->proto.imapc.pp.recvbuf); (void)instate; /* no use for this yet */ if(imapcode == '*') { /* See if this is an UIDVALIDITY response */ - if(sscanf(line + 2, "OK [UIDVALIDITY %19[0123456789]]", tmp) == 1) { - Curl_safefree(imapc->mailbox_uidvalidity); - imapc->mailbox_uidvalidity = strdup(tmp); + 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 == 'O') { + else if(imapcode == IMAP_RESP_OK) { /* Check if the UIDVALIDITY has been specified and matches */ if(imap->uidvalidity && imapc->mailbox_uidvalidity && - strcmp(imap->uidvalidity, imapc->mailbox_uidvalidity)) { - failf(conn->data, "Mailbox UIDVALIDITY has changed"); + !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(conn); + result = imap_perform_list(data); else if(imap->query) - result = imap_perform_search(conn); + result = imap_perform_search(data); else - result = imap_perform_fetch(conn); + result = imap_perform_fetch(data); } } else { @@ -1094,14 +1136,15 @@ static CURLcode imap_state_select_resp(struct connectdata *conn, int imapcode, } /* For the (first line of the) FETCH responses */ -static CURLcode imap_state_fetch_resp(struct connectdata *conn, int imapcode, +static CURLcode imap_state_fetch_resp(struct Curl_easy *data, + struct connectdata *conn, int imapcode, imapstate instate) { CURLcode result = CURLE_OK; - struct Curl_easy *data = conn->data; struct imap_conn *imapc = &conn->proto.imapc; struct pingpong *pp = &imapc->pp; - const char *ptr = data->state.buffer; + const char *ptr = Curl_dyn_ptr(&data->conn->proto.imapc.pp.recvbuf); + size_t len = data->conn->proto.imapc.pp.nfinal; bool parsed = FALSE; curl_off_t size = 0; @@ -1109,87 +1152,90 @@ static CURLcode imap_state_fetch_resp(struct connectdata *conn, int imapcode, if(imapcode != '*') { Curl_pgrsSetDownloadSize(data, -1); - state(conn, IMAP_STOP); - return CURLE_REMOTE_FILE_NOT_FOUND; /* TODO: Fix error code */ + 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 == '{') { + ptr = memchr(ptr, '{', len); + if(ptr) { char *endptr; - size = curlx_strtoofft(ptr + 1, &endptr, 10); - if(endptr - ptr > 1 && endptr[0] == '}' && - endptr[1] == '\r' && endptr[2] == '\0') + if(!curlx_strtoofft(ptr + 1, &endptr, 10, &size) && + (endptr - ptr > 1 && *endptr == '}')) parsed = TRUE; } if(parsed) { - infof(data, "Found %" CURL_FORMAT_CURL_OFF_TU " bytes to download\n", - size); + infof(data, "Found %" FMT_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(pp->overflow) { + /* At this point there is a data in the receive buffer that is 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->overflow; + + /* keep only the overflow */ + Curl_dyn_tail(&pp->recvbuf, chunk); + pp->nfinal = 0; /* done */ if(chunk > (size_t)size) /* The conversion from curl_off_t to size_t is always fine here */ chunk = (size_t)size; - result = Curl_client_write(conn, CLIENTWRITE_BODY, pp->cache, chunk); + if(!chunk) { + /* no size, we are done with the data */ + imap_state(data, IMAP_STOP); + return CURLE_OK; + } + result = Curl_client_write(data, CLIENTWRITE_BODY, + Curl_dyn_ptr(&pp->recvbuf), chunk); if(result) return result; - data->req.bytecount += chunk; - - infof(data, "Written %" CURL_FORMAT_CURL_OFF_TU - " bytes, %" CURL_FORMAT_CURL_OFF_TU - " bytes are left for transfer\n", (curl_off_t)chunk, - size - chunk); + infof(data, "Written %zu bytes, %" FMT_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; + /* Have we used the entire overflow or just part of it?*/ + if(pp->overflow > chunk) { + /* remember the remaining trailing overflow data */ + pp->overflow -= chunk; + Curl_dyn_tail(&pp->recvbuf, pp->overflow); } else { + pp->overflow = 0; /* handled */ /* Free the cache */ - Curl_safefree(pp->cache); - - /* Reset the cache size */ - pp->cache_size = 0; + Curl_dyn_reset(&pp->recvbuf); } } if(data->req.bytecount == size) /* The entire data is already transferred! */ - Curl_setup_transfer(conn, -1, -1, FALSE, NULL, -1, NULL); + Curl_xfer_setup_nop(data); else { /* IMAP download */ data->req.maxdownload = size; - Curl_setup_transfer(conn, FIRSTSOCKET, size, FALSE, NULL, -1, NULL); + /* force a recv/send check of this connection, as the data might've been + read off the socket already */ + data->state.select_bits = CURL_CSELECT_IN; + Curl_xfer_setup1(data, CURL_XFER_RECV, size, FALSE); } } else { - /* We don't know how to parse this line */ - failf(pp->conn->data, "Failed to parse FETCH response."); + /* We do not know how to parse this line */ + failf(data, "Failed to parse FETCH response."); result = CURLE_WEIRD_SERVER_REPLY; } /* End of DO phase */ - state(conn, IMAP_STOP); + imap_state(data, IMAP_STOP); return result; } /* For final FETCH responses performed after the download */ -static CURLcode imap_state_fetch_final_resp(struct connectdata *conn, +static CURLcode imap_state_fetch_final_resp(struct Curl_easy *data, int imapcode, imapstate instate) { @@ -1197,22 +1243,20 @@ static CURLcode imap_state_fetch_final_resp(struct connectdata *conn, (void)instate; /* No use for this yet */ - if(imapcode != 'O') + if(imapcode != IMAP_RESP_OK) result = CURLE_WEIRD_SERVER_REPLY; else /* End of DONE phase */ - state(conn, IMAP_STOP); + imap_state(data, IMAP_STOP); return result; } /* For APPEND responses */ -static CURLcode imap_state_append_resp(struct connectdata *conn, int imapcode, +static CURLcode imap_state_append_resp(struct Curl_easy *data, int imapcode, imapstate instate) { CURLcode result = CURLE_OK; - struct Curl_easy *data = conn->data; - (void)instate; /* No use for this yet */ if(imapcode != '+') { @@ -1223,17 +1267,17 @@ static CURLcode imap_state_append_resp(struct connectdata *conn, int imapcode, Curl_pgrsSetUploadSize(data, data->state.infilesize); /* IMAP upload */ - Curl_setup_transfer(conn, -1, -1, FALSE, NULL, FIRSTSOCKET, NULL); + Curl_xfer_setup1(data, CURL_XFER_SEND, -1, FALSE); /* End of DO phase */ - state(conn, IMAP_STOP); + imap_state(data, IMAP_STOP); } return result; } /* For final APPEND responses performed after the upload */ -static CURLcode imap_state_append_final_resp(struct connectdata *conn, +static CURLcode imap_state_append_final_resp(struct Curl_easy *data, int imapcode, imapstate instate) { @@ -1241,35 +1285,36 @@ static CURLcode imap_state_append_final_resp(struct connectdata *conn, (void)instate; /* No use for this yet */ - if(imapcode != 'O') + if(imapcode != IMAP_RESP_OK) result = CURLE_UPLOAD_FAILED; else /* End of DONE phase */ - state(conn, IMAP_STOP); + imap_state(data, IMAP_STOP); return result; } -static CURLcode imap_statemach_act(struct connectdata *conn) +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(conn); + return imap_perform_upgrade_tls(data, conn); /* Flush any data that needs to be sent */ if(pp->sendleft) - return Curl_pp_flushsend(pp); + return Curl_pp_flushsend(data, pp); do { /* Read the response from the server */ - result = Curl_pp_readresp(sock, pp, &imapcode, &nread); + result = Curl_pp_readresp(data, FIRSTSOCKET, pp, &imapcode, &nread); if(result) return result; @@ -1283,58 +1328,54 @@ static CURLcode imap_statemach_act(struct connectdata *conn) /* We have now received a full IMAP server response */ switch(imapc->state) { case IMAP_SERVERGREET: - result = imap_state_servergreet_resp(conn, imapcode, imapc->state); + result = imap_state_servergreet_resp(data, imapcode, imapc->state); break; case IMAP_CAPABILITY: - result = imap_state_capability_resp(conn, imapcode, imapc->state); + result = imap_state_capability_resp(data, imapcode, imapc->state); break; case IMAP_STARTTLS: - result = imap_state_starttls_resp(conn, imapcode, imapc->state); + result = imap_state_starttls_resp(data, imapcode, imapc->state); break; case IMAP_AUTHENTICATE: - result = imap_state_auth_resp(conn, imapcode, imapc->state); + result = imap_state_auth_resp(data, conn, imapcode, imapc->state); break; case IMAP_LOGIN: - result = imap_state_login_resp(conn, imapcode, imapc->state); + result = imap_state_login_resp(data, imapcode, imapc->state); break; case IMAP_LIST: - result = imap_state_listsearch_resp(conn, imapcode, imapc->state); + case IMAP_SEARCH: + result = imap_state_listsearch_resp(data, imapcode, imapc->state); break; case IMAP_SELECT: - result = imap_state_select_resp(conn, imapcode, imapc->state); + result = imap_state_select_resp(data, imapcode, imapc->state); break; case IMAP_FETCH: - result = imap_state_fetch_resp(conn, imapcode, imapc->state); + result = imap_state_fetch_resp(data, conn, imapcode, imapc->state); break; case IMAP_FETCH_FINAL: - result = imap_state_fetch_final_resp(conn, imapcode, imapc->state); + result = imap_state_fetch_final_resp(data, imapcode, imapc->state); break; case IMAP_APPEND: - result = imap_state_append_resp(conn, imapcode, imapc->state); + result = imap_state_append_resp(data, imapcode, imapc->state); break; case IMAP_APPEND_FINAL: - result = imap_state_append_final_resp(conn, imapcode, imapc->state); - break; - - case IMAP_SEARCH: - result = imap_state_listsearch_resp(conn, imapcode, imapc->state); + result = imap_state_append_final_resp(data, imapcode, imapc->state); break; case IMAP_LOGOUT: - /* fallthrough, just stop! */ default: /* internal error */ - state(conn, IMAP_STOP); + imap_state(data, IMAP_STOP); break; } } while(!result && imapc->state != IMAP_STOP && Curl_pp_moredata(pp)); @@ -1343,43 +1384,47 @@ static CURLcode imap_statemach_act(struct connectdata *conn) } /* Called repeatedly until done from multi.c */ -static CURLcode imap_multi_statemach(struct connectdata *conn, bool *done) +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) { - result = Curl_ssl_connect_nonblocking(conn, FIRSTSOCKET, &imapc->ssldone); - if(result || !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(&imapc->pp, FALSE); + result = Curl_pp_statemach(data, &imapc->pp, FALSE, FALSE); *done = (imapc->state == IMAP_STOP) ? TRUE : FALSE; return result; } -static CURLcode imap_block_statemach(struct connectdata *conn) +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(&imapc->pp, TRUE); + 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 connectdata *conn) +static CURLcode imap_init(struct Curl_easy *data) { CURLcode result = CURLE_OK; - struct Curl_easy *data = conn->data; struct IMAP *imap; - imap = data->req.protop = calloc(sizeof(struct IMAP), 1); + imap = data->req.p.imap = calloc(1, sizeof(struct IMAP)); if(!imap) result = CURLE_OUT_OF_MEMORY; @@ -1387,10 +1432,11 @@ static CURLcode imap_init(struct connectdata *conn) } /* For the IMAP "protocol connect" and "doing" phases only */ -static int imap_getsock(struct connectdata *conn, curl_socket_t *socks, - int numsocks) +static int imap_getsock(struct Curl_easy *data, + struct connectdata *conn, + curl_socket_t *socks) { - return Curl_pp_getsock(&conn->proto.imapc.pp, socks, numsocks); + return Curl_pp_getsock(data, &conn->proto.imapc.pp, socks); } /*********************************************************************** @@ -1403,9 +1449,10 @@ static int imap_getsock(struct connectdata *conn, curl_socket_t *socks, * 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 connectdata *conn, bool *done) +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; @@ -1414,17 +1461,13 @@ static CURLcode imap_connect(struct connectdata *conn, bool *done) /* We always support persistent connections in IMAP */ connkeep(conn, "IMAP default"); - /* Set the default response time-out */ - pp->response_time = RESP_TIMEOUT; - pp->statemach_act = imap_statemach_act; - pp->endofresp = imap_endofresp; - pp->conn = conn; + 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, &saslimap); + Curl_sasl_init(&imapc->sasl, data, &saslimap); - /* Initialise the pingpong layer */ + Curl_dyn_init(&imapc->dyn, DYN_IMAP_CMD); Curl_pp_init(pp); /* Parse the URL options */ @@ -1433,12 +1476,12 @@ static CURLcode imap_connect(struct connectdata *conn, bool *done) return result; /* Start off waiting for the server greeting response */ - state(conn, IMAP_SERVERGREET); + imap_state(data, IMAP_SERVERGREET); /* Start off with an response id of '*' */ strcpy(imapc->resptag, "*"); - result = imap_multi_statemach(conn, done); + result = imap_multi_statemach(data, done); return result; } @@ -1452,12 +1495,12 @@ static CURLcode imap_connect(struct connectdata *conn, bool *done) * * Input argument is already checked for validity. */ -static CURLcode imap_done(struct connectdata *conn, CURLcode status, +static CURLcode imap_done(struct Curl_easy *data, CURLcode status, bool premature) { CURLcode result = CURLE_OK; - struct Curl_easy *data = conn->data; - struct IMAP *imap = data->req.protop; + struct connectdata *conn = data->conn; + struct IMAP *imap = data->req.p.imap; (void)premature; @@ -1469,31 +1512,29 @@ static CURLcode imap_done(struct connectdata *conn, CURLcode status, result = status; /* use the already set error code */ } else if(!data->set.connect_only && !imap->custom && - (imap->uid || data->set.upload)) { + (imap->uid || imap->mindex || data->state.upload || + IS_MIME_POST(data))) { /* Handle responses after FETCH or APPEND transfer has finished */ - if(!data->set.upload) - state(conn, IMAP_FETCH_FINAL); + + if(!data->state.upload && !IS_MIME_POST(data)) + imap_state(data, IMAP_FETCH_FINAL); else { /* End the APPEND command first by sending an empty line */ - result = Curl_pp_sendf(&conn->proto.imapc.pp, "%s", ""); + result = Curl_pp_sendf(data, &conn->proto.imapc.pp, "%s", ""); if(!result) - state(conn, IMAP_APPEND_FINAL); + imap_state(data, IMAP_APPEND_FINAL); } - /* Run the state-machine - - TODO: when the multi interface is used, this _really_ should be using - the imap_multi_statemach function but we have no general support for - non-blocking DONE operations! - */ + /* Run the state-machine */ if(!result) - result = imap_block_statemach(conn); + 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); @@ -1501,7 +1542,7 @@ static CURLcode imap_done(struct connectdata *conn, CURLcode status, Curl_safefree(imap->custom_params); /* Clear the transfer mode for the next request */ - imap->transfer = FTPTRANSFER_BODY; + imap->transfer = PPTRANSFER_BODY; return result; } @@ -1513,21 +1554,21 @@ static CURLcode imap_done(struct connectdata *conn, CURLcode status, * 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 connectdata *conn, bool *connected, +static CURLcode imap_perform(struct Curl_easy *data, bool *connected, bool *dophase_done) { /* This is IMAP and no proxy */ CURLcode result = CURLE_OK; - struct Curl_easy *data = conn->data; - struct IMAP *imap = data->req.protop; + struct connectdata *conn = data->conn; + struct IMAP *imap = data->req.p.imap; struct imap_conn *imapc = &conn->proto.imapc; bool selected = FALSE; - DEBUGF(infof(conn->data, "DO phase starts\n")); + DEBUGF(infof(data, "DO phase starts")); - if(conn->data->set.opt_no_body) { + if(data->req.no_body) { /* Requested no body means no transfer */ - imap->transfer = FTPTRANSFER_INFO; + imap->transfer = PPTRANSFER_INFO; } *dophase_done = FALSE; /* not done yet */ @@ -1535,42 +1576,42 @@ static CURLcode imap_perform(struct connectdata *conn, bool *connected, /* Determine if the requested mailbox (with the same UIDVALIDITY if set) has already been selected on this connection */ if(imap->mailbox && imapc->mailbox && - !strcmp(imap->mailbox, imapc->mailbox) && + strcasecompare(imap->mailbox, imapc->mailbox) && (!imap->uidvalidity || !imapc->mailbox_uidvalidity || - !strcmp(imap->uidvalidity, imapc->mailbox_uidvalidity))) + strcasecompare(imap->uidvalidity, imapc->mailbox_uidvalidity))) selected = TRUE; /* Start the first command in the DO phase */ - if(conn->data->set.upload) + if(data->state.upload || IS_MIME_POST(data)) /* APPEND can be executed directly */ - result = imap_perform_append(conn); + 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(conn); - else if(!imap->custom && selected && imap->uid) + result = imap_perform_list(data); + else if(!imap->custom && selected && (imap->uid || imap->mindex)) /* FETCH from the same mailbox */ - result = imap_perform_fetch(conn); + result = imap_perform_fetch(data); else if(!imap->custom && selected && imap->query) /* SEARCH the current mailbox */ - result = imap_perform_search(conn); + result = imap_perform_search(data); else if(imap->mailbox && !selected && - (imap->custom || imap->uid || imap->query)) + (imap->custom || imap->uid || imap->mindex || imap->query)) /* SELECT the mailbox */ - result = imap_perform_select(conn); + result = imap_perform_select(data); else /* LIST */ - result = imap_perform_list(conn); + result = imap_perform_list(data); if(result) return result; /* Run the state-machine */ - result = imap_multi_statemach(conn, dophase_done); + result = imap_multi_statemach(data, dophase_done); - *connected = conn->bits.tcpconnect[FIRSTSOCKET]; + *connected = Curl_conn_is_connected(conn, FIRSTSOCKET); if(*dophase_done) - DEBUGF(infof(conn->data, "DO phase is complete\n")); + DEBUGF(infof(data, "DO phase is complete")); return result; } @@ -1584,23 +1625,22 @@ static CURLcode imap_perform(struct connectdata *conn, bool *connected, * * The input argument is already checked for validity. */ -static CURLcode imap_do(struct connectdata *conn, bool *done) +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(conn); + result = imap_parse_url_path(data); if(result) return result; /* Parse the custom request */ - result = imap_parse_custom_request(conn); + result = imap_parse_custom_request(data); if(result) return result; - result = imap_regular_transfer(conn, done); + result = imap_regular_transfer(data, done); return result; } @@ -1612,9 +1652,11 @@ static CURLcode imap_do(struct connectdata *conn, bool *done) * Disconnect from an IMAP server. Cleanup protocol-specific per-connection * resources. BLOCKING. */ -static CURLcode imap_disconnect(struct connectdata *conn, bool dead_connection) +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 @@ -1622,12 +1664,14 @@ static CURLcode imap_disconnect(struct connectdata *conn, bool dead_connection) /* The IMAP session may or may not have been allocated/setup at this point! */ - if(!dead_connection && imapc->pp.conn && imapc->pp.conn->bits.protoconnstart) - if(!imap_perform_logout(conn)) - (void)imap_block_statemach(conn); /* ignore errors on LOGOUT */ + 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); @@ -1640,30 +1684,30 @@ static CURLcode imap_disconnect(struct connectdata *conn, bool dead_connection) } /* Call this when the DO phase has completed */ -static CURLcode imap_dophase_done(struct connectdata *conn, bool connected) +static CURLcode imap_dophase_done(struct Curl_easy *data, bool connected) { - struct IMAP *imap = conn->data->req.protop; + struct IMAP *imap = data->req.p.imap; (void)connected; - if(imap->transfer != FTPTRANSFER_BODY) + if(imap->transfer != PPTRANSFER_BODY) /* no data to transfer */ - Curl_setup_transfer(conn, -1, -1, FALSE, NULL, -1, NULL); + Curl_xfer_setup_nop(data); return CURLE_OK; } /* Called from multi.c while DOing */ -static CURLcode imap_doing(struct connectdata *conn, bool *dophase_done) +static CURLcode imap_doing(struct Curl_easy *data, bool *dophase_done) { - CURLcode result = imap_multi_statemach(conn, dophase_done); + CURLcode result = imap_multi_statemach(data, dophase_done); if(result) - DEBUGF(infof(conn->data, "DO phase failed\n")); + DEBUGF(infof(data, "DO phase failed")); else if(*dophase_done) { - result = imap_dophase_done(conn, FALSE /* not connected */); + result = imap_dophase_done(data, FALSE /* not connected */); - DEBUGF(infof(conn->data, "DO phase is complete\n")); + DEBUGF(infof(data, "DO phase is complete")); } return result; @@ -1678,12 +1722,11 @@ static CURLcode imap_doing(struct connectdata *conn, bool *dophase_done) * Performs all commands done before a regular transfer between a local and a * remote host. */ -static CURLcode imap_regular_transfer(struct connectdata *conn, +static CURLcode imap_regular_transfer(struct Curl_easy *data, bool *dophase_done) { CURLcode result = CURLE_OK; bool connected = FALSE; - struct Curl_easy *data = conn->data; /* Make sure size is unknown at this point */ data->req.size = -1; @@ -1695,52 +1738,25 @@ static CURLcode imap_regular_transfer(struct connectdata *conn, Curl_pgrsSetDownloadSize(data, -1); /* Carry out the perform */ - result = imap_perform(conn, &connected, dophase_done); + result = imap_perform(data, &connected, dophase_done); /* Perform post DO phase operations if necessary */ if(!result && *dophase_done) - result = imap_dophase_done(conn, connected); + result = imap_dophase_done(data, connected); return result; } -static CURLcode imap_setup_connection(struct connectdata *conn) +static CURLcode imap_setup_connection(struct Curl_easy *data, + struct connectdata *conn) { - struct Curl_easy *data = conn->data; - /* Initialise the IMAP layer */ - CURLcode result = imap_init(conn); + CURLcode result = imap_init(data); if(result) return result; /* Clear the TLS upgraded flag */ - conn->tls_upgraded = FALSE; - - /* Set up the proxy if necessary */ - if(conn->bits.httpproxy && !data->set.tunnel_thru_httpproxy) { - /* Unless we have asked to tunnel IMAP operations through the proxy, we - switch and use HTTP operations only */ -#ifndef CURL_DISABLE_HTTP - if(conn->handler == &Curl_handler_imap) - conn->handler = &Curl_handler_imap_proxy; - else { -#ifdef USE_SSL - conn->handler = &Curl_handler_imaps_proxy; -#else - failf(data, "IMAPS not supported!"); - return CURLE_UNSUPPORTED_PROTOCOL; -#endif - } - - /* set it up as an HTTP connection instead */ - return conn->handler->setup_connection(conn); -#else - failf(data, "IMAP over http proxy requires HTTP support built-in!"); - return CURLE_UNSUPPORTED_PROTOCOL; -#endif - } - - data->state.path++; /* don't include the initial slash */ + conn->bits.tls_upgraded = FALSE; return CURLE_OK; } @@ -1749,38 +1765,40 @@ static CURLcode imap_setup_connection(struct connectdata *conn) * * imap_sendf() * - * Sends the formated string as an IMAP command to the server. + * Sends the formatted string as an IMAP command to the server. * * Designed to never block. */ -static CURLcode imap_sendf(struct connectdata *conn, const char *fmt, ...) +static CURLcode imap_sendf(struct Curl_easy *data, const char *fmt, ...) { CURLcode result = CURLE_OK; - struct imap_conn *imapc = &conn->proto.imapc; - char *taggedfmt; - va_list ap; + struct imap_conn *imapc = &data->conn->proto.imapc; DEBUGASSERT(fmt); - /* Calculate the next command ID wrapping at 3 digits */ - imapc->cmdid = (imapc->cmdid + 1) % 1000; - /* Calculate the tag based on the connection ID and command ID */ - snprintf(imapc->resptag, sizeof(imapc->resptag), "%c%03d", - 'A' + curlx_sltosi(conn->connection_id % 26), imapc->cmdid); - - /* Prefix the format with the tag */ - taggedfmt = aprintf("%s %s", imapc->resptag, fmt); - if(!taggedfmt) - return CURLE_OUT_OF_MEMORY; + msnprintf(imapc->resptag, sizeof(imapc->resptag), "%c%03d", + 'A' + curlx_sltosi((long)(data->conn->connection_id % 26)), + ++imapc->cmdid); - /* Send the data with the tag */ - va_start(ap, fmt); - result = Curl_pp_vsendf(&imapc->pp, taggedfmt, ap); - va_end(ap); - - free(taggedfmt); + /* 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); +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wformat-nonliteral" +#endif + result = Curl_pp_vsendf(data, &imapc->pp, Curl_dyn_ptr(&imapc->dyn), ap); +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + va_end(ap); + } return result; } @@ -1796,79 +1814,37 @@ static CURLcode imap_sendf(struct connectdata *conn, const char *fmt, ...) */ static char *imap_atom(const char *str, bool escape_only) { - /* !checksrc! disable PARENBRACE 1 */ - const char atom_specials[] = "(){ %*]"; - const char *p1; - char *p2; - size_t backsp_count = 0; - size_t quote_count = 0; - bool others_exists = FALSE; - size_t newlen = 0; - char *newstr = NULL; + struct dynbuf line; + size_t nclean; + size_t len; if(!str) return NULL; - /* Look for "atom-specials", counting the backslash and quote characters as - these will need escapping */ - p1 = str; - while(*p1) { - if(*p1 == '\\') - backsp_count++; - else if(*p1 == '"') - quote_count++; - else if(!escape_only) { - const char *p3 = atom_specials; - - while(*p3 && !others_exists) { - if(*p1 == *p3) - others_exists = TRUE; - - p3++; - } - } - - p1++; - } - - /* Does the input contain any "atom-special" characters? */ - if(!backsp_count && !quote_count && !others_exists) + len = strlen(str); + nclean = strcspn(str, "() {%*]\\\""); + if(len == nclean) + /* nothing to escape, return a strdup */ return strdup(str); - /* Calculate the new string length */ - newlen = strlen(str) + backsp_count + quote_count + (others_exists ? 2 : 0); + Curl_dyn_init(&line, 2000); - /* Allocate the new string */ - newstr = (char *) malloc((newlen + 1) * sizeof(char)); - if(!newstr) + if(!escape_only && Curl_dyn_addn(&line, "\"", 1)) return NULL; - /* Surround the string in quotes if necessary */ - p2 = newstr; - if(others_exists) { - newstr[0] = '"'; - newstr[newlen - 1] = '"'; - p2++; + while(*str) { + if((*str == '\\' || *str == '"') && + Curl_dyn_addn(&line, "\\", 1)) + return NULL; + if(Curl_dyn_addn(&line, str, 1)) + return NULL; + str++; } - /* Copy the string, escaping backslash and quote characters along the way */ - p1 = str; - while(*p1) { - if(*p1 == '\\' || *p1 == '"') { - *p2 = '\\'; - p2++; - } - - *p2 = *p1; - - p1++; - p2++; - } - - /* Terminate the string */ - newstr[newlen] = '\0'; + if(!escape_only && Curl_dyn_addn(&line, "\"", 1)) + return NULL; - return newstr; + return Curl_dyn_ptr(&line); } /*********************************************************************** @@ -1880,22 +1856,17 @@ static char *imap_atom(const char *str, bool escape_only) */ 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 */ - case '0': case '1': case '2': case '3': case '4': case '5': case '6': - case '7': case '8': case '9': - case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G': - case 'H': case 'I': case 'J': case 'K': case 'L': case 'M': case 'N': - case 'O': case 'P': case 'Q': case 'R': case 'S': case 'T': case 'U': - case 'V': case 'W': case 'X': case 'Y': case 'Z': - case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': case 'g': - case 'h': case 'i': case 'j': case 'k': case 'l': case 'm': case 'n': - case 'o': case 'p': case 'q': case 'r': case 's': case 't': case 'u': - case 'v': case 'w': case 'x': case 'y': case 'z': + /* bchar -> achar -> uchar -> unreserved (without alphanumeric) */ case '-': case '.': case '_': case '~': /* bchar -> achar -> uchar -> sub-delims-sh */ case '!': case '$': case '\'': case '(': case ')': case '*': @@ -1920,41 +1891,53 @@ 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; - - imapc->sasl.resetprefs = TRUE; + bool prefer_login = false; while(!result && ptr && *ptr) { const char *key = ptr; const char *value; while(*ptr && *ptr != '=') - ptr++; + ptr++; value = ptr + 1; while(*ptr && *ptr != ';') ptr++; - if(strncasecompare(key, "AUTH=", 5)) + 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 + } + else { + prefer_login = false; result = CURLE_URL_MALFORMAT; + } if(*ptr == ';') ptr++; } - 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; + 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; @@ -1967,13 +1950,12 @@ static CURLcode imap_parse_url_options(struct connectdata *conn) * Parse the URL path into separate path components. * */ -static CURLcode imap_parse_url_path(struct connectdata *conn) +static CURLcode imap_parse_url_path(struct Curl_easy *data) { /* The imap struct is already initialised in imap_connect() */ CURLcode result = CURLE_OK; - struct Curl_easy *data = conn->data; - struct IMAP *imap = data->req.protop; - const char *begin = data->state.path; + 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 */ @@ -1986,8 +1968,8 @@ static CURLcode imap_parse_url_path(struct connectdata *conn) if(end > begin && end[-1] == '/') end--; - result = Curl_urldecode(data, begin, end - begin, &imap->mailbox, NULL, - TRUE); + result = Curl_urldecode(begin, end - begin, &imap->mailbox, NULL, + REJECT_CTRL); if(result) return result; } @@ -2009,7 +1991,8 @@ static CURLcode imap_parse_url_path(struct connectdata *conn) return CURLE_URL_MALFORMAT; /* Decode the name parameter */ - result = Curl_urldecode(data, begin, ptr - begin, &name, NULL, TRUE); + result = Curl_urldecode(begin, ptr - begin, &name, NULL, + REJECT_CTRL); if(result) return result; @@ -2019,13 +2002,14 @@ static CURLcode imap_parse_url_path(struct connectdata *conn) ptr++; /* Decode the value parameter */ - result = Curl_urldecode(data, begin, ptr - begin, &value, &valuelen, TRUE); + result = Curl_urldecode(begin, ptr - begin, &value, &valuelen, + REJECT_CTRL); if(result) { free(name); return result; } - DEBUGF(infof(conn->data, "IMAP URL parameter '%s' = '%s'\n", name, value)); + 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. @@ -2045,6 +2029,13 @@ static CURLcode imap_parse_url_path(struct connectdata *conn) 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'; @@ -2072,17 +2063,10 @@ static CURLcode imap_parse_url_path(struct connectdata *conn) /* 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 && *ptr == '?') { - /* Find the length of the query parameter */ - begin = ++ptr; - while(imap_is_bchar(*ptr)) - ptr++; - - /* Decode the query parameter */ - result = Curl_urldecode(data, begin, ptr - begin, &imap->query, NULL, - TRUE); - if(result) - return result; + 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 */ @@ -2098,16 +2082,15 @@ static CURLcode imap_parse_url_path(struct connectdata *conn) * * Parse the custom request. */ -static CURLcode imap_parse_custom_request(struct connectdata *conn) +static CURLcode imap_parse_custom_request(struct Curl_easy *data) { CURLcode result = CURLE_OK; - struct Curl_easy *data = conn->data; - struct IMAP *imap = data->req.protop; + 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(data, custom, 0, &imap->custom, NULL, TRUE); + result = Curl_urldecode(custom, 0, &imap->custom, NULL, REJECT_CTRL); /* Extract the parameters if specified */ if(!result) { diff --git a/contrib/curl/lib/imap.h b/contrib/curl/lib/imap.h index 5e0e228f5e..784ee97e55 100644 --- a/contrib/curl/lib/imap.h +++ b/contrib/curl/lib/imap.h @@ -7,11 +7,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 2009 - 2015, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -20,6 +20,8 @@ * 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" @@ -58,6 +60,7 @@ struct IMAP { 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 */ @@ -69,17 +72,19 @@ struct IMAP { struct */ struct imap_conn { struct pingpong pp; - imapstate state; /* Always use imap.c:state() to change state! */ - bool ssldone; /* Is connect() over SSL done? */ struct SASL sasl; /* SASL-related parameters */ - unsigned int preftype; /* Preferred authentication type */ - int cmdid; /* Last used command ID */ - char resptag[5]; /* Response tag to wait for */ - bool tls_supported; /* StartTLS capability supported by server */ - bool login_disabled; /* LOGIN command disabled by server */ - bool ir_supported; /* Initial response supported by server */ + 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; @@ -91,6 +96,6 @@ extern const struct Curl_handler Curl_handler_imaps; /* Authentication type values */ #define IMAP_TYPE_NONE 0 -#define IMAP_TYPE_ANY ~0U +#define IMAP_TYPE_ANY (IMAP_TYPE_CLEARTEXT|IMAP_TYPE_SASL) #endif /* HEADER_CURL_IMAP_H */ diff --git a/contrib/curl/lib/inet_ntop.c b/contrib/curl/lib/inet_ntop.c index 9afbdbb327..a2812cf8e2 100644 --- a/contrib/curl/lib/inet_ntop.c +++ b/contrib/curl/lib/inet_ntop.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 1996-2001 Internet Software Consortium. + * 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 @@ -13,6 +13,8 @@ * 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. @@ -40,56 +42,64 @@ #define INT16SZ 2 /* - * Format an IPv4 address, more or less like inet_ntoa(). + * If USE_IPV6 is disabled, we still want to parse IPv6 addresses, so make + * sure we have _some_ value for AF_INET6 without polluting our fake value + * everywhere. + */ +#if !defined(USE_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) +static char *inet_ntop4(const unsigned char *src, char *dst, size_t size) { - char tmp[sizeof "255.255.255.255"]; + char tmp[sizeof("255.255.255.255")]; size_t len; DEBUGASSERT(size >= 16); tmp[0] = '\0'; - (void)snprintf(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); + (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) { - SET_ERRNO(ENOSPC); + errno = ENOSPC; return (NULL); } strcpy(dst, tmp); return dst; } -#ifdef ENABLE_IPV6 /* * Convert IPv6 binary address into presentation (printable) format. */ -static char *inet_ntop6 (const unsigned char *src, char *dst, size_t size) +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 + * 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. + * 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 { - long base; - long len; + int base; + int len; } best, cur; - unsigned long words[IN6ADDRSZ / INT16SZ]; + unsigned int words[IN6ADDRSZ / INT16SZ]; int i; /* Preprocess: @@ -98,7 +108,7 @@ static char *inet_ntop6 (const unsigned char *src, char *dst, size_t size) */ memset(words, '\0', sizeof(words)); for(i = 0; i < IN6ADDRSZ; i++) - words[i/2] |= (src[i] << ((1 - (i % 2)) << 3)); + words[i/2] |= ((unsigned int)src[i] << ((1 - (i % 2)) << 3)); best.base = -1; cur.base = -1; @@ -107,8 +117,9 @@ static char *inet_ntop6 (const unsigned char *src, char *dst, size_t size) for(i = 0; i < (IN6ADDRSZ / INT16SZ); i++) { if(words[i] == 0) { - if(cur.base == -1) - cur.base = i, cur.len = 1; + if(cur.base == -1) { + cur.base = i; cur.len = 1; + } else cur.len++; } @@ -134,39 +145,38 @@ static char *inet_ntop6 (const unsigned char *src, char *dst, size_t size) /* Are we following an initial run of 0x00s or any real hex? */ - if(i != 0) + 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))) { - SET_ERRNO(ENOSPC); + if(!inet_ntop4(src + 12, tp, sizeof(tmp) - (tp - tmp))) { + errno = ENOSPC; return (NULL); } tp += strlen(tp); break; } - tp += snprintf(tp, 5, "%lx", words[i]); + 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++ = ':'; *tp++ = '\0'; - /* Check for overflow, copy, and we're done. + /* Check for overflow, copy, and we are done. */ if((size_t)(tp - tmp) > size) { - SET_ERRNO(ENOSPC); + errno = ENOSPC; return (NULL); } strcpy(dst, tmp); return dst; } -#endif /* ENABLE_IPV6 */ /* * Convert a network format address to presentation format. @@ -175,22 +185,19 @@ static char *inet_ntop6 (const unsigned char *src, char *dst, size_t size) * 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 use macro ERRNO to fetch the - * errno this function sets when returning NULL, not SOCKERRNO. + * On Windows we store the error in the thread errno, not in the Winsock error + * code. This is to avoid losing the actual last Winsock error. When this + * function returns NULL, check errno not SOCKERRNO. */ 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); -#ifdef ENABLE_IPV6 case AF_INET6: return inet_ntop6((const unsigned char *)src, buf, size); -#endif default: - SET_ERRNO(EAFNOSUPPORT); + errno = EAFNOSUPPORT; return NULL; } } diff --git a/contrib/curl/lib/inet_ntop.h b/contrib/curl/lib/inet_ntop.h index 9f44612710..f592f25251 100644 --- a/contrib/curl/lib/inet_ntop.h +++ b/contrib/curl/lib/inet_ntop.h @@ -7,11 +7,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2015, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -20,6 +20,8 @@ * 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" @@ -30,9 +32,13 @@ char *Curl_inet_ntop(int af, const void *addr, char *buf, size_t size); #ifdef HAVE_ARPA_INET_H #include #endif +#ifdef _WIN32 +#define Curl_inet_ntop(af,addr,buf,size) \ + inet_ntop(af, addr, buf, size) +#else #define Curl_inet_ntop(af,addr,buf,size) \ - inet_ntop(af, addr, buf, (curl_socklen_t)size) + inet_ntop(af, addr, buf, (curl_socklen_t)(size)) +#endif #endif #endif /* HEADER_CURL_INET_NTOP_H */ - diff --git a/contrib/curl/lib/inet_pton.c b/contrib/curl/lib/inet_pton.c index 475f44abc6..97e6f80d79 100644 --- a/contrib/curl/lib/inet_pton.c +++ b/contrib/curl/lib/inet_pton.c @@ -1,6 +1,6 @@ /* This is from the BIND 4.9.4 release, modified to compile by itself */ -/* Copyright (c) 1996 by Internet Software Consortium. +/* 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 @@ -14,6 +14,8 @@ * 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" @@ -37,14 +39,21 @@ #define INT16SZ 2 /* - * 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. + * If USE_IPV6 is disabled, we still want to parse IPv6 addresses, so make + * sure we have _some_ value for AF_INET6 without polluting our fake value + * everywhere. + */ +#if !defined(USE_IPV6) && !defined(AF_INET6) +#define AF_INET6 (AF_INET + 1) +#endif + +/* + * WARNING: Do not 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); -#ifdef ENABLE_IPV6 static int inet_pton6(const char *src, unsigned char *dst); -#endif /* int * inet_pton(af, src, dst) @@ -52,13 +61,13 @@ static int inet_pton6(const char *src, unsigned char *dst); * 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) + * 0 if the address was not valid (`dst' is untouched in this case) * -1 if some other error occurred (`dst' is untouched in this case, too) * notice: * On Windows we store the error in the thread errno, not - * in the winsock error code. This is to avoid losing the - * actual last winsock error. So use macro ERRNO to fetch the - * errno this function sets when returning (-1), not SOCKERRNO. + * in the Winsock error code. This is to avoid losing the + * actual last Winsock error. When this function returns + * -1, check errno not SOCKERRNO. * author: * Paul Vixie, 1996. */ @@ -68,12 +77,10 @@ Curl_inet_pton(int af, const char *src, void *dst) switch(af) { case AF_INET: return (inet_pton4(src, (unsigned char *)dst)); -#ifdef ENABLE_IPV6 case AF_INET6: return (inet_pton6(src, (unsigned char *)dst)); -#endif default: - SET_ERRNO(EAFNOSUPPORT); + errno = EAFNOSUPPORT; return (-1); } /* NOTREACHED */ @@ -85,7 +92,7 @@ Curl_inet_pton(int af, const char *src, void *dst) * return: * 1 if `src' is a valid dotted quad, else 0. * notice: - * does not touch `dst' unless it's returning 1. + * does not touch `dst' unless it is returning 1. * author: * Paul Vixie, 1996. */ @@ -105,14 +112,15 @@ inet_pton4(const char *src, unsigned char *dst) pch = strchr(digits, ch); if(pch) { - unsigned int val = *tp * 10 + (unsigned int)(pch - digits); + unsigned int val = (unsigned int)(*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(!saw_digit) { if(++octets > 4) return (0); saw_digit = 1; @@ -133,14 +141,13 @@ inet_pton4(const char *src, unsigned char *dst) return (1); } -#ifdef ENABLE_IPV6 /* 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. + * (1) does not touch `dst' unless it is returning 1. * (2) :: in a full address is silently ignored. * credit: * inspired by Mark Andrews. @@ -153,7 +160,7 @@ 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 *xdigits, *curtok; + const char *curtok; int ch, saw_xdigit; size_t val; @@ -168,12 +175,13 @@ inet_pton6(const char *src, unsigned char *dst) 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 != NULL) { + if(pch) { val <<= 4; val |= (pch - xdigits); if(++saw_xdigit > 4) @@ -210,10 +218,10 @@ inet_pton6(const char *src, unsigned char *dst) *tp++ = (unsigned char) ((val >> 8) & 0xff); *tp++ = (unsigned char) (val & 0xff); } - if(colonp != NULL) { + if(colonp) { /* * Since some memmove()'s erroneously fail to handle - * overlapping regions, we'll do the shift by hand. + * overlapping regions, we will do the shift by hand. */ const ssize_t n = tp - colonp; ssize_t i; @@ -231,6 +239,5 @@ inet_pton6(const char *src, unsigned char *dst) memcpy(dst, tmp, IN6ADDRSZ); return (1); } -#endif /* ENABLE_IPV6 */ #endif /* HAVE_INET_PTON */ diff --git a/contrib/curl/lib/inet_pton.h b/contrib/curl/lib/inet_pton.h index 9188d9598f..f8562fa8a7 100644 --- a/contrib/curl/lib/inet_pton.h +++ b/contrib/curl/lib/inet_pton.h @@ -7,11 +7,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2005, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -20,6 +20,8 @@ * 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" @@ -34,4 +36,3 @@ int Curl_inet_pton(int, const char *, void *); #endif #endif /* HEADER_CURL_INET_PTON_H */ - diff --git a/contrib/curl/lib/krb5.c b/contrib/curl/lib/krb5.c index 067b0a5778..d586498640 100644 --- a/contrib/curl/lib/krb5.c +++ b/contrib/curl/lib/krb5.c @@ -1,10 +1,12 @@ /* GSSAPI/krb5 support for FTP - loosely based on old krb4.c * - * Copyright (c) 1995, 1996, 1997, 1998, 1999 Kungliga Tekniska Högskolan + * Copyright (c) 1995, 1996, 1997, 1998, 1999 Kungliga Tekniska Hƶgskolan * (Royal Institute of Technology, Stockholm, Sweden). - * Copyright (c) 2004 - 2016 Daniel Stenberg + * 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: @@ -23,7 +25,7 @@ * 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 + * 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) @@ -39,20 +41,78 @@ #ifdef HAVE_NETDB_H #include #endif +#ifdef HAVE_ARPA_INET_H +#include +#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_sec.h" +#include "transfer.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) +{ + size_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 + + DEBUGASSERT(cmd); + + 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_xfer_send(data, sptr, write_len, FALSE, &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, bytes_written); + + if(bytes_written != write_len) { + write_len -= bytes_written; + sptr += bytes_written; + } + else + break; + } + + return result; +} + static int krb5_init(void *app_data) { @@ -85,12 +145,9 @@ krb5_decode(void *app_data, void *buf, int len, enc.value = buf; enc.length = len; - maj = gss_unseal(&min, *context, &enc, &dec, NULL, NULL); - if(maj != GSS_S_COMPLETE) { - if(len >= 4) - strcpy(buf, "599 "); + 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); @@ -99,16 +156,6 @@ krb5_decode(void *app_data, void *buf, int len, return len; } -static int -krb5_overhead(void *app_data, int level, int len) -{ - /* no arguments are used */ - (void)app_data; - (void)level; - (void)len; - return 0; -} - static int krb5_encode(void *app_data, const void *from, int length, int level, void **to) { @@ -119,11 +166,11 @@ krb5_encode(void *app_data, const void *from, int length, int level, void **to) int len; /* NOTE that the cast is safe, neither of the krb5, gnu gss and heimdal - * libraries modify the input buffer in gss_seal() + * libraries modify the input buffer in gss_wrap() */ dec.value = (void *)from; - dec.length = length; - maj = gss_seal(&min, *context, + dec.length = (size_t)length; + maj = gss_wrap(&min, *context, level == PROT_PRIVATE, GSS_C_QOP_DEFAULT, &dec, &state, &enc); @@ -131,7 +178,7 @@ krb5_encode(void *app_data, const void *from, int length, int level, void **to) if(maj != GSS_S_COMPLETE) return -1; - /* malloc a new buffer, in case gss_release_buffer doesn't work as + /* malloc a new buffer, in case gss_release_buffer does not work as expected */ *to = malloc(enc.length); if(!*to) @@ -143,14 +190,13 @@ krb5_encode(void *app_data, const void *from, int length, int level, void **to) } static int -krb5_auth(void *app_data, struct connectdata *conn) +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); - struct Curl_easy *data = conn->data; CURLcode result; const char *service = data->set.str[STRING_SERVICE_NAME] ? data->set.str[STRING_SERVICE_NAME] : @@ -162,8 +208,9 @@ krb5_auth(void *app_data, struct connectdata *conn) 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 **)&conn->ip_addr->ai_addr; + struct sockaddr_in *remote_addr = + (struct sockaddr_in *)(void *)&conn->remote_addr->curl_sa_addr; + char *stringp; if(getsockname(conn->sock[FIRSTSOCKET], (struct sockaddr *)&conn->local_addr, &l) < 0) @@ -174,35 +221,41 @@ krb5_auth(void *app_data, struct connectdata *conn) 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.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 */ + /* this really should not be repeated here, but cannot help it */ if(service == srv_host) { - result = Curl_ftpsend(conn, "AUTH GSSAPI"); + result = ftpsend(data, conn, "AUTH GSSAPI"); if(result) return -2; - if(Curl_GetFTPResponse(&nread, conn, NULL)) - return -1; - - if(data->state.buffer[0] != '3') + if(Curl_GetFTPResponse(data, &nread, NULL)) return -1; + else { + struct pingpong *pp = &conn->proto.ftpc.pp; + char *line = Curl_dyn_ptr(&pp->recvbuf); + if(line[0] != '3') + return -1; + } } - input_buffer.value = data->state.buffer; - input_buffer.length = snprintf(input_buffer.value, BUFSIZE, "%s@%s", - service, host); + 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) { - Curl_failf(data, "Error importing service name %s", - input_buffer.value); + failf(data, "Error importing service name %s@%s", service, host); return AUTH_ERROR; } service = srv_host; @@ -210,7 +263,7 @@ krb5_auth(void *app_data, struct connectdata *conn) } /* We pass NULL as |output_name_type| to avoid a leak. */ gss_display_name(&min, gssname, &output_buffer, NULL); - Curl_infof(data, "Trying against %s\n", output_buffer.value); + infof(data, "Trying against %s", (char *)output_buffer.value); gssresp = GSS_C_NO_BUFFER; *context = GSS_C_NO_CONTEXT; @@ -237,59 +290,66 @@ krb5_auth(void *app_data, struct connectdata *conn) } if(GSS_ERROR(maj)) { - Curl_infof(data, "Error creating security context\n"); + infof(data, "Error creating security context"); ret = AUTH_ERROR; break; } - if(output_buffer.length != 0) { + if(output_buffer.length) { char *cmd; - result = Curl_base64_encode(data, (char *)output_buffer.value, + result = Curl_base64_encode((char *)output_buffer.value, output_buffer.length, &p, &base64_sz); if(result) { - Curl_infof(data, "base64-encoding: %s\n", - curl_easy_strerror(result)); + infof(data, "base64-encoding: %s", curl_easy_strerror(result)); ret = AUTH_ERROR; break; } cmd = aprintf("ADAT %s", p); if(cmd) - result = Curl_ftpsend(conn, cmd); + result = ftpsend(data, conn, cmd); else result = CURLE_OUT_OF_MEMORY; free(p); + free(cmd); if(result) { ret = -2; break; } - if(Curl_GetFTPResponse(&nread, conn, NULL)) { + if(Curl_GetFTPResponse(data, &nread, NULL)) { ret = -1; break; } - - if(data->state.buffer[0] != '2' && data->state.buffer[0] != '3') { - Curl_infof(data, "Server didn't accept auth data\n"); - ret = AUTH_ERROR; - break; + else { + struct pingpong *pp = &conn->proto.ftpc.pp; + size_t len = Curl_dyn_len(&pp->recvbuf); + p = Curl_dyn_ptr(&pp->recvbuf); + if((len < 4) || (p[0] != '2' && p[0] != '3')) { + infof(data, "Server did not accept auth data"); + ret = AUTH_ERROR; + break; + } } - p = data->state.buffer + 4; + _gssresp.value = NULL; /* make sure it is initialized */ + _gssresp.length = 0; + p += 4; /* over '789 ' */ p = strstr(p, "ADAT="); if(p) { - result = Curl_base64_decode(p + 5, - (unsigned char **)&_gssresp.value, - &_gssresp.length); + unsigned char *outptr; + size_t outlen; + result = Curl_base64_decode(p + 5, &outptr, &outlen); if(result) { - Curl_failf(data, "base64-decoding: %s", - curl_easy_strerror(result)); + failf(data, "base64-decoding: %s", curl_easy_strerror(result)); ret = AUTH_CONTINUE; break; } + _gssresp.value = outptr; + _gssresp.length = outlen; } gssresp = &_gssresp; @@ -315,24 +375,553 @@ static void krb5_end(void *app_data) OM_uint32 min; gss_ctx_id_t *context = app_data; if(*context != GSS_C_NO_CONTEXT) { -#ifdef DEBUGBUILD - OM_uint32 maj = -#endif - gss_delete_sec_context(&min, context, GSS_C_NO_BUFFER); + OM_uint32 maj = gss_delete_sec_context(&min, context, GSS_C_NO_BUFFER); + (void)maj; DEBUGASSERT(maj == GSS_S_COMPLETE); } } -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_overhead, - krb5_encode, - krb5_decode +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: + 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, ...) + CURL_PRINTF(2, 3); + +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) { + result = Curl_conn_recv(data, sockindex, to_p, len, &nread); + 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; + size_t written; + + while(len > 0) { + result = Curl_conn_send(data, sockindex, to_p, len, FALSE, &written); + if(!result && 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) { + len = (int)ntohl((uint32_t)len); + if(len > CURL_MAX_INPUT_LENGTH) + return CURLE_TOO_LARGE; + + Curl_dyn_reset(&buf->buf); + } + else + return CURLE_RECV_ERROR; + + do { + char buffer[1024]; + nread = CURLMIN(len, (int)sizeof(buffer)); + result = socket_read(data, sockindex, buffer, (size_t)nread); + if(result) + return result; + result = Curl_dyn_addn(&buf->buf, buffer, nread); + if(result) + return result; + len -= nread; + } while(len); + /* this decodes the dynbuf *in place* */ + nread = conn->mech->decode(conn->app_data, + Curl_dyn_ptr(&buf->buf), + len, conn->data_prot, conn); + if(nread < 0) + return CURLE_RECV_ERROR; + Curl_dyn_setlen(&buf->buf, nread); + buf->index = 0; + return CURLE_OK; +} + +static size_t +buffer_read(struct krb5buffer *buf, void *data, size_t len) +{ + size_t size = Curl_dyn_len(&buf->buf); + if(size - buf->index < len) + len = size - buf->index; + memcpy(data, Curl_dyn_ptr(&buf->buf) + buf->index, len); + buf->index += len; + return len; +} + +/* 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) { + ssize_t nread; + *err = Curl_conn_recv(data, sockindex, buffer, len, &nread); + return nread; + } + + 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(Curl_dyn_len(&conn->in_buffer.buf) == 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, (int)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 = (int)htonl((OM_uint32)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, bool eos, + CURLcode *err) +{ + struct connectdata *conn = data->conn; + curl_socket_t fd = conn->sock[sockindex]; + (void)eos; /* unused */ + *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, + (int)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 */ + struct pingpong *pp = &conn->proto.ftpc.pp; + char *line; + + 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; + + line = Curl_dyn_ptr(&pp->recvbuf); + pbsz = strstr(line, "PBSZ="); + if(pbsz) { + /* stick to default value if the check fails */ + if(ISDIGIT(pbsz[5])) + buffer_size = (unsigned int)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; + } + Curl_dyn_init(&conn->in_buffer.buf, CURL_MAX_INPUT_LENGTH); + } + + 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, do not 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); + Curl_safefree(conn->app_data); + Curl_dyn_free(&conn->in_buffer.buf); + conn->in_buffer.index = 0; + conn->in_buffer.eof_flag = 0; + conn->sec_complete = 0; + conn->data_prot = PROT_CLEAR; + conn->mech = NULL; +} + #endif /* HAVE_GSSAPI && !CURL_DISABLE_FTP */ diff --git a/contrib/curl/lib/ldap.c b/contrib/curl/lib/ldap.c index 979ce7de43..01429ba79e 100644 --- a/contrib/curl/lib/ldap.c +++ b/contrib/curl/lib/ldap.c @@ -5,11 +5,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2016, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -18,6 +18,8 @@ * 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" @@ -35,7 +37,27 @@ * 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 +/* 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 /* for [P]UNICODE_STRING */ +# ifdef _MSC_VER +# pragma warning(pop) +# endif # include # ifndef LDAP_VENDOR_NAME # error Your Platform SDK is NOT sufficient for LDAP support! \ @@ -54,15 +76,6 @@ # endif /* HAVE_LDAP_SSL && HAVE_LDAP_SSL_H */ #endif -/* These are macros in both (in above ) and typedefs - * in BoringSSL's - */ -#ifdef HAVE_BORINGSSL -# undef X509_NAME -# undef X509_CERT_PAIR -# undef X509_EXTENSIONS -#endif - #include "urldata.h" #include #include "sendf.h" @@ -84,7 +97,7 @@ /* Use our own implementation. */ -typedef struct { +struct ldap_urldesc { char *lud_host; int lud_port; #if defined(USE_WIN32_LDAP) @@ -104,12 +117,13 @@ typedef struct { 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 */ -} CURL_LDAPURLDesc; +}; #undef LDAPURLDesc -#define LDAPURLDesc CURL_LDAPURLDesc +#define LDAPURLDesc struct ldap_urldesc -static int _ldap_url_parse(const struct connectdata *conn, +static int _ldap_url_parse(struct Curl_easy *data, + const struct connectdata *conn, LDAPURLDesc **ludp); static void _ldap_free_urldesc(LDAPURLDesc *ludp); @@ -121,24 +135,38 @@ static void _ldap_free_urldesc(LDAPURLDesc *ludp); #define LDAP_TRACE(x) do { \ _ldap_trace("%u: ", __LINE__); \ _ldap_trace x; \ - } WHILE_FALSE + } while(0) - static void _ldap_trace(const char *fmt, ...); + static void _ldap_trace(const char *fmt, ...) CURL_PRINTF(1, 2); #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 Curl_ldap(struct connectdata *conn, bool *done); +static CURLcode ldap_do(struct Curl_easy *data, bool *done); /* * LDAP protocol handler. */ const struct Curl_handler Curl_handler_ldap = { - "LDAP", /* scheme */ + "ldap", /* scheme */ ZERO_NULL, /* setup_connection */ - Curl_ldap, /* do_it */ + ldap_do, /* do_it */ ZERO_NULL, /* done */ ZERO_NULL, /* do_more */ ZERO_NULL, /* connect_it */ @@ -149,9 +177,13 @@ const struct Curl_handler Curl_handler_ldap = { ZERO_NULL, /* domore_getsock */ ZERO_NULL, /* perform_getsock */ ZERO_NULL, /* disconnect */ - ZERO_NULL, /* readwrite */ + ZERO_NULL, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ + ZERO_NULL, /* connection_check */ + ZERO_NULL, /* attach connection */ PORT_LDAP, /* defport */ CURLPROTO_LDAP, /* protocol */ + CURLPROTO_LDAP, /* family */ PROTOPT_NONE /* flags */ }; @@ -161,9 +193,9 @@ const struct Curl_handler Curl_handler_ldap = { */ const struct Curl_handler Curl_handler_ldaps = { - "LDAPS", /* scheme */ + "ldaps", /* scheme */ ZERO_NULL, /* setup_connection */ - Curl_ldap, /* do_it */ + ldap_do, /* do_it */ ZERO_NULL, /* done */ ZERO_NULL, /* do_more */ ZERO_NULL, /* connect_it */ @@ -174,15 +206,105 @@ const struct Curl_handler Curl_handler_ldaps = { ZERO_NULL, /* domore_getsock */ ZERO_NULL, /* perform_getsock */ ZERO_NULL, /* disconnect */ - ZERO_NULL, /* readwrite */ + ZERO_NULL, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ + 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) { + CURLcode res = Curl_create_sspi_identity(user, passwd, &cred); + rc = (int)res; + if(!rc) { + rc = (int)ldap_bind_s(server, NULL, (TCHAR *)&cred, method); + Curl_sspi_free_identity(&cred); + } + } + else { + /* proceed with current user credentials */ + method = LDAP_AUTH_NEGOTIATE; + rc = (int)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 = (int)ldap_simple_bind_s(server, inuser, inpass); + + curlx_unicodefree(inuser); + curlx_unicodefree(inpass); + } +#if defined(USE_WINDOWS_SSPI) + else { + rc = (int)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) +#define curl_ldap_num_t ULONG +#else +#define FREE_ON_WINLDAP(x) +#define curl_ldap_num_t int +#endif + -static CURLcode Curl_ldap(struct connectdata *conn, bool *done) +static CURLcode ldap_do(struct Curl_easy *data, bool *done) { CURLcode result = CURLE_OK; int rc = 0; @@ -191,72 +313,59 @@ static CURLcode Curl_ldap(struct connectdata *conn, bool *done) LDAPMessage *ldapmsg = NULL; LDAPMessage *entryIterator; int num = 0; - struct Curl_easy *data=conn->data; + struct connectdata *conn = data->conn; int ldap_proto = LDAP_VERSION3; int ldap_ssl = 0; char *val_b64 = NULL; size_t val_b64_sz = 0; - curl_off_t dlsize = 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; - TCHAR *user = NULL; - TCHAR *passwd = NULL; #else char *host = NULL; +#endif char *user = NULL; char *passwd = NULL; -#endif *done = TRUE; /* unconditionally */ - infof(data, "LDAP local: LDAP Vendor = %s ; LDAP Version = %d\n", - LDAP_VENDOR_NAME, LDAP_VENDOR_VERSION); - infof(data, "LDAP local: %s\n", data->change.url); + 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->change.url, &ludp); + rc = ldap_url_parse(data->state.url, &ludp); #else - rc = _ldap_url_parse(conn, &ludp); + rc = _ldap_url_parse(data, conn, &ludp); #endif - if(rc != 0) { - failf(data, "LDAP local: %s", ldap_err2string(rc)); - result = CURLE_LDAP_INVALID_URL; + if(rc) { + failf(data, "Bad LDAP URL: %s", ldap_err2string((curl_ldap_num_t)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\n", - ldap_ssl ? "encrypted" : "cleartext"); + infof(data, "LDAP local: trying to establish %s connection", + ldap_ssl ? "encrypted" : "cleartext"); #if defined(USE_WIN32_LDAP) - host = Curl_convert_UTF8_to_tchar(conn->host.name); + host = curlx_convert_UTF8_to_tchar(conn->host.name); if(!host) { result = CURLE_OUT_OF_MEMORY; goto quit; } - - if(conn->bits.user_passwd) { - user = Curl_convert_UTF8_to_tchar(conn->user); - passwd = Curl_convert_UTF8_to_tchar(conn->passwd); - if(!user || !passwd) { - result = CURLE_OUT_OF_MEMORY; - - goto quit; - } - } #else host = conn->host.name; +#endif - if(conn->bits.user_passwd) { + if(data->state.aptr.user) { user = conn->user; passwd = conn->passwd; } -#endif #ifdef LDAP_OPT_NETWORK_TIMEOUT ldap_set_option(NULL, LDAP_OPT_NETWORK_TIMEOUT, &ldap_timeout); @@ -266,8 +375,8 @@ static CURLcode Curl_ldap(struct connectdata *conn, bool *done) 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, (int)conn->port, 1); + /* Win32 LDAP SDK does not support insecure mode without CA! */ + server = ldap_sslinit(host, (curl_ldap_num_t)conn->primary.remote_port, 1); ldap_set_option(server, LDAP_OPT_SSL, LDAP_OPT_ON); #else int ldap_option; @@ -286,19 +395,19 @@ static CURLcode Curl_ldap(struct connectdata *conn, bool *done) (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!", + 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'\n", - (cert_type == LDAPSSL_CERT_FILETYPE_DER ? "DER" : "PEM"), - ldap_ca); + 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)); + (cert_type == LDAPSSL_CERT_FILETYPE_DER ? "DER" : "PEM"), + ldap_err2string(rc)); result = CURLE_SSL_CERTPROBLEM; goto quit; } @@ -313,10 +422,10 @@ static CURLcode Curl_ldap(struct connectdata *conn, bool *done) result = CURLE_SSL_CERTPROBLEM; goto quit; } - server = ldapssl_init(host, (int)conn->port, 1); - if(server == NULL) { - failf(data, "LDAP local: Cannot connect to %s:%ld", - conn->host.dispname, conn->port); + server = ldapssl_init(host, conn->primary.remote_port, 1); + if(!server) { + failf(data, "LDAP local: Cannot connect to %s:%u", + conn->host.dispname, conn->primary.remote_port); result = CURLE_COULDNT_CONNECT; goto quit; } @@ -325,16 +434,16 @@ static CURLcode Curl_ldap(struct connectdata *conn, bool *done) /* 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!"); + 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!"); + 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\n", ldap_ca); + 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", @@ -354,10 +463,10 @@ static CURLcode Curl_ldap(struct connectdata *conn, bool *done) result = CURLE_SSL_CERTPROBLEM; goto quit; } - server = ldap_init(host, (int)conn->port); - if(server == NULL) { - failf(data, "LDAP local: Cannot connect to %s:%ld", - conn->host.dispname, conn->port); + server = ldap_init(host, conn->primary.remote_port); + if(!server) { + failf(data, "LDAP local: Cannot connect to %s:%u", + conn->host.dispname, conn->primary.remote_port); result = CURLE_COULDNT_CONNECT; goto quit; } @@ -379,6 +488,8 @@ static CURLcode Curl_ldap(struct connectdata *conn, bool *done) } */ #else + (void)ldap_option; + (void)ldap_ca; /* we should probably never come up to here since configure should check in first place if we can support LDAP SSL/TLS */ failf(data, "LDAP local: SSL/TLS not supported with this version " @@ -389,48 +500,67 @@ static CURLcode Curl_ldap(struct connectdata *conn, bool *done) #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, (int)conn->port); - if(server == NULL) { - failf(data, "LDAP local: Cannot connect to %s:%ld", - conn->host.dispname, conn->port); + server = ldap_init(host, (curl_ldap_num_t)conn->primary.remote_port); + if(!server) { + failf(data, "LDAP local: Cannot connect to %s:%u", + conn->host.dispname, conn->primary.remote_port); result = CURLE_COULDNT_CONNECT; goto quit; } } #ifdef USE_WIN32_LDAP ldap_set_option(server, LDAP_OPT_PROTOCOL_VERSION, &ldap_proto); -#endif - + rc = ldap_win_bind(data, server, user, passwd); +#else rc = ldap_simple_bind_s(server, user, passwd); - if(!ldap_ssl && rc != 0) { +#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 != 0) { - failf(data, "LDAP local: ldap_simple_bind_s %s", ldap_err2string(rc)); + if(rc) { +#ifdef USE_WIN32_LDAP + failf(data, "LDAP local: bind via ldap_win_bind %s", + ldap_err2string((ULONG)rc)); +#else + failf(data, "LDAP local: bind via ldap_simple_bind_s %s", + ldap_err2string(rc)); +#endif result = CURLE_LDAP_CANNOT_BIND; goto quit; } - rc = ldap_search_s(server, ludp->lud_dn, ludp->lud_scope, - ludp->lud_filter, ludp->lud_attrs, 0, &ldapmsg); + Curl_pgrsSetDownloadCounter(data, 0); + rc = (int)ldap_search_s(server, ludp->lud_dn, + (curl_ldap_num_t)ludp->lud_scope, + ludp->lud_filter, ludp->lud_attrs, 0, &ldapmsg); - if(rc != 0 && rc != LDAP_SIZELIMIT_EXCEEDED) { - failf(data, "LDAP remote: %s", ldap_err2string(rc)); + if(rc && rc != LDAP_SIZELIMIT_EXCEEDED) { + failf(data, "LDAP remote: %s", ldap_err2string((curl_ldap_num_t)rc)); result = CURLE_LDAP_SEARCH_FAILED; goto quit; } - for(num = 0, entryIterator = ldap_first_entry(server, ldapmsg); + num = 0; + for(entryIterator = ldap_first_entry(server, ldapmsg); entryIterator; entryIterator = ldap_next_entry(server, entryIterator), num++) { BerElement *ber = NULL; #if defined(USE_WIN32_LDAP) TCHAR *attribute; #else - char *attribute; /*! suspicious that this isn't 'const' */ + char *attribute; #endif int i; @@ -440,7 +570,7 @@ static CURLcode Curl_ldap(struct connectdata *conn, bool *done) size_t name_len; #if defined(USE_WIN32_LDAP) TCHAR *dn = ldap_get_dn(server, entryIterator); - name = Curl_convert_tchar_to_UTF8(dn); + name = curlx_convert_tchar_to_UTF8(dn); if(!name) { ldap_memfree(dn); @@ -453,42 +583,29 @@ static CURLcode Curl_ldap(struct connectdata *conn, bool *done) #endif name_len = strlen(name); - result = Curl_client_write(conn, CLIENTWRITE_BODY, (char *)"DN: ", 4); + result = Curl_client_write(data, CLIENTWRITE_BODY, (char *)"DN: ", 4); if(result) { -#if defined(USE_WIN32_LDAP) - Curl_unicodefree(name); -#endif + FREE_ON_WINLDAP(name); ldap_memfree(dn); - goto quit; } - result = Curl_client_write(conn, CLIENTWRITE_BODY, (char *) name, - name_len); + result = Curl_client_write(data, CLIENTWRITE_BODY, name, name_len); if(result) { -#if defined(USE_WIN32_LDAP) - Curl_unicodefree(name); -#endif + FREE_ON_WINLDAP(name); ldap_memfree(dn); - goto quit; } - result = Curl_client_write(conn, CLIENTWRITE_BODY, (char *)"\n", 1); + result = Curl_client_write(data, CLIENTWRITE_BODY, (char *)"\n", 1); if(result) { -#if defined(USE_WIN32_LDAP) - Curl_unicodefree(name); -#endif + FREE_ON_WINLDAP(name); ldap_memfree(dn); goto quit; } - dlsize += name_len + 5; - -#if defined(USE_WIN32_LDAP) - Curl_unicodefree(name); -#endif + FREE_ON_WINLDAP(name); ldap_memfree(dn); } @@ -499,7 +616,7 @@ static CURLcode Curl_ldap(struct connectdata *conn, bool *done) BerValue **vals; size_t attr_len; #if defined(USE_WIN32_LDAP) - char *attr = Curl_convert_tchar_to_UTF8(attribute); + char *attr = curlx_convert_tchar_to_UTF8(attribute); if(!attr) { if(ber) ber_free(ber, 0); @@ -507,21 +624,19 @@ static CURLcode Curl_ldap(struct connectdata *conn, bool *done) 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 != NULL) { + if(vals) { for(i = 0; (vals[i] != NULL); i++) { - result = Curl_client_write(conn, CLIENTWRITE_BODY, (char *)"\t", 1); + result = Curl_client_write(data, CLIENTWRITE_BODY, (char *)"\t", 1); if(result) { ldap_value_free_len(vals); -#if defined(USE_WIN32_LDAP) - Curl_unicodefree(attr); -#endif + FREE_ON_WINLDAP(attr); ldap_memfree(attribute); if(ber) ber_free(ber, 0); @@ -529,13 +644,10 @@ static CURLcode Curl_ldap(struct connectdata *conn, bool *done) goto quit; } - result = Curl_client_write(conn, CLIENTWRITE_BODY, - (char *) attr, attr_len); + result = Curl_client_write(data, CLIENTWRITE_BODY, attr, attr_len); if(result) { ldap_value_free_len(vals); -#if defined(USE_WIN32_LDAP) - Curl_unicodefree(attr); -#endif + FREE_ON_WINLDAP(attr); ldap_memfree(attribute); if(ber) ber_free(ber, 0); @@ -543,12 +655,10 @@ static CURLcode Curl_ldap(struct connectdata *conn, bool *done) goto quit; } - result = Curl_client_write(conn, CLIENTWRITE_BODY, (char *)": ", 2); + result = Curl_client_write(data, CLIENTWRITE_BODY, (char *)": ", 2); if(result) { ldap_value_free_len(vals); -#if defined(USE_WIN32_LDAP) - Curl_unicodefree(attr); -#endif + FREE_ON_WINLDAP(attr); ldap_memfree(attribute); if(ber) ber_free(ber, 0); @@ -556,21 +666,14 @@ static CURLcode Curl_ldap(struct connectdata *conn, bool *done) goto quit; } - dlsize += attr_len + 3; - if((attr_len > 7) && - (strcmp(";binary", (char *) attr + (attr_len - 7)) == 0)) { + (strcmp(";binary", attr + (attr_len - 7)) == 0)) { /* Binary attribute, encode to base64. */ - result = Curl_base64_encode(data, - vals[i]->bv_val, - vals[i]->bv_len, - &val_b64, - &val_b64_sz); + result = Curl_base64_encode(vals[i]->bv_val, vals[i]->bv_len, + &val_b64, &val_b64_sz); if(result) { ldap_value_free_len(vals); -#if defined(USE_WIN32_LDAP) - Curl_unicodefree(attr); -#endif + FREE_ON_WINLDAP(attr); ldap_memfree(attribute); if(ber) ber_free(ber, 0); @@ -579,56 +682,44 @@ static CURLcode Curl_ldap(struct connectdata *conn, bool *done) } if(val_b64_sz > 0) { - result = Curl_client_write(conn, CLIENTWRITE_BODY, val_b64, + result = Curl_client_write(data, CLIENTWRITE_BODY, val_b64, val_b64_sz); free(val_b64); if(result) { ldap_value_free_len(vals); -#if defined(USE_WIN32_LDAP) - Curl_unicodefree(attr); -#endif + FREE_ON_WINLDAP(attr); ldap_memfree(attribute); if(ber) ber_free(ber, 0); goto quit; } - - dlsize += val_b64_sz; } } else { - result = Curl_client_write(conn, CLIENTWRITE_BODY, vals[i]->bv_val, + result = Curl_client_write(data, CLIENTWRITE_BODY, vals[i]->bv_val, vals[i]->bv_len); if(result) { ldap_value_free_len(vals); -#if defined(USE_WIN32_LDAP) - Curl_unicodefree(attr); -#endif + FREE_ON_WINLDAP(attr); ldap_memfree(attribute); if(ber) ber_free(ber, 0); goto quit; } - - dlsize += vals[i]->bv_len; } - result = Curl_client_write(conn, CLIENTWRITE_BODY, (char *)"\n", 1); + result = Curl_client_write(data, CLIENTWRITE_BODY, (char *)"\n", 1); if(result) { ldap_value_free_len(vals); -#if defined(USE_WIN32_LDAP) - Curl_unicodefree(attr); -#endif + FREE_ON_WINLDAP(attr); ldap_memfree(attribute); if(ber) ber_free(ber, 0); goto quit; } - - dlsize++; } /* Free memory used to store values */ @@ -636,20 +727,16 @@ static CURLcode Curl_ldap(struct connectdata *conn, bool *done) } /* Free the attribute as we are done with it */ -#if defined(USE_WIN32_LDAP) - Curl_unicodefree(attr); -#endif + FREE_ON_WINLDAP(attr); ldap_memfree(attribute); - result = Curl_client_write(conn, CLIENTWRITE_BODY, (char *)"\n", 1); + result = Curl_client_write(data, CLIENTWRITE_BODY, (char *)"\n", 1); if(result) goto quit; - dlsize++; - Curl_pgrsSetDownloadCounter(data, dlsize); } if(ber) - ber_free(ber, 0); + ber_free(ber, 0); } quit: @@ -658,7 +745,7 @@ static CURLcode Curl_ldap(struct connectdata *conn, bool *done) LDAP_TRACE(("Received %d entries\n", num)); } if(rc == LDAP_SIZELIMIT_EXCEEDED) - infof(data, "There are more than %d entries\n", num); + infof(data, "There are more than %d entries", num); if(ludp) ldap_free_urldesc(ludp); if(server) @@ -668,15 +755,11 @@ static CURLcode Curl_ldap(struct connectdata *conn, bool *done) ldapssl_client_deinit(); #endif /* HAVE_LDAP_SSL && CURL_HAS_NOVELL_LDAPSDK */ -#if defined(USE_WIN32_LDAP) - Curl_unicodefree(passwd); - Curl_unicodefree(user); - Curl_unicodefree(host); -#endif + FREE_ON_WINLDAP(host); /* no data to transfer */ - Curl_setup_transfer(conn, -1, -1, FALSE, NULL, -1, NULL); - connclose(conn, "LDAP connection always disable re-use"); + Curl_xfer_setup_nop(data); + connclose(conn, "LDAP connection always disable reuse"); return result; } @@ -759,25 +842,27 @@ static bool split_str(char *str, char ***out, size_t *count) * * already known from 'conn->host.name'. * already known from 'conn->remote_port'. - * extract the rest from 'conn->data->state.path+1'. All fields are optional. + * extract the rest from 'data->state.path+1'. All fields are optional. * e.g. * ldap://:/??? * yields ludp->lud_dn = "". * * Defined in RFC4516 section 2. */ -static int _ldap_url_parse2(const struct connectdata *conn, LDAPURLDesc *ludp) +static int _ldap_url_parse2(struct Curl_easy *data, + const struct connectdata *conn, LDAPURLDesc *ludp) { int rc = LDAP_SUCCESS; - char *path; char *p; - char *q; + char *path; + char *q = NULL; + char *query = NULL; size_t i; - if(!conn->data || - !conn->data->state.path || - conn->data->state.path[0] != '/' || - !checkprefix("LDAP", conn->data->change.url)) + 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; @@ -785,15 +870,20 @@ static int _ldap_url_parse2(const struct connectdata *conn, LDAPURLDesc *ludp) ludp->lud_host = conn->host.name; /* Duplicate the path */ - p = path = strdup(conn->data->state.path + 1); + p = path = strdup(data->state.up.path + 1); if(!path) return LDAP_NO_MEMORY; - /* Parse the DN (Distinguished Name) */ - q = strchr(p, '?'); - if(q) - *q++ = '\0'; + /* 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; @@ -802,7 +892,7 @@ static int _ldap_url_parse2(const struct connectdata *conn, LDAPURLDesc *ludp) LDAP_TRACE(("DN '%s'\n", dn)); /* Unescape the DN */ - result = Curl_urldecode(conn->data, dn, 0, &unescaped, NULL, FALSE); + result = Curl_urldecode(dn, 0, &unescaped, NULL, REJECT_ZERO); if(result) { rc = LDAP_NO_MEMORY; @@ -811,10 +901,10 @@ static int _ldap_url_parse2(const struct connectdata *conn, LDAPURLDesc *ludp) #if defined(USE_WIN32_LDAP) /* Convert the unescaped string to a tchar */ - ludp->lud_dn = Curl_convert_UTF8_to_tchar(unescaped); + ludp->lud_dn = curlx_convert_UTF8_to_tchar(unescaped); /* Free the unescaped string as we are done with it */ - Curl_unicodefree(unescaped); + free(unescaped); if(!ludp->lud_dn) { rc = LDAP_NO_MEMORY; @@ -864,11 +954,11 @@ static int _ldap_url_parse2(const struct connectdata *conn, LDAPURLDesc *ludp) char *unescaped; CURLcode result; - LDAP_TRACE(("attr[%d] '%s'\n", i, attributes[i])); + LDAP_TRACE(("attr[%zu] '%s'\n", i, attributes[i])); /* Unescape the attribute */ - result = Curl_urldecode(conn->data, attributes[i], 0, &unescaped, NULL, - FALSE); + result = Curl_urldecode(attributes[i], 0, &unescaped, NULL, + REJECT_ZERO); if(result) { free(attributes); @@ -879,10 +969,10 @@ static int _ldap_url_parse2(const struct connectdata *conn, LDAPURLDesc *ludp) #if defined(USE_WIN32_LDAP) /* Convert the unescaped string to a tchar */ - ludp->lud_attrs[i] = Curl_convert_UTF8_to_tchar(unescaped); + ludp->lud_attrs[i] = curlx_convert_UTF8_to_tchar(unescaped); /* Free the unescaped string as we are done with it */ - Curl_unicodefree(unescaped); + free(unescaped); if(!ludp->lud_attrs[i]) { free(attributes); @@ -937,7 +1027,7 @@ static int _ldap_url_parse2(const struct connectdata *conn, LDAPURLDesc *ludp) LDAP_TRACE(("filter '%s'\n", filter)); /* Unescape the filter */ - result = Curl_urldecode(conn->data, filter, 0, &unescaped, NULL, FALSE); + result = Curl_urldecode(filter, 0, &unescaped, NULL, REJECT_ZERO); if(result) { rc = LDAP_NO_MEMORY; @@ -946,10 +1036,10 @@ static int _ldap_url_parse2(const struct connectdata *conn, LDAPURLDesc *ludp) #if defined(USE_WIN32_LDAP) /* Convert the unescaped string to a tchar */ - ludp->lud_filter = Curl_convert_UTF8_to_tchar(unescaped); + ludp->lud_filter = curlx_convert_UTF8_to_tchar(unescaped); /* Free the unescaped string as we are done with it */ - Curl_unicodefree(unescaped); + free(unescaped); if(!ludp->lud_filter) { rc = LDAP_NO_MEMORY; @@ -970,11 +1060,13 @@ static int _ldap_url_parse2(const struct connectdata *conn, LDAPURLDesc *ludp) quit: free(path); + free(query); return rc; } -static int _ldap_url_parse(const struct connectdata *conn, +static int _ldap_url_parse(struct Curl_easy *data, + const struct connectdata *conn, LDAPURLDesc **ludpp) { LDAPURLDesc *ludp = calloc(1, sizeof(*ludp)); @@ -982,9 +1074,9 @@ static int _ldap_url_parse(const struct connectdata *conn, *ludpp = NULL; if(!ludp) - return LDAP_NO_MEMORY; + return LDAP_NO_MEMORY; - rc = _ldap_url_parse2(conn, ludp); + rc = _ldap_url_parse2(data, conn, ludp); if(rc != LDAP_SUCCESS) { _ldap_free_urldesc(ludp); ludp = NULL; @@ -995,17 +1087,26 @@ static int _ldap_url_parse(const struct connectdata *conn, static void _ldap_free_urldesc(LDAPURLDesc *ludp) { - size_t i; - 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) { - for(i = 0; i < ludp->lud_attrs_dups; i++) + 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); } diff --git a/contrib/curl/lib/libcurl.def b/contrib/curl/lib/libcurl.def new file mode 100644 index 0000000000..9bf9fcc958 --- /dev/null +++ b/contrib/curl/lib/libcurl.def @@ -0,0 +1,95 @@ +EXPORTS +curl_easy_cleanup +curl_easy_duphandle +curl_easy_escape +curl_easy_getinfo +curl_easy_header +curl_easy_init +curl_easy_nextheader +curl_easy_option_by_id +curl_easy_option_by_name +curl_easy_option_next +curl_easy_pause +curl_easy_perform +curl_easy_recv +curl_easy_reset +curl_easy_send +curl_easy_setopt +curl_easy_strerror +curl_easy_unescape +curl_easy_upkeep +curl_escape +curl_formadd +curl_formfree +curl_formget +curl_free +curl_getdate +curl_getenv +curl_global_cleanup +curl_global_init +curl_global_init_mem +curl_global_sslset +curl_global_trace +curl_maprintf +curl_mfprintf +curl_mime_addpart +curl_mime_data +curl_mime_data_cb +curl_mime_encoder +curl_mime_filedata +curl_mime_filename +curl_mime_free +curl_mime_headers +curl_mime_init +curl_mime_name +curl_mime_subparts +curl_mime_type +curl_mprintf +curl_msnprintf +curl_msprintf +curl_multi_add_handle +curl_multi_assign +curl_multi_cleanup +curl_multi_fdset +curl_multi_get_handles +curl_multi_info_read +curl_multi_init +curl_multi_perform +curl_multi_poll +curl_multi_remove_handle +curl_multi_setopt +curl_multi_socket +curl_multi_socket_action +curl_multi_socket_all +curl_multi_strerror +curl_multi_timeout +curl_multi_wait +curl_multi_waitfds +curl_multi_wakeup +curl_mvaprintf +curl_mvfprintf +curl_mvprintf +curl_mvsnprintf +curl_mvsprintf +curl_pushheader_byname +curl_pushheader_bynum +curl_share_cleanup +curl_share_init +curl_share_setopt +curl_share_strerror +curl_slist_append +curl_slist_free_all +curl_strequal +curl_strnequal +curl_unescape +curl_url +curl_url_cleanup +curl_url_dup +curl_url_get +curl_url_set +curl_url_strerror +curl_version +curl_version_info +curl_ws_meta +curl_ws_recv +curl_ws_send diff --git a/contrib/curl/lib/libcurl.plist b/contrib/curl/lib/libcurl.plist deleted file mode 100644 index 622f66cfbf..0000000000 --- a/contrib/curl/lib/libcurl.plist +++ /dev/null @@ -1,35 +0,0 @@ - - - - - CFBundleInfoDictionaryVersion - 6.0 - - CFBundleDevelopmentRegion - English - - CFBundleExecutable - curl - - CFBundleIdentifier - se.haxx.curl.libcurl - - CFBundleVersion - 7.12.3 - - CFBundleName - libcurl - - CFBundlePackageType - FMWK - - CFBundleSignature - ???? - - CFBundleShortVersionString - libcurl 7.12.3 - - CFBundleGetInfoString - libcurl.plist 7.12.3 - - diff --git a/contrib/curl/lib/libcurl.rc b/contrib/curl/lib/libcurl.rc index 3316fba191..1ceb4691fb 100644 --- a/contrib/curl/lib/libcurl.rc +++ b/contrib/curl/lib/libcurl.rc @@ -5,11 +5,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2017, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -18,41 +18,43 @@ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY * KIND, either express or implied. * + * SPDX-License-Identifier: curl + * ***************************************************************************/ #include #include "../include/curl/curlver.h" -LANGUAGE 0x09,0x01 +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 0x3fL -#if defined(DEBUGBUILD) || defined(_DEBUG) - FILEFLAGS 1 + FILEFLAGSMASK VS_FFI_FILEFLAGSMASK +#if defined(DEBUGBUILD) || defined(UNITTESTS) || defined(CURLDEBUG) || defined(_DEBUG) + FILEFLAGS VS_FF_DEBUG #else - FILEFLAGS 0 + FILEFLAGS 0L #endif FILEOS VOS__WINDOWS32 FILETYPE VFT_DLL - FILESUBTYPE 0x0L + FILESUBTYPE 0L BEGIN BLOCK "StringFileInfo" BEGIN BLOCK "040904b0" BEGIN - VALUE "CompanyName", "The curl library, https://curl.haxx.se/\0" + 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", "\xa9 " LIBCURL_COPYRIGHT "\0" /* a9: Copyright symbol */ - VALUE "License", "https://curl.haxx.se/docs/copyright.html\0" + VALUE "LegalCopyright", "Copyright (C) " LIBCURL_COPYRIGHT "\0" + VALUE "License", "https://curl.se/docs/copyright.html\0" END END diff --git a/contrib/curl/lib/libcurl.vers.in b/contrib/curl/lib/libcurl.vers.in index ae978a485d..8219669993 100644 --- a/contrib/curl/lib/libcurl.vers.in +++ b/contrib/curl/lib/libcurl.vers.in @@ -1,12 +1,4 @@ -HIDDEN -{ - local: - __*; - _rest*; - _save*; -}; - -CURL_@CURL_LT_SHLIB_VERSIONED_FLAVOUR@4 +CURL_@CURL_LIBCURL_VERSIONED_SYMBOLS_PREFIX@@CURL_LIBCURL_VERSIONED_SYMBOLS_SONAME@ { global: curl_*; local: *; diff --git a/contrib/curl/lib/llist.c b/contrib/curl/lib/llist.c index 482aaa0410..e5c65fb90e 100644 --- a/contrib/curl/lib/llist.c +++ b/contrib/curl/lib/llist.c @@ -5,11 +5,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2016, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -18,6 +18,8 @@ * 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" @@ -30,30 +32,34 @@ /* this must be the last include file */ #include "memdebug.h" +#define LLISTINIT 0x100cc001 /* random pattern */ +#define NODEINIT 0x12344321 /* random pattern */ +#define NODEREM 0x54321012 /* random pattern */ + + +#ifdef DEBUGBUILD +#define VERIFYNODE(x) verifynode(x) +static struct Curl_llist_node *verifynode(struct Curl_llist_node *n) +{ + DEBUGASSERT(!n || (n->_init == NODEINIT)); + return n; +} +#else +#define VERIFYNODE(x) x +#endif /* * @unittest: 1300 */ -static void -llist_init(struct curl_llist *l, curl_llist_dtor dtor) -{ - l->size = 0; - l->dtor = dtor; - l->head = NULL; - l->tail = NULL; -} - -struct curl_llist * -Curl_llist_alloc(curl_llist_dtor dtor) +void +Curl_llist_init(struct Curl_llist *l, Curl_llist_dtor dtor) { - struct curl_llist *list; - - list = malloc(sizeof(struct curl_llist)); - if(!list) - return NULL; - - llist_init(list, dtor); - - return list; + l->_size = 0; + l->_dtor = dtor; + l->_head = NULL; + l->_tail = NULL; +#ifdef DEBUGBUILD + l->_init = LLISTINIT; +#endif } /* @@ -63,152 +69,198 @@ Curl_llist_alloc(curl_llist_dtor dtor) * entry is NULL and the list already has elements, the new one will be * inserted first in the list. * - * Returns: 1 on success and 0 on failure. + * The 'ne' argument should be a pointer into the object to store. * * @unittest: 1300 */ -int -Curl_llist_insert_next(struct curl_llist *list, struct curl_llist_element *e, - const void *p) +void +Curl_llist_insert_next(struct Curl_llist *list, + struct Curl_llist_node *e, /* may be NULL */ + const void *p, + struct Curl_llist_node *ne) { - struct curl_llist_element *ne = malloc(sizeof(struct curl_llist_element)); - if(!ne) - return 0; - - ne->ptr = (void *) p; - if(list->size == 0) { - list->head = ne; - list->head->prev = NULL; - list->head->next = NULL; - list->tail = ne; + DEBUGASSERT(list); + DEBUGASSERT(list->_init == LLISTINIT); + DEBUGASSERT(ne); + +#ifdef DEBUGBUILD + ne->_init = NODEINIT; +#endif + ne->_ptr = (void *) p; + ne->_list = list; + 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; + ne->_next = e ? e->_next : list->_head; + ne->_prev = e; if(!e) { - list->head->prev = ne; - list->head = ne; + list->_head->_prev = ne; + list->_head = ne; } - else if(e->next) { - e->next->prev = ne; + else if(e->_next) { + e->_next->_prev = ne; } else { - list->tail = ne; + list->_tail = ne; } if(e) - e->next = ne; + e->_next = ne; } - ++list->size; + ++list->_size; +} - return 1; +/* + * Curl_llist_append() + * + * Adds a new list element to the end of the list. + * + * The 'ne' argument should be a pointer into the object to store. + * + * @unittest: 1300 + */ +void +Curl_llist_append(struct Curl_llist *list, const void *p, + struct Curl_llist_node *ne) +{ + DEBUGASSERT(list); + DEBUGASSERT(list->_init == LLISTINIT); + DEBUGASSERT(ne); + Curl_llist_insert_next(list, list->_tail, p, ne); } /* * @unittest: 1300 */ -int -Curl_llist_remove(struct curl_llist *list, struct curl_llist_element *e, - void *user) +void +Curl_node_uremove(struct Curl_llist_node *e, void *user) { - if(e == NULL || list->size == 0) - return 1; + void *ptr; + struct Curl_llist *list; + if(!e) + return; - if(e == list->head) { - list->head = e->next; + list = e->_list; + DEBUGASSERT(list); + DEBUGASSERT(list->_init == LLISTINIT); + DEBUGASSERT(list->_size); + DEBUGASSERT(e->_init == NODEINIT); + if(e == list->_head) { + list->_head = e->_next; - if(list->head == NULL) - list->tail = NULL; + if(!list->_head) + list->_tail = NULL; else - e->next->prev = NULL; + e->_next->_prev = NULL; } else { - e->prev->next = e->next; - if(!e->next) - list->tail = e->prev; + if(e->_prev) + e->_prev->_next = e->_next; + + if(!e->_next) + list->_tail = e->_prev; else - e->next->prev = e->prev; + e->_next->_prev = e->_prev; } - list->dtor(user, e->ptr); + ptr = e->_ptr; + + e->_list = NULL; + e->_ptr = NULL; + e->_prev = NULL; + e->_next = NULL; +#ifdef DEBUGBUILD + e->_init = NODEREM; /* specific pattern on remove - not zero */ +#endif - e->ptr = NULL; - e->prev = NULL; - e->next = NULL; + --list->_size; - free(e); - --list->size; + /* call the dtor() last for when it actually frees the 'e' memory itself */ + if(list->_dtor) + list->_dtor(user, ptr); +} - return 1; +void Curl_node_remove(struct Curl_llist_node *e) +{ + Curl_node_uremove(e, NULL); } void -Curl_llist_destroy(struct curl_llist *list, void *user) +Curl_llist_destroy(struct Curl_llist *list, void *user) { if(list) { - while(list->size > 0) - Curl_llist_remove(list, list->tail, user); - - free(list); + DEBUGASSERT(list->_init == LLISTINIT); + while(list->_size > 0) + Curl_node_uremove(list->_tail, user); } } -size_t -Curl_llist_count(struct curl_llist *list) +/* Curl_llist_head() returns the first 'struct Curl_llist_node *', which + might be NULL */ +struct Curl_llist_node *Curl_llist_head(struct Curl_llist *list) { - return list->size; + DEBUGASSERT(list); + DEBUGASSERT(list->_init == LLISTINIT); + return VERIFYNODE(list->_head); } -/* - * @unittest: 1300 - */ -int Curl_llist_move(struct curl_llist *list, struct curl_llist_element *e, - struct curl_llist *to_list, - struct curl_llist_element *to_e) +#ifdef UNITTESTS +/* Curl_llist_tail() returns the last 'struct Curl_llist_node *', which + might be NULL */ +struct Curl_llist_node *Curl_llist_tail(struct Curl_llist *list) { - /* Remove element from list */ - if(e == NULL || list->size == 0) - return 0; + DEBUGASSERT(list); + DEBUGASSERT(list->_init == LLISTINIT); + return VERIFYNODE(list->_tail); +} +#endif - if(e == list->head) { - list->head = e->next; +/* Curl_llist_count() returns a size_t the number of nodes in the list */ +size_t Curl_llist_count(struct Curl_llist *list) +{ + DEBUGASSERT(list); + DEBUGASSERT(list->_init == LLISTINIT); + return list->_size; +} - if(list->head == NULL) - list->tail = NULL; - else - e->next->prev = NULL; - } - else { - e->prev->next = e->next; - if(!e->next) - list->tail = e->prev; - else - e->next->prev = e->prev; - } +/* Curl_node_elem() returns the custom data from a Curl_llist_node */ +void *Curl_node_elem(struct Curl_llist_node *n) +{ + DEBUGASSERT(n); + DEBUGASSERT(n->_init == NODEINIT); + return n->_ptr; +} - --list->size; +/* Curl_node_next() returns the next element in a list from a given + Curl_llist_node */ +struct Curl_llist_node *Curl_node_next(struct Curl_llist_node *n) +{ + DEBUGASSERT(n); + DEBUGASSERT(n->_init == NODEINIT); + return VERIFYNODE(n->_next); +} - /* Add element to to_list after to_e */ - if(to_list->size == 0) { - to_list->head = e; - to_list->head->prev = NULL; - to_list->head->next = NULL; - to_list->tail = e; - } - else { - e->next = to_e->next; - e->prev = to_e; - if(to_e->next) { - to_e->next->prev = e; - } - else { - to_list->tail = e; - } - to_e->next = e; - } +#ifdef UNITTESTS + +/* Curl_node_prev() returns the previous element in a list from a given + Curl_llist_node */ +struct Curl_llist_node *Curl_node_prev(struct Curl_llist_node *n) +{ + DEBUGASSERT(n); + DEBUGASSERT(n->_init == NODEINIT); + return VERIFYNODE(n->_prev); +} - ++to_list->size; +#endif - return 1; +struct Curl_llist *Curl_node_llist(struct Curl_llist_node *n) +{ + DEBUGASSERT(n); + DEBUGASSERT(!n->_list || n->_init == NODEINIT); + return n->_list; } diff --git a/contrib/curl/lib/llist.h b/contrib/curl/lib/llist.h index 39ff408eeb..26581869a3 100644 --- a/contrib/curl/lib/llist.h +++ b/contrib/curl/lib/llist.h @@ -7,11 +7,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2010, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -20,38 +20,70 @@ * 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 -typedef void (*curl_llist_dtor)(void *, void *); +typedef void (*Curl_llist_dtor)(void *user, void *elem); -struct curl_llist_element { - void *ptr; +/* none of these struct members should be referenced directly, use the + dedicated functions */ - struct curl_llist_element *prev; - struct curl_llist_element *next; +struct Curl_llist { + struct Curl_llist_node *_head; + struct Curl_llist_node *_tail; + Curl_llist_dtor _dtor; + size_t _size; +#ifdef DEBUGBUILD + int _init; /* detect API usage mistakes */ +#endif }; -struct curl_llist { - struct curl_llist_element *head; - struct curl_llist_element *tail; +struct Curl_llist_node { + struct Curl_llist *_list; /* the list where this belongs */ + void *_ptr; + struct Curl_llist_node *_prev; + struct Curl_llist_node *_next; +#ifdef DEBUGBUILD + int _init; /* detect API usage mistakes */ +#endif +}; - curl_llist_dtor dtor; +void Curl_llist_init(struct Curl_llist *, Curl_llist_dtor); +void Curl_llist_insert_next(struct Curl_llist *, struct Curl_llist_node *, + const void *, struct Curl_llist_node *node); +void Curl_llist_append(struct Curl_llist *, + const void *, struct Curl_llist_node *node); +void Curl_node_uremove(struct Curl_llist_node *, void *); +void Curl_node_remove(struct Curl_llist_node *); +void Curl_llist_destroy(struct Curl_llist *, void *); - size_t size; -}; +/* Curl_llist_head() returns the first 'struct Curl_llist_node *', which + might be NULL */ +struct Curl_llist_node *Curl_llist_head(struct Curl_llist *list); -struct curl_llist *Curl_llist_alloc(curl_llist_dtor); -int Curl_llist_insert_next(struct curl_llist *, struct curl_llist_element *, - const void *); -int 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 *); -int Curl_llist_move(struct curl_llist *, struct curl_llist_element *, - struct curl_llist *, struct curl_llist_element *); +/* Curl_llist_tail() returns the last 'struct Curl_llist_node *', which + might be NULL */ +struct Curl_llist_node *Curl_llist_tail(struct Curl_llist *list); -#endif /* HEADER_CURL_LLIST_H */ +/* Curl_llist_count() returns a size_t the number of nodes in the list */ +size_t Curl_llist_count(struct Curl_llist *list); + +/* Curl_node_elem() returns the custom data from a Curl_llist_node */ +void *Curl_node_elem(struct Curl_llist_node *n); +/* Curl_node_next() returns the next element in a list from a given + Curl_llist_node */ +struct Curl_llist_node *Curl_node_next(struct Curl_llist_node *n); + +/* Curl_node_prev() returns the previous element in a list from a given + Curl_llist_node */ +struct Curl_llist_node *Curl_node_prev(struct Curl_llist_node *n); + +/* Curl_node_llist() return the list the node is in or NULL. */ +struct Curl_llist *Curl_node_llist(struct Curl_llist_node *n); + +#endif /* HEADER_CURL_LLIST_H */ diff --git a/contrib/curl/lib/macos.c b/contrib/curl/lib/macos.c new file mode 100644 index 0000000000..e4662be1d3 --- /dev/null +++ b/contrib/curl/lib/macos.c @@ -0,0 +1,53 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#ifdef CURL_MACOS_CALL_COPYPROXIES + +#include + +#include "macos.h" + +#include + +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 does not support system-wide HTTP proxies, we + * therefore do not 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/contrib/curl/lib/macos.h b/contrib/curl/lib/macos.h new file mode 100644 index 0000000000..637860e80f --- /dev/null +++ b/contrib/curl/lib/macos.h @@ -0,0 +1,39 @@ +#ifndef HEADER_CURL_MACOS_H +#define HEADER_CURL_MACOS_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#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/contrib/curl/lib/makefile.amiga b/contrib/curl/lib/makefile.amiga deleted file mode 100644 index c692e5ebe3..0000000000 --- a/contrib/curl/lib/makefile.amiga +++ /dev/null @@ -1,21 +0,0 @@ -# -# libcurl Makefile for AmigaOS ... -# - -# change the follow to where you have the AmiTCP SDK v4.3 includes: - -ATCPSDKI= /GG/netinclude - - -CC = m68k-amigaos-gcc -CFLAGS = -I$(ATCPSDKI) -m68020-60 -O2 -msoft-float -noixemul -g -I. -I../include -W -Wall - -include Makefile.inc -OBJS = $(CSOURCES:.c=.o) - -all: $(OBJS) - ar cru libcurl.a $(OBJS) - ranlib libcurl.a - -install: - $(INSTALL) -c ./libcurl.a /lib/libcurl.a diff --git a/contrib/curl/lib/makefile.dj b/contrib/curl/lib/makefile.dj deleted file mode 100644 index 2331afe313..0000000000 --- a/contrib/curl/lib/makefile.dj +++ /dev/null @@ -1,73 +0,0 @@ -#*************************************************************************** -# _ _ ____ _ -# Project ___| | | | _ \| | -# / __| | | | |_) | | -# | (__| |_| | _ <| |___ -# \___|\___/|_| \_\_____| -# -# Copyright (C) 2003 - 2008, Gisle Vanem . -# Copyright (C) 2003 - 2015, Daniel Stenberg, , et al. -# -# This software is licensed as described in the file COPYING, which -# you should have received as part of this distribution. The terms -# are also available at https://curl.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. -# -#*************************************************************************** - -# -# Adapted for djgpp2 / Watt-32 / DOS -# - -DEPEND_PREREQ = curl_config.h -VPATH = vtls -TOPDIR = .. - -include ../packages/DOS/common.dj -include Makefile.inc - -SOURCES = $(sort $(CSOURCES)) -OBJECTS = $(addprefix $(OBJ_DIR)/, $(notdir $(SOURCES:.c=.o))) - -CURL_LIB = libcurl.a - -# NOTE: if ../include/curl/curlbuild.h is missing, you're probably building -# this from a git checkout and then you need to run buildconf.bat first. - -all: $(OBJ_DIR) curl_config.h $(CURL_LIB) - -$(CURL_LIB): $(OBJECTS) - ar rs $@ $? - -curl_config.h: config-dos.h - $(COPY) $^ $@ - -# clean generated files -# -genclean: - - $(DELETE) curl_config.h - -# clean object files and subdir -# -objclean: genclean - - $(DELETE) $(OBJ_DIR)$(DS)*.o - - $(RMDIR) $(OBJ_DIR) - -# clean without removing built library -# -clean: objclean - - $(DELETE) depend.dj - -# clean everything -# -realclean vclean: clean - - $(DELETE) $(CURL_LIB) - --include depend.dj - diff --git a/contrib/curl/lib/md4.c b/contrib/curl/lib/md4.c index 1bdc9f367c..f006bdcf05 100644 --- a/contrib/curl/lib/md4.c +++ b/contrib/curl/lib/md4.c @@ -1,15 +1,239 @@ -/* +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#if defined(USE_CURL_NTLM_CORE) + +#include + +#include "strdup.h" +#include "curl_md4.h" +#include "warnless.h" + +#ifdef USE_OPENSSL +#include +#if (OPENSSL_VERSION_NUMBER >= 0x30000000L) && !defined(USE_AMISSL) +/* OpenSSL 3.0.0 marks the MD4 functions as deprecated */ +#define OPENSSL_NO_MD4 +#else +/* Cover also OPENSSL_NO_MD4 configured in openssl */ +#include +#endif +#endif /* USE_OPENSSL */ + +#ifdef USE_WOLFSSL +#include +#define VOID_MD4_INIT +#ifdef NO_MD4 +#define WOLFSSL_NO_MD4 +#endif +#endif + +#ifdef USE_MBEDTLS +#include +#if MBEDTLS_VERSION_NUMBER >= 0x03000000 +#include +#else +#include +#endif +#if(MBEDTLS_VERSION_NUMBER >= 0x02070000) && \ + (MBEDTLS_VERSION_NUMBER < 0x03000000) + #define HAS_MBEDTLS_RESULT_CODE_BASED_FUNCTIONS +#endif +#endif /* USE_MBEDTLS */ + +#if defined(USE_GNUTLS) +#include +/* When OpenSSL or wolfSSL is available, we use their MD4 functions. */ +#elif defined(USE_WOLFSSL) && !defined(WOLFSSL_NO_MD4) +#include +#elif defined(USE_OPENSSL) && !defined(OPENSSL_NO_MD4) +#include +#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 +#elif defined(USE_WIN32_CRYPTO) +#include +#elif(defined(USE_MBEDTLS) && defined(MBEDTLS_MD4_C)) +#include +#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 = Curl_memdup(data, size); + if(ctx->data) + 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 does not + * 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: - http://openwall.info/wiki/people/solar/software/public-domain-source-code/md4 + https://openwall.info/wiki/people/solar/software/public-domain-source-code/md4 * * Author: * Alexander Peslyak, better known as Solar Designer * - * 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 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 @@ -18,47 +242,35 @@ * Redistribution and use in source and binary forms, with or without * modification, are permitted. * - * There's ABSOLUTELY NO WARRANTY, express or implied. + * There is 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 + * unsigned integer data type will do), there is 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 + * 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. */ -#include "curl_setup.h" - -/* NSS and OS/400 crypto library do not provide the MD4 hash algorithm, so - * that we have a local implementation of it */ -#if defined(USE_NSS) || defined(USE_OS400CRYPTO) - -#include "curl_md4.h" -#include "warnless.h" - -#ifndef HAVE_OPENSSL - -#include - /* Any 32-bit or wider unsigned integer data type will do */ typedef unsigned int MD4_u32plus; -typedef struct { +struct md4_ctx { MD4_u32plus lo, hi; MD4_u32plus a, b, c, d; unsigned char buffer[64]; MD4_u32plus block[16]; -} MD4_CTX; +}; +typedef struct md4_ctx MD4_CTX; -static void MD4_Init(MD4_CTX *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); @@ -68,14 +280,14 @@ static void MD4_Final(unsigned char *result, MD4_CTX *ctx); * 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)) +#define MD4_F(x, y, z) ((z) ^ ((x) & ((y) ^ (z)))) +#define MD4_G(x, y, z) (((x) & ((y) | (z))) | ((y) & (z))) +#define MD4_H(x, y, z) ((x) ^ (y) ^ (z)) /* * The MD4 transformation for all three rounds. */ -#define STEP(f, a, b, c, d, x, s) \ +#define MD4_STEP(f, a, b, c, d, x, s) \ (a) += f((b), (c), (d)) + (x); \ (a) = (((a) << (s)) | (((a) & 0xffffffff) >> (32 - (s)))); @@ -84,34 +296,34 @@ static void MD4_Final(unsigned char *result, MD4_CTX *ctx); * 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. + * memory accesses is just an optimization. Nothing will break if it + * does not work. */ #if defined(__i386__) || defined(__x86_64__) || defined(__vax__) -#define SET(n) \ - (*(MD4_u32plus *)&ptr[(n) * 4]) -#define GET(n) \ - SET(n) +#define MD4_SET(n) \ + (*(MD4_u32plus *)(void *)&ptr[(n) * 4]) +#define MD4_GET(n) \ + MD4_SET(n) #else -#define SET(n) \ +#define MD4_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) \ +#define MD4_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. + * the bit counters. There are no alignment requirements. */ -static const void *body(MD4_CTX *ctx, const void *data, unsigned long size) +static const void *my_md4_body(MD4_CTX *ctx, + const void *data, unsigned long size) { const unsigned char *ptr; MD4_u32plus a, b, c, d; - MD4_u32plus saved_a, saved_b, saved_c, saved_d; ptr = (const unsigned char *)data; @@ -121,66 +333,68 @@ static const void *body(MD4_CTX *ctx, const void *data, unsigned long size) 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) + MD4_STEP(MD4_F, a, b, c, d, MD4_SET(0), 3) + MD4_STEP(MD4_F, d, a, b, c, MD4_SET(1), 7) + MD4_STEP(MD4_F, c, d, a, b, MD4_SET(2), 11) + MD4_STEP(MD4_F, b, c, d, a, MD4_SET(3), 19) + MD4_STEP(MD4_F, a, b, c, d, MD4_SET(4), 3) + MD4_STEP(MD4_F, d, a, b, c, MD4_SET(5), 7) + MD4_STEP(MD4_F, c, d, a, b, MD4_SET(6), 11) + MD4_STEP(MD4_F, b, c, d, a, MD4_SET(7), 19) + MD4_STEP(MD4_F, a, b, c, d, MD4_SET(8), 3) + MD4_STEP(MD4_F, d, a, b, c, MD4_SET(9), 7) + MD4_STEP(MD4_F, c, d, a, b, MD4_SET(10), 11) + MD4_STEP(MD4_F, b, c, d, a, MD4_SET(11), 19) + MD4_STEP(MD4_F, a, b, c, d, MD4_SET(12), 3) + MD4_STEP(MD4_F, d, a, b, c, MD4_SET(13), 7) + MD4_STEP(MD4_F, c, d, a, b, MD4_SET(14), 11) + MD4_STEP(MD4_F, b, c, d, a, MD4_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) + MD4_STEP(MD4_G, a, b, c, d, MD4_GET(0) + 0x5a827999, 3) + MD4_STEP(MD4_G, d, a, b, c, MD4_GET(4) + 0x5a827999, 5) + MD4_STEP(MD4_G, c, d, a, b, MD4_GET(8) + 0x5a827999, 9) + MD4_STEP(MD4_G, b, c, d, a, MD4_GET(12) + 0x5a827999, 13) + MD4_STEP(MD4_G, a, b, c, d, MD4_GET(1) + 0x5a827999, 3) + MD4_STEP(MD4_G, d, a, b, c, MD4_GET(5) + 0x5a827999, 5) + MD4_STEP(MD4_G, c, d, a, b, MD4_GET(9) + 0x5a827999, 9) + MD4_STEP(MD4_G, b, c, d, a, MD4_GET(13) + 0x5a827999, 13) + MD4_STEP(MD4_G, a, b, c, d, MD4_GET(2) + 0x5a827999, 3) + MD4_STEP(MD4_G, d, a, b, c, MD4_GET(6) + 0x5a827999, 5) + MD4_STEP(MD4_G, c, d, a, b, MD4_GET(10) + 0x5a827999, 9) + MD4_STEP(MD4_G, b, c, d, a, MD4_GET(14) + 0x5a827999, 13) + MD4_STEP(MD4_G, a, b, c, d, MD4_GET(3) + 0x5a827999, 3) + MD4_STEP(MD4_G, d, a, b, c, MD4_GET(7) + 0x5a827999, 5) + MD4_STEP(MD4_G, c, d, a, b, MD4_GET(11) + 0x5a827999, 9) + MD4_STEP(MD4_G, b, c, d, a, MD4_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; + MD4_STEP(MD4_H, a, b, c, d, MD4_GET(0) + 0x6ed9eba1, 3) + MD4_STEP(MD4_H, d, a, b, c, MD4_GET(8) + 0x6ed9eba1, 9) + MD4_STEP(MD4_H, c, d, a, b, MD4_GET(4) + 0x6ed9eba1, 11) + MD4_STEP(MD4_H, b, c, d, a, MD4_GET(12) + 0x6ed9eba1, 15) + MD4_STEP(MD4_H, a, b, c, d, MD4_GET(2) + 0x6ed9eba1, 3) + MD4_STEP(MD4_H, d, a, b, c, MD4_GET(10) + 0x6ed9eba1, 9) + MD4_STEP(MD4_H, c, d, a, b, MD4_GET(6) + 0x6ed9eba1, 11) + MD4_STEP(MD4_H, b, c, d, a, MD4_GET(14) + 0x6ed9eba1, 15) + MD4_STEP(MD4_H, a, b, c, d, MD4_GET(1) + 0x6ed9eba1, 3) + MD4_STEP(MD4_H, d, a, b, c, MD4_GET(9) + 0x6ed9eba1, 9) + MD4_STEP(MD4_H, c, d, a, b, MD4_GET(5) + 0x6ed9eba1, 11) + MD4_STEP(MD4_H, b, c, d, a, MD4_GET(13) + 0x6ed9eba1, 15) + MD4_STEP(MD4_H, a, b, c, d, MD4_GET(3) + 0x6ed9eba1, 3) + MD4_STEP(MD4_H, d, a, b, c, MD4_GET(11) + 0x6ed9eba1, 9) + MD4_STEP(MD4_H, c, d, a, b, MD4_GET(7) + 0x6ed9eba1, 11) + MD4_STEP(MD4_H, b, c, d, a, MD4_GET(15) + 0x6ed9eba1, 15) + + a += saved_a; b += saved_b; c += saved_c; d += saved_d; @@ -196,7 +410,7 @@ static const void *body(MD4_CTX *ctx, const void *data, unsigned long size) return ptr; } -static void MD4_Init(MD4_CTX *ctx) +static int MD4_Init(MD4_CTX *ctx) { ctx->a = 0x67452301; ctx->b = 0xefcdab89; @@ -205,12 +419,13 @@ static void MD4_Init(MD4_CTX *ctx) 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, available; + unsigned long used; saved_lo = ctx->lo; ctx->lo = (saved_lo + size) & 0x1fffffff; @@ -221,7 +436,7 @@ static void MD4_Update(MD4_CTX *ctx, const void *data, unsigned long size) used = saved_lo & 0x3f; if(used) { - available = 64 - used; + unsigned long available = 64 - used; if(size < available) { memcpy(&ctx->buffer[used], data, size); @@ -231,11 +446,11 @@ static void MD4_Update(MD4_CTX *ctx, const void *data, unsigned long size) memcpy(&ctx->buffer[used], data, available); data = (const unsigned char *)data + available; size -= available; - body(ctx, ctx->buffer, 64); + my_md4_body(ctx, ctx->buffer, 64); } if(size >= 64) { - data = body(ctx, data, size & ~(unsigned long)0x3f); + data = my_md4_body(ctx, data, size & ~(unsigned long)0x3f); size &= 0x3f; } @@ -254,7 +469,7 @@ static void MD4_Final(unsigned char *result, MD4_CTX *ctx) if(available < 8) { memset(&ctx->buffer[used], 0, available); - body(ctx, ctx->buffer, 64); + my_md4_body(ctx, ctx->buffer, 64); used = 0; available = 64; } @@ -271,7 +486,7 @@ static void MD4_Final(unsigned char *result, MD4_CTX *ctx) ctx->buffer[62] = curlx_ultouc((ctx->hi >> 16)&0xff); ctx->buffer[63] = curlx_ultouc(ctx->hi >> 24); - body(ctx, ctx->buffer, 64); + my_md4_body(ctx, ctx->buffer, 64); result[0] = curlx_ultouc((ctx->a)&0xff); result[1] = curlx_ultouc((ctx->a >> 8)&0xff); @@ -293,13 +508,23 @@ static void MD4_Final(unsigned char *result, MD4_CTX *ctx) memset(ctx, 0, sizeof(*ctx)); } -#endif +#endif /* CRYPTO LIBS */ -void Curl_md4it(unsigned char *output, const unsigned char *input, size_t len) +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 /* defined(USE_NSS) || defined(USE_OS400CRYPTO) */ + +#endif /* USE_CURL_NTLM_CORE */ diff --git a/contrib/curl/lib/md5.c b/contrib/curl/lib/md5.c index f2dc16c099..7b51429b48 100644 --- a/contrib/curl/lib/md5.c +++ b/contrib/curl/lib/md5.c @@ -5,11 +5,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2016, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -18,140 +18,217 @@ * 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_CRYPTO_AUTH +#if (defined(USE_CURL_NTLM_CORE) && !defined(USE_WINDOWS_SSPI)) \ + || !defined(CURL_DISABLE_DIGEST_AUTH) +#include #include #include "curl_md5.h" #include "curl_hmac.h" #include "warnless.h" -#if defined(USE_GNUTLS_NETTLE) +#ifdef USE_MBEDTLS +#include +#if(MBEDTLS_VERSION_NUMBER >= 0x02070000) && \ + (MBEDTLS_VERSION_NUMBER < 0x03000000) + #define HAS_MBEDTLS_RESULT_CODE_BASED_FUNCTIONS +#endif +#endif /* USE_MBEDTLS */ + +#ifdef USE_OPENSSL + #include + #if !defined(OPENSSL_NO_MD5) && !defined(OPENSSL_NO_DEPRECATED_3_0) + #define USE_OPENSSL_MD5 + #endif +#endif + +#ifdef USE_WOLFSSL + #include + #ifndef NO_MD5 + #define USE_WOLFSSL_MD5 + #endif +#endif + +#if defined(USE_GNUTLS) #include +#elif defined(USE_OPENSSL_MD5) +#include +#elif defined(USE_WOLFSSL_MD5) +#include +#elif defined(USE_MBEDTLS) +#include +#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 +#elif defined(USE_WIN32_CRYPTO) +#include +#endif + +/* The last 3 #include files should be in this order */ +#include "curl_printf.h" #include "curl_memory.h" -/* The last #include file should be: */ #include "memdebug.h" -typedef struct md5_ctx MD5_CTX; +#if defined(USE_GNUTLS) + +typedef struct md5_ctx my_md5_ctx; -static void MD5_Init(MD5_CTX * ctx) +static CURLcode my_md5_init(my_md5_ctx *ctx) { md5_init(ctx); + return CURLE_OK; } -static void MD5_Update(MD5_CTX * ctx, - const unsigned char *input, - unsigned int inputLen) +static void my_md5_update(my_md5_ctx *ctx, + const unsigned char *input, + unsigned int inputLen) { md5_update(ctx, inputLen, input); } -static void MD5_Final(unsigned char digest[16], MD5_CTX * ctx) +static void my_md5_final(unsigned char *digest, my_md5_ctx *ctx) { md5_digest(ctx, 16, digest); } -#elif defined(USE_GNUTLS) +#elif defined(USE_OPENSSL_MD5) || defined(USE_WOLFSSL_MD5) -#include -#include "curl_memory.h" -/* The last #include file should be: */ -#include "memdebug.h" +typedef MD5_CTX my_md5_ctx; -typedef gcry_md_hd_t 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 MD5_Init(MD5_CTX * ctx) +static void my_md5_update(my_md5_ctx *ctx, + const unsigned char *input, + unsigned int len) { - gcry_md_open(ctx, GCRY_MD_MD5, 0); + (void)MD5_Update(ctx, input, len); } -static void MD5_Update(MD5_CTX * ctx, - const unsigned char *input, - unsigned int inputLen) +static void my_md5_final(unsigned char *digest, my_md5_ctx *ctx) { - gcry_md_write(*ctx, input, inputLen); + (void)MD5_Final(digest, ctx); } -static void MD5_Final(unsigned char digest[16], MD5_CTX * ctx) +#elif defined(USE_MBEDTLS) + +typedef mbedtls_md5_context my_md5_ctx; + +static CURLcode my_md5_init(my_md5_ctx *ctx) { - memcpy(digest, gcry_md_read(*ctx, 0), 16); - gcry_md_close(*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; } -#elif defined(USE_OPENSSL) -/* When OpenSSL is available we use the MD5-function from OpenSSL */ -#include -#include "curl_memory.h" -/* The last #include file should be: */ -#include "memdebug.h" +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 +} -#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)) +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. + and later. If you are 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. */ -# include -# define MD5_CTX CC_MD5_CTX -#include "curl_memory.h" -/* The last #include file should be: */ -#include "memdebug.h" +# define my_md5_ctx CC_MD5_CTX -static void MD5_Init(MD5_CTX *ctx) +static CURLcode my_md5_init(my_md5_ctx *ctx) { - CC_MD5_Init(ctx); + if(!CC_MD5_Init(ctx)) + return CURLE_OUT_OF_MEMORY; + + return CURLE_OK; } -static void MD5_Update(MD5_CTX *ctx, - const unsigned char *input, - unsigned int inputLen) +static void my_md5_update(my_md5_ctx *ctx, + const unsigned char *input, + unsigned int inputLen) { CC_MD5_Update(ctx, input, inputLen); } -static void MD5_Final(unsigned char digest[16], MD5_CTX *ctx) +static void my_md5_final(unsigned char *digest, my_md5_ctx *ctx) { CC_MD5_Final(digest, ctx); } -#elif defined(_WIN32) && !defined(CURL_WINDOWS_APP) +#elif defined(USE_WIN32_CRYPTO) -#include -#include "curl_memory.h" -/* The last #include file should be: */ -#include "memdebug.h" - -typedef struct { +struct md5_ctx { HCRYPTPROV hCryptProv; HCRYPTHASH hHash; -} MD5_CTX; +}; +typedef struct md5_ctx my_md5_ctx; -static void MD5_Init(MD5_CTX *ctx) +static CURLcode my_md5_init(my_md5_ctx *ctx) { - if(CryptAcquireContext(&ctx->hCryptProv, NULL, NULL, - PROV_RSA_FULL, CRYPT_VERIFYCONTEXT)) { - CryptCreateHash(ctx->hCryptProv, CALG_MD5, 0, 0, &ctx->hHash); + 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 MD5_Update(MD5_CTX *ctx, - const unsigned char *input, - unsigned int inputLen) +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 MD5_Final(unsigned char digest[16], MD5_CTX *ctx) +static void my_md5_final(unsigned char *digest, my_md5_ctx *ctx) { unsigned long length = 0; CryptGetHashParam(ctx->hHash, HP_HASHVAL, NULL, &length, 0); @@ -163,26 +240,21 @@ static void MD5_Final(unsigned char digest[16], MD5_CTX *ctx) CryptReleaseContext(ctx->hCryptProv, 0); } -#elif defined(USE_AXTLS) -#include -#include -#include -#include "curl_memory.h" -/* The last #include file should be: */ -#include "memdebug.h" #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: - http://openwall.info/wiki/people/solar/software/public-domain-source-code/md5 + https://openwall.info/wiki/people/solar/software/public-domain-source-code/md5 * * Author: * Alexander Peslyak, better known as Solar Designer * - * This software was written by Alexander Peslyak in 2001. No copyright is + * 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 @@ -192,42 +264,38 @@ static void MD5_Final(unsigned char digest[16], MD5_CTX *ctx) * Redistribution and use in source and binary forms, with or without * modification, are permitted. * - * There's ABSOLUTELY NO WARRANTY, express or implied. + * There is 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 + * unsigned integer data type will do), there is 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 + * 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. */ -#include - -/* The last #include files should be: */ -#include "curl_memory.h" -#include "memdebug.h" - /* Any 32-bit or wider unsigned integer data type will do */ typedef unsigned int MD5_u32plus; -typedef struct { +struct md5_ctx { MD5_u32plus lo, hi; MD5_u32plus a, b, c, d; unsigned char buffer[64]; MD5_u32plus block[16]; -} MD5_CTX; +}; +typedef struct md5_ctx my_md5_ctx; -static void MD5_Init(MD5_CTX *ctx); -static void MD5_Update(MD5_CTX *ctx, const void *data, unsigned long size); -static void MD5_Final(unsigned char *result, MD5_CTX *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. @@ -236,16 +304,16 @@ static void MD5_Final(unsigned char *result, MD5_CTX *ctx); * 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))) +#define MD5_F(x, y, z) ((z) ^ ((x) & ((y) ^ (z)))) +#define MD5_G(x, y, z) ((y) ^ ((z) & ((x) ^ (y)))) +#define MD5_H(x, y, z) (((x) ^ (y)) ^ (z)) +#define MD5_H2(x, y, z) ((x) ^ ((y) ^ (z))) +#define MD5_I(x, y, z) ((y) ^ ((x) | ~(z))) /* * The MD5 transformation for all four rounds. */ -#define STEP(f, a, b, c, d, x, t, s) \ +#define MD5_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); @@ -255,34 +323,34 @@ static void MD5_Final(unsigned char *result, MD5_CTX *ctx); * 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. + * memory accesses is just an optimization. Nothing will break if it + * does not work. */ #if defined(__i386__) || defined(__x86_64__) || defined(__vax__) -#define SET(n) \ - (*(MD5_u32plus *)&ptr[(n) * 4]) -#define GET(n) \ - SET(n) +#define MD5_SET(n) \ + (*(MD5_u32plus *)(void *)&ptr[(n) * 4]) +#define MD5_GET(n) \ + MD5_SET(n) #else -#define SET(n) \ +#define MD5_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) \ +#define MD5_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. + * the bit counters. There are no alignment requirements. */ -static const void *body(MD5_CTX *ctx, const void *data, unsigned long size) +static const void *my_md5_body(my_md5_ctx *ctx, + const void *data, unsigned long size) { const unsigned char *ptr; MD5_u32plus a, b, c, d; - MD5_u32plus saved_a, saved_b, saved_c, saved_d; ptr = (const unsigned char *)data; @@ -292,84 +360,86 @@ static const void *body(MD5_CTX *ctx, const void *data, unsigned long size) 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) + MD5_STEP(MD5_F, a, b, c, d, MD5_SET(0), 0xd76aa478, 7) + MD5_STEP(MD5_F, d, a, b, c, MD5_SET(1), 0xe8c7b756, 12) + MD5_STEP(MD5_F, c, d, a, b, MD5_SET(2), 0x242070db, 17) + MD5_STEP(MD5_F, b, c, d, a, MD5_SET(3), 0xc1bdceee, 22) + MD5_STEP(MD5_F, a, b, c, d, MD5_SET(4), 0xf57c0faf, 7) + MD5_STEP(MD5_F, d, a, b, c, MD5_SET(5), 0x4787c62a, 12) + MD5_STEP(MD5_F, c, d, a, b, MD5_SET(6), 0xa8304613, 17) + MD5_STEP(MD5_F, b, c, d, a, MD5_SET(7), 0xfd469501, 22) + MD5_STEP(MD5_F, a, b, c, d, MD5_SET(8), 0x698098d8, 7) + MD5_STEP(MD5_F, d, a, b, c, MD5_SET(9), 0x8b44f7af, 12) + MD5_STEP(MD5_F, c, d, a, b, MD5_SET(10), 0xffff5bb1, 17) + MD5_STEP(MD5_F, b, c, d, a, MD5_SET(11), 0x895cd7be, 22) + MD5_STEP(MD5_F, a, b, c, d, MD5_SET(12), 0x6b901122, 7) + MD5_STEP(MD5_F, d, a, b, c, MD5_SET(13), 0xfd987193, 12) + MD5_STEP(MD5_F, c, d, a, b, MD5_SET(14), 0xa679438e, 17) + MD5_STEP(MD5_F, b, c, d, a, MD5_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) + MD5_STEP(MD5_G, a, b, c, d, MD5_GET(1), 0xf61e2562, 5) + MD5_STEP(MD5_G, d, a, b, c, MD5_GET(6), 0xc040b340, 9) + MD5_STEP(MD5_G, c, d, a, b, MD5_GET(11), 0x265e5a51, 14) + MD5_STEP(MD5_G, b, c, d, a, MD5_GET(0), 0xe9b6c7aa, 20) + MD5_STEP(MD5_G, a, b, c, d, MD5_GET(5), 0xd62f105d, 5) + MD5_STEP(MD5_G, d, a, b, c, MD5_GET(10), 0x02441453, 9) + MD5_STEP(MD5_G, c, d, a, b, MD5_GET(15), 0xd8a1e681, 14) + MD5_STEP(MD5_G, b, c, d, a, MD5_GET(4), 0xe7d3fbc8, 20) + MD5_STEP(MD5_G, a, b, c, d, MD5_GET(9), 0x21e1cde6, 5) + MD5_STEP(MD5_G, d, a, b, c, MD5_GET(14), 0xc33707d6, 9) + MD5_STEP(MD5_G, c, d, a, b, MD5_GET(3), 0xf4d50d87, 14) + MD5_STEP(MD5_G, b, c, d, a, MD5_GET(8), 0x455a14ed, 20) + MD5_STEP(MD5_G, a, b, c, d, MD5_GET(13), 0xa9e3e905, 5) + MD5_STEP(MD5_G, d, a, b, c, MD5_GET(2), 0xfcefa3f8, 9) + MD5_STEP(MD5_G, c, d, a, b, MD5_GET(7), 0x676f02d9, 14) + MD5_STEP(MD5_G, b, c, d, a, MD5_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) + MD5_STEP(MD5_H, a, b, c, d, MD5_GET(5), 0xfffa3942, 4) + MD5_STEP(MD5_H2, d, a, b, c, MD5_GET(8), 0x8771f681, 11) + MD5_STEP(MD5_H, c, d, a, b, MD5_GET(11), 0x6d9d6122, 16) + MD5_STEP(MD5_H2, b, c, d, a, MD5_GET(14), 0xfde5380c, 23) + MD5_STEP(MD5_H, a, b, c, d, MD5_GET(1), 0xa4beea44, 4) + MD5_STEP(MD5_H2, d, a, b, c, MD5_GET(4), 0x4bdecfa9, 11) + MD5_STEP(MD5_H, c, d, a, b, MD5_GET(7), 0xf6bb4b60, 16) + MD5_STEP(MD5_H2, b, c, d, a, MD5_GET(10), 0xbebfbc70, 23) + MD5_STEP(MD5_H, a, b, c, d, MD5_GET(13), 0x289b7ec6, 4) + MD5_STEP(MD5_H2, d, a, b, c, MD5_GET(0), 0xeaa127fa, 11) + MD5_STEP(MD5_H, c, d, a, b, MD5_GET(3), 0xd4ef3085, 16) + MD5_STEP(MD5_H2, b, c, d, a, MD5_GET(6), 0x04881d05, 23) + MD5_STEP(MD5_H, a, b, c, d, MD5_GET(9), 0xd9d4d039, 4) + MD5_STEP(MD5_H2, d, a, b, c, MD5_GET(12), 0xe6db99e5, 11) + MD5_STEP(MD5_H, c, d, a, b, MD5_GET(15), 0x1fa27cf8, 16) + MD5_STEP(MD5_H2, b, c, d, a, MD5_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; + MD5_STEP(MD5_I, a, b, c, d, MD5_GET(0), 0xf4292244, 6) + MD5_STEP(MD5_I, d, a, b, c, MD5_GET(7), 0x432aff97, 10) + MD5_STEP(MD5_I, c, d, a, b, MD5_GET(14), 0xab9423a7, 15) + MD5_STEP(MD5_I, b, c, d, a, MD5_GET(5), 0xfc93a039, 21) + MD5_STEP(MD5_I, a, b, c, d, MD5_GET(12), 0x655b59c3, 6) + MD5_STEP(MD5_I, d, a, b, c, MD5_GET(3), 0x8f0ccc92, 10) + MD5_STEP(MD5_I, c, d, a, b, MD5_GET(10), 0xffeff47d, 15) + MD5_STEP(MD5_I, b, c, d, a, MD5_GET(1), 0x85845dd1, 21) + MD5_STEP(MD5_I, a, b, c, d, MD5_GET(8), 0x6fa87e4f, 6) + MD5_STEP(MD5_I, d, a, b, c, MD5_GET(15), 0xfe2ce6e0, 10) + MD5_STEP(MD5_I, c, d, a, b, MD5_GET(6), 0xa3014314, 15) + MD5_STEP(MD5_I, b, c, d, a, MD5_GET(13), 0x4e0811a1, 21) + MD5_STEP(MD5_I, a, b, c, d, MD5_GET(4), 0xf7537e82, 6) + MD5_STEP(MD5_I, d, a, b, c, MD5_GET(11), 0xbd3af235, 10) + MD5_STEP(MD5_I, c, d, a, b, MD5_GET(2), 0x2ad7d2bb, 15) + MD5_STEP(MD5_I, b, c, d, a, MD5_GET(9), 0xeb86d391, 21) + + a += saved_a; b += saved_b; c += saved_c; d += saved_d; @@ -385,7 +455,7 @@ static const void *body(MD5_CTX *ctx, const void *data, unsigned long size) return ptr; } -static void MD5_Init(MD5_CTX *ctx) +static CURLcode my_md5_init(my_md5_ctx *ctx) { ctx->a = 0x67452301; ctx->b = 0xefcdab89; @@ -394,12 +464,15 @@ static void MD5_Init(MD5_CTX *ctx) ctx->lo = 0; ctx->hi = 0; + + return CURLE_OK; } -static void MD5_Update(MD5_CTX *ctx, const void *data, unsigned long size) +static void my_md5_update(my_md5_ctx *ctx, const void *data, + unsigned long size) { MD5_u32plus saved_lo; - unsigned long used, available; + unsigned long used; saved_lo = ctx->lo; ctx->lo = (saved_lo + size) & 0x1fffffff; @@ -410,7 +483,7 @@ static void MD5_Update(MD5_CTX *ctx, const void *data, unsigned long size) used = saved_lo & 0x3f; if(used) { - available = 64 - used; + unsigned long available = 64 - used; if(size < available) { memcpy(&ctx->buffer[used], data, size); @@ -420,18 +493,18 @@ static void MD5_Update(MD5_CTX *ctx, const void *data, unsigned long size) memcpy(&ctx->buffer[used], data, available); data = (const unsigned char *)data + available; size -= available; - body(ctx, ctx->buffer, 64); + my_md5_body(ctx, ctx->buffer, 64); } if(size >= 64) { - data = body(ctx, data, size & ~(unsigned long)0x3f); + data = my_md5_body(ctx, data, size & ~(unsigned long)0x3f); size &= 0x3f; } memcpy(ctx->buffer, data, size); } -static void MD5_Final(unsigned char *result, MD5_CTX *ctx) +static void my_md5_final(unsigned char *result, my_md5_ctx *ctx) { unsigned long used, available; @@ -443,7 +516,7 @@ static void MD5_Final(unsigned char *result, MD5_CTX *ctx) if(available < 8) { memset(&ctx->buffer[used], 0, available); - body(ctx, ctx->buffer, 64); + my_md5_body(ctx, ctx->buffer, 64); used = 0; available = 64; } @@ -460,7 +533,7 @@ static void MD5_Final(unsigned char *result, MD5_CTX *ctx) ctx->buffer[62] = curlx_ultouc((ctx->hi >> 16)&0xff); ctx->buffer[63] = curlx_ultouc(ctx->hi >> 24); - body(ctx, ctx->buffer, 64); + my_md5_body(ctx, ctx->buffer, 64); result[0] = curlx_ultouc((ctx->a)&0xff); result[1] = curlx_ultouc((ctx->a >> 8)&0xff); @@ -484,45 +557,62 @@ static void MD5_Final(unsigned char *result, MD5_CTX *ctx) #endif /* CRYPTO LIBS */ -const HMAC_params Curl_HMAC_MD5[] = { +const struct HMAC_params Curl_HMAC_MD5[] = { { - (HMAC_hinit_func) MD5_Init, /* Hash initialization function. */ - (HMAC_hupdate_func) MD5_Update, /* Hash update function. */ - (HMAC_hfinal_func) MD5_Final, /* Hash computation end function. */ - sizeof(MD5_CTX), /* Size of hash context structure. */ - 64, /* Maximum key length. */ - 16 /* Result size. */ + /* 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 MD5_params Curl_DIGEST_MD5[] = { +const struct MD5_params Curl_DIGEST_MD5[] = { { - (Curl_MD5_init_func) MD5_Init, /* Digest initialization function */ - (Curl_MD5_update_func) MD5_Update, /* Digest update function */ - (Curl_MD5_final_func) MD5_Final, /* Digest computation end function */ - sizeof(MD5_CTX), /* Size of digest context struct */ - 16 /* Result size */ + /* 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. */ -void Curl_md5it(unsigned char *outbuffer, /* 16 bytes */ - const unsigned char *input) +CURLcode Curl_md5it(unsigned char *outbuffer, const unsigned char *input, + const size_t len) { - MD5_CTX ctx; - MD5_Init(&ctx); - MD5_Update(&ctx, input, curlx_uztoui(strlen((char *)input))); - MD5_Final(outbuffer, &ctx); + 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; } -MD5_context *Curl_MD5_init(const MD5_params *md5params) +struct MD5_context *Curl_MD5_init(const struct MD5_params *md5params) { - MD5_context *ctxt; + struct MD5_context *ctxt; /* Create MD5 context */ - ctxt = malloc(sizeof *ctxt); + ctxt = malloc(sizeof(*ctxt)); if(!ctxt) return ctxt; @@ -536,28 +626,32 @@ MD5_context *Curl_MD5_init(const MD5_params *md5params) ctxt->md5_hash = md5params; - (*md5params->md5_init_func)(ctxt->md5_hashctx); + if((*md5params->md5_init_func)(ctxt->md5_hashctx)) { + free(ctxt->md5_hashctx); + free(ctxt); + return NULL; + } return ctxt; } -int Curl_MD5_update(MD5_context *context, - const unsigned char *data, - unsigned int len) +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 0; + return CURLE_OK; } -int Curl_MD5_final(MD5_context *context, unsigned char *result) +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 0; + return CURLE_OK; } -#endif /* CURL_DISABLE_CRYPTO_AUTH */ +#endif /* Using NTLM (without SSPI) || Digest */ diff --git a/contrib/curl/lib/memdebug.c b/contrib/curl/lib/memdebug.c index 15e8661617..bc83d3eea3 100644 --- a/contrib/curl/lib/memdebug.c +++ b/contrib/curl/lib/memdebug.c @@ -5,11 +5,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2016, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -18,6 +18,8 @@ * 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" @@ -28,63 +30,13 @@ #include "urldata.h" -#define MEMDEBUG_NODEFINES /* don't redefine the standard functions */ +#define MEMDEBUG_NODEFINES /* do not 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" -#ifndef HAVE_ASSERT_H -# define assert(x) Curl_nop_stmt -#endif - -/* - * Until 2011-08-17 libcurl's Memory Tracking feature also performed - * automatic malloc and free filling operations using 0xA5 and 0x13 - * values. Our own preinitialization of dynamically allocated memory - * might be useful when not using third party memory debuggers, but - * on the other hand this would fool memory debuggers into thinking - * that all dynamically allocated memory is properly initialized. - * - * As a default setting, libcurl's Memory Tracking feature no longer - * performs preinitialization of dynamically allocated memory on its - * own. If you know what you are doing, and really want to retain old - * behavior, you can achieve this compiling with preprocessor symbols - * CURL_MT_MALLOC_FILL and CURL_MT_FREE_FILL defined with appropriate - * values. - */ - -#ifdef CURL_MT_MALLOC_FILL -# if (CURL_MT_MALLOC_FILL < 0) || (CURL_MT_MALLOC_FILL > 0xff) -# error "invalid CURL_MT_MALLOC_FILL or out of range" -# endif -#endif - -#ifdef CURL_MT_FREE_FILL -# if (CURL_MT_FREE_FILL < 0) || (CURL_MT_FREE_FILL > 0xff) -# error "invalid CURL_MT_FREE_FILL or out of range" -# endif -#endif - -#if defined(CURL_MT_MALLOC_FILL) && defined(CURL_MT_FREE_FILL) -# if (CURL_MT_MALLOC_FILL == CURL_MT_FREE_FILL) -# error "CURL_MT_MALLOC_FILL same as CURL_MT_FREE_FILL" -# endif -#endif - -#ifdef CURL_MT_MALLOC_FILL -# define mt_malloc_fill(buf,len) memset((buf), CURL_MT_MALLOC_FILL, (len)) -#else -# define mt_malloc_fill(buf,len) Curl_nop_stmt -#endif - -#ifdef CURL_MT_FREE_FILL -# define mt_free_fill(buf,len) memset((buf), CURL_MT_FREE_FILL, (len)) -#else -# define mt_free_fill(buf,len) Curl_nop_stmt -#endif - struct memdebug { size_t size; union { @@ -92,8 +44,8 @@ struct memdebug { 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 :-( */ + /* I am hoping this is the thing with the strictest alignment + * requirements. That also means we waste some space :-( */ }; /* @@ -101,32 +53,49 @@ struct memdebug { * remain so. For advanced analysis, record a log file and write perl scripts * to analyze them! * - * Don't use these with multithreaded test programs! + * Do not use these with multithreaded test programs! */ -#define logfile curl_debuglogfile -FILE *curl_debuglogfile = NULL; +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 */ -/* this sets the log file name */ -void curl_memdebug(const char *logname) +/* 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(!logfile) { + if(curl_dbg_logfile && + curl_dbg_logfile != stderr && + curl_dbg_logfile != stdout) { + fclose(curl_dbg_logfile); + } + curl_dbg_logfile = NULL; +} + +/* this sets the log filename */ +void curl_dbg_memdebug(const char *logname) +{ + if(!curl_dbg_logfile) { if(logname && *logname) - logfile = fopen(logname, FOPEN_WRITETEXT); + curl_dbg_logfile = fopen(logname, FOPEN_WRITETEXT); else - logfile = stderr; + curl_dbg_logfile = stderr; #ifdef MEMDEBUG_LOG_SYNC - /* Flush the log file after every line so the log isn't lost in a crash */ - setbuf(logfile, (char *)NULL); + /* Flush the log file after every line so the log is not 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_memlimit(long limit) +void curl_dbg_memlimit(long limit) { if(!memlimit) { memlimit = TRUE; @@ -134,23 +103,21 @@ void curl_memlimit(long limit) } } -/* returns TRUE if this isn't allowed! */ +/* returns TRUE if this is not 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) { - if(source) { - /* log to file */ - curl_memlog("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(logfile); /* because it might crash now */ - } - SET_ERRNO(ENOMEM); + /* 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 @@ -162,42 +129,41 @@ static bool countcheck(const char *func, int line, const char *source) return FALSE; /* allow this */ } -void *curl_domalloc(size_t wantedsize, int line, const char *source) +ALLOC_FUNC void *curl_dbg_malloc(size_t wantedsize, + int line, const char *source) { struct memdebug *mem; size_t size; - assert(wantedsize != 0); + DEBUGASSERT(wantedsize != 0); if(countcheck("malloc", line, source)) return NULL; /* alloc at least 64 bytes */ - size = sizeof(struct memdebug)+wantedsize; + size = sizeof(struct memdebug) + wantedsize; mem = (Curl_cmalloc)(size); if(mem) { - /* fill memory with junk */ - mt_malloc_fill(mem->mem, wantedsize); mem->size = wantedsize; } if(source) - curl_memlog("MEM %s:%d malloc(%zu) = %p\n", - source, line, wantedsize, - mem ? (void *)mem->mem : (void *)0); + curl_dbg_log("MEM %s:%d malloc(%zu) = %p\n", + source, line, wantedsize, + mem ? (void *)mem->mem : (void *)0); return (mem ? mem->mem : NULL); } -void *curl_docalloc(size_t wanted_elements, size_t wanted_size, - int line, const char *source) +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; - assert(wanted_elements != 0); - assert(wanted_size != 0); + DEBUGASSERT(wanted_elements != 0); + DEBUGASSERT(wanted_size != 0); if(countcheck("calloc", line, source)) return NULL; @@ -211,43 +177,45 @@ void *curl_docalloc(size_t wanted_elements, size_t wanted_size, mem->size = user_size; if(source) - curl_memlog("MEM %s:%d calloc(%zu,%zu) = %p\n", - source, line, wanted_elements, wanted_size, - mem ? (void *)mem->mem : (void *)0); + 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); } -char *curl_dostrdup(const char *str, int line, const char *source) +ALLOC_FUNC char *curl_dbg_strdup(const char *str, + int line, const char *source) { char *mem; size_t len; - assert(str != NULL); + DEBUGASSERT(str != NULL); if(countcheck("strdup", line, source)) return NULL; - len=strlen(str)+1; + len = strlen(str) + 1; - mem=curl_domalloc(len, 0, NULL); /* NULL prevents logging */ + mem = curl_dbg_malloc(len, 0, NULL); /* NULL prevents logging */ if(mem) memcpy(mem, str, len); if(source) - curl_memlog("MEM %s:%d strdup(%p) (%zu) = %p\n", - source, line, (void *)str, len, (void *)mem); + 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) -wchar_t *curl_dowcsdup(const wchar_t *str, int line, const char *source) +#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; - assert(str != NULL); + DEBUGASSERT(str != NULL); if(countcheck("wcsdup", line, source)) return NULL; @@ -255,12 +223,12 @@ wchar_t *curl_dowcsdup(const wchar_t *str, int line, const char *source) wsiz = wcslen(str) + 1; bsiz = wsiz * sizeof(wchar_t); - mem = curl_domalloc(bsiz, 0, NULL); /* NULL prevents logging */ + mem = curl_dbg_malloc(bsiz, 0, NULL); /* NULL prevents logging */ if(mem) memcpy(mem, str, bsiz); if(source) - curl_memlog("MEM %s:%d wcsdup(%p) (%zu) = %p\n", + curl_dbg_log("MEM %s:%d wcsdup(%p) (%zu) = %p\n", source, line, (void *)str, bsiz, (void *)mem); return mem; @@ -269,14 +237,14 @@ wchar_t *curl_dowcsdup(const wchar_t *str, int line, const char *source) /* We provide a realloc() that accepts a NULL as pointer, which then performs a malloc(). In order to work with ares. */ -void *curl_dorealloc(void *ptr, size_t wantedsize, - int line, const char *source) +void *curl_dbg_realloc(void *ptr, size_t wantedsize, + int line, const char *source) { - struct memdebug *mem=NULL; + struct memdebug *mem = NULL; - size_t size = sizeof(struct memdebug)+wantedsize; + size_t size = sizeof(struct memdebug) + wantedsize; - assert(wantedsize != 0); + DEBUGASSERT(wantedsize != 0); if(countcheck("realloc", line, source)) return NULL; @@ -296,7 +264,7 @@ void *curl_dorealloc(void *ptr, size_t wantedsize, mem = (Curl_crealloc)(mem, size); if(source) - curl_memlog("MEM %s:%d realloc(%p, %zu) = %p\n", + curl_dbg_log("MEM %s:%d realloc(%p, %zu) = %p\n", source, line, (void *)ptr, wantedsize, mem ? (void *)mem->mem : (void *)0); @@ -308,11 +276,10 @@ void *curl_dorealloc(void *ptr, size_t wantedsize, return NULL; } -void curl_dofree(void *ptr, int line, const char *source) +void curl_dbg_free(void *ptr, int line, const char *source) { - struct memdebug *mem; - if(ptr) { + struct memdebug *mem; #ifdef __INTEL_COMPILER # pragma warning(push) @@ -326,146 +293,154 @@ void curl_dofree(void *ptr, int line, const char *source) # pragma warning(pop) #endif - /* destroy */ - mt_free_fill(mem->mem, mem->size); - /* free for real */ (Curl_cfree)(mem); } - if(source) - curl_memlog("MEM %s:%d free(%p)\n", source, line, (void *)ptr); + if(source && ptr) + curl_dbg_log("MEM %s:%d free(%p)\n", source, line, (void *)ptr); } -curl_socket_t curl_socket(int domain, int type, int protocol, - int line, const char *source) +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; - curl_socket_t sockfd = socket(domain, type, protocol); + sockfd = socket(domain, type, protocol); if(source && (sockfd != CURL_SOCKET_BAD)) - curl_memlog(fmt, source, line, sockfd); + curl_dbg_log("FD %s:%d socket() = %" FMT_SOCKET_T "\n", + source, line, sockfd); return sockfd; } -#ifdef HAVE_SOCKETPAIR -int curl_socketpair(int domain, int type, int protocol, - curl_socket_t socket_vector[2], - int line, const char *source) +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) { - 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"; + 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) +{ int res = socketpair(domain, type, protocol, socket_vector); if(source && (0 == res)) - curl_memlog(fmt, source, line, socket_vector[0], socket_vector[1]); + curl_dbg_log("FD %s:%d socketpair() = " + "%" FMT_SOCKET_T " %" FMT_SOCKET_T "\n", + source, line, socket_vector[0], socket_vector[1]); return res; } #endif -curl_socket_t curl_accept(curl_socket_t s, void *saddr, void *saddrlen, - int line, const char *source) +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_memlog(fmt, source, line, sockfd); + curl_dbg_log("FD %s:%d accept() = %" FMT_SOCKET_T "\n", + source, line, sockfd); return sockfd; } /* separate function to allow libcurl to mark a "faked" close */ -void curl_mark_sclose(curl_socket_t sockfd, int line, const char *source) +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_memlog(fmt, source, line, sockfd); + curl_dbg_log("FD %s:%d sclose(%" FMT_SOCKET_T ")\n", + source, line, sockfd); } /* this is our own defined way to close sockets on *ALL* platforms */ -int curl_sclose(curl_socket_t sockfd, int line, const char *source) +int curl_dbg_sclose(curl_socket_t sockfd, int line, const char *source) { - int res=sclose(sockfd); - curl_mark_sclose(sockfd, line, source); + int res = sclose(sockfd); + curl_dbg_mark_sclose(sockfd, line, source); return res; } -FILE *curl_fopen(const char *file, const char *mode, - int line, const char *source) +ALLOC_FUNC FILE *curl_dbg_fopen(const char *file, const char *mode, + int line, const char *source) { - FILE *res=fopen(file, mode); + FILE *res = fopen(file, mode); if(source) - curl_memlog("FILE %s:%d fopen(\"%s\",\"%s\") = %p\n", + curl_dbg_log("FILE %s:%d fopen(\"%s\",\"%s\") = %p\n", source, line, file, mode, (void *)res); return res; } -#ifdef HAVE_FDOPEN -FILE *curl_fdopen(int filedes, const char *mode, - int line, const char *source) +ALLOC_FUNC FILE *curl_dbg_fdopen(int filedes, const char *mode, + int line, const char *source) { - FILE *res=fdopen(filedes, mode); - + FILE *res = fdopen(filedes, mode); if(source) - curl_memlog("FILE %s:%d fdopen(\"%d\",\"%s\") = %p\n", - source, line, filedes, mode, (void *)res); - + curl_dbg_log("FILE %s:%d fdopen(\"%d\",\"%s\") = %p\n", + source, line, filedes, mode, (void *)res); return res; } -#endif -int curl_fclose(FILE *file, int line, const char *source) +int curl_dbg_fclose(FILE *file, int line, const char *source) { int res; - assert(file != NULL); - - res=fclose(file); + DEBUGASSERT(file != NULL); if(source) - curl_memlog("FILE %s:%d fclose(%p)\n", - source, line, (void *)file); + 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 writting to the memory tracking log file */ -void curl_memlog(const char *format, ...) +/* 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(!logfile) + if(!curl_dbg_logfile) return; buf = (Curl_cmalloc)(LOGLINE_BUFSIZE); @@ -473,14 +448,14 @@ void curl_memlog(const char *format, ...) return; va_start(ap, format); - nchars = vsnprintf(buf, LOGLINE_BUFSIZE, format, ap); + 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, nchars, logfile); + fwrite(buf, 1, (size_t)nchars, curl_dbg_logfile); (Curl_cfree)(buf); } diff --git a/contrib/curl/lib/memdebug.h b/contrib/curl/lib/memdebug.h index 835dab38c7..80f3374e52 100644 --- a/contrib/curl/lib/memdebug.h +++ b/contrib/curl/lib/memdebug.h @@ -8,11 +8,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2016, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -21,6 +21,8 @@ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY * KIND, either express or implied. * + * SPDX-License-Identifier: curl + * ***************************************************************************/ /* @@ -28,126 +30,162 @@ * as well as the library. Do not mix with library internals! */ -#define CURL_MT_LOGFNAME_BUFSIZE 512 +#include +#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 logfile curl_debuglogfile +#define CURL_MT_LOGFNAME_BUFSIZE 512 -extern FILE *logfile; +extern FILE *curl_dbg_logfile; /* memory functions */ -CURL_EXTERN void *curl_domalloc(size_t size, int line, const char *source); -CURL_EXTERN void *curl_docalloc(size_t elements, size_t size, int line, - const char *source); -CURL_EXTERN void *curl_dorealloc(void *ptr, size_t size, int line, - const char *source); -CURL_EXTERN void curl_dofree(void *ptr, int line, const char *source); -CURL_EXTERN char *curl_dostrdup(const char *str, int line, const char *source); -#if defined(WIN32) && defined(UNICODE) -CURL_EXTERN wchar_t *curl_dowcsdup(const wchar_t *str, int line, - const char *source); +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_memdebug(const char *logname); -CURL_EXTERN void curl_memlimit(long limit); -CURL_EXTERN void curl_memlog(const char *format, ...); +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, ...) CURL_PRINTF(1, 2); /* file descriptor manipulators */ -CURL_EXTERN curl_socket_t curl_socket(int domain, int type, int protocol, +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 void curl_mark_sclose(curl_socket_t sockfd, - int line, const char *source); -CURL_EXTERN int curl_sclose(curl_socket_t sockfd, - int line, const char *source); -CURL_EXTERN curl_socket_t curl_accept(curl_socket_t s, void *a, void *alen, - int line, const char *source); -#ifdef HAVE_SOCKETPAIR -CURL_EXTERN int curl_socketpair(int domain, int type, int protocol, - curl_socket_t socket_vector[2], +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 FILE *curl_fopen(const char *file, const char *mode, int line, - const char *source); -#ifdef HAVE_FDOPEN -CURL_EXTERN FILE *curl_fdopen(int filedes, const char *mode, int line, - const char *source); -#endif -CURL_EXTERN int curl_fclose(FILE *file, int line, const char *source); +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_dostrdup(ptr, __LINE__, __FILE__) -#define malloc(size) curl_domalloc(size, __LINE__, __FILE__) -#define calloc(nbelem,size) curl_docalloc(nbelem, size, __LINE__, __FILE__) -#define realloc(ptr,size) curl_dorealloc(ptr, size, __LINE__, __FILE__) -#define free(ptr) curl_dofree(ptr, __LINE__, __FILE__) - -#ifdef WIN32 +#define strdup(ptr) curl_dbg_strdup(ptr, __LINE__, __FILE__) +#undef malloc +#define malloc(size) curl_dbg_malloc(size, __LINE__, __FILE__) +#undef calloc +#define calloc(nbelem,size) curl_dbg_calloc(nbelem, size, __LINE__, __FILE__) +#undef realloc +#define realloc(ptr,size) curl_dbg_realloc(ptr, size, __LINE__, __FILE__) +#undef free +#define free(ptr) curl_dbg_free(ptr, __LINE__, __FILE__) +#undef send +#define send(a,b,c,d) curl_dbg_send(a,b,c,d, __LINE__, __FILE__) +#undef recv +#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_dowcsdup(ptr, __LINE__, __FILE__) +# define wcsdup(ptr) curl_dbg_wcsdup(ptr, __LINE__, __FILE__) # undef _wcsdup -# define _wcsdup(ptr) curl_dowcsdup(ptr, __LINE__, __FILE__) +# define _wcsdup(ptr) curl_dbg_wcsdup(ptr, __LINE__, __FILE__) # undef _tcsdup -# define _tcsdup(ptr) curl_dowcsdup(ptr, __LINE__, __FILE__) +# define _tcsdup(ptr) curl_dbg_wcsdup(ptr, __LINE__, __FILE__) # else # undef _tcsdup -# define _tcsdup(ptr) curl_dostrdup(ptr, __LINE__, __FILE__) +# define _tcsdup(ptr) curl_dbg_strdup(ptr, __LINE__, __FILE__) # endif #endif #undef socket #define socket(domain,type,protocol)\ - curl_socket(domain, type, protocol, __LINE__, __FILE__) + curl_dbg_socket((int)domain, type, protocol, __LINE__, __FILE__) #undef accept /* for those with accept as a macro */ #define accept(sock,addr,len)\ - curl_accept(sock, addr, len, __LINE__, __FILE__) + curl_dbg_accept(sock, addr, len, __LINE__, __FILE__) #ifdef HAVE_SOCKETPAIR #define socketpair(domain,type,protocol,socket_vector)\ - curl_socketpair(domain, type, protocol, socket_vector, __LINE__, __FILE__) + curl_dbg_socketpair((int)domain, type, protocol, socket_vector, \ + __LINE__, __FILE__) #endif +#ifndef CURL_NO_GETADDRINFO_OVERRIDE #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_dogetaddrinfo(host, serv, hint, res, __LINE__, __FILE__) + curl_dbg_getaddrinfo(host, serv, hint, res, __LINE__, __FILE__) #else #undef getaddrinfo #define getaddrinfo(host,serv,hint,res) \ - curl_dogetaddrinfo(host, serv, hint, res, __LINE__, __FILE__) + curl_dbg_getaddrinfo(host, serv, hint, res, __LINE__, __FILE__) #endif #endif /* HAVE_GETADDRINFO */ -#ifdef HAVE_GETNAMEINFO -#undef getnameinfo -#define getnameinfo(sa,salen,host,hostlen,serv,servlen,flags) \ - curl_dogetnameinfo(sa, salen, host, hostlen, serv, servlen, flags, \ - __LINE__, __FILE__) -#endif /* HAVE_GETNAMEINFO */ - #ifdef HAVE_FREEADDRINFO #undef freeaddrinfo #define freeaddrinfo(data) \ - curl_dofreeaddrinfo(data, __LINE__, __FILE__) + curl_dbg_freeaddrinfo(data, __LINE__, __FILE__) #endif /* HAVE_FREEADDRINFO */ +#endif /* !CURL_NO_GETADDRINFO_OVERRIDE */ /* sclose is probably already defined, redefine it! */ #undef sclose -#define sclose(sockfd) curl_sclose(sockfd,__LINE__,__FILE__) +#define sclose(sockfd) curl_dbg_sclose(sockfd,__LINE__,__FILE__) -#define fake_sclose(sockfd) curl_mark_sclose(sockfd,__LINE__,__FILE__) +#define fake_sclose(sockfd) curl_dbg_mark_sclose(sockfd,__LINE__,__FILE__) #undef fopen -#define fopen(file,mode) curl_fopen(file,mode,__LINE__,__FILE__) +#define fopen(file,mode) curl_dbg_fopen(file,mode,__LINE__,__FILE__) #undef fdopen -#define fdopen(file,mode) curl_fdopen(file,mode,__LINE__,__FILE__) -#define fclose(file) curl_fclose(file,__LINE__,__FILE__) +#define fdopen(file,mode) curl_dbg_fdopen(file,mode,__LINE__,__FILE__) +#define fclose(file) curl_dbg_fclose(file,__LINE__,__FILE__) #endif /* MEMDEBUG_NODEFINES */ @@ -168,6 +206,6 @@ CURL_EXTERN int curl_fclose(FILE *file, int line, const char *source); */ #define Curl_safefree(ptr) \ - do { free((ptr)); (ptr) = NULL;} WHILE_FALSE + do { free((ptr)); (ptr) = NULL;} while(0) #endif /* HEADER_CURL_MEMDEBUG_H */ diff --git a/contrib/curl/lib/mime.c b/contrib/curl/lib/mime.c new file mode 100644 index 0000000000..968fc3daee --- /dev/null +++ b/contrib/curl/lib/mime.c @@ -0,0 +1,2278 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#include + +#include "mime.h" +#include "warnless.h" +#include "urldata.h" +#include "sendf.h" +#include "strdup.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 +#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 curl_off_t mime_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 +/* + * 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 &= ~(unsigned int)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 will not 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 will not 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 is 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. */ + DEBUGF(infof(data, "invalid MIMEKIND* attempt")); + 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 filename. */ +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 *ptr, size_t datasize) +{ + if(!part) + return CURLE_BAD_FUNCTION_ARGUMENT; + + cleanup_part_content(part); + + if(ptr) { + if(datasize == CURL_ZERO_TERMINATED) + datasize = strlen(ptr); + + part->data = Curl_memdup0(ptr, datasize); + if(!part->data) + return CURLE_OUT_OF_MEMORY; + + part->datasize = datasize; + 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)) + result = CURLE_READ_ERROR; + else { + part->data = strdup(filename); + if(!part->data) + result = CURLE_OUT_OF_MEMORY; + else { + part->datasize = -1; + if(S_ISREG(sbuf.st_mode)) { + part->datasize = filesize(filename, sbuf); + part->seekfunc = mime_file_seek; + } + + part->readfunc = mime_file_read; + part->freefunc = mime_file_free; + part->kind = MIMEKIND_FILE; + + /* As a side effect, set the filename to the current file's base name. + It is possible to withdraw this by explicitly calling + curl_mime_filename() with a NULL filename argument after the current + call. */ + base = strippath(filename); + if(!base) + result = CURLE_OUT_OF_MEMORY; + else { + result = curl_mime_filename(part, base); + free(base); + } + } + } + } + return result; +} + +/* 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 &= ~(unsigned int)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) { + /* cannot 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. */ + + /* TODO: this loop is broken. If `nitems` is <= 4, some encoders will + * return STOP_FILLING without adding any data and this loops infinitely. */ + 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. */ +static CURLcode 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 = mime_size(part); + + if(sz < 0) + size = sz; + + if(size >= 0) + size += boundarysize + sz; + } + + return size; +} + +/* Get/compute mime size. */ +static curl_off_t 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 = vaprintf(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 will not 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. */ +static void 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) + mime_unpause(subpart); + } + } + } +} + +struct cr_mime_ctx { + struct Curl_creader super; + curl_mimepart *part; + curl_off_t total_len; + curl_off_t read_len; + CURLcode error_result; + BIT(seen_eos); + BIT(errored); +}; + +static CURLcode cr_mime_init(struct Curl_easy *data, + struct Curl_creader *reader) +{ + struct cr_mime_ctx *ctx = reader->ctx; + (void)data; + ctx->total_len = -1; + ctx->read_len = 0; + return CURLE_OK; +} + +/* Real client reader to installed client callbacks. */ +static CURLcode cr_mime_read(struct Curl_easy *data, + struct Curl_creader *reader, + char *buf, size_t blen, + size_t *pnread, bool *peos) +{ + struct cr_mime_ctx *ctx = reader->ctx; + size_t nread; + + + /* Once we have errored, we will return the same error forever */ + if(ctx->errored) { + CURL_TRC_READ(data, "cr_mime_read(len=%zu) is errored -> %d, eos=0", + blen, ctx->error_result); + *pnread = 0; + *peos = FALSE; + return ctx->error_result; + } + if(ctx->seen_eos) { + CURL_TRC_READ(data, "cr_mime_read(len=%zu) seen eos -> 0, eos=1", blen); + *pnread = 0; + *peos = TRUE; + return CURLE_OK; + } + /* respect length limitations */ + if(ctx->total_len >= 0) { + curl_off_t remain = ctx->total_len - ctx->read_len; + if(remain <= 0) + blen = 0; + else if(remain < (curl_off_t)blen) + blen = (size_t)remain; + } + + if(blen <= 4) { + /* TODO: Curl_mime_read() may go into an infinite loop when reading + * such small lengths. Returning 0 bytes read is a fix that only works + * as request upload buffers will get flushed eventually and larger + * reads will happen again. */ + CURL_TRC_READ(data, "cr_mime_read(len=%zu), too small, return", blen); + *pnread = 0; + *peos = FALSE; + goto out; + } + + nread = Curl_mime_read(buf, 1, blen, ctx->part); + CURL_TRC_READ(data, "cr_mime_read(len=%zu), mime_read() -> %zd", + blen, nread); + + switch(nread) { + case 0: + if((ctx->total_len >= 0) && (ctx->read_len < ctx->total_len)) { + failf(data, "client mime read EOF fail, " + "only %"FMT_OFF_T"/%"FMT_OFF_T + " of needed bytes read", ctx->read_len, ctx->total_len); + return CURLE_READ_ERROR; + } + *pnread = 0; + *peos = TRUE; + ctx->seen_eos = TRUE; + break; + + case CURL_READFUNC_ABORT: + failf(data, "operation aborted by callback"); + *pnread = 0; + *peos = FALSE; + ctx->errored = TRUE; + ctx->error_result = CURLE_ABORTED_BY_CALLBACK; + return CURLE_ABORTED_BY_CALLBACK; + + case CURL_READFUNC_PAUSE: + /* CURL_READFUNC_PAUSE pauses read callbacks that feed socket writes */ + CURL_TRC_READ(data, "cr_mime_read(len=%zu), paused by callback", blen); + data->req.keepon |= KEEP_SEND_PAUSE; /* mark socket send as paused */ + *pnread = 0; + *peos = FALSE; + break; /* nothing was read */ + + case STOP_FILLING: + case READ_ERROR: + failf(data, "read error getting mime data"); + *pnread = 0; + *peos = FALSE; + ctx->errored = TRUE; + ctx->error_result = CURLE_READ_ERROR; + return CURLE_READ_ERROR; + + default: + if(nread > blen) { + /* the read function returned a too large value */ + failf(data, "read function returned funny value"); + *pnread = 0; + *peos = FALSE; + ctx->errored = TRUE; + ctx->error_result = CURLE_READ_ERROR; + return CURLE_READ_ERROR; + } + ctx->read_len += nread; + if(ctx->total_len >= 0) + ctx->seen_eos = (ctx->read_len >= ctx->total_len); + *pnread = nread; + *peos = ctx->seen_eos; + break; + } + +out: + CURL_TRC_READ(data, "cr_mime_read(len=%zu, total=%" FMT_OFF_T + ", read=%"FMT_OFF_T") -> %d, %zu, %d", + blen, ctx->total_len, ctx->read_len, CURLE_OK, *pnread, *peos); + return CURLE_OK; +} + +static bool cr_mime_needs_rewind(struct Curl_easy *data, + struct Curl_creader *reader) +{ + struct cr_mime_ctx *ctx = reader->ctx; + (void)data; + return ctx->read_len > 0; +} + +static curl_off_t cr_mime_total_length(struct Curl_easy *data, + struct Curl_creader *reader) +{ + struct cr_mime_ctx *ctx = reader->ctx; + (void)data; + return ctx->total_len; +} + +static CURLcode cr_mime_resume_from(struct Curl_easy *data, + struct Curl_creader *reader, + curl_off_t offset) +{ + struct cr_mime_ctx *ctx = reader->ctx; + + if(offset > 0) { + curl_off_t passed = 0; + + do { + char scratch[4*1024]; + size_t readthisamountnow = + (offset - passed > (curl_off_t)sizeof(scratch)) ? + sizeof(scratch) : + curlx_sotouz(offset - passed); + size_t nread; + + nread = Curl_mime_read(scratch, 1, readthisamountnow, ctx->part); + passed += (curl_off_t)nread; + if((nread == 0) || (nread > readthisamountnow)) { + /* this checks for greater-than only to make sure that the + CURL_READFUNC_ABORT return code still aborts */ + failf(data, "Could only read %" FMT_OFF_T + " bytes from the mime post", passed); + return CURLE_READ_ERROR; + } + } while(passed < offset); + + /* now, decrease the size of the read */ + if(ctx->total_len > 0) { + ctx->total_len -= offset; + + if(ctx->total_len <= 0) { + failf(data, "Mime post already completely uploaded"); + return CURLE_PARTIAL_FILE; + } + } + /* we have passed, proceed as normal */ + } + return CURLE_OK; +} + +static CURLcode cr_mime_rewind(struct Curl_easy *data, + struct Curl_creader *reader) +{ + struct cr_mime_ctx *ctx = reader->ctx; + CURLcode result = mime_rewind(ctx->part); + if(result) + failf(data, "Cannot rewind mime/post data"); + return result; +} + +static CURLcode cr_mime_unpause(struct Curl_easy *data, + struct Curl_creader *reader) +{ + struct cr_mime_ctx *ctx = reader->ctx; + (void)data; + mime_unpause(ctx->part); + return CURLE_OK; +} + +static bool cr_mime_is_paused(struct Curl_easy *data, + struct Curl_creader *reader) +{ + struct cr_mime_ctx *ctx = reader->ctx; + (void)data; + return (ctx->part && ctx->part->lastreadstatus == CURL_READFUNC_PAUSE); +} + +static const struct Curl_crtype cr_mime = { + "cr-mime", + cr_mime_init, + cr_mime_read, + Curl_creader_def_close, + cr_mime_needs_rewind, + cr_mime_total_length, + cr_mime_resume_from, + cr_mime_rewind, + cr_mime_unpause, + cr_mime_is_paused, + Curl_creader_def_done, + sizeof(struct cr_mime_ctx) +}; + +CURLcode Curl_creader_set_mime(struct Curl_easy *data, curl_mimepart *part) +{ + struct Curl_creader *r; + struct cr_mime_ctx *ctx; + CURLcode result; + + result = Curl_creader_create(&r, data, &cr_mime, CURL_CR_CLIENT); + if(result) + return result; + ctx = r->ctx; + ctx->part = part; + /* Make sure we will read the entire mime structure. */ + result = mime_rewind(ctx->part); + if(result) { + Curl_creader_free(data, r); + return result; + } + ctx->total_len = mime_size(ctx->part); + + return Curl_creader_set(data, r); +} + +#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/contrib/curl/lib/mime.h b/contrib/curl/lib/mime.h new file mode 100644 index 0000000000..5073a38f70 --- /dev/null +++ b/contrib/curl/lib/mime.h @@ -0,0 +1,176 @@ +#ifndef HEADER_CURL_MIME_H +#define HEADER_CURL_MIME_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#define 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 filename. */ + 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 filename. */ + 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, ...) + CURL_PRINTF(2, 3); + +#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); +size_t Curl_mime_read(char *buffer, size_t size, size_t nitems, + void *instream); +const char *Curl_mime_contenttype(const char *filename); + +/** + * Install a client reader as upload source that reads the given + * mime part. + */ +CURLcode Curl_creader_set_mime(struct Curl_easy *data, 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_read NULL +#define Curl_creader_set_mime(x,y) ((void)x, CURLE_NOT_BUILT_IN) +#endif + + +#endif /* HEADER_CURL_MIME_H */ diff --git a/contrib/curl/lib/mk-ca-bundle.pl b/contrib/curl/lib/mk-ca-bundle.pl deleted file mode 100644 index 9574f1dbf9..0000000000 --- a/contrib/curl/lib/mk-ca-bundle.pl +++ /dev/null @@ -1,554 +0,0 @@ -#!/usr/bin/perl -w -# *************************************************************************** -# * _ _ ____ _ -# * Project ___| | | | _ \| | -# * / __| | | | |_) | | -# * | (__| |_| | _ <| |___ -# * \___|\___/|_| \_\_____| -# * -# * Copyright (C) 1998 - 2016, Daniel Stenberg, , et al. -# * -# * This software is licensed as described in the file COPYING, which -# * you should have received as part of this distribution. The terms -# * are also available at https://curl.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. -# * -# *************************************************************************** -# This Perl script creates a fresh ca-bundle.crt file for use with libcurl. -# It downloads certdata.txt from Mozilla's source tree (see URL below), -# then parses certdata.txt and extracts CA Root Certificates into PEM format. -# These are then processed with the OpenSSL commandline tool to produce the -# final ca-bundle.crt file. -# The script is based on the parse-certs script written by Roland Krikava. -# This Perl script works on almost any platform since its only external -# dependency is the OpenSSL commandline tool for optional text listing. -# Hacked by Guenter Knauf. -# -use Encode; -use Getopt::Std; -use MIME::Base64; -use strict; -use vars qw($opt_b $opt_d $opt_f $opt_h $opt_i $opt_k $opt_l $opt_m $opt_n $opt_p $opt_q $opt_s $opt_t $opt_u $opt_v $opt_w); -use List::Util; -use Text::Wrap; -my $MOD_SHA = "Digest::SHA"; -eval "require $MOD_SHA"; -if ($@) { - $MOD_SHA = "Digest::SHA::PurePerl"; - eval "require $MOD_SHA"; -} -eval "require LWP::UserAgent"; - -my %urls = ( - 'nss' => - 'https://hg.mozilla.org/projects/nss/raw-file/tip/lib/ckfw/builtins/certdata.txt', - 'central' => - 'https://hg.mozilla.org/mozilla-central/raw-file/default/security/nss/lib/ckfw/builtins/certdata.txt', - 'aurora' => - 'https://hg.mozilla.org/releases/mozilla-aurora/raw-file/default/security/nss/lib/ckfw/builtins/certdata.txt', - 'beta' => - 'https://hg.mozilla.org/releases/mozilla-beta/raw-file/default/security/nss/lib/ckfw/builtins/certdata.txt', - 'release' => - 'https://hg.mozilla.org/releases/mozilla-release/raw-file/default/security/nss/lib/ckfw/builtins/certdata.txt', -); - -$opt_d = 'release'; - -# If the OpenSSL commandline is not in search path you can configure it here! -my $openssl = 'openssl'; - -my $version = '1.27'; - -$opt_w = 76; # default base64 encoded lines length - -# default cert types to include in the output (default is to include CAs which may issue SSL server certs) -my $default_mozilla_trust_purposes = "SERVER_AUTH"; -my $default_mozilla_trust_levels = "TRUSTED_DELEGATOR"; -$opt_p = $default_mozilla_trust_purposes . ":" . $default_mozilla_trust_levels; - -my @valid_mozilla_trust_purposes = ( - "DIGITAL_SIGNATURE", - "NON_REPUDIATION", - "KEY_ENCIPHERMENT", - "DATA_ENCIPHERMENT", - "KEY_AGREEMENT", - "KEY_CERT_SIGN", - "CRL_SIGN", - "SERVER_AUTH", - "CLIENT_AUTH", - "CODE_SIGNING", - "EMAIL_PROTECTION", - "IPSEC_END_SYSTEM", - "IPSEC_TUNNEL", - "IPSEC_USER", - "TIME_STAMPING", - "STEP_UP_APPROVED" -); - -my @valid_mozilla_trust_levels = ( - "TRUSTED_DELEGATOR", # CAs - "NOT_TRUSTED", # Don't trust these certs. - "MUST_VERIFY_TRUST", # This explicitly tells us that it ISN'T a CA but is otherwise ok. In other words, this should tell the app to ignore any other sources that claim this is a CA. - "TRUSTED" # This cert is trusted, but only for itself and not for delegates (i.e. it is not a CA). -); - -my $default_signature_algorithms = $opt_s = "MD5"; - -my @valid_signature_algorithms = ( - "MD5", - "SHA1", - "SHA256", - "SHA384", - "SHA512" -); - -$0 =~ s@.*(/|\\)@@; -$Getopt::Std::STANDARD_HELP_VERSION = 1; -getopts('bd:fhiklmnp:qs:tuvw:'); - -if(!defined($opt_d)) { - # to make plain "-d" use not cause warnings, and actually still work - $opt_d = 'release'; -} - -# Use predefined URL or else custom URL specified on command line. -my $url; -if(defined($urls{$opt_d})) { - $url = $urls{$opt_d}; - if(!$opt_k && $url !~ /^https:\/\//i) { - die "The URL for '$opt_d' is not HTTPS. Use -k to override (insecure).\n"; - } -} -else { - $url = $opt_d; -} - -my $curl = `curl -V`; - -if ($opt_i) { - print ("=" x 78 . "\n"); - print "Script Version : $version\n"; - print "Perl Version : $]\n"; - print "Operating System Name : $^O\n"; - print "Getopt::Std.pm Version : ${Getopt::Std::VERSION}\n"; - print "MIME::Base64.pm Version : ${MIME::Base64::VERSION}\n"; - print "LWP::UserAgent.pm Version : ${LWP::UserAgent::VERSION}\n" if($LWP::UserAgent::VERSION); - print "LWP.pm Version : ${LWP::VERSION}\n" if($LWP::VERSION); - print "Digest::SHA.pm Version : ${Digest::SHA::VERSION}\n" if ($Digest::SHA::VERSION); - print "Digest::SHA::PurePerl.pm Version : ${Digest::SHA::PurePerl::VERSION}\n" if ($Digest::SHA::PurePerl::VERSION); - print ("=" x 78 . "\n"); -} - -sub warning_message() { - if ( $opt_d =~ m/^risk$/i ) { # Long Form Warning and Exit - print "Warning: Use of this script may pose some risk:\n"; - print "\n"; - print " 1) If you use HTTP URLs they are subject to a man in the middle attack\n"; - print " 2) Default to 'release', but more recent updates may be found in other trees\n"; - print " 3) certdata.txt file format may change, lag time to update this script\n"; - print " 4) Generally unwise to blindly trust CAs without manual review & verification\n"; - print " 5) Mozilla apps use additional security checks aren't represented in certdata\n"; - print " 6) Use of this script will make a security engineer grind his teeth and\n"; - print " swear at you. ;)\n"; - exit; - } else { # Short Form Warning - print "Warning: Use of this script may pose some risk, -d risk for more details.\n"; - } -} - -sub HELP_MESSAGE() { - print "Usage:\t${0} [-b] [-d] [-f] [-i] [-k] [-l] [-n] [-p] [-q] [-s] [-t] [-u] [-v] [-w] []\n"; - print "\t-b\tbackup an existing version of ca-bundle.crt\n"; - print "\t-d\tspecify Mozilla tree to pull certdata.txt or custom URL\n"; - print "\t\t Valid names are:\n"; - print "\t\t ", join( ", ", map { ( $_ =~ m/$opt_d/ ) ? "$_ (default)" : "$_" } sort keys %urls ), "\n"; - print "\t-f\tforce rebuild even if certdata.txt is current\n"; - print "\t-i\tprint version info about used modules\n"; - print "\t-k\tallow URLs other than HTTPS, enable HTTP fallback (insecure)\n"; - print "\t-l\tprint license info about certdata.txt\n"; - print "\t-m\tinclude meta data in output\n"; - print "\t-n\tno download of certdata.txt (to use existing)\n"; - print wrap("\t","\t\t", "-p\tlist of Mozilla trust purposes and levels for certificates to include in output. Takes the form of a comma separated list of purposes, a colon, and a comma separated list of levels. (default: $default_mozilla_trust_purposes:$default_mozilla_trust_levels)"), "\n"; - print "\t\t Valid purposes are:\n"; - print wrap("\t\t ","\t\t ", join( ", ", "ALL", @valid_mozilla_trust_purposes ) ), "\n"; - print "\t\t Valid levels are:\n"; - print wrap("\t\t ","\t\t ", join( ", ", "ALL", @valid_mozilla_trust_levels ) ), "\n"; - print "\t-q\tbe really quiet (no progress output at all)\n"; - print wrap("\t","\t\t", "-s\tcomma separated list of certificate signatures/hashes to output in plain text mode. (default: $default_signature_algorithms)\n"); - print "\t\t Valid signature algorithms are:\n"; - print wrap("\t\t ","\t\t ", join( ", ", "ALL", @valid_signature_algorithms ) ), "\n"; - print "\t-t\tinclude plain text listing of certificates\n"; - print "\t-u\tunlink (remove) certdata.txt after processing\n"; - print "\t-v\tbe verbose and print out processed CAs\n"; - print "\t-w \twrap base64 output lines after chars (default: ${opt_w})\n"; - exit; -} - -sub VERSION_MESSAGE() { - print "${0} version ${version} running Perl ${]} on ${^O}\n"; -} - -warning_message() unless ($opt_q || $url =~ m/^(ht|f)tps:/i ); -HELP_MESSAGE() if ($opt_h); - -sub report($@) { - my $output = shift; - - print STDERR $output . "\n" unless $opt_q; -} - -sub is_in_list($@) { - my $target = shift; - - return defined(List::Util::first { $target eq $_ } @_); -} - -# Parses $param_string as a case insensitive comma separated list with optional whitespace -# validates that only allowed parameters are supplied -sub parse_csv_param($$@) { - my $description = shift; - my $param_string = shift; - my @valid_values = @_; - - my @values = map { - s/^\s+//; # strip leading spaces - s/\s+$//; # strip trailing spaces - uc $_ # return the modified string as upper case - } split( ',', $param_string ); - - # Find all values which are not in the list of valid values or "ALL" - my @invalid = grep { !is_in_list($_,"ALL",@valid_values) } @values; - - if ( scalar(@invalid) > 0 ) { - # Tell the user which parameters were invalid and print the standard help message which will exit - print "Error: Invalid ", $description, scalar(@invalid) == 1 ? ": " : "s: ", join( ", ", map { "\"$_\"" } @invalid ), "\n"; - HELP_MESSAGE(); - } - - @values = @valid_values if ( is_in_list("ALL",@values) ); - - return @values; -} - -sub sha256 { - my $result; - if ($Digest::SHA::VERSION || $Digest::SHA::PurePerl::VERSION) { - open(FILE, $_[0]) or die "Can't open '$_[0]': $!"; - binmode(FILE); - $result = $MOD_SHA->new(256)->addfile(*FILE)->hexdigest; - close(FILE); - } else { - # Use OpenSSL command if Perl Digest::SHA modules not available - $result = `"$openssl" dgst -r -sha256 "$_[0]"`; - $result =~ s/^([0-9a-f]{64}) .+/$1/is; - } - return $result; -} - - -sub oldhash { - my $hash = ""; - open(C, "<$_[0]") || return 0; - while() { - chomp; - if($_ =~ /^\#\# SHA256: (.*)/) { - $hash = $1; - last; - } - } - close(C); - return $hash; -} - -if ( $opt_p !~ m/:/ ) { - print "Error: Mozilla trust identifier list must include both purposes and levels\n"; - HELP_MESSAGE(); -} - -(my $included_mozilla_trust_purposes_string, my $included_mozilla_trust_levels_string) = split( ':', $opt_p ); -my @included_mozilla_trust_purposes = parse_csv_param( "trust purpose", $included_mozilla_trust_purposes_string, @valid_mozilla_trust_purposes ); -my @included_mozilla_trust_levels = parse_csv_param( "trust level", $included_mozilla_trust_levels_string, @valid_mozilla_trust_levels ); - -my @included_signature_algorithms = parse_csv_param( "signature algorithm", $opt_s, @valid_signature_algorithms ); - -sub should_output_cert(%) { - my %trust_purposes_by_level = @_; - - foreach my $level (@included_mozilla_trust_levels) { - # for each level we want to output, see if any of our desired purposes are included - return 1 if ( defined( List::Util::first { is_in_list( $_, @included_mozilla_trust_purposes ) } @{$trust_purposes_by_level{$level}} ) ); - } - - return 0; -} - -my $crt = $ARGV[0] || 'ca-bundle.crt'; -(my $txt = $url) =~ s@(.*/|\?.*)@@g; - -my $stdout = $crt eq '-'; -my $resp; -my $fetched; - -my $oldhash = oldhash($crt); - -report "SHA256 of old file: $oldhash"; - -if(!$opt_n) { - report "Downloading $txt ..."; - - # If we have an HTTPS URL then use curl - if($url =~ /^https:\/\//i) { - if($curl) { - if($curl =~ /^Protocols:.* https( |$)/m) { - report "Get certdata with curl!"; - my $proto = !$opt_k ? "--proto =https" : ""; - my $quiet = $opt_q ? "-s" : ""; - my @out = `curl -w %{response_code} $proto $quiet -o "$txt" "$url"`; - if(@out && $out[0] == 200) { - $fetched = 1; - report "Downloaded $txt"; - } - else { - report "Failed downloading via HTTPS with curl"; - if(-e $txt && !unlink($txt)) { - report "Failed to remove '$txt': $!"; - } - } - } - else { - report "curl lacks https support"; - } - } - else { - report "curl not found"; - } - } - - # If nothing was fetched then use LWP - if(!$fetched) { - if($url =~ /^https:\/\//i) { - report "Falling back to HTTP"; - $url =~ s/^https:\/\//http:\/\//i; - } - if(!$opt_k) { - report "URLs other than HTTPS are disabled by default, to enable use -k"; - exit 1; - } - report "Get certdata with LWP!"; - if(!defined(${LWP::UserAgent::VERSION})) { - report "LWP is not available (LWP::UserAgent not found)"; - exit 1; - } - my $ua = new LWP::UserAgent(agent => "$0/$version"); - $ua->env_proxy(); - $resp = $ua->mirror($url, $txt); - if($resp && $resp->code eq '304') { - report "Not modified"; - exit 0 if -e $crt && !$opt_f; - } - else { - $fetched = 1; - report "Downloaded $txt"; - } - if(!$resp || $resp->code !~ /^(?:200|304)$/) { - report "Unable to download latest data: " - . ($resp? $resp->code . ' - ' . $resp->message : "LWP failed"); - exit 1 if -e $crt || ! -r $txt; - } - } -} - -my $filedate = $resp ? $resp->last_modified : (stat($txt))[9]; -my $datesrc = "as of"; -if(!$filedate) { - # mxr.mozilla.org gave us a time, hg.mozilla.org does not! - $filedate = time(); - $datesrc="downloaded on"; -} - -# get the hash from the download file -my $newhash= sha256($txt); - -if(!$opt_f && $oldhash eq $newhash) { - report "Downloaded file identical to previous run\'s source file. Exiting"; - exit; -} - -report "SHA256 of new file: $newhash"; - -my $currentdate = scalar gmtime($filedate); - -my $format = $opt_t ? "plain text and " : ""; -if( $stdout ) { - open(CRT, '> -') or die "Couldn't open STDOUT: $!\n"; -} else { - open(CRT,">$crt.~") or die "Couldn't open $crt.~: $!\n"; -} -print CRT <) { - if (/\*\*\*\*\* BEGIN LICENSE BLOCK \*\*\*\*\*/) { - print CRT; - print if ($opt_l); - while () { - print CRT; - print if ($opt_l); - last if (/\*\*\*\*\* END LICENSE BLOCK \*\*\*\*\*/); - } - } - elsif(/^# (Issuer|Serial Number|Subject|Not Valid Before|Not Valid After |Fingerprint \(MD5\)|Fingerprint \(SHA1\)):/) { - push @precert, $_; - next; - } - elsif(/^#|^\s*$/) { - undef @precert; - next; - } - chomp; - - # this is a match for the start of a certificate - if (/^CKA_CLASS CK_OBJECT_CLASS CKO_CERTIFICATE/) { - $start_of_cert = 1 - } - if ($start_of_cert && /^CKA_LABEL UTF8 \"(.*)\"/) { - $caname = $1; - } - my %trust_purposes_by_level; - if ($start_of_cert && /^CKA_VALUE MULTILINE_OCTAL/) { - my $data; - while () { - last if (/^END/); - chomp; - my @octets = split(/\\/); - shift @octets; - for (@octets) { - $data .= chr(oct); - } - } - # scan forwards until the trust part - while () { - last if (/^CKA_CLASS CK_OBJECT_CLASS CKO_NSS_TRUST/); - chomp; - } - # now scan the trust part to determine how we should trust this cert - while () { - last if (/^#/); - if (/^CKA_TRUST_([A-Z_]+)\s+CK_TRUST\s+CKT_NSS_([A-Z_]+)\s*$/) { - if ( !is_in_list($1,@valid_mozilla_trust_purposes) ) { - report "Warning: Unrecognized trust purpose for cert: $caname. Trust purpose: $1. Trust Level: $2"; - } elsif ( !is_in_list($2,@valid_mozilla_trust_levels) ) { - report "Warning: Unrecognized trust level for cert: $caname. Trust purpose: $1. Trust Level: $2"; - } else { - push @{$trust_purposes_by_level{$2}}, $1; - } - } - } - - if ( !should_output_cert(%trust_purposes_by_level) ) { - $skipnum ++; - } else { - my $encoded = MIME::Base64::encode_base64($data, ''); - $encoded =~ s/(.{1,${opt_w}})/$1\n/g; - my $pem = "-----BEGIN CERTIFICATE-----\n" - . $encoded - . "-----END CERTIFICATE-----\n"; - print CRT "\n$caname\n"; - print CRT @precert if($opt_m); - my $maxStringLength = length(decode('UTF-8', $caname, Encode::FB_CROAK)); - if ($opt_t) { - foreach my $key (keys %trust_purposes_by_level) { - my $string = $key . ": " . join(", ", @{$trust_purposes_by_level{$key}}); - $maxStringLength = List::Util::max( length($string), $maxStringLength ); - print CRT $string . "\n"; - } - } - print CRT ("=" x $maxStringLength . "\n"); - if (!$opt_t) { - print CRT $pem; - } else { - my $pipe = ""; - foreach my $hash (@included_signature_algorithms) { - $pipe = "|$openssl x509 -" . $hash . " -fingerprint -noout -inform PEM"; - if (!$stdout) { - $pipe .= " >> $crt.~"; - close(CRT) or die "Couldn't close $crt.~: $!"; - } - open(TMP, $pipe) or die "Couldn't open openssl pipe: $!"; - print TMP $pem; - close(TMP) or die "Couldn't close openssl pipe: $!"; - if (!$stdout) { - open(CRT, ">>$crt.~") or die "Couldn't open $crt.~: $!"; - } - } - $pipe = "|$openssl x509 -text -inform PEM"; - if (!$stdout) { - $pipe .= " >> $crt.~"; - close(CRT) or die "Couldn't close $crt.~: $!"; - } - open(TMP, $pipe) or die "Couldn't open openssl pipe: $!"; - print TMP $pem; - close(TMP) or die "Couldn't close openssl pipe: $!"; - if (!$stdout) { - open(CRT, ">>$crt.~") or die "Couldn't open $crt.~: $!"; - } - } - report "Parsing: $caname" if ($opt_v); - $certnum ++; - $start_of_cert = 0; - } - undef @precert; - } - -} -close(TXT) or die "Couldn't close $txt: $!\n"; -close(CRT) or die "Couldn't close $crt.~: $!\n"; -unless( $stdout ) { - if ($opt_b && -e $crt) { - my $bk = 1; - while (-e "$crt.~${bk}~") { - $bk++; - } - rename $crt, "$crt.~${bk}~" or die "Failed to create backup $crt.~$bk}~: $!\n"; - } elsif( -e $crt ) { - unlink( $crt ) or die "Failed to remove $crt: $!\n"; - } - rename "$crt.~", $crt or die "Failed to rename $crt.~ to $crt: $!\n"; -} -if($opt_u && -e $txt && !unlink($txt)) { - report "Failed to remove $txt: $!\n"; -} -report "Done ($certnum CA certs processed, $skipnum skipped)."; diff --git a/contrib/curl/lib/mk-ca-bundle.vbs b/contrib/curl/lib/mk-ca-bundle.vbs deleted file mode 100644 index da7a577ea7..0000000000 --- a/contrib/curl/lib/mk-ca-bundle.vbs +++ /dev/null @@ -1,431 +0,0 @@ -'*************************************************************************** -'* _ _ ____ _ -'* Project ___| | | | _ \| | -'* / __| | | | |_) | | -'* | (__| |_| | _ <| |___ -'* \___|\___/|_| \_\_____| -'* -'* Copyright (C) 1998 - 2014, Daniel Stenberg, , et al. -'* -'* This software is licensed as described in the file COPYING, which -'* you should have received as part of this distribution. The terms -'* are also available at https://curl.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. -'* -'*************************************************************************** -'* Script to fetch certdata.txt from Mozilla.org site and create a -'* ca-bundle.crt for use with OpenSSL / libcurl / libcurl bindings -'* Requires WinHttp.WinHttpRequest.5.1 and ADODB.Stream which are part of -'* W2000 SP3 or later, WXP SP1 or later, W2003 Server SP1 or later. -'* Hacked by Guenter Knauf -'*************************************************************************** -Option Explicit -Const myVersion = "0.4.0" - -Const myUrl = "https://hg.mozilla.org/releases/mozilla-release/raw-file/default/security/nss/lib/ckfw/builtins/certdata.txt" - -Const myOpenSSL = "openssl.exe" -Dim myUseOpenSSL -myUseOpenSSL = TRUE ' Flag: TRUE to use OpenSSL. If TRUE and is not - ' found then a warning is shown before continuing. - -Const myCdSavF = TRUE ' Flag: save downloaded data to file certdata.txt -Const myCaBakF = TRUE ' Flag: backup existing ca-bundle certificate -Const myAskLiF = TRUE ' Flag: display certdata.txt license agreement -Const myWrapLe = 76 ' Default length of base64 output lines - -' cert info code doesn't work properly with any recent openssl, leave disabled. -' Also: we want our certificate output by default to be as similar as possible -' to mk-ca-bundle.pl and setting this TRUE changes the base64 width to -' OpenSSL's built-in default width, which is not the same as mk-ca-bundle.pl. -Const myAskTiF = FALSE ' Flag: ask to include certificate text info - -' -'******************* Nothing to configure below! ******************* -' -Const adTypeBinary = 1 -Const adTypeText = 2 -Const adSaveCreateNotExist = 1 -Const adSaveCreateOverWrite = 2 -Dim objShell, objNetwork, objFSO, objHttp -Dim myBase, mySelf, myStream, myTmpFh, myCdData, myCdFile -Dim myCaFile, myTmpName, myBakNum, myOptTxt, i -Set objNetwork = WScript.CreateObject("WScript.Network") -Set objShell = WScript.CreateObject("WScript.Shell") -Set objFSO = WScript.CreateObject("Scripting.FileSystemObject") -Set objHttp = WScript.CreateObject("WinHttp.WinHttpRequest.5.1") -If objHttp Is Nothing Then Set objHttp = WScript.CreateObject("WinHttp.WinHttpRequest") -myBase = Left(WScript.ScriptFullName, InstrRev(WScript.ScriptFullName, "\")) -mySelf = Left(WScript.ScriptName, InstrRev(WScript.ScriptName, ".") - 1) & " " & myVersion - -myCdFile = Mid(myUrl, InstrRev(myUrl, "/") + 1) -myCaFile = "ca-bundle.crt" -myTmpName = InputBox("It will take a minute to download and parse the " & _ - "certificate data." & _ - vbLf & vbLf & _ - "Please enter the output filename:", mySelf, myCaFile) -If (myTmpName = "") Then - WScript.Quit 1 -End If -myCaFile = myTmpName -If (myCdFile = "") Then - MsgBox("URL does not contain filename!"), vbCritical, mySelf - WScript.Quit 1 -End If - -' Don't use OpenSSL if it's not present. -If (myUseOpenSSL = TRUE) Then - Dim errnum - - On Error Resume Next - Call objShell.Run("""" & myOpenSSL & """ version", 0, TRUE) - errnum = Err.Number - On Error GoTo 0 - - If Not (errnum = 0) Then - myUseOpenSSL = FALSE - MsgBox("OpenSSL was not found so the certificate bundle will not " & _ - "include the SHA256 hash of the raw certificate data file " & _ - "that was used to generate the certificates in the bundle. " & _ - vbLf & vbLf & _ - "This does not have any effect on the certificate output, " & _ - "so this script will continue." & _ - vbLf & vbLf & _ - "If you want to set a custom location for OpenSSL or disable " & _ - "this message then edit the variables at the start of the " & _ - "script."), vbInformation, mySelf - End If -End If - -If (myAskTiF = TRUE) And (myUseOpenSSL = TRUE) Then - If (6 = objShell.PopUp("Do you want to include text information about " & _ - "each certificate?" & vbLf & _ - "(Requires OpenSSL.exe in the current directory " & _ - "or search path)",, _ - mySelf, vbQuestion + vbYesNo + vbDefaultButton2)) Then - myOptTxt = TRUE - Else - myOptTxt = FALSE - End If -End If - -' Uncomment the line below to ignore SSL invalid cert errors -' objHttp.Option(4) = 256 + 512 + 4096 + 8192 -objHttp.SetTimeouts 0, 5000, 10000, 10000 -objHttp.Open "GET", myUrl, FALSE -objHttp.setRequestHeader "User-Agent", WScript.ScriptName & "/" & myVersion -objHttp.Send "" -If Not (objHttp.Status = 200) Then - MsgBox("Failed to download '" & myCdFile & "': " & objHttp.Status & " - " & objHttp.StatusText), vbCritical, mySelf - WScript.Quit 1 -End If -' Write received data to file if enabled -If (myCdSavF = TRUE) Then - Call SaveBinaryData(myCdFile, objHttp.ResponseBody) -End If -' Convert data from ResponseBody instead of using ResponseText because of UTF-8 -myCdData = ConvertBinaryToUTF8(objHttp.ResponseBody) -Set objHttp = Nothing -' Backup exitsing ca-bundle certificate file -If (myCaBakF = TRUE) Then - If objFSO.FileExists(myCaFile) Then - Dim myBakFile, b - b = 1 - myBakFile = myCaFile & ".~" & b & "~" - While objFSO.FileExists(myBakFile) - b = b + 1 - myBakFile = myCaFile & ".~" & b & "~" - Wend - Set myTmpFh = objFSO.GetFile(myCaFile) - myTmpFh.Move myBakFile - End If -End If - -' Process the received data -Dim myLines, myPattern, myInsideCert, myInsideLicense, myLicenseText, myNumCerts, myNumSkipped -Dim myLabel, myOctets, myData, myPem, myRev, myUntrusted, j -myNumSkipped = 0 -myNumCerts = 0 -myData = "" -myLines = Split(myCdData, vbLf, -1) -Set myStream = CreateObject("ADODB.Stream") -myStream.Open -myStream.Type = adTypeText -myStream.Charset = "utf-8" -myStream.WriteText "##" & vbLf & _ - "## Bundle of CA Root Certificates" & vbLf & _ - "##" & vbLf & _ - "## Certificate data from Mozilla as of: " & _ - ConvertDateToString(LocalDateToUTC(Now)) & " GMT" & vbLf & _ - "##" & vbLf & _ - "## This is a bundle of X.509 certificates of public Certificate Authorities" & vbLf & _ - "## (CA). These were automatically extracted from Mozilla's root certificates" & vbLf & _ - "## file (certdata.txt). This file can be found in the mozilla source tree:" & vbLf & _ - "## " & myUrl & vbLf & _ - "##" & vbLf & _ - "## It contains the certificates in PEM format and therefore" & vbLf & _ - "## can be directly used with curl / libcurl / php_curl, or with" & vbLf & _ - "## an Apache+mod_ssl webserver for SSL client authentication." & vbLf & _ - "## Just configure this file as the SSLCACertificateFile." & vbLf & _ - "##" & vbLf & _ - "## Conversion done with mk-ca-bundle.vbs version " & myVersion & "." & vbLf -If (myCdSavF = TRUE) And (myUseOpenSSL = TRUE) Then - myStream.WriteText "## SHA256: " & FileSHA256(myCdFile) & vbLf -End If -myStream.WriteText "##" & vbLf & vbLf - -myStream.WriteText vbLf -For i = 0 To UBound(myLines) - If InstrRev(myLines(i), "CKA_LABEL ") Then - myPattern = "^CKA_LABEL\s+[A-Z0-9]+\s+""(.+?)""" - myLabel = RegExprFirst(myPattern, myLines(i)) - End If - If (myInsideCert = TRUE) Then - If InstrRev(myLines(i), "END") Then - myInsideCert = FALSE - While (i < UBound(myLines)) And Not (myLines(i) = "#") - i = i + 1 - If InstrRev(myLines(i), "CKA_TRUST_SERVER_AUTH CK_TRUST CKT_NSS_TRUSTED_DELEGATOR") Then - myUntrusted = FALSE - End If - Wend - If (myUntrusted = TRUE) Then - myNumSkipped = myNumSkipped + 1 - Else - myStream.WriteText myLabel & vbLf - myStream.WriteText String(Len(myLabel), "=") & vbLf - myPem = "-----BEGIN CERTIFICATE-----" & vbLf & _ - Base64Encode(myData) & vbLf & _ - "-----END CERTIFICATE-----" & vbLf - If (myOptTxt = FALSE) Then - myStream.WriteText myPem & vbLf - Else - Dim myCmd, myRval, myTmpIn, myTmpOut - myTmpIn = objFSO.GetSpecialFolder(2).Path & "\" & objFSO.GetTempName - myTmpOut = objFSO.GetSpecialFolder(2).Path & "\" & objFSO.GetTempName - Set myTmpFh = objFSO.OpenTextFile(myTmpIn, 2, TRUE) - myTmpFh.Write myPem - myTmpFh.Close - myCmd = """" & myOpenSSL & """ x509 -md5 -fingerprint -text " & _ - "-inform PEM -in " & myTmpIn & " -out " & myTmpOut - myRval = objShell.Run (myCmd, 0, TRUE) - objFSO.DeleteFile myTmpIn, TRUE - If Not (myRval = 0) Then - MsgBox("Failed to process PEM cert with OpenSSL commandline!"), vbCritical, mySelf - objFSO.DeleteFile myTmpOut, TRUE - WScript.Quit 3 - End If - Set myTmpFh = objFSO.OpenTextFile(myTmpOut, 1) - myStream.WriteText myTmpFh.ReadAll & vbLf - myTmpFh.Close - objFSO.DeleteFile myTmpOut, TRUE - End If - myNumCerts = myNumCerts + 1 - End If - Else - myOctets = Split(myLines(i), "\") - For j = 1 To UBound(myOctets) - myData = myData & Chr(CByte("&o" & myOctets(j))) - Next - End If - End If - If InstrRev(myLines(i), "CVS_ID ") Then - myPattern = "^CVS_ID\s+""(.+?)""" - myRev = RegExprFirst(myPattern, myLines(i)) - myStream.WriteText "# " & myRev & vbLf & vbLf - End If - If InstrRev(myLines(i), "CKA_VALUE MULTILINE_OCTAL") Then - myInsideCert = TRUE - myUntrusted = TRUE - myData = "" - End If - If InstrRev(myLines(i), "***** BEGIN LICENSE BLOCK *****") Then - myInsideLicense = TRUE - End If - If (myInsideLicense = TRUE) Then - myStream.WriteText myLines(i) & vbLf - myLicenseText = myLicenseText & Mid(myLines(i), 2) & vbLf - End If - If InstrRev(myLines(i), "***** END LICENSE BLOCK *****") Then - myInsideLicense = FALSE - If (myAskLiF = TRUE) Then - If Not (6 = objShell.PopUp(myLicenseText & vbLf & _ - "Do you agree to the license shown above (required to proceed) ?",, _ - mySelf, vbQuestion + vbYesNo + vbDefaultButton1)) Then - myStream.Close - objFSO.DeleteFile myCaFile, TRUE - WScript.Quit 2 - End If - End If - End If -Next - -' To stop the UTF-8 BOM from being written the stream has to be copied and -' then saved as binary. -Dim myCopy -Set myCopy = CreateObject("ADODB.Stream") -myCopy.Type = adTypeBinary -myCopy.Open -myStream.Position = 3 ' Skip UTF-8 BOM -myStream.CopyTo myCopy -myCopy.SaveToFile myCaFile, adSaveCreateOverWrite -myCopy.Close -myStream.Close -Set myCopy = Nothing -Set myStream = Nothing - -' Done -objShell.PopUp "Done (" & myNumCerts & " CA certs processed, " & myNumSkipped & _ - " untrusted skipped).", 20, mySelf, vbInformation -WScript.Quit 0 - -Function ConvertBinaryToUTF8(arrBytes) - Dim objStream - Set objStream = CreateObject("ADODB.Stream") - objStream.Open - objStream.Type = adTypeBinary - objStream.Write arrBytes - objStream.Position = 0 - objStream.Type = adTypeText - objStream.Charset = "utf-8" - ConvertBinaryToUTF8 = objStream.ReadText - Set objStream = Nothing -End Function - -Function SaveBinaryData(filename, data) - Dim objStream - Set objStream = CreateObject("ADODB.Stream") - objStream.Type = adTypeBinary - objStream.Open - objStream.Write data - objStream.SaveToFile filename, adSaveCreateOverWrite - objStream.Close - Set objStream = Nothing -End Function - -Function RegExprFirst(SearchPattern, TheString) - Dim objRegExp, Matches ' create variables. - Set objRegExp = New RegExp ' create a regular expression. - objRegExp.Pattern = SearchPattern ' sets the search pattern. - objRegExp.IgnoreCase = TRUE ' set to ignores case. - objRegExp.Global = TRUE ' set to gloabal search. - Set Matches = objRegExp.Execute(TheString) ' do the search. - If (Matches.Count) Then - RegExprFirst = Matches(0).SubMatches(0) ' return first match. - Else - RegExprFirst = "" - End If - Set objRegExp = Nothing -End Function - -Function Base64Encode(inData) - Const Base64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" - Dim cOut, sOut, lWrap, I - lWrap = Int(myWrapLe * 3 / 4) - - 'For each group of 3 bytes - For I = 1 To Len(inData) Step 3 - Dim nGroup, pOut, sGroup - - 'Create one long from this 3 bytes. - nGroup = &H10000 * Asc(Mid(inData, I, 1)) + _ - &H100 * MyASC(Mid(inData, I + 1, 1)) + _ - MyASC(Mid(inData, I + 2, 1)) - - 'Oct splits the long To 8 groups with 3 bits - nGroup = Oct(nGroup) - - 'Add leading zeros - nGroup = String(8 - Len(nGroup), "0") & nGroup - - 'Convert To base64 - pOut = Mid(Base64, CLng("&o" & Mid(nGroup, 1, 2)) + 1, 1) & _ - Mid(Base64, CLng("&o" & Mid(nGroup, 3, 2)) + 1, 1) & _ - Mid(Base64, CLng("&o" & Mid(nGroup, 5, 2)) + 1, 1) & _ - Mid(Base64, CLng("&o" & Mid(nGroup, 7, 2)) + 1, 1) - - 'Add the part To OutPut string - sOut = sOut + pOut - - 'Add a new line For Each myWrapLe chars In dest - If (I < Len(inData) - 2) Then - If (I + 2) Mod lWrap = 0 Then sOut = sOut & vbLf - End If - Next - Select Case Len(inData) Mod 3 - Case 1: '8 bit final - sOut = Left(sOut, Len(sOut) - 2) & "==" - Case 2: '16 bit final - sOut = Left(sOut, Len(sOut) - 1) & "=" - End Select - Base64Encode = sOut -End Function - -Function MyASC(OneChar) - If OneChar = "" Then MyASC = 0 Else MyASC = Asc(OneChar) -End Function - -' Return the date in the same format as perl to match mk-ca-bundle.pl output: -' Wed Sep 7 03:12:05 2016 -Function ConvertDateToString(input) - Dim output - output = WeekDayName(WeekDay(input), TRUE) & " " & _ - MonthName(Month(input), TRUE) & " " - If (Len(Day(input)) = 1) Then - output = output & " " - End If - output = output & _ - Day(input) & " " & _ - FormatDateTime(input, vbShortTime) & ":" - If (Len(Second(input)) = 1) Then - output = output & "0" - End If - output = output & _ - Second(input) & " " & _ - Year(input) - ConvertDateToString = output -End Function - -' Convert local Date to UTC. Microsoft says: -' Use Win32_ComputerSystem CurrentTimeZone property, because it automatically -' adjusts the Time Zone bias for daylight saving time; Win32_Time Zone Bias -' property does not. -' https://msdn.microsoft.com/en-us/library/windows/desktop/ms696015.aspx -Function LocalDateToUTC(localdate) - Dim item, offset - For Each item In GetObject("winmgmts:").InstancesOf("Win32_ComputerSystem") - offset = item.CurrentTimeZone ' the offset in minutes - Next - If (offset < 0) Then - LocalDateToUTC = DateAdd("n", ABS(offset), localdate) - Else - LocalDateToUTC = DateAdd("n", -ABS(offset), localdate) - End If - 'objShell.PopUp LocalDateToUTC -End Function - -Function FileSHA256(filename) - Dim cmd, rval, tmpOut, tmpFh - if (myUseOpenSSL = TRUE) Then - tmpOut = objFSO.GetSpecialFolder(2).Path & "\" & objFSO.GetTempName - cmd = """" & myOpenSSL & """ dgst -r -sha256 -out """ & tmpOut & """ """ & filename & """" - rval = objShell.Run(cmd, 0, TRUE) - If Not (rval = 0) Then - MsgBox("Failed to get sha256 of """ & filename & """ with OpenSSL commandline!"), vbCritical, mySelf - objFSO.DeleteFile tmpOut, TRUE - WScript.Quit 3 - End If - Set tmpFh = objFSO.OpenTextFile(tmpOut, 1) - FileSHA256 = RegExprFirst("^([0-9a-f]{64}) .+", tmpFh.ReadAll) - tmpFh.Close - objFSO.DeleteFile tmpOut, TRUE - Else - FileSHA256 = "" - End If -End Function diff --git a/contrib/curl/lib/mprintf.c b/contrib/curl/lib/mprintf.c index e4270abe83..478b0b074e 100644 --- a/contrib/curl/lib/mprintf.c +++ b/contrib/curl/lib/mprintf.c @@ -5,11 +5,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1999 - 2016, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -18,42 +18,22 @@ * 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 $ - * 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 +#include "dynbuf.h" +#include "curl_printf.h" #include "curl_memory.h" /* The last #include file should be: */ #include "memdebug.h" -#ifndef SIZEOF_LONG_DOUBLE -#define SIZEOF_LONG_DOUBLE 0 -#endif - /* * If SIZEOF_SIZE_T has not been defined, default to the size of long. */ -#ifndef SIZEOF_SIZE_T -# define SIZEOF_SIZE_T CURL_SIZEOF_LONG -#endif - #ifdef HAVE_LONGLONG # define LONG_LONG_TYPE long long # define HAVE_LONG_LONG_TYPE @@ -67,19 +47,6 @@ # endif #endif -/* - * Non-ANSI integer extensions - */ - -#if (defined(__BORLANDC__) && (__BORLANDC__ >= 0x520)) || \ - (defined(__WATCOMC__) && defined(__386__)) || \ - (defined(__POCC__) && defined(_MSC_VER)) || \ - (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 */ @@ -94,7 +61,8 @@ #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 */ +#define MAX_PARAMETERS 128 /* number of input arguments */ +#define MAX_SEGMENTS 128 /* number of output segments */ #ifdef __AMIGA__ # undef FORMAT_INT @@ -106,68 +74,87 @@ 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_FALSE +#define OUTCHAR(x) \ + do { \ + if(!stream((unsigned char)x, userp)) \ + done++; \ + else \ + return done; /* return on failure */ \ + } while(0) /* Data type to read from the arglist */ -typedef enum { - FORMAT_UNKNOWN = 0, +typedef enum { FORMAT_STRING, FORMAT_PTR, - FORMAT_INT, FORMAT_INTPTR, + FORMAT_INT, FORMAT_LONG, FORMAT_LONGLONG, + FORMAT_INTU, + FORMAT_LONGU, + FORMAT_LONGLONGU, FORMAT_DOUBLE, FORMAT_LONGDOUBLE, - FORMAT_WIDTH /* For internal use */ + FORMAT_WIDTH, + FORMAT_PRECISION } 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 '*$' 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 */ + 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 '*$' 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 */ + FLAGS_SUBSTR = 1 << 20 /* no input, only substring */ }; -typedef struct { - FormatType type; - int flags; - long width; /* width OR width parameter number */ - long precision; /* precision OR precision parameter number */ +enum { + DOLLAR_UNKNOWN, + DOLLAR_NOPE, + DOLLAR_USE +}; + +/* + * Describes an input va_arg type and hold its value. + */ +struct va_input { + FormatType type; /* FormatType */ union { char *str; void *ptr; - union { - mp_intmax_t as_signed; - mp_uintmax_t as_unsigned; - } num; + mp_intmax_t nums; /* signed */ + mp_uintmax_t numu; /* unsigned */ double dnum; - } data; -} va_stack_t; + } val; +}; + +/* + * Describes an output segment. + */ +struct outsegment { + int width; /* width OR width parameter number */ + int precision; /* precision OR precision parameter number */ + unsigned int flags; + unsigned int input; /* input argument array index */ + char *start; /* format string start to output */ + size_t outlen; /* number of bytes from the format string to output */ +}; struct nsprintf { char *buffer; @@ -176,119 +163,125 @@ struct nsprintf { }; struct asprintf { - char *buffer; /* allocated buffer */ - size_t len; /* length of string */ - size_t alloc; /* length of alloc */ - int fail; /* (!= 0) if an alloc has failed and thus - the output is not the complete data */ + struct dynbuf *b; + char merr; }; -static long dprintf_DollarString(char *input, char **end) -{ - int number=0; - while(ISDIGIT(*input)) { - number *= 10; - number += *input-'0'; - input++; - } - if(number && ('$'==*input++)) { - *end = input; - return number; - } - return 0; -} +/* the provided input number is 1-based but this returns the number 0-based. -static bool dprintf_IsQualifierNoDollar(const char *fmt) + returns -1 if no valid number was provided. +*/ +static int dollarstring(char *input, char **end) { -#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; + if(ISDIGIT(*input)) { + int number = 0; + do { + if(number < MAX_PARAMETERS) { + number *= 10; + number += *input - '0'; + } + input++; + } while(ISDIGIT(*input)); - default: - return FALSE; + if(number && (number <= MAX_PARAMETERS) && ('$' == *input)) { + *end = ++input; + return number - 1; + } } + return -1; } -/****************************************************************** +/* + * Parse the format string. * - * Pass 1: - * Create an index with the type of each parameter entry and its - * value (may vary in size) + * Create two arrays. One describes the inputs, one describes the outputs. * * Returns zero on success. - * - ******************************************************************/ + */ -static int dprintf_Pass1(const char *format, va_stack_t *vto, char **endpos, - va_list arglist) +#define PFMT_OK 0 +#define PFMT_DOLLAR 1 /* bad dollar for main param */ +#define PFMT_DOLLARWIDTH 2 /* bad dollar use for width */ +#define PFMT_DOLLARPREC 3 /* bad dollar use for precision */ +#define PFMT_MANYARGS 4 /* too many input arguments used */ +#define PFMT_PREC 5 /* precision overflow */ +#define PFMT_PRECMIX 6 /* bad mix of precision specifiers */ +#define PFMT_WIDTH 7 /* width overflow */ +#define PFMT_INPUTGAP 8 /* gap in arguments */ +#define PFMT_WIDTHARG 9 /* attempted to use same arg twice, for width */ +#define PFMT_PRECARG 10 /* attempted to use same arg twice, for prec */ +#define PFMT_MANYSEGS 11 /* maxed out output segments */ + +static int parsefmt(const char *format, + struct outsegment *out, + struct va_input *in, + int *opieces, + int *ipieces, 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; + int param; + int width; + int precision; + unsigned int flags; + FormatType type; + int max_param = -1; + int i; + int ocount = 0; + unsigned char usedinput[MAX_PARAMETERS/8]; + size_t outlen = 0; + struct outsegment *optr; + int use_dollar = DOLLAR_UNKNOWN; + char *start = fmt; + + /* clear, set a bit for each used input */ + memset(usedinput, 0, sizeof(usedinput)); while(*fmt) { - if(*fmt++ == '%') { + if(*fmt == '%') { + struct va_input *iptr; + bool loopit = TRUE; + fmt++; + outlen = (size_t)(fmt - start - 1); if(*fmt == '%') { + /* this means a %% that should be output only as %. Create an output + segment. */ + if(outlen) { + optr = &out[ocount++]; + if(ocount > MAX_SEGMENTS) + return PFMT_MANYSEGS; + optr->input = 0; + optr->flags = FLAGS_SUBSTR; + optr->start = start; + optr->outlen = outlen; + } + start = 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; + flags = 0; + width = precision = 0; - /* - * 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. - */ + if(use_dollar != DOLLAR_NOPE) { + param = dollarstring(fmt, &fmt); + if(param < 0) { + if(use_dollar == DOLLAR_USE) + /* illegal combo */ + return PFMT_DOLLAR; - 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; + /* we got no positional, just get the next arg */ + param = -1; + use_dollar = DOLLAR_NOPE; } else -#endif + use_dollar = DOLLAR_USE; + } + else + param = -1; + /* Handle the flags */ + while(loopit) { switch(*fmt++) { case ' ': flags |= FLAGS_SPACE; @@ -298,7 +291,7 @@ static int dprintf_Pass1(const char *format, va_stack_t *vto, char **endpos, break; case '-': flags |= FLAGS_LEFT; - flags &= ~FLAGS_PAD_NIL; + flags &= ~(unsigned int)FLAGS_PAD_NIL; break; case '#': flags |= FLAGS_ALT; @@ -306,37 +299,66 @@ static int dprintf_Pass1(const char *format, va_stack_t *vto, char **endpos, 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; + if(use_dollar == DOLLAR_USE) { + precision = dollarstring(fmt, &fmt); + if(precision < 0) + /* illegal combo */ + return PFMT_DOLLARPREC; + } else - precision = param_num; - - if(precision > max_param) - max_param = precision; + /* get it from the next argument */ + precision = -1; } else { + bool is_neg = FALSE; flags |= FLAGS_PREC; - precision = strtol(fmt, &fmt, 10); + precision = 0; + if('-' == *fmt) { + is_neg = TRUE; + fmt++; + } + while(ISDIGIT(*fmt)) { + if(precision > INT_MAX/10) + return PFMT_PREC; + precision *= 10; + precision += *fmt - '0'; + fmt++; + } + if(is_neg) + precision = -precision; } + 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 PFMT_PRECMIX; break; case 'h': flags |= FLAGS_SHORT; break; -#if defined(MP_HAVE_INT_EXTENSIONS) +#if defined(_WIN32) || defined(_WIN32_WCE) case 'I': -#if (CURL_SIZEOF_CURL_OFF_T > CURL_SIZEOF_LONG) - flags |= FLAGS_LONGLONG; + /* Non-ANSI integer extensions I32 I64 */ + if((fmt[0] == '3') && (fmt[1] == '2')) { + flags |= FLAGS_LONG; + fmt += 2; + } + else if((fmt[0] == '6') && (fmt[1] == '4')) { + flags |= FLAGS_LONGLONG; + fmt += 2; + } + else { +#if (SIZEOF_CURL_OFF_T > SIZEOF_LONG) + flags |= FLAGS_LONGLONG; #else - flags |= FLAGS_LONG; + flags |= FLAGS_LONG; #endif + } break; -#endif +#endif /* _WIN32 || _WIN32_WCE */ case 'l': if(flags & FLAGS_LONG) flags |= FLAGS_LONGLONG; @@ -352,14 +374,14 @@ static int dprintf_Pass1(const char *format, va_stack_t *vto, char **endpos, case 'z': /* the code below generates a warning if -Wunreachable-code is used */ -#if (SIZEOF_SIZE_T > CURL_SIZEOF_LONG) +#if (SIZEOF_SIZE_T > SIZEOF_LONG) flags |= FLAGS_LONGLONG; #else flags |= FLAGS_LONG; #endif break; case 'O': -#if (CURL_SIZEOF_CURL_OFF_T > CURL_SIZEOF_LONG) +#if (SIZEOF_CURL_OFF_T > SIZEOF_LONG) flags |= FLAGS_LONGLONG; #else flags |= FLAGS_LONG; @@ -368,395 +390,421 @@ static int dprintf_Pass1(const char *format, va_stack_t *vto, char **endpos, case '0': if(!(flags & FLAGS_LEFT)) flags |= FLAGS_PAD_NIL; - /* FALLTHROUGH */ + 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); + width = 0; + fmt--; + do { + if(width > INT_MAX/10) + return PFMT_WIDTH; + width *= 10; + width += *fmt - '0'; + fmt++; + } while(ISDIGIT(*fmt)); break; - case '*': /* Special case */ + case '*': /* read width from argument list */ flags |= FLAGS_WIDTHPARAM; - param_num++; - - i = dprintf_DollarString(fmt, &fmt); - if(i) - width = i; + if(use_dollar == DOLLAR_USE) { + width = dollarstring(fmt, &fmt); + if(width < 0) + /* illegal combo */ + return PFMT_DOLLARWIDTH; + } else - width = param_num; - if(width > max_param) - max_param=width; + /* pick from the next argument */ + width = -1; break; default: + loopit = FALSE; + fmt--; break; - } - } /* switch */ - - /* Handle the specifier */ + } /* switch */ + } /* while */ - i = this_param - 1; - - if((i < 0) || (i >= MAX_PARAMETERS)) - /* out of allowed range */ - return 1; - - switch (*fmt) { + switch(*fmt) { case 'S': flags |= FLAGS_ALT; - /* FALLTHROUGH */ + FALLTHROUGH(); case 's': - vto[i].type = FORMAT_STRING; + type = FORMAT_STRING; break; case 'n': - vto[i].type = FORMAT_INTPTR; + type = FORMAT_INTPTR; break; case 'p': - vto[i].type = FORMAT_PTR; + type = FORMAT_PTR; break; - case 'd': case 'i': - vto[i].type = FORMAT_INT; + case 'd': + case 'i': + if(flags & FLAGS_LONGLONG) + type = FORMAT_LONGLONG; + else if(flags & FLAGS_LONG) + type = FORMAT_LONG; + else + type = FORMAT_INT; break; case 'u': - vto[i].type = FORMAT_INT; + if(flags & FLAGS_LONGLONG) + type = FORMAT_LONGLONGU; + else if(flags & FLAGS_LONG) + type = FORMAT_LONGU; + else + type = FORMAT_INTU; flags |= FLAGS_UNSIGNED; break; case 'o': - vto[i].type = FORMAT_INT; + type = FORMAT_INT; flags |= FLAGS_OCTAL; break; case 'x': - vto[i].type = FORMAT_INT; + type = FORMAT_INTU; flags |= FLAGS_HEX|FLAGS_UNSIGNED; break; case 'X': - vto[i].type = FORMAT_INT; + type = FORMAT_INTU; flags |= FLAGS_HEX|FLAGS_UPPER|FLAGS_UNSIGNED; break; case 'c': - vto[i].type = FORMAT_INT; + type = FORMAT_INT; flags |= FLAGS_CHAR; break; case 'f': - vto[i].type = FORMAT_DOUBLE; + type = FORMAT_DOUBLE; break; case 'e': - vto[i].type = FORMAT_DOUBLE; + type = FORMAT_DOUBLE; flags |= FLAGS_FLOATE; break; case 'E': - vto[i].type = FORMAT_DOUBLE; + type = FORMAT_DOUBLE; flags |= FLAGS_FLOATE|FLAGS_UPPER; break; case 'g': - vto[i].type = FORMAT_DOUBLE; + type = FORMAT_DOUBLE; flags |= FLAGS_FLOATG; break; case 'G': - vto[i].type = FORMAT_DOUBLE; + type = FORMAT_DOUBLE; flags |= FLAGS_FLOATG|FLAGS_UPPER; break; default: - vto[i].type = FORMAT_UNKNOWN; - break; + /* invalid instruction, disregard and continue */ + continue; } /* 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; - 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(width < 0) + width = param_num++; + else { + /* if this identifies a parameter already used, this + is illegal */ + if(usedinput[width/8] & (1 << (width&7))) + return PFMT_WIDTHARG; + } + if(width >= MAX_PARAMETERS) + return PFMT_MANYARGS; + if(width >= max_param) + max_param = width; + + in[width].type = FORMAT_WIDTH; + /* mark as used */ + usedinput[width/8] |= (unsigned char)(1 << (width&7)); } + 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; - 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; + if(precision < 0) + precision = param_num++; + else { + /* if this identifies a parameter already used, this + is illegal */ + if(usedinput[precision/8] & (1 << (precision&7))) + return PFMT_PRECARG; + } + if(precision >= MAX_PARAMETERS) + return PFMT_MANYARGS; + if(precision >= max_param) + max_param = precision; + + in[precision].type = FORMAT_PRECISION; + usedinput[precision/8] |= (unsigned char)(1 << (precision&7)); } - *endpos++ = fmt + 1; /* end of this sequence */ + + /* Handle the specifier */ + if(param < 0) + param = param_num++; + if(param >= MAX_PARAMETERS) + return PFMT_MANYARGS; + if(param >= max_param) + max_param = param; + + iptr = &in[param]; + iptr->type = type; + + /* mark this input as used */ + usedinput[param/8] |= (unsigned char)(1 << (param&7)); + + fmt++; + optr = &out[ocount++]; + if(ocount > MAX_SEGMENTS) + return PFMT_MANYSEGS; + optr->input = (unsigned int)param; + optr->flags = flags; + optr->width = width; + optr->precision = precision; + optr->start = start; + optr->outlen = outlen; + start = fmt; } + else + fmt++; } - /* Read the arg list parameters into our data list */ - for(i=0; i MAX_SEGMENTS) + return PFMT_MANYSEGS; + optr->input = 0; + optr->flags = FLAGS_SUBSTR; + optr->start = start; + optr->outlen = outlen; + } - switch(vto[i].type) { + /* Read the arg list parameters into our data list */ + for(i = 0; i < max_param + 1; i++) { + struct va_input *iptr = &in[i]; + if(!(usedinput[i/8] & (1 << (i&7)))) + /* bad input */ + return PFMT_INPUTGAP; + + /* based on the type, read the correct argument */ + switch(iptr->type) { case FORMAT_STRING: - vto[i].data.str = va_arg(arglist, char *); + iptr->val.str = va_arg(arglist, char *); break; case FORMAT_INTPTR: - case FORMAT_UNKNOWN: case FORMAT_PTR: - vto[i].data.ptr = va_arg(arglist, void *); + iptr->val.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); - } + case FORMAT_LONGLONGU: + iptr->val.numu = (mp_uintmax_t)va_arg(arglist, mp_uintmax_t); break; - case FORMAT_DOUBLE: - vto[i].data.dnum = va_arg(arglist, double); + case FORMAT_LONGLONG: + iptr->val.nums = (mp_intmax_t)va_arg(arglist, mp_intmax_t); break; + case FORMAT_LONGU: + iptr->val.numu = (mp_uintmax_t)va_arg(arglist, unsigned long); + break; + + case FORMAT_LONG: + iptr->val.nums = (mp_intmax_t)va_arg(arglist, long); + break; + + case FORMAT_INTU: + iptr->val.numu = (mp_uintmax_t)va_arg(arglist, unsigned int); + break; + + case FORMAT_INT: case FORMAT_WIDTH: - /* Argument has been read. Silently convert it into an integer - * for later use - */ - vto[i].type = FORMAT_INT; + case FORMAT_PRECISION: + iptr->val.nums = (mp_intmax_t)va_arg(arglist, int); + break; + + case FORMAT_DOUBLE: + iptr->val.dnum = va_arg(arglist, double); break; default: + DEBUGASSERT(NULL); /* unexpected */ break; } } + *ipieces = max_param + 1; + *opieces = ocount; - return 0; - + return PFMT_OK; } -static int dprintf_formatf( - void *data, /* untouched by format(), just sent to the stream() function in - the second argument */ +/* + * formatf() - the general printf function. + * + * It calls parsefmt() to parse the format string. It populates two arrays; + * one that describes the input arguments and one that describes a number of + * output segments. + * + * On success, the input array describes the type of all arguments and their + * values. + * + * The function then iterates over the output segments and outputs them one + * by one until done. Using the appropriate input arguments (if any). + * + * All output is sent to the 'stream()' callback, one byte at a time. + */ + +static int formatf( + void *userp, /* untouched by format(), just sent to the stream() function in + the second argument */ /* function pointer called for each output character */ - int (*stream)(int, FILE *), + int (*stream)(unsigned char, void *), 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 */ - - va_stack_t vto[MAX_PARAMETERS]; - char *endpos[MAX_PARAMETERS]; - char **end; - + static const char nilstr[] = "(nil)"; + const char *digits = lower_digits; /* Base-36 digits for numbers. */ + int done = 0; /* number of characters written */ + int i; + int ocount = 0; /* number of output segments */ + int icount = 0; /* number of input arguments */ + + struct outsegment output[MAX_SEGMENTS]; + struct va_input input[MAX_PARAMETERS]; char work[BUFFSIZE]; - va_stack_t *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 -1; - - 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. */ - long base; - - /* Integral values to be written. */ - mp_uintmax_t num; - - /* Used to convert negative in positive. */ - mp_intmax_t signed_num; - + /* Parse the format string */ + if(parsefmt(format, output, input, &ocount, &icount, ap_save)) + return 0; + + for(i = 0; i < ocount; i++) { + struct outsegment *optr = &output[i]; + struct va_input *iptr; + bool is_alt; /* Format spec modifiers. */ + int width; /* Width of a field. */ + int prec; /* Precision of a field. */ + bool is_neg; /* Decimal integer is negative. */ + unsigned long base; /* Base of a number to be written. */ + mp_uintmax_t num; /* Integral values to be written. */ + mp_intmax_t signed_num; /* Used to convert negative in positive. */ 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; + size_t outlen = optr->outlen; + unsigned int flags = optr->flags; + + if(outlen) { + char *str = optr->start; + for(; outlen && *str; outlen--) + OUTCHAR(*str++); + if(optr->flags & FLAGS_SUBSTR) + /* this is just a substring */ + continue; } - /* If this is a positional parameter, the position must follow immediately - after the %, thus create a %$ 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(flags & FLAGS_WIDTHPARAM) { + width = (int)input[optr->width].val.nums; 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; + if(width == INT_MIN) + width = INT_MAX; + else + width = -width; + flags |= FLAGS_LEFT; + flags &= ~(unsigned int)FLAGS_PAD_NIL; } } else - width = p->width; + width = optr->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(flags & FLAGS_PRECPARAM) { + prec = (int)input[optr->precision].val.nums; 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 if(flags & FLAGS_PREC) + prec = optr->precision; else prec = -1; - is_alt = (p->flags & FLAGS_ALT) ? 1 : 0; + is_alt = (flags & FLAGS_ALT) ? 1 : 0; + iptr = &input[optr->input]; - switch(p->type) { + switch(iptr->type) { + case FORMAT_INTU: + case FORMAT_LONGU: + case FORMAT_LONGLONGU: + flags |= FLAGS_UNSIGNED; + FALLTHROUGH(); case FORMAT_INT: - num = p->data.num.as_unsigned; - if(p->flags & FLAGS_CHAR) { + case FORMAT_LONG: + case FORMAT_LONGLONG: + num = iptr->val.numu; + if(flags & FLAGS_CHAR) { /* Character. */ - if(!(p->flags & FLAGS_LEFT)) + if(!(flags & FLAGS_LEFT)) while(--width > 0) OUTCHAR(' '); OUTCHAR((char) num); - if(p->flags & FLAGS_LEFT) + if(flags & FLAGS_LEFT) while(--width > 0) OUTCHAR(' '); break; } - if(p->flags & FLAGS_OCTAL) { - /* Octal unsigned integer. */ + if(flags & FLAGS_OCTAL) { + /* Octal unsigned integer */ base = 8; - goto unsigned_number; + is_neg = FALSE; } - else if(p->flags & FLAGS_HEX) { - /* Hexadecimal unsigned integer. */ - - digits = (p->flags & FLAGS_UPPER)? upper_digits : lower_digits; + else if(flags & FLAGS_HEX) { + /* Hexadecimal unsigned integer */ + digits = (flags & FLAGS_UPPER) ? upper_digits : lower_digits; base = 16; - goto unsigned_number; + is_neg = FALSE; } - else if(p->flags & FLAGS_UNSIGNED) { - /* Decimal unsigned integer. */ + else if(flags & FLAGS_UNSIGNED) { + /* Decimal unsigned integer */ base = 10; - goto unsigned_number; + is_neg = FALSE; } + else { + /* Decimal integer. */ + base = 10; - /* 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; + is_neg = (iptr->val.nums < (mp_intmax_t)0); + if(is_neg) { + /* signed_num might fail to hold absolute negative minimum by 1 */ + signed_num = iptr->val.nums + (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. */ - +number: /* 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; + switch(base) { + case 10: + while(num > 0) { + *w-- = (char)('0' + (num % 10)); + num /= 10; + } + break; + default: + while(num > 0) { + *w-- = digits[num % base]; + num /= base; + } + break; } - width -= (long)(workend - w); - prec -= (long)(workend - w); + width -= (int)(workend - w); + prec -= (int)(workend - w); if(is_alt && base == 8 && prec <= 0) { *w-- = '0'; @@ -765,36 +813,36 @@ static int dprintf_formatf( if(prec > 0) { width -= prec; - while(prec-- > 0) + 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)) + if(is_neg || (flags & FLAGS_SHOWSIGN) || (flags & FLAGS_SPACE)) --width; - if(!(p->flags & FLAGS_LEFT) && !(p->flags & FLAGS_PAD_NIL)) + if(!(flags & FLAGS_LEFT) && !(flags & FLAGS_PAD_NIL)) while(width-- > 0) OUTCHAR(' '); if(is_neg) OUTCHAR('-'); - else if(p->flags & FLAGS_SHOWSIGN) + else if(flags & FLAGS_SHOWSIGN) OUTCHAR('+'); - else if(p->flags & FLAGS_SPACE) + else if(flags & FLAGS_SPACE) OUTCHAR(' '); if(is_alt && base == 16) { OUTCHAR('0'); - if(p->flags & FLAGS_UPPER) + if(flags & FLAGS_UPPER) OUTCHAR('X'); else OUTCHAR('x'); } - if(!(p->flags & FLAGS_LEFT) && (p->flags & FLAGS_PAD_NIL)) + if(!(flags & FLAGS_LEFT) && (flags & FLAGS_PAD_NIL)) while(width-- > 0) OUTCHAR('0'); @@ -803,204 +851,200 @@ static int dprintf_formatf( OUTCHAR(*w); } - if(p->flags & FLAGS_LEFT) + if(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 == NULL) { - /* 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; - } + case FORMAT_STRING: { + const char *str; + size_t len; + + str = (char *)iptr->val.str; + if(!str) { + /* Write null string if there is space. */ + if(prec == -1 || prec >= (int) sizeof(nilstr) - 1) { + str = nilstr; + len = sizeof(nilstr) - 1; + /* Disable quotes around (nil) */ + flags &= ~(unsigned int)FLAGS_ALT; } - else if(prec != -1) - len = (size_t)prec; - else - len = strlen(str); + 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; + width -= (len > INT_MAX) ? INT_MAX : (int)len; - if(p->flags & FLAGS_ALT) - OUTCHAR('"'); + if(flags & FLAGS_ALT) + OUTCHAR('"'); - if(!(p->flags&FLAGS_LEFT)) - while(width-- > 0) - OUTCHAR(' '); + if(!(flags & FLAGS_LEFT)) + while(width-- > 0) + OUTCHAR(' '); - while((len-- > 0) && *str) - OUTCHAR(*str++); - if(p->flags&FLAGS_LEFT) - while(width-- > 0) - OUTCHAR(' '); + for(; len && *str; len--) + OUTCHAR(*str++); + if(flags & FLAGS_LEFT) + while(width-- > 0) + OUTCHAR(' '); - if(p->flags & FLAGS_ALT) - OUTCHAR('"'); - } + if(flags & FLAGS_ALT) + OUTCHAR('"'); break; + } case FORMAT_PTR: /* Generic pointer. */ - { - void *ptr; - ptr = (void *) p->data.ptr; - if(ptr != NULL) { - /* 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(' '); - } + if(iptr->val.ptr) { + /* If the pointer is not NULL, write it as a %#x spec. */ + base = 16; + digits = (flags & FLAGS_UPPER) ? upper_digits : lower_digits; + is_alt = TRUE; + num = (size_t) iptr->val.ptr; + is_neg = FALSE; + goto number; } - break; + else { + /* Write "(nil)" for a nil pointer. */ + const char *point; - 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; + width -= (int)(sizeof(nilstr) - 1); + if(flags & FLAGS_LEFT) + while(width-- > 0) + OUTCHAR(' '); + for(point = nilstr; *point != '\0'; ++point) + OUTCHAR(*point); + if(!(flags & FLAGS_LEFT)) + while(width-- > 0) + OUTCHAR(' '); + } + break; - 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; + case FORMAT_DOUBLE: { + char formatbuf[32]="%"; + char *fptr = &formatbuf[1]; + size_t left = sizeof(formatbuf)-strlen(formatbuf); + int len; + + if(flags & FLAGS_WIDTH) + width = optr->width; + + if(flags & FLAGS_PREC) + prec = optr->precision; + + if(flags & FLAGS_LEFT) + *fptr++ = '-'; + if(flags & FLAGS_SHOWSIGN) + *fptr++ = '+'; + if(flags & FLAGS_SPACE) + *fptr++ = ' '; + if(flags & FLAGS_ALT) + *fptr++ = '#'; + + *fptr = 0; + + if(width >= 0) { + size_t dlen; + if(width >= (int)sizeof(work)) + width = sizeof(work)-1; + /* RECURSIVE USAGE */ + dlen = (size_t)curl_msnprintf(fptr, left, "%d", width); + fptr += dlen; + left -= dlen; + } + if(prec >= 0) { + /* for each digit in the integer part, we can have one less + precision */ + size_t maxprec = sizeof(work) - 2; + double val = iptr->val.dnum; + if(width > 0 && prec <= width) + maxprec -= (size_t)width; + while(val >= 10.0) { + val /= 10; + maxprec--; } - 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; - while(val >= 10.0) { - val /= 10; - maxprec--; - } - if(prec > (long)maxprec) - prec = (long)maxprec-1; - /* RECURSIVE USAGE */ - len = curl_msnprintf(fptr, left, ".%ld", prec); - fptr += len; - } - if(p->flags & FLAGS_LONG) - *fptr++ = 'l'; + if(prec > (int)maxprec) + prec = (int)maxprec-1; + if(prec < 0) + prec = 0; + /* RECURSIVE USAGE */ + len = curl_msnprintf(fptr, left, ".%d", prec); + fptr += len; + } + if(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'; + if(flags & FLAGS_FLOATE) + *fptr++ = (char)((flags & FLAGS_UPPER) ? 'E' : 'e'); + else if(flags & FLAGS_FLOATG) + *fptr++ = (char)((flags & FLAGS_UPPER) ? 'G' : 'g'); + else + *fptr++ = 'f'; - *fptr = 0; /* and a final zero termination */ + *fptr = 0; /* and a final null-termination */ - /* NOTE NOTE NOTE!! Not all sprintf implementations return number of - output characters */ - (sprintf)(work, formatbuf, p->data.dnum); -#ifdef CURLDEBUG - assert(strlen(work) <= sizeof(work)); +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wformat-nonliteral" #endif - for(fptr=work; *fptr; fptr++) - OUTCHAR(*fptr); - } + /* NOTE NOTE NOTE!! Not all sprintf implementations return number of + output characters */ +#ifdef HAVE_SNPRINTF + (snprintf)(work, sizeof(work), formatbuf, iptr->val.dnum); +#else + (sprintf)(work, formatbuf, iptr->val.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; + if(flags & FLAGS_LONGLONG) + *(LONG_LONG_TYPE *) iptr->val.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; + if(flags & FLAGS_LONG) + *(long *) iptr->val.ptr = (long)done; + else if(!(flags & FLAGS_SHORT)) + *(int *) iptr->val.ptr = (int)done; else - *(short *) p->data.ptr = (short)done; + *(short *) iptr->val.ptr = (short)done; break; default: break; } - f = *end++; /* goto end of %-code */ - } return done; } /* fputc() look-alike */ -static int addbyter(int output, FILE *data) +static int addbyter(unsigned char outc, void *f) { - struct nsprintf *infop=(struct nsprintf *)data; - unsigned char outc = (unsigned char)output; - + struct nsprintf *infop = f; 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 */ + /* only do this if we have not reached max length yet */ + *infop->buffer++ = (char)outc; /* store */ infop->length++; /* we are now one byte larger */ - return outc; /* fputc() returns like this on success */ + return 0; /* fputc() returns like this on success */ } - return -1; + return 1; } int curl_mvsnprintf(char *buffer, size_t maxlength, const char *format, @@ -1013,12 +1057,15 @@ int curl_mvsnprintf(char *buffer, size_t maxlength, const char *format, info.length = 0; info.max = maxlength; - retcode = dprintf_formatf(&info, addbyter, format, ap_save); - if((retcode != -1) && info.max) { + retcode = 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 */ + if(info.max == info.length) { + /* we are at maximum, scrap the last letter */ info.buffer[-1] = 0; + DEBUGASSERT(retcode); + retcode--; /* do not count the nul byte */ + } else info.buffer[0] = 0; } @@ -1036,102 +1083,66 @@ int curl_msnprintf(char *buffer, size_t maxlength, const char *format, ...) } /* fputc() look-alike */ -static int alloc_addbyter(int output, FILE *data) +static int alloc_addbyter(unsigned char outc, void *f) { - struct asprintf *infop=(struct asprintf *)data; - unsigned char outc = (unsigned char)output; - - if(!infop->buffer) { - infop->buffer = malloc(32); - if(!infop->buffer) { - infop->fail = 1; - return -1; /* fail */ - } - infop->alloc = 32; - infop->len =0; + struct asprintf *infop = f; + CURLcode result = Curl_dyn_addn(infop->b, &outc, 1); + if(result) { + infop->merr = result == CURLE_TOO_LARGE ? MERR_TOO_LARGE : MERR_MEM; + return 1 ; /* fail */ } - else if(infop->len+1 >= infop->alloc) { - char *newptr = NULL; - size_t newsize = infop->alloc*2; - - /* detect wrap-around or other overflow problems */ - if(newsize > infop->alloc) - newptr = realloc(infop->buffer, newsize); - - if(!newptr) { - infop->fail = 1; - return -1; /* fail */ - } - infop->buffer = newptr; - infop->alloc = newsize; - } - - infop->buffer[ infop->len ] = outc; - - infop->len++; - - return outc; /* fputc() returns like this on success */ + return 0; } -char *curl_maprintf(const char *format, ...) +/* appends the formatted string, returns MERR error code */ +int Curl_dyn_vprintf(struct dynbuf *dyn, const char *format, va_list ap_save) { - va_list ap_save; /* argument pointer */ - int retcode; struct asprintf info; + info.b = dyn; + info.merr = MERR_OK; - info.buffer = NULL; - info.len = 0; - info.alloc = 0; - info.fail = 0; - - va_start(ap_save, format); - retcode = dprintf_formatf(&info, alloc_addbyter, format, ap_save); - va_end(ap_save); - if((-1 == retcode) || info.fail) { - if(info.alloc) - free(info.buffer); - return NULL; - } - if(info.alloc) { - info.buffer[info.len] = 0; /* we terminate this with a zero byte */ - return info.buffer; + (void)formatf(&info, alloc_addbyter, format, ap_save); + if(info.merr) { + Curl_dyn_free(info.b); + return info.merr; } - else - return strdup(""); + return 0; } char *curl_mvaprintf(const char *format, va_list ap_save) { - int retcode; struct asprintf info; - - info.buffer = NULL; - info.len = 0; - info.alloc = 0; - info.fail = 0; - - retcode = dprintf_formatf(&info, alloc_addbyter, format, ap_save); - if((-1 == retcode) || info.fail) { - if(info.alloc) - free(info.buffer); + struct dynbuf dyn; + info.b = &dyn; + Curl_dyn_init(info.b, DYN_APRINTF); + info.merr = MERR_OK; + + (void)formatf(&info, alloc_addbyter, format, ap_save); + if(info.merr) { + Curl_dyn_free(info.b); return NULL; } + if(Curl_dyn_len(info.b)) + return Curl_dyn_ptr(info.b); + return strdup(""); +} - if(info.alloc) { - info.buffer[info.len] = 0; /* we terminate this with a zero byte */ - return info.buffer; - } - else - 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) +static int storebuffer(unsigned char outc, void *f) { - char **buffer = (char **)data; - unsigned char outc = (unsigned char)output; - **buffer = outc; + char **buffer = f; + **buffer = (char)outc; (*buffer)++; - return outc; /* act like fputc() ! */ + return 0; } int curl_msprintf(char *buffer, const char *format, ...) @@ -1139,19 +1150,27 @@ 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); + retcode = formatf(&buffer, storebuffer, format, ap_save); va_end(ap_save); - *buffer=0; /* we terminate this with a zero byte */ + *buffer = 0; /* we terminate this with a zero byte */ return retcode; } +static int fputc_wrapper(unsigned char outc, void *f) +{ + int out = outc; + FILE *s = f; + int rc = fputc(out, s); + return rc == EOF; +} + 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); + retcode = formatf(stdout, fputc_wrapper, format, ap_save); va_end(ap_save); return retcode; } @@ -1161,25 +1180,24 @@ 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); + retcode = formatf(whereto, fputc_wrapper, 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 */ + int retcode = 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); + return formatf(stdout, fputc_wrapper, format, ap_save); } int curl_mvfprintf(FILE *whereto, const char *format, va_list ap_save) { - return dprintf_formatf(whereto, fputc, format, ap_save); + return formatf(whereto, fputc_wrapper, format, ap_save); } diff --git a/contrib/curl/lib/mqtt.c b/contrib/curl/lib/mqtt.c new file mode 100644 index 0000000000..69eaf344ae --- /dev/null +++ b/contrib/curl/lib/mqtt.c @@ -0,0 +1,842 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * Copyright (C) Bjƶrn Stenberg, + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the 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 +#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, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ + 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; + size_t n; + result = Curl_xfer_send(data, buf, len, FALSE, &n); + if(result) + return result; + Curl_debug(data, CURLINFO_HEADER_OUT, buf, (size_t)n); + if(len != 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) +{ + int i; + + for(i = 0; (len > 0) && (i < 4); i++) { + unsigned char encoded; + encoded = len % 0x80; + len /= 0x80; + if(len) + encoded |= 0x80; + buf[i] = (char)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 username 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_xfer_recv(data, (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) { + DEBUGF(infof(data, "mqtt_publish without payload, return bad arg")); + 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 DEBUGBUILD +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 DEBUGBUILD + 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; + ssize_t nread; + 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 */ + char buffer[4*1024]; + size_t rest = mq->npacket; + if(rest > sizeof(buffer)) + rest = sizeof(buffer); + result = Curl_xfer_recv(data, buffer, 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; + } + + /* if QoS is set, message contains packet id */ + result = Curl_client_write(data, CLIENTWRITE_BODY, buffer, nread); + if(result) + goto end; + + mq->npacket -= nread; + 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; + unsigned char recvbyte; + + *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_xfer_recv(data, (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_xfer_recv(data, (char *)&recvbyte, 1, &nread); + if(result || !nread) + break; + Curl_debug(data, CURLINFO_HEADER_IN, (char *)&recvbyte, 1); + mq->pkt_hd[mq->npacket++] = recvbyte; + } while((recvbyte & 0x80) && (mq->npacket < 4)); + if(!result && nread && (recvbyte & 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(mq->pkt_hd, 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/contrib/curl/lib/mqtt.h b/contrib/curl/lib/mqtt.h new file mode 100644 index 0000000000..99ab12a98a --- /dev/null +++ b/contrib/curl/lib/mqtt.h @@ -0,0 +1,63 @@ +#ifndef HEADER_CURL_MQTT_H +#define HEADER_CURL_MQTT_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Bjƶrn Stenberg, + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the 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; + unsigned char pkt_hd[4]; /* for decoding the arriving packet length */ +}; + +#endif /* HEADER_CURL_MQTT_H */ diff --git a/contrib/curl/lib/multi.c b/contrib/curl/lib/multi.c index bb31dc1d9c..6aa5a18e3a 100644 --- a/contrib/curl/lib/multi.c +++ b/contrib/curl/lib/multi.c @@ -5,11 +5,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2016, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -18,6 +18,8 @@ * 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" @@ -27,10 +29,12 @@ #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" @@ -40,10 +44,12 @@ #include "speedcheck.h" #include "conncache.h" #include "multihandle.h" -#include "pipeline.h" #include "sigpipe.h" #include "vtls/vtls.h" -#include "connect.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" @@ -51,59 +57,93 @@ /* 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 + 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)->type == CURL_MULTI_HANDLE) - -static void singlesocket(struct Curl_multi *multi, - struct Curl_easy *data); -static int update_timer(struct Curl_multi *multi); + (((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 add_next_timeout(struct timeval now, +static void move_pending_to_connect(struct Curl_multi *multi, + struct Curl_easy *data); +static CURLMcode singlesocket(struct Curl_multi *multi, + struct Curl_easy *data); +static CURLMcode add_next_timeout(struct curltime now, struct Curl_multi *multi, struct Curl_easy *d); static CURLMcode multi_timeout(struct Curl_multi *multi, + struct curltime *expire_time, long *timeout_ms); +static void process_pending_handles(struct Curl_multi *multi); +static void multi_xfer_bufs_free(struct Curl_multi *multi); +static void Curl_expire_ex(struct Curl_easy *data, const struct curltime *nowp, + timediff_t milli, expire_id id); -#ifdef DEBUGBUILD -static const char * const statename[]={ +#if defined( DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS) +static const char * const multi_statename[]={ "INIT", - "CONNECT_PEND", + "PENDING", + "SETUP", "CONNECT", - "WAITRESOLVE", - "WAITCONNECT", - "WAITPROXYCONNECT", - "SENDPROTOCONNECT", + "RESOLVING", + "CONNECTING", + "TUNNELING", "PROTOCONNECT", - "WAITDO", + "PROTOCONNECTING", "DO", "DOING", - "DO_MORE", - "DO_DONE", - "WAITPERFORM", - "PERFORM", - "TOOFAST", + "DOING_MORE", + "DID", + "PERFORMING", + "RATELIMITING", "DONE", "COMPLETED", "MSGSENT", }; #endif -static void multi_freetimeout(void *a, void *b); - /* 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 do not 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 @@ -112,11 +152,25 @@ static void mstate(struct Curl_easy *data, CURLMstate state ) { CURLMstate oldstate = data->mstate; - static const init_multistate_func finit[CURLM_STATE_LAST] = { - NULL, - NULL, + static const init_multistate_func finit[MSTATE_LAST] = { + NULL, /* INIT */ + NULL, /* PENDING */ + NULL, /* SETUP */ Curl_init_CONNECT, /* CONNECT */ - /* the rest is NULL too */ + 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) @@ -124,29 +178,30 @@ static void mstate(struct Curl_easy *data, CURLMstate state #endif if(oldstate == state) - /* don't bother when the new state is the same as the old state */ + /* do not 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 >= CURLM_STATE_CONNECT_PEND && - data->mstate < CURLM_STATE_COMPLETED) { - long connection_id = -5000; - - if(data->easy_conn) - connection_id = data->easy_conn->connection_id; - + if(data->mstate >= MSTATE_PENDING && + data->mstate < MSTATE_COMPLETED) { infof(data, - "STATE: %s => %s handle %p; line %d (connection #%ld)\n", - statename[oldstate], statename[data->mstate], - (void *)data, lineno, connection_id); + "STATE: %s => %s handle %p; line %d", + multi_statename[oldstate], multi_statename[data->mstate], + (void *)data, lineno); } #endif - if(state == CURLM_STATE_COMPLETED) - /* changing to COMPLETED means there's one less easy handle 'alive' */ + if(state == MSTATE_COMPLETED) { + /* changing to COMPLETED means there is one less easy handle 'alive' */ + DEBUGASSERT(data->multi->num_alive > 0); data->multi->num_alive--; + if(!data->multi->num_alive) { + /* free the transfer buffer when we have no more active transfers */ + multi_xfer_bufs_free(data->multi); + } + } /* if this state has an init-function, run it */ if(finit[state]) @@ -160,52 +215,95 @@ static void mstate(struct Curl_easy *data, CURLMstate state #endif /* - * We add one of these structs to the sockhash for a particular socket + * We add one of these structs to the sockhash for each socket */ struct Curl_sh_entry { - struct Curl_easy *easy; - int action; /* what action READ/WRITE this socket waits for */ - curl_socket_t socket; /* mainly to ease debugging */ + 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 */ }; -/* bits for 'action' having no bits means this socket is not expecting any - action */ -#define SH_READ 1 -#define SH_WRITE 2 /* look up a given socket in the socket hash, skip invalid sockets */ -static struct Curl_sh_entry *sh_getentry(struct curl_hash *sh, +static struct Curl_sh_entry *sh_getentry(struct Curl_hash *sh, curl_socket_t s) { - if(s != CURL_SOCKET_BAD) + 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 + +/* the given key here is a struct Curl_easy pointer */ +static size_t trhash(void *key, size_t key_length, size_t slots_num) +{ + unsigned char bytes = ((unsigned char *)key)[key_length - 1] ^ + ((unsigned char *)key)[0]; + return (bytes % slots_num); +} + +static size_t trhash_compare(void *k1, size_t k1_len, void *k2, size_t k2_len) +{ + (void)k2_len; + return !memcmp(k1, k2, k1_len); +} + +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_easy *data) +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) + 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 */ - check->easy = data; - check->socket = s; + 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 */ } @@ -215,8 +313,11 @@ static struct Curl_sh_entry *sh_addentry(struct curl_hash *sh, /* delete the given socket + handle from the hash */ -static void sh_delentry(struct curl_hash *sh, curl_socket_t s) +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)); @@ -244,7 +345,7 @@ 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); + return (fd % (curl_socket_t)slots_num); } /* @@ -255,99 +356,96 @@ static size_t hash_fd(void *key, size_t key_length, size_t slots_num) * "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 + * it has a linked list with entries. 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 int sh_init(struct curl_hash *hash, int hashsize) +static void sh_init(struct Curl_hash *hash, size_t hashsize) { - return Curl_hash_init(hash, hashsize, hash_fd, fd_key_compare, - sh_freeentry); + 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 CURLMcode multi_addmsg(struct Curl_multi *multi, - struct Curl_message *msg) +/* multi->proto_hash destructor. Should never be called as elements + * MUST be added with their own destructor */ +static void ph_freeentry(void *p) { - if(!Curl_llist_insert_next(multi->msglist, multi->msglist->tail, msg)) - return CURLM_OUT_OF_MEMORY; - - return CURLM_OK; + (void)p; + /* Will always be FALSE. Cannot use a 0 assert here since compilers + * are not in agreement if they then want a NORETURN attribute or + * not. *sigh* */ + DEBUGASSERT(p == NULL); } /* - * multi_freeamsg() + * multi_addmsg() * - * Callback used by the llist system when a single list entry is destroyed. + * Called when a transfer is completed. Adds the given msg pointer to + * the list kept in the multi handle. */ -static void multi_freeamsg(void *a, void *b) +static void multi_addmsg(struct Curl_multi *multi, struct Curl_message *msg) { - (void)a; - (void)b; + Curl_llist_append(&multi->msglist, msg, &msg->list); } -struct Curl_multi *Curl_multi_handle(int hashsize, /* socket hash */ - int chashsize) /* connection hash */ +struct Curl_multi *Curl_multi_handle(size_t hashsize, /* socket hash */ + size_t chashsize, /* connection hash */ + size_t dnssize) /* dns hash */ { struct Curl_multi *multi = calloc(1, sizeof(struct Curl_multi)); if(!multi) return NULL; - multi->type = CURL_MULTI_HANDLE; + multi->magic = CURL_MULTI_HANDLE; - if(Curl_mk_dnscache(&multi->hostcache)) - goto error; + Curl_init_dnscache(&multi->hostcache, dnssize); - if(sh_init(&multi->sockhash, hashsize)) - goto error; + sh_init(&multi->sockhash, hashsize); - if(Curl_conncache_init(&multi->conn_cache, chashsize)) - goto error; + Curl_hash_init(&multi->proto_hash, 23, + Curl_hash_str, Curl_str_key_compare, ph_freeentry); - multi->msglist = Curl_llist_alloc(multi_freeamsg); - if(!multi->msglist) + if(Curl_cpool_init(&multi->cpool, Curl_on_disconnect, + multi, NULL, chashsize)) goto error; - multi->pending = Curl_llist_alloc(multi_freeamsg); - if(!multi->pending) - goto error; - - /* allocate a new easy handle to use when closing cached connections */ - multi->closure_handle = curl_easy_init(); - if(!multi->closure_handle) - goto error; + Curl_llist_init(&multi->msglist, NULL); + Curl_llist_init(&multi->process, NULL); + Curl_llist_init(&multi->pending, NULL); + Curl_llist_init(&multi->msgsent, NULL); - multi->closure_handle->multi = multi; - multi->closure_handle->state.conn_cache = &multi->conn_cache; + multi->multiplexing = TRUE; + multi->max_concurrent_streams = 100; + multi->last_timeout_ms = -1; - multi->max_pipeline_length = 5; +#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, TRUE) < 0) { + multi->wakeup_pair[0] = CURL_SOCKET_BAD; + multi->wakeup_pair[1] = CURL_SOCKET_BAD; + } +#endif +#endif - /* -1 means it not set by user, use the default value */ - multi->maxconnects = -1; return multi; - error: +error: - Curl_hash_destroy(&multi->sockhash); + sockhash_destroy(&multi->sockhash); + Curl_hash_destroy(&multi->proto_hash); Curl_hash_destroy(&multi->hostcache); - Curl_conncache_destroy(&multi->conn_cache); - Curl_close(multi->closure_handle); - multi->closure_handle = NULL; - Curl_llist_destroy(multi->msglist, NULL); - Curl_llist_destroy(multi->pending, NULL); - + Curl_cpool_destroy(&multi->cpool); free(multi); return NULL; } @@ -355,14 +453,28 @@ struct Curl_multi *Curl_multi_handle(int hashsize, /* socket hash */ struct Curl_multi *curl_multi_init(void) { return Curl_multi_handle(CURL_SOCKET_HASH_TABLE_SIZE, - CURL_CONNECTION_HASH_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 CURLMcode curl_multi_add_handle(struct Curl_multi *multi, struct Curl_easy *data) { - struct curl_llist *timeoutlist; - + CURLMcode rc; /* First, make some basic checks that the CURLM handle is a good handle */ if(!GOOD_MULTI_HANDLE(multi)) return CURLM_BAD_HANDLE; @@ -376,66 +488,41 @@ CURLMcode curl_multi_add_handle(struct Curl_multi *multi, if(data->multi) return CURLM_ADDED_ALREADY; - /* Allocate and initialize timeout list for easy handle */ - timeoutlist = Curl_llist_alloc(multi_freetimeout); - if(!timeoutlist) - return CURLM_OUT_OF_MEMORY; + if(multi->in_callback) + return CURLM_RECURSIVE_API_CALL; - /* - * 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. - */ - - /* Make easy handle use timeout list initialized above */ - data->state.timeoutlist = timeoutlist; - timeoutlist = NULL; - - /* set the easy handle */ - multistate(data, CURLM_STATE_INIT); - - if((data->set.global_dns_cache) && - (data->dns.hostcachetype != HCACHE_GLOBAL)) { - /* global dns cache was requested but still isn't */ - struct curl_hash *global = Curl_global_host_cache_init(); - if(global) { - /* only do this if the global cache init works */ - data->dns.hostcache = global; - data->dns.hostcachetype = HCACHE_GLOBAL; - } - } - /* for multi interface connections, we share DNS cache automatically if the - easy handle's one is currently not set. */ - else if(!data->dns.hostcache || - (data->dns.hostcachetype == HCACHE_NONE)) { - data->dns.hostcache = &multi->hostcache; - data->dns.hostcachetype = HCACHE_MULTI; + 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; } - /* Point to the multi's connection cache */ - data->state.conn_cache = &multi->conn_cache; + if(data->multi_easy) { + /* if this easy handle was previously used for curl_easy_perform(), there + is a private multi handle here that we can kill */ + curl_multi_cleanup(data->multi_easy); + data->multi_easy = NULL; + } - /* This adds the new entry at the 'end' of the doubly-linked circular - list of Curl_easy structs to try and maintain a FIFO queue so - the pipelined requests are in order. */ + /* Initialize timeout list for this handle */ + Curl_llist_init(&data->state.timeoutlist, NULL); - /* We add this new entry last in the list. */ + /* + * No failure allowed in this function beyond this point. No modification of + * easy nor multi handle allowed before this except for potential multi's + * connection pool growing which will not be undone in this function no + * matter what. + */ + if(data->set.errorbuffer) + data->set.errorbuffer[0] = 0; - data->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 */ - } + data->state.os_errno = 0; - /* make the Curl_easy refer back to this multi handle */ + /* 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 @@ -444,7 +531,35 @@ CURLMcode curl_multi_add_handle(struct Curl_multi *multi, 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); + Curl_expire(data, 0, EXPIRE_RUN_NOW); + + rc = Curl_update_timer(multi); + if(rc) { + data->multi = NULL; /* not anymore */ + 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; + } + +#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 + + /* add the easy handle to the process list */ + Curl_llist_append(&multi->process, data, &data->multi_queue); /* increase the node-counter */ multi->num_easy++; @@ -452,34 +567,21 @@ CURLMcode curl_multi_add_handle(struct Curl_multi *multi, /* increase the alive-counter */ multi->num_alive++; - /* A somewhat crude work-around for a little glitch in 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 - update_timer() that prevents calling the application multiple times with - the same timer infor 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 - 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)); - - /* 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. */ - multi->closure_handle->set.timeout = data->set.timeout; - multi->closure_handle->set.server_response_timeout = - data->set.server_response_timeout; - - update_timer(multi); + /* the identifier inside the multi instance */ + data->mid = multi->next_easy_mid++; + if(multi->next_easy_mid <= 0) + multi->next_easy_mid = 0; + + Curl_cpool_xfer_init(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); + * Curl_hash_print(&multi->sockhash, debug_print_sock_hash); * * Enable the hash print function first by editing hash.c */ @@ -487,198 +589,215 @@ static void debug_print_sock_hash(void *p) { struct Curl_sh_entry *sh = (struct Curl_sh_entry *)p; - fprintf(stderr, " [easy %p/magic %x/socket %d]", - (void *)sh->data, sh->data->magic, (int)sh->socket); + fprintf(stderr, " [readers %u][writers %u]", + sh->readers, sh->writers); } #endif -/* Mark the connection as 'idle', or close it if the cache is full. - Returns TRUE if the connection is kept, or FALSE if it was closed. */ -static bool -ConnectionDone(struct Curl_easy *data, struct connectdata *conn) +struct multi_done_ctx { + BIT(premature); +}; + +static void multi_done_locked(struct connectdata *conn, + struct Curl_easy *data, + void *userdata) { - /* data->multi->maxconnects can be negative, deal with it. */ - size_t maxconnects = - (data->multi->maxconnects < 0) ? data->multi->num_easy * 4: - data->multi->maxconnects; - struct connectdata *conn_candidate = NULL; + struct multi_done_ctx *mdctx = userdata; + + Curl_detach_connection(data); - /* Mark the current connection as 'unused' */ - conn->inuse = FALSE; + if(CONN_INUSE(conn)) { + /* Stop if still used. */ + DEBUGF(infof(data, "Connection still in use %zu, " + "no more multi_done now!", + Curl_llist_count(&conn->easyq))); + return; + } - if(maxconnects > 0 && - data->state.conn_cache->num_connections > maxconnects) { - infof(data, "Connection cache is full, closing the oldest one.\n"); + data->state.done = TRUE; /* called just now! */ + data->state.recent_conn_id = conn->connection_id; - conn_candidate = Curl_oldest_idle_connection(data); + if(conn->dns_entry) + Curl_resolv_unlink(data, &conn->dns_entry); /* done with this */ + Curl_hostcache_prune(data); - if(conn_candidate) { - /* Set the connection's owner correctly */ - conn_candidate->data = 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 - /* the winner gets the honour of being disconnected */ - (void)Curl_disconnect(conn_candidate, /* dead_connection */ FALSE); + 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 cannot know in what + state it is for reusing, so we are 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. + */ + + 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 + || (mdctx->premature && !Curl_conn_is_multiplex(conn, FIRSTSOCKET))) { + DEBUGF(infof(data, "multi_done, not reusing connection=%" + FMT_OFF_T ", forbid=%d" + ", close=%d, premature=%d, conn_multiplex=%d", + conn->connection_id, data->set.reuse_forbid, + conn->bits.close, mdctx->premature, + Curl_conn_is_multiplex(conn, FIRSTSOCKET))); + connclose(conn, "disconnecting"); + Curl_cpool_disconnect(data, conn, mdctx->premature); + } + else { + /* the connection is no longer in use by any transfer */ + if(Curl_cpool_conn_now_idle(data, conn)) { + /* connection kept in the cpool */ + 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; + data->state.lastconnect_id = conn->connection_id; + infof(data, "Connection #%" FMT_OFF_T " to host %s left intact", + conn->connection_id, host); + } + else { + /* connection was removed from the cpool and destroyed. */ + data->state.lastconnect_id = -1; } } - - return (conn_candidate == conn) ? FALSE : TRUE; } -static CURLcode multi_done(struct connectdata **connp, - CURLcode status, /* an error if this is called - after an error was detected */ - bool premature) +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; - struct Curl_easy *data; - - DEBUGASSERT(*connp); + CURLcode result, r2; + struct connectdata *conn = data->conn; + struct multi_done_ctx mdctx; - conn = *connp; - data = conn->data; + memset(&mdctx, 0, sizeof(mdctx)); - DEBUGF(infof(data, "multi_done\n")); +#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; - Curl_getoff_all_pipelines(data, conn); + /* Stop the resolver and free its own resources (but not dns_entry yet). */ + Curl_resolver_kill(data); /* Cleanup possible redirect junk */ - free(data->req.newurl); - data->req.newurl = NULL; - free(data->req.location); - data->req.location = NULL; + 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 + /* When we are aborted due to a callback return code it basically have to + be counted as premature as there is trouble ahead if we do not. We have many callbacks and protocols work differently, we could potentially do this more fine-grained in the future. */ premature = TRUE; + FALLTHROUGH(); default: break; } /* this calls the protocol-specific function pointer previously set */ if(conn->handler->done) - result = conn->handler->done(conn, status, premature); + 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 */ - CURLcode rc = Curl_pgrsDone(conn); + int rc = Curl_pgrsDone(data); if(!result && rc) result = CURLE_ABORTED_BY_CALLBACK; } - if(conn->send_pipe->size + conn->recv_pipe->size != 0 && - !data->set.reuse_forbid && - !conn->bits.close) { - /* Stop if pipeline is not empty and we do not have to close - connection. */ - data->easy_conn = NULL; - DEBUGF(infof(data, "Connection still in use, no more multi_done now!\n")); - return CURLE_OK; - } - - data->state.done = TRUE; /* called just now! */ - Curl_resolver_cancel(conn); + /* Make sure that transfer client writes are really done now. */ + r2 = Curl_xfer_write_done(data, premature); + if(r2 && !result) + result = r2; - if(conn->dns_entry) { - Curl_resolv_unlock(data, conn->dns_entry); /* done with this */ - conn->dns_entry = NULL; - } + /* Inform connection filters that this transfer is done */ + Curl_conn_ev_data_done(data, premature); - /* if the transfer was completed in a paused state there can be buffered - data left to write and then kill */ - free(data->state.tempwrite); - data->state.tempwrite = NULL; + process_pending_handles(data->multi); /* connection / multiplex */ - /* 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 authentication handshake + if(!result) + result = Curl_req_done(&data->req, data, premature); - 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 re-using, 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. - */ - - if((data->set.reuse_forbid -#if defined(USE_NTLM) - && !(conn->ntlm.state == NTLMSTATE_TYPE2 || - conn->proxyntlm.state == NTLMSTATE_TYPE2) -#endif - ) || conn->bits.close || premature) { - CURLcode res2 = Curl_disconnect(conn, premature); /* close connection */ - - /* If we had an error already, make sure we return that one. But - if we got a new error, return that. */ - if(!result && res2) - result = res2; - } - else { - /* the connection is no longer in use */ - if(ConnectionDone(data, conn)) { - /* remember the most recently used connection */ - data->state.lastconnect = conn; - - infof(data, "Connection #%ld to host %s left intact\n", - conn->connection_id, - conn->bits.socksproxy ? conn->socks_proxy.host.dispname : - conn->bits.httpproxy ? conn->http_proxy.host.dispname : - conn->bits.conn_to_host ? conn->conn_to_host.dispname : - conn->host.dispname); - } - else - data->state.lastconnect = NULL; - } - - *connp = NULL; /* to make the caller of this function better detect that - this was either closed or handed over to the connection - cache here, and therefore cannot be used from this point on - */ - Curl_free_request_state(data); + /* Under the potential connection pool's share lock, decide what to + * do with the transfer's connection. */ + mdctx.premature = premature; + Curl_cpool_do_locked(data, data->conn, multi_done_locked, &mdctx); return result; } +static void close_connect_only(struct connectdata *conn, + struct Curl_easy *data, + void *userdata) +{ + (void)userdata; + (void)data; + if(conn->connect_only) + connclose(conn, "Removing connect-only easy handle"); +} + CURLMcode curl_multi_remove_handle(struct Curl_multi *multi, struct Curl_easy *data) { struct Curl_easy *easy = data; bool premature; - bool easy_owns_conn; - struct curl_llist_element *e; + struct Curl_llist_node *e; + CURLMcode rc; + bool removed_timer = FALSE; /* 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)) + if(!GOOD_EASY_HANDLE(data) || !multi->num_easy) 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! */ - premature = (data->mstate < CURLM_STATE_COMPLETED) ? TRUE : FALSE; - easy_owns_conn = (data->easy_conn && (data->easy_conn->data == easy)) ? - TRUE : FALSE; + /* 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. */ @@ -686,242 +805,337 @@ CURLMcode curl_multi_remove_handle(struct Curl_multi *multi, /* this handle is "alive" so we need to count down the total number of alive connections when this is removed */ multi->num_alive--; - - /* When this handle gets removed, other handles may be able to get the - connection */ - Curl_multi_process_pending_handles(multi); } - if(data->easy_conn && - data->mstate > CURLM_STATE_DO && - data->mstate < CURLM_STATE_COMPLETED) { - /* If the handle is in a pipeline and has started sending off its - request but not received its response yet, we need to close - connection. */ - streamclose(data->easy_conn, "Removed with partial response"); - /* Set connection owner so that the DONE function closes it. We can + 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. */ - data->easy_conn->data = easy; - easy_owns_conn = TRUE; + streamclose(data->conn, "Removed with partial response"); } - /* 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. */ - Curl_expire_clear(data); + if(data->conn) { + /* multi_done() clears the association between the easy handle and the + connection. - if(data->dns.hostcachetype == HCACHE_MULTI) { - /* stop using the multi handle's DNS cache */ - data->dns.hostcache = NULL; - data->dns.hostcachetype = HCACHE_NONE; + Note that this ignores the return code simply because there is + nothing really useful to do with it anyway! */ + (void)multi_done(data, data->result, premature); } - if(data->easy_conn) { - - /* we must call multi_done() here (if we still own the connection) so that - we don't leave a half-baked one around */ - if(easy_owns_conn) { + /* 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! */ + removed_timer = Curl_expire_clear(data); - /* multi_done() clears the conn->data field to lose the association - between the easy handle and the connection + /* the handle is in a list, remove it from whichever it is */ + Curl_node_remove(&data->multi_queue); - Note that this ignores the return code simply because there's - nothing really useful to do with it anyway! */ - (void)multi_done(&data->easy_conn, data->result, premature); - } - else - /* Clear connection pipelines, if multi_done above was not called */ - Curl_getoff_all_pipelines(data, data->easy_conn); + 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); - /* destroy the timeout list that is held in the easy handle, do this *after* - multi_done() as that may actually call Curl_expire that uses this */ - if(data->state.timeoutlist) { - Curl_llist_destroy(data->state.timeoutlist, NULL); - data->state.timeoutlist = NULL; - } - - /* 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; - /* change state without using multistate(), only to make singlesocket() do what we want */ - data->mstate = CURLM_STATE_COMPLETED; - singlesocket(multi, easy); /* to let the application know what sockets that - vanish with this handle */ + data->mstate = MSTATE_COMPLETED; + + /* This ignores the return code even in case of problems because there is + 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 */ - if(data->easy_conn) { - data->easy_conn->data = NULL; - data->easy_conn = NULL; + 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. 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_cpool_disconnect(data, c, TRUE); + } } - data->multi = NULL; /* clear the association to this multi handle */ + if(data->state.lastconnect_id != -1) { + /* Mark any connect-only connection for closure */ + Curl_cpool_do_by_id(data, data->state.lastconnect_id, + close_connect_only, NULL); + } - /* make sure there's no pending message in the queue sent from this easy - handle */ +#ifdef USE_LIBPSL + /* Remove the PSL association. */ + if(data->psl == &multi->psl) + data->psl = NULL; +#endif - for(e = multi->msglist->head; e; e = e->next) { - struct Curl_message *msg = e->ptr; + /* make sure there is no pending message in the queue sent from this easy + handle */ + for(e = Curl_llist_head(&multi->msglist); e; e = Curl_node_next(e)) { + struct Curl_message *msg = Curl_node_elem(e); if(msg->extmsg.easy_handle == easy) { - Curl_llist_remove(multi->msglist, e, NULL); + Curl_node_remove(e); /* there can only be one from this specific handle */ break; } } - /* 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->multi = NULL; /* clear the association to this multi handle */ + data->mid = -1; /* NOTE NOTE NOTE We do not touch the easy handle here! */ multi->num_easy--; /* one less to care about now */ + process_pending_handles(multi); - update_timer(multi); + if(removed_timer) { + rc = Curl_update_timer(multi); + if(rc) + return rc; + } return CURLM_OK; } -/* Return TRUE if the application asked for a certain set of pipelining */ -bool Curl_pipeline_wanted(const struct Curl_multi *multi, int bits) +/* Return TRUE if the application asked for multiplexing */ +bool Curl_multiplex_wanted(const struct Curl_multi *multi) { - return (multi && (multi->pipelining & bits)) ? TRUE : FALSE; + return (multi && (multi->multiplexing)); } -void Curl_multi_handlePipeBreak(struct Curl_easy *data) +/* + * 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) { - data->easy_conn = NULL; + struct connectdata *conn = data->conn; + if(conn) { + Curl_conn_ev_data_detach(conn, data); + Curl_node_remove(&data->conn_queue); + } + data->conn = NULL; } -static int waitconnect_getsock(struct connectdata *conn, - curl_socket_t *sock, - int numsocks) +/* + * 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) { - int i; - int s=0; - int rc=0; - - if(!numsocks) - return GETSOCK_BLANK; + DEBUGASSERT(data); + DEBUGASSERT(!data->conn); + DEBUGASSERT(conn); + data->conn = conn; + Curl_llist_append(&conn->easyq, data, &data->conn_queue); + if(conn->handler && conn->handler->attach) + conn->handler->attach(data, conn); + Curl_conn_ev_data_attach(conn, data); +} -#ifdef USE_SSL - if(CONNECT_FIRSTSOCKET_PROXY_SSL()) - return Curl_ssl_getsock(conn, sock, numsocks); -#endif +static int connecting_getsock(struct Curl_easy *data, curl_socket_t *socks) +{ + struct connectdata *conn = data->conn; + curl_socket_t sockfd; - for(i=0; i<2; i++) { - if(conn->tempsock[i] != CURL_SOCKET_BAD) { - sock[s] = conn->tempsock[i]; - rc |= GETSOCK_WRITESOCK(s++); - } + if(!conn) + return GETSOCK_BLANK; + sockfd = Curl_conn_get_socket(data, FIRSTSOCKET); + if(sockfd != CURL_SOCKET_BAD) { + /* Default is to wait to something from the server */ + socks[0] = sockfd; + return GETSOCK_READSOCK(0); } - - return rc; + return GETSOCK_BLANK; } -static int waitproxyconnect_getsock(struct connectdata *conn, - curl_socket_t *sock, - int numsocks) +static int protocol_getsock(struct Curl_easy *data, curl_socket_t *socks) { - if(!numsocks) - return GETSOCK_BLANK; - - sock[0] = conn->sock[FIRSTSOCKET]; + struct connectdata *conn = data->conn; + curl_socket_t sockfd; - /* when we've sent a CONNECT to a proxy, we should rather wait for the - socket to become readable to be able to get the response headers */ - if(conn->tunnel_state[FIRSTSOCKET] == TUNNEL_CONNECT) + if(!conn) + return GETSOCK_BLANK; + if(conn->handler->proto_getsock) + return conn->handler->proto_getsock(data, conn, socks); + sockfd = Curl_conn_get_socket(data, FIRSTSOCKET); + if(sockfd != CURL_SOCKET_BAD) { + /* Default is to wait to something from the server */ + socks[0] = sockfd; return GETSOCK_READSOCK(0); + } + return GETSOCK_BLANK; +} - return GETSOCK_WRITESOCK(0); +static int domore_getsock(struct Curl_easy *data, curl_socket_t *socks) +{ + struct connectdata *conn = data->conn; + if(!conn) + return GETSOCK_BLANK; + if(conn->handler->domore_getsock) + return conn->handler->domore_getsock(data, conn, socks); + else if(conn->sockfd != CURL_SOCKET_BAD) { + /* Default is that we want to send something to the server */ + socks[0] = conn->sockfd; + return GETSOCK_WRITESOCK(0); + } + return GETSOCK_BLANK; } -static int domore_getsock(struct connectdata *conn, - curl_socket_t *socks, - int numsocks) +static int doing_getsock(struct Curl_easy *data, curl_socket_t *socks) { - if(conn && conn->handler->domore_getsock) - return conn->handler->domore_getsock(conn, socks, numsocks); + struct connectdata *conn = data->conn; + if(!conn) + return GETSOCK_BLANK; + if(conn->handler->doing_getsock) + return conn->handler->doing_getsock(data, conn, socks); + else if(conn->sockfd != CURL_SOCKET_BAD) { + /* Default is that we want to send something to the server */ + socks[0] = conn->sockfd; + return GETSOCK_WRITESOCK(0); + } return GETSOCK_BLANK; } -/* returns bitmapped flags for this handle and its sockets */ -static int multi_getsock(struct Curl_easy *data, - curl_socket_t *socks, /* points to numsocks number - of sockets */ - int numsocks) +static int perform_getsock(struct Curl_easy *data, curl_socket_t *sock) { - /* If the pipe broke, or if there's no connection left for this easy handle, - then we MUST bail out now with no bitmask set. The no connection case can - happen when this is called from curl_multi_remove_handle() => - singlesocket() => multi_getsock(). - */ - if(data->state.pipe_broke || !data->easy_conn) - return 0; + struct connectdata *conn = data->conn; + if(!conn) + return GETSOCK_BLANK; + else if(conn->handler->perform_getsock) + return conn->handler->perform_getsock(data, conn, sock); + else { + /* Default is to obey the data->req.keepon flags for send/recv */ + int bitmap = GETSOCK_BLANK; + unsigned sockindex = 0; + if(CURL_WANT_RECV(data)) { + DEBUGASSERT(conn->sockfd != CURL_SOCKET_BAD); + bitmap |= GETSOCK_READSOCK(sockindex); + sock[sockindex] = conn->sockfd; + } + + if(Curl_req_want_send(data)) { + 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 */ - if(data->mstate > CURLM_STATE_CONNECT && - data->mstate < CURLM_STATE_COMPLETED) { - /* Set up ownership correctly */ - data->easy_conn->data = data; + DEBUGASSERT(conn->writesockfd != CURL_SOCKET_BAD); + sock[sockindex] = conn->writesockfd; + } + bitmap |= GETSOCK_WRITESOCK(sockindex); + } + return bitmap; } +} + +/* 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) +{ + bool expect_sockets = TRUE; + /* 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: -#if 0 /* switch back on these cases to get the compiler to check for all enums - to be present */ - case CURLM_STATE_TOOFAST: /* returns 0, so will not select. */ - case CURLM_STATE_COMPLETED: - case CURLM_STATE_MSGSENT: - case CURLM_STATE_INIT: - case CURLM_STATE_CONNECT: - case CURLM_STATE_WAITDO: - case CURLM_STATE_DONE: - case CURLM_STATE_LAST: - /* this will get called with CURLM_STATE_COMPLETED when a handle is - removed */ -#endif - return 0; + case MSTATE_INIT: + case MSTATE_PENDING: + case MSTATE_SETUP: + case MSTATE_CONNECT: + /* nothing to poll for yet */ + expect_sockets = FALSE; + break; - case CURLM_STATE_WAITRESOLVE: - return Curl_resolver_getsock(data->easy_conn, socks, numsocks); + case MSTATE_RESOLVING: + Curl_pollset_add_socks(data, ps, Curl_resolv_getsock); + /* connection filters are not involved in this phase. It's ok if we get no + * sockets to wait for. Resolving can wake up from other sources. */ + expect_sockets = FALSE; + break; - case CURLM_STATE_PROTOCONNECT: - case CURLM_STATE_SENDPROTOCONNECT: - return Curl_protocol_getsock(data->easy_conn, socks, numsocks); + case MSTATE_CONNECTING: + case MSTATE_TUNNELING: + Curl_pollset_add_socks(data, ps, connecting_getsock); + Curl_conn_adjust_pollset(data, ps); + break; - case CURLM_STATE_DO: - case CURLM_STATE_DOING: - return Curl_doing_getsock(data->easy_conn, socks, numsocks); + case MSTATE_PROTOCONNECT: + case MSTATE_PROTOCONNECTING: + Curl_pollset_add_socks(data, ps, protocol_getsock); + Curl_conn_adjust_pollset(data, ps); + break; - case CURLM_STATE_WAITPROXYCONNECT: - return waitproxyconnect_getsock(data->easy_conn, socks, numsocks); + case MSTATE_DO: + case MSTATE_DOING: + Curl_pollset_add_socks(data, ps, doing_getsock); + Curl_conn_adjust_pollset(data, ps); + break; - case CURLM_STATE_WAITCONNECT: - return waitconnect_getsock(data->easy_conn, socks, numsocks); + case MSTATE_DOING_MORE: + Curl_pollset_add_socks(data, ps, domore_getsock); + Curl_conn_adjust_pollset(data, ps); + break; - case CURLM_STATE_DO_MORE: - return domore_getsock(data->easy_conn, socks, numsocks); + case MSTATE_DID: /* same as PERFORMING in regard to polling */ + case MSTATE_PERFORMING: + Curl_pollset_add_socks(data, ps, perform_getsock); + Curl_conn_adjust_pollset(data, ps); + break; - case CURLM_STATE_DO_DONE: /* since is set after DO is completed, we switch - to waiting for the same as the *PERFORM - states */ - case CURLM_STATE_PERFORM: - case CURLM_STATE_WAITPERFORM: - return Curl_single_getsock(data->easy_conn, socks, numsocks); + case MSTATE_RATELIMITING: + /* we need to let time pass, ignore socket(s) */ + expect_sockets = FALSE; + break; + + case MSTATE_DONE: + case MSTATE_COMPLETED: + case MSTATE_MSGSENT: + /* nothing more to poll for */ + expect_sockets = FALSE; + break; + + default: + failf(data, "multi_getsock: unexpected multi state %d", data->mstate); + DEBUGASSERT(0); + expect_sockets = FALSE; + break; } + if(expect_sockets && !ps->num && + !Curl_llist_count(&data->state.timeoutlist) && + !Curl_cwriter_is_paused(data) && !Curl_creader_is_paused(data) && + Curl_conn_is_ip_connected(data, FIRSTSOCKET)) { + /* We expected sockets for POLL monitoring, but none are set. + * We are not waiting on any timer. + * None of the READ/WRITE directions are paused. + * We are connected to the server on IP level, at least. */ + infof(data, "WARNING: no socket in pollset or timer, transfer may stall!"); + DEBUGASSERT(0); + } } CURLMcode curl_multi_fdset(struct Curl_multi *multi, @@ -931,41 +1145,33 @@ CURLMcode curl_multi_fdset(struct Curl_multi *multi, /* 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; - curl_socket_t sockbunch[MAX_SOCKSPEREASYHANDLE]; - int bitmap; - int i; + int this_max_fd = -1; + struct Curl_llist_node *e; (void)exc_fd_set; /* not used */ if(!GOOD_MULTI_HANDLE(multi)) return CURLM_BAD_HANDLE; - data=multi->easyp; - while(data) { - bitmap = multi_getsock(data, sockbunch, MAX_SOCKSPEREASYHANDLE); + if(multi->in_callback) + return CURLM_RECURSIVE_API_CALL; - for(i=0; i< MAX_SOCKSPEREASYHANDLE; i++) { - curl_socket_t s = CURL_SOCKET_BAD; + for(e = Curl_llist_head(&multi->process); e; e = Curl_node_next(e)) { + struct Curl_easy *data = Curl_node_elem(e); + unsigned int i; - if((bitmap & GETSOCK_READSOCK(i)) && VALID_SOCK((sockbunch[i]))) { - FD_SET(sockbunch[i], read_fd_set); - s = sockbunch[i]; - } - if((bitmap & GETSOCK_WRITESOCK(i)) && VALID_SOCK((sockbunch[i]))) { - FD_SET(sockbunch[i], write_fd_set); - s = sockbunch[i]; - } - if(s == CURL_SOCKET_BAD) - /* this socket is unused, break out of loop */ - break; - else { - if((int)s > this_max_fd) - this_max_fd = (int)s; - } - } + multi_getsock(data, &data->last_poll); - data = data->next; /* check next handle */ + for(i = 0; i < data->last_poll.num; i++) { + if(!FDSET_SOCK(data->last_poll.sockets[i])) + /* pretend it does not exist */ + continue; + if(data->last_poll.actions[i] & CURL_POLL_IN) + FD_SET(data->last_poll.sockets[i], read_fd_set); + if(data->last_poll.actions[i] & CURL_POLL_OUT) + FD_SET(data->last_poll.sockets[i], write_fd_set); + if((int)data->last_poll.sockets[i] > this_max_fd) + this_max_fd = (int)data->last_poll.sockets[i]; + } } *max_fd = this_max_fd; @@ -973,156 +1179,393 @@ CURLMcode curl_multi_fdset(struct Curl_multi *multi, 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) +CURLMcode curl_multi_waitfds(struct Curl_multi *multi, + struct curl_waitfd *ufds, + unsigned int size, + unsigned int *fd_count) { - struct Curl_easy *data; - curl_socket_t sockbunch[MAX_SOCKSPEREASYHANDLE]; - int bitmap; - unsigned int i; - unsigned int nfds = 0; - unsigned int curlfds; - struct pollfd *ufds = NULL; - long timeout_internal; - int retcode = 0; + struct curl_waitfds cwfds; + CURLMcode result = CURLM_OK; + struct Curl_llist_node *e; + + if(!ufds) + return CURLM_BAD_FUNCTION_ARGUMENT; if(!GOOD_MULTI_HANDLE(multi)) return CURLM_BAD_HANDLE; - /* 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; - - /* Count up how many fds we have from the multi handle */ - data=multi->easyp; - while(data) { - bitmap = multi_getsock(data, sockbunch, MAX_SOCKSPEREASYHANDLE); - - for(i=0; i< MAX_SOCKSPEREASYHANDLE; i++) { - curl_socket_t s = CURL_SOCKET_BAD; + if(multi->in_callback) + return CURLM_RECURSIVE_API_CALL; - if(bitmap & GETSOCK_READSOCK(i)) { - ++nfds; - s = sockbunch[i]; - } - if(bitmap & GETSOCK_WRITESOCK(i)) { - ++nfds; - s = sockbunch[i]; - } - if(s == CURL_SOCKET_BAD) { - break; - } + Curl_waitfds_init(&cwfds, ufds, size); + for(e = Curl_llist_head(&multi->process); e; e = Curl_node_next(e)) { + struct Curl_easy *data = Curl_node_elem(e); + multi_getsock(data, &data->last_poll); + if(Curl_waitfds_add_ps(&cwfds, &data->last_poll)) { + result = CURLM_OUT_OF_MEMORY; + goto out; } + } - data = data->next; /* check next handle */ + if(Curl_cpool_add_waitfds(&multi->cpool, &cwfds)) { + result = CURLM_OUT_OF_MEMORY; + goto out; } - curlfds = nfds; /* number of internal file descriptors */ - nfds += extra_nfds; /* add the externally provided ones */ +out: + if(fd_count) + *fd_count = cwfds.n; + return result; +} - if(nfds || extra_nfds) { - ufds = malloc(nfds * sizeof(struct pollfd)); - if(!ufds) - return CURLM_OUT_OF_MEMORY; - } - nfds = 0; +#ifdef USE_WINSOCK +/* Reset FD_WRITE for TCP sockets. Nothing is actually sent. UDP sockets cannot + * 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 - /* only do the second loop if we found descriptors in the first stage run - above */ +#define NUM_POLLS_ON_STACK 10 - if(curlfds) { - /* Add the curl handles to our pollfds first */ - data=multi->easyp; - while(data) { - bitmap = multi_getsock(data, sockbunch, MAX_SOCKSPEREASYHANDLE); +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) +{ + size_t i; + struct curltime expire_time; + long timeout_internal; + int retcode = 0; + struct pollfd a_few_on_stack[NUM_POLLS_ON_STACK]; + struct curl_pollfds cpfds; + unsigned int curl_nfds = 0; /* how many pfds are for curl transfers */ + CURLMcode result = CURLM_OK; + struct Curl_llist_node *e; - for(i=0; i< MAX_SOCKSPEREASYHANDLE; i++) { - curl_socket_t s = CURL_SOCKET_BAD; +#ifdef USE_WINSOCK + WSANETWORKEVENTS wsa_events; + DEBUGASSERT(multi->wsa_event != WSA_INVALID_EVENT); +#endif +#ifndef ENABLE_WAKEUP + (void)use_wakeup; +#endif - if(bitmap & GETSOCK_READSOCK(i)) { - ufds[nfds].fd = sockbunch[i]; - ufds[nfds].events = POLLIN; - ++nfds; - s = sockbunch[i]; - } - if(bitmap & GETSOCK_WRITESOCK(i)) { - ufds[nfds].fd = sockbunch[i]; - ufds[nfds].events = POLLOUT; - ++nfds; - s = sockbunch[i]; - } - if(s == CURL_SOCKET_BAD) { - break; - } - } + 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; - data = data->next; /* check next handle */ + Curl_pollfds_init(&cpfds, a_few_on_stack, NUM_POLLS_ON_STACK); + + /* Add the curl handles to our pollfds first */ + for(e = Curl_llist_head(&multi->process); e; e = Curl_node_next(e)) { + struct Curl_easy *data = Curl_node_elem(e); + + multi_getsock(data, &data->last_poll); + if(Curl_pollfds_add_ps(&cpfds, &data->last_poll)) { + result = CURLM_OUT_OF_MEMORY; + goto out; } } + if(Curl_cpool_add_pollfds(&multi->cpool, &cpfds)) { + result = CURLM_OUT_OF_MEMORY; + goto out; + } + + curl_nfds = cpfds.n; /* what curl internally uses in cpfds */ /* Add external file descriptions from poll-like struct curl_waitfd */ for(i = 0; i < extra_nfds; i++) { - ufds[nfds].fd = extra_fds[i].fd; - ufds[nfds].events = 0; + unsigned short events = 0; if(extra_fds[i].events & CURL_WAIT_POLLIN) - ufds[nfds].events |= POLLIN; + events |= POLLIN; if(extra_fds[i].events & CURL_WAIT_POLLPRI) - ufds[nfds].events |= POLLPRI; + events |= POLLPRI; if(extra_fds[i].events & CURL_WAIT_POLLOUT) - ufds[nfds].events |= POLLOUT; - ++nfds; + events |= POLLOUT; + if(Curl_pollfds_add_sock(&cpfds, extra_fds[i].fd, events)) { + result = CURLM_OUT_OF_MEMORY; + goto out; + } } - if(nfds) { +#ifdef USE_WINSOCK + /* Set the WSA events based on the collected pollds */ + for(i = 0; i < cpfds.n; i++) { + long mask = 0; + if(cpfds.pfds[i].events & POLLIN) + mask |= FD_READ|FD_ACCEPT|FD_CLOSE; + if(cpfds.pfds[i].events & POLLPRI) + mask |= FD_OOB; + if(cpfds.pfds[i].events & POLLOUT) { + mask |= FD_WRITE|FD_CONNECT|FD_CLOSE; + reset_socket_fdwrite(cpfds.pfds[i].fd); + } + if(mask) { + if(WSAEventSelect(cpfds.pfds[i].fd, multi->wsa_event, mask) != 0) { + result = CURLM_OUT_OF_MEMORY; + goto out; + } + } + } +#endif + +#ifdef ENABLE_WAKEUP +#ifndef USE_WINSOCK + if(use_wakeup && multi->wakeup_pair[0] != CURL_SOCKET_BAD) { + if(Curl_pollfds_add_sock(&cpfds, multi->wakeup_pair[0], POLLIN)) { + result = CURLM_OUT_OF_MEMORY; + goto out; + } + } +#endif +#endif + + /* We check the internal timeout *AFTER* we collected all sockets to + * poll. Collecting the sockets may install new timers by protocols + * and connection filters. + * Use the shorter one of the internal and the caller requested timeout. */ + (void)multi_timeout(multi, &expire_time, &timeout_internal); + if((timeout_internal >= 0) && (timeout_internal < (long)timeout_ms)) + timeout_ms = (int)timeout_internal; + +#if defined(ENABLE_WAKEUP) && defined(USE_WINSOCK) + if(cpfds.n || use_wakeup) { +#else + if(cpfds.n) { +#endif int pollrc; - /* wait... */ - pollrc = Curl_poll(ufds, nfds, timeout_ms); - DEBUGF(infof(data, "Curl_poll(%d ds, %d ms) == %d\n", - nfds, timeout_ms, pollrc)); +#ifdef USE_WINSOCK + if(cpfds.n) /* just pre-check with Winsock */ + pollrc = Curl_poll(cpfds.pfds, cpfds.n, 0); + else + pollrc = 0; +#else + pollrc = Curl_poll(cpfds.pfds, cpfds.n, timeout_ms); /* wait... */ +#endif + if(pollrc < 0) { + result = CURLM_UNRECOVERABLE_POLL; + goto out; + } 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, (DWORD)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 = (unsigned)cpfds.pfds[curl_nfds + i].revents; unsigned short mask = 0; - unsigned r = ufds[curlfds + i].revents; - +#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 = (short)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 = (short)mask; + } + +#ifdef USE_WINSOCK + /* Count up all our own sockets that had activity, + and remove them from the event. */ + if(curl_nfds) { + for(e = Curl_llist_head(&multi->process); e && !result; + e = Curl_node_next(e)) { + struct Curl_easy *data = Curl_node_elem(e); + + for(i = 0; i < data->last_poll.num; i++) { + wsa_events.lNetworkEvents = 0; + if(WSAEnumNetworkEvents(data->last_poll.sockets[i], NULL, + &wsa_events) == 0) { + if(ret && !pollrc && wsa_events.lNetworkEvents) + retcode++; + } + WSAEventSelect(data->last_poll.sockets[i], multi->wsa_event, 0); + } + } + } - extra_fds[i].revents = mask; + WSAResetEvent(multi->wsa_event); +#else +#ifdef ENABLE_WAKEUP + if(use_wakeup && multi->wakeup_pair[0] != CURL_SOCKET_BAD) { + if(cpfds.pfds[curl_nfds + 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 } } - free(ufds); if(ret) *ret = retcode; - return CURLM_OK; +#if defined(ENABLE_WAKEUP) && defined(USE_WINSOCK) + if(extrawait && !cpfds.n && !use_wakeup) { +#else + if(extrawait && !cpfds.n) { +#endif + long sleep_ms = 0; + + /* Avoid busy-looping when there is 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); + } + } + +out: + Curl_pollfds_cleanup(&cpfds); + return result; } -/* - * Curl_multi_connchanged() is called to tell that there is a connection in - * this multi handle that has changed state (pipelining 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) +CURLMcode curl_multi_wait(struct Curl_multi *multi, + struct curl_waitfd extra_fds[], + unsigned int extra_nfds, + int timeout_ms, + int *ret) { - multi->recheckstate = TRUE; + 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 */ + +#if defined(ENABLE_WAKEUP) && !defined(USE_WINSOCK) +#ifdef USE_EVENTFD + const void *buf; + const uint64_t val = 1; +#else + char buf[1]; +#endif +#endif + + /* 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) { +#ifdef USE_EVENTFD + buf = &val; +#else + buf[0] = 1; +#endif + 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 is 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; } /* @@ -1141,399 +1584,425 @@ static bool multi_ischanged(struct Curl_multi *multi, bool clear) 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 + /* pass in NULL for 'conn' here since we do not want to init the connection, only this transfer */ Curl_init_do(data, NULL); /* take this handle to the perform state right away */ - multistate(data, CURLM_STATE_PERFORM); - data->easy_conn = conn; + multistate(data, MSTATE_PERFORMING); + Curl_attach_connection(data, conn); k->keepon |= KEEP_RECV; /* setup to receive! */ } return rc; } -static CURLcode multi_reconnect_request(struct connectdata **connp) +static CURLcode multi_do(struct Curl_easy *data, bool *done) { CURLcode result = CURLE_OK; - struct connectdata *conn = *connp; - struct Curl_easy *data = conn->data; + struct connectdata *conn = data->conn; - /* This was a re-use of a connection and we got a write error in the - * DO-phase. Then we DISCONNECT this connection and have another attempt to - * CONNECT and then DO again! The retry cannot possibly find another - * connection to re-use, since we only keep one possible connection for - * each. */ + DEBUGASSERT(conn); + DEBUGASSERT(conn->handler); - infof(data, "Re-used connection seems dead, get a new one\n"); + if(conn->handler->do_it) + result = conn->handler->do_it(data, done); - connclose(conn, "Reconnect dead connection"); /* enforce close */ - result = multi_done(&conn, result, FALSE); /* we are so done with this */ + return result; +} - /* conn may no longer be a good pointer, clear it to avoid mistakes by - parent functions */ - *connp = NULL; +/* + * 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 is more work to do! + */ - /* - * We need to check for CURLE_SEND_ERROR here as well. This could happen - * when the request failed on a FTP connection and thus multi_done() itself - * tried to use the connection (again). - */ - if(!result || (CURLE_SEND_ERROR == result)) { - bool async; - bool protocol_done = TRUE; - - /* Now, redo the connect and get a new connection */ - result = Curl_connect(data, connp, &async, &protocol_done); - if(!result) { - /* We have connected or sent away a name resolve query fine */ - - conn = *connp; /* setup conn to again point to something nice */ - if(async) { - /* Now, if async is TRUE here, we need to wait for the name - to resolve */ - result = Curl_resolver_wait_resolv(conn, NULL); - if(result) - return result; +static CURLcode multi_do_more(struct Curl_easy *data, int *complete) +{ + CURLcode result = CURLE_OK; + struct connectdata *conn = data->conn; - /* Resolved, continue with the connection */ - result = Curl_async_resolved(conn, &protocol_done); - if(result) - return result; - } - } - } + *complete = 0; + + if(conn->handler->do_more) + result = conn->handler->do_more(data, complete); return result; } /* - * do_complete is called when the DO actions are complete. - * - * We init chunking and trailer bits to their default values here immediately - * before receiving any header data for the current request in the pipeline. + * Check whether a timeout occurred, and handle it if it did */ -static void do_complete(struct connectdata *conn) +static bool multi_handle_timeout(struct Curl_easy *data, + struct curltime *now, + bool *stream_error, + CURLcode *result) { - conn->data->req.chunk=FALSE; - conn->data->req.maxfd = (conn->sockfd>conn->writesockfd? - conn->sockfd:conn->writesockfd)+1; - Curl_pgrsTime(conn->data, TIMER_PRETRANSFER); + bool connect_timeout = data->mstate < MSTATE_DO; + timediff_t timeout_ms = Curl_timeleft(data, now, connect_timeout); + if(timeout_ms < 0) { + /* Handle timed out */ + struct curltime since; + if(connect_timeout) + since = data->progress.t_startsingle; + else + since = data->progress.t_startop; + if(data->mstate == MSTATE_RESOLVING) + failf(data, "Resolving timed out after %" FMT_TIMEDIFF_T + " milliseconds", Curl_timediff(*now, since)); + else if(data->mstate == MSTATE_CONNECTING) + failf(data, "Connection timed out after %" FMT_TIMEDIFF_T + " milliseconds", Curl_timediff(*now, since)); + else { + struct SingleRequest *k = &data->req; + if(k->size != -1) { + failf(data, "Operation timed out after %" FMT_TIMEDIFF_T + " milliseconds with %" FMT_OFF_T " out of %" + FMT_OFF_T " bytes received", + Curl_timediff(*now, since), k->bytecount, k->size); + } + else { + failf(data, "Operation timed out after %" FMT_TIMEDIFF_T + " milliseconds with %" FMT_OFF_T " bytes received", + Curl_timediff(*now, since), k->bytecount); + } + } + *result = CURLE_OPERATION_TIMEDOUT; + if(data->conn) { + /* Force connection closed if the connection has indeed been used */ + if(data->mstate > MSTATE_DO) { + streamclose(data->conn, "Disconnect due to timeout"); + *stream_error = TRUE; + } + (void)multi_done(data, *result, TRUE); + } + return TRUE; + } + + return FALSE; } -static CURLcode multi_do(struct connectdata **connp, bool *done) +/* + * 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 = *connp; - struct Curl_easy *data = conn->data; + CURLcode result = CURLE_OK; + struct connectdata *conn = data->conn; - if(conn->handler->do_it) { - /* generic protocol-specific function pointer set in curl_connect() */ - result = conn->handler->do_it(conn, done); + if(conn && conn->handler->connecting) { + *done = FALSE; + result = conn->handler->connecting(data, done); + } + else + *done = TRUE; - /* This was formerly done in transfer.c, but we better do it here */ - if((CURLE_SEND_ERROR == result) && conn->bits.reuse) { - /* - * If the connection is using an easy handle, call reconnect - * to re-establish the connection. Otherwise, let the multi logic - * figure out how to re-establish the connection. - */ - if(!data->multi) { - result = multi_reconnect_request(connp); + return result; +} - if(!result) { - /* ... finally back to actually retry the DO phase */ - conn = *connp; /* re-assign conn since multi_reconnect_request - creates a new connection */ - result = conn->handler->do_it(conn, done); - } - } - else - 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. + */ - if(!result && *done) - /* do_complete must be called after the protocol-specific DO function */ - do_complete(conn); +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; } /* - * 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. + * We have discovered that the TCP connection has been successful, we can now + * proceed with some action. * - * TODO: A future libcurl should be able to work away this state. - * - * '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 connectdata *conn, int *complete) +static CURLcode protocol_connect(struct Curl_easy *data, + bool *protocol_done) { - CURLcode result=CURLE_OK; + CURLcode result = CURLE_OK; + struct connectdata *conn = data->conn; + DEBUGASSERT(conn); + DEBUGASSERT(protocol_done); - *complete = 0; + *protocol_done = FALSE; - if(conn->handler->do_more) - result = conn->handler->do_more(conn, complete); + 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 do not know if the protocol is actually done. - if(!result && (*complete == 1)) - /* do_complete must be called after the protocol-specific DO function */ - do_complete(conn); + Unless this protocol does not have any protocol-connect callback, as + then we know we are done. */ + if(!conn->handler->connecting) + *protocol_done = TRUE; - return result; + 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 is not stored + in this bit! */ + if(!result) + conn->bits.protoconnstart = TRUE; + } + + return result; /* pass back status */ +} + +static void set_in_callback(struct Curl_multi *multi, bool value) +{ + multi->in_callback = value; +} + +/* + * posttransfer() is called immediately after a transfer ends + */ +static void multi_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 } static CURLMcode multi_runsingle(struct Curl_multi *multi, - struct timeval now, + struct curltime *nowp, struct Curl_easy *data) { struct Curl_message *msg = NULL; bool connected; bool async; - bool protocol_connect = FALSE; + bool protocol_connected = FALSE; bool dophase_done = FALSE; - bool done = FALSE; CURLMcode rc; CURLcode result = CURLE_OK; - struct SingleRequest *k; - time_t timeout_ms; - time_t recv_timeout_ms; - time_t send_timeout_ms; + 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; + multi_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; - /* Handle the case when the pipe breaks, i.e., the connection - we're using gets cleaned up and we're left with nothing. */ - if(data->state.pipe_broke) { - infof(data, "Pipe broke: handle %p, url = %s\n", - (void *)data, data->state.path); - - if(data->mstate < CURLM_STATE_COMPLETED) { - /* Head back to the CONNECT state */ - multistate(data, CURLM_STATE_CONNECT); - rc = CURLM_CALL_MULTI_PERFORM; - result = CURLE_OK; - } - - data->state.pipe_broke = FALSE; - data->easy_conn = NULL; - continue; - } - - if(!data->easy_conn && - data->mstate > CURLM_STATE_CONNECT && - data->mstate < CURLM_STATE_DONE) { - /* In all these states, the code will blindly access 'data->easy_conn' - so this is precaution that it isn't NULL. And it silences static - analyzers. */ - failf(data, "In state %d with no easy_conn, bail out!\n", data->mstate); - return CURLM_INTERNAL_ERROR; - } - if(multi_ischanged(multi, TRUE)) { - DEBUGF(infof(data, "multi changed, check CONNECT_PEND queue!\n")); - Curl_multi_process_pending_handles(multi); + DEBUGF(infof(data, "multi changed, check CONNECT_PEND queue")); + process_pending_handles(multi); /* multiplexed */ } - if(data->easy_conn && data->mstate > CURLM_STATE_CONNECT && - data->mstate < CURLM_STATE_COMPLETED) + if(data->mstate > MSTATE_CONNECT && + data->mstate < MSTATE_COMPLETED) { /* Make sure we set the connection's current owner */ - data->easy_conn->data = data; - - if(data->easy_conn && - (data->mstate >= CURLM_STATE_CONNECT) && - (data->mstate < CURLM_STATE_COMPLETED)) { - /* we need to wait for the connect state as only then is the start time - stored, but we must not check already completed handles */ - - timeout_ms = Curl_timeleft(data, &now, - (data->mstate <= CURLM_STATE_WAITDO)? - TRUE:FALSE); - - if(timeout_ms < 0) { - /* Handle timed out */ - if(data->mstate == CURLM_STATE_WAITRESOLVE) - failf(data, "Resolving timed out after %ld milliseconds", - Curl_tvdiff(now, data->progress.t_startsingle)); - else if(data->mstate == CURLM_STATE_WAITCONNECT) - failf(data, "Connection timed out after %ld milliseconds", - Curl_tvdiff(now, data->progress.t_startsingle)); - else { - k = &data->req; - if(k->size != -1) { - failf(data, "Operation timed out after %ld milliseconds with %" - CURL_FORMAT_CURL_OFF_T " out of %" - CURL_FORMAT_CURL_OFF_T " bytes received", - Curl_tvdiff(now, data->progress.t_startsingle), - k->bytecount, k->size); - } - else { - failf(data, "Operation timed out after %ld milliseconds with %" - CURL_FORMAT_CURL_OFF_T " bytes received", - Curl_tvdiff(now, data->progress.t_startsingle), - k->bytecount); - } - } - - /* Force connection closed if the connection has indeed been used */ - if(data->mstate > CURLM_STATE_DO) { - streamclose(data->easy_conn, "Disconnected with pending data"); - stream_error = TRUE; - } - result = CURLE_OPERATION_TIMEDOUT; - (void)multi_done(&data->easy_conn, result, TRUE); - /* Skip the statemachine and go directly to error handling section. */ - goto statemachine_end; - } + DEBUGASSERT(data->conn); + if(!data->conn) + return CURLM_INTERNAL_ERROR; } - switch(data->mstate) { - case CURLM_STATE_INIT: - /* init this transfer. */ - result=Curl_pretransfer(data); - - if(!result) { - /* after init, go CONNECT */ - multistate(data, CURLM_STATE_CONNECT); - Curl_pgrsTime(data, TIMER_STARTOP); - rc = CURLM_CALL_MULTI_PERFORM; - } - break; + /* Wait for the connect state as only then is the start time stored, but + we must not check already completed handles */ + if((data->mstate >= MSTATE_CONNECT) && (data->mstate < MSTATE_COMPLETED) && + multi_handle_timeout(data, nowp, &stream_error, &result)) + /* Skip the statemachine and go directly to error handling section. */ + goto statemachine_end; - case CURLM_STATE_CONNECT_PEND: - /* We will stay here until there is a connection available. Then - we try again in the CURLM_STATE_CONNECT state. */ - break; + switch(data->mstate) { + case MSTATE_INIT: + /* Transitional state. init this transfer. A handle never comes + back to this state. */ + result = Curl_pretransfer(data); + if(result) + break; - case CURLM_STATE_CONNECT: - /* Connect. We want to get a connection identifier filled in. */ - Curl_pgrsTime(data, TIMER_STARTSINGLE); - result = Curl_connect(data, &data->easy_conn, - &async, &protocol_connect); + /* after init, go SETUP */ + multistate(data, MSTATE_SETUP); + (void)Curl_pgrsTime(data, TIMER_STARTOP); + FALLTHROUGH(); + + case MSTATE_SETUP: + /* Transitional state. Setup things for a new transfer. The handle + can come back to this state on a redirect. */ + *nowp = Curl_pgrsTime(data, TIMER_STARTSINGLE); + if(data->set.timeout) + Curl_expire(data, data->set.timeout, EXPIRE_TIMEOUT); + if(data->set.connecttimeout) + /* Since a connection might go to pending and back to CONNECT several + times before it actually takes off, we need to set the timeout once + in SETUP before we enter CONNECT the first time. */ + Curl_expire(data, data->set.connecttimeout, EXPIRE_CONNECTTIMEOUT); + + multistate(data, MSTATE_CONNECT); + FALLTHROUGH(); + + case MSTATE_CONNECT: + /* Connect. We want to get a connection identifier filled in. This state + can be entered from SETUP and from PENDING. */ + result = Curl_connect(data, &async, &connected); if(CURLE_NO_CONNECTION_AVAILABLE == result) { /* There was no connection available. We will go to the pending state and wait for an available connection. */ - multistate(data, CURLM_STATE_CONNECT_PEND); - - /* add this handle to the list of connect-pending handles */ - if(!Curl_llist_insert_next(multi->pending, multi->pending->tail, data)) - result = CURLE_OUT_OF_MEMORY; - else - result = CURLE_OK; + multistate(data, MSTATE_PENDING); + /* unlink from process list */ + Curl_node_remove(&data->multi_queue); + /* add handle to pending list */ + Curl_llist_append(&multi->pending, data, &data->multi_queue); + result = CURLE_OK; break; } + else + process_pending_handles(data->multi); if(!result) { - /* Add this handle to the send or pend pipeline */ - result = Curl_add_handle_to_pipeline(data, data->easy_conn); - if(result) - stream_error = TRUE; + *nowp = Curl_pgrsTime(data, TIMER_POSTQUEUE); + if(async) + /* We are now waiting for an asynchronous name lookup */ + multistate(data, MSTATE_RESOLVING); else { - if(async) - /* We're now waiting for an asynchronous name lookup */ - multistate(data, CURLM_STATE_WAITRESOLVE); - 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; + /* 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(protocol_connect) - multistate(data, Curl_pipeline_wanted(multi, CURLPIPE_HTTP1)? - CURLM_STATE_WAITDO:CURLM_STATE_DO); - else { -#ifndef CURL_DISABLE_HTTP - if(data->easy_conn->tunnel_state[FIRSTSOCKET] == TUNNEL_CONNECT) - multistate(data, CURLM_STATE_WAITPROXYCONNECT); - else -#endif - multistate(data, CURLM_STATE_WAITCONNECT); + if(connected) { + if(!data->conn->bits.reuse && + Curl_conn_is_multiplex(data->conn, FIRSTSOCKET)) { + /* new connection, can multiplex, wake pending handles */ + process_pending_handles(data->multi); } + multistate(data, MSTATE_PROTOCONNECT); + } + else { + multistate(data, MSTATE_CONNECTING); } } } break; - case CURLM_STATE_WAITRESOLVE: + case MSTATE_RESOLVING: /* awaiting an asynch name resolve to complete */ { struct Curl_dns_entry *dns = NULL; - struct connectdata *conn = data->easy_conn; + 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 if(conn->bits.conn_to_host) - hostname = conn->conn_to_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(conn, hostname, (int)conn->port); + dns = Curl_fetch_addr(data, hostname, conn->primary.remote_port); if(dns) { #ifdef CURLRES_ASYNCH - conn->async.dns = dns; - conn->async.done = TRUE; + data->state.async.dns = dns; + data->state.async.done = TRUE; #endif result = CURLE_OK; - infof(data, "Hostname '%s' was found in DNS cache\n", hostname); + infof(data, "Hostname '%s' was found in DNS cache", hostname); } if(!dns) - result = Curl_resolver_is_resolved(data->easy_conn, &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 + 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. */ - singlesocket(multi, data); + 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_async_resolved(data->easy_conn, &protocol_connect); + result = Curl_once_resolved(data, &connected); if(result) - /* if Curl_async_resolved() returns failure, the connection struct + /* if Curl_once_resolved() returns failure, the connection struct is already freed and gone */ - data->easy_conn = NULL; /* no more connection */ + data->conn = NULL; /* no more connection */ else { /* call again please so that we get the next socket setup */ rc = CURLM_CALL_MULTI_PERFORM; - if(protocol_connect) - multistate(data, Curl_pipeline_wanted(multi, CURLPIPE_HTTP1)? - CURLM_STATE_WAITDO:CURLM_STATE_DO); + if(connected) + multistate(data, MSTATE_PROTOCONNECT); else { -#ifndef CURL_DISABLE_HTTP - if(data->easy_conn->tunnel_state[FIRSTSOCKET] == TUNNEL_CONNECT) - multistate(data, CURLM_STATE_WAITPROXYCONNECT); - else -#endif - multistate(data, CURLM_STATE_WAITCONNECT); + multistate(data, MSTATE_CONNECTING); } } } @@ -1547,414 +2016,429 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, break; #ifndef CURL_DISABLE_HTTP - case CURLM_STATE_WAITPROXYCONNECT: + case MSTATE_TUNNELING: /* this is HTTP-specific, but sending CONNECT to a proxy is HTTP... */ - result = Curl_http_connect(data->easy_conn, &protocol_connect); - - if(data->easy_conn->bits.proxy_connect_closed) { + DEBUGASSERT(data->conn); + result = Curl_http_connect(data, &protocol_connected); + if(!result) { rc = CURLM_CALL_MULTI_PERFORM; - /* connect back to proxy again */ - result = CURLE_OK; - multi_done(&data->easy_conn, CURLE_OK, FALSE); - multistate(data, CURLM_STATE_CONNECT); - } - else if(!result) { - if((data->easy_conn->http_proxy.proxytype != CURLPROXY_HTTPS || - data->easy_conn->bits.proxy_ssl_connected[FIRSTSOCKET]) && - (data->easy_conn->tunnel_state[FIRSTSOCKET] != TUNNEL_CONNECT)) { - rc = CURLM_CALL_MULTI_PERFORM; - /* initiate protocol connect phase */ - multistate(data, CURLM_STATE_SENDPROTOCONNECT); - } + /* initiate protocol connect phase */ + multistate(data, MSTATE_PROTOCONNECT); } + else + stream_error = TRUE; break; #endif - case CURLM_STATE_WAITCONNECT: + case MSTATE_CONNECTING: /* awaiting a completion of an asynch TCP connect */ - result = Curl_is_connected(data->easy_conn, FIRSTSOCKET, &connected); + DEBUGASSERT(data->conn); + result = Curl_conn_connect(data, FIRSTSOCKET, FALSE, &connected); if(connected && !result) { -#ifndef CURL_DISABLE_HTTP - if((data->easy_conn->http_proxy.proxytype == CURLPROXY_HTTPS && - !data->easy_conn->bits.proxy_ssl_connected[FIRSTSOCKET]) || - (data->easy_conn->tunnel_state[FIRSTSOCKET] == TUNNEL_CONNECT)) { - multistate(data, CURLM_STATE_WAITPROXYCONNECT); - break; + if(!data->conn->bits.reuse && + Curl_conn_is_multiplex(data->conn, FIRSTSOCKET)) { + /* new connection, can multiplex, wake pending handles */ + process_pending_handles(data->multi); } -#endif rc = CURLM_CALL_MULTI_PERFORM; - multistate(data, data->easy_conn->bits.tunnel_proxy? - CURLM_STATE_WAITPROXYCONNECT: - CURLM_STATE_SENDPROTOCONNECT); + multistate(data, MSTATE_PROTOCONNECT); } else if(result) { /* failure detected */ - /* Just break, the cleaning up is handled all in one place */ + multi_posttransfer(data); + multi_done(data, result, TRUE); stream_error = TRUE; break; } break; - case CURLM_STATE_SENDPROTOCONNECT: - result = Curl_protocol_connect(data->easy_conn, &protocol_connect); - if(!protocol_connect) + case MSTATE_PROTOCONNECT: + 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, CURLM_STATE_PROTOCONNECT); + multistate(data, MSTATE_PROTOCONNECTING); + rc = CURLM_CALL_MULTI_PERFORM; + } else if(!result) { /* protocol connect has completed, go WAITDO or DO */ - multistate(data, Curl_pipeline_wanted(multi, CURLPIPE_HTTP1)? - CURLM_STATE_WAITDO:CURLM_STATE_DO); + multistate(data, MSTATE_DO); rc = CURLM_CALL_MULTI_PERFORM; } - else if(result) { + else { /* failure detected */ - Curl_posttransfer(data); - multi_done(&data->easy_conn, result, TRUE); + multi_posttransfer(data); + multi_done(data, result, TRUE); stream_error = TRUE; } break; - case CURLM_STATE_PROTOCONNECT: + case MSTATE_PROTOCONNECTING: /* protocol-specific connect phase */ - result = Curl_protocol_connecting(data->easy_conn, &protocol_connect); - if(!result && protocol_connect) { + result = protocol_connecting(data, &protocol_connected); + if(!result && protocol_connected) { /* after the connect has completed, go WAITDO or DO */ - multistate(data, Curl_pipeline_wanted(multi, CURLPIPE_HTTP1)? - CURLM_STATE_WAITDO:CURLM_STATE_DO); + multistate(data, MSTATE_DO); rc = CURLM_CALL_MULTI_PERFORM; } else if(result) { /* failure detected */ - Curl_posttransfer(data); - multi_done(&data->easy_conn, result, TRUE); + multi_posttransfer(data); + multi_done(data, result, TRUE); stream_error = TRUE; } break; - case CURLM_STATE_WAITDO: - /* Wait for our turn to DO when we're pipelining requests */ - if(Curl_pipeline_checkget_write(data, data->easy_conn)) { - /* Grabbed the channel */ - multistate(data, CURLM_STATE_DO); - rc = CURLM_CALL_MULTI_PERFORM; + 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.primary.remote_ip, + data->info.primary.local_ip, + data->info.primary.remote_port, + data->info.primary.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 - do not do any other + processing */ + result = CURLE_ABORTED_BY_CALLBACK; + multi_posttransfer(data); + multi_done(data, result, FALSE); + stream_error = TRUE; + break; + } } - break; - case CURLM_STATE_DO: - if(data->set.connect_only) { + if(data->set.connect_only == 1) { /* keep connection open for application to use the socket */ - connkeep(data->easy_conn, "CONNECT_ONLY"); - multistate(data, CURLM_STATE_DONE); + 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->easy_conn, &dophase_done); + result = multi_do(data, &dophase_done); - /* When multi_do() returns failure, data->easy_conn might be NULL! */ + /* 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->set.wildcardmatch) { - struct WildcardData *wc = &data->wildcard; + 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->easy_conn, CURLE_OK, FALSE); - multistate(data, CURLM_STATE_DONE); + multi_done(data, CURLE_OK, FALSE); + + /* if there is 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, CURLM_STATE_DOING); - rc = CURLM_OK; + multistate(data, MSTATE_DOING); + rc = CURLM_CALL_MULTI_PERFORM; } /* after DO, go DO_DONE... or DO_MORE */ - else if(data->easy_conn->bits.do_more) { - /* we're supposed to do more, but we need to sit down, relax + else if(data->conn->bits.do_more) { + /* we are supposed to do more, but we need to sit down, relax and wait a little while first */ - multistate(data, CURLM_STATE_DO_MORE); - rc = CURLM_OK; + multistate(data, MSTATE_DOING_MORE); + rc = CURLM_CALL_MULTI_PERFORM; } else { - /* we're done with the DO, now DO_DONE */ - multistate(data, CURLM_STATE_DO_DONE); + /* we are done with the DO, now DID */ + multistate(data, MSTATE_DID); rc = CURLM_CALL_MULTI_PERFORM; } } else if((CURLE_SEND_ERROR == result) && - data->easy_conn->bits.reuse) { + data->conn->bits.reuse) { /* * In this situation, a connection that we were trying to use - * may have unexpectedly died. If possible, send the connection + * 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; + followtype follow = FOLLOW_NONE; CURLcode drc; - bool retry = FALSE; - drc = Curl_retry_request(data->easy_conn, &newurl); + drc = Curl_retry_request(data, &newurl); if(drc) { /* a failure here pretty much implies an out of memory */ result = drc; stream_error = TRUE; } - else - retry = (newurl)?TRUE:FALSE; - Curl_posttransfer(data); - drc = multi_done(&data->easy_conn, result, FALSE); + multi_posttransfer(data); + drc = multi_done(data, result, FALSE); - /* When set to retry the connection, we must to go back to - * the CONNECT state */ - if(retry) { + /* 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, CURLM_STATE_CONNECT); + multistate(data, MSTATE_SETUP); rc = CURLM_CALL_MULTI_PERFORM; result = CURLE_OK; } else { /* Follow failed */ result = drc; - free(newurl); } } else { - /* done didn't return OK or SEND_ERROR */ + /* done did not return OK or SEND_ERROR */ result = drc; - free(newurl); } } else { - /* Have error handler disconnect conn if we can't retry */ + /* Have error handler disconnect conn if we cannot retry */ stream_error = TRUE; - free(newurl); } + free(newurl); } else { /* failure detected */ - Curl_posttransfer(data); - if(data->easy_conn) - multi_done(&data->easy_conn, result, FALSE); + multi_posttransfer(data); + if(data->conn) + multi_done(data, result, FALSE); stream_error = TRUE; } } break; - case CURLM_STATE_DOING: + case MSTATE_DOING: /* we continue DOING until the DO phase is complete */ - result = Curl_protocol_doing(data->easy_conn, - &dophase_done); + 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->easy_conn->bits.do_more? - CURLM_STATE_DO_MORE: - CURLM_STATE_DO_DONE); + 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->easy_conn, result, FALSE); + multi_posttransfer(data); + multi_done(data, result, FALSE); stream_error = TRUE; } break; - case CURLM_STATE_DO_MORE: + case MSTATE_DOING_MORE: /* - * When we are connected, DO MORE and then go DO_DONE + * When we are connected, DOING MORE and then go DID */ - result = multi_do_more(data->easy_conn, &control); + DEBUGASSERT(data->conn); + result = multi_do_more(data, &control); - /* No need to remove this handle from the send pipeline here since that - is done in multi_done() */ if(!result) { if(control) { /* if positive, advance to DO_DONE if negative, go back to DOING */ - multistate(data, control==1? - CURLM_STATE_DO_DONE: - CURLM_STATE_DOING); + multistate(data, control == 1 ? + MSTATE_DID : MSTATE_DOING); rc = CURLM_CALL_MULTI_PERFORM; } - else - /* stay in DO_MORE */ - rc = CURLM_OK; + /* else + stay in DO_MORE */ } else { /* failure detected */ - Curl_posttransfer(data); - multi_done(&data->easy_conn, result, FALSE); + multi_posttransfer(data); + multi_done(data, result, FALSE); stream_error = TRUE; } break; - case CURLM_STATE_DO_DONE: - /* Move ourselves from the send to recv pipeline */ - Curl_move_handle_from_send_to_recv_pipe(data, data->easy_conn); - /* Check if we can move pending requests to send pipe */ - Curl_multi_process_pending_handles(multi); + 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. + /* Only perform the transfer if there is a good socket to work with. Having both BAD is a signal to skip immediately to DONE */ - if((data->easy_conn->sockfd != CURL_SOCKET_BAD) || - (data->easy_conn->writesockfd != CURL_SOCKET_BAD)) - multistate(data, CURLM_STATE_WAITPERFORM); - else - multistate(data, CURLM_STATE_DONE); - rc = CURLM_CALL_MULTI_PERFORM; - break; - - case CURLM_STATE_WAITPERFORM: - /* Wait for our turn to PERFORM */ - if(Curl_pipeline_checkget_read(data, data->easy_conn)) { - /* Grabbed the channel */ - multistate(data, CURLM_STATE_PERFORM); - rc = CURLM_CALL_MULTI_PERFORM; + 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 CURLM_STATE_TOOFAST: /* limit-rate exceeded in either direction */ + case MSTATE_RATELIMITING: /* limit-rate exceeded in either direction */ + DEBUGASSERT(data->conn); /* if both rates are within spec, resume transfer */ - if(Curl_pgrsUpdate(data->easy_conn)) + if(Curl_pgrsUpdate(data)) result = CURLE_ABORTED_BY_CALLBACK; else - result = Curl_speedcheck(data, now); + result = Curl_speedcheck(data, *nowp); - if(!result) { + if(result) { + if(!(data->conn->handler->flags & PROTOPT_DUAL) && + result != CURLE_HTTP2_STREAM) + streamclose(data->conn, "Transfer returned error"); + + multi_posttransfer(data); + multi_done(data, result, TRUE); + } + else { send_timeout_ms = 0; - if(data->set.max_send_speed > 0) - send_timeout_ms = Curl_pgrsLimitWaitTime(data->progress.uploaded, - data->progress.ul_limit_size, - data->set.max_send_speed, - data->progress.ul_limit_start, - now); + if(data->set.max_send_speed) + send_timeout_ms = + Curl_pgrsLimitWaitTime(&data->progress.ul, + data->set.max_send_speed, + *nowp); recv_timeout_ms = 0; - if(data->set.max_recv_speed > 0) - recv_timeout_ms = Curl_pgrsLimitWaitTime(data->progress.downloaded, - data->progress.dl_limit_size, - data->set.max_recv_speed, - data->progress.dl_limit_start, - now); - - if(send_timeout_ms <= 0 && recv_timeout_ms <= 0) - multistate(data, CURLM_STATE_PERFORM); + if(data->set.max_recv_speed) + recv_timeout_ms = + Curl_pgrsLimitWaitTime(&data->progress.dl, + data->set.max_recv_speed, + *nowp); + + if(!send_timeout_ms && !recv_timeout_ms) { + multistate(data, MSTATE_PERFORMING); + Curl_ratelimit(data, *nowp); + /* start performing again right away */ + rc = CURLM_CALL_MULTI_PERFORM; + } else if(send_timeout_ms >= recv_timeout_ms) - Curl_expire_latest(data, send_timeout_ms); + Curl_expire(data, send_timeout_ms, EXPIRE_TOOFAST); else - Curl_expire_latest(data, recv_timeout_ms); + Curl_expire(data, recv_timeout_ms, EXPIRE_TOOFAST); } break; - case CURLM_STATE_PERFORM: + case MSTATE_PERFORMING: { char *newurl = NULL; bool retry = FALSE; - bool comeback = FALSE; - /* check if over send speed */ send_timeout_ms = 0; - if(data->set.max_send_speed > 0) - send_timeout_ms = Curl_pgrsLimitWaitTime(data->progress.uploaded, - data->progress.ul_limit_size, + if(data->set.max_send_speed) + send_timeout_ms = Curl_pgrsLimitWaitTime(&data->progress.ul, data->set.max_send_speed, - data->progress.ul_limit_start, - now); + *nowp); /* check if over recv speed */ recv_timeout_ms = 0; - if(data->set.max_recv_speed > 0) - recv_timeout_ms = Curl_pgrsLimitWaitTime(data->progress.downloaded, - data->progress.dl_limit_size, + if(data->set.max_recv_speed) + recv_timeout_ms = Curl_pgrsLimitWaitTime(&data->progress.dl, data->set.max_recv_speed, - data->progress.dl_limit_start, - now); + *nowp); - if(send_timeout_ms > 0 || recv_timeout_ms > 0) { - multistate(data, CURLM_STATE_TOOFAST); + if(send_timeout_ms || recv_timeout_ms) { + Curl_ratelimit(data, *nowp); + multistate(data, MSTATE_RATELIMITING); if(send_timeout_ms >= recv_timeout_ms) - Curl_expire_latest(data, send_timeout_ms); + Curl_expire(data, send_timeout_ms, EXPIRE_TOOFAST); else - Curl_expire_latest(data, recv_timeout_ms); + Curl_expire(data, recv_timeout_ms, EXPIRE_TOOFAST); break; } /* read/write data if it is ready to do so */ - result = Curl_readwrite(data->easy_conn, data, &done, &comeback); + result = Curl_sendrecv(data, nowp); - k = &data->req; - - if(!(k->keepon & KEEP_RECV)) - /* We're done receiving */ - Curl_pipeline_leave_read(data->easy_conn); - - if(!(k->keepon & KEEP_SEND)) - /* We're done sending */ - Curl_pipeline_leave_write(data->easy_conn); - - if(done || (result == CURLE_RECV_ERROR)) { + if(data->req.done || (result == CURLE_RECV_ERROR)) { /* If CURLE_RECV_ERROR happens early enough, we assume it was a race - * condition and the server closed the re-used connection exactly when + * 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->easy_conn, &newurl); + CURLcode ret = Curl_retry_request(data, &newurl); if(!ret) - retry = (newurl)?TRUE:FALSE; + 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; + data->req.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; + data->req.done = TRUE; } + else + result = ret; } if(result) { /* * The transfer phase returned error, we mark the connection to get - * closed to prevent being re-used. This is because we can't possibly - * know if the connection is in a good shape or not now. Unless it is + * closed to prevent being reused. This is because we cannot 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->easy_conn->handler->flags & PROTOPT_DUAL) && + if(!(data->conn->handler->flags & PROTOPT_DUAL) && result != CURLE_HTTP2_STREAM) - streamclose(data->easy_conn, "Transfer returned error"); + streamclose(data->conn, "Transfer returned error"); - Curl_posttransfer(data); - multi_done(&data->easy_conn, result, TRUE); + multi_posttransfer(data); + multi_done(data, result, TRUE); } - else if(done) { - followtype follow=FOLLOW_NONE; + else if(data->req.done && !Curl_cwriter_is_paused(data)) { /* call this even if the readwrite function returned error */ - Curl_posttransfer(data); - - /* we're no longer receiving */ - Curl_removeHandleFromPipeline(data, data->easy_conn->recv_pipe); - - /* expire the new receiving pipeline head */ - if(data->easy_conn->recv_pipe->head) - Curl_expire_latest(data->easy_conn->recv_pipe->head->ptr, 0); - - /* Check if we can move pending requests to send pipe */ - Curl_multi_process_pending_handles(multi); + multi_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 */ @@ -1965,210 +2449,223 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, } else follow = FOLLOW_RETRY; - result = multi_done(&data->easy_conn, CURLE_OK, FALSE); + (void)multi_done(data, CURLE_OK, FALSE); + /* multi_done() might return CURLE_GOT_NOTHING */ + result = Curl_follow(data, newurl, follow); if(!result) { - result = Curl_follow(data, newurl, follow); - if(!result) { - multistate(data, CURLM_STATE_CONNECT); - rc = CURLM_CALL_MULTI_PERFORM; - newurl = NULL; /* handed over the memory ownership to - Curl_follow(), make sure we don't free() it - here */ - } + multistate(data, MSTATE_SETUP); + rc = CURLM_CALL_MULTI_PERFORM; } } 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 */ + /* but first check to see if we got a location info even though we + are not following redirects */ if(data->req.location) { free(newurl); newurl = data->req.location; data->req.location = NULL; result = Curl_follow(data, newurl, FOLLOW_FAKE); - if(!result) - newurl = NULL; /* allocation was handed over Curl_follow() */ - else + if(result) { stream_error = TRUE; + result = multi_done(data, result, TRUE); + } } - multistate(data, CURLM_STATE_DONE); - rc = CURLM_CALL_MULTI_PERFORM; + if(!result) { + multistate(data, MSTATE_DONE); + rc = CURLM_CALL_MULTI_PERFORM; + } } } - else if(comeback) - rc = CURLM_CALL_MULTI_PERFORM; - + else if(data->state.select_bits && !Curl_xfer_is_blocked(data)) { + /* This avoids CURLM_CALL_MULTI_PERFORM so that a very fast transfer + will not get stuck on this transfer at the expense of other + concurrent transfers */ + Curl_expire(data, 0, EXPIRE_RUN_NOW); + } free(newurl); break; } - case CURLM_STATE_DONE: + case MSTATE_DONE: /* this state is highly transient, so run another loop after this */ rc = CURLM_CALL_MULTI_PERFORM; - if(data->easy_conn) { + if(data->conn) { CURLcode res; - /* Remove ourselves from the receive pipeline, if we are there. */ - Curl_removeHandleFromPipeline(data, data->easy_conn->recv_pipe); - /* Check if we can move pending requests to send pipe */ - Curl_multi_process_pending_handles(multi); - /* post-transfer command */ - res = multi_done(&data->easy_conn, result, FALSE); + res = multi_done(data, result, FALSE); /* allow a previously set error code take precedence */ if(!result) result = res; - - /* - * If there are other handles on the pipeline, multi_done won't set - * easy_conn to NULL. In such a case, curl_multi_remove_handle() can - * access free'd data, if the connection is free'd and the handle - * removed before we perform the processing in CURLM_STATE_COMPLETED - */ - if(data->easy_conn) - data->easy_conn = NULL; } - if(data->set.wildcardmatch) { - if(data->wildcard.state != CURLWC_DONE) { +#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 CURLM_STATE_INIT */ - multistate(data, CURLM_STATE_INIT); + with MSTATE_INIT */ + multistate(data, MSTATE_INIT); break; } } - - /* after we have DONE what we're supposed to do, go COMPLETED, and - it doesn't matter what the multi_done() returned! */ - multistate(data, CURLM_STATE_COMPLETED); +#endif + /* after we have DONE what we are supposed to do, go COMPLETED, and + it does not matter what the multi_done() returned! */ + multistate(data, MSTATE_COMPLETED); break; - case CURLM_STATE_COMPLETED: - /* this is a completed transfer, it is likely to still be connected */ - - /* This node should be delinked from the list now and we should post - an information message that we are complete. */ - - /* Important: reset the conn pointer so that we don't point to memory - that could be freed anytime */ - data->easy_conn = NULL; - - Curl_expire_clear(data); /* stop all timers */ + case MSTATE_COMPLETED: break; - case CURLM_STATE_MSGSENT: - data->result = result; - return CURLM_OK; /* do nothing */ + case MSTATE_PENDING: + case MSTATE_MSGSENT: + /* handles in these states should NOT be in this list */ + DEBUGASSERT(0); + break; default: return CURLM_INTERNAL_ERROR; } - statemachine_end: - if(data->mstate < CURLM_STATE_COMPLETED) { + if(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); + } + +statemachine_end: + + if(data->mstate < MSTATE_COMPLETED) { if(result) { /* - * If an error was returned, and we aren't in completed state now, + * If an error was returned, and we are not 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 */ - data->state.pipe_broke = FALSE; - /* Check if we can move pending requests to send pipe */ - Curl_multi_process_pending_handles(multi); - - if(data->easy_conn) { - /* if this has a connection, unsubscribe from the pipelines */ - Curl_pipeline_leave_write(data->easy_conn); - Curl_pipeline_leave_read(data->easy_conn); - Curl_removeHandleFromPipeline(data, data->easy_conn->send_pipe); - Curl_removeHandleFromPipeline(data, data->easy_conn->recv_pipe); + process_pending_handles(multi); /* connection */ + if(data->conn) { if(stream_error) { - /* Don't attempt to send data over a connection that timed out */ + /* Do not attempt to send data over a connection that timed out */ bool dead_connection = result == CURLE_OPERATION_TIMEDOUT; - /* disconnect properly */ - Curl_disconnect(data->easy_conn, dead_connection); + struct connectdata *conn = data->conn; - /* This is where we make sure that the easy_conn pointer is reset. - We don't have to do this in every case block above where a + /* This is where we make sure that the conn pointer is reset. + We do not have to do this in every case block above where a failure is detected */ - data->easy_conn = NULL; + Curl_detach_connection(data); + Curl_cpool_disconnect(data, conn, dead_connection); } } - else if(data->mstate == CURLM_STATE_CONNECT) { + else if(data->mstate == MSTATE_CONNECT) { /* Curl_connect() failed */ - (void)Curl_posttransfer(data); + multi_posttransfer(data); + Curl_pgrsUpdate_nometer(data); } - multistate(data, CURLM_STATE_COMPLETED); + multistate(data, MSTATE_COMPLETED); + rc = CURLM_CALL_MULTI_PERFORM; } - /* if there's still a connection to use, call the progress function */ - else if(data->easy_conn && Curl_pgrsUpdate(data->easy_conn)) { + /* if there is 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->easy_conn, "Aborted by callback"); + streamclose(data->conn, "Aborted by callback"); /* if not yet in DONE state, go there, otherwise COMPLETED */ - multistate(data, (data->mstate < CURLM_STATE_DONE)? - CURLM_STATE_DONE: CURLM_STATE_COMPLETED); + multistate(data, (data->mstate < MSTATE_DONE) ? + MSTATE_DONE : MSTATE_COMPLETED); rc = CURLM_CALL_MULTI_PERFORM; } } - if(CURLM_STATE_COMPLETED == data->mstate) { - /* now fill in the Curl_message with this info */ - msg = &data->msg; + 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; + msg->extmsg.msg = CURLMSG_DONE; + msg->extmsg.easy_handle = data; + msg->extmsg.data.result = result; - rc = multi_addmsg(multi, msg); + multi_addmsg(multi, msg); + DEBUGASSERT(!data->conn); + } + multistate(data, MSTATE_MSGSENT); - multistate(data, CURLM_STATE_MSGSENT); + /* unlink from the process list */ + Curl_node_remove(&data->multi_queue); + /* add this handle msgsent list */ + Curl_llist_append(&multi->msgsent, data, &data->multi_queue); + 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 timeval now = Curl_tvnow(); + CURLMcode returncode = CURLM_OK; + struct Curl_tree *t = NULL; + struct curltime now = Curl_now(); + struct Curl_llist_node *e; + struct Curl_llist_node *n = NULL; + SIGPIPE_VARIABLE(pipe_st); if(!GOOD_MULTI_HANDLE(multi)) return CURLM_BAD_HANDLE; - data=multi->easyp; - while(data) { - CURLMcode result; - SIGPIPE_VARIABLE(pipe_st); + if(multi->in_callback) + return CURLM_RECURSIVE_API_CALL; - sigpipe_ignore(data, &pipe_st); - result = multi_runsingle(multi, now, data); - sigpipe_restore(&pipe_st); + sigpipe_init(&pipe_st); + for(e = Curl_llist_head(&multi->process); e; e = n) { + struct Curl_easy *data = Curl_node_elem(e); + CURLMcode result; + /* Do the loop and only alter the signal ignore state if the next handle + has a different NO_SIGNAL state than the previous */ + + /* the current node might be unlinked in multi_runsingle(), get the next + pointer now */ + n = Curl_node_next(e); + + if(data != multi->cpool.idata) { + /* connection pool handle is processed below */ + sigpipe_apply(data, &pipe_st); + result = multi_runsingle(multi, &now, data); + if(result) + returncode = result; + } + } - if(result) - returncode = result; + sigpipe_apply(multi->cpool.idata, &pipe_st); + Curl_cpool_multi_perform(multi); - data = data->next; /* operate on next handle */ - } + sigpipe_restore(&pipe_st); /* * Simply remove all expired timers from the splay since handles are dealt @@ -2182,75 +2679,72 @@ CURLMcode curl_multi_perform(struct Curl_multi *multi, int *running_handles) */ do { multi->timetree = Curl_splaygetbest(now, multi->timetree, &t); - if(t) + if(t) { /* the removed may have another timeout in queue */ - (void)add_next_timeout(now, multi, t->payload); - + struct Curl_easy *data = Curl_splayget(t); + if(data->mstate == MSTATE_PENDING) { + bool stream_unused; + CURLcode result_unused; + if(multi_handle_timeout(data, &now, &stream_unused, &result_unused)) { + infof(data, "PENDING handle timeout"); + move_pending_to_connect(multi, data); + } + } + (void)add_next_timeout(now, multi, Curl_splayget(t)); + } } while(t); - *running_handles = multi->num_alive; + if(running_handles) + *running_handles = (int)multi->num_alive; if(CURLM_OK >= returncode) - update_timer(multi); + returncode = Curl_update_timer(multi); return returncode; } -static void close_all_connections(struct Curl_multi *multi) +/* unlink_all_msgsent_handles() moves all nodes back from the msgsent list to + the process list */ +static void unlink_all_msgsent_handles(struct Curl_multi *multi) { - struct connectdata *conn; - - conn = Curl_conncache_find_first_connection(&multi->conn_cache); - while(conn) { - SIGPIPE_VARIABLE(pipe_st); - conn->data = multi->closure_handle; - - sigpipe_ignore(conn->data, &pipe_st); - conn->data->easy_conn = NULL; /* clear the easy handle's connection - pointer */ - /* This will remove the connection from the cache */ - connclose(conn, "kill all"); - (void)Curl_disconnect(conn, FALSE); - sigpipe_restore(&pipe_st); - - conn = Curl_conncache_find_first_connection(&multi->conn_cache); + struct Curl_llist_node *e; + for(e = Curl_llist_head(&multi->msgsent); e; e = Curl_node_next(e)) { + struct Curl_easy *data = Curl_node_elem(e); + if(data) { + DEBUGASSERT(data->mstate == MSTATE_MSGSENT); + Curl_node_remove(&data->multi_queue); + /* put it into the process list */ + Curl_llist_append(&multi->process, data, &data->multi_queue); + } } } CURLMcode curl_multi_cleanup(struct Curl_multi *multi) { - struct Curl_easy *data; - struct Curl_easy *nextdata; - if(GOOD_MULTI_HANDLE(multi)) { - bool restore_pipe = FALSE; - SIGPIPE_VARIABLE(pipe_st); - - multi->type = 0; /* not good anymore */ + struct Curl_llist_node *e; + struct Curl_llist_node *n; + if(multi->in_callback) + return CURLM_RECURSIVE_API_CALL; - /* Close all the connections in the connection cache */ - close_all_connections(multi); + multi->magic = 0; /* not good anymore */ - if(multi->closure_handle) { - sigpipe_ignore(multi->closure_handle, &pipe_st); - restore_pipe = TRUE; + /* move the pending and msgsent entries back to process + so that there is just one list to iterate over */ + unlink_all_msgsent_handles(multi); + process_pending_handles(multi); - multi->closure_handle->dns.hostcache = &multi->hostcache; - Curl_hostcache_clean(multi->closure_handle, - multi->closure_handle->dns.hostcache); + /* First remove all remaining easy handles */ + for(e = Curl_llist_head(&multi->process); e; e = n) { + struct Curl_easy *data = Curl_node_elem(e); - Curl_close(multi->closure_handle); - } - - Curl_hash_destroy(&multi->sockhash); - Curl_conncache_destroy(&multi->conn_cache); - Curl_llist_destroy(multi->msglist, NULL); - Curl_llist_destroy(multi->pending, NULL); + if(!GOOD_EASY_HANDLE(data)) + return CURLM_BAD_HANDLE; - /* remove all easy handles */ - data = multi->easyp; - while(data) { - nextdata=data->next; + n = Curl_node_next(e); + 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); @@ -2258,27 +2752,38 @@ CURLMcode curl_multi_cleanup(struct Curl_multi *multi) data->dns.hostcachetype = HCACHE_NONE; } - /* Clear the pointer to the connection cache */ - data->state.conn_cache = NULL; data->multi = NULL; /* clear the association */ - data = nextdata; +#ifdef USE_LIBPSL + if(data->psl == &multi->psl) + data->psl = NULL; +#endif } + Curl_cpool_destroy(&multi->cpool); + + sockhash_destroy(&multi->sockhash); + Curl_hash_destroy(&multi->proto_hash); Curl_hash_destroy(&multi->hostcache); + Curl_psl_destroy(&multi->psl); - /* Free the blacklists by setting them to NULL */ - Curl_pipeline_set_site_blacklist(NULL, &multi->pipelining_site_bl); - Curl_pipeline_set_server_blacklist(NULL, &multi->pipelining_server_bl); +#ifdef USE_WINSOCK + WSACloseEvent(multi->wsa_event); +#else +#ifdef ENABLE_WAKEUP + wakeup_close(multi->wakeup_pair[0]); +#ifndef USE_EVENTFD + wakeup_close(multi->wakeup_pair[1]); +#endif +#endif +#endif + multi_xfer_bufs_free(multi); free(multi); - if(restore_pipe) - sigpipe_restore(&pipe_st); return CURLM_OK; } - else - return CURLM_BAD_HANDLE; + return CURLM_BAD_HANDLE; } /* @@ -2297,24 +2802,25 @@ CURLMsg *curl_multi_info_read(struct Curl_multi *multi, int *msgs_in_queue) *msgs_in_queue = 0; /* default to none */ - if(GOOD_MULTI_HANDLE(multi) && Curl_llist_count(multi->msglist)) { + 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; + struct Curl_llist_node *e; /* extract the head of the list to return */ - e = multi->msglist->head; + e = Curl_llist_head(&multi->msglist); - msg = e->ptr; + msg = Curl_node_elem(e); /* remove the extracted entry */ - Curl_llist_remove(multi->msglist, e, NULL); + Curl_node_remove(e); - *msgs_in_queue = curlx_uztosi(Curl_llist_count(multi->msglist)); + *msgs_in_queue = curlx_uztosi(Curl_llist_count(&multi->msglist)); return &msg->extmsg; } - else - return NULL; + return NULL; } /* @@ -2322,176 +2828,232 @@ CURLMsg *curl_multi_info_read(struct Curl_multi *multi, int *msgs_in_queue) * and if we have a different state in any of those sockets from last time we * call the callback accordingly. */ -static void singlesocket(struct Curl_multi *multi, - struct Curl_easy *data) +static CURLMcode singlesocket(struct Curl_multi *multi, + struct Curl_easy *data) { - curl_socket_t socks[MAX_SOCKSPEREASYHANDLE]; - int i; - struct Curl_sh_entry *entry; - curl_socket_t s; - int num; - unsigned int curraction; - - for(i=0; i< MAX_SOCKSPEREASYHANDLE; i++) - socks[i] = CURL_SOCKET_BAD; + struct easy_pollset cur_poll; + CURLMcode mresult; /* Fill in the 'current' struct with the state as it is now: what sockets to supervise and for what actions */ - curraction = multi_getsock(data, socks, MAX_SOCKSPEREASYHANDLE); + multi_getsock(data, &cur_poll); + mresult = Curl_multi_pollset_ev(multi, data, &cur_poll, &data->last_poll); + + if(!mresult) /* Remember for next time */ + memcpy(&data->last_poll, &cur_poll, sizeof(cur_poll)); + return mresult; +} + +CURLMcode Curl_multi_pollset_ev(struct Curl_multi *multi, + struct Curl_easy *data, + struct easy_pollset *ps, + struct easy_pollset *last_ps) +{ + unsigned int i; + struct Curl_sh_entry *entry; + curl_socket_t s; + int rc; /* 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< MAX_SOCKSPEREASYHANDLE) && - (curraction & (GETSOCK_READSOCK(i) | GETSOCK_WRITESOCK(i))); - i++) { - int action = CURL_POLL_NONE; + for(i = 0; i < ps->num; i++) { + unsigned char cur_action = ps->actions[i]; + unsigned char last_action = 0; + int comboaction; - s = socks[i]; + s = ps->sockets[i]; /* get it from the hash */ entry = sh_getentry(&multi->sockhash, s); - - if(curraction & GETSOCK_READSOCK(i)) - action |= CURL_POLL_IN; - if(curraction & GETSOCK_WRITESOCK(i)) - action |= CURL_POLL_OUT; - if(entry) { - /* yeps, already present so check if it has the same action set */ - if(entry->action == action) - /* same, continue */ - continue; + /* check if new for this transfer */ + unsigned int j; + for(j = 0; j < last_ps->num; j++) { + if(s == last_ps->sockets[j]) { + last_action = last_ps->actions[j]; + break; + } + } } else { - /* this is a socket we didn't have before, add it! */ - entry = sh_addentry(&multi->sockhash, s, data); + /* this is a socket we did not have before, add it to the hash! */ + entry = sh_addentry(&multi->sockhash, s); if(!entry) /* fatal */ - return; + 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) { + DEBUGASSERT(entry->readers); + entry->readers--; + } + if(last_action & CURL_POLL_OUT) { + DEBUGASSERT(entry->writers); + entry->writers--; + } + if(cur_action & CURL_POLL_IN) { + entry->readers++; + } + if(cur_action & CURL_POLL_OUT) + entry->writers++; + } + else if(!last_action && + !Curl_hash_pick(&entry->transfers, (char *)&data, /* hash key */ + sizeof(struct Curl_easy *))) { + DEBUGASSERT(entry->users < 100000); /* detect weird values */ + /* 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; + } } - /* we know (entry != NULL) at this point, see the logic above */ - if(multi->socket_cb) - multi->socket_cb(data, - s, - action, - multi->socket_userp, - entry->socketp); + comboaction = (entry->writers ? CURL_POLL_OUT : 0) | + (entry->readers ? CURL_POLL_IN : 0); - entry->action = action; /* store the current action state */ - } + /* 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; + } + } - num = i; /* number of sockets */ + /* store the current action state */ + entry->action = (unsigned int)comboaction; + } - /* when we've walked over all the sockets we should have right now, we must - make sure to detect sockets that are removed */ - for(i=0; i< data->numsocks; i++) { - int j; - s = data->sockets[i]; - for(j=0; jsockets. + * 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 < last_ps->num; i++) { + unsigned int j; + bool stillused = FALSE; + s = last_ps->sockets[i]; + for(j = 0; j < ps->num; j++) { + if(s == ps->sockets[j]) { /* this is still supervised */ - s = CURL_SOCKET_BAD; + 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) { - /* this socket has been removed. Tell the app to remove it */ - bool remove_sock_from_hash = TRUE; - - /* check if the socket to be removed serves a connection which has - other easy-s in a pipeline. In this case the socket should not be - removed. */ - struct connectdata *easy_conn = data->easy_conn; - if(easy_conn) { - if(easy_conn->recv_pipe && easy_conn->recv_pipe->size > 1) { - /* the handle should not be removed from the pipe yet */ - remove_sock_from_hash = FALSE; - - /* Update the sockhash entry to instead point to the next in line - for the recv_pipe, or the first (in case this particular easy - isn't already) */ - if(entry->easy == data) { - if(Curl_recvpipe_head(data, easy_conn)) - entry->easy = easy_conn->recv_pipe->head->next->ptr; - else - entry->easy = easy_conn->recv_pipe->head->ptr; - } + unsigned char oldactions = last_ps->actions[i]; + /* this socket has been removed. Decrease user count */ + DEBUGASSERT(entry->users); + entry->users--; + if(oldactions & CURL_POLL_OUT) + entry->writers--; + if(oldactions & CURL_POLL_IN) + entry->readers--; + if(!entry->users) { + bool dead = FALSE; + 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) + dead = TRUE; } - if(easy_conn->send_pipe && easy_conn->send_pipe->size > 1) { - /* the handle should not be removed from the pipe yet */ - remove_sock_from_hash = FALSE; - - /* Update the sockhash entry to instead point to the next in line - for the send_pipe, or the first (in case this particular easy - isn't already) */ - if(entry->easy == data) { - if(Curl_sendpipe_head(data, easy_conn)) - entry->easy = easy_conn->send_pipe->head->next->ptr; - else - entry->easy = easy_conn->send_pipe->head->ptr; - } + sh_delentry(entry, &multi->sockhash, s); + if(dead) { + multi->dead = TRUE; + return CURLM_ABORTED_BY_CALLBACK; } - /* Don't worry about overwriting recv_pipe head with send_pipe_head, - when action will be asked on the socket (see multi_socket()), the - head of the correct pipe will be taken according to the - action. */ } - - if(remove_sock_from_hash) { - /* in this case 'entry' is always non-NULL */ - if(multi->socket_cb) - multi->socket_cb(data, - s, - CURL_POLL_REMOVE, - multi->socket_userp, - entry->socketp); - sh_delentry(&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); + } } - } /* if sockhash entry existed */ - } /* for loop over numsocks */ + } + } /* for loop over num */ + + return CURLM_OK; +} - memcpy(data->sockets, socks, num*sizeof(curl_socket_t)); - data->numsocks = num; +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 + * 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 connectdata *conn, curl_socket_t s) +void Curl_multi_closed(struct Curl_easy *data, curl_socket_t s) { - struct Curl_multi *multi = conn->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) { - if(multi->socket_cb) - multi->socket_cb(conn->data, s, CURL_POLL_REMOVE, - multi->socket_userp, - entry->socketp); + if(data) { + /* if there is still an easy handle associated with this connection */ + struct Curl_multi *multi = data->multi; + DEBUGF(infof(data, "Curl_multi_closed, fd=%" FMT_SOCKET_T + " multi is %p", s, (void *)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); + + DEBUGF(infof(data, "Curl_multi_closed, fd=%" FMT_SOCKET_T + " entry is %p", s, (void *)entry)); + 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(&multi->sockhash, s); + /* 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() * @@ -2504,29 +3066,30 @@ void Curl_multi_closed(struct connectdata *conn, curl_socket_t s) * 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 timeval now, +static CURLMcode add_next_timeout(struct curltime now, struct Curl_multi *multi, struct Curl_easy *d) { - struct timeval *tv = &d->state.expiretime; - struct curl_llist *list = d->state.timeoutlist; - struct curl_llist_element *e; + struct curltime *tv = &d->state.expiretime; + struct Curl_llist *list = &d->state.timeoutlist; + struct Curl_llist_node *e; /* 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; - time_t diff = curlx_tvdiff(*(struct timeval *)e->ptr, now); + for(e = Curl_llist_head(list); e;) { + struct Curl_llist_node *n = Curl_node_next(e); + struct time_node *node = Curl_node_elem(e); + timediff_t diff = Curl_timediff_us(node->time, now); if(diff <= 0) /* remove outdated entry */ - Curl_llist_remove(list, e, NULL); + Curl_node_remove(e); else /* the list is sorted so get out on the first mismatch */ break; e = n; } - e = list->head; + e = Curl_llist_head(list); if(!e) { /* clear the expire times within the handles that we remove from the splay tree */ @@ -2534,19 +3097,71 @@ static CURLMcode add_next_timeout(struct timeval now, tv->tv_usec = 0; } else { + struct time_node *node = Curl_node_elem(e); /* copy the first entry to 'tv' */ - memcpy(tv, e->ptr, sizeof(*tv)); + memcpy(tv, &node->time, sizeof(*tv)); - /* remove first entry from list */ - Curl_llist_remove(list, e, NULL); - - /* insert this node again into the splay */ + /* 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; } +struct multi_run_ctx { + struct Curl_multi *multi; + struct curltime now; + size_t run_xfers; + SIGPIPE_MEMBER(pipe_st); + bool run_cpool; +}; + +static CURLMcode multi_run_expired(struct multi_run_ctx *mrc) +{ + struct Curl_multi *multi = mrc->multi; + struct Curl_easy *data = NULL; + struct Curl_tree *t = NULL; + CURLMcode result = CURLM_OK; + + /* + * The loop following here will go on as long as there are expire-times left + * to process (compared to mrc->now) in the splay and 'data' will be + * re-assigned for every expired handle we deal with. + */ + while(1) { + /* Check if there is one (more) expired timer to deal with! This function + extracts a matching node if there is one */ + multi->timetree = Curl_splaygetbest(mrc->now, multi->timetree, &t); + if(!t) + goto out; + + data = Curl_splayget(t); /* assign this for next loop */ + if(!data) + continue; + + (void)add_next_timeout(mrc->now, multi, data); + if(data == multi->cpool.idata) { + mrc->run_cpool = TRUE; + continue; + } + + mrc->run_xfers++; + sigpipe_apply(data, &mrc->pipe_st); + result = multi_runsingle(multi, &mrc->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) + goto out; + } + } + +out: + return result; +} static CURLMcode multi_socket(struct Curl_multi *multi, bool checkall, curl_socket_t s, @@ -2555,129 +3170,93 @@ static CURLMcode multi_socket(struct Curl_multi *multi, { CURLMcode result = CURLM_OK; struct Curl_easy *data = NULL; - struct Curl_tree *t; - struct timeval now = Curl_tvnow(); + struct multi_run_ctx mrc; + + (void)ev_bitmask; + memset(&mrc, 0, sizeof(mrc)); + mrc.multi = multi; + mrc.now = Curl_now(); + sigpipe_init(&mrc.pipe_st); if(checkall) { + struct Curl_llist_node *e; /* *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) { - singlesocket(multi, data); - data = data->next; + for(e = Curl_llist_head(&multi->process); e && !result; + e = Curl_node_next(e)) { + result = singlesocket(multi, Curl_node_elem(e)); } } - - /* or should we fall-through and do the timer-based stuff? */ - return result; + mrc.run_cpool = TRUE; + goto out; } - else if(s != CURL_SOCKET_TIMEOUT) { + 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 + if(!entry) { + /* Unmatched socket, we cannot 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. */ - ; + /* The socket might come from a connection that is being shut down + * by the multi's connection pool. */ + Curl_cpool_multi_socket(multi, s, ev_bitmask); + } else { - SIGPIPE_VARIABLE(pipe_st); - - data = entry->easy; - - if(data->magic != CURLEASY_MAGIC_NUMBER) - /* bad bad bad bad bad bad bad */ - return CURLM_INTERNAL_ERROR; - - /* If the pipeline is enabled, take the handle which is in the head of - the pipeline. If we should write into the socket, take the send_pipe - head. If we should read from the socket, take the recv_pipe head. */ - if(data->easy_conn) { - if((ev_bitmask & CURL_POLL_OUT) && - data->easy_conn->send_pipe && - data->easy_conn->send_pipe->head) - data = data->easy_conn->send_pipe->head->ptr; - else if((ev_bitmask & CURL_POLL_IN) && - data->easy_conn->recv_pipe && - data->easy_conn->recv_pipe->head) - data = data->easy_conn->recv_pipe->head->ptr; + 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 == multi->cpool.idata) + mrc.run_cpool = TRUE; + else { + /* Expire with out current now, so we will get it below when + * asking the splaytree for expired transfers. */ + Curl_expire_ex(data, &mrc.now, 0, EXPIRE_RUN_NOW); + } } - - if(data->easy_conn && - !(data->easy_conn->handler->flags & PROTOPT_DIRLOCK)) - /* set socket event bitmask if they're not locked */ - data->easy_conn->cselect_bits = ev_bitmask; - - sigpipe_ignore(data, &pipe_st); - result = multi_runsingle(multi, now, data); - sigpipe_restore(&pipe_st); - - if(data->easy_conn && - !(data->easy_conn->handler->flags & PROTOPT_DIRLOCK)) - /* clear the bitmask only if not locked */ - data->easy_conn->cselect_bits = 0; - - if(CURLM_OK >= result) - /* get the socket(s) and check if the state has been changed since - last */ - singlesocket(multi, data); - - /* 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_tvnow(); /* 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 - 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) { - SIGPIPE_VARIABLE(pipe_st); - - sigpipe_ignore(data, &pipe_st); - result = multi_runsingle(multi, now, data); - sigpipe_restore(&pipe_st); - if(CURLM_OK >= result) - /* get the socket(s) and check if the state has been changed since - last */ - singlesocket(multi, data); - } - - /* Check if there's one (more) expired timer to deal with! This function - extracts a matching node if there is one */ + result = multi_run_expired(&mrc); + if(result) + goto out; + + if(mrc.run_xfers) { + /* Running transfers takes time. With a new timestamp, we might catch + * other expires which are due now. Instead of telling the application + * to set a 0 timeout and call us again, we run them here. + * Do that only once or it might be unfair to transfers on other + * sockets. */ + mrc.now = Curl_now(); + result = multi_run_expired(&mrc); + } - 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); - } +out: + if(mrc.run_cpool) { + sigpipe_apply(multi->cpool.idata, &mrc.pipe_st); + Curl_cpool_multi_perform(multi); + } + sigpipe_restore(&mrc.pipe_st); - } while(t); + if(running_handles) + *running_handles = (int)multi->num_alive; - *running_handles = multi->num_alive; + if(CURLM_OK >= result) + result = Curl_update_timer(multi); return result; } @@ -2687,10 +3266,14 @@ CURLMcode curl_multi_setopt(struct Curl_multi *multi, { 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) { @@ -2707,7 +3290,7 @@ CURLMcode curl_multi_setopt(struct Curl_multi *multi, multi->push_userp = va_arg(param, void *); break; case CURLMOPT_PIPELINING: - multi->pipelining = va_arg(param, long); + multi->multiplexing = va_arg(param, long) & CURLPIPE_MULTIPLEX ? 1 : 0; break; case CURLMOPT_TIMERFUNCTION: multi->timer_cb = va_arg(param, curl_multi_timer_callback); @@ -2716,30 +3299,37 @@ CURLMcode curl_multi_setopt(struct Curl_multi *multi, multi->timer_userp = va_arg(param, void *); break; case CURLMOPT_MAXCONNECTS: - multi->maxconnects = va_arg(param, long); + 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); + /* for now, let this also decide the max number of connections + * in shutdown handling */ + multi->max_shutdown_connections = va_arg(param, long); + break; + /* options formerly used for pipelining */ case CURLMOPT_MAX_PIPELINE_LENGTH: - multi->max_pipeline_length = va_arg(param, long); break; case CURLMOPT_CONTENT_LENGTH_PENALTY_SIZE: - multi->content_length_penalty_size = va_arg(param, long); break; case CURLMOPT_CHUNK_LENGTH_PENALTY_SIZE: - multi->chunk_length_penalty_size = va_arg(param, long); break; case CURLMOPT_PIPELINING_SITE_BL: - res = Curl_pipeline_set_site_blacklist(va_arg(param, char **), - &multi->pipelining_site_bl); break; case CURLMOPT_PIPELINING_SERVER_BL: - res = Curl_pipeline_set_server_blacklist(va_arg(param, char **), - &multi->pipelining_server_bl); break; - case CURLMOPT_MAX_TOTAL_CONNECTIONS: - multi->max_total_connections = va_arg(param, long); + 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; @@ -2755,63 +3345,66 @@ CURLMcode curl_multi_setopt(struct Curl_multi *multi, CURLMcode curl_multi_socket(struct Curl_multi *multi, curl_socket_t s, int *running_handles) { - CURLMcode result = multi_socket(multi, FALSE, s, 0, running_handles); - if(CURLM_OK >= result) - update_timer(multi); - return result; + if(multi->in_callback) + return CURLM_RECURSIVE_API_CALL; + return multi_socket(multi, FALSE, s, 0, running_handles); } CURLMcode curl_multi_socket_action(struct Curl_multi *multi, curl_socket_t s, int ev_bitmask, int *running_handles) { - CURLMcode result = multi_socket(multi, FALSE, s, - ev_bitmask, running_handles); - if(CURLM_OK >= result) - update_timer(multi); - return result; + if(multi->in_callback) + return CURLM_RECURSIVE_API_CALL; + return multi_socket(multi, FALSE, s, ev_bitmask, running_handles); } CURLMcode curl_multi_socket_all(struct Curl_multi *multi, int *running_handles) - { - CURLMcode result = multi_socket(multi, TRUE, CURL_SOCKET_BAD, 0, - running_handles); - if(CURLM_OK >= result) - update_timer(multi); - return result; + if(multi->in_callback) + return CURLM_RECURSIVE_API_CALL; + return multi_socket(multi, TRUE, CURL_SOCKET_BAD, 0, running_handles); } static CURLMcode multi_timeout(struct Curl_multi *multi, + struct curltime *expire_time, long *timeout_ms) { - static struct timeval tv_zero = {0, 0}; + 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 timeval now = Curl_tvnow(); + 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) { + /* this will not return NULL from a non-emtpy tree, but some compilers + * are not convinced of that. Analyzers are hard. */ + *expire_time = multi->timetree ? multi->timetree->key : tv_zero; + + /* 'multi->timetree' will be non-NULL here but the compilers sometimes + yell at us if we assume so */ + if(multi->timetree && + Curl_timediff_us(multi->timetree->key, now) > 0) { /* some time left before expiration */ - *timeout_ms = (long)curlx_tvdiff(multi->timetree->key, now); - if(!*timeout_ms) - /* - * Since we only provide millisecond resolution on the returned value - * and the diff might be less than one millisecond here, we don't - * return zero as that may cause short bursts of busyloops on fast - * processors while the diff is still present but less than one - * millisecond! instead we return 1 until the time is ripe. - */ - *timeout_ms=1; + timediff_t diff = Curl_timediff_ceil(multi->timetree->key, now); + /* this should be safe even on 32-bit archs, as we do not use that + overly long timeouts */ + *timeout_ms = (long)diff; } - else + else { /* 0 means immediately */ *timeout_ms = 0; + } } - else + else { + *expire_time = tv_zero; *timeout_ms = -1; + } return CURLM_OK; } @@ -2819,61 +3412,110 @@ static CURLMcode multi_timeout(struct Curl_multi *multi, CURLMcode curl_multi_timeout(struct Curl_multi *multi, long *timeout_ms) { + struct curltime expire_time; + /* First, make some basic checks that the CURLM handle is a good handle */ if(!GOOD_MULTI_HANDLE(multi)) return CURLM_BAD_HANDLE; - return multi_timeout(multi, timeout_ms); + if(multi->in_callback) + return CURLM_RECURSIVE_API_CALL; + + return multi_timeout(multi, &expire_time, timeout_ms); } +#define DEBUG_UPDATE_TIMER 0 + /* * Tell the application it should update its timers, if it subscribes to the * update timer callback. */ -static int update_timer(struct Curl_multi *multi) +CURLMcode Curl_update_timer(struct Curl_multi *multi) { + struct curltime expire_ts; long timeout_ms; + int rc; + bool set_value = FALSE; - if(!multi->timer_cb) - return 0; - if(multi_timeout(multi, &timeout_ms)) { - return -1; - } - if(timeout_ms < 0) { - static const struct timeval 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 */ - return multi->timer_cb(multi, -1, multi->timer_userp); - } - return 0; + if(!multi->timer_cb || multi->dead) + return CURLM_OK; + if(multi_timeout(multi, &expire_ts, &timeout_ms)) { + 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 0; - - multi->timer_lastcall = multi->timetree->key; + if(timeout_ms < 0 && multi->last_timeout_ms < 0) { +#if DEBUG_UPDATE_TIMER + fprintf(stderr, "Curl_update_timer(), still no timeout, no change\n"); +#endif + } + else if(timeout_ms < 0) { + /* there is no timeout now but there was one previously */ +#if DEBUG_UPDATE_TIMER + fprintf(stderr, "Curl_update_timer(), remove timeout, " + " last_timeout=%ldms\n", multi->last_timeout_ms); +#endif + timeout_ms = -1; /* normalize */ + set_value = TRUE; + } + else if(multi->last_timeout_ms < 0) { +#if DEBUG_UPDATE_TIMER + fprintf(stderr, "Curl_update_timer(), had no timeout, set now\n"); +#endif + set_value = TRUE; + } + else if(Curl_timediff_us(multi->last_expire_ts, expire_ts)) { + /* We had a timeout before and have one now, the absolute timestamp + * differs. The relative timeout_ms may be the same, but the starting + * point differs. Let the application restart its timer. */ +#if DEBUG_UPDATE_TIMER + fprintf(stderr, "Curl_update_timer(), expire timestamp changed\n"); +#endif + set_value = TRUE; + } + else { + /* We have same expire time as previously. Our relative 'timeout_ms' + * may be different now, but the application has the timer running + * and we do not to tell it to start this again. */ +#if DEBUG_UPDATE_TIMER + fprintf(stderr, "Curl_update_timer(), same expire timestamp, no change\n"); +#endif + } - return multi->timer_cb(multi, timeout_ms, multi->timer_userp); + if(set_value) { +#if DEBUG_UPDATE_TIMER + fprintf(stderr, "Curl_update_timer(), set timeout %ldms\n", timeout_ms); +#endif + multi->last_expire_ts = expire_ts; + multi->last_timeout_ms = timeout_ms; + 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_freetimeout() + * multi_deltimeout() * - * Callback used by the llist system when a single timeout list entry is - * destroyed. + * Remove a given timestamp from the list of timeouts. */ -static void multi_freetimeout(void *user, void *entryptr) +static void +multi_deltimeout(struct Curl_easy *data, expire_id eid) { - (void)user; - - /* the entry was plain malloc()'ed */ - free(entryptr); + struct Curl_llist_node *e; + struct Curl_llist *timeoutlist = &data->state.timeoutlist; + /* find and remove the specific node from the list */ + for(e = Curl_llist_head(timeoutlist); e; e = Curl_node_next(e)) { + struct time_node *n = Curl_node_elem(e); + if(n->eid == eid) { + Curl_node_remove(e); + return; + } + } } /* @@ -2884,25 +3526,28 @@ static void multi_freetimeout(void *user, void *entryptr) * */ static CURLMcode -multi_addtimeout(struct curl_llist *timeoutlist, - struct timeval *stamp) +multi_addtimeout(struct Curl_easy *data, + struct curltime *stamp, + expire_id eid) { - struct curl_llist_element *e; - struct timeval *timedup; - struct curl_llist_element *prev = NULL; + struct Curl_llist_node *e; + struct time_node *node; + struct Curl_llist_node *prev = NULL; + size_t n; + struct Curl_llist *timeoutlist = &data->state.timeoutlist; - timedup = malloc(sizeof(*timedup)); - if(!timedup) - return CURLM_OUT_OF_MEMORY; + node = &data->state.expires[eid]; - /* copy the timestamp */ - memcpy(timedup, stamp, sizeof(*timedup)); + /* copy the timestamp and id */ + memcpy(&node->time, stamp, sizeof(*stamp)); + node->eid = eid; /* also marks it as in use */ - if(Curl_llist_count(timeoutlist)) { + n = Curl_llist_count(timeoutlist); + if(n) { /* find the correct spot in the list */ - for(e = timeoutlist->head; e; e = e->next) { - struct timeval *checktime = e->ptr; - time_t diff = curlx_tvdiff(*checktime, *timedup); + for(e = Curl_llist_head(timeoutlist); e; e = Curl_node_next(e)) { + struct time_node *check = Curl_node_elem(e); + timediff_t diff = Curl_timediff(check->time, node->time); if(diff > 0) break; prev = e; @@ -2912,11 +3557,7 @@ multi_addtimeout(struct curl_llist *timeoutlist, /* else this is the first timeout on the list */ - if(!Curl_llist_insert_next(timeoutlist, prev, timedup)) { - free(timedup); - return CURLM_OUT_OF_MEMORY; - } - + Curl_llist_insert_next(timeoutlist, prev, node, &node->list); return CURLM_OK; } @@ -2928,146 +3569,132 @@ multi_addtimeout(struct curl_llist *timeoutlist, * * 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, time_t milli) +static void Curl_expire_ex(struct Curl_easy *data, + const struct curltime *nowp, + timediff_t milli, expire_id id) { struct Curl_multi *multi = data->multi; - struct timeval *nowp = &data->state.expiretime; - int rc; - struct timeval set; + struct curltime *curr_expire = &data->state.expiretime; + struct curltime set; /* this is only interesting while there is still an associated multi struct remaining! */ if(!multi) return; - set = Curl_tvnow(); - set.tv_sec += (long)(milli/1000); - set.tv_usec += (milli%1000)*1000; + DEBUGASSERT(id < EXPIRE_LAST); + + set = *nowp; + set.tv_sec += (time_t)(milli/1000); /* might be a 64 to 32 bits conversion */ + set.tv_usec += (int)(milli%1000)*1000; if(set.tv_usec >= 1000000) { set.tv_sec++; set.tv_usec -= 1000000; } - if(nowp->tv_sec || nowp->tv_usec) { + /* 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(curr_expire->tv_sec || curr_expire->tv_usec) { /* This means that the struct is added as a node in the splay tree. Compare if the new time is earlier, and only remove-old/add-new if it is. */ - time_t diff = curlx_tvdiff(set, *nowp); + timediff_t diff = Curl_timediff(set, *curr_expire); + int rc; + if(diff > 0) { - /* the new expire time was later so just add it to the queue - and get out */ - multi_addtimeout(data->state.timeoutlist, &set); + /* The current splay tree entry is sooner than this new expiry time. + We do not need to update our splay tree entry. */ return; } - /* the new time is newer than the presently set one, so add the current - to the queue and update the head */ - multi_addtimeout(data->state.timeoutlist, nowp); - /* 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_splayremovebyaddr(multi->timetree, - &data->state.timenode, - &multi->timetree); + rc = Curl_splayremove(multi->timetree, &data->state.timenode, + &multi->timetree); if(rc) - infof(data, "Internal error removing splay node = %d\n", rc); + infof(data, "Internal error removing splay node = %d", rc); } - *nowp = set; - data->state.timenode.payload = data; - multi->timetree = Curl_splayinsert(*nowp, multi->timetree, + /* Indicate that we are in the splay tree and insert the new timer expiry + value since it is our local minimum. */ + *curr_expire = set; + Curl_splayset(&data->state.timenode, data); + multi->timetree = Curl_splayinsert(*curr_expire, multi->timetree, &data->state.timenode); } +void Curl_expire(struct Curl_easy *data, timediff_t milli, expire_id id) +{ + struct curltime now = Curl_now(); + Curl_expire_ex(data, &now, milli, id); +} + /* - * Curl_expire_latest() - * - * This is like Curl_expire() but will only add a timeout node to the list of - * timers if there is no timeout that will expire before the given time. + * Curl_expire_done() * - * Use this function if the code logic risks calling this function many times - * or if there's no particular conditional wait in the code for this specific - * time-out period to expire. + * Removes the expire timer. Marks it as done. * */ -void Curl_expire_latest(struct Curl_easy *data, time_t milli) +void Curl_expire_done(struct Curl_easy *data, expire_id id) { - struct timeval *expire = &data->state.expiretime; - - struct timeval set; - - set = Curl_tvnow(); - set.tv_sec += (long)(milli / 1000); - set.tv_usec += (milli % 1000) * 1000; - - if(set.tv_usec >= 1000000) { - set.tv_sec++; - set.tv_usec -= 1000000; - } - - if(expire->tv_sec || expire->tv_usec) { - /* This means that the struct is added as a node in the splay tree. - Compare if the new time is earlier, and only remove-old/add-new if it - is. */ - time_t diff = curlx_tvdiff(set, *expire); - if(diff > 0) - /* the new expire time was later than the top time, so just skip this */ - return; - } - - /* Just add the timeout like normal */ - Curl_expire(data, milli); + /* 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) +bool Curl_expire_clear(struct Curl_easy *data) { struct Curl_multi *multi = data->multi; - struct timeval *nowp = &data->state.expiretime; - int rc; + struct curltime *nowp = &data->state.expiretime; /* this is only interesting while there is still an associated multi struct remaining! */ if(!multi) - return; + return FALSE; 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; + struct Curl_llist *list = &data->state.timeoutlist; + int rc; - rc = Curl_splayremovebyaddr(multi->timetree, - &data->state.timenode, - &multi->timetree); + rc = Curl_splayremove(multi->timetree, &data->state.timenode, + &multi->timetree); if(rc) - infof(data, "Internal error clearing splay node = %d\n", 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); + /* clear the timeout list too */ + Curl_llist_destroy(list, NULL); #ifdef DEBUGBUILD - infof(data, "Expire cleared\n"); + infof(data, "Expire cleared"); #endif nowp->tv_sec = 0; nowp->tv_usec = 0; + return TRUE; } + return FALSE; } - - - CURLMcode curl_multi_assign(struct Curl_multi *multi, curl_socket_t s, void *hashp) { struct Curl_sh_entry *there = NULL; + if(!GOOD_MULTI_HANDLE(multi)) + return CURLM_BAD_HANDLE; there = sh_getentry(&multi->sockhash, s); @@ -3079,87 +3706,223 @@ CURLMcode curl_multi_assign(struct Curl_multi *multi, curl_socket_t s, return CURLM_OK; } -size_t Curl_multi_max_host_connections(struct Curl_multi *multi) +static void move_pending_to_connect(struct Curl_multi *multi, + struct Curl_easy *data) { - return multi ? multi->max_host_connections : 0; + DEBUGASSERT(data->mstate == MSTATE_PENDING); + + /* Remove this node from the pending list */ + Curl_node_remove(&data->multi_queue); + + /* put it into the process list */ + Curl_llist_append(&multi->process, data, &data->multi_queue); + + multistate(data, MSTATE_CONNECT); + + /* Make sure that the handle will be processed soonish. */ + Curl_expire(data, 0, EXPIRE_RUN_NOW); } -size_t Curl_multi_max_total_connections(struct Curl_multi *multi) +/* process_pending_handles() moves a handle from PENDING back into the process + list and change state to CONNECT. + + We do not move all transfers because that can be a significant amount. + Since this is tried every now and then doing too many too often becomes a + performance problem. + + When there is a change for connection limits like max host connections etc, + this likely only allows one new transfer. When there is a pipewait change, + it can potentially allow hundreds of new transfers. + + We could consider an improvement where we store the queue reason and allow + more pipewait rechecks than others. +*/ +static void process_pending_handles(struct Curl_multi *multi) { - return multi ? multi->max_total_connections : 0; + struct Curl_llist_node *e = Curl_llist_head(&multi->pending); + if(e) { + struct Curl_easy *data = Curl_node_elem(e); + move_pending_to_connect(multi, data); + } } -curl_off_t Curl_multi_content_length_penalty_size(struct Curl_multi *multi) +void Curl_set_in_callback(struct Curl_easy *data, bool value) { - return multi ? multi->content_length_penalty_size : 0; + if(data && data->multi) + data->multi->in_callback = value; } -curl_off_t Curl_multi_chunk_length_penalty_size(struct Curl_multi *multi) +bool Curl_is_in_callback(struct Curl_easy *data) { - return multi ? multi->chunk_length_penalty_size : 0; + return (data && data->multi && data->multi->in_callback); } -struct curl_llist *Curl_multi_pipelining_site_bl(struct Curl_multi *multi) +unsigned int Curl_multi_max_concurrent_streams(struct Curl_multi *multi) { - return multi->pipelining_site_bl; + DEBUGASSERT(multi); + return multi->max_concurrent_streams; } -struct curl_llist *Curl_multi_pipelining_server_bl(struct Curl_multi *multi) +struct Curl_easy **curl_multi_get_handles(struct Curl_multi *multi) { - return multi->pipelining_server_bl; + struct Curl_easy **a = malloc(sizeof(struct Curl_easy *) * + (multi->num_easy + 1)); + if(a) { + unsigned int i = 0; + struct Curl_llist_node *e; + for(e = Curl_llist_head(&multi->process); e; e = Curl_node_next(e)) { + struct Curl_easy *data = Curl_node_elem(e); + DEBUGASSERT(i < multi->num_easy); + if(!data->state.internal) + a[i++] = data; + } + a[i] = NULL; /* last entry is a NULL */ + } + return a; } -void Curl_multi_process_pending_handles(struct Curl_multi *multi) +CURLcode Curl_multi_xfer_buf_borrow(struct Curl_easy *data, + char **pbuf, size_t *pbuflen) { - struct curl_llist_element *e = multi->pending->head; + DEBUGASSERT(data); + DEBUGASSERT(data->multi); + *pbuf = NULL; + *pbuflen = 0; + if(!data->multi) { + failf(data, "transfer has no multi handle"); + return CURLE_FAILED_INIT; + } + if(!data->set.buffer_size) { + failf(data, "transfer buffer size is 0"); + return CURLE_FAILED_INIT; + } + if(data->multi->xfer_buf_borrowed) { + failf(data, "attempt to borrow xfer_buf when already borrowed"); + return CURLE_AGAIN; + } + + if(data->multi->xfer_buf && + data->set.buffer_size > data->multi->xfer_buf_len) { + /* not large enough, get a new one */ + free(data->multi->xfer_buf); + data->multi->xfer_buf = NULL; + data->multi->xfer_buf_len = 0; + } - while(e) { - struct Curl_easy *data = e->ptr; - struct curl_llist_element *next = e->next; + if(!data->multi->xfer_buf) { + data->multi->xfer_buf = malloc((size_t)data->set.buffer_size); + if(!data->multi->xfer_buf) { + failf(data, "could not allocate xfer_buf of %zu bytes", + (size_t)data->set.buffer_size); + return CURLE_OUT_OF_MEMORY; + } + data->multi->xfer_buf_len = data->set.buffer_size; + } - if(data->mstate == CURLM_STATE_CONNECT_PEND) { - multistate(data, CURLM_STATE_CONNECT); + data->multi->xfer_buf_borrowed = TRUE; + *pbuf = data->multi->xfer_buf; + *pbuflen = data->multi->xfer_buf_len; + return CURLE_OK; +} - /* Remove this node from the list */ - Curl_llist_remove(multi->pending, e, NULL); +void Curl_multi_xfer_buf_release(struct Curl_easy *data, char *buf) +{ + (void)buf; + DEBUGASSERT(data); + DEBUGASSERT(data->multi); + DEBUGASSERT(!buf || data->multi->xfer_buf == buf); + data->multi->xfer_buf_borrowed = FALSE; +} - /* Make sure that the handle will be processed soonish. */ - Curl_expire_latest(data, 0); - } +CURLcode Curl_multi_xfer_ulbuf_borrow(struct Curl_easy *data, + char **pbuf, size_t *pbuflen) +{ + DEBUGASSERT(data); + DEBUGASSERT(data->multi); + *pbuf = NULL; + *pbuflen = 0; + if(!data->multi) { + failf(data, "transfer has no multi handle"); + return CURLE_FAILED_INIT; + } + if(!data->set.upload_buffer_size) { + failf(data, "transfer upload buffer size is 0"); + return CURLE_FAILED_INIT; + } + if(data->multi->xfer_ulbuf_borrowed) { + failf(data, "attempt to borrow xfer_ulbuf when already borrowed"); + return CURLE_AGAIN; + } + + if(data->multi->xfer_ulbuf && + data->set.upload_buffer_size > data->multi->xfer_ulbuf_len) { + /* not large enough, get a new one */ + free(data->multi->xfer_ulbuf); + data->multi->xfer_ulbuf = NULL; + data->multi->xfer_ulbuf_len = 0; + } - e = next; /* operate on next handle */ + if(!data->multi->xfer_ulbuf) { + data->multi->xfer_ulbuf = malloc((size_t)data->set.upload_buffer_size); + if(!data->multi->xfer_ulbuf) { + failf(data, "could not allocate xfer_ulbuf of %zu bytes", + (size_t)data->set.upload_buffer_size); + return CURLE_OUT_OF_MEMORY; + } + data->multi->xfer_ulbuf_len = data->set.upload_buffer_size; } + + data->multi->xfer_ulbuf_borrowed = TRUE; + *pbuf = data->multi->xfer_ulbuf; + *pbuflen = data->multi->xfer_ulbuf_len; + return CURLE_OK; } -#ifdef DEBUGBUILD -void Curl_multi_dump(struct Curl_multi *multi) -{ - struct Curl_easy *data; - int i; - fprintf(stderr, "* Multi status: %d handles, %d alive\n", - multi->num_easy, multi->num_alive); - for(data=multi->easyp; data; data = data->next) { - if(data->mstate < CURLM_STATE_COMPLETED) { - /* only display handles that are not completed */ - fprintf(stderr, "handle %p, state %s, %d sockets\n", - (void *)data, - statename[data->mstate], data->numsocks); - for(i=0; i < data->numsocks; i++) { - curl_socket_t s = data->sockets[i]; - struct Curl_sh_entry *entry = sh_getentry(&multi->sockhash, s); - - fprintf(stderr, "%d ", (int)s); - if(!entry) { - fprintf(stderr, "INTERNAL CONFUSION\n"); - continue; - } - fprintf(stderr, "[%s %s] ", - entry->action&CURL_POLL_IN?"RECVING":"", - entry->action&CURL_POLL_OUT?"SENDING":""); - } - if(data->numsocks) - fprintf(stderr, "\n"); +void Curl_multi_xfer_ulbuf_release(struct Curl_easy *data, char *buf) +{ + (void)buf; + DEBUGASSERT(data); + DEBUGASSERT(data->multi); + DEBUGASSERT(!buf || data->multi->xfer_ulbuf == buf); + data->multi->xfer_ulbuf_borrowed = FALSE; +} + +static void multi_xfer_bufs_free(struct Curl_multi *multi) +{ + DEBUGASSERT(multi); + Curl_safefree(multi->xfer_buf); + multi->xfer_buf_len = 0; + multi->xfer_buf_borrowed = FALSE; + Curl_safefree(multi->xfer_ulbuf); + multi->xfer_ulbuf_len = 0; + multi->xfer_ulbuf_borrowed = FALSE; +} + +struct Curl_easy *Curl_multi_get_handle(struct Curl_multi *multi, + curl_off_t mid) +{ + + if(mid >= 0) { + struct Curl_easy *data; + struct Curl_llist_node *e; + + for(e = Curl_llist_head(&multi->process); e; e = Curl_node_next(e)) { + data = Curl_node_elem(e); + if(data->mid == mid) + return data; + } + /* may be in msgsent queue */ + for(e = Curl_llist_head(&multi->msgsent); e; e = Curl_node_next(e)) { + data = Curl_node_elem(e); + if(data->mid == mid) + return data; + } + /* may be in pending queue */ + for(e = Curl_llist_head(&multi->pending); e; e = Curl_node_next(e)) { + data = Curl_node_elem(e); + if(data->mid == mid) + return data; } } + return NULL; } -#endif diff --git a/contrib/curl/lib/multihandle.h b/contrib/curl/lib/multihandle.h index 0b78de943b..fef117c067 100644 --- a/contrib/curl/lib/multihandle.h +++ b/contrib/curl/lib/multihandle.h @@ -7,11 +7,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2015, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -20,11 +20,20 @@ * 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_node list; /* the 'CURLMsg' is the part that is visible to the external user */ struct CURLMsg extmsg; }; @@ -33,29 +42,27 @@ struct Curl_message { well! */ typedef enum { - CURLM_STATE_INIT, /* 0 - start in this state */ - CURLM_STATE_CONNECT_PEND, /* 1 - no connections, waiting for one */ - CURLM_STATE_CONNECT, /* 2 - resolve/connect has been sent off */ - CURLM_STATE_WAITRESOLVE, /* 3 - awaiting the resolve to finalize */ - CURLM_STATE_WAITCONNECT, /* 4 - awaiting the TCP connect to finalize */ - CURLM_STATE_WAITPROXYCONNECT, /* 5 - awaiting HTTPS proxy SSL initialization - to complete and/or proxy CONNECT to - finalize */ - CURLM_STATE_SENDPROTOCONNECT, /* 6 - initiate protocol connect procedure */ - CURLM_STATE_PROTOCONNECT, /* 7 - completing the protocol-specific connect - phase */ - CURLM_STATE_WAITDO, /* 8 - wait for our turn to send the request */ - CURLM_STATE_DO, /* 9 - start send off the request (part 1) */ - CURLM_STATE_DOING, /* 10 - sending off the request (part 1) */ - CURLM_STATE_DO_MORE, /* 11 - send off the request (part 2) */ - CURLM_STATE_DO_DONE, /* 12 - done sending off request */ - CURLM_STATE_WAITPERFORM, /* 13 - wait for our turn to read the response */ - CURLM_STATE_PERFORM, /* 14 - transfer data */ - CURLM_STATE_TOOFAST, /* 15 - wait because limit-rate exceeded */ - CURLM_STATE_DONE, /* 16 - post data transfer operation */ - CURLM_STATE_COMPLETED, /* 17 - operation complete */ - CURLM_STATE_MSGSENT, /* 18 - the operation complete message is sent */ - CURLM_STATE_LAST /* 19 - not a true state, never use this */ + MSTATE_INIT, /* 0 - start in this state */ + MSTATE_PENDING, /* 1 - no connections, waiting for one */ + MSTATE_SETUP, /* 2 - start a new transfer */ + MSTATE_CONNECT, /* 3 - resolve/connect has been sent off */ + MSTATE_RESOLVING, /* 4 - awaiting the resolve to finalize */ + MSTATE_CONNECTING, /* 5 - awaiting the TCP connect to finalize */ + MSTATE_TUNNELING, /* 6 - awaiting HTTPS proxy SSL initialization to + complete and/or proxy CONNECT to finalize */ + MSTATE_PROTOCONNECT, /* 7 - initiate protocol connect procedure */ + MSTATE_PROTOCONNECTING, /* 8 - completing the protocol-specific connect + phase */ + MSTATE_DO, /* 9 - start send off the request (part 1) */ + MSTATE_DOING, /* 10 - sending off the request (part 1) */ + MSTATE_DOING_MORE, /* 11 - send off the request (part 2) */ + MSTATE_DID, /* 12 - done sending off request */ + MSTATE_PERFORMING, /* 13 - transfer data */ + MSTATE_RATELIMITING, /* 14 - wait because limit-rate exceeded */ + MSTATE_DONE, /* 15 - post data transfer operation */ + MSTATE_COMPLETED, /* 16 - operation complete */ + MSTATE_MSGSENT, /* 17 - the operation complete message is sent */ + MSTATE_LAST /* 18 - not a true state, never use this */ } CURLMstate; /* we support N sockets per easy handle. Set the corresponding bit to what @@ -64,26 +71,32 @@ typedef enum { #define GETSOCK_READABLE (0x00ff) #define GETSOCK_WRITABLE (0xff00) -#define CURLPIPE_ANY (CURLPIPE_HTTP1 | CURLPIPE_MULTIPLEX) +#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) /* 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. */ - long type; + unsigned int magic; - /* We have a doubly-linked circular 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 */ - int num_easy; /* amount of entries in the linked list above. */ - 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 *msglist; /* a list of messages from completed transfers */ - - struct curl_llist *pending; /* Curl_easys that are in the - CURLM_STATE_CONNECT_PEND state */ + /* Each added easy handle is added to ONE of these three lists */ + struct Curl_llist process; /* not in PENDING or MSGSENT */ + struct Curl_llist pending; /* in PENDING */ + struct Curl_llist msgsent; /* in MSGSENT */ + curl_off_t next_easy_mid; /* next multi-id for easy handle added */ /* callback function and user data pointer for the *socket() API */ curl_socket_callback socket_cb; @@ -94,61 +107,83 @@ struct Curl_multi { void *push_userp; /* Hostname cache */ - struct curl_hash hostcache; + 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; + /* buffer used for transfer data, lazy initialized */ + char *xfer_buf; /* the actual buffer */ + size_t xfer_buf_len; /* the allocated length */ + /* buffer used for upload data, lazy initialized */ + char *xfer_ulbuf; /* the actual buffer */ + size_t xfer_ulbuf_len; /* the allocated length */ + /* '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; - - /* pipelining wanted bits (CURLPIPE*) */ - long pipelining; - - bool recheckstate; /* see Curl_multi_connchanged */ + struct Curl_hash sockhash; + /* `proto_hash` is a general key-value store for protocol implementations + * with the lifetime of the multi handle. The number of elements kept here + * should be in the order of supported protocols (and sub-protocols like + * TLS), *not* in the order of connections or current transfers! + * Elements need to be added with their own destructor to be invoked when + * the multi handle is cleaned up (see Curl_hash_add2()).*/ + struct Curl_hash proto_hash; /* Shared connection cache (bundles)*/ - struct conncache conn_cache; - - /* This handle will be used for closing the cached connections in - curl_multi_cleanup() */ - struct Curl_easy *closure_handle; - - long maxconnects; /* if >0, a fixed limit of the maximum number of entries - we're allowed to grow the connection cache to */ + struct cpool cpool; 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 */ - - long max_pipeline_length; /* if >0, maximum number of requests in a - pipeline */ - - long content_length_penalty_size; /* a connection with a - content-length bigger than - this is not considered - for pipelining */ - - long chunk_length_penalty_size; /* a connection with a chunk length - bigger than this is not - considered for pipelining */ - - struct curl_llist *pipelining_site_bl; /* List of sites that are blacklisted - from pipelining */ - - struct curl_llist *pipelining_server_bl; /* List of server types that are - blacklisted from pipelining */ + long max_shutdown_connections; /* if >0, a fixed limit of the maximum number + of connections in shutdown handling */ /* timer callback and user data pointer for the *socket() API */ curl_multi_timer_callback timer_cb; void *timer_userp; - struct timeval timer_lastcall; /* the fixed time for the timeout for the - previous callback */ + long last_timeout_ms; /* the last timeout value set via timer_cb */ + struct curltime last_expire_ts; /* timestamp of last expiry */ + +#ifdef USE_WINSOCK + WSAEVENT wsa_event; /* Winsock event used for waits */ +#else +#ifdef ENABLE_WAKEUP + curl_socket_t wakeup_pair[2]; /* eventfd()/pipe()/socketpair() used for + wakeup 0 is used for read, 1 is used + for write */ +#endif +#endif + unsigned int max_concurrent_streams; + unsigned int maxconnects; /* if >0, a fixed limit of the maximum number of + entries we are allowed to grow the connection + cache to */ +#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 */ + BIT(xfer_buf_borrowed); /* xfer_buf is currently being borrowed */ + BIT(xfer_ulbuf_borrowed); /* xfer_ulbuf is currently being borrowed */ +#ifdef DEBUGBUILD + BIT(warned); /* true after user warned of DEBUGBUILD */ +#endif }; #endif /* HEADER_CURL_MULTIHANDLE_H */ diff --git a/contrib/curl/lib/multiif.h b/contrib/curl/lib/multiif.h index e5de1fc493..e5872cd6dc 100644 --- a/contrib/curl/lib/multiif.h +++ b/contrib/curl/lib/multiif.h @@ -7,11 +7,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2016, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -20,20 +20,34 @@ * 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 */ -void Curl_expire(struct Curl_easy *data, time_t milli); -void Curl_expire_clear(struct Curl_easy *data); -void Curl_expire_latest(struct Curl_easy *data, time_t milli); -bool Curl_pipeline_wanted(const struct Curl_multi* multi, int bits); -void Curl_multi_handlePipeBreak(struct Curl_easy *data); + +CURLcode Curl_updatesocket(struct Curl_easy *data); +void Curl_expire(struct Curl_easy *data, timediff_t milli, expire_id); +bool 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 *data); +CURLcode Curl_preconnect(struct Curl_easy *data); + +void Curl_multi_connchanged(struct Curl_multi *multi); /* Internal version of curl_multi_init() accepts size parameters for the - socket and connection hashes */ -struct Curl_multi *Curl_multi_handle(int hashsize, int chashsize); + socket, connection and dns hashes */ +struct Curl_multi *Curl_multi_handle(size_t hashsize, + size_t chashsize, + size_t dnssize); /* the write bits start at bit 16 for the *getsock() bitmap */ #define GETSOCK_WRITEBITSTART 16 @@ -46,48 +60,29 @@ struct Curl_multi *Curl_multi_handle(int hashsize, int chashsize); /* set the bit for the given sock number to make the bitmap for readable */ #define GETSOCK_READSOCK(x) (1 << (x)) -#ifdef DEBUGBUILD - /* - * Curl_multi_dump is not a stable public function, this is only meant to - * allow easier tracking of the internal handle's state and what sockets - * they use. Only for research and development DEBUGBUILD enabled builds. - */ -void Curl_multi_dump(struct Curl_multi *multi); -#endif - -void Curl_multi_process_pending_handles(struct Curl_multi *multi); - -/* 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_CONTENT_LENGTH_PENALTY_SIZE option */ -curl_off_t Curl_multi_content_length_penalty_size(struct Curl_multi *multi); - -/* Return the value of the CURLMOPT_CHUNK_LENGTH_PENALTY_SIZE option */ -curl_off_t Curl_multi_chunk_length_penalty_size(struct Curl_multi *multi); - -/* Return the value of the CURLMOPT_PIPELINING_SITE_BL option */ -struct curl_llist *Curl_multi_pipelining_site_bl(struct Curl_multi *multi); - -/* Return the value of the CURLMOPT_PIPELINING_SERVER_BL option */ -struct curl_llist *Curl_multi_pipelining_server_bl(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_multi_connchanged(struct Curl_multi *multi); +/* mask for checking if read and/or write is set for index x */ +#define GETSOCK_MASK_RW(x) (GETSOCK_READSOCK(x)|GETSOCK_WRITESOCK(x)) /* * 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 + * 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 connectdata *conn, curl_socket_t s); +void Curl_multi_closed(struct Curl_easy *data, curl_socket_t s); + +/* Compare the two pollsets to notify the multi_socket API of changes + * in socket polling, e.g calling multi->socket_cb() with the changes if + * differences are seen. + */ +CURLMcode Curl_multi_pollset_ev(struct Curl_multi *multi, + struct Curl_easy *data, + struct easy_pollset *ps, + struct easy_pollset *last_ps); /* * Add a handle and move it into PERFORM state at once. For pushed streams. @@ -95,4 +90,64 @@ void Curl_multi_closed(struct connectdata *conn, curl_socket_t s); 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); + +/** + * Borrow the transfer buffer from the multi, suitable + * for the given transfer `data`. The buffer may only be used in one + * multi processing of the easy handle. It MUST be returned to the + * multi before it can be borrowed again. + * Pointers into the buffer remain only valid as long as it is borrowed. + * + * @param data the easy handle + * @param pbuf on return, the buffer to use or NULL on error + * @param pbuflen on return, the size of *pbuf or 0 on error + * @return CURLE_OK when buffer is available and is returned. + * CURLE_OUT_OF_MEMORy on failure to allocate the buffer, + * CURLE_FAILED_INIT if the easy handle is without multi. + * CURLE_AGAIN if the buffer is borrowed already. + */ +CURLcode Curl_multi_xfer_buf_borrow(struct Curl_easy *data, + char **pbuf, size_t *pbuflen); +/** + * Release the borrowed buffer. All references into the buffer become + * invalid after this. + * @param buf the buffer pointer borrowed for coding error checks. + */ +void Curl_multi_xfer_buf_release(struct Curl_easy *data, char *buf); + +/** + * Borrow the upload buffer from the multi, suitable + * for the given transfer `data`. The buffer may only be used in one + * multi processing of the easy handle. It MUST be returned to the + * multi before it can be borrowed again. + * Pointers into the buffer remain only valid as long as it is borrowed. + * + * @param data the easy handle + * @param pbuf on return, the buffer to use or NULL on error + * @param pbuflen on return, the size of *pbuf or 0 on error + * @return CURLE_OK when buffer is available and is returned. + * CURLE_OUT_OF_MEMORy on failure to allocate the buffer, + * CURLE_FAILED_INIT if the easy handle is without multi. + * CURLE_AGAIN if the buffer is borrowed already. + */ +CURLcode Curl_multi_xfer_ulbuf_borrow(struct Curl_easy *data, + char **pbuf, size_t *pbuflen); + +/** + * Release the borrowed upload buffer. All references into the buffer become + * invalid after this. + * @param buf the upload buffer pointer borrowed for coding error checks. + */ +void Curl_multi_xfer_ulbuf_release(struct Curl_easy *data, char *buf); + +/** + * Get the transfer handle for the given id. Returns NULL if not found. + */ +struct Curl_easy *Curl_multi_get_handle(struct Curl_multi *multi, + curl_off_t id); + #endif /* HEADER_CURL_MULTIIF_H */ diff --git a/contrib/curl/lib/netrc.c b/contrib/curl/lib/netrc.c index 996711d119..490efb64ca 100644 --- a/contrib/curl/lib/netrc.c +++ b/contrib/curl/lib/netrc.c @@ -5,11 +5,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2016, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -18,9 +18,12 @@ * 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 @@ -30,6 +33,7 @@ #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" @@ -41,161 +45,309 @@ enum host_lookup_state { NOTHING, HOSTFOUND, /* the 'machine' keyword was found */ - HOSTVALID /* this is "our" machine! */ + HOSTVALID, /* this is "our" machine! */ + MACDEF }; +#define NETRC_FILE_MISSING 1 +#define NETRC_FAILED -1 +#define NETRC_SUCCESS 0 + +#define MAX_NETRC_LINE 4096 + /* - * @unittest: 1304 - * - * *loginp and *passwordp MUST be allocated if they aren't NULL when passed - * in. + * Returns zero on success. */ -int Curl_parsenetrc(const char *host, - char **loginp, - char **passwordp, - char *netrcfile) +static int parsenetrc(const char *host, + char **loginp, + char **passwordp, + char *netrcfile) { FILE *file; - int retcode=1; - int specific_login = (*loginp && **loginp != 0); - bool netrc_alloc = FALSE; - enum host_lookup_state state=NOTHING; + 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=FALSE; /* With specific_login, found *our* login name */ + 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) */ -#define NETRC DOT_CHAR "netrc" + DEBUGASSERT(netrcfile); - if(!netrcfile) { - bool home_alloc = FALSE; - char *home = curl_getenv("HOME"); /* portable environment reader */ - if(home) { - home_alloc = TRUE; -#if defined(HAVE_GETPWUID_R) && defined(HAVE_GETEUID) - } - else { - struct passwd pw, *pw_res; - char pwbuf[1024]; - if(!getpwuid_r(geteuid(), &pw, pwbuf, sizeof(pwbuf), &pw_res) - && pw_res) { - home = strdup(pw.pw_dir); - if(!home) - return CURLE_OUT_OF_MEMORY; - home_alloc = TRUE; - } -#elif defined(HAVE_GETPWUID) && defined(HAVE_GETEUID) - } - else { - struct passwd *pw; - pw= getpwuid(geteuid()); - if(pw) { - home = pw->pw_dir; + file = fopen(netrcfile, FOPEN_READTEXT); + if(file) { + bool done = FALSE; + struct dynbuf buf; + Curl_dyn_init(&buf, MAX_NETRC_LINE); + + while(!done && Curl_get_line(&buf, file)) { + char *tok; + char *tok_end; + bool quoted; + char *netrcbuffer = Curl_dyn_ptr(&buf); + if(state == MACDEF) { + if((netrcbuffer[0] == '\n') || (netrcbuffer[0] == '\r')) + state = NOTHING; + else + continue; } -#endif - } + 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; - if(!home) - return retcode; /* no home directory found (or possibly out of memory) */ + /* leading double-quote means quoted string */ + quoted = (*tok == '\"'); - netrcfile = curl_maprintf("%s%s%s", home, DIR_CHAR, NETRC); - if(home_alloc) - free(home); - if(!netrcfile) { - return -1; - } - netrc_alloc = TRUE; - } + 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; + } + } - file = fopen(netrcfile, FOPEN_READTEXT); - if(netrc_alloc) - free(netrcfile); - if(file) { - char *tok; - char *tok_buf; - bool done=FALSE; - char netrcbuffer[256]; - int netrcbuffsize = (int)sizeof(netrcbuffer); - - while(!done && fgets(netrcbuffer, netrcbuffsize, file)) { - tok=strtok_r(netrcbuffer, " \t\n", &tok_buf); - while(!done && tok) { - - if((*loginp && **loginp) && (*passwordp && **passwordp)) { - done=TRUE; + if((login && *login) && (password && *password)) { + done = TRUE; break; } switch(state) { case NOTHING: - if(strcasecompare("machine", tok)) { + 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; + state = HOSTFOUND; } else if(strcasecompare("default", tok)) { - state=HOSTVALID; - retcode=0; /* we did find our host */ + 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=0; /* we did find our host */ + state = HOSTVALID; + retcode = NETRC_SUCCESS; /* we did find our host */ } else /* not our host */ - state=NOTHING; + state = NOTHING; break; case HOSTVALID: /* we are now parsing sub-keywords concerning "our" host */ if(state_login) { if(specific_login) { - state_our_login = strcasecompare(*loginp, tok); + state_our_login = !Curl_timestrcmp(login, tok); } - else { - free(*loginp); - *loginp = strdup(tok); - if(!*loginp) { - retcode = -1; /* allocation failed */ + 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; + state_login = 0; } else if(state_password) { - if(state_our_login || !specific_login) { - free(*passwordp); - *passwordp = strdup(tok); - if(!*passwordp) { - retcode = -1; /* allocation failed */ + 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; + state_password = 0; } else if(strcasecompare("login", tok)) - state_login=1; + state_login = 1; else if(strcasecompare("password", tok)) - state_password=1; + state_password = 1; else if(strcasecompare("machine", tok)) { - /* ok, there's machine here go => */ + /* ok, there is machine here go => */ state = HOSTFOUND; state_our_login = FALSE; } break; } /* switch (state) */ + tok = ++tok_end; + } + } /* while Curl_get_line() */ - tok = strtok_r(NULL, " \t\n", &tok_buf); - } /* while(tok) */ - } /* while fgets() */ - - out: +out: + Curl_dyn_free(&buf); + 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 are not 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 = aprintf("%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 = aprintf("%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/contrib/curl/lib/netrc.h b/contrib/curl/lib/netrc.h index d980166e6b..37c95db5e4 100644 --- a/contrib/curl/lib/netrc.h +++ b/contrib/curl/lib/netrc.h @@ -7,11 +7,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2011, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -20,17 +20,24 @@ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY * KIND, either express or implied. * + * SPDX-License-Identifier: curl + * ***************************************************************************/ -/* 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); +#include "curl_setup.h" +#ifndef CURL_DISABLE_NETRC + +/* returns -1 on failure, 0 if the host is found, 1 is the host is not 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/contrib/curl/lib/non-ascii.c b/contrib/curl/lib/non-ascii.c deleted file mode 100644 index 2f5de4c681..0000000000 --- a/contrib/curl/lib/non-ascii.c +++ /dev/null @@ -1,338 +0,0 @@ -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) 1998 - 2016, Daniel Stenberg, , et al. - * - * This software is licensed as described in the file COPYING, which - * you should have received as part of this distribution. The terms - * are also available at https://curl.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. - * - ***************************************************************************/ - -#include "curl_setup.h" - -#ifdef CURL_DOES_CONVERSIONS - -#include - -#include "non-ascii.h" -#include "formdata.h" -#include "sendf.h" -#include "urldata.h" - -#include "curl_memory.h" -/* The last #include file should be: */ -#include "memdebug.h" - -#ifdef HAVE_ICONV -#include -/* set default codesets for iconv */ -#ifndef CURL_ICONV_CODESET_OF_NETWORK -#define CURL_ICONV_CODESET_OF_NETWORK "ISO8859-1" -#endif -#ifndef CURL_ICONV_CODESET_FOR_UTF8 -#define CURL_ICONV_CODESET_FOR_UTF8 "UTF-8" -#endif -#define ICONV_ERROR (size_t)-1 -#endif /* HAVE_ICONV */ - -/* - * Curl_convert_clone() returns a malloced copy of the source string (if - * returning CURLE_OK), with the data converted to network format. - */ -CURLcode Curl_convert_clone(struct Curl_easy *data, - const char *indata, - size_t insize, - char **outbuf) -{ - char *convbuf; - CURLcode result; - - convbuf = malloc(insize); - if(!convbuf) - return CURLE_OUT_OF_MEMORY; - - memcpy(convbuf, indata, insize); - result = Curl_convert_to_network(data, convbuf, insize); - if(result) { - free(convbuf); - return result; - } - - *outbuf = convbuf; /* return the converted buffer */ - - return CURLE_OK; -} - -/* - * Curl_convert_to_network() is an internal function for performing ASCII - * conversions on non-ASCII platforms. It convers the buffer _in place_. - */ -CURLcode Curl_convert_to_network(struct Curl_easy *data, - char *buffer, size_t length) -{ - if(data->set.convtonetwork) { - /* use translation callback */ - CURLcode result = data->set.convtonetwork(buffer, length); - if(result) { - failf(data, - "CURLOPT_CONV_TO_NETWORK_FUNCTION callback returned %d: %s", - (int)result, curl_easy_strerror(result)); - } - - return result; - } - else { -#ifdef HAVE_ICONV - /* do the translation ourselves */ - char *input_ptr, *output_ptr; - size_t in_bytes, out_bytes, rc; - int error; - - /* open an iconv conversion descriptor if necessary */ - if(data->outbound_cd == (iconv_t)-1) { - data->outbound_cd = iconv_open(CURL_ICONV_CODESET_OF_NETWORK, - CURL_ICONV_CODESET_OF_HOST); - if(data->outbound_cd == (iconv_t)-1) { - error = ERRNO; - failf(data, - "The iconv_open(\"%s\", \"%s\") call failed with errno %i: %s", - CURL_ICONV_CODESET_OF_NETWORK, - CURL_ICONV_CODESET_OF_HOST, - error, strerror(error)); - return CURLE_CONV_FAILED; - } - } - /* call iconv */ - input_ptr = output_ptr = buffer; - in_bytes = out_bytes = length; - rc = iconv(data->outbound_cd, (const char **)&input_ptr, &in_bytes, - &output_ptr, &out_bytes); - if((rc == ICONV_ERROR) || (in_bytes != 0)) { - error = ERRNO; - failf(data, - "The Curl_convert_to_network iconv call failed with errno %i: %s", - error, strerror(error)); - return CURLE_CONV_FAILED; - } -#else - failf(data, "CURLOPT_CONV_TO_NETWORK_FUNCTION callback required"); - return CURLE_CONV_REQD; -#endif /* HAVE_ICONV */ - } - - return CURLE_OK; -} - -/* - * Curl_convert_from_network() is an internal function for performing ASCII - * conversions on non-ASCII platforms. It convers the buffer _in place_. - */ -CURLcode Curl_convert_from_network(struct Curl_easy *data, - char *buffer, size_t length) -{ - if(data->set.convfromnetwork) { - /* use translation callback */ - CURLcode result = data->set.convfromnetwork(buffer, length); - if(result) { - failf(data, - "CURLOPT_CONV_FROM_NETWORK_FUNCTION callback returned %d: %s", - (int)result, curl_easy_strerror(result)); - } - - return result; - } - else { -#ifdef HAVE_ICONV - /* do the translation ourselves */ - char *input_ptr, *output_ptr; - size_t in_bytes, out_bytes, rc; - int error; - - /* open an iconv conversion descriptor if necessary */ - if(data->inbound_cd == (iconv_t)-1) { - data->inbound_cd = iconv_open(CURL_ICONV_CODESET_OF_HOST, - CURL_ICONV_CODESET_OF_NETWORK); - if(data->inbound_cd == (iconv_t)-1) { - error = ERRNO; - failf(data, - "The iconv_open(\"%s\", \"%s\") call failed with errno %i: %s", - CURL_ICONV_CODESET_OF_HOST, - CURL_ICONV_CODESET_OF_NETWORK, - error, strerror(error)); - return CURLE_CONV_FAILED; - } - } - /* call iconv */ - input_ptr = output_ptr = buffer; - in_bytes = out_bytes = length; - rc = iconv(data->inbound_cd, (const char **)&input_ptr, &in_bytes, - &output_ptr, &out_bytes); - if((rc == ICONV_ERROR) || (in_bytes != 0)) { - error = ERRNO; - failf(data, - "Curl_convert_from_network iconv call failed with errno %i: %s", - error, strerror(error)); - return CURLE_CONV_FAILED; - } -#else - failf(data, "CURLOPT_CONV_FROM_NETWORK_FUNCTION callback required"); - return CURLE_CONV_REQD; -#endif /* HAVE_ICONV */ - } - - return CURLE_OK; -} - -/* - * Curl_convert_from_utf8() is an internal function for performing UTF-8 - * conversions on non-ASCII platforms. - */ -CURLcode Curl_convert_from_utf8(struct Curl_easy *data, - char *buffer, size_t length) -{ - if(data->set.convfromutf8) { - /* use translation callback */ - CURLcode result = data->set.convfromutf8(buffer, length); - if(result) { - failf(data, - "CURLOPT_CONV_FROM_UTF8_FUNCTION callback returned %d: %s", - (int)result, curl_easy_strerror(result)); - } - - return result; - } - else { -#ifdef HAVE_ICONV - /* do the translation ourselves */ - const char *input_ptr; - char *output_ptr; - size_t in_bytes, out_bytes, rc; - int error; - - /* open an iconv conversion descriptor if necessary */ - if(data->utf8_cd == (iconv_t)-1) { - data->utf8_cd = iconv_open(CURL_ICONV_CODESET_OF_HOST, - CURL_ICONV_CODESET_FOR_UTF8); - if(data->utf8_cd == (iconv_t)-1) { - error = ERRNO; - failf(data, - "The iconv_open(\"%s\", \"%s\") call failed with errno %i: %s", - CURL_ICONV_CODESET_OF_HOST, - CURL_ICONV_CODESET_FOR_UTF8, - error, strerror(error)); - return CURLE_CONV_FAILED; - } - } - /* call iconv */ - input_ptr = output_ptr = buffer; - in_bytes = out_bytes = length; - rc = iconv(data->utf8_cd, &input_ptr, &in_bytes, - &output_ptr, &out_bytes); - if((rc == ICONV_ERROR) || (in_bytes != 0)) { - error = ERRNO; - failf(data, - "The Curl_convert_from_utf8 iconv call failed with errno %i: %s", - error, strerror(error)); - return CURLE_CONV_FAILED; - } - if(output_ptr < input_ptr) { - /* null terminate the now shorter output string */ - *output_ptr = 0x00; - } -#else - failf(data, "CURLOPT_CONV_FROM_UTF8_FUNCTION callback required"); - return CURLE_CONV_REQD; -#endif /* HAVE_ICONV */ - } - - return CURLE_OK; -} - -/* - * Init conversion stuff for a Curl_easy - */ -void Curl_convert_init(struct Curl_easy *data) -{ -#if defined(CURL_DOES_CONVERSIONS) && defined(HAVE_ICONV) - /* conversion descriptors for iconv calls */ - data->outbound_cd = (iconv_t)-1; - data->inbound_cd = (iconv_t)-1; - data->utf8_cd = (iconv_t)-1; -#else - (void)data; -#endif /* CURL_DOES_CONVERSIONS && HAVE_ICONV */ -} - -/* - * Setup conversion stuff for a Curl_easy - */ -void Curl_convert_setup(struct Curl_easy *data) -{ - data->inbound_cd = iconv_open(CURL_ICONV_CODESET_OF_HOST, - CURL_ICONV_CODESET_OF_NETWORK); - data->outbound_cd = iconv_open(CURL_ICONV_CODESET_OF_NETWORK, - CURL_ICONV_CODESET_OF_HOST); - data->utf8_cd = iconv_open(CURL_ICONV_CODESET_OF_HOST, - CURL_ICONV_CODESET_FOR_UTF8); -} - -/* - * Close conversion stuff for a Curl_easy - */ - -void Curl_convert_close(struct Curl_easy *data) -{ -#ifdef HAVE_ICONV - /* close iconv conversion descriptors */ - if(data->inbound_cd != (iconv_t)-1) { - iconv_close(data->inbound_cd); - } - if(data->outbound_cd != (iconv_t)-1) { - iconv_close(data->outbound_cd); - } - if(data->utf8_cd != (iconv_t)-1) { - iconv_close(data->utf8_cd); - } -#else - (void)data; -#endif /* HAVE_ICONV */ -} - -/* - * Curl_convert_form() is used from http.c, this converts any form items that - need to be sent in the network encoding. Returns CURLE_OK on success. - */ -CURLcode Curl_convert_form(struct Curl_easy *data, struct FormData *form) -{ - CURLcode result; - - if(!data) - return CURLE_BAD_FUNCTION_ARGUMENT; - - while(form) { - if(form->type == FORM_DATA) { - result = Curl_convert_to_network(data, form->line, form->length); - /* Curl_convert_to_network calls failf if unsuccessful */ - if(result) - return result; - } - - form = form->next; - } - - return CURLE_OK; -} - -#endif /* CURL_DOES_CONVERSIONS */ diff --git a/contrib/curl/lib/non-ascii.h b/contrib/curl/lib/non-ascii.h deleted file mode 100644 index e27f1f41f2..0000000000 --- a/contrib/curl/lib/non-ascii.h +++ /dev/null @@ -1,63 +0,0 @@ -#ifndef HEADER_CURL_NON_ASCII_H -#define HEADER_CURL_NON_ASCII_H -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) 1998 - 2011, Daniel Stenberg, , et al. - * - * This software is licensed as described in the file COPYING, which - * you should have received as part of this distribution. The terms - * are also available at https://curl.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. - * - ***************************************************************************/ -#include "curl_setup.h" - -#ifdef CURL_DOES_CONVERSIONS - -#include "urldata.h" - -/* - * Curl_convert_clone() returns a malloced copy of the source string (if - * returning CURLE_OK), with the data converted to network format. - * - * If no conversion was needed *outbuf may be NULL. - */ -CURLcode Curl_convert_clone(struct Curl_easy *data, - const char *indata, - size_t insize, - char **outbuf); - -void Curl_convert_init(struct Curl_easy *data); -void Curl_convert_setup(struct Curl_easy *data); -void Curl_convert_close(struct Curl_easy *data); - -CURLcode Curl_convert_to_network(struct Curl_easy *data, - char *buffer, size_t length); -CURLcode Curl_convert_from_network(struct Curl_easy *data, - char *buffer, size_t length); -CURLcode Curl_convert_from_utf8(struct Curl_easy *data, - char *buffer, size_t length); -CURLcode Curl_convert_form(struct Curl_easy *data, struct FormData *form); -#else -#define Curl_convert_clone(a,b,c,d) ((void)a, CURLE_OK) -#define Curl_convert_init(x) Curl_nop_stmt -#define Curl_convert_setup(x) Curl_nop_stmt -#define Curl_convert_close(x) Curl_nop_stmt -#define Curl_convert_to_network(a,b,c) ((void)a, CURLE_OK) -#define Curl_convert_from_network(a,b,c) ((void)a, CURLE_OK) -#define Curl_convert_from_utf8(a,b,c) ((void)a, CURLE_OK) -#define Curl_convert_form(a,b) CURLE_OK -#endif - -#endif /* HEADER_CURL_NON_ASCII_H */ diff --git a/contrib/curl/lib/nonblock.c b/contrib/curl/lib/nonblock.c index b764278a51..6dcf42a7ea 100644 --- a/contrib/curl/lib/nonblock.c +++ b/contrib/curl/lib/nonblock.c @@ -5,11 +5,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2015, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -18,6 +18,8 @@ * 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" @@ -29,9 +31,6 @@ #include #endif -#if (defined(HAVE_IOCTL_FIONBIO) && defined(NETWARE)) -#include -#endif #ifdef __VMS #include #include @@ -47,23 +46,26 @@ int curlx_nonblock(curl_socket_t sockfd, /* operate on this */ int nonblock /* TRUE or FALSE */) { -#if defined(USE_BLOCKING_SOCKETS) - - return 0; /* returns success */ - -#elif defined(HAVE_FCNTL_O_NONBLOCK) - - /* most recent unix versions */ +#if defined(HAVE_FCNTL_O_NONBLOCK) + /* most recent Unix versions */ int flags; flags = sfcntl(sockfd, F_GETFL, 0); + if(flags < 0) + return -1; + /* Check if the current file status flags have already satisfied + * the request, if so, it is no need to call fcntl() to replicate it. + */ + if(!!(flags & O_NONBLOCK) == !!nonblock) + return 0; if(nonblock) - return sfcntl(sockfd, F_SETFL, flags | O_NONBLOCK); + flags |= O_NONBLOCK; else - return sfcntl(sockfd, F_SETFL, flags & (~O_NONBLOCK)); + flags &= ~O_NONBLOCK; + return sfcntl(sockfd, F_SETFL, flags); #elif defined(HAVE_IOCTL_FIONBIO) - /* older unix versions */ + /* older Unix versions */ int flags = nonblock ? 1 : 0; return ioctl(sockfd, FIONBIO, &flags); @@ -71,7 +73,7 @@ int curlx_nonblock(curl_socket_t sockfd, /* operate on this */ /* Windows */ unsigned long flags = nonblock ? 1UL : 0UL; - return ioctlsocket(sockfd, FIONBIO, &flags); + return ioctlsocket(sockfd, (long)FIONBIO, &flags); #elif defined(HAVE_IOCTLSOCKET_CAMEL_FIONBIO) @@ -81,7 +83,7 @@ int curlx_nonblock(curl_socket_t sockfd, /* operate on this */ #elif defined(HAVE_SETSOCKOPT_SO_NONBLOCK) - /* BeOS */ + /* Orbis OS */ long b = nonblock ? 1L : 0L; return setsockopt(sockfd, SOL_SOCKET, SO_NONBLOCK, &b, sizeof(b)); diff --git a/contrib/curl/lib/nonblock.h b/contrib/curl/lib/nonblock.h index 98cdc25ab9..4a1a6151f2 100644 --- a/contrib/curl/lib/nonblock.h +++ b/contrib/curl/lib/nonblock.h @@ -7,11 +7,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2009, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -20,6 +20,8 @@ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY * KIND, either express or implied. * + * SPDX-License-Identifier: curl + * ***************************************************************************/ #include /* for curl_socket_t */ @@ -28,4 +30,3 @@ int curlx_nonblock(curl_socket_t sockfd, /* operate on this */ int nonblock /* TRUE or FALSE */); #endif /* HEADER_CURL_NONBLOCK_H */ - diff --git a/contrib/curl/lib/noproxy.c b/contrib/curl/lib/noproxy.c new file mode 100644 index 0000000000..dbfafc93eb --- /dev/null +++ b/contrib/curl/lib/noproxy.c @@ -0,0 +1,264 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#ifndef CURL_DISABLE_PROXY + +#include "inet_pton.h" +#include "strcase.h" +#include "noproxy.h" + +#ifdef HAVE_NETINET_IN_H +#include +#endif + +#ifdef HAVE_ARPA_INET_H +#include +#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 USE_IPV6 + unsigned int bytes; + unsigned int rest; + unsigned char address[16]; + unsigned char check[16]; + + if(!bits) + bits = 128; + + bytes = bits / 8; + rest = bits & 0x07; + if((bytes > 16) || ((bytes == 16) && rest)) + return FALSE; + if(1 != Curl_inet_pton(AF_INET6, ipv6, address)) + return FALSE; + if(1 != Curl_inet_pton(AF_INET6, network, check)) + 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) +{ + char hostip[128]; + + /* + * If we do not 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 was not 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 hostname */ + 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: + 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) { + /* if the bits variable gets a crazy value here, that is fine as + the value will then be rejected in the cidr function */ + bits = (unsigned int)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, this ends the loop */ + if(*p != ',') + break; + /* pass any number of commas */ + while(*p == ',') + p++; + } /* while(*p) */ + } /* NO_PROXY was specified and it was not just an asterisk */ + + return FALSE; +} + +#endif /* CURL_DISABLE_PROXY */ diff --git a/contrib/curl/lib/noproxy.h b/contrib/curl/lib/noproxy.h new file mode 100644 index 0000000000..71ae7eaafa --- /dev/null +++ b/contrib/curl/lib/noproxy.h @@ -0,0 +1,43 @@ +#ifndef HEADER_CURL_NOPROXY_H +#define HEADER_CURL_NOPROXY_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ +#include "curl_setup.h" + +#ifndef CURL_DISABLE_PROXY + +#ifdef UNITTESTS + +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); +#endif + +#endif /* HEADER_CURL_NOPROXY_H */ diff --git a/contrib/curl/lib/nwlib.c b/contrib/curl/lib/nwlib.c deleted file mode 100644 index 290cbe31f4..0000000000 --- a/contrib/curl/lib/nwlib.c +++ /dev/null @@ -1,327 +0,0 @@ -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) 1998 - 2016, Daniel Stenberg, , et al. - * - * This software is licensed as described in the file COPYING, which - * you should have received as part of this distribution. The terms - * are also available at https://curl.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. - * - ***************************************************************************/ - -#include "curl_setup.h" - -#ifdef NETWARE /* Novell NetWare */ - -#ifdef __NOVELL_LIBC__ -/* For native LibC-based NLM we need to register as a real lib. */ -#include -#include -#include -#include -#include - -#include "curl_memory.h" -/* The last #include file should be: */ -#include "memdebug.h" - -typedef struct -{ - int _errno; - void *twentybytes; -} libthreaddata_t; - -typedef struct -{ - int x; - int y; - int z; - void *tenbytes; - NXKey_t perthreadkey; /* if -1, no key obtained... */ - NXMutex_t *lock; -} libdata_t; - -int gLibId = -1; -void *gLibHandle = (void *) NULL; -rtag_t gAllocTag = (rtag_t) NULL; -NXMutex_t *gLibLock = (NXMutex_t *) NULL; - -/* internal library function prototypes... */ -int DisposeLibraryData(void *); -void DisposeThreadData(void *); -int GetOrSetUpData(int id, libdata_t **data, libthreaddata_t **threaddata); - - -int _NonAppStart(void *NLMHandle, - void *errorScreen, - const char *cmdLine, - const char *loadDirPath, - size_t uninitializedDataLength, - void *NLMFileHandle, - int (*readRoutineP)(int conn, - void *fileHandle, size_t offset, - size_t nbytes, - size_t *bytesRead, - void *buffer), - size_t customDataOffset, - size_t customDataSize, - int messageCount, - const char **messages) -{ - NX_LOCK_INFO_ALLOC(liblock, "Per-Application Data Lock", 0); - -#ifndef __GNUC__ -#pragma unused(cmdLine) -#pragma unused(loadDirPath) -#pragma unused(uninitializedDataLength) -#pragma unused(NLMFileHandle) -#pragma unused(readRoutineP) -#pragma unused(customDataOffset) -#pragma unused(customDataSize) -#pragma unused(messageCount) -#pragma unused(messages) -#endif - - /* - * Here we process our command line, post errors (to the error screen), - * perform initializations and anything else we need to do before being able - * to accept calls into us. If we succeed, we return non-zero and the NetWare - * Loader will leave us up, otherwise we fail to load and get dumped. - */ - gAllocTag = AllocateResourceTag(NLMHandle, - " memory allocations", - AllocSignature); - - if(!gAllocTag) { - OutputToScreen(errorScreen, "Unable to allocate resource tag for " - "library memory allocations.\n"); - return -1; - } - - gLibId = register_library(DisposeLibraryData); - - if(gLibId < -1) { - OutputToScreen(errorScreen, "Unable to register library with kernel.\n"); - return -1; - } - - gLibHandle = NLMHandle; - - gLibLock = NXMutexAlloc(0, 0, &liblock); - - if(!gLibLock) { - OutputToScreen(errorScreen, "Unable to allocate library data lock.\n"); - return -1; - } - - return 0; -} - -/* - * Here we clean up any resources we allocated. Resource tags is a big part - * of what we created, but NetWare doesn't ask us to free those. - */ -void _NonAppStop(void) -{ - (void) unregister_library(gLibId); - NXMutexFree(gLibLock); -} - -/* - * This function cannot be the first in the file for if the file is linked - * first, then the check-unload function's offset will be nlmname.nlm+0 - * which is how to tell that there isn't one. When the check function is - * first in the linked objects, it is ambiguous. For this reason, we will - * put it inside this file after the stop function. - * - * Here we check to see if it's alright to ourselves to be unloaded. If not, - * we return a non-zero value. Right now, there isn't any reason not to allow - * it. - */ -int _NonAppCheckUnload(void) -{ - return 0; -} - -int GetOrSetUpData(int id, libdata_t **appData, - libthreaddata_t **threadData) -{ - int err; - libdata_t *app_data; - libthreaddata_t *thread_data; - NXKey_t key; - NX_LOCK_INFO_ALLOC(liblock, "Application Data Lock", 0); - - err = 0; - thread_data = (libthreaddata_t *) NULL; - - /* - * Attempt to get our data for the application calling us. This is where we - * store whatever application-specific information we need to carry in - * support of calling applications. - */ - app_data = (libdata_t *) get_app_data(id); - - if(!app_data) { - /* - * This application hasn't called us before; set up application AND - * per-thread data. Of course, just in case a thread from this same - * application is calling us simultaneously, we better lock our application - * data-creation mutex. We also need to recheck for data after we acquire - * the lock because WE might be that other thread that was too late to - * create the data and the first thread in will have created it. - */ - NXLock(gLibLock); - - app_data = (libdata_t *) get_app_data(id); - if(!app_data) { - app_data = malloc(sizeof(libdata_t)); - - if(app_data) { - memset(app_data, 0, sizeof(libdata_t)); - - app_data->tenbytes = malloc(10); - app_data->lock = NXMutexAlloc(0, 0, &liblock); - - if(!app_data->tenbytes || !app_data->lock) { - if(app_data->lock) - NXMutexFree(app_data->lock); - - free(app_data); - app_data = (libdata_t *) NULL; - err = ENOMEM; - } - - if(app_data) { - /* - * Here we burn in the application data that we were trying to get - * by calling get_app_data(). Next time we call the first function, - * we'll get this data we're just now setting. We also go on here to - * establish the per-thread data for the calling thread, something - * we'll have to do on each application thread the first time - * it calls us. - */ - err = set_app_data(gLibId, app_data); - - if(err) { - free(app_data); - app_data = (libdata_t *) NULL; - err = ENOMEM; - } - else { - /* create key for thread-specific data... */ - err = NXKeyCreate(DisposeThreadData, (void *) NULL, &key); - - if(err) /* (no more keys left?) */ - key = -1; - - app_data->perthreadkey = key; - } - } - } - } - - NXUnlock(gLibLock); - } - - if(app_data) { - key = app_data->perthreadkey; - - if(key != -1 /* couldn't create a key? no thread data */ - && !(err = NXKeyGetValue(key, (void **) &thread_data)) - && !thread_data) { - /* - * Allocate the per-thread data for the calling thread. Regardless of - * whether there was already application data or not, this may be the - * first call by a new thread. The fact that we allocation 20 bytes on - * a pointer is not very important, this just helps to demonstrate that - * we can have arbitrarily complex per-thread data. - */ - thread_data = malloc(sizeof(libthreaddata_t)); - - if(thread_data) { - thread_data->_errno = 0; - thread_data->twentybytes = malloc(20); - - if(!thread_data->twentybytes) { - free(thread_data); - thread_data = (libthreaddata_t *) NULL; - err = ENOMEM; - } - - err = NXKeySetValue(key, thread_data); - if(err) { - free(thread_data->twentybytes); - free(thread_data); - thread_data = (libthreaddata_t *) NULL; - } - } - } - } - - if(appData) - *appData = app_data; - - if(threadData) - *threadData = thread_data; - - return err; -} - -int DisposeLibraryData(void *data) -{ - if(data) { - void *tenbytes = ((libdata_t *) data)->tenbytes; - - free(tenbytes); - free(data); - } - - return 0; -} - -void DisposeThreadData(void *data) -{ - if(data) { - void *twentybytes = ((libthreaddata_t *) data)->twentybytes; - - free(twentybytes); - free(data); - } -} - -#else /* __NOVELL_LIBC__ */ -/* For native CLib-based NLM seems we can do a bit more simple. */ -#include - -int main(void) -{ - /* initialize any globals here... */ - - /* do this if any global initializing was done - SynchronizeStart(); - */ - ExitThread(TSR_THREAD, 0); - return 0; -} - -#endif /* __NOVELL_LIBC__ */ - -#else /* NETWARE */ - -#ifdef __POCC__ -# pragma warn(disable:2024) /* Disable warning #2024: Empty input file */ -#endif - -#endif /* NETWARE */ diff --git a/contrib/curl/lib/nwos.c b/contrib/curl/lib/nwos.c deleted file mode 100644 index c6c22ccbbf..0000000000 --- a/contrib/curl/lib/nwos.c +++ /dev/null @@ -1,88 +0,0 @@ -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) 1998 - 2016, Daniel Stenberg, , et al. - * - * This software is licensed as described in the file COPYING, which - * you should have received as part of this distribution. The terms - * are also available at https://curl.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. - * - ***************************************************************************/ - -#include "curl_setup.h" - -#ifdef NETWARE /* Novell NetWare */ - -#ifdef __NOVELL_LIBC__ -/* For native LibC-based NLM we need to do nothing. */ -int netware_init(void) -{ - return 0; -} - -#else /* __NOVELL_LIBC__ */ - -/* For native CLib-based NLM we need to initialize the LONG namespace. */ -#include -#include -#include -/* Make the CLIB Ctx stuff link */ -#include -NETDB_DEFINE_CONTEXT -/* Make the CLIB Inet stuff link */ -#include -#include -NETINET_DEFINE_CONTEXT - -int netware_init(void) -{ - int rc = 0; - unsigned int myHandle = GetNLMHandle(); - /* import UnAugmentAsterisk dynamically for NW4.x compatibility */ - void (*pUnAugmentAsterisk)(int) = (void(*)(int)) - ImportSymbol(myHandle, "UnAugmentAsterisk"); - /* import UseAccurateCaseForPaths dynamically for NW3.x compatibility */ - void (*pUseAccurateCaseForPaths)(int) = (void(*)(int)) - ImportSymbol(myHandle, "UseAccurateCaseForPaths"); - if(pUnAugmentAsterisk) - pUnAugmentAsterisk(1); - if(pUseAccurateCaseForPaths) - pUseAccurateCaseForPaths(1); - UnimportSymbol(myHandle, "UnAugmentAsterisk"); - UnimportSymbol(myHandle, "UseAccurateCaseForPaths"); - /* set long name space */ - if((SetCurrentNameSpace(4) == 255)) { - rc = 1; - } - if((SetTargetNameSpace(4) == 255)) { - rc = rc + 2; - } - return rc; -} - -/* dummy function to satisfy newer prelude */ -int __init_environment(void) -{ - return 0; -} - -/* dummy function to satisfy newer prelude */ -int __deinit_environment(void) -{ - return 0; -} - -#endif /* __NOVELL_LIBC__ */ - -#endif /* NETWARE */ diff --git a/contrib/curl/lib/objnames-test08.sh b/contrib/curl/lib/objnames-test08.sh deleted file mode 100644 index 485975765c..0000000000 --- a/contrib/curl/lib/objnames-test08.sh +++ /dev/null @@ -1,217 +0,0 @@ -#!/bin/sh -# *************************************************************************** -# * _ _ ____ _ -# * Project ___| | | | _ \| | -# * / __| | | | |_) | | -# * | (__| |_| | _ <| |___ -# * \___|\___/|_| \_\_____| -# * -# * Copyright (C) 2013, Daniel Stenberg, , et al. -# * -# * This software is licensed as described in the file COPYING, which -# * you should have received as part of this distribution. The terms -# * are also available at https://curl.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. -# * -# *************************************************************************** - -# -# This Bourne shell script file is used by test case 1222 to do -# unit testing of curl_8char_object_name() shell function which -# is defined in file objnames.inc and sourced by this file and -# any other shell script that may use it. -# - -# -# argument validation -# - -if test $# -eq 1; then - : -else - echo "Usage: ${0} srcdir" - exit 1 -fi - -if test -f "${1}/runtests.pl"; then - : -else - echo "${0}: Wrong srcdir" - exit 1 -fi - -srcdir=${1} - -if test -f "$srcdir/../lib/objnames.inc"; then - : -else - echo "$0: Missing objnames.inc" - exit 1 -fi - -# -# Some variables -# - -logdir=log -tstnum=1222 - -list_c=$logdir/${tstnum}_list_c -list_obj=$logdir/${tstnum}_list_obj -list_obj_c=$logdir/${tstnum}_list_obj_c -list_obj_uniq=$logdir/${tstnum}_list_obj_uniq - - -# -# Source curl_8char_object_name() function definition -# - -. $srcdir/../lib/objnames.inc - -# -# Some curl_8char_object_name() unit tests -# - -echo 'Testing curl_8char_object_name...' -echo "" - -argstr=123__678__ABC__FGH__KLM__PQRSTUV -expect=16AFKPQR -outstr=`curl_8char_object_name $argstr` -echo "result: $outstr expected: $expect input: $argstr" - -argstr=123__678__ABC__FGH__KLM__PQ.S.UV -expect=16AFKPQ -outstr=`curl_8char_object_name $argstr` -echo "result: $outstr expected: $expect input: $argstr" - -argstr=123__678__ABC..FGH..KLM..PQRSTUV -expect=16ABC -outstr=`curl_8char_object_name $argstr` -echo "result: $outstr expected: $expect input: $argstr" - -argstr=123__678_.ABC._FGH__KLM__PQRSTUV -expect=16 -outstr=`curl_8char_object_name $argstr` -echo "result: $outstr expected: $expect input: $argstr" - -argstr=123.567.90ABCDEFGHIJKLMNOPQRSTUV -expect=123 -outstr=`curl_8char_object_name $argstr` -echo "result: $outstr expected: $expect input: $argstr" - -argstr=1234567.90A.CDEFGHIJKLMNOPQRSTUV -expect=1234567 -outstr=`curl_8char_object_name $argstr` -echo "result: $outstr expected: $expect input: $argstr" - -argstr=1234567890.BCD.FGHIJKLMNOPQRSTUV -expect=12345678 -outstr=`curl_8char_object_name $argstr` -echo "result: $outstr expected: $expect input: $argstr" - -argstr=12=45-78+0AB.DE.GHIJKLMNOPQRSTUV -expect=1470AB -outstr=`curl_8char_object_name $argstr` -echo "result: $outstr expected: $expect input: $argstr" - -argstr=1234567890ABCDEFGHIJKLMNOPQRSTUV -expect=12345678 -outstr=`curl_8char_object_name $argstr` -echo "result: $outstr expected: $expect input: $argstr" - -argstr=123_567_90A_CDE_GHIJKLMNOPQRSTUV -expect=159CGHIJ -outstr=`curl_8char_object_name $argstr` -echo "result: $outstr expected: $expect input: $argstr" - -argstr=123_567_90A_CDEFGHIJKLMNOPQRSTUV -expect=159CDEFG -outstr=`curl_8char_object_name $argstr` -echo "result: $outstr expected: $expect input: $argstr" - -argstr=123_567_90ABCDEFGHIJKLMNOPQRSTUV -expect=1590ABCD -outstr=`curl_8char_object_name $argstr` -echo "result: $outstr expected: $expect input: $argstr" - -argstr=123_567890ABCDEFGHIJKLMNOPQRSTUV -expect=1567890A -outstr=`curl_8char_object_name $argstr` -echo "result: $outstr expected: $expect input: $argstr" - -argstr=1234567890ABCDEFGHIJKLMNOPQRSTUV -expect=12345678 -outstr=`curl_8char_object_name $argstr` -echo "result: $outstr expected: $expect input: $argstr" - -# -# Verify that generated object name is distinct for -# all *.c source files in lib and src subdirectories. -# - -ls $srcdir/../lib/*.c > $list_c -ls $srcdir/../src/*.c >> $list_c - -rm -f $list_obj - -for c_fname in `cat $list_c`; do - obj_name=`curl_8char_object_name $c_fname` - echo "$obj_name" >> $list_obj -done - -sort -u $list_obj > $list_obj_uniq - -cnt_c=`cat $list_c | wc -l` -cnt_u=`cat $list_obj_uniq | wc -l` - -echo "" -echo "" -echo "" -if test $cnt_c -eq $cnt_u; then - echo "8-characters-or-less generated object names are unique." - obj_name_clash="no" -else - echo "8-characters-or-less generated object names are clashing..." - obj_name_clash="yes" -fi - -if test $obj_name_clash = "yes"; then - # - # Show clashing object names and respective source file names - # - echo "" - paste $list_obj $list_c | sort > $list_obj_c - prev_match="no" - prev_line="unknown" - prev_obj_name="unknown" - while read this_line; do - obj_name=`echo "$this_line" | cut -f1` - if test "x$obj_name" = "x$prev_obj_name"; then - if test "x$prev_match" != "xyes"; then - echo "$prev_line" - echo "$this_line" - prev_match="yes" - else - echo "$this_line" - fi - else - prev_match="no" - fi - prev_line=$this_line - prev_obj_name=$obj_name - done < $list_obj_c -fi - -rm -f $list_c -rm -f $list_obj -rm -f $list_obj_c -rm -f $list_obj_uniq - -# end of objnames-test.sh diff --git a/contrib/curl/lib/objnames-test10.sh b/contrib/curl/lib/objnames-test10.sh deleted file mode 100644 index 62184b8640..0000000000 --- a/contrib/curl/lib/objnames-test10.sh +++ /dev/null @@ -1,217 +0,0 @@ -#!/bin/sh -# *************************************************************************** -# * _ _ ____ _ -# * Project ___| | | | _ \| | -# * / __| | | | |_) | | -# * | (__| |_| | _ <| |___ -# * \___|\___/|_| \_\_____| -# * -# * Copyright (C) 2013, Daniel Stenberg, , et al. -# * -# * This software is licensed as described in the file COPYING, which -# * you should have received as part of this distribution. The terms -# * are also available at https://curl.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. -# * -# *************************************************************************** - -# -# This Bourne shell script file is used by test case 1221 to do -# unit testing of curl_10char_object_name() shell function which -# is defined in file objnames.inc and sourced by this file and -# any other shell script that may use it. -# - -# -# argument validation -# - -if test $# -eq 1; then - : -else - echo "Usage: ${0} srcdir" - exit 1 -fi - -if test -f "${1}/runtests.pl"; then - : -else - echo "${0}: Wrong srcdir" - exit 1 -fi - -srcdir=${1} - -if test -f "$srcdir/../lib/objnames.inc"; then - : -else - echo "$0: Missing objnames.inc" - exit 1 -fi - -# -# Some variables -# - -logdir=log -tstnum=1221 - -list_c=$logdir/${tstnum}_list_c -list_obj=$logdir/${tstnum}_list_obj -list_obj_c=$logdir/${tstnum}_list_obj_c -list_obj_uniq=$logdir/${tstnum}_list_obj_uniq - - -# -# Source curl_10char_object_name() function definition -# - -. $srcdir/../lib/objnames.inc - -# -# Some curl_10char_object_name() unit tests -# - -echo 'Testing curl_10char_object_name...' -echo "" - -argstr=123__678__ABC__FGH__KLM__PQRSTUV -expect=16AFKPQRST -outstr=`curl_10char_object_name $argstr` -echo "result: $outstr expected: $expect input: $argstr" - -argstr=123__678__ABC__FGH__KLM__PQ.S.UV -expect=16AFKPQ -outstr=`curl_10char_object_name $argstr` -echo "result: $outstr expected: $expect input: $argstr" - -argstr=123__678__ABC..FGH..KLM..PQRSTUV -expect=16ABC -outstr=`curl_10char_object_name $argstr` -echo "result: $outstr expected: $expect input: $argstr" - -argstr=123__678_.ABC._FGH__KLM__PQRSTUV -expect=16 -outstr=`curl_10char_object_name $argstr` -echo "result: $outstr expected: $expect input: $argstr" - -argstr=123.567.90ABCDEFGHIJKLMNOPQRSTUV -expect=123 -outstr=`curl_10char_object_name $argstr` -echo "result: $outstr expected: $expect input: $argstr" - -argstr=1234567.90A.CDEFGHIJKLMNOPQRSTUV -expect=1234567 -outstr=`curl_10char_object_name $argstr` -echo "result: $outstr expected: $expect input: $argstr" - -argstr=1234567890.BCD.FGHIJKLMNOPQRSTUV -expect=1234567890 -outstr=`curl_10char_object_name $argstr` -echo "result: $outstr expected: $expect input: $argstr" - -argstr=12=45-78+0AB.DE.GHIJKLMNOPQRSTUV -expect=1470AB -outstr=`curl_10char_object_name $argstr` -echo "result: $outstr expected: $expect input: $argstr" - -argstr=1234567890ABCDEFGHIJKLMNOPQRSTUV -expect=1234567890 -outstr=`curl_10char_object_name $argstr` -echo "result: $outstr expected: $expect input: $argstr" - -argstr=123_567_90A_CDE_GHIJKLMNOPQRSTUV -expect=159CGHIJKL -outstr=`curl_10char_object_name $argstr` -echo "result: $outstr expected: $expect input: $argstr" - -argstr=123_567_90A_CDEFGHIJKLMNOPQRSTUV -expect=159CDEFGHI -outstr=`curl_10char_object_name $argstr` -echo "result: $outstr expected: $expect input: $argstr" - -argstr=123_567_90ABCDEFGHIJKLMNOPQRSTUV -expect=1590ABCDEF -outstr=`curl_10char_object_name $argstr` -echo "result: $outstr expected: $expect input: $argstr" - -argstr=123_567890ABCDEFGHIJKLMNOPQRSTUV -expect=1567890ABC -outstr=`curl_10char_object_name $argstr` -echo "result: $outstr expected: $expect input: $argstr" - -argstr=1234567890ABCDEFGHIJKLMNOPQRSTUV -expect=1234567890 -outstr=`curl_10char_object_name $argstr` -echo "result: $outstr expected: $expect input: $argstr" - -# -# Verify that generated object name is distinct for -# all *.c source files in lib and src subdirectories. -# - -ls $srcdir/../lib/*.c > $list_c -ls $srcdir/../src/*.c >> $list_c - -rm -f $list_obj - -for c_fname in `cat $list_c`; do - obj_name=`curl_10char_object_name $c_fname` - echo "$obj_name" >> $list_obj -done - -sort -u $list_obj > $list_obj_uniq - -cnt_c=`cat $list_c | wc -l` -cnt_u=`cat $list_obj_uniq | wc -l` - -echo "" -echo "" -echo "" -if test $cnt_c -eq $cnt_u; then - echo "10-characters-or-less generated object names are unique." - obj_name_clash="no" -else - echo "10-characters-or-less generated object names are clashing..." - obj_name_clash="yes" -fi - -if test $obj_name_clash = "yes"; then - # - # Show clashing object names and respective source file names - # - echo "" - paste $list_obj $list_c | sort > $list_obj_c - prev_match="no" - prev_line="unknown" - prev_obj_name="unknown" - while read this_line; do - obj_name=`echo "$this_line" | cut -f1` - if test "x$obj_name" = "x$prev_obj_name"; then - if test "x$prev_match" != "xyes"; then - echo "$prev_line" - echo "$this_line" - prev_match="yes" - else - echo "$this_line" - fi - else - prev_match="no" - fi - prev_line=$this_line - prev_obj_name=$obj_name - done < $list_obj_c -fi - -rm -f $list_c -rm -f $list_obj -rm -f $list_obj_c -rm -f $list_obj_uniq - -# end of objnames-test10.sh diff --git a/contrib/curl/lib/objnames.inc b/contrib/curl/lib/objnames.inc deleted file mode 100644 index b895528afd..0000000000 --- a/contrib/curl/lib/objnames.inc +++ /dev/null @@ -1,107 +0,0 @@ -# *************************************************************************** -# * _ _ ____ _ -# * Project ___| | | | _ \| | -# * / __| | | | |_) | | -# * | (__| |_| | _ <| |___ -# * \___|\___/|_| \_\_____| -# * -# * Copyright (C) 2012, Daniel Stenberg, , et al. -# * -# * This software is licensed as described in the file COPYING, which -# * you should have received as part of this distribution. The terms -# * are also available at https://curl.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. -# * -# *************************************************************************** - -# -# This file is sourced from curl/packages/OS400/initscript.sh and -# other Bourne shell scripts. Keep it as portable as possible. -# - -# -# curl_10char_object_name -# -# This shell function accepts a single string argument with unspecified -# length representing a (*.c) source file name and returns a string which -# is a transformation of given argument. -# -# The intended purpose of this function is to transliterate a (*.c) source -# file name that may be longer than 10 characters, or not, into a string -# with at most 10 characters which may be used as an OS/400 object name. -# -# This function might not be universally usefull, nor we care about it. -# -# It is intended to be used with libcurl's (*.c) source file names, so -# dependency on libcurl's source file naming scheme is acceptable and -# good enough for its intended use. Specifically it makes use of the fact -# that libcurl's (*.c) source file names which may be longer than 10 chars -# are conformed with underscore '_' separated substrings, or separated by -# other character which does not belong to the [0-9], [a-z] or [A-Z] sets. -# -# This allows repeatable and automatic short object name generation with -# no need for a hardcoded mapping table. -# -# Transformation is done in the following way: -# -# 1) Leading directory components are removed. -# 2) Leftmost dot character and any other char following it are removed. -# 3) Lowercase characters are transliterated to uppercase. -# 4) Characters not in [A-Z] or [0-9] are transliterated to underscore '_'. -# 5) Every sequence of one or more underscores is replaced with a single one. -# 6) Five leftmost substrings which end in an underscore character are -# replaced by the first character of each substring, while retaining -# the rest of the string. -# 7) Finally the result is truncated to 10 characters. -# -# Resulting object name may be shorter than 10 characters. -# -# Test case 1221 does unit testng of this function and also verifies -# that it is possible to generate distinct short object names for all -# curl and libcurl *.c source file names. -# - -curl_10char_object_name() { - echo "${1}" | \ - sed -e 's:.*/::' \ - -e 's:[.].*::' \ - -e 'y:abcdefghijklmnopqrstuvwxyz:ABCDEFGHIJKLMNOPQRSTUVWXYZ:' \ - -e 's:[^ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890_]:_:g' \ - -e 's:__*:_:g' \ - -e 's:\([^_]\)[^_]*_\(.*\):\1\2:' \ - -e 's:\([^_]\)\([^_]\)[^_]*_\(.*\):\1\2\3:' \ - -e 's:\([^_]\)\([^_]\)\([^_]\)[^_]*_\(.*\):\1\2\3\4:' \ - -e 's:\([^_]\)\([^_]\)\([^_]\)\([^_]\)[^_]*_\(.*\):\1\2\3\4\5:' \ - -e 's:\([^_]\)\([^_]\)\([^_]\)\([^_]\)\([^_]\)[^_]*_\(.*\):\1\2\3\4\5\6:' \ - -e 's:^\(..........\).*:\1:' -} - -# -# curl_8char_object_name -# -# Same as curl_10char_object_name() description and details above, except -# that object name is limited to 8 charcters maximum. -# - -curl_8char_object_name() { - echo "${1}" | \ - sed -e 's:.*/::' \ - -e 's:[.].*::' \ - -e 'y:abcdefghijklmnopqrstuvwxyz:ABCDEFGHIJKLMNOPQRSTUVWXYZ:' \ - -e 's:[^ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890_]:_:g' \ - -e 's:__*:_:g' \ - -e 's:\([^_]\)[^_]*_\(.*\):\1\2:' \ - -e 's:\([^_]\)\([^_]\)[^_]*_\(.*\):\1\2\3:' \ - -e 's:\([^_]\)\([^_]\)\([^_]\)[^_]*_\(.*\):\1\2\3\4:' \ - -e 's:\([^_]\)\([^_]\)\([^_]\)\([^_]\)[^_]*_\(.*\):\1\2\3\4\5:' \ - -e 's:\([^_]\)\([^_]\)\([^_]\)\([^_]\)\([^_]\)[^_]*_\(.*\):\1\2\3\4\5\6:' \ - -e 's:^\(........\).*:\1:' -} - -# end of objectname.inc diff --git a/contrib/curl/lib/openldap.c b/contrib/curl/lib/openldap.c index 4b8cfb9c24..542802a073 100644 --- a/contrib/curl/lib/openldap.c +++ b/contrib/curl/lib/openldap.c @@ -5,12 +5,12 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 2010, Howard Chu, - * Copyright (C) 2011 - 2016, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. + * Copyright (C) Howard Chu, * * This software is licensed 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. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -19,6 +19,8 @@ * 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" @@ -45,48 +47,96 @@ #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 ldap_setup_connection(struct connectdata *conn); -static CURLcode ldap_do(struct connectdata *conn, bool *done); -static CURLcode ldap_done(struct connectdata *conn, CURLcode, bool); -static CURLcode ldap_connect(struct connectdata *conn, bool *done); -static CURLcode ldap_connecting(struct connectdata *conn, bool *done); -static CURLcode ldap_disconnect(struct connectdata *conn, bool dead); +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 ldap_recv; +static Curl_recv oldap_recv; /* * LDAP protocol handler. */ const struct Curl_handler Curl_handler_ldap = { - "LDAP", /* scheme */ - ldap_setup_connection, /* setup_connection */ - ldap_do, /* do_it */ - ldap_done, /* done */ + "ldap", /* scheme */ + oldap_setup_connection, /* setup_connection */ + oldap_do, /* do_it */ + oldap_done, /* done */ ZERO_NULL, /* do_more */ - ldap_connect, /* connect_it */ - ldap_connecting, /* connecting */ + 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 */ - ldap_disconnect, /* disconnect */ - ZERO_NULL, /* readwrite */ + oldap_disconnect, /* disconnect */ + ZERO_NULL, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ + ZERO_NULL, /* connection_check */ + ZERO_NULL, /* attach connection */ PORT_LDAP, /* defport */ CURLPROTO_LDAP, /* protocol */ + CURLPROTO_LDAP, /* family */ PROTOPT_NONE /* flags */ }; @@ -96,303 +146,781 @@ const struct Curl_handler Curl_handler_ldap = { */ const struct Curl_handler Curl_handler_ldaps = { - "LDAPS", /* scheme */ - ldap_setup_connection, /* setup_connection */ - ldap_do, /* do_it */ - ldap_done, /* done */ + "ldaps", /* scheme */ + oldap_setup_connection, /* setup_connection */ + oldap_do, /* do_it */ + oldap_done, /* done */ ZERO_NULL, /* do_more */ - ldap_connect, /* connect_it */ - ldap_connecting, /* connecting */ + 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 */ - ldap_disconnect, /* disconnect */ - ZERO_NULL, /* readwrite */ + oldap_disconnect, /* disconnect */ + ZERO_NULL, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ + ZERO_NULL, /* connection_check */ + ZERO_NULL, /* attach connection */ PORT_LDAPS, /* defport */ - CURLPROTO_LDAP, /* protocol */ + CURLPROTO_LDAPS, /* protocol */ + CURLPROTO_LDAP, /* family */ PROTOPT_SSL /* flags */ }; #endif -static const char *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" +/* 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 */ }; -typedef struct ldapconninfo { - LDAP *ld; - Curl_recv *recv; /* for stacking SSL handler */ +struct ldapconninfo { + struct SASL sasl; /* SASL-related parameters */ + LDAP *ld; /* Openldap connection handle. */ + Curl_recv *recv; /* For stacking SSL handler */ Curl_send *send; - int proto; - int msgid; - bool ssldone; - bool sslinst; - bool didbind; -} ldapconninfo; + 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. */ +}; -typedef struct ldapreqinfo { +struct ldapreqinfo { int msgid; int nument; -} ldapreqinfo; +}; -static CURLcode ldap_setup_connection(struct connectdata *conn) +/* + * oldap_state() + * + * This is the ONLY way to change LDAP state! + */ +static void oldap_state(struct Curl_easy *data, ldapstate newstate) { - ldapconninfo *li; - LDAPURLDesc *lud; - struct Curl_easy *data=conn->data; - int rc, proto; - CURLcode status; + 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; +} - rc = ldap_url_parse(data->change.url, &lud); +/* Map some particular LDAP error codes to CURLcode values. */ +static CURLcode oldap_map_error(int rc, CURLcode result) +{ + switch(rc) { + case LDAP_NO_MEMORY: + return CURLE_OUT_OF_MEMORY; + case LDAP_INVALID_CREDENTIALS: + return CURLE_LOGIN_DENIED; + case LDAP_PROTOCOL_ERROR: + return CURLE_UNSUPPORTED_PROTOCOL; + case LDAP_INSUFFICIENT_ACCESS: + return CURLE_REMOTE_ACCESS_DENIED; + } + 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"; - status = CURLE_URL_MALFORMAT; - if(rc > LDAP_URL_SUCCESS && rc <= LDAP_URL_ERR_BADEXTS) { - if(rc == LDAP_URL_ERR_MEM) - status = CURLE_OUT_OF_MEMORY; + + 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(conn->data, "LDAP local: %s", msg); - return status; + failf(data, "LDAP local: %s", msg); } - proto = ldap_pvt_url_scheme2proto(lud->lud_scheme); + 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); - li = calloc(1, sizeof(ldapconninfo)); - if(!li) - return CURLE_OUT_OF_MEMORY; - li->proto = proto; - conn->proto.generic = li; - connkeep(conn, "OpenLDAP default"); - /* TODO: - * - provide option to choose SASL Binds instead of Simple - */ + 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; + 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) + return oldap_map_error(rc, CURLE_LDAP_CANNOT_BIND); + return CURLE_OK; +} + +/* + * 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; + 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) + return oldap_map_error(rc, CURLE_LDAP_CANNOT_BIND); + return CURLE_OK; +} + +/* + * Sends SASL bind cancellation. + */ +static CURLcode oldap_cancel_auth(struct Curl_easy *data, const char *mech) +{ + struct ldapconninfo *li = data->conn->proto.ldapc; + int rc = ldap_sasl_bind(li->ld, NULL, LDAP_SASL_NULL, NULL, NULL, NULL, + &li->msgid); + + (void)mech; + if(rc != LDAP_SUCCESS) + return oldap_map_error(rc, CURLE_LDAP_CANNOT_BIND); + return CURLE_OK; +} + +/* Starts LDAP simple bind. */ +static CURLcode oldap_perform_bind(struct Curl_easy *data, ldapstate newstate) +{ + 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) + return oldap_map_error(rc, + data->state.aptr.user ? + CURLE_LOGIN_DENIED : CURLE_LDAP_CANNOT_BIND); + oldap_state(data, newstate); + return CURLE_OK; +} + +/* Query the supported SASL authentication mechanisms. */ +static CURLcode oldap_perform_mechs(struct Curl_easy *data) +{ + 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) + return oldap_map_error(rc, CURLE_LOGIN_DENIED); + oldap_state(data, OLDAP_MECHS); + return CURLE_OK; +} + +/* 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) +{ + struct connectdata *conn = data->conn; + struct ldapconninfo *li = conn->proto.ldapc; + bool ssldone = 0; + CURLcode 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) +{ + struct ldapconninfo *li = data->conn->proto.ldapc; + int rc = ldap_start_tls(li->ld, NULL, NULL, &li->msgid); + + if(rc != LDAP_SUCCESS) + return oldap_map_error(rc, CURLE_USE_SSL_FAILED); + oldap_state(data, OLDAP_STARTTLS); + return CURLE_OK; +} #endif -static CURLcode ldap_connect(struct connectdata *conn, bool *done) +static CURLcode oldap_connect(struct Curl_easy *data, bool *done) { - ldapconninfo *li = conn->proto.generic; - struct Curl_easy *data = conn->data; - int rc, proto = LDAP_VERSION3; - char hosturl[1024]; - char *ptr; + 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; - strcpy(hosturl, "ldap"); - ptr = hosturl+4; - if(conn->handler->flags & PROTOPT_SSL) - *ptr++ = 's'; - snprintf(ptr, sizeof(hosturl)-(ptr-hosturl), "://%s:%d", - conn->host.name, conn->remote_port); + 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("%s://%s%s%s:%d", + conn->handler->scheme, + conn->bits.ipv6_ip ? "[" : "", + conn->host.name, + conn->bits.ipv6_ip ? "]" : "", + conn->remote_port); + if(!hosturl) + return CURLE_OUT_OF_MEMORY; 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; } - ldap_set_option(li->ld, LDAP_OPT_PROTOCOL_VERSION, &proto); + free(hosturl); -#ifdef USE_SSL - if(conn->handler->flags & PROTOPT_SSL) { - CURLcode result; - result = Curl_ssl_connect_nonblocking(conn, FIRSTSOCKET, &li->ssldone); - if(result) - return result; +#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 - return CURLE_OK; -} + /* Try version 3 first. */ + ldap_set_option(li->ld, LDAP_OPT_PROTOCOL_VERSION, &version); -static CURLcode ldap_connecting(struct connectdata *conn, bool *done) -{ - ldapconninfo *li = conn->proto.generic; - struct Curl_easy *data = conn->data; - LDAPMessage *msg = NULL; - struct timeval tv = {0, 1}, *tvp; - int rc, err; - char *info = NULL; + /* Do not chase referrals. */ + ldap_set_option(li->ld, LDAP_OPT_REFERRALS, LDAP_OPT_OFF); #ifdef USE_SSL - if(conn->handler->flags & PROTOPT_SSL) { - /* Is the SSL handshake complete yet? */ - if(!li->ssldone) { - CURLcode result = Curl_ssl_connect_nonblocking(conn, FIRSTSOCKET, - &li->ssldone); - if(result || !li->ssldone) - return result; - } + if(conn->handler->flags & PROTOPT_SSL) + return oldap_ssl_connect(data, OLDAP_SSL); - /* Have we installed the libcurl SSL handlers into the sockbuf yet? */ - if(!li->sslinst) { - Sockbuf *sb; - ldap_get_option(li->ld, LDAP_OPT_SOCKBUF, &sb); - ber_sockbuf_add_io(sb, &ldapsb_tls, LBER_SBIOD_LEVEL_TRANSPORT, conn); - li->sslinst = TRUE; - li->recv = conn->recv[FIRSTSOCKET]; - li->send = conn->send[FIRSTSOCKET]; - } + if(data->set.use_ssl) { + CURLcode result = oldap_perform_starttls(data); + + if(!result || data->set.use_ssl != CURLUSESSL_TRY) + return result; } #endif - tvp = &tv; + if(li->sasl.prefmech != SASL_AUTH_NONE) + return oldap_perform_mechs(data); -retry: - if(!li->didbind) { - char *binddn; - struct berval passwd; + /* 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(conn->bits.user_passwd) { - binddn = conn->user; - passwd.bv_val = conn->passwd; - passwd.bv_len = strlen(passwd.bv_val); + 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); + } } - else { - binddn = NULL; - passwd.bv_val = NULL; - passwd.bv_len = 0; + 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; } - rc = ldap_sasl_bind(li->ld, binddn, LDAP_SASL_SIMPLE, &passwd, - NULL, NULL, &li->msgid); - if(rc) - return CURLE_LDAP_CANNOT_BIND; - li->didbind = TRUE; - if(tvp) - return CURLE_OK; + break; + default: + break; } + return result; +} - rc = ldap_result(li->ld, li->msgid, LDAP_MSG_ONE, tvp, &msg); - if(rc < 0) { - failf(data, "LDAP local: bind ldap_result %s", ldap_err2string(rc)); - return CURLE_LDAP_CANNOT_BIND; +/* 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); } - if(rc == 0) { - /* timed out */ - return CURLE_OK; + else { + result = Curl_sasl_continue(&li->sasl, data, code, &progress); + if(!result && progress != SASL_INPROGRESS) + oldap_state(data, OLDAP_STOP); } - rc = ldap_parse_result(li->ld, msg, &err, NULL, &info, NULL, NULL, 1); - if(rc) { - failf(data, "LDAP local: bind ldap_parse_result %s", ldap_err2string(rc)); - return CURLE_LDAP_CANNOT_BIND; + 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); - /* Try to fallback to LDAPv2? */ - if(err == LDAP_PROTOCOL_ERROR) { - int proto; - ldap_get_option(li->ld, LDAP_OPT_PROTOCOL_VERSION, &proto); - if(proto == LDAP_VERSION3) { - if(info) { - ldap_memfree(info); - info = NULL; + 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); } - proto = LDAP_VERSION2; - ldap_set_option(li->ld, LDAP_OPT_PROTOCOL_VERSION, &proto); - li->didbind = FALSE; - goto retry; + 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); } } - if(err) { - failf(data, "LDAP remote: bind failed %s %s", ldap_err2string(rc), - info ? info : ""); - if(info) - ldap_memfree(info); - return CURLE_LOGIN_DENIED; + /* 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; + } + result = Curl_ssl_cfilter_add(data, conn, FIRSTSOCKET); + if(result) + break; + FALLTHROUGH(); + case OLDAP_TLS: + result = oldap_ssl_connect(data, OLDAP_TLS); + if(result) + 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; } - if(info) - ldap_memfree(info); - conn->recv[FIRSTSOCKET] = ldap_recv; - *done = TRUE; + ldap_msgfree(msg); - return CURLE_OK; + *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 ldap_disconnect(struct connectdata *conn, bool dead_connection) +static CURLcode oldap_disconnect(struct Curl_easy *data, + struct connectdata *conn, + bool dead_connection) { - ldapconninfo *li = conn->proto.generic; + 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; } - conn->proto.generic = NULL; + Curl_sasl_cleanup(conn, li->sasl.authused); + conn->proto.ldapc = NULL; free(li); } return CURLE_OK; } -static CURLcode ldap_do(struct connectdata *conn, bool *done) +static CURLcode oldap_do(struct Curl_easy *data, bool *done) { - ldapconninfo *li = conn->proto.generic; - ldapreqinfo *lr; - CURLcode status = CURLE_OK; - int rc = 0; - LDAPURLDesc *ludp = NULL; + struct connectdata *conn = data->conn; + struct ldapconninfo *li = conn->proto.ldapc; + struct ldapreqinfo *lr; + CURLcode result; + int rc; + LDAPURLDesc *lud; int msgid; - struct Curl_easy *data=conn->data; connkeep(conn, "OpenLDAP do"); - infof(data, "LDAP local: %s\n", data->change.url); + infof(data, "LDAP local: %s", data->state.url); - rc = ldap_url_parse(data->change.url, &ludp); - if(rc != LDAP_URL_SUCCESS) { - const char *msg = "url parsing problem"; - status = CURLE_URL_MALFORMAT; - if(rc > LDAP_URL_SUCCESS && rc <= LDAP_URL_ERR_BADEXTS) { - if(rc == LDAP_URL_ERR_MEM) - status = CURLE_OUT_OF_MEMORY; - msg = url_errs[rc]; + result = oldap_url_parse(data, &lud); + if(!result) { +#ifdef USE_SSL + if(ssl_installed(conn)) { + 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); } - failf(conn->data, "LDAP local: %s", msg); - return status; - } +#endif - rc = ldap_search_ext(li->ld, ludp->lud_dn, ludp->lud_scope, - ludp->lud_filter, ludp->lud_attrs, 0, - NULL, NULL, NULL, 0, &msgid); - ldap_free_urldesc(ludp); - if(rc != LDAP_SUCCESS) { - failf(data, "LDAP local: ldap_search_ext %s", ldap_err2string(rc)); - return CURLE_LDAP_SEARCH_FAILED; + 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_xfer_setup1(data, CURL_XFER_RECV, -1, FALSE); + *done = TRUE; + } + } } - lr = calloc(1, sizeof(ldapreqinfo)); - if(!lr) - return CURLE_OUT_OF_MEMORY; - lr->msgid = msgid; - data->req.protop = lr; - Curl_setup_transfer(conn, FIRSTSOCKET, -1, FALSE, NULL, -1, NULL); - *done = TRUE; - return CURLE_OK; + return result; } -static CURLcode ldap_done(struct connectdata *conn, CURLcode res, - bool premature) +static CURLcode oldap_done(struct Curl_easy *data, CURLcode res, + bool premature) { - ldapreqinfo *lr = conn->data->req.protop; + struct connectdata *conn = data->conn; + struct ldapreqinfo *lr = data->req.p.ldap; (void)res; (void)premature; @@ -400,154 +928,153 @@ static CURLcode ldap_done(struct connectdata *conn, CURLcode res, if(lr) { /* if there was a search in progress, abandon it */ if(lr->msgid) { - ldapconninfo *li = conn->proto.generic; + struct ldapconninfo *li = conn->proto.ldapc; ldap_abandon_ext(li->ld, lr->msgid, NULL, NULL); lr->msgid = 0; } - conn->data->req.protop = NULL; + data->req.p.ldap = NULL; free(lr); } return CURLE_OK; } -static ssize_t ldap_recv(struct connectdata *conn, int sockindex, char *buf, - size_t len, CURLcode *err) +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) { - ldapconninfo *li = conn->proto.generic; - struct Curl_easy *data = conn->data; - ldapreqinfo *lr = data->req.protop; - int rc, ret; + 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; - LDAPMessage *ent; BerElement *ber = NULL; - struct timeval tv = {0, 1}; + 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_RECEIVED, &tv, &msg); + 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)); - *err = CURLE_RECV_ERROR; - return -1; + result = CURLE_RECV_ERROR; } - *err = CURLE_AGAIN; - ret = -1; + *err = result; - /* timed out */ + /* error or timed out */ if(!msg) - return ret; - - for(ent = ldap_first_message(li->ld, msg); ent; - ent = ldap_next_message(li->ld, ent)) { - struct berval bv, *bvals, **bvp = &bvals; - int binary = 0, msgtype; - CURLcode writeerr; - - msgtype = ldap_msgtype(ent); - if(msgtype == LDAP_RES_SEARCH_RESULT) { - int code; - char *info = NULL; - rc = ldap_parse_result(li->ld, ent, &code, NULL, &info, NULL, NULL, 0); - if(rc) { - failf(data, "LDAP local: search ldap_parse_result %s", - ldap_err2string(rc)); - *err = CURLE_LDAP_SEARCH_FAILED; - } - else if(code && code != LDAP_SIZELIMIT_EXCEEDED) { - failf(data, "LDAP remote: search failed %s %s", ldap_err2string(rc), - info ? info : ""); - *err = CURLE_LDAP_SEARCH_FAILED; - } - else { - /* successful */ - if(code == LDAP_SIZELIMIT_EXCEEDED) - infof(data, "There are more than %d entries\n", lr->nument); - data->req.size = data->req.bytecount; - *err = CURLE_OK; - ret = 0; - } - lr->msgid = 0; - ldap_memfree(info); + 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; } - else if(msgtype != LDAP_RES_SEARCH_ENTRY) - continue; + /* 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, ent, &ber, &bv); + rc = ldap_get_dn_ber(li->ld, msg, &ber, &bv); if(rc < 0) { - /* TODO: verify that this is really how this return code should be - handled */ - *err = CURLE_RECV_ERROR; - return -1; - } - writeerr = Curl_client_write(conn, CLIENTWRITE_BODY, (char *)"DN: ", 4); - if(writeerr) { - *err = writeerr; - return -1; - } - - writeerr = Curl_client_write(conn, CLIENTWRITE_BODY, (char *)bv.bv_val, - bv.bv_len); - if(writeerr) { - *err = writeerr; - return -1; + result = CURLE_RECV_ERROR; + break; } - writeerr = Curl_client_write(conn, CLIENTWRITE_BODY, (char *)"\n", 1); - if(writeerr) { - *err = writeerr; - return -1; - } - data->req.bytecount += bv.bv_len + 5; + 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, ent, ber, &bv, bvp); - rc == LDAP_SUCCESS; - rc = ldap_get_attribute_ber(li->ld, ent, ber, &bv, bvp)) { + 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 == NULL) break; + if(!bv.bv_val) + break; - if(bv.bv_len > 7 && !strncmp(bv.bv_val + bv.bv_len - 7, ";binary", 7)) - binary = 1; - else - binary = 0; + if(!bvals) { + result = client_write(data, STRCONST("\t"), bv.bv_val, bv.bv_len, + STRCONST(":\n")); + if(result) + break; + continue; + } - for(i=0; bvals[i].bv_val != NULL; i++) { - int binval = 0; - writeerr = Curl_client_write(conn, CLIENTWRITE_BODY, (char *)"\t", 1); - if(writeerr) { - *err = writeerr; - return -1; - } + binary = bv.bv_len > 7 && + !strncmp(bv.bv_val + bv.bv_len - 7, ";binary", 7); - writeerr = Curl_client_write(conn, CLIENTWRITE_BODY, (char *)bv.bv_val, - bv.bv_len); - if(writeerr) { - *err = writeerr; - return -1; - } + for(i = 0; bvals[i].bv_val != NULL; i++) { + int binval = 0; - writeerr = Curl_client_write(conn, CLIENTWRITE_BODY, (char *)":", 1); - if(writeerr) { - *err = writeerr; - return -1; - } - data->req.bytecount += bv.bv_len + 2; + 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(ISSPACE(bvals[i].bv_val[0]) || - ISSPACE(bvals[i].bv_val[bvals[i].bv_len-1])) + 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; jreq.bytecount += 2; - if(val_b64_sz > 0) { - writeerr = Curl_client_write(conn, CLIENTWRITE_BODY, val_b64, - val_b64_sz); - if(writeerr) { - *err = writeerr; - return -1; - } - free(val_b64); - data->req.bytecount += val_b64_sz; - } - } - else { - writeerr = Curl_client_write(conn, CLIENTWRITE_BODY, (char *)" ", 1); - if(writeerr) { - *err = writeerr; - return -1; - } - - writeerr = Curl_client_write(conn, CLIENTWRITE_BODY, bvals[i].bv_val, - bvals[i].bv_len); - if(writeerr) { - *err = writeerr; - return -1; - } - - data->req.bytecount += bvals[i].bv_len + 1; - } - writeerr = Curl_client_write(conn, CLIENTWRITE_BODY, (char *)"\n", 0); - if(writeerr) { - *err = writeerr; - return -1; + /* 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); } - - data->req.bytecount++; + else + result = client_write(data, STRCONST(" "), + bvals[i].bv_val, bvals[i].bv_len, + STRCONST("\n")); + if(result) + break; } + ber_memfree(bvals); - writeerr = Curl_client_write(conn, CLIENTWRITE_BODY, (char *)"\n", 0); - if(writeerr) { - *err = writeerr; - return -1; - } - data->req.bytecount++; - } - writeerr = Curl_client_write(conn, CLIENTWRITE_BODY, (char *)"\n", 0); - if(writeerr) { - *err = writeerr; - return -1; + bvals = NULL; + if(!result) + result = client_write(data, STRCONST("\n"), NULL, 0, NULL, 0); + if(result) + break; } - data->req.bytecount++; + 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); - return ret; + *err = result; + return result ? -1 : 0; } #ifdef USE_SSL @@ -648,7 +1139,7 @@ ldapsb_tls_remove(Sockbuf_IO_Desc *sbiod) return 0; } -/* We don't need to do anything because libcurl does it already */ +/* We do not need to do anything because libcurl does it already */ static int ldapsb_tls_close(Sockbuf_IO_Desc *sbiod) { @@ -661,8 +1152,8 @@ ldapsb_tls_ctrl(Sockbuf_IO_Desc *sbiod, int opt, void *arg) { (void)arg; if(opt == LBER_SB_OPT_DATA_READY) { - struct connectdata *conn = sbiod->sbiod_pvt; - return Curl_ssl_data_pending(conn, FIRSTSOCKET); + struct Curl_easy *data = sbiod->sbiod_pvt; + return Curl_conn_data_pending(data, FIRSTSOCKET); } return 0; } @@ -670,14 +1161,19 @@ ldapsb_tls_ctrl(Sockbuf_IO_Desc *sbiod, int opt, void *arg) static ber_slen_t ldapsb_tls_read(Sockbuf_IO_Desc *sbiod, void *buf, ber_len_t len) { - struct connectdata *conn = sbiod->sbiod_pvt; - ldapconninfo *li = conn->proto.generic; - ber_slen_t ret; - CURLcode err = CURLE_RECV_ERROR; - - ret = li->recv(conn, FIRSTSOCKET, buf, len, &err); - if(ret < 0 && err == CURLE_AGAIN) { - SET_SOCKERRNO(EWOULDBLOCK); + 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; } @@ -685,14 +1181,18 @@ ldapsb_tls_read(Sockbuf_IO_Desc *sbiod, void *buf, ber_len_t len) static ber_slen_t ldapsb_tls_write(Sockbuf_IO_Desc *sbiod, void *buf, ber_len_t len) { - struct connectdata *conn = sbiod->sbiod_pvt; - ldapconninfo *li = conn->proto.generic; - ber_slen_t ret; - CURLcode err = CURLE_SEND_ERROR; - - ret = li->send(conn, FIRSTSOCKET, buf, len, &err); - if(ret < 0 && err == CURLE_AGAIN) { - SET_SOCKERRNO(EWOULDBLOCK); + 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, FALSE, &err); + if(ret < 0 && err == CURLE_AGAIN) { + SET_SOCKERRNO(EWOULDBLOCK); + } + } } return ret; } diff --git a/contrib/curl/lib/optiontable.pl b/contrib/curl/lib/optiontable.pl new file mode 100755 index 0000000000..2727784e7b --- /dev/null +++ b/contrib/curl/lib/optiontable.pl @@ -0,0 +1,152 @@ +#!/usr/bin/env perl + +print <, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the 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[] = { +HEAD + ; + +my $lastnum=0; + +sub add { + my($opt, $type, $num)=@_; + my $name; + # remove all spaces from the type + $type =~ s/ //g; + my $ext = $type; + + if($opt =~ /OBSOLETE/) { + # skip obsolete options + next; + } + + if($opt =~ /^CURLOPT_(.*)/) { + $name=$1; + } + $ext =~ s/CURLOPTTYPE_//; + $ext =~ s/CBPOINT/CBPTR/; + $ext =~ s/POINT\z//; + $type = "CURLOT_$ext"; + + $opt{$name} = $opt; + $type{$name} = $type; + push @names, $name; + if($num < $lastnum) { + print STDERR "ERROR: $opt has bad number: $num < $lastnum\n"; + exit 2; + } + else { + $lastnum = $num; + } +} + + +my $fl; +while() { + my $l = $_; + if($fl) { + # continued deprecation + if($l =~ /(.*)\),/) { + $fl .= $1; + + # the end + my @p=split(/, */, $fl); + add($p[0], $p[1], $p[2]); + undef $fl; + } + else { + # another line to append + chomp $l; + $fl .= $l; + } + } + + if(/^ *CURLOPTDEPRECATED\((.*)/) { + $fl = $1; + chomp $fl; + } + + if(/^ *CURLOPT\(([^,]*), ([^,]*), (\d+)\)/) { + my($opt, $type, $num)=($1,$2,$3); + add($opt, $type, $num); + } + + # alias for an older option + # old = new + if(/^#define (CURLOPT_[^ ]*) *(CURLOPT_\S*)/) { + my ($o, $n)=($1, $2); + # skip obsolete ones + if(($n !~ /OBSOLETE/) && ($o !~ /OBSOLETE/)) { + $o =~ s/^CURLOPT_//; + $n =~ s/^CURLOPT_//; + $alias{$o} = $n; + push @names, $o, + } + } +} + + +for my $name (sort @names) { + my $oname = $name; + my $a = $alias{$name}; + my $flag = "0"; + if($a) { + $name = $alias{$name}; + $flag = "CURLOT_FLAG_ALIAS"; + } + $o = sprintf(" {\"%s\", %s, %s, %s},\n", + $oname, $opt{$name}, $type{$name}, $flag); + if(length($o) < 80) { + print $o; + } + else { + printf(" {\"%s\", %s,\n %s, %s},\n", + $oname, $opt{$name}, $type{$name}, $flag); + } +} + +print <, et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -18,6 +18,8 @@ * 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: @@ -75,29 +77,13 @@ #include "curl_setup.h" -#ifdef HAVE_LIMITS_H #include -#endif #include #include "strcase.h" #include "warnless.h" #include "parsedate.h" -const char * const Curl_wkday[] = -{"Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"}; -static const char * const weekday[] = -{ "Monday", "Tuesday", "Wednesday", "Thursday", - "Friday", "Saturday", "Sunday" }; -const char * const Curl_month[]= -{ "Jan", "Feb", "Mar", "Apr", "May", "Jun", - "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; - -struct tzinfo { - char name[5]; - int offset; /* +/- in minutes */ -}; - /* * parsedate() * @@ -116,11 +102,32 @@ static int parsedate(const char *date, time_t *output); #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 */ @@ -167,20 +174,20 @@ static const struct tzinfo tz[]= { 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 */ + {"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 */ + {"K", 10 * 60}, /* Kilo */ + {"L", 11 * 60}, /* Lima */ + {"M", 12 * 60}, /* Mike */ {"N", -1 * 60}, /* November */ {"O", -2 * 60}, /* Oscar */ {"P", -3 * 60}, /* Papa */ @@ -205,61 +212,60 @@ static int checkday(const char *check, size_t len) { int i; const char * const *what; - bool found= FALSE; if(len > 3) what = &weekday[0]; - else + else if(len == 3) what = &Curl_wkday[0]; - for(i=0; i<7; i++) { - if(strcasecompare(check, what[0])) { - found=TRUE; - break; - } + 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 found?i:-1; + return -1; } -static int checkmonth(const char *check) +static int checkmonth(const char *check, size_t len) { int i; - const char * const *what; - bool found= FALSE; + const char * const *what = &Curl_month[0]; + if(len != 3) + return -1; /* not a month */ - what = &Curl_month[0]; - for(i=0; i<12; i++) { - if(strcasecompare(check, what[0])) { - found=TRUE; - break; - } + for(i = 0; i < 12; i++) { + if(strncasecompare(check, what[0], 3)) + return i; what++; } - return found?i:-1; /* return the offset or -1, no real offset is -1 */ + 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 */ + of seconds or -1 if the timezone was not found/legal */ -static int checktz(const char *check) +static int checktz(const char *check, size_t len) { unsigned int i; - const struct tzinfo *what; - bool found= FALSE; - - what = tz; - for(i=0; i< sizeof(tz)/sizeof(tz[0]); i++) { - if(strcasecompare(check, what->name)) { - found=TRUE; - break; - } + 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 found?what->offset*60:-1; + return -1; } static void skip(const char **date) { - /* skip everything that aren't letters or digits */ + /* skip everything that are not letters or digits */ while(**date && !ISALNUM(**date)) (*date)++; } @@ -270,51 +276,68 @@ enum assume { DATE_TIME }; -/* this is a clone of 'struct tm' but with all fields we don't need or use - cut out */ -struct my_tm { - int tm_sec; - int tm_min; - int tm_hour; - int tm_mday; - int tm_mon; - int tm_year; -}; - -/* struct tm to time since epoch in GMT time zone. - * This is similar to the standard mktime function but for GMT only, and - * doesn't suffer from the various bugs and portability problems that - * some systems' implementations have. +/* + * time2epoch: time stamp to seconds since epoch in GMT time zone. Similar to + * mktime but for GMT only. */ -static time_t my_timegm(struct my_tm *tm) +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 month, year, leap_days; - - if(tm->tm_year < 70) - /* we don't support years before 1970 as they will cause this function - to return a negative value */ - return -1; + 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; +} - year = tm->tm_year + 1900; - month = tm->tm_mon; - if(month < 0) { - year += (11 - month) / 12; - month = 11 - (11 - month) % 12; - } - else if(month >= 12) { - year -= month / 12; - month = month % 12; +/* 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; +} - leap_days = year - (tm->tm_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 [month] + tm->tm_mday - 1) * 24 - + tm->tm_hour) * 60 + tm->tm_min) * 60 + tm->tm_sec; +/* 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; } /* @@ -328,55 +351,57 @@ static time_t my_timegm(struct my_tm *tm) * 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; - struct my_tm tm; + 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; + bool found = FALSE; skip(&date); if(ISALPHA(*date)) { /* a name coming up */ - char buf[32]=""; - size_t len; - if(sscanf(date, "%31[ABCDEFGHIJKLMNOPQRSTUVWXYZ" - "abcdefghijklmnopqrstuvwxyz]", buf)) - len = strlen(buf); - else - len = 0; - - if(wdaynum == -1) { - wdaynum = checkday(buf, len); - if(wdaynum != -1) - found = TRUE; - } - if(!found && (monnum == -1)) { - monnum = checkmonth(buf); - if(monnum != -1) - found = TRUE; + size_t len = 0; + const char *p = date; + while(ISALPHA(*p) && (len < NAME_LEN)) { + p++; + len++; } - if(!found && (tzoff == -1)) { - /* this just must be a time zone string */ - tzoff = checktz(buf); - if(tzoff != -1) - found = TRUE; - } + 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 */ @@ -386,30 +411,22 @@ static int parsedate(const char *date, time_t *output) /* a digit */ int val; char *end; - int len=0; if((secnum == -1) && - (3 == sscanf(date, "%02d:%02d:%02d%n", - &hournum, &minnum, &secnum, &len))) { - /* time stamp! */ - date += len; - } - else if((secnum == -1) && - (2 == sscanf(date, "%02d:%02d%n", &hournum, &minnum, &len))) { - /* time stamp without seconds */ - date += len; - secnum = 0; + match_time(date, &hournum, &minnum, &secnum, &end)) { + /* time stamp */ + date = end; } else { long lval; int error; int old_errno; - old_errno = ERRNO; - SET_ERRNO(0); + old_errno = errno; + errno = 0; lval = strtol(date, &end, 10); - error = ERRNO; - if(error != old_errno) - SET_ERRNO(old_errno); + error = errno; + if(errno != old_errno) + errno = old_errno; if(error) return PARSEDATE_FAIL; @@ -424,11 +441,11 @@ static int parsedate(const char *date, time_t *output) if((tzoff == -1) && ((end - date) == 4) && (val <= 1400) && - (indate< date) && + (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 + 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 @@ -438,8 +455,8 @@ static int parsedate(const char *date, time_t *output) tzoff = (val/100 * 60 + val%100)*60; /* the + and - prefix indicates the local time compared to GMT, - this we need ther reversed math to get what we want */ - tzoff = date[-1]=='+'?-tzoff:tzoff; + this we need their reversed math to get what we want */ + tzoff = date[-1]=='+' ? -tzoff : tzoff; } if(((end - date) == 8) && @@ -454,7 +471,7 @@ static int parsedate(const char *date, time_t *output) } if(!found && (dignext == DATE_MDAY) && (mdaynum == -1)) { - if((val > 0) && (val<32)) { + if((val > 0) && (val < 32)) { mdaynum = val; found = TRUE; } @@ -464,7 +481,7 @@ static int parsedate(const char *date, time_t *output) if(!found && (dignext == DATE_YEAR) && (yearnum == -1)) { yearnum = val; found = TRUE; - if(yearnum < 1900) { + if(yearnum < 100) { if(yearnum > 70) yearnum += 1900; else @@ -493,57 +510,74 @@ static int parsedate(const char *date, time_t *output) /* lacks vital info, fail */ return PARSEDATE_FAIL; -#if SIZEOF_TIME_T < 5 - /* 32 bit time_t can only hold dates to the beginning of 2038 */ - if(yearnum > 2037) { - *output = 0x7fffffff; - return PARSEDATE_LATER; +#ifdef HAVE_TIME_T_UNSIGNED + if(yearnum < 1970) { + /* only positive numbers cannot return earlier */ + *output = TIME_T_MIN; + return PARSEDATE_SOONER; } #endif - if(yearnum < 1970) { - *output = 0; +#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 */ - tm.tm_sec = secnum; - tm.tm_min = minnum; - tm.tm_hour = hournum; - tm.tm_mday = mdaynum; - tm.tm_mon = monnum; - tm.tm_year = yearnum - 1900; - - /* my_timegm() returns a time_t. time_t is often 32 bits, even on many - architectures that feature 64 bit 'long'. - - Some systems have 64 bit time_t and deal with years beyond 2038. However, - even on some of the systems with 64 bit time_t mktime() returns -1 for - dates beyond 03:14:07 UTC, January 19, 2038. (Such as AIX 5100-06) + /* time2epoch() returns a time_t. time_t is often 32 bits, sometimes even on + architectures that feature a 64 bits 'long' but ultimately time_t is the + correct data type to use. */ - t = my_timegm(&tm); - - /* time zone adjust (cast t to int to compare to negative one) */ - if(-1 != (int)t) { + t = time2epoch(secnum, minnum, hournum, mdaynum, monnum, yearnum); - /* Add the time zone diff between local time zone and GMT. */ - long delta = (long)(tzoff!=-1?tzoff:0); + /* Add the time zone diff between local time zone and GMT. */ + if(tzoff == -1) + tzoff = 0; - if((delta>0) && (t > LONG_MAX - delta)) { - *output = 0x7fffffff; - return PARSEDATE_LATER; /* time_t overflow */ - } - - t += delta; + 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) { @@ -551,14 +585,38 @@ time_t curl_getdate(const char *p, const time_t *now) 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: - case PARSEDATE_SOONER: + /* this returns the maximum time value */ return parsed; + default: + return -1; /* everything else is fail */ } - /* everything else is fail */ - return -1; + /* UNREACHABLE */ } /* @@ -574,6 +632,7 @@ CURLcode Curl_gmtime(time_t intime, struct tm *store) /* 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 */ diff --git a/contrib/curl/lib/parsedate.h b/contrib/curl/lib/parsedate.h index 2e59eb17c2..84c37f1675 100644 --- a/contrib/curl/lib/parsedate.h +++ b/contrib/curl/lib/parsedate.h @@ -7,11 +7,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2011, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -20,6 +20,8 @@ * 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]; @@ -27,5 +29,10 @@ extern const char * const Curl_month[12]; CURLcode Curl_gmtime(time_t intime, struct tm *store); -#endif /* HEADER_CURL_PARSEDATE_H */ +/* 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/contrib/curl/lib/pingpong.c b/contrib/curl/lib/pingpong.c index 7a99357279..bd0d3e3a2a 100644 --- a/contrib/curl/lib/pingpong.c +++ b/contrib/curl/lib/pingpong.c @@ -5,11 +5,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2016, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -18,6 +18,8 @@ * 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. * @@ -26,14 +28,15 @@ #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 "non-ascii.h" #include "vtls/vtls.h" +#include "strdup.h" /* The last 3 #include files should be in this order */ #include "curl_printf.h" @@ -44,14 +47,13 @@ /* Returns timeout in ms. 0 or negative number means the timeout has already triggered */ -time_t Curl_pp_state_timeout(struct pingpong *pp) +timediff_t Curl_pp_state_timeout(struct Curl_easy *data, + struct pingpong *pp, bool disconnecting) { - struct connectdata *conn = pp->conn; - struct Curl_easy *data=conn->data; - time_t timeout_ms; /* in milliseconds */ - time_t timeout2_ms; /* in milliseconds */ - long response_time= (data->set.server_response_timeout)? - data->set.server_response_timeout: pp->response_time; + 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 @@ -61,12 +63,12 @@ time_t Curl_pp_state_timeout(struct pingpong *pp) /* 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_tvdiff(Curl_tvnow(), pp->response); /* spent time */ + Curl_timediff(Curl_now(), pp->response); /* spent time */ - if(data->set.timeout) { + if(data->set.timeout && !disconnecting) { /* if timeout is requested, find out how much remaining time we have */ - timeout2_ms = data->set.timeout - /* timeout time */ - Curl_tvdiff(Curl_tvnow(), conn->now); /* spent time */ + 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); @@ -78,17 +80,18 @@ time_t Curl_pp_state_timeout(struct pingpong *pp) /* * Curl_pp_statemach() */ -CURLcode Curl_pp_statemach(struct pingpong *pp, bool block) +CURLcode Curl_pp_statemach(struct Curl_easy *data, + struct pingpong *pp, bool block, + bool disconnecting) { - struct connectdata *conn = pp->conn; + struct connectdata *conn = data->conn; curl_socket_t sock = conn->sock[FIRSTSOCKET]; int rc; - time_t interval_ms; - time_t timeout_ms = Curl_pp_state_timeout(pp); - struct Curl_easy *data=conn->data; + timediff_t interval_ms; + timediff_t timeout_ms = Curl_pp_state_timeout(data, pp, disconnecting); CURLcode result = CURLE_OK; - if(timeout_ms <=0) { + if(timeout_ms <= 0) { failf(data, "server response timeout"); return CURLE_OPERATION_TIMEDOUT; /* already too little time */ } @@ -101,26 +104,26 @@ CURLcode Curl_pp_statemach(struct pingpong *pp, bool block) else interval_ms = 0; /* immediate */ - if(Curl_ssl_data_pending(conn, FIRSTSOCKET)) + if(Curl_conn_data_pending(data, FIRSTSOCKET)) rc = 1; - else if(Curl_pp_moredata(pp)) + else if(pp->overflow) /* We are receiving and there is data in the cache so just read it */ rc = 1; - else if(!pp->sendleft && Curl_ssl_data_pending(conn, FIRSTSOCKET)) + 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 */ + rc = Curl_socket_check(pp->sendleft ? CURL_SOCKET_BAD : sock, /* reading */ CURL_SOCKET_BAD, - pp->sendleft?sock:CURL_SOCKET_BAD, /* writing */ + 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(conn)) + /* if we did not wait, we do not have to spend time on this now */ + if(Curl_pgrsUpdate(data)) result = CURLE_ABORTED_BY_CALLBACK; else - result = Curl_speedcheck(data, Curl_tvnow()); + result = Curl_speedcheck(data, Curl_now()); if(result) return result; @@ -131,7 +134,7 @@ CURLcode Curl_pp_statemach(struct pingpong *pp, bool block) result = CURLE_OUT_OF_MEMORY; } else if(rc) - result = pp->statemach_act(conn); + result = pp->statemachine(data, data->conn); return result; } @@ -139,96 +142,88 @@ CURLcode Curl_pp_statemach(struct pingpong *pp, bool block) /* initialize stuff to prepare for reading a fresh new response */ void Curl_pp_init(struct pingpong *pp) { - struct connectdata *conn = pp->conn; pp->nread_resp = 0; - pp->linestart_resp = conn->data->state.buffer; + pp->response = Curl_now(); /* start response time-out now! */ pp->pending_resp = TRUE; - pp->response = Curl_tvnow(); /* start response time-out now! */ + Curl_dyn_init(&pp->sendbuf, DYN_PINGPPONG_CMD); + Curl_dyn_init(&pp->recvbuf, DYN_PINGPPONG_CMD); } - - /*********************************************************************** * * Curl_pp_vsendf() * - * Send the formated string as a command to a pingpong server. Note that + * 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 pingpong *pp, +CURLcode Curl_pp_vsendf(struct Curl_easy *data, + struct pingpong *pp, const char *fmt, va_list args) { - ssize_t bytes_written; + size_t bytes_written = 0; size_t write_len; - char *fmt_crlf; char *s; CURLcode result; - struct connectdata *conn = pp->conn; - struct Curl_easy *data = conn->data; + struct connectdata *conn = data->conn; #ifdef HAVE_GSSAPI - enum protection_level data_sec = conn->data_prot; + enum protection_level data_sec; #endif DEBUGASSERT(pp->sendleft == 0); DEBUGASSERT(pp->sendsize == 0); DEBUGASSERT(pp->sendthis == NULL); - fmt_crlf = aprintf("%s\r\n", fmt); /* append a trailing CRLF */ - if(!fmt_crlf) - return CURLE_OUT_OF_MEMORY; - - s = vaprintf(fmt_crlf, args); /* trailing CRLF appended */ - free(fmt_crlf); - if(!s) - return CURLE_OUT_OF_MEMORY; + if(!conn) + /* cannot send without a connection! */ + return CURLE_SEND_ERROR; - bytes_written = 0; - write_len = strlen(s); - - Curl_pp_init(pp); + Curl_dyn_reset(&pp->sendbuf); + result = Curl_dyn_vaddf(&pp->sendbuf, fmt, args); + if(result) + return result; - result = Curl_convert_to_network(data, s, write_len); - /* Curl_convert_to_network calls failf if unsuccessful */ - if(result) { - free(s); + /* append CRLF */ + result = Curl_dyn_addn(&pp->sendbuf, "\r\n", 2); + if(result) return result; - } + + pp->pending_resp = TRUE; + write_len = Curl_dyn_len(&pp->sendbuf); + s = Curl_dyn_ptr(&pp->sendbuf); #ifdef HAVE_GSSAPI conn->data_prot = PROT_CMD; #endif - result = Curl_write(conn, conn->sock[FIRSTSOCKET], s, write_len, - &bytes_written); + result = Curl_conn_send(data, FIRSTSOCKET, s, write_len, FALSE, + &bytes_written); + if(result == CURLE_AGAIN) { + bytes_written = 0; + } + else if(result) + return result; #ifdef HAVE_GSSAPI + data_sec = conn->data_prot; DEBUGASSERT(data_sec > PROT_NONE && data_sec < PROT_LAST); - conn->data_prot = data_sec; + conn->data_prot = (unsigned char)data_sec; #endif - if(result) { - free(s); - return result; - } + Curl_debug(data, CURLINFO_HEADER_OUT, s, bytes_written); - if(conn->data->set.verbose) - Curl_debug(conn->data, CURLINFO_HEADER_OUT, - s, (size_t)bytes_written, conn); - - if(bytes_written != (ssize_t)write_len) { + if(bytes_written != 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 { - free(s); pp->sendthis = NULL; pp->sendleft = pp->sendsize = 0; - pp->response = Curl_tvnow(); + pp->response = Curl_now(); } return CURLE_OK; @@ -239,226 +234,161 @@ CURLcode Curl_pp_vsendf(struct pingpong *pp, * * Curl_pp_sendf() * - * Send the formated string as a command to a pingpong server. Note that + * 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 pingpong *pp, +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(pp, fmt, ap); + result = Curl_pp_vsendf(data, pp, fmt, ap); va_end(ap); return result; } +static CURLcode pingpong_read(struct Curl_easy *data, + int sockindex, + char *buffer, + size_t buflen, + ssize_t *nread) +{ + CURLcode result; +#ifdef HAVE_GSSAPI + enum protection_level prot = data->conn->data_prot; + data->conn->data_prot = PROT_CLEAR; +#endif + result = Curl_conn_recv(data, sockindex, buffer, buflen, nread); +#ifdef HAVE_GSSAPI + DEBUGASSERT(prot > PROT_NONE && prot < PROT_LAST); + data->conn->data_prot = (unsigned char)prot; +#endif + return result; +} + /* * Curl_pp_readresp() * * Reads a piece of a server response. */ -CURLcode Curl_pp_readresp(curl_socket_t sockfd, +CURLcode Curl_pp_readresp(struct Curl_easy *data, + int sockindex, 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 = pp->conn; - struct Curl_easy *data = conn->data; - char * const buf = data->state.buffer; + struct connectdata *conn = data->conn; CURLcode result = CURLE_OK; + ssize_t gotbytes; + char buffer[900]; *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_respcache) { - /* 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. - */ - DEBUGASSERT((ptr+pp->cache_size) <= (buf+BUFSIZE+1)); - 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 */ + do { + gotbytes = 0; + if(pp->nfinal) { + /* a previous call left this many bytes in the beginning of the buffer as + that was the final line; now ditch that */ + size_t full = Curl_dyn_len(&pp->recvbuf); + + /* trim off the "final" leading part */ + Curl_dyn_tail(&pp->recvbuf, full - pp->nfinal); + + pp->nfinal = 0; /* now gone */ } - else { -#ifdef HAVE_GSSAPI - enum protection_level prot = conn->data_prot; - conn->data_prot = PROT_CLEAR; -#endif - DEBUGASSERT((ptr+BUFSIZE-pp->nread_resp) <= (buf+BUFSIZE+1)); - result = Curl_read(conn, sockfd, ptr, BUFSIZE-pp->nread_resp, - &gotbytes); -#ifdef HAVE_GSSAPI - DEBUGASSERT(prot > PROT_NONE && prot < PROT_LAST); - conn->data_prot = prot; -#endif + if(!pp->overflow) { + result = pingpong_read(data, sockindex, buffer, sizeof(buffer), + &gotbytes); if(result == CURLE_AGAIN) - return CURLE_OK; /* return */ - - if(!result && (gotbytes > 0)) - /* convert from the network encoding */ - result = Curl_convert_from_network(data, ptr, gotbytes); - /* Curl_convert_from_network calls failf if unsuccessful */ + return CURLE_OK; if(result) - /* Set outer result variable to this error. */ - keepon = FALSE; - } + return result; - if(!keepon) - ; - else if(gotbytes <= 0) { - keepon = FALSE; - result = CURLE_RECV_ERROR; - failf(data, "response reading failed"); - } - 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; + if(gotbytes <= 0) { + failf(data, "response reading failed (errno: %d)", SOCKERRNO); + return CURLE_RECV_ERROR; + } - data->req.headerbytecount += (long)gotbytes; + result = Curl_dyn_addn(&pp->recvbuf, buffer, gotbytes); + if(result) + return result; + + 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 */ + } + + do { + char *line = Curl_dyn_ptr(&pp->recvbuf); + char *nl = memchr(line, '\n', Curl_dyn_len(&pp->recvbuf)); + if(nl) { + /* a newline is CRLF in pp-talk, so the CR is ignored as + the line is not really terminated until the LF comes */ + size_t length = nl - line + 1; - /* output debug output if that is requested */ + /* output debug output if that is requested */ #ifdef HAVE_GSSAPI - if(!conn->sec_complete) + if(!conn->sec_complete) #endif - if(data->set.verbose) - Curl_debug(data, CURLINFO_HEADER_IN, - pp->linestart_resp, (size_t)perline, conn); - - /* - * 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(conn, CLIENTWRITE_HEADER, - pp->linestart_resp, perline); - if(result) - return result; - - if(pp->endofresp(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 zero terminate, for old times sake */ - size_t n = ptr - pp->linestart_resp; - memmove(buf, pp->linestart_resp, n); - buf[n]=0; /* zero 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; + Curl_debug(data, CURLINFO_HEADER_IN, line, length); + + /* + * 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, line, length); + if(result) + return result; + + if(pp->endofresp(data, conn, line, length, code)) { + /* When at "end of response", keep the endofresp line first in the + buffer since it will be accessed outside (by pingpong + parsers). Store the overflow counter to inform about additional + data in this buffer after the endofresp line. */ + pp->nfinal = length; + if(Curl_dyn_len(&pp->recvbuf) > length) + pp->overflow = Curl_dyn_len(&pp->recvbuf) - length; + else + pp->overflow = 0; + *size = pp->nread_resp; /* size of the response */ + pp->nread_resp = 0; /* restart */ + gotbytes = 0; /* force break out of outer loop */ + break; } - } - - 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\n", - (int)clipamount)); - } - else if(keepon) { - - if((perline == gotbytes) && (gotbytes > BUFSIZE/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\n", 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 > BUFSIZE/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); + if(Curl_dyn_len(&pp->recvbuf) > length) + /* keep the remaining piece */ + Curl_dyn_tail((&pp->recvbuf), Curl_dyn_len(&pp->recvbuf) - length); else - return CURLE_OUT_OF_MEMORY; + Curl_dyn_reset(&pp->recvbuf); } - 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; + else { + /* without a newline, there is no overflow */ + pp->overflow = 0; + break; } - } /* there was data */ + } while(1); /* while there is buffer left to scan */ - } /* while there's buffer left and loop is requested */ + } while(gotbytes == sizeof(buffer)); pp->pending_resp = FALSE; return result; } -int Curl_pp_getsock(struct pingpong *pp, - curl_socket_t *socks, - int numsocks) +int Curl_pp_getsock(struct Curl_easy *data, + struct pingpong *pp, curl_socket_t *socks) { - struct connectdata *conn = pp->conn; - - if(!numsocks) - return GETSOCK_BLANK; - + struct connectdata *conn = data->conn; socks[0] = conn->sock[FIRSTSOCKET]; if(pp->sendleft) { @@ -470,41 +400,55 @@ int Curl_pp_getsock(struct pingpong *pp, return GETSOCK_READSOCK(0); } -CURLcode Curl_pp_flushsend(struct pingpong *pp) +bool Curl_pp_needs_flush(struct Curl_easy *data, + struct pingpong *pp) +{ + (void)data; + return pp->sendleft > 0; +} + +CURLcode Curl_pp_flushsend(struct Curl_easy *data, + struct pingpong *pp) { /* we have a piece of a command still left to send */ - struct connectdata *conn = pp->conn; - ssize_t written; - curl_socket_t sock = conn->sock[FIRSTSOCKET]; - CURLcode result = Curl_write(conn, sock, pp->sendthis + pp->sendsize - - pp->sendleft, pp->sendleft, &written); + size_t written; + CURLcode result; + + if(!Curl_pp_needs_flush(data, pp)) + return CURLE_OK; + + result = Curl_conn_send(data, FIRSTSOCKET, + pp->sendthis + pp->sendsize - pp->sendleft, + pp->sendleft, FALSE, &written); + if(result == CURLE_AGAIN) { + result = CURLE_OK; + written = 0; + } if(result) return result; - if(written != (ssize_t)pp->sendleft) { + if(written != pp->sendleft) { /* only a fraction was sent */ pp->sendleft -= written; } else { - free(pp->sendthis); - pp->sendthis=NULL; + pp->sendthis = NULL; pp->sendleft = pp->sendsize = 0; - pp->response = Curl_tvnow(); + pp->response = Curl_now(); } return CURLE_OK; } CURLcode Curl_pp_disconnect(struct pingpong *pp) { - free(pp->cache); - pp->cache = NULL; + Curl_dyn_free(&pp->sendbuf); + Curl_dyn_free(&pp->recvbuf); return CURLE_OK; } bool Curl_pp_moredata(struct pingpong *pp) { - return (!pp->sendleft && pp->cache && pp->nread_resp < pp->cache_size) ? - TRUE : FALSE; + return (!pp->sendleft && Curl_dyn_len(&pp->recvbuf) > pp->nfinal); } #endif diff --git a/contrib/curl/lib/pingpong.h b/contrib/curl/lib/pingpong.h index 500100ae43..72239ff059 100644 --- a/contrib/curl/lib/pingpong.h +++ b/contrib/curl/lib/pingpong.h @@ -7,11 +7,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2013, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -20,6 +20,8 @@ * 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" @@ -33,10 +35,9 @@ struct connectdata; typedef enum { - FTPTRANSFER_BODY, /* yes do transfer a body */ - FTPTRANSFER_INFO, /* do still go through to get info/headers */ - FTPTRANSFER_NONE, /* don't get anything and don't get info */ - FTPTRANSFER_LAST /* end of list marker, never used */ + PPTRANSFER_BODY, /* yes do transfer a body */ + PPTRANSFER_INFO, /* do still go through to get info/headers */ + PPTRANSFER_NONE /* do not get anything and do not get info */ } curl_pp_transfer; /* @@ -46,96 +47,107 @@ typedef enum { * 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 */ + char *sendthis; /* 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 timeval response; /* set to Curl_tvnow() when a command has been sent - off, used to time-out response reading */ - long response_time; /* When no timeout is given, this is the amount of - milliseconds we await for a server response. */ - - struct connectdata *conn; /* points to the connectdata struct that this - belongs to */ + 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; + struct dynbuf recvbuf; + size_t overflow; /* number of bytes left after a final response line */ + size_t nfinal; /* number of bytes in the final response line, which + after a match is first in the receice buffer */ /* Function pointers the protocols MUST implement and provide for the pingpong layer to function */ - CURLcode (*statemach_act)(struct connectdata *conn); - - bool (*endofresp)(struct connectdata *conn, char *ptr, size_t len, - int *code); + 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. + * socket if there is no traffic. */ -CURLcode Curl_pp_statemach(struct pingpong *pp, bool block); +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 pingpong *pp); /* Returns timeout in ms. 0 or negative number means the timeout has already triggered */ -time_t Curl_pp_state_timeout(struct pingpong *pp); +timediff_t Curl_pp_state_timeout(struct Curl_easy *data, + struct pingpong *pp, bool disconnecting); /*********************************************************************** * * Curl_pp_sendf() * - * Send the formated string as a command to a pingpong server. Note that + * 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 pingpong *pp, - const char *fmt, ...); +CURLcode Curl_pp_sendf(struct Curl_easy *data, + struct pingpong *pp, + const char *fmt, ...) CURL_PRINTF(3, 4); /*********************************************************************** * * Curl_pp_vsendf() * - * Send the formated string as a command to a pingpong server. Note that + * 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 pingpong *pp, +CURLcode Curl_pp_vsendf(struct Curl_easy *data, + struct pingpong *pp, const char *fmt, - va_list args); + va_list args) CURL_PRINTF(3, 0); /* * Curl_pp_readresp() * * Reads a piece of a server response. */ -CURLcode Curl_pp_readresp(curl_socket_t sockfd, +CURLcode Curl_pp_readresp(struct Curl_easy *data, + int sockindex, struct pingpong *pp, int *code, /* return the server code if done */ size_t *size); /* size of the response */ +bool Curl_pp_needs_flush(struct Curl_easy *data, + struct pingpong *pp); -CURLcode Curl_pp_flushsend(struct pingpong *pp); +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 pingpong *pp, curl_socket_t *socks, - int numsocks); +int Curl_pp_getsock(struct Curl_easy *data, struct pingpong *pp, + curl_socket_t *socks); /*********************************************************************** diff --git a/contrib/curl/lib/pipeline.c b/contrib/curl/lib/pipeline.c deleted file mode 100644 index 40a5e82d83..0000000000 --- a/contrib/curl/lib/pipeline.c +++ /dev/null @@ -1,439 +0,0 @@ -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) 2013, Linus Nielsen Feltzing, - * Copyright (C) 2013-2016, Daniel Stenberg, , et al. - * - * This software is licensed as described in the file COPYING, which - * you should have received as part of this distribution. The terms - * are also available at https://curl.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. - * - ***************************************************************************/ - -#include "curl_setup.h" - -#include - -#include "urldata.h" -#include "url.h" -#include "progress.h" -#include "multiif.h" -#include "pipeline.h" -#include "sendf.h" -#include "strcase.h" - -#include "curl_memory.h" -/* The last #include file should be: */ -#include "memdebug.h" - -struct site_blacklist_entry { - char *hostname; - unsigned short port; -}; - -static void site_blacklist_llist_dtor(void *user, void *element) -{ - struct site_blacklist_entry *entry = element; - (void)user; - - Curl_safefree(entry->hostname); - free(entry); -} - -static void server_blacklist_llist_dtor(void *user, void *element) -{ - (void)user; - free(element); -} - -bool Curl_pipeline_penalized(struct Curl_easy *data, - struct connectdata *conn) -{ - if(data) { - bool penalized = FALSE; - curl_off_t penalty_size = - Curl_multi_content_length_penalty_size(data->multi); - curl_off_t chunk_penalty_size = - Curl_multi_chunk_length_penalty_size(data->multi); - curl_off_t recv_size = -2; /* Make it easy to spot in the log */ - - /* Find the head of the recv pipe, if any */ - if(conn->recv_pipe && conn->recv_pipe->head) { - struct Curl_easy *recv_handle = conn->recv_pipe->head->ptr; - - recv_size = recv_handle->req.size; - - if(penalty_size > 0 && recv_size > penalty_size) - penalized = TRUE; - } - - if(chunk_penalty_size > 0 && - (curl_off_t)conn->chunk.datasize > chunk_penalty_size) - penalized = TRUE; - - infof(data, "Conn: %ld (%p) Receive pipe weight: (%" - CURL_FORMAT_CURL_OFF_T "/%zu), penalized: %s\n", - conn->connection_id, (void *)conn, recv_size, - conn->chunk.datasize, penalized?"TRUE":"FALSE"); - return penalized; - } - return FALSE; -} - -static CURLcode addHandleToPipeline(struct Curl_easy *data, - struct curl_llist *pipeline) -{ - if(!Curl_llist_insert_next(pipeline, pipeline->tail, data)) - return CURLE_OUT_OF_MEMORY; - return CURLE_OK; -} - - -CURLcode Curl_add_handle_to_pipeline(struct Curl_easy *handle, - struct connectdata *conn) -{ - struct curl_llist_element *sendhead = conn->send_pipe->head; - struct curl_llist *pipeline; - CURLcode result; - - pipeline = conn->send_pipe; - - result = addHandleToPipeline(handle, pipeline); - - if(pipeline == conn->send_pipe && sendhead != conn->send_pipe->head) { - /* this is a new one as head, expire it */ - Curl_pipeline_leave_write(conn); /* not in use yet */ - Curl_expire(conn->send_pipe->head->ptr, 0); - } - -#if 0 /* enable for pipeline debugging */ - print_pipeline(conn); -#endif - - return result; -} - -/* Move this transfer from the sending list to the receiving list. - - Pay special attention to the new sending list "leader" as it needs to get - checked to update what sockets it acts on. - -*/ -void Curl_move_handle_from_send_to_recv_pipe(struct Curl_easy *handle, - struct connectdata *conn) -{ - struct curl_llist_element *curr; - - curr = conn->send_pipe->head; - while(curr) { - if(curr->ptr == handle) { - Curl_llist_move(conn->send_pipe, curr, - conn->recv_pipe, conn->recv_pipe->tail); - - if(conn->send_pipe->head) { - /* Since there's a new easy handle at the start of the send pipeline, - set its timeout value to 1ms to make it trigger instantly */ - Curl_pipeline_leave_write(conn); /* not used now */ -#ifdef DEBUGBUILD - infof(conn->data, "%p is at send pipe head B!\n", - (void *)conn->send_pipe->head->ptr); -#endif - Curl_expire(conn->send_pipe->head->ptr, 0); - } - - /* The receiver's list is not really interesting here since either this - handle is now first in the list and we'll deal with it soon, or - another handle is already first and thus is already taken care of */ - - break; /* we're done! */ - } - curr = curr->next; - } -} - -bool Curl_pipeline_site_blacklisted(struct Curl_easy *handle, - struct connectdata *conn) -{ - if(handle->multi) { - struct curl_llist *blacklist = - Curl_multi_pipelining_site_bl(handle->multi); - - if(blacklist) { - struct curl_llist_element *curr; - - curr = blacklist->head; - while(curr) { - struct site_blacklist_entry *site; - - site = curr->ptr; - if(strcasecompare(site->hostname, conn->host.name) && - site->port == conn->remote_port) { - infof(handle, "Site %s:%d is pipeline blacklisted\n", - conn->host.name, conn->remote_port); - return TRUE; - } - curr = curr->next; - } - } - } - return FALSE; -} - -CURLMcode Curl_pipeline_set_site_blacklist(char **sites, - struct curl_llist **list_ptr) -{ - struct curl_llist *old_list = *list_ptr; - struct curl_llist *new_list = NULL; - - if(sites) { - new_list = Curl_llist_alloc((curl_llist_dtor) site_blacklist_llist_dtor); - if(!new_list) - return CURLM_OUT_OF_MEMORY; - - /* Parse the URLs and populate the list */ - while(*sites) { - char *hostname; - char *port; - struct site_blacklist_entry *entry; - - hostname = strdup(*sites); - if(!hostname) { - Curl_llist_destroy(new_list, NULL); - return CURLM_OUT_OF_MEMORY; - } - - entry = malloc(sizeof(struct site_blacklist_entry)); - if(!entry) { - free(hostname); - Curl_llist_destroy(new_list, NULL); - return CURLM_OUT_OF_MEMORY; - } - - port = strchr(hostname, ':'); - if(port) { - *port = '\0'; - port++; - entry->port = (unsigned short)strtol(port, NULL, 10); - } - else { - /* Default port number for HTTP */ - entry->port = 80; - } - - entry->hostname = hostname; - - if(!Curl_llist_insert_next(new_list, new_list->tail, entry)) { - site_blacklist_llist_dtor(NULL, entry); - Curl_llist_destroy(new_list, NULL); - return CURLM_OUT_OF_MEMORY; - } - - sites++; - } - } - - /* Free the old list */ - if(old_list) { - Curl_llist_destroy(old_list, NULL); - } - - /* This might be NULL if sites == NULL, i.e the blacklist is cleared */ - *list_ptr = new_list; - - return CURLM_OK; -} - -bool Curl_pipeline_server_blacklisted(struct Curl_easy *handle, - char *server_name) -{ - if(handle->multi && server_name) { - struct curl_llist *blacklist = - Curl_multi_pipelining_server_bl(handle->multi); - - if(blacklist) { - struct curl_llist_element *curr; - - curr = blacklist->head; - while(curr) { - char *bl_server_name; - - bl_server_name = curr->ptr; - if(strncasecompare(bl_server_name, server_name, - strlen(bl_server_name))) { - infof(handle, "Server %s is blacklisted\n", server_name); - return TRUE; - } - curr = curr->next; - } - } - - DEBUGF(infof(handle, "Server %s is not blacklisted\n", server_name)); - } - return FALSE; -} - -CURLMcode Curl_pipeline_set_server_blacklist(char **servers, - struct curl_llist **list_ptr) -{ - struct curl_llist *old_list = *list_ptr; - struct curl_llist *new_list = NULL; - - if(servers) { - new_list = Curl_llist_alloc((curl_llist_dtor) server_blacklist_llist_dtor); - if(!new_list) - return CURLM_OUT_OF_MEMORY; - - /* Parse the URLs and populate the list */ - while(*servers) { - char *server_name; - - server_name = strdup(*servers); - if(!server_name) { - Curl_llist_destroy(new_list, NULL); - return CURLM_OUT_OF_MEMORY; - } - - if(!Curl_llist_insert_next(new_list, new_list->tail, server_name)) { - Curl_llist_destroy(new_list, NULL); - Curl_safefree(server_name); - return CURLM_OUT_OF_MEMORY; - } - - servers++; - } - } - - /* Free the old list */ - if(old_list) { - Curl_llist_destroy(old_list, NULL); - } - - /* This might be NULL if sites == NULL, i.e the blacklist is cleared */ - *list_ptr = new_list; - - return CURLM_OK; -} - -static bool pipe_head(struct Curl_easy *data, - struct curl_llist *pipeline) -{ - if(pipeline) { - struct curl_llist_element *curr = pipeline->head; - if(curr) - return (curr->ptr == data) ? TRUE : FALSE; - } - return FALSE; -} - -/* returns TRUE if the given handle is head of the recv pipe */ -bool Curl_recvpipe_head(struct Curl_easy *data, - struct connectdata *conn) -{ - return pipe_head(data, conn->recv_pipe); -} - -/* returns TRUE if the given handle is head of the send pipe */ -bool Curl_sendpipe_head(struct Curl_easy *data, - struct connectdata *conn) -{ - return pipe_head(data, conn->send_pipe); -} - - -/* - * Check if the write channel is available and this handle as at the head, - * then grab the channel and return TRUE. - * - * If not available, return FALSE. - */ - -bool Curl_pipeline_checkget_write(struct Curl_easy *data, - struct connectdata *conn) -{ - if(conn->bits.multiplex) - /* when multiplexing, we can use it at once */ - return TRUE; - - if(!conn->writechannel_inuse && Curl_sendpipe_head(data, conn)) { - /* Grab the channel */ - conn->writechannel_inuse = TRUE; - return TRUE; - } - return FALSE; -} - - -/* - * Check if the read channel is available and this handle as at the head, then - * grab the channel and return TRUE. - * - * If not available, return FALSE. - */ - -bool Curl_pipeline_checkget_read(struct Curl_easy *data, - struct connectdata *conn) -{ - if(conn->bits.multiplex) - /* when multiplexing, we can use it at once */ - return TRUE; - - if(!conn->readchannel_inuse && Curl_recvpipe_head(data, conn)) { - /* Grab the channel */ - conn->readchannel_inuse = TRUE; - return TRUE; - } - return FALSE; -} - -/* - * The current user of the pipeline write channel gives it up. - */ -void Curl_pipeline_leave_write(struct connectdata *conn) -{ - conn->writechannel_inuse = FALSE; -} - -/* - * The current user of the pipeline read channel gives it up. - */ -void Curl_pipeline_leave_read(struct connectdata *conn) -{ - conn->readchannel_inuse = FALSE; -} - - -#if 0 -void print_pipeline(struct connectdata *conn) -{ - struct curl_llist_element *curr; - struct connectbundle *cb_ptr; - struct Curl_easy *data = conn->data; - - cb_ptr = conn->bundle; - - if(cb_ptr) { - curr = cb_ptr->conn_list->head; - while(curr) { - conn = curr->ptr; - infof(data, "- Conn %ld (%p) send_pipe: %zu, recv_pipe: %zu\n", - conn->connection_id, - (void *)conn, - conn->send_pipe->size, - conn->recv_pipe->size); - curr = curr->next; - } - } -} - -#endif diff --git a/contrib/curl/lib/pipeline.h b/contrib/curl/lib/pipeline.h deleted file mode 100644 index a64f710251..0000000000 --- a/contrib/curl/lib/pipeline.h +++ /dev/null @@ -1,56 +0,0 @@ -#ifndef HEADER_CURL_PIPELINE_H -#define HEADER_CURL_PIPELINE_H -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) 2015, Daniel Stenberg, , et al. - * Copyright (C) 2013 - 2014, Linus Nielsen Feltzing, - * - * This software is licensed 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. - * - ***************************************************************************/ - -CURLcode Curl_add_handle_to_pipeline(struct Curl_easy *handle, - struct connectdata *conn); -void Curl_move_handle_from_send_to_recv_pipe(struct Curl_easy *handle, - struct connectdata *conn); -bool Curl_pipeline_penalized(struct Curl_easy *data, - struct connectdata *conn); - -bool Curl_pipeline_site_blacklisted(struct Curl_easy *handle, - struct connectdata *conn); - -CURLMcode Curl_pipeline_set_site_blacklist(char **sites, - struct curl_llist **list_ptr); - -bool Curl_pipeline_server_blacklisted(struct Curl_easy *handle, - char *server_name); - -CURLMcode Curl_pipeline_set_server_blacklist(char **servers, - struct curl_llist **list_ptr); - -bool Curl_pipeline_checkget_write(struct Curl_easy *data, - struct connectdata *conn); -bool Curl_pipeline_checkget_read(struct Curl_easy *data, - struct connectdata *conn); -void Curl_pipeline_leave_write(struct connectdata *conn); -void Curl_pipeline_leave_read(struct connectdata *conn); -bool Curl_recvpipe_head(struct Curl_easy *data, - struct connectdata *conn); -bool Curl_sendpipe_head(struct Curl_easy *data, - struct connectdata *conn); - -#endif /* HEADER_CURL_PIPELINE_H */ diff --git a/contrib/curl/lib/pop3.c b/contrib/curl/lib/pop3.c index 3feb3be833..1f5334d917 100644 --- a/contrib/curl/lib/pop3.c +++ b/contrib/curl/lib/pop3.c @@ -5,11 +5,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2017, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -18,6 +18,8 @@ * 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 @@ -30,6 +32,7 @@ * 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 * ***************************************************************************/ @@ -44,9 +47,6 @@ #ifdef HAVE_ARPA_INET_H #include #endif -#ifdef HAVE_UTSNAME_H -#include -#endif #ifdef HAVE_NETDB_H #include #endif @@ -55,11 +55,6 @@ #include #endif -#if (defined(NETWARE) && defined(__NOVELL_LIBC__)) -#undef in_addr_t -#define in_addr_t unsigned long -#endif - #include #include "urldata.h" #include "sendf.h" @@ -73,45 +68,60 @@ #include "strtoofft.h" #include "strcase.h" #include "vtls/vtls.h" +#include "cfilters.h" #include "connect.h" -#include "strerror.h" #include "select.h" #include "multiif.h" #include "url.h" +#include "bufref.h" #include "curl_sasl.h" #include "curl_md5.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" +#ifndef ARRAYSIZE +#define ARRAYSIZE(A) (sizeof(A)/sizeof((A)[0])) +#endif + /* Local API functions */ -static CURLcode pop3_regular_transfer(struct connectdata *conn, bool *done); -static CURLcode pop3_do(struct connectdata *conn, bool *done); -static CURLcode pop3_done(struct connectdata *conn, CURLcode status, +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 connectdata *conn, bool *done); -static CURLcode pop3_disconnect(struct connectdata *conn, bool dead); -static CURLcode pop3_multi_statemach(struct connectdata *conn, bool *done); -static int pop3_getsock(struct connectdata *conn, curl_socket_t *socks, - int numsocks); -static CURLcode pop3_doing(struct connectdata *conn, bool *dophase_done); -static CURLcode pop3_setup_connection(struct connectdata *conn); +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 connectdata *conn); -static CURLcode pop3_parse_custom_request(struct connectdata *conn); -static CURLcode pop3_perform_auth(struct connectdata *conn, const char *mech, - const char *initresp); -static CURLcode pop3_continue_auth(struct connectdata *conn, const char *resp); -static void pop3_get_message(char *buffer, char **outptr); +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); + +/* This function scans the body after the end-of-body and writes everything + * until the end is found */ +static CURLcode pop3_write(struct Curl_easy *data, + const char *str, size_t nread, bool is_eos); /* * POP3 protocol handler. */ const struct Curl_handler Curl_handler_pop3 = { - "POP3", /* scheme */ + "pop3", /* scheme */ pop3_setup_connection, /* setup_connection */ pop3_do, /* do_it */ pop3_done, /* done */ @@ -124,9 +134,13 @@ const struct Curl_handler Curl_handler_pop3 = { ZERO_NULL, /* domore_getsock */ ZERO_NULL, /* perform_getsock */ pop3_disconnect, /* disconnect */ - ZERO_NULL, /* readwrite */ + pop3_write, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ + ZERO_NULL, /* connection_check */ + ZERO_NULL, /* attach connection */ PORT_POP3, /* defport */ CURLPROTO_POP3, /* protocol */ + CURLPROTO_POP3, /* family */ PROTOPT_CLOSEACTION | PROTOPT_NOURLQUERY | /* flags */ PROTOPT_URLOPTIONS }; @@ -137,7 +151,7 @@ const struct Curl_handler Curl_handler_pop3 = { */ const struct Curl_handler Curl_handler_pop3s = { - "POP3S", /* scheme */ + "pop3s", /* scheme */ pop3_setup_connection, /* setup_connection */ pop3_do, /* do_it */ pop3_done, /* done */ @@ -150,75 +164,30 @@ const struct Curl_handler Curl_handler_pop3s = { ZERO_NULL, /* domore_getsock */ ZERO_NULL, /* perform_getsock */ pop3_disconnect, /* disconnect */ - ZERO_NULL, /* readwrite */ + pop3_write, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ + 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 -#ifndef CURL_DISABLE_HTTP -/* - * HTTP-proxyed POP3 protocol handler. - */ - -static const struct Curl_handler Curl_handler_pop3_proxy = { - "POP3", /* scheme */ - Curl_http_setup_conn, /* setup_connection */ - Curl_http, /* do_it */ - Curl_http_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 */ - PORT_POP3, /* defport */ - CURLPROTO_HTTP, /* protocol */ - PROTOPT_NONE /* flags */ -}; - -#ifdef USE_SSL -/* - * HTTP-proxyed POP3S protocol handler. - */ - -static const struct Curl_handler Curl_handler_pop3s_proxy = { - "POP3S", /* scheme */ - Curl_http_setup_conn, /* setup_connection */ - Curl_http, /* do_it */ - Curl_http_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 */ - PORT_POP3S, /* defport */ - CURLPROTO_HTTP, /* protocol */ - PROTOPT_NONE /* flags */ -}; -#endif -#endif - /* SASL parameters for the pop3 protocol */ static const struct SASLproto saslpop3 = { - "pop", /* The service name */ - '*', /* Code received when continuation is expected */ - '+', /* Code to receive upon authentication success */ - 255 - 8, /* Maximum initial response length (no max) */ - pop3_perform_auth, /* Send authentication command */ - pop3_continue_auth, /* Send authentication continuation */ - pop3_get_message /* Get SASL response message */ + "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 @@ -228,12 +197,59 @@ static void pop3_to_pop3s(struct connectdata *conn) conn->handler = &Curl_handler_pop3s; /* Set the connection's upgraded to TLS flag */ - conn->tls_upgraded = TRUE; + conn->bits.tls_upgraded = TRUE; } #else #define pop3_to_pop3s(x) Curl_nop_stmt #endif +struct pop3_cmd { + const char *name; + unsigned short nlen; + BIT(multiline); /* response is multi-line with last '.' line */ + BIT(multiline_with_args); /* is multi-line when command has args */ +}; + +static const struct pop3_cmd pop3cmds[] = { + { "APOP", 4, FALSE, FALSE }, + { "AUTH", 4, FALSE, FALSE }, + { "CAPA", 4, TRUE, TRUE }, + { "DELE", 4, FALSE, FALSE }, + { "LIST", 4, TRUE, FALSE }, + { "MSG", 3, TRUE, TRUE }, + { "NOOP", 4, FALSE, FALSE }, + { "PASS", 4, FALSE, FALSE }, + { "QUIT", 4, FALSE, FALSE }, + { "RETR", 4, TRUE, TRUE }, + { "RSET", 4, FALSE, FALSE }, + { "STAT", 4, FALSE, FALSE }, + { "STLS", 4, FALSE, FALSE }, + { "TOP", 3, TRUE, TRUE }, + { "UIDL", 4, TRUE, FALSE }, + { "USER", 4, FALSE, FALSE }, + { "UTF8", 4, FALSE, FALSE }, + { "XTND", 4, TRUE, TRUE }, +}; + +/* Return iff a command is defined as "multi-line" (RFC 1939), + * has a response terminated by a last line with a '.'. + */ +static bool pop3_is_multiline(const char *cmdline) +{ + size_t i; + for(i = 0; i < ARRAYSIZE(pop3cmds); ++i) { + if(strncasecompare(pop3cmds[i].name, cmdline, pop3cmds[i].nlen)) { + if(!cmdline[pop3cmds[i].nlen]) + return pop3cmds[i].multiline; + else if(cmdline[pop3cmds[i].nlen] == ' ') + return pop3cmds[i].multiline_with_args; + } + } + /* Unknown command, assume multi-line for backward compatibility with + * earlier curl versions that only could do multi-line responses. */ + return TRUE; +} + /*********************************************************************** * * pop3_endofresp() @@ -243,10 +259,11 @@ static void pop3_to_pop3s(struct connectdata *conn) * capabilities from the CAPA response including the supported authentication * types and allowed SASL mechanisms. */ -static bool pop3_endofresp(struct connectdata *conn, char *line, size_t len, - int *resp) +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)) { @@ -258,7 +275,7 @@ static bool pop3_endofresp(struct connectdata *conn, char *line, size_t len, /* Are we processing CAPA command responses? */ if(pop3c->state == POP3_CAPA) { /* Do we have the terminating line? */ - if(len >= 1 && !memcmp(line, ".", 1)) + if(len >= 1 && line[0] == '.') /* Treat the response as a success */ *resp = '+'; else @@ -276,7 +293,7 @@ static bool pop3_endofresp(struct connectdata *conn, char *line, size_t len, } /* Do we have a continuation response? */ - if(len >= 1 && !memcmp("+", line, 1)) { + if(len >= 1 && line[0] == '+') { *resp = '*'; return TRUE; @@ -291,38 +308,43 @@ static bool pop3_endofresp(struct connectdata *conn, char *line, size_t len, * * Gets the authentication message from the response buffer. */ -static void pop3_get_message(char *buffer, char **outptr) +static CURLcode pop3_get_message(struct Curl_easy *data, struct bufref *out) { - size_t len = 0; - char *message = NULL; - - /* Find the start of the message */ - for(message = buffer + 2; *message == ' ' || *message == '\t'; message++) - ; - - /* Find the end of the message */ - for(len = strlen(message); len--;) - if(message[len] != '\r' && message[len] != '\n' && message[len] != ' ' && - message[len] != '\t') - break; - - /* Terminate the message */ - if(++len) { - message[len] = '\0'; + char *message = Curl_dyn_ptr(&data->conn->proto.pop3c.pp.recvbuf); + size_t len = data->conn->proto.pop3c.pp.nfinal; + + 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); - *outptr = message; + return CURLE_OK; } /*********************************************************************** * - * state() + * pop3_state() * * This is the ONLY way to change POP3 state! */ -static void state(struct connectdata *conn, pop3state newstate) +static void pop3_state(struct Curl_easy *data, pop3state newstate) { - struct pop3_conn *pop3c = &conn->proto.pop3c; + struct pop3_conn *pop3c = &data->conn->proto.pop3c; #if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS) /* for debug purposes */ static const char * const names[] = { @@ -341,7 +363,7 @@ static void state(struct connectdata *conn, pop3state newstate) }; if(pop3c->state != newstate) - infof(conn->data, "POP3 %p state change from %s to %s\n", + infof(data, "POP3 %p state change from %s to %s", (void *)pop3c, names[pop3c->state], names[newstate]); #endif @@ -355,7 +377,8 @@ static void state(struct connectdata *conn, pop3state newstate) * Sends the CAPA command in order to obtain a list of server side supported * capabilities. */ -static CURLcode pop3_perform_capa(struct connectdata *conn) +static CURLcode pop3_perform_capa(struct Curl_easy *data, + struct connectdata *conn) { CURLcode result = CURLE_OK; struct pop3_conn *pop3c = &conn->proto.pop3c; @@ -365,10 +388,10 @@ static CURLcode pop3_perform_capa(struct connectdata *conn) pop3c->tls_supported = FALSE; /* Clear the TLS capability */ /* Send the CAPA command */ - result = Curl_pp_sendf(&pop3c->pp, "%s", "CAPA"); + result = Curl_pp_sendf(data, &pop3c->pp, "%s", "CAPA"); if(!result) - state(conn, POP3_CAPA); + pop3_state(data, POP3_CAPA); return result; } @@ -379,15 +402,14 @@ static CURLcode pop3_perform_capa(struct connectdata *conn) * * Sends the STLS command to start the upgrade to TLS. */ -static CURLcode pop3_perform_starttls(struct connectdata *conn) +static CURLcode pop3_perform_starttls(struct Curl_easy *data, + struct connectdata *conn) { - CURLcode result = CURLE_OK; - /* Send the STLS command */ - result = Curl_pp_sendf(&conn->proto.pop3c.pp, "%s", "STLS"); + CURLcode result = Curl_pp_sendf(data, &conn->proto.pop3c.pp, "%s", "STLS"); if(!result) - state(conn, POP3_STARTTLS); + pop3_state(data, POP3_STARTTLS); return result; } @@ -398,24 +420,33 @@ static CURLcode pop3_perform_starttls(struct connectdata *conn) * * Performs the upgrade to TLS. */ -static CURLcode pop3_perform_upgrade_tls(struct connectdata *conn) +static CURLcode pop3_perform_upgrade_tls(struct Curl_easy *data, + struct connectdata *conn) { - CURLcode result = CURLE_OK; + /* Start the SSL connection */ struct pop3_conn *pop3c = &conn->proto.pop3c; + CURLcode result; + bool ssldone = FALSE; - /* Start the SSL connection */ - result = Curl_ssl_connect_nonblocking(conn, FIRSTSOCKET, &pop3c->ssldone); + 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) - state(conn, POP3_UPGRADETLS); + pop3_state(data, POP3_UPGRADETLS); if(pop3c->ssldone) { pop3_to_pop3s(conn); - result = pop3_perform_capa(conn); + result = pop3_perform_capa(data, conn); } } - +out: return result; } @@ -425,47 +456,49 @@ static CURLcode pop3_perform_upgrade_tls(struct connectdata *conn) * * Sends a clear text USER command to authenticate with. */ -static CURLcode pop3_perform_user(struct connectdata *conn) +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(!conn->bits.user_passwd) { - state(conn, POP3_STOP); + connect phase if we do not */ + if(!data->state.aptr.user) { + pop3_state(data, POP3_STOP); return result; } /* Send the USER command */ - result = Curl_pp_sendf(&conn->proto.pop3c.pp, "USER %s", + result = Curl_pp_sendf(data, &conn->proto.pop3c.pp, "USER %s", conn->user ? conn->user : ""); if(!result) - state(conn, POP3_USER); + pop3_state(data, POP3_USER); return result; } -#ifndef CURL_DISABLE_CRYPTO_AUTH +#ifndef CURL_DISABLE_DIGEST_AUTH /*********************************************************************** * * pop3_perform_apop() * * Sends an APOP command to authenticate with. */ -static CURLcode pop3_perform_apop(struct connectdata *conn) +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; - MD5_context *ctxt; + 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(!conn->bits.user_passwd) { - state(conn, POP3_STOP); + connect phase if we do not */ + if(!data->state.aptr.user) { + pop3_state(data, POP3_STOP); return result; } @@ -486,12 +519,12 @@ static CURLcode pop3_perform_apop(struct connectdata *conn) /* Convert the calculated 16 octet digest into a 32 byte hex string */ for(i = 0; i < MD5_DIGEST_LEN; i++) - snprintf(&secret[2 * i], 3, "%02x", digest[i]); + msnprintf(&secret[2 * i], 3, "%02x", digest[i]); - result = Curl_pp_sendf(&pop3c->pp, "APOP %s %s", conn->user, secret); + result = Curl_pp_sendf(data, &pop3c->pp, "APOP %s %s", conn->user, secret); if(!result) - state(conn, POP3_APOP); + pop3_state(data, POP3_APOP); return result; } @@ -504,20 +537,21 @@ static CURLcode pop3_perform_apop(struct connectdata *conn) * Sends an AUTH command allowing the client to login with the given SASL * authentication mechanism. */ -static CURLcode pop3_perform_auth(struct connectdata *conn, +static CURLcode pop3_perform_auth(struct Curl_easy *data, const char *mech, - const char *initresp) + const struct bufref *initresp) { CURLcode result = CURLE_OK; - struct pop3_conn *pop3c = &conn->proto.pop3c; + struct pop3_conn *pop3c = &data->conn->proto.pop3c; + const char *ir = (const char *) Curl_bufref_ptr(initresp); - if(initresp) { /* AUTH ... */ + if(ir) { /* AUTH ... */ /* Send the AUTH command with the initial response */ - result = Curl_pp_sendf(&pop3c->pp, "AUTH %s %s", mech, initresp); + result = Curl_pp_sendf(data, &pop3c->pp, "AUTH %s %s", mech, ir); } else { /* Send the AUTH command */ - result = Curl_pp_sendf(&pop3c->pp, "AUTH %s", mech); + result = Curl_pp_sendf(data, &pop3c->pp, "AUTH %s", mech); } return result; @@ -527,14 +561,33 @@ static CURLcode pop3_perform_auth(struct connectdata *conn, * * pop3_continue_auth() * - * Sends SASL continuation data or cancellation. + * Sends SASL continuation data. */ -static CURLcode pop3_continue_auth(struct connectdata *conn, - const char *resp) +static CURLcode pop3_continue_auth(struct Curl_easy *data, + const char *mech, + const struct bufref *resp) { - struct pop3_conn *pop3c = &conn->proto.pop3c; + 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; - return Curl_pp_sendf(&pop3c->pp, "%s", resp); + (void)mech; + + return Curl_pp_sendf(data, &pop3c->pp, "*"); } /*********************************************************************** @@ -545,41 +598,42 @@ static CURLcode pop3_continue_auth(struct connectdata *conn, * 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 connectdata *conn) +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, conn)) { - state(conn, POP3_STOP); + connect phase if we do not */ + 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, conn, FALSE, &progress); + result = Curl_sasl_start(&pop3c->sasl, data, FALSE, &progress); if(!result) if(progress == SASL_INPROGRESS) - state(conn, POP3_AUTH); + pop3_state(data, POP3_AUTH); } if(!result && progress == SASL_IDLE) { -#ifndef CURL_DISABLE_CRYPTO_AUTH +#ifndef CURL_DISABLE_DIGEST_AUTH if(pop3c->authtypes & pop3c->preftype & POP3_TYPE_APOP) /* Perform APOP authentication */ - result = pop3_perform_apop(conn); + result = pop3_perform_apop(data, conn); else #endif if(pop3c->authtypes & pop3c->preftype & POP3_TYPE_CLEARTEXT) /* Perform clear text authentication */ - result = pop3_perform_user(conn); + result = pop3_perform_user(data, conn); else { /* Other mechanisms not supported */ - infof(conn->data, "No known authentication mechanisms supported!\n"); + infof(data, "No known authentication mechanisms supported"); result = CURLE_LOGIN_DENIED; } } @@ -593,36 +647,38 @@ static CURLcode pop3_perform_authentication(struct connectdata *conn) * * Sends a POP3 based command. */ -static CURLcode pop3_perform_command(struct connectdata *conn) +static CURLcode pop3_perform_command(struct Curl_easy *data) { CURLcode result = CURLE_OK; - struct Curl_easy *data = conn->data; - struct POP3 *pop3 = data->req.protop; + 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' || conn->data->set.ftp_list_only) { + 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 = FTPTRANSFER_INFO; + pop3->transfer = PPTRANSFER_INFO; } else command = "RETR"; + if(pop3->custom && pop3->custom[0] != '\0') + command = pop3->custom; + /* Send the command */ if(pop3->id[0] != '\0') - result = Curl_pp_sendf(&conn->proto.pop3c.pp, "%s %s", - (pop3->custom && pop3->custom[0] != '\0' ? - pop3->custom : command), pop3->id); + result = Curl_pp_sendf(data, &conn->proto.pop3c.pp, "%s %s", + command, pop3->id); else - result = Curl_pp_sendf(&conn->proto.pop3c.pp, "%s", - (pop3->custom && pop3->custom[0] != '\0' ? - pop3->custom : command)); + result = Curl_pp_sendf(data, &conn->proto.pop3c.pp, "%s", command); - if(!result) - state(conn, POP3_COMMAND); + if(!result) { + pop3_state(data, POP3_COMMAND); + data->req.no_body = !pop3_is_multiline(command); + } return result; } @@ -633,30 +689,28 @@ static CURLcode pop3_perform_command(struct connectdata *conn) * * Performs the quit action prior to sclose() be called. */ -static CURLcode pop3_perform_quit(struct connectdata *conn) +static CURLcode pop3_perform_quit(struct Curl_easy *data, + struct connectdata *conn) { - CURLcode result = CURLE_OK; - /* Send the QUIT command */ - result = Curl_pp_sendf(&conn->proto.pop3c.pp, "%s", "QUIT"); + CURLcode result = Curl_pp_sendf(data, &conn->proto.pop3c.pp, "%s", "QUIT"); if(!result) - state(conn, POP3_QUIT); + pop3_state(data, POP3_QUIT); return result; } /* For the initial server greeting */ -static CURLcode pop3_state_servergreet_resp(struct connectdata *conn, +static CURLcode pop3_state_servergreet_resp(struct Curl_easy *data, int pop3code, pop3state instate) { CURLcode result = CURLE_OK; - struct Curl_easy *data = conn->data; + struct connectdata *conn = data->conn; struct pop3_conn *pop3c = &conn->proto.pop3c; - const char *line = data->state.buffer; - size_t len = strlen(line); - size_t i; + const char *line = Curl_dyn_ptr(&data->conn->proto.pop3c.pp.recvbuf); + size_t len = data->conn->proto.pop3c.pp.nfinal; (void)instate; /* no use for this yet */ @@ -664,50 +718,49 @@ static CURLcode pop3_state_servergreet_resp(struct connectdata *conn, failf(data, "Got unexpected pop3-server response"); result = CURLE_WEIRD_SERVER_REPLY; } - else { + else if(len > 3) { /* Does the server support APOP authentication? */ - if(len >= 4 && line[len - 2] == '>') { - /* Look for the APOP timestamp */ - for(i = 3; i < len - 2; ++i) { - if(line[i] == '<') { - /* Calculate the length of the timestamp */ - size_t timestamplen = len - 1 - i; - 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'; - - /* Store the APOP capability */ - pop3c->authtypes |= POP3_TYPE_APOP; - break; - } + char *lt; + char *gt = NULL; + + /* Look for the APOP timestamp */ + lt = memchr(line, '<', len); + if(lt) + /* search the remainder for '>' */ + gt = memchr(lt, '>', len - (lt - line)); + if(gt) { + /* the length of the timestamp, including the brackets */ + size_t timestamplen = gt - lt + 1; + char *at = memchr(lt, '@', timestamplen); + /* 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. */ + if(at) { + /* dupe the timestamp */ + pop3c->apoptimestamp = Curl_memdup0(lt, timestamplen); + if(!pop3c->apoptimestamp) + return CURLE_OUT_OF_MEMORY; + /* Store the APOP capability */ + pop3c->authtypes |= POP3_TYPE_APOP; } } - result = pop3_perform_capa(conn); + if(!result) + result = pop3_perform_capa(data, conn); } return result; } /* For CAPA responses */ -static CURLcode pop3_state_capa_resp(struct connectdata *conn, int pop3code, +static CURLcode pop3_state_capa_resp(struct Curl_easy *data, int pop3code, pop3state instate) { CURLcode result = CURLE_OK; - struct Curl_easy *data = conn->data; + struct connectdata *conn = data->conn; struct pop3_conn *pop3c = &conn->proto.pop3c; - const char *line = data->state.buffer; - size_t len = strlen(line); - size_t wordlen; + const char *line = Curl_dyn_ptr(&data->conn->proto.pop3c.pp.recvbuf); + size_t len = data->conn->proto.pop3c.pp.nfinal; (void)instate; /* no use for this yet */ @@ -732,7 +785,8 @@ static CURLcode pop3_state_capa_resp(struct connectdata *conn, int pop3code, /* Loop through the data line */ for(;;) { size_t llen; - unsigned int mechbit; + size_t wordlen; + unsigned short mechbit; while(len && (*line == ' ' || *line == '\t' || @@ -761,85 +815,83 @@ static CURLcode pop3_state_capa_resp(struct connectdata *conn, int pop3code, } } } - else if(pop3code == '+') { - if(data->set.use_ssl && !conn->ssl[FIRSTSOCKET].use) { - /* We don't have a SSL/TLS connection yet, but SSL is requested */ - if(pop3c->tls_supported) - /* Switch to TLS connection now */ - result = pop3_perform_starttls(conn); - else if(data->set.use_ssl == CURLUSESSL_TRY) - /* Fallback and carry on with authentication */ - result = pop3_perform_authentication(conn); - else { - failf(data, "STLS not supported."); - result = CURLE_USE_SSL_FAILED; - } - } - else - result = pop3_perform_authentication(conn); - } else { - /* Clear text is supported when CAPA isn't recognised */ - pop3c->authtypes |= POP3_TYPE_CLEARTEXT; + /* Clear text is supported when CAPA is not recognised */ + if(pop3code != '+') + pop3c->authtypes |= POP3_TYPE_CLEARTEXT; - result = pop3_perform_authentication(conn); + 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 connectdata *conn, +static CURLcode pop3_state_starttls_resp(struct Curl_easy *data, + struct connectdata *conn, int pop3code, pop3state instate) { CURLcode result = CURLE_OK; - struct Curl_easy *data = conn->data; - (void)instate; /* no use for this yet */ + /* Pipelining in response is forbidden. */ + if(data->conn->proto.pop3c.pp.overflow) + 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(conn); + result = pop3_perform_authentication(data, conn); } else - result = pop3_perform_upgrade_tls(conn); + result = pop3_perform_upgrade_tls(data, conn); return result; } /* For SASL authentication responses */ -static CURLcode pop3_state_auth_resp(struct connectdata *conn, +static CURLcode pop3_state_auth_resp(struct Curl_easy *data, int pop3code, pop3state instate) { CURLcode result = CURLE_OK; - struct Curl_easy *data = conn->data; + 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, conn, pop3code, &progress); + result = Curl_sasl_continue(&pop3c->sasl, data, pop3code, &progress); if(!result) switch(progress) { case SASL_DONE: - state(conn, POP3_STOP); /* Authenticated */ + pop3_state(data, POP3_STOP); /* Authenticated */ break; case SASL_IDLE: /* No mechanism left after cancellation */ -#ifndef CURL_DISABLE_CRYPTO_AUTH +#ifndef CURL_DISABLE_DIGEST_AUTH if(pop3c->authtypes & pop3c->preftype & POP3_TYPE_APOP) /* Perform APOP authentication */ - result = pop3_perform_apop(conn); + result = pop3_perform_apop(data, conn); else #endif if(pop3c->authtypes & pop3c->preftype & POP3_TYPE_CLEARTEXT) /* Perform clear text authentication */ - result = pop3_perform_user(conn); + result = pop3_perform_user(data, conn); else { failf(data, "Authentication cancelled"); result = CURLE_LOGIN_DENIED; @@ -852,14 +904,12 @@ static CURLcode pop3_state_auth_resp(struct connectdata *conn, return result; } -#ifndef CURL_DISABLE_CRYPTO_AUTH +#ifndef CURL_DISABLE_DIGEST_AUTH /* For APOP responses */ -static CURLcode pop3_state_apop_resp(struct connectdata *conn, int pop3code, +static CURLcode pop3_state_apop_resp(struct Curl_easy *data, int pop3code, pop3state instate) { CURLcode result = CURLE_OK; - struct Curl_easy *data = conn->data; - (void)instate; /* no use for this yet */ if(pop3code != '+') { @@ -868,19 +918,18 @@ static CURLcode pop3_state_apop_resp(struct connectdata *conn, int pop3code, } else /* End of connect phase */ - state(conn, POP3_STOP); + pop3_state(data, POP3_STOP); return result; } #endif /* For USER responses */ -static CURLcode pop3_state_user_resp(struct connectdata *conn, int pop3code, +static CURLcode pop3_state_user_resp(struct Curl_easy *data, int pop3code, pop3state instate) { CURLcode result = CURLE_OK; - struct Curl_easy *data = conn->data; - + struct connectdata *conn = data->conn; (void)instate; /* no use for this yet */ if(pop3code != '+') { @@ -889,21 +938,19 @@ static CURLcode pop3_state_user_resp(struct connectdata *conn, int pop3code, } else /* Send the PASS command */ - result = Curl_pp_sendf(&conn->proto.pop3c.pp, "PASS %s", + result = Curl_pp_sendf(data, &conn->proto.pop3c.pp, "PASS %s", conn->passwd ? conn->passwd : ""); if(!result) - state(conn, POP3_PASS); + pop3_state(data, POP3_PASS); return result; } /* For PASS responses */ -static CURLcode pop3_state_pass_resp(struct connectdata *conn, int pop3code, +static CURLcode pop3_state_pass_resp(struct Curl_easy *data, int pop3code, pop3state instate) { CURLcode result = CURLE_OK; - struct Curl_easy *data = conn->data; - (void)instate; /* no use for this yet */ if(pop3code != '+') { @@ -912,27 +959,27 @@ static CURLcode pop3_state_pass_resp(struct connectdata *conn, int pop3code, } else /* End of connect phase */ - state(conn, POP3_STOP); + pop3_state(data, POP3_STOP); return result; } /* For command responses */ -static CURLcode pop3_state_command_resp(struct connectdata *conn, +static CURLcode pop3_state_command_resp(struct Curl_easy *data, int pop3code, pop3state instate) { CURLcode result = CURLE_OK; - struct Curl_easy *data = conn->data; - struct POP3 *pop3 = data->req.protop; + 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 != '+') { - state(conn, POP3_STOP); - return CURLE_RECV_ERROR; + 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 @@ -942,60 +989,66 @@ static CURLcode pop3_state_command_resp(struct connectdata *conn, 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. */ + the strip counter here so that these bytes will not be delivered. */ pop3c->strip = 2; - if(pop3->transfer == FTPTRANSFER_BODY) { + if(pop3->transfer == PPTRANSFER_BODY) { /* POP3 download */ - Curl_setup_transfer(conn, FIRSTSOCKET, -1, FALSE, NULL, -1, NULL); + Curl_xfer_setup1(data, CURL_XFER_RECV, -1, FALSE); - 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(pp->overflow) { + /* The recv buffer contains data that is actually body content so send + it as such. Note that there may even be additional "headers" after + the body */ - if(!data->set.opt_no_body) { - result = Curl_pop3_write(conn, pp->cache, pp->cache_size); + /* keep only the overflow */ + Curl_dyn_tail(&pp->recvbuf, pp->overflow); + pp->nfinal = 0; /* done */ + + if(!data->req.no_body) { + result = pop3_write(data, Curl_dyn_ptr(&pp->recvbuf), + Curl_dyn_len(&pp->recvbuf), FALSE); if(result) return result; } - /* Free the cache */ - Curl_safefree(pp->cache); - - /* Reset the cache size */ - pp->cache_size = 0; + /* reset the buffer */ + Curl_dyn_reset(&pp->recvbuf); + pp->overflow = 0; } } + else + pp->overflow = 0; /* End of DO phase */ - state(conn, POP3_STOP); + pop3_state(data, POP3_STOP); return result; } -static CURLcode pop3_statemach_act(struct connectdata *conn) +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(conn); + return pop3_perform_upgrade_tls(data, conn); /* Flush any data that needs to be sent */ if(pp->sendleft) - return Curl_pp_flushsend(pp); + return Curl_pp_flushsend(data, pp); do { /* Read the response from the server */ - result = Curl_pp_readresp(sock, pp, &pop3code, &nread); - if(result) - return result; + result = Curl_pp_readresp(data, FIRSTSOCKET, pp, &pop3code, &nread); + if(result) + return result; if(!pop3code) break; @@ -1003,44 +1056,46 @@ static CURLcode pop3_statemach_act(struct connectdata *conn) /* We have now received a full POP3 server response */ switch(pop3c->state) { case POP3_SERVERGREET: - result = pop3_state_servergreet_resp(conn, pop3code, pop3c->state); + result = pop3_state_servergreet_resp(data, pop3code, pop3c->state); break; case POP3_CAPA: - result = pop3_state_capa_resp(conn, pop3code, pop3c->state); + result = pop3_state_capa_resp(data, pop3code, pop3c->state); break; case POP3_STARTTLS: - result = pop3_state_starttls_resp(conn, pop3code, pop3c->state); + result = pop3_state_starttls_resp(data, conn, pop3code, pop3c->state); break; case POP3_AUTH: - result = pop3_state_auth_resp(conn, pop3code, pop3c->state); + result = pop3_state_auth_resp(data, pop3code, pop3c->state); break; -#ifndef CURL_DISABLE_CRYPTO_AUTH +#ifndef CURL_DISABLE_DIGEST_AUTH case POP3_APOP: - result = pop3_state_apop_resp(conn, pop3code, pop3c->state); + result = pop3_state_apop_resp(data, pop3code, pop3c->state); break; #endif case POP3_USER: - result = pop3_state_user_resp(conn, pop3code, pop3c->state); + result = pop3_state_user_resp(data, pop3code, pop3c->state); break; case POP3_PASS: - result = pop3_state_pass_resp(conn, pop3code, pop3c->state); + result = pop3_state_pass_resp(data, pop3code, pop3c->state); break; case POP3_COMMAND: - result = pop3_state_command_resp(conn, pop3code, pop3c->state); + result = pop3_state_command_resp(data, pop3code, pop3c->state); break; case POP3_QUIT: - /* fallthrough, just stop! */ + pop3_state(data, POP3_STOP); + break; + default: /* internal error */ - state(conn, POP3_STOP); + pop3_state(data, POP3_STOP); break; } } while(!result && pop3c->state != POP3_STOP && Curl_pp_moredata(pp)); @@ -1049,43 +1104,47 @@ static CURLcode pop3_statemach_act(struct connectdata *conn) } /* Called repeatedly until done from multi.c */ -static CURLcode pop3_multi_statemach(struct connectdata *conn, bool *done) +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) { - result = Curl_ssl_connect_nonblocking(conn, FIRSTSOCKET, &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(&pop3c->pp, FALSE); + result = Curl_pp_statemach(data, &pop3c->pp, FALSE, FALSE); *done = (pop3c->state == POP3_STOP) ? TRUE : FALSE; return result; } -static CURLcode pop3_block_statemach(struct connectdata *conn) +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(&pop3c->pp, TRUE); + 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 connectdata *conn) +static CURLcode pop3_init(struct Curl_easy *data) { CURLcode result = CURLE_OK; - struct Curl_easy *data = conn->data; struct POP3 *pop3; - pop3 = data->req.protop = calloc(sizeof(struct POP3), 1); + pop3 = data->req.p.pop3 = calloc(1, sizeof(struct POP3)); if(!pop3) result = CURLE_OUT_OF_MEMORY; @@ -1093,10 +1152,10 @@ static CURLcode pop3_init(struct connectdata *conn) } /* For the POP3 "protocol connect" and "doing" phases only */ -static int pop3_getsock(struct connectdata *conn, curl_socket_t *socks, - int numsocks) +static int pop3_getsock(struct Curl_easy *data, + struct connectdata *conn, curl_socket_t *socks) { - return Curl_pp_getsock(&conn->proto.pop3c.pp, socks, numsocks); + return Curl_pp_getsock(data, &conn->proto.pop3c.pp, socks); } /*********************************************************************** @@ -1109,9 +1168,10 @@ static int pop3_getsock(struct connectdata *conn, curl_socket_t *socks, * 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 connectdata *conn, bool *done) +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; @@ -1120,15 +1180,11 @@ static CURLcode pop3_connect(struct connectdata *conn, bool *done) /* We always support persistent connections in POP3 */ connkeep(conn, "POP3 default"); - /* Set the default response time-out */ - pp->response_time = RESP_TIMEOUT; - pp->statemach_act = pop3_statemach_act; - pp->endofresp = pop3_endofresp; - pp->conn = conn; + 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, &saslpop3); + Curl_sasl_init(&pop3c->sasl, data, &saslpop3); /* Initialise the pingpong layer */ Curl_pp_init(pp); @@ -1139,9 +1195,9 @@ static CURLcode pop3_connect(struct connectdata *conn, bool *done) return result; /* Start off waiting for the server greeting response */ - state(conn, POP3_SERVERGREET); + pop3_state(data, POP3_SERVERGREET); - result = pop3_multi_statemach(conn, done); + result = pop3_multi_statemach(data, done); return result; } @@ -1155,12 +1211,11 @@ static CURLcode pop3_connect(struct connectdata *conn, bool *done) * * Input argument is already checked for validity. */ -static CURLcode pop3_done(struct connectdata *conn, CURLcode status, +static CURLcode pop3_done(struct Curl_easy *data, CURLcode status, bool premature) { CURLcode result = CURLE_OK; - struct Curl_easy *data = conn->data; - struct POP3 *pop3 = data->req.protop; + struct POP3 *pop3 = data->req.p.pop3; (void)premature; @@ -1168,7 +1223,7 @@ static CURLcode pop3_done(struct connectdata *conn, CURLcode status, return CURLE_OK; if(status) { - connclose(conn, "POP3 done with bad status"); + connclose(data->conn, "POP3 done with bad status"); result = status; /* use the already set error code */ } @@ -1177,7 +1232,7 @@ static CURLcode pop3_done(struct connectdata *conn, CURLcode status, Curl_safefree(pop3->custom); /* Clear the transfer mode for the next request */ - pop3->transfer = FTPTRANSFER_BODY; + pop3->transfer = PPTRANSFER_BODY; return result; } @@ -1189,34 +1244,33 @@ static CURLcode pop3_done(struct connectdata *conn, CURLcode status, * This is the actual DO function for POP3. Get a message/listing according to * the options previously setup. */ -static CURLcode pop3_perform(struct connectdata *conn, bool *connected, +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 = conn->data->req.protop; + struct POP3 *pop3 = data->req.p.pop3; - DEBUGF(infof(conn->data, "DO phase starts\n")); + DEBUGF(infof(data, "DO phase starts")); - if(conn->data->set.opt_no_body) { + if(data->req.no_body) { /* Requested no body means no transfer */ - pop3->transfer = FTPTRANSFER_INFO; + pop3->transfer = PPTRANSFER_INFO; } *dophase_done = FALSE; /* not done yet */ /* Start the first command in the DO phase */ - result = pop3_perform_command(conn); + result = pop3_perform_command(data); if(result) return result; /* Run the state-machine */ - result = pop3_multi_statemach(conn, dophase_done); - - *connected = conn->bits.tcpconnect[FIRSTSOCKET]; + result = pop3_multi_statemach(data, dophase_done); + *connected = Curl_conn_is_connected(data->conn, FIRSTSOCKET); if(*dophase_done) - DEBUGF(infof(conn->data, "DO phase is complete\n")); + DEBUGF(infof(data, "DO phase is complete")); return result; } @@ -1230,23 +1284,22 @@ static CURLcode pop3_perform(struct connectdata *conn, bool *connected, * * The input argument is already checked for validity. */ -static CURLcode pop3_do(struct connectdata *conn, bool *done) +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(conn); + result = pop3_parse_url_path(data); if(result) return result; /* Parse the custom request */ - result = pop3_parse_custom_request(conn); + result = pop3_parse_custom_request(data); if(result) return result; - result = pop3_regular_transfer(conn, done); + result = pop3_regular_transfer(data, done); return result; } @@ -1258,19 +1311,20 @@ static CURLcode pop3_do(struct connectdata *conn, bool *done) * Disconnect from an POP3 server. Cleanup protocol-specific per-connection * resources. BLOCKING. */ -static CURLcode pop3_disconnect(struct connectdata *conn, bool dead_connection) +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. */ - /* The POP3 session may or may not have been allocated/setup at this - point! */ - if(!dead_connection && pop3c->pp.conn && pop3c->pp.conn->bits.protoconnstart) - if(!pop3_perform_quit(conn)) - (void)pop3_block_statemach(conn); /* ignore errors on QUIT */ + 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); @@ -1285,25 +1339,25 @@ static CURLcode pop3_disconnect(struct connectdata *conn, bool dead_connection) } /* Call this when the DO phase has completed */ -static CURLcode pop3_dophase_done(struct connectdata *conn, bool connected) +static CURLcode pop3_dophase_done(struct Curl_easy *data, bool connected) { - (void)conn; + (void)data; (void)connected; return CURLE_OK; } /* Called from multi.c while DOing */ -static CURLcode pop3_doing(struct connectdata *conn, bool *dophase_done) +static CURLcode pop3_doing(struct Curl_easy *data, bool *dophase_done) { - CURLcode result = pop3_multi_statemach(conn, dophase_done); + CURLcode result = pop3_multi_statemach(data, dophase_done); if(result) - DEBUGF(infof(conn->data, "DO phase failed\n")); + DEBUGF(infof(data, "DO phase failed")); else if(*dophase_done) { - result = pop3_dophase_done(conn, FALSE /* not connected */); + result = pop3_dophase_done(data, FALSE /* not connected */); - DEBUGF(infof(conn->data, "DO phase is complete\n")); + DEBUGF(infof(data, "DO phase is complete")); } return result; @@ -1318,12 +1372,11 @@ static CURLcode pop3_doing(struct connectdata *conn, bool *dophase_done) * Performs all commands done before a regular transfer between a local and a * remote host. */ -static CURLcode pop3_regular_transfer(struct connectdata *conn, +static CURLcode pop3_regular_transfer(struct Curl_easy *data, bool *dophase_done) { CURLcode result = CURLE_OK; bool connected = FALSE; - struct Curl_easy *data = conn->data; /* Make sure size is unknown at this point */ data->req.size = -1; @@ -1335,52 +1388,25 @@ static CURLcode pop3_regular_transfer(struct connectdata *conn, Curl_pgrsSetDownloadSize(data, -1); /* Carry out the perform */ - result = pop3_perform(conn, &connected, dophase_done); + result = pop3_perform(data, &connected, dophase_done); /* Perform post DO phase operations if necessary */ if(!result && *dophase_done) - result = pop3_dophase_done(conn, connected); + result = pop3_dophase_done(data, connected); return result; } -static CURLcode pop3_setup_connection(struct connectdata *conn) +static CURLcode pop3_setup_connection(struct Curl_easy *data, + struct connectdata *conn) { - struct Curl_easy *data = conn->data; - /* Initialise the POP3 layer */ - CURLcode result = pop3_init(conn); + CURLcode result = pop3_init(data); if(result) return result; /* Clear the TLS upgraded flag */ - conn->tls_upgraded = FALSE; - - /* Set up the proxy if necessary */ - if(conn->bits.httpproxy && !data->set.tunnel_thru_httpproxy) { - /* Unless we have asked to tunnel POP3 operations through the proxy, we - switch and use HTTP operations only */ -#ifndef CURL_DISABLE_HTTP - if(conn->handler == &Curl_handler_pop3) - conn->handler = &Curl_handler_pop3_proxy; - else { -#ifdef USE_SSL - conn->handler = &Curl_handler_pop3s_proxy; -#else - failf(data, "POP3S not supported!"); - return CURLE_UNSUPPORTED_PROTOCOL; -#endif - } - - /* set it up as an HTTP connection instead */ - return conn->handler->setup_connection(conn); -#else - failf(data, "POP3 over http proxy requires HTTP support built-in!"); - return CURLE_UNSUPPORTED_PROTOCOL; -#endif - } - - data->state.path++; /* don't include the initial slash */ + conn->bits.tls_upgraded = FALSE; return CURLE_OK; } @@ -1397,14 +1423,12 @@ static CURLcode pop3_parse_url_options(struct connectdata *conn) struct pop3_conn *pop3c = &conn->proto.pop3c; const char *ptr = conn->options; - pop3c->sasl.resetprefs = TRUE; - while(!result && ptr && *ptr) { const char *key = ptr; const char *value; while(*ptr && *ptr != '=') - ptr++; + ptr++; value = ptr + 1; @@ -1450,15 +1474,14 @@ static CURLcode pop3_parse_url_options(struct connectdata *conn) * * Parse the URL path into separate path components. */ -static CURLcode pop3_parse_url_path(struct connectdata *conn) +static CURLcode pop3_parse_url_path(struct Curl_easy *data) { /* The POP3 struct is already initialised in pop3_connect() */ - struct Curl_easy *data = conn->data; - struct POP3 *pop3 = data->req.protop; - const char *path = data->state.path; + 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(data, path, 0, &pop3->id, NULL, TRUE); + return Curl_urldecode(path, 0, &pop3->id, NULL, REJECT_CTRL); } /*********************************************************************** @@ -1467,38 +1490,38 @@ static CURLcode pop3_parse_url_path(struct connectdata *conn) * * Parse the custom request. */ -static CURLcode pop3_parse_custom_request(struct connectdata *conn) +static CURLcode pop3_parse_custom_request(struct Curl_easy *data) { CURLcode result = CURLE_OK; - struct Curl_easy *data = conn->data; - struct POP3 *pop3 = data->req.protop; + 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(data, custom, 0, &pop3->custom, NULL, TRUE); + result = Curl_urldecode(custom, 0, &pop3->custom, NULL, REJECT_CTRL); return result; } /*********************************************************************** * - * Curl_pop3_write() + * 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 connectdata *conn, char *str, size_t nread) +static CURLcode pop3_write(struct Curl_easy *data, const char *str, + size_t nread, bool is_eos) { /* This code could be made into a special function in the handler struct */ CURLcode result = CURLE_OK; - struct Curl_easy *data = conn->data; 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; + (void)is_eos; /* 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 @@ -1514,8 +1537,8 @@ CURLcode Curl_pop3_write(struct connectdata *conn, char *str, size_t nread) pop3c->eob++; if(i) { - /* Write out the body part that didn't match */ - result = Curl_client_write(conn, CLIENTWRITE_BODY, &str[last], + /* Write out the body part that did not match */ + result = Curl_client_write(data, CLIENTWRITE_BODY, &str[last], i - last); if(result) @@ -1527,7 +1550,7 @@ CURLcode Curl_pop3_write(struct connectdata *conn, char *str, size_t nread) else if(pop3c->eob == 3) pop3c->eob++; else - /* If the character match wasn't at position 0 or 3 then restart the + /* If the character match was not at position 0 or 3 then restart the pattern matching */ pop3c->eob = 1; break; @@ -1536,7 +1559,7 @@ CURLcode Curl_pop3_write(struct connectdata *conn, char *str, size_t nread) if(pop3c->eob == 1 || pop3c->eob == 4) pop3c->eob++; else - /* If the character match wasn't at position 1 or 4 then start the + /* If the character match was not at position 1 or 4 then start the search again */ pop3c->eob = 0; break; @@ -1550,7 +1573,7 @@ CURLcode Curl_pop3_write(struct connectdata *conn, char *str, size_t nread) pop3c->eob = 0; } else - /* If the character match wasn't at position 2 then start the search + /* If the character match was not at position 2 then start the search again */ pop3c->eob = 0; break; @@ -1573,8 +1596,17 @@ CURLcode Curl_pop3_write(struct connectdata *conn, char *str, size_t nread) if(prev) { /* If the partial match was the CRLF and dot then only write the CRLF as the server would have inserted the dot */ - result = Curl_client_write(conn, CLIENTWRITE_BODY, (char *)POP3_EOB, - strip_dot ? prev - 1 : prev); + 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; @@ -1589,7 +1621,7 @@ CURLcode Curl_pop3_write(struct connectdata *conn, char *str, size_t nread) /* 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(conn, CLIENTWRITE_BODY, (char *)POP3_EOB, 2); + result = Curl_client_write(data, CLIENTWRITE_BODY, (char *)POP3_EOB, 2); k->keepon &= ~KEEP_RECV; pop3c->eob = 0; @@ -1602,7 +1634,7 @@ CURLcode Curl_pop3_write(struct connectdata *conn, char *str, size_t nread) return CURLE_OK; if(nread - last) { - result = Curl_client_write(conn, CLIENTWRITE_BODY, &str[last], + result = Curl_client_write(data, CLIENTWRITE_BODY, &str[last], nread - last); } diff --git a/contrib/curl/lib/pop3.h b/contrib/curl/lib/pop3.h index a8e697cde2..3d08dafa19 100644 --- a/contrib/curl/lib/pop3.h +++ b/contrib/curl/lib/pop3.h @@ -7,11 +7,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 2009 - 2015, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -20,6 +20,8 @@ * 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" @@ -60,16 +62,16 @@ struct POP3 { struct pop3_conn { struct pingpong pp; pop3state state; /* Always use pop3.c:state() to change state! */ - bool ssldone; /* Is connect() over SSL done? */ 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 */ - unsigned int authtypes; /* Accepted authentication types */ - unsigned int preftype; /* Preferred authentication type */ char *apoptimestamp; /* APOP timestamp from the server greeting */ - bool tls_supported; /* StartTLS capability supported by server */ + 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; @@ -82,14 +84,10 @@ extern const struct Curl_handler Curl_handler_pop3s; /* Authentication type values */ #define POP3_TYPE_NONE 0 -#define POP3_TYPE_ANY ~0U +#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 connectdata *conn, char *str, size_t nread); - #endif /* HEADER_CURL_POP3_H */ diff --git a/contrib/curl/lib/progress.c b/contrib/curl/lib/progress.c index 60627b2af4..db4f7276d5 100644 --- a/contrib/curl/lib/progress.c +++ b/contrib/curl/lib/progress.c @@ -5,11 +5,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2016, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -18,41 +18,47 @@ * 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 d, h, m, s; + curl_off_t h; if(seconds <= 0) { strcpy(r, "--:--:--"); return; } h = seconds / CURL_OFF_T_C(3600); if(h <= CURL_OFF_T_C(99)) { - m = (seconds - (h*CURL_OFF_T_C(3600))) / CURL_OFF_T_C(60); - s = (seconds - (h*CURL_OFF_T_C(3600))) - (m*CURL_OFF_T_C(60)); - snprintf(r, 9, "%2" CURL_FORMAT_CURL_OFF_T ":%02" CURL_FORMAT_CURL_OFF_T - ":%02" CURL_FORMAT_CURL_OFF_T, h, m, s); + 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" FMT_OFF_T ":%02" FMT_OFF_T ":%02" FMT_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. */ - d = seconds / CURL_OFF_T_C(86400); + 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)) - snprintf(r, 9, "%3" CURL_FORMAT_CURL_OFF_T - "d %02" CURL_FORMAT_CURL_OFF_T "h", d, h); + msnprintf(r, 9, "%3" FMT_OFF_T "d %02" FMT_OFF_T "h", d, h); else - snprintf(r, 9, "%7" CURL_FORMAT_CURL_OFF_T "d", d); + msnprintf(r, 9, "%7" FMT_OFF_T "d", d); } } @@ -68,53 +74,45 @@ static char *max5data(curl_off_t bytes, char *max5) #define ONE_PETABYTE (CURL_OFF_T_C(1024) * ONE_TERABYTE) if(bytes < CURL_OFF_T_C(100000)) - snprintf(max5, 6, "%5" CURL_FORMAT_CURL_OFF_T, bytes); + msnprintf(max5, 6, "%5" FMT_OFF_T, bytes); else if(bytes < CURL_OFF_T_C(10000) * ONE_KILOBYTE) - snprintf(max5, 6, "%4" CURL_FORMAT_CURL_OFF_T "k", bytes/ONE_KILOBYTE); + msnprintf(max5, 6, "%4" FMT_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 */ - snprintf(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)) ); - -#if (CURL_SIZEOF_CURL_OFF_T > 4) + /* 'XX.XM' is good as long as we are less than 100 megs */ + msnprintf(max5, 6, "%2" FMT_OFF_T ".%0" + FMT_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 */ - snprintf(max5, 6, "%4" CURL_FORMAT_CURL_OFF_T "M", bytes/ONE_MEGABYTE); + /* 'XXXXM' is good until we are at 10000MB or above */ + msnprintf(max5, 6, "%4" FMT_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 */ - snprintf(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)) ); + msnprintf(max5, 6, "%2" FMT_OFF_T ".%0" + FMT_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 */ - snprintf(max5, 6, "%4" CURL_FORMAT_CURL_OFF_T "G", bytes/ONE_GIGABYTE); + msnprintf(max5, 6, "%4" FMT_OFF_T "G", bytes/ONE_GIGABYTE); else if(bytes < CURL_OFF_T_C(10000) * ONE_TERABYTE) /* up to 10000TB, display without decimal: XXXXT */ - snprintf(max5, 6, "%4" CURL_FORMAT_CURL_OFF_T "T", bytes/ONE_TERABYTE); + msnprintf(max5, 6, "%4" FMT_OFF_T "T", bytes/ONE_TERABYTE); else /* up to 10000PB, display without decimal: XXXXP */ - snprintf(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. */ + msnprintf(max5, 6, "%4" FMT_OFF_T "P", bytes/ONE_PETABYTE); -#else - - else - snprintf(max5, 6, "%4" CURL_FORMAT_CURL_OFF_T "M", bytes/ONE_MEGABYTE); - -#endif + /* 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 /* @@ -130,18 +128,17 @@ static char *max5data(curl_off_t bytes, char *max5) */ -int Curl_pgrsDone(struct connectdata *conn) +int Curl_pgrsDone(struct Curl_easy *data) { int rc; - struct Curl_easy *data = conn->data; - data->progress.lastshow=0; - rc = Curl_pgrsUpdate(conn); /* the final (forced) update */ + 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 + /* only output if we do not use a progress callback and we are not * hidden */ fprintf(data->set.err, "\n"); @@ -149,21 +146,21 @@ int Curl_pgrsDone(struct connectdata *conn) return 0; } -/* reset all times except redirect, and reset the known transfer sizes */ -void Curl_pgrsResetTimesSizes(struct Curl_easy *data) +/* reset the known transfer sizes */ +void Curl_pgrsResetTransferSizes(struct Curl_easy *data) { - data->progress.t_nslookup = 0.0; - data->progress.t_connect = 0.0; - data->progress.t_pretransfer = 0.0; - data->progress.t_starttransfer = 0.0; - Curl_pgrsSetDownloadSize(data, -1); Curl_pgrsSetUploadSize(data, -1); } -void Curl_pgrsTime(struct Curl_easy *data, timerid timer) +/* + * + * Curl_pgrsTimeWas(). Store the timestamp time at the given label. + */ +void Curl_pgrsTimeWas(struct Curl_easy *data, timerid timer, + struct curltime timestamp) { - struct timeval now = Curl_tvnow(); + timediff_t *delta = NULL; switch(timer) { default: @@ -172,149 +169,202 @@ void Curl_pgrsTime(struct Curl_easy *data, timerid timer) break; case TIMER_STARTOP: /* This is set at the start of a transfer */ - data->progress.t_startop = now; + data->progress.t_startop = timestamp; break; case TIMER_STARTSINGLE: - /* This is set at the start of each single fetch */ - data->progress.t_startsingle = now; + /* This is set at the start of each single transfer */ + data->progress.t_startsingle = timestamp; + data->progress.is_t_startransfer_set = false; + break; + case TIMER_POSTQUEUE: + /* Set when the transfer starts (after potentially having been brought + back from the waiting queue). It needs to count from t_startop and not + t_startsingle since the latter is reset when a connection is brought + back from the pending queue. */ + data->progress.t_postqueue = + Curl_timediff_us(timestamp, data->progress.t_startop); break; - case TIMER_STARTACCEPT: - data->progress.t_acceptdata = Curl_tvnow(); + data->progress.t_acceptdata = timestamp; break; - case TIMER_NAMELOOKUP: - data->progress.t_nslookup = - Curl_tvdiff_secs(now, data->progress.t_startsingle); + delta = &data->progress.t_nslookup; break; case TIMER_CONNECT: - data->progress.t_connect = - Curl_tvdiff_secs(now, data->progress.t_startsingle); + delta = &data->progress.t_connect; break; case TIMER_APPCONNECT: - data->progress.t_appconnect = - Curl_tvdiff_secs(now, data->progress.t_startsingle); + delta = &data->progress.t_appconnect; break; case TIMER_PRETRANSFER: - data->progress.t_pretransfer = - Curl_tvdiff_secs(now, data->progress.t_startsingle); + delta = &data->progress.t_pretransfer; break; case TIMER_STARTTRANSFER: - data->progress.t_starttransfer = - Curl_tvdiff_secs(now, data->progress.t_startsingle); - break; + delta = &data->progress.t_starttransfer; + /* prevent updating t_starttransfer unless: + * 1) this is the first time we are 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 */ + delta = &data->progress.t_posttransfer; break; case TIMER_REDIRECT: - data->progress.t_redirect = Curl_tvdiff_secs(now, data->progress.start); + 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_tvnow(); - data->progress.ul_limit_start.tv_sec = 0; - data->progress.ul_limit_start.tv_usec = 0; - data->progress.dl_limit_start.tv_sec = 0; - data->progress.dl_limit_start.tv_usec = 0; + 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.start_size = 0; + data->progress.dl.limit.start_size = 0; + data->progress.dl.cur_size = 0; + data->progress.ul.cur_size = 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 much milliseconds we - * need to wait until we're back under the speed limit, if needed. + * This is used to handle speed limits, calculating how many milliseconds to + * wait until we are back under the speed limit, if needed. * * The way it works is by having a "starting point" (time & amount of data - * transfered 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 tranfer). + * 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 transfered, the amount at the - * starting point, the limit (in bytes/s), the time of the starting point and - * the current time. + * 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 -1 if no waiting is needed (not enough data transfered since - * starting point yet), 0 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. + * 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. */ -long Curl_pgrsLimitWaitTime(curl_off_t cursize, - curl_off_t startsize, - curl_off_t limit, - struct timeval start, - struct timeval now) +timediff_t Curl_pgrsLimitWaitTime(struct pgrs_dir *d, + curl_off_t speed_limit, + struct curltime now) { - curl_off_t size = cursize - startsize; - time_t minimum; - time_t actual; + curl_off_t size = d->cur_size - d->limit.start_size; + timediff_t minimum; + timediff_t actual; - /* we don't have a starting point yet -- return 0 so it gets (re)set */ - if(start.tv_sec == 0 && start.tv_usec == 0) + if(!speed_limit || !size) return 0; - /* not enough data yet */ - if(size < limit) - return -1; + /* + * '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 / speed_limit); + else { + minimum = (timediff_t) (size / speed_limit); + if(minimum < TIMEDIFF_T_MAX/1000) + minimum *= 1000; + else + minimum = TIMEDIFF_T_MAX; + } - minimum = (time_t) (CURL_OFF_T_C(1000) * size / limit); - actual = Curl_tvdiff(now, start); + /* + * 'actual' is the time in milliseconds it took to actually download the + * last 'size' bytes. + */ + actual = Curl_timediff_ceil(now, d->limit.start); + if(actual < minimum) { + /* if it downloaded the data faster than the limit, make it wait the + difference */ + return (minimum - actual); + } - if(actual < minimum) - /* this is a conversion on some systems (64bit time_t => 32bit long) */ - return (long)(minimum - actual); - else - return 0; + return 0; } -void Curl_pgrsSetDownloadCounter(struct Curl_easy *data, curl_off_t size) +/* + * Set the number of downloaded bytes so far. + */ +CURLcode Curl_pgrsSetDownloadCounter(struct Curl_easy *data, curl_off_t size) { - struct timeval now = Curl_tvnow(); - - data->progress.downloaded = size; - - /* download speed limit */ - if((data->set.max_recv_speed > 0) && - (Curl_pgrsLimitWaitTime(data->progress.downloaded, - data->progress.dl_limit_size, - data->set.max_recv_speed, - data->progress.dl_limit_start, - now) == 0)) { - data->progress.dl_limit_start = now; - data->progress.dl_limit_size = size; + data->progress.dl.cur_size = 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) +{ + /* do not 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.start_size = data->progress.dl.cur_size; + } + } + 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.start_size = data->progress.ul.cur_size; + } } } +/* + * Set the number of uploaded bytes so far. + */ void Curl_pgrsSetUploadCounter(struct Curl_easy *data, curl_off_t size) { - struct timeval now = Curl_tvnow(); - - data->progress.uploaded = size; - - /* upload speed limit */ - if((data->set.max_send_speed > 0) && - (Curl_pgrsLimitWaitTime(data->progress.uploaded, - data->progress.ul_limit_size, - data->set.max_send_speed, - data->progress.ul_limit_start, - now) == 0)) { - data->progress.ul_limit_start = now; - data->progress.ul_limit_size = size; - } + data->progress.ul.cur_size = size; } void Curl_pgrsSetDownloadSize(struct Curl_easy *data, curl_off_t size) { if(size >= 0) { - data->progress.size_dl = size; + data->progress.dl.total_size = size; data->progress.flags |= PGRS_DL_SIZE_KNOWN; } else { - data->progress.size_dl = 0; + data->progress.dl.total_size = 0; data->progress.flags &= ~PGRS_DL_SIZE_KNOWN; } } @@ -322,246 +372,266 @@ void Curl_pgrsSetDownloadSize(struct Curl_easy *data, curl_off_t size) void Curl_pgrsSetUploadSize(struct Curl_easy *data, curl_off_t size) { if(size >= 0) { - data->progress.size_ul = size; + data->progress.ul.total_size = size; data->progress.flags |= PGRS_UL_SIZE_KNOWN; } else { - data->progress.size_ul = 0; + data->progress.ul.total_size = 0; data->progress.flags &= ~PGRS_UL_SIZE_KNOWN; } } -/* - * Curl_pgrsUpdate() returns 0 for success or the value returned by the - * progress callback! - */ -int Curl_pgrsUpdate(struct connectdata *conn) +/* returns the average speed in bytes / second */ +static curl_off_t trspeed(curl_off_t size, /* number of bytes */ + curl_off_t us) /* microseconds */ { - struct timeval now; - int result; - 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; - curl_off_t timespent; - struct Curl_easy *data = conn->data; - int nowindex = data->progress.speeder_c% CURR_TIME; - int checkindex; - int countindex; /* amount of seconds stored in the speeder array */ - 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; - bool shownow=FALSE; - - now = Curl_tvnow(); /* what time is it */ - - /* The time spent so far (from the start) */ - data->progress.timespent = curlx_tvdiff_secs(now, data->progress.start); - timespent = (curl_off_t)data->progress.timespent; + 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; +} - /* The average download speed this far */ - data->progress.dlspeed = (curl_off_t) - ((double)data->progress.downloaded/ - (data->progress.timespent>0?data->progress.timespent:1)); +/* returns TRUE if it is 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 average upload speed this far */ - data->progress.ulspeed = (curl_off_t) - ((double)data->progress.uploaded/ - (data->progress.timespent>0?data->progress.timespent:1)); + /* The time spent so far (from the start) in microseconds */ + p->timespent = Curl_timediff_us(now, p->start); + p->dl.speed = trspeed(p->dl.cur_size, p->timespent); + p->ul.speed = trspeed(p->ul.cur_size, p->timespent); /* Calculations done at most once a second, unless end is reached */ - if(data->progress.lastshow != now.tv_sec) { - shownow = TRUE; + 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; - data->progress.lastshow = now.tv_sec; - - /* Let's do the "current speed" thing, which should use the fastest - of the dl/ul speeds. Store the faster speed at entry 'nowindex'. */ - data->progress.speeder[ nowindex ] = - data->progress.downloaded>data->progress.uploaded? - data->progress.downloaded:data->progress.uploaded; + /* Let's do the "current speed" thing, with the dl + ul speeds + combined. Store the speed at entry 'nowindex'. */ + p->speeder[ nowindex ] = p->dl.cur_size + p->ul.cur_size; /* remember the exact time for this moment */ - data->progress.speeder_time [ nowindex ] = now; + 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! */ - data->progress.speeder_c++; + 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 = ((data->progress.speeder_c>=CURR_TIME)? - CURR_TIME:data->progress.speeder_c) - 1; + after two seconds we have 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 */ + /* first of all, we do not do this if there is no counted seconds yet */ if(countindex) { - time_t span_ms; + 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 = (data->progress.speeder_c>=CURR_TIME)? - data->progress.speeder_c%CURR_TIME:0; + checkindex = (p->speeder_c >= CURR_TIME) ? p->speeder_c%CURR_TIME : 0; /* Figure out the exact time for the time span */ - span_ms = Curl_tvdiff(now, - data->progress.speeder_time[checkindex]); + span_ms = Curl_timediff(now, p->speeder_time[checkindex]); if(0 == span_ms) - span_ms=1; /* at least one millisecond MUST have passed */ + span_ms = 1; /* at least one millisecond MUST have passed */ /* Calculate the average speed the last 'span_ms' milliseconds */ - { - curl_off_t amount = data->progress.speeder[nowindex]- - data->progress.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 */ - data->progress.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 */ - data->progress.current_speed = amount*CURL_OFF_T_C(1000)/span_ms; - } + 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 main average */ - data->progress.current_speed = - (data->progress.ulspeed>data->progress.dlspeed)? - data->progress.ulspeed:data->progress.dlspeed; + /* the first second we use the average */ + p->current_speed = p->ul.speed + p->dl.speed; } /* Calculations end */ + return timetoshow; +} - if(!(data->progress.flags & PGRS_HIDE)) { - /* progress meter has not been shut off */ - - if(data->set.fxferinfo) { - /* There's a callback set, call that */ - result= data->set.fxferinfo(data->set.progress_client, - data->progress.size_dl, - data->progress.downloaded, - data->progress.size_ul, - data->progress.uploaded); - if(result) - failf(data, "Callback aborted"); - return result; - } - else if(data->set.fprogress) { - /* The older deprecated callback is set, call that */ - 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); - if(result) - failf(data, "Callback aborted"); - return result; - } +#ifndef CURL_DISABLE_PROGRESS_METER - if(!shownow) - /* only show the internal progress meter once per second */ - return 0; +struct pgrs_estimate { + curl_off_t secs; + curl_off_t percent; +}; - /* If there's no external callback set, use internal code to show - progress */ +static curl_off_t pgrs_est_percent(curl_off_t total, curl_off_t cur) +{ + if(total > CURL_OFF_T_C(10000)) + return cur / (total/CURL_OFF_T_C(100)); + else if(total > CURL_OFF_T_C(0)) + return (cur*100) / total; + return 0; +} - 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 */ - } +static void pgrs_estimates(struct pgrs_dir *d, + bool total_known, + struct pgrs_estimate *est) +{ + est->secs = 0; + est->percent = 0; + if(total_known && (d->speed > CURL_OFF_T_C(0))) { + est->secs = d->total_size / d->speed; + est->percent = pgrs_est_percent(d->total_size, d->cur_size); + } +} - /* 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; - } +static void progress_meter(struct Curl_easy *data) +{ + struct Progress *p = &data->progress; + char max5[6][10]; + struct pgrs_estimate dl_estm; + struct pgrs_estimate ul_estm; + struct pgrs_estimate total_estm; + curl_off_t total_cur_size; + curl_off_t total_expected_size; + char time_left[10]; + char time_total[10]; + char time_spent[10]; + curl_off_t cur_secs = (curl_off_t)p->timespent/1000000; /* seconds */ - /* ... 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; + if(!(p->flags & PGRS_HEADERS_OUT)) { + if(data->state.resume_from) { + fprintf(data->set.err, + "** Resuming transfer from byte position %" FMT_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"); + p->flags |= PGRS_HEADERS_OUT; /* headers are shown */ + } - /* Now figure out which of them is slower and use that one for the - total estimate! */ - total_estimate = ulestimate>dlestimate?ulestimate:dlestimate; + /* Figure out the estimated time of arrival for upload and download */ + pgrs_estimates(&p->ul, (p->flags & PGRS_UL_SIZE_KNOWN), &ul_estm); + pgrs_estimates(&p->dl, (p->flags & PGRS_DL_SIZE_KNOWN), &dl_estm); + + /* Since both happen at the same time, total expected duration is max. */ + total_estm.secs = CURLMAX(ul_estm.secs, dl_estm.secs); + /* create the three time strings */ + time2str(time_left, total_estm.secs > 0 ? (total_estm.secs - cur_secs) : 0); + time2str(time_total, total_estm.secs); + time2str(time_spent, cur_secs); + + /* Get the total amount of data expected to get transferred */ + total_expected_size = + ((p->flags & PGRS_UL_SIZE_KNOWN) ? p->ul.total_size : p->ul.cur_size) + + ((p->flags & PGRS_DL_SIZE_KNOWN) ? p->dl.total_size : p->dl.cur_size); + + /* We have transferred this much so far */ + total_cur_size = p->dl.cur_size + p->ul.cur_size; + + /* Get the percentage of data transferred so far */ + total_estm.percent = pgrs_est_percent(total_expected_size, total_cur_size); + + fprintf(data->set.err, + "\r" + "%3" FMT_OFF_T " %s " + "%3" FMT_OFF_T " %s " + "%3" FMT_OFF_T " %s %s %s %s %s %s %s", + total_estm.percent, /* 3 letters */ /* total % */ + max5data(total_expected_size, max5[2]), /* total size */ + dl_estm.percent, /* 3 letters */ /* rcvd % */ + max5data(p->dl.cur_size, max5[0]), /* rcvd size */ + ul_estm.percent, /* 3 letters */ /* xfer % */ + max5data(p->ul.cur_size, max5[1]), /* xfer size */ + max5data(p->dl.speed, max5[3]), /* avrg dl speed */ + max5data(p->ul.speed, max5[4]), /* avrg ul speed */ + time_total, /* 8 letters */ /* total time */ + time_spent, /* 8 letters */ /* time spent */ + time_left, /* 8 letters */ /* time left */ + max5data(p->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 - /* 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); +/* + * Curl_pgrsUpdate() returns 0 for success or the value returned by the + * progress callback! + */ +static int pgrsupdate(struct Curl_easy *data, bool showprogress) +{ + if(!(data->progress.flags & PGRS_HIDE)) { + if(data->set.fxferinfo) { + int result; + /* There is a callback set, call that */ + Curl_set_in_callback(data, true); + result = data->set.fxferinfo(data->set.progress_client, + data->progress.dl.total_size, + data->progress.dl.cur_size, + data->progress.ul.total_size, + data->progress.ul.cur_size); + Curl_set_in_callback(data, false); + 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.dl.total_size, + (double)data->progress.dl.cur_size, + (double)data->progress.ul.total_size, + (double)data->progress.ul.cur_size); + Curl_set_in_callback(data, false); + if(result != CURL_PROGRESSFUNC_CONTINUE) { + if(result) + failf(data, "Callback aborted"); + return result; + } + } - /* We have transferred this much so far */ - total_transfer = data->progress.downloaded + data->progress.uploaded; + if(showprogress) + progress_meter(data); + } - /* 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; + return 0; +} - 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]) /* current speed */ - ); - - /* we flush the output stream to make it appear as soon as possible */ - fflush(data->set.err); - - } /* !(data->progress.flags & PGRS_HIDE) */ +int Curl_pgrsUpdate(struct Curl_easy *data) +{ + struct curltime now = Curl_now(); /* what time is it */ + bool showprogress = progress_calc(data, now); + return pgrsupdate(data, showprogress); +} - return 0; +/* + * Update all progress, do not do progress meter/callbacks. + */ +void Curl_pgrsUpdate_nometer(struct Curl_easy *data) +{ + struct curltime now = Curl_now(); /* what time is it */ + (void)progress_calc(data, now); } diff --git a/contrib/curl/lib/progress.h b/contrib/curl/lib/progress.h index 155ff04fe0..04a8f5bce9 100644 --- a/contrib/curl/lib/progress.h +++ b/contrib/curl/lib/progress.h @@ -7,11 +7,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2014, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -20,6 +20,8 @@ * 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" @@ -28,7 +30,8 @@ typedef enum { TIMER_NONE, TIMER_STARTOP, - TIMER_STARTSINGLE, + TIMER_STARTSINGLE, /* start of transfer, might get queued */ + TIMER_POSTQUEUE, /* start, immediately after dequeue */ TIMER_NAMELOOKUP, TIMER_CONNECT, TIMER_APPCONNECT, @@ -40,38 +43,35 @@ typedef enum { TIMER_LAST /* must be last */ } timerid; -int Curl_pgrsDone(struct connectdata *); +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); -void Curl_pgrsSetDownloadCounter(struct Curl_easy *data, curl_off_t size); -void Curl_pgrsSetUploadCounter(struct Curl_easy *data, curl_off_t size); -int Curl_pgrsUpdate(struct connectdata *); -void Curl_pgrsResetTimesSizes(struct Curl_easy *data); -void Curl_pgrsTime(struct Curl_easy *data, timerid timer); -long Curl_pgrsLimitWaitTime(curl_off_t cursize, - curl_off_t startsize, - curl_off_t limit, - struct timeval start, - struct timeval now); -/* Don't show progress for sizes smaller than: */ -#define LEAST_SIZE_PROGRESS BUFSIZE +/* 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_pgrsUpdate_nometer(struct Curl_easy *data); -#define PROGRESS_DOWNLOAD (1<<0) -#define PROGRESS_UPLOAD (1<<1) -#define PROGRESS_DOWN_AND_UP (PROGRESS_UPLOAD | PROGRESS_DOWNLOAD) +void Curl_pgrsResetTransferSizes(struct Curl_easy *data); +struct curltime Curl_pgrsTime(struct Curl_easy *data, timerid timer); +timediff_t Curl_pgrsLimitWaitTime(struct pgrs_dir *d, + curl_off_t speed_limit, + 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_SHOW_DL (1<<0) -#define PGRS_SHOW_UL (1<<1) -#define PGRS_DONE_DL (1<<2) -#define PGRS_DONE_UL (1<<3) #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/contrib/curl/lib/psl.c b/contrib/curl/lib/psl.c new file mode 100644 index 0000000000..0b88b05b4c --- /dev/null +++ b/contrib/curl/lib/psl.c @@ -0,0 +1,113 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#include + +#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/contrib/curl/lib/psl.h b/contrib/curl/lib/psl.h new file mode 100644 index 0000000000..23cfa921c4 --- /dev/null +++ b/contrib/curl/lib/psl.h @@ -0,0 +1,49 @@ +#ifndef HEADER_PSL_H +#define HEADER_PSL_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#ifdef USE_LIBPSL +#include + +#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/contrib/curl/lib/rand.c b/contrib/curl/lib/rand.c index 4da37b9dc4..8d55e260a4 100644 --- a/contrib/curl/lib/rand.c +++ b/contrib/curl/lib/rand.c @@ -5,11 +5,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2016, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -18,94 +18,182 @@ * 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 + #ifdef HAVE_FCNTL_H #include #endif +#ifdef HAVE_ARPA_INET_H +#include +#endif #include +#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" -static CURLcode randit(struct Curl_easy *data, unsigned int *rnd) +#ifdef _WIN32 + +#if defined(_WIN32_WINNT) && _WIN32_WINNT >= 0x600 && \ + !defined(CURL_WINDOWS_UWP) +# define HAVE_WIN_BCRYPTGENRANDOM +# include +# 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 +# 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 + +#if !defined(USE_SSL) +/* ---- possibly non-cryptographic version following ---- */ +static CURLcode weak_random(struct Curl_easy *data, + unsigned char *entropy, + size_t length) /* always 4, size of int */ { unsigned int r; - CURLcode result = CURLE_OK; - static unsigned int randseed; - static bool seeded = FALSE; + DEBUGASSERT(length == sizeof(int)); -#ifdef CURLDEBUG - char *force_entropy = getenv("CURL_ENTROPY"); - if(force_entropy) { + /* Trying cryptographically secure functions first */ +#ifdef _WIN32 + (void)data; + { + CURLcode result = Curl_win32_random(entropy, length); + if(result != CURLE_NOT_BUILT_IN) + return result; + } +#endif + +#if defined(HAVE_ARC4RANDOM) + (void)data; + r = (unsigned int)arc4random(); + memcpy(entropy, &r, length); +#else + infof(data, "WARNING: using weak random seed"); + { + static unsigned int randseed; + static bool seeded = FALSE; + unsigned int rnd; if(!seeded) { - size_t elen = strlen(force_entropy); - size_t clen = sizeof(randseed); - size_t min = elen < clen ? elen : clen; - memcpy((char *)&randseed, force_entropy, min); + struct curltime now = Curl_now(); + 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; } - else - randseed++; - *rnd = randseed; - return CURLE_OK; + + /* Return an unsigned 32-bit pseudo-random number. */ + r = randseed = randseed * 1103515245 + 12345; + rnd = (r << 16) | ((r >> 16) & 0xFFFF); + memcpy(entropy, &rnd, length); } +#endif + 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 funtion in the TLS backend do the non crypto - version, otherwise return result */ - return result; +#ifdef USE_SSL +#define _random(x,y,z) Curl_ssl_random(x,y,z) +#else +#define _random(x,y,z) weak_random(x,y,z) +#endif - /* ---- non-cryptographic version following ---- */ +static CURLcode randit(struct Curl_easy *data, unsigned int *rnd, + bool env_override) +{ +#ifdef DEBUGBUILD + if(env_override) { + char *force_entropy = getenv("CURL_ENTROPY"); + if(force_entropy) { + static unsigned int randseed; + static bool seeded = FALSE; -#ifdef RANDOM_FILE - 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)) + 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; - close(fd); + } + else + randseed++; + *rnd = randseed; + return CURLE_OK; } } +#else + (void)env_override; #endif - if(!seeded) { - struct timeval now = curlx_tvnow(); - infof(data, "WARNING: Using weak random seed\n"); - 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; - } - - /* Return an unsigned 32-bit pseudo-random number. */ - r = randseed = randseed * 1103515245 + 12345; - *rnd = (r << 16) | ((r >> 16) & 0xFFFF); - return CURLE_OK; + /* data may be NULL! */ + return _random(data, (unsigned char *)rnd, sizeof(*rnd)); } /* - * Curl_rand() stores 'num' number of random unsigned integers in the buffer - * 'rndptr' points to. + * 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 (Gskit, PolarSSL or mbedTLS), this function will use - * "weak" random. + * 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. @@ -115,18 +203,102 @@ static CURLcode randit(struct Curl_easy *data, unsigned int *rnd) * */ -CURLcode Curl_rand(struct Curl_easy *data, unsigned int *rndptr, - unsigned int num) +CURLcode Curl_rand_bytes(struct Curl_easy *data, +#ifdef DEBUGBUILD + bool env_override, +#endif + unsigned char *rnd, size_t num) { CURLcode result = CURLE_BAD_FUNCTION_ARGUMENT; - unsigned int i; +#ifndef DEBUGBUILD + const bool env_override = FALSE; +#endif + + DEBUGASSERT(num); - assert(num > 0); + while(num) { + unsigned int r; + size_t left = num < sizeof(unsigned int) ? num : sizeof(unsigned int); - for(i = 0; i < num; i++) { - result = randit(data, rndptr++); + result = randit(data, &r, env_override); 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! */ + DEBUGF(infof(data, "invalid buffer size with Curl_rand_hex")); + 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 unsigned int alnumspace = sizeof(alnum) - 1; + unsigned int r; + DEBUGASSERT(num > 1); + + num--; /* save one for null-termination */ + + while(num) { + do { + result = randit(data, &r, TRUE); + if(result) + return result; + } while(r >= (UINT_MAX - UINT_MAX % alnumspace)); + + *rnd++ = (unsigned char)alnum[r % alnumspace]; + num--; + } + *rnd = 0; + return result; } diff --git a/contrib/curl/lib/rand.h b/contrib/curl/lib/rand.h index 0f8986120e..2ba60e7297 100644 --- a/contrib/curl/lib/rand.h +++ b/contrib/curl/lib/rand.h @@ -7,11 +7,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2016, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -20,24 +20,41 @@ * 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_bytes(struct Curl_easy *data, +#ifdef DEBUGBUILD + bool allow_env_override, +#endif + unsigned char *rnd, size_t num); + +#ifdef DEBUGBUILD +#define Curl_rand(a,b,c) Curl_rand_bytes((a), TRUE, (b), (c)) +#else +#define Curl_rand(a,b,c) Curl_rand_bytes((a), (b), (c)) +#endif + /* - * Curl_rand() stores 'num' number of random unsigned integers in the buffer - * 'rnd' points to. - * - * If libcurl is built without TLS support or with a TLS backend that lacks a - * proper random API (Gskit, PolarSSL 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! - * + * 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(struct Curl_easy *data, unsigned int *rnd, - unsigned int num); +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/contrib/curl/lib/rename.c b/contrib/curl/lib/rename.c new file mode 100644 index 0000000000..8715a4306d --- /dev/null +++ b/contrib/curl/lib/rename.c @@ -0,0 +1,73 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "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 does not overwrite, so we cannot 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/contrib/curl/lib/dotdot.h b/contrib/curl/lib/rename.h similarity index 74% rename from contrib/curl/lib/dotdot.h rename to contrib/curl/lib/rename.h index fac8e6f2ad..04440820c5 100644 --- a/contrib/curl/lib/dotdot.h +++ b/contrib/curl/lib/rename.h @@ -1,5 +1,5 @@ -#ifndef HEADER_CURL_DOTDOT_H -#define HEADER_CURL_DOTDOT_H +#ifndef HEADER_CURL_RENAME_H +#define HEADER_CURL_RENAME_H /*************************************************************************** * _ _ ____ _ * Project ___| | | | _ \| | @@ -7,11 +7,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2014, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -20,6 +20,10 @@ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY * KIND, either express or implied. * + * SPDX-License-Identifier: curl + * ***************************************************************************/ -char *Curl_dedotdotify(const char *input); -#endif + +int Curl_rename(const char *oldpath, const char *newpath); + +#endif /* HEADER_CURL_RENAME_H */ diff --git a/contrib/curl/lib/request.c b/contrib/curl/lib/request.c new file mode 100644 index 0000000000..310e4eac00 --- /dev/null +++ b/contrib/curl/lib/request.c @@ -0,0 +1,479 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#include "urldata.h" +#include "cfilters.h" +#include "dynbuf.h" +#include "doh.h" +#include "multiif.h" +#include "progress.h" +#include "request.h" +#include "sendf.h" +#include "transfer.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" + +void Curl_req_init(struct SingleRequest *req) +{ + memset(req, 0, sizeof(*req)); +} + +CURLcode Curl_req_soft_reset(struct SingleRequest *req, + struct Curl_easy *data) +{ + CURLcode result; + + req->done = FALSE; + req->upload_done = FALSE; + req->upload_aborted = FALSE; + req->download_done = FALSE; + req->eos_written = FALSE; + req->eos_read = FALSE; + req->eos_sent = FALSE; + req->ignorebody = FALSE; + req->shutdown = FALSE; + req->bytecount = 0; + req->writebytecount = 0; + req->header = TRUE; /* assume header */ + req->headerline = 0; + req->headerbytecount = 0; + req->allheadercount = 0; + req->deductheadercount = 0; + + result = Curl_client_start(data); + if(result) + return result; + + if(!req->sendbuf_init) { + Curl_bufq_init2(&req->sendbuf, data->set.upload_buffer_size, 1, + BUFQ_OPT_SOFT_LIMIT); + req->sendbuf_init = TRUE; + } + else { + Curl_bufq_reset(&req->sendbuf); + if(data->set.upload_buffer_size != req->sendbuf.chunk_size) { + Curl_bufq_free(&req->sendbuf); + Curl_bufq_init2(&req->sendbuf, data->set.upload_buffer_size, 1, + BUFQ_OPT_SOFT_LIMIT); + } + } + + return CURLE_OK; +} + +CURLcode Curl_req_start(struct SingleRequest *req, + struct Curl_easy *data) +{ + req->start = Curl_now(); + return Curl_req_soft_reset(req, data); +} + +static CURLcode req_flush(struct Curl_easy *data); + +CURLcode Curl_req_done(struct SingleRequest *req, + struct Curl_easy *data, bool aborted) +{ + (void)req; + if(!aborted) + (void)req_flush(data); + Curl_client_reset(data); +#ifndef CURL_DISABLE_DOH + Curl_doh_close(data); +#endif + return CURLE_OK; +} + +void Curl_req_hard_reset(struct SingleRequest *req, struct Curl_easy *data) +{ + struct curltime t0 = {0, 0}; + + /* This is a bit ugly. `req->p` is a union and we assume we can + * free this safely without leaks. */ + Curl_safefree(req->p.ftp); + Curl_safefree(req->newurl); + Curl_client_reset(data); + if(req->sendbuf_init) + Curl_bufq_reset(&req->sendbuf); + +#ifndef CURL_DISABLE_DOH + Curl_doh_close(data); +#endif + /* Can no longer memset() this struct as we need to keep some state */ + req->size = -1; + req->maxdownload = -1; + req->bytecount = 0; + req->writebytecount = 0; + req->start = t0; + req->headerbytecount = 0; + req->allheadercount = 0; + req->deductheadercount = 0; + req->headerline = 0; + req->offset = 0; + req->httpcode = 0; + req->keepon = 0; + req->upgr101 = UPGR101_INIT; + req->timeofdoc = 0; + req->location = NULL; + req->newurl = NULL; +#ifndef CURL_DISABLE_COOKIES + req->setcookies = 0; +#endif + req->header = FALSE; + req->content_range = FALSE; + req->download_done = FALSE; + req->eos_written = FALSE; + req->eos_read = FALSE; + req->eos_sent = FALSE; + req->upload_done = FALSE; + req->upload_aborted = FALSE; + req->ignorebody = FALSE; + req->http_bodyless = FALSE; + req->chunk = FALSE; + req->ignore_cl = FALSE; + req->upload_chunky = FALSE; + req->getheader = FALSE; + req->no_body = data->set.opt_no_body; + req->authneg = FALSE; + req->shutdown = FALSE; +#ifdef USE_HYPER + req->bodywritten = FALSE; +#endif +} + +void Curl_req_free(struct SingleRequest *req, struct Curl_easy *data) +{ + /* This is a bit ugly. `req->p` is a union and we assume we can + * free this safely without leaks. */ + Curl_safefree(req->p.ftp); + Curl_safefree(req->newurl); + if(req->sendbuf_init) + Curl_bufq_free(&req->sendbuf); + Curl_client_cleanup(data); + +#ifndef CURL_DISABLE_DOH + Curl_doh_cleanup(data); +#endif +} + +static CURLcode xfer_send(struct Curl_easy *data, + const char *buf, size_t blen, + size_t hds_len, size_t *pnwritten) +{ + CURLcode result = CURLE_OK; + bool eos = FALSE; + + *pnwritten = 0; + DEBUGASSERT(hds_len <= blen); +#ifdef DEBUGBUILD + { + /* Allow debug builds to override this logic to force short initial + sends */ + size_t body_len = blen - hds_len; + char *p = getenv("CURL_SMALLREQSEND"); + if(p) { + size_t body_small = (size_t)strtoul(p, NULL, 10); + if(body_small && body_small < body_len) + blen = hds_len + body_small; + } + } +#endif + /* Make sure this does not send more body bytes than what the max send + speed says. The headers do not count to the max speed. */ + if(data->set.max_send_speed) { + size_t body_bytes = blen - hds_len; + if((curl_off_t)body_bytes > data->set.max_send_speed) + blen = hds_len + (size_t)data->set.max_send_speed; + } + + if(data->req.eos_read && + (Curl_bufq_is_empty(&data->req.sendbuf) || + Curl_bufq_len(&data->req.sendbuf) == blen)) { + DEBUGF(infof(data, "sending last upload chunk of %zu bytes", blen)); + eos = TRUE; + } + result = Curl_xfer_send(data, buf, blen, eos, pnwritten); + if(!result) { + if(eos && (blen == *pnwritten)) + data->req.eos_sent = TRUE; + if(*pnwritten) { + if(hds_len) + Curl_debug(data, CURLINFO_HEADER_OUT, (char *)buf, + CURLMIN(hds_len, *pnwritten)); + if(*pnwritten > hds_len) { + size_t body_len = *pnwritten - hds_len; + Curl_debug(data, CURLINFO_DATA_OUT, (char *)buf + hds_len, body_len); + data->req.writebytecount += body_len; + Curl_pgrsSetUploadCounter(data, data->req.writebytecount); + } + } + } + return result; +} + +static CURLcode req_send_buffer_flush(struct Curl_easy *data) +{ + CURLcode result = CURLE_OK; + const unsigned char *buf; + size_t blen; + + while(Curl_bufq_peek(&data->req.sendbuf, &buf, &blen)) { + size_t nwritten, hds_len = CURLMIN(data->req.sendbuf_hds_len, blen); + result = xfer_send(data, (const char *)buf, blen, hds_len, &nwritten); + if(result) + break; + + Curl_bufq_skip(&data->req.sendbuf, nwritten); + if(hds_len) { + data->req.sendbuf_hds_len -= CURLMIN(hds_len, nwritten); + } + /* leave if we could not send all. Maybe network blocking or + * speed limits on transfer */ + if(nwritten < blen) + break; + } + return result; +} + +CURLcode Curl_req_set_upload_done(struct Curl_easy *data) +{ + DEBUGASSERT(!data->req.upload_done); + data->req.upload_done = TRUE; + data->req.keepon &= ~(KEEP_SEND|KEEP_SEND_TIMED); /* we are done sending */ + + Curl_pgrsTime(data, TIMER_POSTRANSFER); + Curl_creader_done(data, data->req.upload_aborted); + + if(data->req.upload_aborted) { + Curl_bufq_reset(&data->req.sendbuf); + if(data->req.writebytecount) + infof(data, "abort upload after having sent %" FMT_OFF_T " bytes", + data->req.writebytecount); + else + infof(data, "abort upload"); + } + else if(data->req.writebytecount) + infof(data, "upload completely sent off: %" FMT_OFF_T " bytes", + data->req.writebytecount); + else if(!data->req.download_done) { + DEBUGASSERT(Curl_bufq_is_empty(&data->req.sendbuf)); + infof(data, Curl_creader_total_length(data) ? + "We are completely uploaded and fine" : + "Request completely sent off"); + } + + return Curl_xfer_send_close(data); +} + +static CURLcode req_flush(struct Curl_easy *data) +{ + CURLcode result; + + if(!data || !data->conn) + return CURLE_FAILED_INIT; + + if(!Curl_bufq_is_empty(&data->req.sendbuf)) { + result = req_send_buffer_flush(data); + if(result) + return result; + if(!Curl_bufq_is_empty(&data->req.sendbuf)) { + DEBUGF(infof(data, "Curl_req_flush(len=%zu) -> EAGAIN", + Curl_bufq_len(&data->req.sendbuf))); + return CURLE_AGAIN; + } + } + else if(Curl_xfer_needs_flush(data)) { + DEBUGF(infof(data, "Curl_req_flush(), xfer send_pending")); + return Curl_xfer_flush(data); + } + + if(data->req.eos_read && !data->req.eos_sent) { + char tmp; + size_t nwritten; + result = xfer_send(data, &tmp, 0, 0, &nwritten); + if(result) + return result; + DEBUGASSERT(data->req.eos_sent); + } + + if(!data->req.upload_done && data->req.eos_read && data->req.eos_sent) { + DEBUGASSERT(Curl_bufq_is_empty(&data->req.sendbuf)); + if(data->req.shutdown) { + bool done; + result = Curl_xfer_send_shutdown(data, &done); + if(result && data->req.shutdown_err_ignore) { + infof(data, "Shutdown send direction error: %d. Broken server? " + "Proceeding as if everything is ok.", result); + result = CURLE_OK; + done = TRUE; + } + + if(result) + return result; + if(!done) + return CURLE_AGAIN; + } + return Curl_req_set_upload_done(data); + } + return CURLE_OK; +} + +static ssize_t add_from_client(void *reader_ctx, + unsigned char *buf, size_t buflen, + CURLcode *err) +{ + struct Curl_easy *data = reader_ctx; + size_t nread; + bool eos; + + *err = Curl_client_read(data, (char *)buf, buflen, &nread, &eos); + if(*err) + return -1; + if(eos) + data->req.eos_read = TRUE; + return (ssize_t)nread; +} + +#ifndef USE_HYPER + +static CURLcode req_send_buffer_add(struct Curl_easy *data, + const char *buf, size_t blen, + size_t hds_len) +{ + CURLcode result = CURLE_OK; + ssize_t n; + n = Curl_bufq_write(&data->req.sendbuf, + (const unsigned char *)buf, blen, &result); + if(n < 0) + return result; + /* We rely on a SOFTLIMIT on sendbuf, so it can take all data in */ + DEBUGASSERT((size_t)n == blen); + data->req.sendbuf_hds_len += hds_len; + return CURLE_OK; +} + +CURLcode Curl_req_send(struct Curl_easy *data, struct dynbuf *req) +{ + CURLcode result; + const char *buf; + size_t blen, nwritten; + + if(!data || !data->conn) + return CURLE_FAILED_INIT; + + buf = Curl_dyn_ptr(req); + blen = Curl_dyn_len(req); + if(!Curl_creader_total_length(data)) { + /* Request without body. Try to send directly from the buf given. */ + data->req.eos_read = TRUE; + result = xfer_send(data, buf, blen, blen, &nwritten); + if(result) + return result; + buf += nwritten; + blen -= nwritten; + } + + if(blen) { + /* Either we have a request body, or we could not send the complete + * request in one go. Buffer the remainder and try to add as much + * body bytes as room is left in the buffer. Then flush. */ + result = req_send_buffer_add(data, buf, blen, blen); + if(result) + return result; + + return Curl_req_send_more(data); + } + return CURLE_OK; +} +#endif /* !USE_HYPER */ + +bool Curl_req_sendbuf_empty(struct Curl_easy *data) +{ + return !data->req.sendbuf_init || Curl_bufq_is_empty(&data->req.sendbuf); +} + +bool Curl_req_want_send(struct Curl_easy *data) +{ + /* Not done and + * - KEEP_SEND and not PAUSEd. + * - or request has buffered data to send + * - or transfer connection has pending data to send */ + return !data->req.done && + (((data->req.keepon & KEEP_SENDBITS) == KEEP_SEND) || + !Curl_req_sendbuf_empty(data) || + Curl_xfer_needs_flush(data)); +} + +bool Curl_req_done_sending(struct Curl_easy *data) +{ + return data->req.upload_done && !Curl_req_want_send(data); +} + +CURLcode Curl_req_send_more(struct Curl_easy *data) +{ + CURLcode result; + + /* Fill our send buffer if more from client can be read. */ + if(!data->req.upload_aborted && + !data->req.eos_read && + !(data->req.keepon & KEEP_SEND_PAUSE) && + !Curl_bufq_is_full(&data->req.sendbuf)) { + ssize_t nread = Curl_bufq_sipn(&data->req.sendbuf, 0, + add_from_client, data, &result); + if(nread < 0 && result != CURLE_AGAIN) + return result; + } + + result = req_flush(data); + if(result == CURLE_AGAIN) + result = CURLE_OK; + + return result; +} + +CURLcode Curl_req_abort_sending(struct Curl_easy *data) +{ + if(!data->req.upload_done) { + Curl_bufq_reset(&data->req.sendbuf); + data->req.upload_aborted = TRUE; + /* no longer KEEP_SEND and KEEP_SEND_PAUSE */ + data->req.keepon &= ~KEEP_SENDBITS; + return Curl_req_set_upload_done(data); + } + return CURLE_OK; +} + +CURLcode Curl_req_stop_send_recv(struct Curl_easy *data) +{ + /* stop receiving and ALL sending as well, including PAUSE and HOLD. + * We might still be paused on receive client writes though, so + * keep those bits around. */ + data->req.keepon &= ~(KEEP_RECV|KEEP_SENDBITS); + return Curl_req_abort_sending(data); +} diff --git a/contrib/curl/lib/request.h b/contrib/curl/lib/request.h new file mode 100644 index 0000000000..bb72247788 --- /dev/null +++ b/contrib/curl/lib/request.h @@ -0,0 +1,251 @@ +#ifndef HEADER_CURL_REQUEST_H +#define HEADER_CURL_REQUEST_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +/* This file is for lib internal stuff */ + +#include "curl_setup.h" + +#include "bufq.h" + +/* forward declarations */ +struct UserDefined; +#ifndef CURL_DISABLE_DOH +struct doh_probes; +#endif + +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 */ +}; + + +/* + * 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 is 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 */ + + 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 does not 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 httpversion; /* Version in response (09, 10, 11, etc.) */ + int httpcode; /* error code from the 'HTTP/1.? XXX' or + 'RTSP/1.? XXX' line */ + int keepon; + enum upgrade101 upgr101; /* 101 upgrade state */ + + /* Client Writer stack, handles transfer- and content-encodings, protocol + * checks, pausing by client callbacks. */ + struct Curl_cwriter *writer_stack; + /* Client Reader stack, handles transfer- and content-encodings, protocol + * checks, pausing by client callbacks. */ + struct Curl_creader *reader_stack; + struct bufq sendbuf; /* data which needs to be send to the server */ + size_t sendbuf_hds_len; /* amount of header bytes in sendbuf */ + time_t timeofdoc; + 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 */ + + /* Allocated protocol-specific data. Each protocol handler makes sure this + points to data it needs. */ + union { + struct FILEPROTO *file; + struct FTP *ftp; + 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 doh_probes *doh; /* DoH specific data for this request */ +#endif +#ifndef CURL_DISABLE_COOKIES + unsigned char setcookies; +#endif + BIT(header); /* incoming data has HTTP header */ + BIT(done); /* request is done, e.g. no more send/recv should + * happen. This can be TRUE before `upload_done` or + * `download_done` is TRUE. */ + BIT(content_range); /* set TRUE if Content-Range: was found */ + BIT(download_done); /* set to TRUE when download is complete */ + BIT(eos_written); /* iff EOS has been written to client */ + BIT(eos_read); /* iff EOS has been read from the client */ + BIT(eos_sent); /* iff EOS has been sent to the server */ + BIT(rewind_read); /* iff reader needs rewind at next start */ + BIT(upload_done); /* set to TRUE when all request data has been sent */ + BIT(upload_aborted); /* set to TRUE when upload was aborted. Will also + * show `upload_done` as TRUE. */ + 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(resp_trailer); /* response carried 'Trailer:' header field */ + 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(no_body); /* the response has no body */ + 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. */ + BIT(sendbuf_init); /* sendbuf is initialized */ + BIT(shutdown); /* request end will shutdown connection */ + BIT(shutdown_err_ignore); /* errors in shutdown will not fail request */ +#ifdef USE_HYPER + BIT(bodywritten); +#endif +}; + +/** + * Initialize the state of the request for first use. + */ +void Curl_req_init(struct SingleRequest *req); + +/** + * The request is about to start. Record time and do a soft reset. + */ +CURLcode Curl_req_start(struct SingleRequest *req, + struct Curl_easy *data); + +/** + * The request may continue with a follow up. Reset + * members, but keep start time for overall duration calc. + */ +CURLcode Curl_req_soft_reset(struct SingleRequest *req, + struct Curl_easy *data); + +/** + * The request is done. If not aborted, make sure that buffers are + * flushed to the client. + * @param req the request + * @param data the transfer + * @param aborted TRUE iff the request was aborted/errored + */ +CURLcode Curl_req_done(struct SingleRequest *req, + struct Curl_easy *data, bool aborted); + +/** + * Free the state of the request, not usable afterwards. + */ +void Curl_req_free(struct SingleRequest *req, struct Curl_easy *data); + +/** + * Hard reset the state of the request to virgin state base on + * transfer settings. + */ +void Curl_req_hard_reset(struct SingleRequest *req, struct Curl_easy *data); + +#ifndef USE_HYPER +/** + * Send request headers. If not all could be sent + * they will be buffered. Use `Curl_req_flush()` to make sure + * bytes are really send. + * @param data the transfer making the request + * @param buf the complete header bytes, no body + * @return CURLE_OK (on blocking with *pnwritten == 0) or error. + */ +CURLcode Curl_req_send(struct Curl_easy *data, struct dynbuf *buf); + +#endif /* !USE_HYPER */ + +/** + * TRUE iff the request has sent all request headers and data. + */ +bool Curl_req_done_sending(struct Curl_easy *data); + +/* + * Read more from client and flush all buffered request bytes. + * @return CURLE_OK on success or the error on the sending. + * Never returns CURLE_AGAIN. + */ +CURLcode Curl_req_send_more(struct Curl_easy *data); + +/** + * TRUE iff the request wants to send, e.g. has buffered bytes. + */ +bool Curl_req_want_send(struct Curl_easy *data); + +/** + * TRUE iff the request has no buffered bytes yet to send. + */ +bool Curl_req_sendbuf_empty(struct Curl_easy *data); + +/** + * Stop sending any more request data to the server. + * Will clear the send buffer and mark request sending as done. + */ +CURLcode Curl_req_abort_sending(struct Curl_easy *data); + +/** + * Stop sending and receiving any more request data. + * Will abort sending if not done. + */ +CURLcode Curl_req_stop_send_recv(struct Curl_easy *data); + +/** + * Invoked when all request data has been uploaded. + */ +CURLcode Curl_req_set_upload_done(struct Curl_easy *data); + +#endif /* HEADER_CURL_REQUEST_H */ diff --git a/contrib/curl/lib/rtsp.c b/contrib/curl/lib/rtsp.c index 65c6c3b05d..886e2725ed 100644 --- a/contrib/curl/lib/rtsp.c +++ b/contrib/curl/lib/rtsp.c @@ -5,11 +5,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2016, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -18,11 +18,13 @@ * 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_RTSP +#if !defined(CURL_DISABLE_RTSP) && !defined(USE_HYPER) #include "urldata.h" #include @@ -36,74 +38,69 @@ #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" -/* - * TODO (general) - * -incoming server requests - * -server CSeq counter - * -digest authentication - * -connect thru proxy - * -pipelining? - */ - - -#define RTP_PKT_CHANNEL(p) ((int)((unsigned char)((p)[1]))) - -#define RTP_PKT_LENGTH(p) ((((int)((unsigned char)((p)[2]))) << 8) | \ - ((int)((unsigned char)((p)[3])))) +#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 connectdata *conn, bool *done); -static CURLcode rtsp_done(struct connectdata *conn, CURLcode, bool premature); -static CURLcode rtsp_connect(struct connectdata *conn, bool *done); -static CURLcode rtsp_disconnect(struct connectdata *conn, bool dead); - -static int rtsp_getsock_do(struct connectdata *conn, - curl_socket_t *socks, - int numsocks); +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. - * - * nread: amount of data left after k->str. will be modified if RTP - * data is parsed and k->str is moved up - * readmore: whether or not the RTP parser needs more data right away + * Parse and write out an RTSP response. + * @param data the transfer + * @param conn the connection + * @param buf data read from connection + * @param blen amount of data in buf + * @param is_eos TRUE iff this is the last write + * @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, - ssize_t *nread, - bool *readmore); - -static CURLcode rtsp_setup_connection(struct connectdata *conn); +static CURLcode rtsp_rtp_write_resp(struct Curl_easy *data, + const char *buf, + size_t blen, + bool is_eos); +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 + interface and then we are always _sending_ a request and thus we wait for the single socket to become writable only */ -static int rtsp_getsock_do(struct connectdata *conn, - curl_socket_t *socks, - int numsocks) +static int rtsp_getsock_do(struct Curl_easy *data, struct connectdata *conn, + curl_socket_t *socks) { /* write mode */ - (void)numsocks; /* unused, we trust it to be at least 1 */ + (void)data; socks[0] = conn->sock[FIRSTSOCKET]; return GETSOCK_WRITESOCK(0); } static -CURLcode rtp_client_write(struct connectdata *conn, char *ptr, size_t len); +CURLcode rtp_client_write(struct Curl_easy *data, const char *ptr, size_t len); +static +CURLcode rtsp_parse_transport(struct Curl_easy *data, const char *transport); /* * RTSP handler interface. */ const struct Curl_handler Curl_handler_rtsp = { - "RTSP", /* scheme */ + "rtsp", /* scheme */ rtsp_setup_connection, /* setup_connection */ rtsp_do, /* do_it */ rtsp_done, /* done */ @@ -116,61 +113,58 @@ const struct Curl_handler Curl_handler_rtsp = { ZERO_NULL, /* domore_getsock */ ZERO_NULL, /* perform_getsock */ rtsp_disconnect, /* disconnect */ - rtsp_rtp_readwrite, /* readwrite */ + rtsp_rtp_write_resp, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ + 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 connectdata *conn) +static CURLcode rtsp_setup_connection(struct Curl_easy *data, + struct connectdata *conn) { struct RTSP *rtsp; + (void)conn; - conn->data->req.protop = rtsp = calloc(1, sizeof(struct RTSP)); + 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; } /* - * The server may send us RTP data at any point, and RTSPREQ_RECEIVE does not - * want to block the application forever while receiving a stream. Therefore, - * we cannot assume that an RTSP socket is dead just because it is readable. - * - * Instead, if it is readable, run Curl_connalive() to peek at the socket - * and distinguish between closed and data. + * Function to check on various aspects of a connection. */ -bool Curl_rtsp_connisdead(struct connectdata *check) +static unsigned int rtsp_conncheck(struct Curl_easy *data, + struct connectdata *conn, + unsigned int checks_to_perform) { - int sval; - bool ret_val = TRUE; + unsigned int ret_val = CONNRESULT_NONE; + (void)data; - sval = SOCKET_READABLE(check->sock[FIRSTSOCKET], 0); - if(sval == 0) { - /* timeout */ - ret_val = FALSE; - } - else if(sval & CURL_CSELECT_ERR) { - /* socket is in an error state */ - ret_val = TRUE; - } - else if(sval & CURL_CSELECT_IN) { - /* readable with no error. could still be closed */ - ret_val = !Curl_connalive(check); + 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 connectdata *conn, bool *done) + +static CURLcode rtsp_connect(struct Curl_easy *data, bool *done) { CURLcode httpStatus; - struct Curl_easy *data = conn->data; - httpStatus = Curl_http_connect(conn, done); + httpStatus = Curl_http_connect(data, done); /* Initialize the CSeq if not already done */ if(data->state.rtsp_next_client_CSeq == 0) @@ -178,64 +172,59 @@ static CURLcode rtsp_connect(struct connectdata *conn, bool *done) if(data->state.rtsp_next_server_CSeq == 0) data->state.rtsp_next_server_CSeq = 1; - conn->proto.rtspc.rtp_channel = -1; + data->conn->proto.rtspc.rtp_channel = -1; return httpStatus; } -static CURLcode rtsp_disconnect(struct connectdata *conn, bool dead) +static CURLcode rtsp_disconnect(struct Curl_easy *data, + struct connectdata *conn, bool dead) { (void) dead; - Curl_safefree(conn->proto.rtspc.rtp_buf); + (void) data; + Curl_dyn_free(&conn->proto.rtspc.buf); return CURLE_OK; } -static CURLcode rtsp_done(struct connectdata *conn, +static CURLcode rtsp_done(struct Curl_easy *data, CURLcode status, bool premature) { - struct Curl_easy *data = conn->data; - struct RTSP *rtsp = data->req.protop; + struct RTSP *rtsp = data->req.p.rtsp; CURLcode httpStatus; - long CSeq_sent; - long CSeq_recv; /* Bypass HTTP empty-reply checks on receive */ if(data->set.rtspreq == RTSPREQ_RECEIVE) premature = TRUE; - httpStatus = Curl_http_done(conn, status, premature); + httpStatus = Curl_http_done(data, status, premature); - if(rtsp) { + if(rtsp && !status && !httpStatus) { /* Check the sequence numbers */ - CSeq_sent = rtsp->CSeq_sent; - CSeq_recv = rtsp->CSeq_recv; + 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; } - else if(data->set.rtspreq == RTSPREQ_RECEIVE && - (conn->proto.rtspc.rtp_channel == -1)) { - infof(data, "Got an RTP Receive with a CSeq of %ld\n", CSeq_recv); - /* TODO CPC: Server -> Client logic here */ + 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 connectdata *conn, bool *done) +static CURLcode rtsp_do(struct Curl_easy *data, bool *done) { - struct Curl_easy *data = conn->data; - CURLcode result=CURLE_OK; + struct connectdata *conn = data->conn; + CURLcode result = CURLE_OK; Curl_RtspReq rtspreq = data->set.rtspreq; - struct RTSP *rtsp = data->req.protop; - struct HTTP *http; - Curl_send_buffer *req_buffer; - curl_off_t postsize = 0; /* for ANNOUNCE and SET_PARAMETER */ - curl_off_t putsize = 0; /* for ANNOUNCE and SET_PARAMETER */ + struct RTSP *rtsp = data->req.p.rtsp; + struct dynbuf req_buffer; const char *p_request = NULL; const char *p_session_id = NULL; @@ -250,19 +239,29 @@ static CURLcode rtsp_do(struct connectdata *conn, bool *done) const char *p_userpwd = NULL; *done = TRUE; - - http = &(rtsp->http_wrapper); - /* Assert that no one has changed the RTSP struct in an evil way */ - DEBUGASSERT((void *)http == (void *)rtsp); + /* Initialize a dynamic send buffer */ + Curl_dyn_init(&req_buffer, DYN_RTSP_REQ_HEADER); 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->set.opt_no_body = TRUE; /* most requests don't contain a body */ + data->req.no_body = TRUE; /* most requests do not contain a body */ switch(rtspreq) { default: failf(data, "Got invalid RTSP request"); @@ -272,7 +271,7 @@ static CURLcode rtsp_do(struct connectdata *conn, bool *done) break; case RTSPREQ_DESCRIBE: p_request = "DESCRIBE"; - data->set.opt_no_body = FALSE; + data->req.no_body = FALSE; break; case RTSPREQ_ANNOUNCE: p_request = "ANNOUNCE"; @@ -292,7 +291,7 @@ static CURLcode rtsp_do(struct connectdata *conn, bool *done) case RTSPREQ_GET_PARAMETER: /* GET_PARAMETER's no_body status is determined later */ p_request = "GET_PARAMETER"; - data->set.opt_no_body = FALSE; + data->req.no_body = FALSE; break; case RTSPREQ_SET_PARAMETER: p_request = "SET_PARAMETER"; @@ -302,8 +301,8 @@ static CURLcode rtsp_do(struct connectdata *conn, bool *done) break; case RTSPREQ_RECEIVE: p_request = ""; - /* Treat interleaved RTP as body*/ - data->set.opt_no_body = FALSE; + /* Treat interleaved RTP as body */ + data->req.no_body = FALSE; break; case RTSPREQ_LAST: failf(data, "Got invalid RTSP request: RTSPREQ_LAST"); @@ -311,22 +310,21 @@ static CURLcode rtsp_do(struct connectdata *conn, bool *done) } if(rtspreq == RTSPREQ_RECEIVE) { - Curl_setup_transfer(conn, FIRSTSOCKET, -1, TRUE, - &http->readbytecount, -1, NULL); - - return result; + Curl_xfer_setup1(data, CURL_XFER_RECV, -1, TRUE); + goto out; } p_session_id = data->set.str[STRING_RTSP_SESSION_ID]; if(!p_session_id && - (rtspreq & ~(RTSPREQ_OPTIONS | RTSPREQ_DESCRIBE | RTSPREQ_SETUP))) { + (rtspreq & ~(Curl_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; + result = CURLE_BAD_FUNCTION_ARGUMENT; + goto out; } - /* TODO: proxy? */ - /* 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]; @@ -336,44 +334,46 @@ static CURLcode rtsp_do(struct connectdata *conn, bool *done) } /* Transport Header for SETUP requests */ - p_transport = Curl_checkheaders(conn, "Transport:"); + 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(conn->allocptr.rtsp_transport); + Curl_safefree(data->state.aptr.rtsp_transport); - conn->allocptr.rtsp_transport = + data->state.aptr.rtsp_transport = aprintf("Transport: %s\r\n", data->set.str[STRING_RTSP_TRANSPORT]); - if(!conn->allocptr.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; + result = CURLE_BAD_FUNCTION_ARGUMENT; + goto out; } - p_transport = conn->allocptr.rtsp_transport; + p_transport = data->state.aptr.rtsp_transport; } /* Accept Headers for DESCRIBE requests */ if(rtspreq == RTSPREQ_DESCRIBE) { /* Accept Header */ - p_accept = Curl_checkheaders(conn, "Accept:")? - NULL:"Accept: application/sdp\r\n"; + p_accept = Curl_checkheaders(data, STRCONST("Accept")) ? + NULL : "Accept: application/sdp\r\n"; /* Accept-Encoding header */ - if(!Curl_checkheaders(conn, "Accept-Encoding:") && + if(!Curl_checkheaders(data, STRCONST("Accept-Encoding")) && data->set.str[STRING_ENCODING]) { - Curl_safefree(conn->allocptr.accept_encoding); - conn->allocptr.accept_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(!conn->allocptr.accept_encoding) - return CURLE_OUT_OF_MEMORY; - - p_accept_encoding = conn->allocptr.accept_encoding; + if(!data->state.aptr.accept_encoding) { + result = CURLE_OUT_OF_MEMORY; + goto out; + } + p_accept_encoding = data->state.aptr.accept_encoding; } } @@ -381,31 +381,32 @@ static CURLcode rtsp_do(struct connectdata *conn, bool *done) 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(conn, "User-Agent:") && conn->allocptr.uagent) { - Curl_safefree(conn->allocptr.uagent); - conn->allocptr.uagent = NULL; + if(Curl_checkheaders(data, STRCONST("User-Agent")) && + data->state.aptr.uagent) { + Curl_safefree(data->state.aptr.uagent); } - else if(!Curl_checkheaders(conn, "User-Agent:") && + else if(!Curl_checkheaders(data, STRCONST("User-Agent")) && data->set.str[STRING_USERAGENT]) { - p_uagent = conn->allocptr.uagent; + p_uagent = data->state.aptr.uagent; } /* setup the authentication headers */ - result = Curl_http_output_auth(conn, p_request, p_stream_uri, FALSE); + result = Curl_http_output_auth(data, conn, p_request, HTTPREQ_GET, + p_stream_uri, FALSE); if(result) - return result; + goto out; - p_proxyuserpwd = conn->allocptr.proxyuserpwd; - p_userpwd = conn->allocptr.userpwd; +#ifndef CURL_DISABLE_PROXY + p_proxyuserpwd = data->state.aptr.proxyuserpwd; +#endif + p_userpwd = data->state.aptr.userpwd; /* Referrer */ - Curl_safefree(conn->allocptr.ref); - if(data->change.referer && !Curl_checkheaders(conn, "Referer:")) - conn->allocptr.ref = aprintf("Referer: %s\r\n", data->change.referer); - else - conn->allocptr.ref = NULL; + 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 = conn->allocptr.ref; + p_referrer = data->state.aptr.ref; /* * Range Header @@ -417,329 +418,489 @@ static CURLcode rtsp_do(struct connectdata *conn, bool *done) (rtspreq & (RTSPREQ_PLAY | RTSPREQ_PAUSE | RTSPREQ_RECORD))) { /* Check to see if there is a range set in the custom headers */ - if(!Curl_checkheaders(conn, "Range:") && data->state.range) { - Curl_safefree(conn->allocptr.rangeline); - conn->allocptr.rangeline = aprintf("Range: %s\r\n", data->state.range); - p_range = conn->allocptr.rangeline; + 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(conn, "CSeq:")) { + if(Curl_checkheaders(data, STRCONST("CSeq"))) { failf(data, "CSeq cannot be set as a custom header."); - return CURLE_RTSP_CSEQ_ERROR; + result = CURLE_RTSP_CSEQ_ERROR; + goto out; } - if(Curl_checkheaders(conn, "Session:")) { + if(Curl_checkheaders(data, STRCONST("Session"))) { failf(data, "Session ID cannot be set as a custom header."); - return CURLE_BAD_FUNCTION_ARGUMENT; + result = CURLE_BAD_FUNCTION_ARGUMENT; + goto out; } - /* Initialize a dynamic send buffer */ - req_buffer = Curl_add_buffer_init(); - - if(!req_buffer) - return CURLE_OUT_OF_MEMORY; - result = - Curl_add_bufferf(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); + 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; + goto out; /* * Rather than do a normal alloc line, keep the session_id unformatted * to make comparison easier */ if(p_session_id) { - result = Curl_add_bufferf(req_buffer, "Session: %s\r\n", p_session_id); + result = Curl_dyn_addf(&req_buffer, "Session: %s\r\n", p_session_id); if(result) - return result; + goto out; } /* * Shared HTTP-like options */ - result = Curl_add_bufferf(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 : ""); + 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(conn->allocptr.userpwd); - conn->allocptr.userpwd = NULL; + Curl_safefree(data->state.aptr.userpwd); if(result) - return result; + goto out; if((rtspreq == RTSPREQ_SETUP) || (rtspreq == RTSPREQ_DESCRIBE)) { - result = Curl_add_timecondition(data, req_buffer); + result = Curl_add_timecondition(data, &req_buffer); if(result) - return result; + goto out; } - result = Curl_add_custom_headers(conn, FALSE, req_buffer); + result = Curl_add_custom_headers(data, FALSE, &req_buffer); if(result) - return result; + goto out; if(rtspreq == RTSPREQ_ANNOUNCE || rtspreq == RTSPREQ_SET_PARAMETER || rtspreq == RTSPREQ_GET_PARAMETER) { - - if(data->set.upload) { - putsize = data->state.infilesize; - data->set.httpreq = HTTPREQ_PUT; - + curl_off_t req_clen; /* request content length */ + + if(data->state.upload) { + req_clen = data->state.infilesize; + data->state.httpreq = HTTPREQ_PUT; + result = Curl_creader_set_fread(data, req_clen); + if(result) + goto out; } else { - postsize = (data->state.infilesize != -1)? - data->state.infilesize: - (data->set.postfields? (curl_off_t)strlen(data->set.postfields):0); - data->set.httpreq = HTTPREQ_POST; + if(data->set.postfields) { + size_t plen = strlen(data->set.postfields); + req_clen = (curl_off_t)plen; + result = Curl_creader_set_buf(data, data->set.postfields, plen); + } + else if(data->state.infilesize >= 0) { + req_clen = data->state.infilesize; + result = Curl_creader_set_fread(data, req_clen); + } + else { + req_clen = 0; + result = Curl_creader_set_null(data); + } + if(result) + goto out; } - if(putsize > 0 || postsize > 0) { + if(req_clen > 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(conn, "Content-Length:")) { - result = Curl_add_bufferf(req_buffer, - "Content-Length: %" CURL_FORMAT_CURL_OFF_T"\r\n", - (data->set.upload ? putsize : postsize)); + if(!Curl_checkheaders(data, STRCONST("Content-Length"))) { + result = + Curl_dyn_addf(&req_buffer, "Content-Length: %" FMT_OFF_T"\r\n", + req_clen); if(result) - return result; + goto out; } if(rtspreq == RTSPREQ_SET_PARAMETER || rtspreq == RTSPREQ_GET_PARAMETER) { - if(!Curl_checkheaders(conn, "Content-Type:")) { - result = Curl_add_bufferf(req_buffer, - "Content-Type: text/parameters\r\n"); + if(!Curl_checkheaders(data, STRCONST("Content-Type"))) { + result = Curl_dyn_addn(&req_buffer, + STRCONST("Content-Type: " + "text/parameters\r\n")); if(result) - return result; + goto out; } } if(rtspreq == RTSPREQ_ANNOUNCE) { - if(!Curl_checkheaders(conn, "Content-Type:")) { - result = Curl_add_bufferf(req_buffer, - "Content-Type: application/sdp\r\n"); + if(!Curl_checkheaders(data, STRCONST("Content-Type"))) { + result = Curl_dyn_addn(&req_buffer, + STRCONST("Content-Type: " + "application/sdp\r\n")); if(result) - return result; + goto out; } } - - data->state.expect100header = FALSE; /* RTSP posts are simple/small */ } else if(rtspreq == RTSPREQ_GET_PARAMETER) { /* Check for an empty GET_PARAMETER (heartbeat) request */ - data->set.httpreq = HTTPREQ_HEAD; - data->set.opt_no_body = TRUE; + data->state.httpreq = HTTPREQ_HEAD; + data->req.no_body = TRUE; } } + else { + result = Curl_creader_set_null(data); + if(result) + goto out; + } - /* RTSP never allows chunked transfer */ - data->req.forbidchunk = TRUE; /* Finish the request buffer */ - result = Curl_add_buffer(req_buffer, "\r\n", 2); + result = Curl_dyn_addn(&req_buffer, STRCONST("\r\n")); if(result) - return result; + goto out; - if(postsize > 0) { - result = Curl_add_buffer(req_buffer, data->set.postfields, - (size_t)postsize); - if(result) - return result; - } + Curl_xfer_setup1(data, CURL_XFER_SENDRECV, -1, TRUE); /* issue the request */ - result = Curl_add_buffer_send(req_buffer, conn, - &data->info.request_size, 0, FIRSTSOCKET); + result = Curl_req_send(data, &req_buffer); if(result) { failf(data, "Failed sending RTSP request"); - return result; + goto out; } - Curl_setup_transfer(conn, FIRSTSOCKET, -1, TRUE, &http->readbytecount, - putsize?FIRSTSOCKET:-1, - putsize?&http->writebytecount:NULL); - /* Increment the CSeq on success */ data->state.rtsp_next_client_CSeq++; - if(http->writebytecount) { + if(data->req.writebytecount) { /* if a request-body has been sent off, we make sure this progress is noted properly */ - Curl_pgrsSetUploadCounter(data, http->writebytecount); - if(Curl_pgrsUpdate(conn)) + Curl_pgrsSetUploadCounter(data, data->req.writebytecount); + if(Curl_pgrsUpdate(data)) result = CURLE_ABORTED_BY_CALLBACK; } - +out: + Curl_dyn_free(&req_buffer); return result; } - -static CURLcode rtsp_rtp_readwrite(struct Curl_easy *data, - struct connectdata *conn, - ssize_t *nread, - bool *readmore) { - struct SingleRequest *k = &data->req; - struct rtsp_conn *rtspc = &(conn->proto.rtspc); - - char *rtp; /* moving pointer to rtp data */ - ssize_t rtp_dataleft; /* how much data left to parse in this round */ - char *scratch; - CURLcode result; - - if(rtspc->rtp_buf) { - /* There was some leftover data the last time. Merge buffers */ - char *newptr = Curl_saferealloc(rtspc->rtp_buf, - rtspc->rtp_bufsize + *nread); - if(!newptr) { - rtspc->rtp_buf = NULL; - rtspc->rtp_bufsize = 0; - return CURLE_OUT_OF_MEMORY; - } - rtspc->rtp_buf = newptr; - memcpy(rtspc->rtp_buf + rtspc->rtp_bufsize, k->str, *nread); - rtspc->rtp_bufsize += *nread; - rtp = rtspc->rtp_buf; - rtp_dataleft = rtspc->rtp_bufsize; - } - else { - /* Just parse the request buffer directly */ - rtp = k->str; - rtp_dataleft = *nread; +/** + * write any BODY bytes missing to the client, ignore the rest. + */ +static CURLcode rtp_write_body_junk(struct Curl_easy *data, + const char *buf, + size_t blen) +{ + struct rtsp_conn *rtspc = &(data->conn->proto.rtspc); + curl_off_t body_remain; + bool in_body; + + in_body = (data->req.headerline && !rtspc->in_header) && + (data->req.size >= 0) && + (data->req.bytecount < data->req.size); + body_remain = in_body ? (data->req.size - data->req.bytecount) : 0; + DEBUGASSERT(body_remain >= 0); + if(body_remain) { + if((curl_off_t)blen > body_remain) + blen = (size_t)body_remain; + return Curl_client_write(data, CLIENTWRITE_BODY, (char *)buf, blen); } + return CURLE_OK; +} - while((rtp_dataleft > 0) && - (rtp[0] == '$')) { - if(rtp_dataleft > 4) { - int rtp_length; - - /* Parse the header */ - /* The channel identifier immediately follows and is 1 byte */ - rtspc->rtp_channel = RTP_PKT_CHANNEL(rtp); - - /* The length is two bytes */ - rtp_length = RTP_PKT_LENGTH(rtp); +static CURLcode rtsp_filter_rtp(struct Curl_easy *data, + const char *buf, + size_t blen, + size_t *pconsumed) +{ + struct rtsp_conn *rtspc = &(data->conn->proto.rtspc); + CURLcode result = CURLE_OK; + size_t skip_len = 0; + + *pconsumed = 0; + while(blen) { + bool in_body = (data->req.headerline && !rtspc->in_header) && + (data->req.size >= 0) && + (data->req.bytecount < data->req.size); + switch(rtspc->state) { + + case RTP_PARSE_SKIP: { + DEBUGASSERT(Curl_dyn_len(&rtspc->buf) == 0); + 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/BODY, consume without buffering */ + *pconsumed += 1; + ++buf; + --blen; + ++skip_len; + } + if(blen && buf[0] == '$') { + /* possible start of an RTP message, buffer */ + if(skip_len) { + /* end of junk/BODY bytes, flush */ + result = rtp_write_body_junk(data, + (char *)(buf - skip_len), skip_len); + skip_len = 0; + if(result) + goto out; + } + 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; + } - if(rtp_dataleft < rtp_length + 4) { - /* Need more - incomplete payload*/ - *readmore = TRUE; + 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; + DEBUGASSERT(skip_len == 0); + /* we do not consume this byte, it is BODY data */ + DEBUGF(infof(data, "RTSP: invalid RTP channel %d, skipping", 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 = rtp_write_body_junk(data, Curl_dyn_ptr(&rtspc->buf), 1); + if(result) + goto out; + } + else { + /* count the '$' as skip and continue */ + skip_len = 1; + } + Curl_dyn_free(&rtspc->buf); break; } - else { - /* We have the full RTP interleaved packet - * Write out the header including the leading '$' */ - DEBUGF(infof(data, "RTP write channel %d rtp_length %d\n", - rtspc->rtp_channel, rtp_length)); - result = rtp_client_write(conn, &rtp[0], rtp_length + 4); - if(result) { - failf(data, "Got an error writing an RTP packet"); - *readmore = FALSE; - Curl_safefree(rtspc->rtp_buf); - rtspc->rtp_buf = NULL; - rtspc->rtp_bufsize = 0; - return result; - } + /* 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; + } - /* Move forward in the buffer */ - rtp_dataleft -= rtp_length + 4; - rtp += rtp_length + 4; + 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; + } - if(data->set.rtspreq == RTSPREQ_RECEIVE) { - /* If we are in a passive receive, give control back - * to the app as often as we can. - */ - k->keepon &= ~KEEP_RECV; + 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; } - } - else { - /* Need more - incomplete header */ - *readmore = TRUE; break; } + + default: + DEBUGASSERT(0); + return CURLE_RECV_ERROR; + } } +out: + if(!result && skip_len) + result = rtp_write_body_junk(data, (char *)(buf - skip_len), skip_len); + return result; +} - if(rtp_dataleft != 0 && rtp[0] == '$') { - DEBUGF(infof(data, "RTP Rewinding %zd %s\n", rtp_dataleft, - *readmore ? "(READMORE)" : "")); +static CURLcode rtsp_rtp_write_resp(struct Curl_easy *data, + const char *buf, + size_t blen, + bool is_eos) +{ + struct rtsp_conn *rtspc = &(data->conn->proto.rtspc); + CURLcode result = CURLE_OK; + size_t consumed = 0; + + if(!data->req.header) + rtspc->in_header = FALSE; + if(!blen) { + goto out; + } - /* Store the incomplete RTP packet for a "rewind" */ - scratch = malloc(rtp_dataleft); - if(!scratch) { - Curl_safefree(rtspc->rtp_buf); - rtspc->rtp_buf = NULL; - rtspc->rtp_bufsize = 0; - return CURLE_OUT_OF_MEMORY; - } - memcpy(scratch, rtp, rtp_dataleft); - Curl_safefree(rtspc->rtp_buf); - rtspc->rtp_buf = scratch; - rtspc->rtp_bufsize = rtp_dataleft; + DEBUGF(infof(data, "rtsp_rtp_write_resp(len=%zu, in_header=%d, eos=%d)", + blen, rtspc->in_header, is_eos)); - /* As far as the transfer is concerned, this data is consumed */ - *nread = 0; - return CURLE_OK; + /* If header parsing is not ongoing, extract RTP messages */ + if(!rtspc->in_header) { + result = rtsp_filter_rtp(data, buf, blen, &consumed); + if(result) + goto out; + buf += consumed; + blen -= consumed; + /* either we consumed all or are at the start of header parsing */ + if(blen && !data->req.header) + DEBUGF(infof(data, "RTSP: %zu bytes, possibly excess in response body", + blen)); } - else { - /* Fix up k->str to point just after the last RTP packet */ - k->str += *nread - rtp_dataleft; - /* either all of the data has been read or... - * rtp now points at the next byte to parse - */ - if(rtp_dataleft > 0) - DEBUGASSERT(k->str[0] == rtp[0]); + /* we want to parse headers, do so */ + if(data->req.header && blen) { + rtspc->in_header = TRUE; + result = Curl_http_write_resp_hds(data, buf, blen, &consumed); + if(result) + goto out; - DEBUGASSERT(rtp_dataleft <= *nread); /* sanity check */ + buf += consumed; + blen -= consumed; - *nread = rtp_dataleft; + if(!data->req.header) + rtspc->in_header = FALSE; + + if(!rtspc->in_header) { + /* If header parsing is done, extract interleaved RTP messages */ + if(data->req.size <= -1) { + /* Respect section 4.4 of rfc2326: If the Content-Length header is + absent, a length 0 must be assumed. */ + data->req.size = 0; + data->req.download_done = TRUE; + } + result = rtsp_filter_rtp(data, buf, blen, &consumed); + if(result) + goto out; + blen -= consumed; + } } - /* If we get here, we have finished with the leftover/merge buffer */ - Curl_safefree(rtspc->rtp_buf); - rtspc->rtp_buf = NULL; - rtspc->rtp_bufsize = 0; + if(rtspc->state != RTP_PARSE_SKIP) + data->req.done = FALSE; + /* we SHOULD have consumed all bytes, unless the response is borked. + * In which case we write out the left over bytes, letting the client + * writer deal with it (it will report EXCESS and fail the transfer). */ + DEBUGF(infof(data, "rtsp_rtp_write_resp(len=%zu, in_header=%d, done=%d " + " rtspc->state=%d, req.size=%" FMT_OFF_T ")", + blen, rtspc->in_header, data->req.done, rtspc->state, + data->req.size)); + if(!result && (is_eos || blen)) { + result = Curl_client_write(data, CLIENTWRITE_BODY| + (is_eos ? CLIENTWRITE_EOS : 0), + (char *)buf, blen); + } - return CURLE_OK; +out: + if((data->set.rtspreq == RTSPREQ_RECEIVE) && + (rtspc->state == RTP_PARSE_SKIP)) { + /* 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.download_done = TRUE; + } + return result; } static -CURLcode rtp_client_write(struct connectdata *conn, char *ptr, size_t len) +CURLcode rtp_client_write(struct Curl_easy *data, const char *ptr, size_t len) { - struct Curl_easy *data = conn->data; 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; } - writeit = data->set.fwrite_rtp?data->set.fwrite_rtp:data->set.fwrite_func; - wrote = writeit(ptr, 1, len, data->set.rtp_out); + /* 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"); @@ -754,17 +915,17 @@ CURLcode rtp_client_write(struct connectdata *conn, char *ptr, size_t len) return CURLE_OK; } -CURLcode Curl_rtsp_parseheader(struct connectdata *conn, - char *header) +CURLcode Curl_rtsp_parseheader(struct Curl_easy *data, const char *header) { - struct Curl_easy *data = conn->data; - long CSeq = 0; - if(checkprefix("CSeq:", header)) { - /* Store the received CSeq. Match is verified in rtsp_done */ - int nc = sscanf(&header[4], ": %ld", &CSeq); - if(nc == 1) { - struct RTSP *rtsp = data->req.protop; + long CSeq = 0; + char *endp; + const 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 */ } @@ -774,20 +935,35 @@ CURLcode Curl_rtsp_parseheader(struct connectdata *conn, } } else if(checkprefix("Session:", header)) { - char *start; + const char *start, *end; + size_t idlen; /* Find the first non-space letter */ start = header + 8; - while(*start && ISSPACE(*start)) + while(*start && ISBLANK(*start)) start++; if(!*start) { failf(data, "Got a blank Session ID"); + return CURLE_RTSP_SESSION_ERROR; } - else if(data->set.str[STRING_RTSP_SESSION_ID]) { + + /* Find the end of Session ID + * + * Allow any non whitespace content, up to the field separator or end of + * line. RFC 2326 is not 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(strncmp(start, data->set.str[STRING_RTSP_SESSION_ID], - strlen(data->set.str[STRING_RTSP_SESSION_ID])) != 0) { + if(strlen(data->set.str[STRING_RTSP_SESSION_ID]) != idlen || + strncmp(start, data->set.str[STRING_RTSP_SESSION_ID], idlen)) { 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; @@ -796,24 +972,70 @@ CURLcode Curl_rtsp_parseheader(struct connectdata *conn, else { /* If the Session ID is not set, and we find it in a response, then set * it. - * - * Allow any non whitespace content, up to the field seperator 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. */ - char *end = start; - while(*end && *end != ';' && !ISSPACE(*end)) - end++; /* Copy the id substring into a new buffer */ - data->set.str[STRING_RTSP_SESSION_ID] = malloc(end - start + 1); - if(data->set.str[STRING_RTSP_SESSION_ID] == NULL) + data->set.str[STRING_RTSP_SESSION_ID] = Curl_memdup0(start, idlen); + if(!data->set.str[STRING_RTSP_SESSION_ID]) return CURLE_OUT_OF_MEMORY; - memcpy(data->set.str[STRING_RTSP_SESSION_ID], start, end - start); - (data->set.str[STRING_RTSP_SESSION_ID])[end - start] = '\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, const char *transport) +{ + /* If we receive multiple Transport response-headers, the linterleaved + channels of each response header is recorded and used together for + subsequent data validity checks.*/ + /* e.g.: ' RTP/AVP/TCP;unicast;interleaved=5-6' */ + const char *start, *end; + start = transport; + while(start && *start) { + while(*start && ISBLANK(*start) ) + start++; + end = strchr(start, ';'); + if(checkprefix("interleaved=", start)) { + long chan1, chan2, chan; + char *endp; + const char *p = start + 12; + chan1 = strtol(p, &endp, 10); + if(p != endp && chan1 >= 0 && chan1 <= 255) { + unsigned char *rtp_channel_mask = data->state.rtp_channel_mask; + 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 */ + +#endif /* CURL_DISABLE_RTSP or using Hyper */ diff --git a/contrib/curl/lib/rtsp.h b/contrib/curl/lib/rtsp.h index 5a8d5556fc..41b09503ff 100644 --- a/contrib/curl/lib/rtsp.h +++ b/contrib/curl/lib/rtsp.h @@ -7,11 +7,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2011, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -20,50 +20,51 @@ * 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; -bool Curl_rtsp_connisdead(struct connectdata *check); -CURLcode Curl_rtsp_parseheader(struct connectdata *conn, char *header); +CURLcode Curl_rtsp_parseheader(struct Curl_easy *data, const char *header); #else /* disabled */ #define Curl_rtsp_parseheader(x,y) CURLE_NOT_BUILT_IN -#define Curl_rtsp_connisdead(x) TRUE #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 { - char *rtp_buf; - ssize_t rtp_bufsize; + 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/contrib/curl/lib/security.c b/contrib/curl/lib/security.c deleted file mode 100644 index f4a8763413..0000000000 --- a/contrib/curl/lib/security.c +++ /dev/null @@ -1,588 +0,0 @@ -/* This source code was modified by Martin Hedenfalk for - * use in Curl. His latest changes were done 2000-09-18. - * - * It has since been patched and modified a lot by Daniel Stenberg - * to make it better applied to curl conditions, and to make - * it not use globals, pollute name space and more. This source code awaits a - * rewrite to work around the paragraph 2 in the BSD licenses as explained - * below. - * - * Copyright (c) 1998, 1999 Kungliga Tekniska Högskolan - * (Royal Institute of Technology, Stockholm, Sweden). - * - * Copyright (C) 2001 - 2015, Daniel Stenberg, , et al. - * - * All rights reserved. - * - * 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" - -#ifndef CURL_DISABLE_FTP -#ifdef HAVE_GSSAPI - -#ifdef HAVE_NETDB_H -#include -#endif - -#ifdef HAVE_LIMITS_H -#include -#endif - -#include "urldata.h" -#include "curl_base64.h" -#include "curl_memory.h" -#include "curl_sec.h" -#include "ftp.h" -#include "sendf.h" -#include "strcase.h" -#include "warnless.h" -#include "strdup.h" -/* The last #include file should be: */ -#include "memdebug.h" - -static const struct { - enum protection_level level; - const char *name; -} level_names[] = { - { PROT_CLEAR, "clear" }, - { PROT_SAFE, "safe" }, - { PROT_CONFIDENTIAL, "confidential" }, - { PROT_PRIVATE, "private" } -}; - -static enum protection_level -name_to_level(const char *name) -{ - int i; - for(i = 0; i < (int)sizeof(level_names)/(int)sizeof(level_names[0]); i++) - if(checkprefix(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 connectdata *conn, const char *message, ...) -{ - int ftp_code; - ssize_t nread=0; - va_list args; - char print_buffer[50]; - - va_start(args, message); - vsnprintf(print_buffer, sizeof(print_buffer), message, args); - va_end(args); - - if(Curl_ftpsend(conn, print_buffer)) { - ftp_code = -1; - } - else { - if(Curl_GetFTPResponse(&nread, conn, &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(curl_socket_t fd, void *to, size_t len) -{ - char *to_p = to; - CURLcode result; - ssize_t nread; - - while(len > 0) { - result = Curl_read_plain(fd, to_p, len, &nread); - if(!result) { - len -= nread; - to_p += nread; - } - else { - /* FIXME: We are doing a busy wait */ - 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 connectdata *conn, curl_socket_t fd, const void *to, - size_t len) -{ - const char *to_p = to; - CURLcode result; - ssize_t written; - - while(len > 0) { - result = Curl_write_plain(conn, fd, to_p, len, &written); - if(!result) { - len -= written; - to_p += written; - } - else { - /* FIXME: We are doing a busy wait */ - if(result == CURLE_AGAIN) - continue; - return result; - } - } - return CURLE_OK; -} - -static CURLcode read_data(struct connectdata *conn, - curl_socket_t fd, - struct krb5buffer *buf) -{ - int len; - void *tmp = NULL; - CURLcode result; - - result = socket_read(fd, &len, sizeof(len)); - if(result) - return result; - - if(len) { - /* only realloc if there was a length */ - len = ntohl(len); - tmp = Curl_saferealloc(buf->data, len); - } - if(tmp == NULL) - return CURLE_OUT_OF_MEMORY; - - buf->data = tmp; - result = socket_read(fd, buf->data, len); - if(result) - return result; - buf->size = conn->mech->decode(conn->app_data, buf->data, len, - conn->data_prot, conn); - 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 connectdata *conn, int sockindex, - char *buffer, size_t len, CURLcode *err) -{ - size_t bytes_read; - size_t total_read = 0; - curl_socket_t fd = conn->sock[sockindex]; - - *err = CURLE_OK; - - /* Handle clear text response. */ - if(conn->sec_complete == 0 || conn->data_prot == PROT_CLEAR) - return read(fd, buffer, len); - - 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(conn, fd, &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; - } - /* FIXME: Check for overflow */ - return total_read; -} - -/* Send |length| bytes from |from| to the |fd| socket taking care of encoding - and negociating with the server. |from| can be NULL. */ -/* FIXME: We don't check for errors nor report any! */ -static void do_sec_send(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(conn->data, 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(conn, fd, enc, 4); - else - socket_write(conn, fd, mic, 4); - - socket_write(conn, fd, cmd_buffer, cmd_size); - socket_write(conn, fd, "\r\n", 2); - infof(conn->data, "Send: %s%s\n", prot_level == PROT_PRIVATE?enc:mic, - cmd_buffer); - free(cmd_buffer); - } - } - else { - htonl_bytes = htonl(bytes); - socket_write(conn, fd, &htonl_bytes, sizeof(htonl_bytes)); - socket_write(conn, fd, buffer, curlx_sitouz(bytes)); - } - free(buffer); -} - -static ssize_t sec_write(struct connectdata *conn, curl_socket_t fd, - const char *buffer, size_t length) -{ - ssize_t tx = 0, len = conn->buffer_size; - - len -= conn->mech->overhead(conn->app_data, conn->data_prot, - curlx_sztosi(len)); - if(len <= 0) - len = length; - while(length) { - if(length < (size_t)len) - len = length; - - do_sec_send(conn, fd, buffer, curlx_sztosi(len)); - length -= len; - buffer += len; - tx += len; - } - return tx; -} - -/* Matches Curl_send signature */ -static ssize_t sec_send(struct connectdata *conn, int sockindex, - const void *buffer, size_t len, CURLcode *err) -{ - curl_socket_t fd = conn->sock[sockindex]; - *err = CURLE_OK; - return sec_write(conn, fd, buffer, len); -} - -int Curl_sec_read_msg(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; - - if(!conn->mech) - /* not inititalized, 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; - } - - if(conn->data->set.verbose) { - buf[decoded_len] = '\n'; - Curl_debug(conn->data, CURLINFO_HEADER_IN, buf, decoded_len + 1, conn); - } - - buf[decoded_len] = '\0'; - if(decoded_len <= 3) - /* suspiciously short */ - return 0; - - if(buf[3] != '-') - /* safe to ignore return code */ - (void)sscanf(buf, "%d", &ret_code); - - if(buf[decoded_len - 1] == '\n') - buf[decoded_len - 1] = '\0'; - /* FIXME: Is |buffer| length always greater than |decoded_len|? */ - strcpy(buffer, buf); - free(buf); - return ret_code; -} - -/* FIXME: The error code returned here is never checked. */ -static int sec_set_protection_level(struct connectdata *conn) -{ - int code; - char *pbsz; - static unsigned int buffer_size = 1 << 20; /* 1048576 */ - enum protection_level level = conn->request_data_prot; - - DEBUGASSERT(level > PROT_NONE && level < PROT_LAST); - - if(!conn->sec_complete) { - infof(conn->data, "Trying to change the protection level after the" - "completion of the data exchange.\n"); - return -1; - } - - /* Bail out if we try to set up the same level */ - if(conn->data_prot == level) - return 0; - - if(level) { - code = ftp_send_command(conn, "PBSZ %u", buffer_size); - if(code < 0) - return -1; - - if(code/100 != 2) { - failf(conn->data, "Failed to set the protection's buffer size."); - return -1; - } - conn->buffer_size = buffer_size; - - pbsz = strstr(conn->data->state.buffer, "PBSZ="); - if(pbsz) { - /* ignore return code, use default value if it fails */ - (void)sscanf(pbsz, "PBSZ=%u", &buffer_size); - if(buffer_size < conn->buffer_size) - conn->buffer_size = buffer_size; - } - } - - /* Now try to negiociate the protection level. */ - code = ftp_send_command(conn, "PROT %c", level_to_char(level)); - - if(code < 0) - return -1; - - if(code/100 != 2) { - failf(conn->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) -{ - enum protection_level 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 connectdata *conn) -{ - int ret; - struct Curl_easy *data = conn->data; - 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 == NULL) { - failf(data, "Failed realloc of size %u", 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.\n", - mech->name); - return CURLE_FAILED_INIT; - } - } - - infof(data, "Trying mechanism %s...\n", mech->name); - ret = ftp_send_command(conn, "AUTH %s", mech->name); - if(ret < 0) - /* FIXME: This error is too generic but it is OK for now. */ - 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).\n", mech->name); - break; - case 534: - infof(data, "Mechanism %s was rejected by the server (server returned " - "ftp code: 534).\n", mech->name); - break; - default: - if(ret/100 == 5) { - infof(data, "server does not support the security extensions\n"); - return CURLE_USE_SSL_FAILED; - } - break; - } - return CURLE_LOGIN_DENIED; - } - - /* Authenticate */ - ret = mech->auth(conn->app_data, conn); - - if(ret != AUTH_CONTINUE) { - if(ret != AUTH_OK) { - /* Mechanism has dumped the error to stderr, don't error here. */ - return -1; - } - 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(conn); - } - - return CURLE_OK; -} - -CURLcode -Curl_sec_login(struct connectdata *conn) -{ - return choose_mech(conn); -} - - -void -Curl_sec_end(struct connectdata *conn) -{ - if(conn->mech != NULL && 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; - /* FIXME: Is this really needed? */ - conn->in_buffer.eof_flag = 0; - } - conn->sec_complete = 0; - conn->data_prot = PROT_CLEAR; - conn->mech = NULL; -} - -#endif /* HAVE_GSSAPI */ - -#endif /* CURL_DISABLE_FTP */ diff --git a/contrib/curl/lib/select.c b/contrib/curl/lib/select.c index 03af645eef..c1779ad1e5 100644 --- a/contrib/curl/lib/select.c +++ b/contrib/curl/lib/select.c @@ -5,11 +5,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2016, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -18,70 +18,57 @@ * 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_SELECT_H -#include +#if !defined(HAVE_SELECT) && !defined(HAVE_POLL) +#error "We cannot compile without select() or poll() support." #endif -#if !defined(HAVE_SELECT) && !defined(HAVE_POLL_FINE) -#error "We can't compile without select() or poll() support." -#endif +#include -#if defined(__BEOS__) && !defined(__HAIKU__) -/* BeOS has FD_SET defined in socket.h */ -#include +#ifdef HAVE_SYS_SELECT_H +#include +#elif defined(HAVE_UNISTD_H) +#include #endif #ifdef MSDOS #include /* delay() */ #endif -#ifdef __VXWORKS__ -#include /* bzero() in FD_SET */ -#endif - #include #include "urldata.h" #include "connect.h" #include "select.h" +#include "timediff.h" #include "warnless.h" - -/* Convenience local macros */ -#define ELAPSED_MS() (int)curlx_tvdiff(curlx_tvnow(), initial_tv) - -int Curl_ack_eintr = 0; -#define ERROR_NOT_EINTR(error) (Curl_ack_eintr || error != EINTR) +/* The last 3 #include files should be in this order */ +#include "curl_printf.h" +#include "curl_memory.h" +#include "memdebug.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 citical issue - * for the intended use of this function in the library. + * 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, invalid timeout value, or interrupted - * 0 = specified timeout has elapsed + * -1 = system call error, or invalid timeout value + * 0 = specified timeout has elapsed, or interrupted */ -int Curl_wait_ms(int timeout_ms) +int Curl_wait_ms(timediff_t timeout_ms) { -#if !defined(MSDOS) && !defined(USE_WINSOCK) -#ifndef HAVE_POLL_FINE - struct timeval pending_tv; -#endif - struct timeval initial_tv; - int pending_ms; - int error; -#endif int r = 0; if(!timeout_ms) @@ -92,45 +79,98 @@ int Curl_wait_ms(int timeout_ms) } #if defined(MSDOS) delay(timeout_ms); -#elif defined(USE_WINSOCK) - Sleep(timeout_ms); -#else - pending_ms = timeout_ms; - initial_tv = curlx_tvnow(); - do { -#if defined(HAVE_POLL_FINE) - r = poll(NULL, 0, pending_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; + /* do not use ULONG_MAX, because that is equal to INFINITE */ +#endif + Sleep((ULONG)timeout_ms); #else - pending_tv.tv_sec = pending_ms / 1000; - pending_tv.tv_usec = (pending_ms % 1000) * 1000; - r = select(0, NULL, NULL, NULL, &pending_tv); -#endif /* HAVE_POLL_FINE */ - if(r != -1) - break; - error = SOCKERRNO; - if(error && ERROR_NOT_EINTR(error)) - break; - pending_ms = timeout_ms - ELAPSED_MS(); - if(pending_ms <= 0) { - r = 0; /* Simulate a "call timed out" case */ - break; - } - } while(r == -1); -#endif /* USE_WINSOCK */ - if(r) - r = -1; + /* avoid using poll() for this since it behaves incorrectly with no sockets + on Apple operating systems */ + { + struct timeval pending_tv; + r = select(0, NULL, NULL, NULL, curlx_mstotv(&pending_tv, timeout_ms)); + } +#endif /* _WIN32 */ + 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 +/* + * 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() cannot 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 cannot 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 does not 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 + * when 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, - * unles no valid file descriptor is given, when this happens the - * negative timeout is ignored and the function times out immediately. + * 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 @@ -145,50 +185,23 @@ int Curl_wait_ms(int timeout_ms) 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 */ - time_t timeout_ms) /* milliseconds to wait */ + timediff_t timeout_ms) /* milliseconds to wait */ { -#ifdef HAVE_POLL_FINE struct pollfd pfd[3]; int num; -#else - struct timeval pending_tv; - struct timeval *ptimeout; - fd_set fds_read; - fd_set fds_write; - fd_set fds_err; - curl_socket_t maxfd; -#endif - struct timeval initial_tv = {0, 0}; - int pending_ms = 0; - int error; int r; - int ret; - -#if SIZEOF_LONG != SIZEOF_INT - /* wrap-around precaution */ - if(timeout_ms >= INT_MAX) - timeout_ms = INT_MAX; -#endif if((readfd0 == CURL_SOCKET_BAD) && (readfd1 == CURL_SOCKET_BAD) && (writefd == CURL_SOCKET_BAD)) { /* no sockets, just wait */ - r = Curl_wait_ms((int)timeout_ms); - return r; + return Curl_wait_ms(timeout_ms); } - /* Avoid initial timestamp, avoid curlx_tvnow() call, when elapsed + /* 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. */ - if(timeout_ms > 0) { - pending_ms = (int)timeout_ms; - initial_tv = curlx_tvnow(); - } - -#ifdef HAVE_POLL_FINE - num = 0; if(readfd0 != CURL_SOCKET_BAD) { pfd[num].fd = readfd0; @@ -204,183 +217,47 @@ int Curl_socket_check(curl_socket_t readfd0, /* two sockets to read from */ } if(writefd != CURL_SOCKET_BAD) { pfd[num].fd = writefd; - pfd[num].events = POLLWRNORM|POLLOUT; + pfd[num].events = POLLWRNORM|POLLOUT|POLLPRI; pfd[num].revents = 0; num++; } - do { - if(timeout_ms < 0) - pending_ms = -1; - else if(!timeout_ms) - pending_ms = 0; - r = poll(pfd, num, pending_ms); - if(r != -1) - break; - error = SOCKERRNO; - if(error && ERROR_NOT_EINTR(error)) - break; - if(timeout_ms > 0) { - pending_ms = (int)(timeout_ms - ELAPSED_MS()); - if(pending_ms <= 0) { - r = 0; /* Simulate a "call timed out" case */ - break; - } - } - } while(r == -1); - - if(r < 0) - return -1; - if(r == 0) - return 0; + r = Curl_poll(pfd, (unsigned int)num, timeout_ms); + if(r <= 0) + return r; - ret = 0; + r = 0; num = 0; if(readfd0 != CURL_SOCKET_BAD) { if(pfd[num].revents & (POLLRDNORM|POLLIN|POLLERR|POLLHUP)) - ret |= CURL_CSELECT_IN; - if(pfd[num].revents & (POLLRDBAND|POLLPRI|POLLNVAL)) - ret |= CURL_CSELECT_ERR; + 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)) - ret |= CURL_CSELECT_IN2; - if(pfd[num].revents & (POLLRDBAND|POLLPRI|POLLNVAL)) - ret |= CURL_CSELECT_ERR; + 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)) - ret |= CURL_CSELECT_OUT; - if(pfd[num].revents & (POLLERR|POLLHUP|POLLNVAL)) - ret |= CURL_CSELECT_ERR; - } - - return ret; - -#else /* HAVE_POLL_FINE */ - - FD_ZERO(&fds_err); - maxfd = (curl_socket_t)-1; - - FD_ZERO(&fds_read); - if(readfd0 != CURL_SOCKET_BAD) { - VERIFY_SOCK(readfd0); - FD_SET(readfd0, &fds_read); - FD_SET(readfd0, &fds_err); - maxfd = readfd0; - } - if(readfd1 != CURL_SOCKET_BAD) { - VERIFY_SOCK(readfd1); - FD_SET(readfd1, &fds_read); - FD_SET(readfd1, &fds_err); - if(readfd1 > maxfd) - maxfd = readfd1; - } - - FD_ZERO(&fds_write); - if(writefd != CURL_SOCKET_BAD) { - VERIFY_SOCK(writefd); - FD_SET(writefd, &fds_write); - FD_SET(writefd, &fds_err); - if(writefd > maxfd) - maxfd = writefd; + r |= CURL_CSELECT_OUT; + if(pfd[num].revents & (POLLERR|POLLHUP|POLLPRI|POLLNVAL)) + r |= CURL_CSELECT_ERR; } - ptimeout = (timeout_ms < 0) ? NULL : &pending_tv; - - do { - if(timeout_ms > 0) { - pending_tv.tv_sec = pending_ms / 1000; - pending_tv.tv_usec = (pending_ms % 1000) * 1000; - } - else if(!timeout_ms) { - pending_tv.tv_sec = 0; - pending_tv.tv_usec = 0; - } - - /* 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. - - We know that we have at least one bit set in at least two fd_sets in - this case, but we may have no bits set in either fds_read or fd_write, - so check for that and handle it. Luckily, with WinSock, we can _also_ - ask how many bits are set on an fd_set. - - It is unclear why WinSock doesn't just handle this for us instead of - calling this an error. - - 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). - */ -#ifdef USE_WINSOCK - r = select((int)maxfd + 1, - fds_read.fd_count ? &fds_read : NULL, - fds_write.fd_count ? &fds_write : NULL, - &fds_err, ptimeout); -#else - r = select((int)maxfd + 1, &fds_read, &fds_write, &fds_err, ptimeout); -#endif - - if(r != -1) - break; - error = SOCKERRNO; - if(error && ERROR_NOT_EINTR(error)) - break; - if(timeout_ms > 0) { - pending_ms = (int)(timeout_ms - ELAPSED_MS()); - if(pending_ms <= 0) { - r = 0; /* Simulate a "call timed out" case */ - break; - } - } - } while(r == -1); - - if(r < 0) - return -1; - if(r == 0) - return 0; - - ret = 0; - if(readfd0 != CURL_SOCKET_BAD) { - if(FD_ISSET(readfd0, &fds_read)) - ret |= CURL_CSELECT_IN; - if(FD_ISSET(readfd0, &fds_err)) - ret |= CURL_CSELECT_ERR; - } - if(readfd1 != CURL_SOCKET_BAD) { - if(FD_ISSET(readfd1, &fds_read)) - ret |= CURL_CSELECT_IN2; - if(FD_ISSET(readfd1, &fds_err)) - ret |= CURL_CSELECT_ERR; - } - if(writefd != CURL_SOCKET_BAD) { - if(FD_ISSET(writefd, &fds_write)) - ret |= CURL_CSELECT_OUT; - if(FD_ISSET(writefd, &fds_err)) - ret |= CURL_CSELECT_ERR; - } - - return ret; - -#endif /* HAVE_POLL_FINE */ - + 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 + * 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, - * unles no valid file descriptor is given, when this happens the + * unless no valid file descriptor is given, when this happens the * negative timeout is ignored and the function times out immediately. * * Return values: @@ -388,21 +265,18 @@ int Curl_socket_check(curl_socket_t readfd0, /* two sockets to read from */ * 0 = timeout * N = number of structures with non zero revent fields */ -int Curl_poll(struct pollfd ufds[], unsigned int nfds, int timeout_ms) +int Curl_poll(struct pollfd ufds[], unsigned int nfds, timediff_t timeout_ms) { -#ifndef HAVE_POLL_FINE - struct timeval pending_tv; - struct timeval *ptimeout; +#ifdef HAVE_POLL + int pending_ms; +#else fd_set fds_read; fd_set fds_write; fd_set fds_err; curl_socket_t maxfd; #endif - struct timeval initial_tv = {0, 0}; bool fds_none = TRUE; unsigned int i; - int pending_ms = 0; - int error; int r; if(ufds) { @@ -414,46 +288,35 @@ int Curl_poll(struct pollfd ufds[], unsigned int nfds, int timeout_ms) } } if(fds_none) { - r = Curl_wait_ms(timeout_ms); - return r; + /* no sockets, just wait */ + return Curl_wait_ms(timeout_ms); } - /* Avoid initial timestamp, avoid curlx_tvnow() call, when elapsed + /* 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. */ - if(timeout_ms > 0) { - pending_ms = timeout_ms; - initial_tv = curlx_tvnow(); - } - -#ifdef HAVE_POLL_FINE - - do { - if(timeout_ms < 0) - pending_ms = -1; - else if(!timeout_ms) - pending_ms = 0; - r = poll(ufds, nfds, pending_ms); - if(r != -1) - break; - error = SOCKERRNO; - if(error && ERROR_NOT_EINTR(error)) - break; - if(timeout_ms > 0) { - pending_ms = (int)(timeout_ms - ELAPSED_MS()); - if(pending_ms <= 0) { - r = 0; /* Simulate a "call timed out" case */ - break; - } - } - } while(r == -1); +#ifdef HAVE_POLL - if(r < 0) - return -1; - if(r == 0) - return 0; + /* 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) @@ -461,10 +324,10 @@ int Curl_poll(struct pollfd ufds[], unsigned int nfds, int timeout_ms) if(ufds[i].revents & POLLHUP) ufds[i].revents |= POLLIN; if(ufds[i].revents & POLLERR) - ufds[i].revents |= (POLLIN|POLLOUT); + ufds[i].revents |= POLLIN|POLLOUT; } -#else /* HAVE_POLL_FINE */ +#else /* HAVE_POLL */ FD_ZERO(&fds_read); FD_ZERO(&fds_write); @@ -477,7 +340,7 @@ int Curl_poll(struct pollfd ufds[], unsigned int nfds, int timeout_ms) continue; VERIFY_SOCK(ufds[i].fd); if(ufds[i].events & (POLLIN|POLLOUT|POLLPRI| - POLLRDNORM|POLLWRNORM|POLLRDBAND)) { + POLLRDNORM|POLLWRNORM|POLLRDBAND)) { if(ufds[i].fd > maxfd) maxfd = ufds[i].fd; if(ufds[i].events & (POLLRDNORM|POLLIN)) @@ -489,95 +352,192 @@ int Curl_poll(struct pollfd ufds[], unsigned int nfds, int timeout_ms) } } -#ifdef USE_WINSOCK - /* WinSock select() can't handle zero events. See the comment about this in - Curl_check_socket(). */ - if(fds_read.fd_count == 0 && fds_write.fd_count == 0 - && fds_err.fd_count == 0) { - r = Curl_wait_ms(timeout_ms); + /* + Note also that Winsock ignores the first argument, so we do not worry + about the fact that maxfd is computed incorrectly with Winsock (since + curl_socket_t is unsigned in such cases and thus -1 is the largest + value). + */ + 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; } -#endif - - ptimeout = (timeout_ms < 0) ? NULL : &pending_tv; - - do { - if(timeout_ms > 0) { - pending_tv.tv_sec = pending_ms / 1000; - pending_tv.tv_usec = (pending_ms % 1000) * 1000; - } - else if(!timeout_ms) { - pending_tv.tv_sec = 0; - pending_tv.tv_usec = 0; - } - -#ifdef USE_WINSOCK - r = select((int)maxfd + 1, - /* WinSock select() can't handle fd_sets with zero bits set, so - don't give it such arguments. See the comment about this in - Curl_check_socket(). - */ - fds_read.fd_count ? &fds_read : NULL, - fds_write.fd_count ? &fds_write : NULL, - fds_err.fd_count ? &fds_err : NULL, ptimeout); -#else - r = select((int)maxfd + 1, &fds_read, &fds_write, &fds_err, ptimeout); -#endif - if(r != -1) - break; - error = SOCKERRNO; - if(error && ERROR_NOT_EINTR(error)) - break; - if(timeout_ms > 0) { - pending_ms = timeout_ms - ELAPSED_MS(); - if(pending_ms <= 0) { - r = 0; /* Simulate a "call timed out" case */ - break; - } - } - } while(r == -1); - - if(r < 0) - return -1; - if(r == 0) - return 0; 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)) - ufds[i].revents |= POLLIN; - if(FD_ISSET(ufds[i].fd, &fds_write)) - ufds[i].revents |= POLLOUT; - if(FD_ISSET(ufds[i].fd, &fds_err)) - ufds[i].revents |= POLLPRI; - if(ufds[i].revents != 0) + 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 */ +#endif /* HAVE_POLL */ return r; } -#ifdef TPF -/* - * This is a replacement for select() on the TPF platform. - * It is used whenever libcurl calls select(). - * The call below to tpf_process_signals() is required because - * TPF's select calls are not signal interruptible. - * - * Return values are the same as select's. - */ -int tpf_select_libcurl(int maxfds, fd_set* reads, fd_set* writes, - fd_set* excepts, struct timeval* tv) +void Curl_pollfds_init(struct curl_pollfds *cpfds, + struct pollfd *static_pfds, + unsigned int static_count) +{ + DEBUGASSERT(cpfds); + memset(cpfds, 0, sizeof(*cpfds)); + if(static_pfds && static_count) { + cpfds->pfds = static_pfds; + cpfds->count = static_count; + } +} + +void Curl_pollfds_cleanup(struct curl_pollfds *cpfds) +{ + DEBUGASSERT(cpfds); + if(cpfds->allocated_pfds) { + free(cpfds->pfds); + } + memset(cpfds, 0, sizeof(*cpfds)); +} + +static CURLcode cpfds_increase(struct curl_pollfds *cpfds, unsigned int inc) +{ + struct pollfd *new_fds; + unsigned int new_count = cpfds->count + inc; + + new_fds = calloc(new_count, sizeof(struct pollfd)); + if(!new_fds) + return CURLE_OUT_OF_MEMORY; + + memcpy(new_fds, cpfds->pfds, cpfds->count * sizeof(struct pollfd)); + if(cpfds->allocated_pfds) + free(cpfds->pfds); + cpfds->pfds = new_fds; + cpfds->count = new_count; + cpfds->allocated_pfds = TRUE; + return CURLE_OK; +} + +static CURLcode cpfds_add_sock(struct curl_pollfds *cpfds, + curl_socket_t sock, short events, bool fold) { - int rc; + int i; - rc = tpf_select_bsd(maxfds, reads, writes, excepts, tv); - tpf_process_signals(); - return rc; + if(fold && cpfds->n <= INT_MAX) { + for(i = (int)cpfds->n - 1; i >= 0; --i) { + if(sock == cpfds->pfds[i].fd) { + cpfds->pfds[i].events |= events; + return CURLE_OK; + } + } + } + /* not folded, add new entry */ + if(cpfds->n >= cpfds->count) { + if(cpfds_increase(cpfds, 100)) + return CURLE_OUT_OF_MEMORY; + } + cpfds->pfds[cpfds->n].fd = sock; + cpfds->pfds[cpfds->n].events = events; + ++cpfds->n; + return CURLE_OK; +} + +CURLcode Curl_pollfds_add_sock(struct curl_pollfds *cpfds, + curl_socket_t sock, short events) +{ + return cpfds_add_sock(cpfds, sock, events, FALSE); +} + +CURLcode Curl_pollfds_add_ps(struct curl_pollfds *cpfds, + struct easy_pollset *ps) +{ + size_t i; + + DEBUGASSERT(cpfds); + DEBUGASSERT(ps); + for(i = 0; i < ps->num; i++) { + short events = 0; + if(ps->actions[i] & CURL_POLL_IN) + events |= POLLIN; + if(ps->actions[i] & CURL_POLL_OUT) + events |= POLLOUT; + if(events) { + if(cpfds_add_sock(cpfds, ps->sockets[i], events, TRUE)) + return CURLE_OUT_OF_MEMORY; + } + } + return CURLE_OK; +} + +void Curl_waitfds_init(struct curl_waitfds *cwfds, + struct curl_waitfd *static_wfds, + unsigned int static_count) +{ + DEBUGASSERT(cwfds); + DEBUGASSERT(static_wfds); + memset(cwfds, 0, sizeof(*cwfds)); + cwfds->wfds = static_wfds; + cwfds->count = static_count; +} + +static CURLcode cwfds_add_sock(struct curl_waitfds *cwfds, + curl_socket_t sock, short events) +{ + int i; + + if(cwfds->n <= INT_MAX) { + for(i = (int)cwfds->n - 1; i >= 0; --i) { + if(sock == cwfds->wfds[i].fd) { + cwfds->wfds[i].events |= events; + return CURLE_OK; + } + } + } + /* not folded, add new entry */ + if(cwfds->n >= cwfds->count) + return CURLE_OUT_OF_MEMORY; + cwfds->wfds[cwfds->n].fd = sock; + cwfds->wfds[cwfds->n].events = events; + ++cwfds->n; + return CURLE_OK; +} + +CURLcode Curl_waitfds_add_ps(struct curl_waitfds *cwfds, + struct easy_pollset *ps) +{ + size_t i; + + DEBUGASSERT(cwfds); + DEBUGASSERT(ps); + for(i = 0; i < ps->num; i++) { + short events = 0; + if(ps->actions[i] & CURL_POLL_IN) + events |= CURL_WAIT_POLLIN; + if(ps->actions[i] & CURL_POLL_OUT) + events |= CURL_WAIT_POLLOUT; + if(events) { + if(cwfds_add_sock(cwfds, ps->sockets[i], events)) + return CURLE_OUT_OF_MEMORY; + } + } + return CURLE_OK; } -#endif /* TPF */ diff --git a/contrib/curl/lib/select.h b/contrib/curl/lib/select.h index c0b450f33d..f01acbdefc 100644 --- a/contrib/curl/lib/select.h +++ b/contrib/curl/lib/select.h @@ -7,11 +7,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2016, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -20,22 +20,23 @@ * 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_POLL_H -#include -#elif defined(HAVE_POLL_H) +#ifdef HAVE_POLL_H #include +#elif defined(HAVE_SYS_POLL_H) +#include #endif /* * Definition of pollfd struct and constants for platforms lacking them. */ -#if !defined(HAVE_STRUCT_POLLFD) && \ - !defined(HAVE_SYS_POLL_H) && \ +#if !defined(HAVE_SYS_POLL_H) && \ !defined(HAVE_POLL_H) && \ !defined(POLLIN) @@ -74,43 +75,73 @@ struct pollfd int Curl_socket_check(curl_socket_t readfd, curl_socket_t readfd2, curl_socket_t writefd, - time_t timeout_ms); - + 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, int timeout_ms); +int Curl_poll(struct pollfd ufds[], unsigned int nfds, timediff_t timeout_ms); +int Curl_wait_ms(timediff_t timeout_ms); -/* On non-DOS and non-Winsock platforms, when Curl_ack_eintr is set, - * EINTR condition is honored and function might exit early without - * awaiting full timeout. Otherwise EINTR will be ignored and full - * timeout will elapse. */ -extern int Curl_ack_eintr; - -int Curl_wait_ms(int timeout_ms); - -#ifdef TPF -int tpf_select_libcurl(int maxfds, fd_set* reads, fd_set* writes, - fd_set* excepts, struct timeval* tv); -#endif - -/* Winsock and TPF sockets are not in range [0..FD_SETSIZE-1], which - unfortunately makes it impossible for us to easily check if they're valid +/* + 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 */ -#if defined(USE_WINSOCK) || defined(TPF) -#define VALID_SOCK(x) 1 -#define VERIFY_SOCK(x) Curl_nop_stmt -#else -#define VALID_SOCK(s) (((s) >= 0) && ((s) < FD_SETSIZE)) +#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(EINVAL); \ + SET_SOCKERRNO(WSAEINVAL); \ return -1; \ } \ -} WHILE_FALSE +} 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 */ +struct curl_pollfds { + struct pollfd *pfds; + unsigned int n; + unsigned int count; + BIT(allocated_pfds); +}; + +void Curl_pollfds_init(struct curl_pollfds *cpfds, + struct pollfd *static_pfds, + unsigned int static_count); + +void Curl_pollfds_cleanup(struct curl_pollfds *cpfds); + +CURLcode Curl_pollfds_add_ps(struct curl_pollfds *cpfds, + struct easy_pollset *ps); + +CURLcode Curl_pollfds_add_sock(struct curl_pollfds *cpfds, + curl_socket_t sock, short events); +struct curl_waitfds { + struct curl_waitfd *wfds; + unsigned int n; + unsigned int count; +}; + +void Curl_waitfds_init(struct curl_waitfds *cwfds, + struct curl_waitfd *static_wfds, + unsigned int static_count); + +CURLcode Curl_waitfds_add_ps(struct curl_waitfds *cwfds, + struct easy_pollset *ps); + + +#endif /* HEADER_CURL_SELECT_H */ diff --git a/contrib/curl/lib/sendf.c b/contrib/curl/lib/sendf.c index 7601697824..bd459ac978 100644 --- a/contrib/curl/lib/sendf.c +++ b/contrib/curl/lib/sendf.c @@ -5,11 +5,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2016, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -18,822 +18,1426 @@ * 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 +#endif + +#ifdef HAVE_LINUX_TCP_H +#include +#elif defined(HAVE_NETINET_TCP_H) +#include +#endif + #include #include "urldata.h" #include "sendf.h" +#include "cfilters.h" #include "connect.h" +#include "content_encoding.h" +#include "cw-out.h" #include "vtls/vtls.h" -#include "ssh.h" +#include "vssh/ssh.h" +#include "easyif.h" #include "multiif.h" -#include "non-ascii.h" #include "strerror.h" #include "select.h" +#include "strdup.h" +#include "http2.h" +#include "progress.h" +#include "warnless.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" -#ifdef CURL_DO_LINEEND_CONV -/* - * 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 CURLcode do_init_writer_stack(struct Curl_easy *data); + +/* 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. */ -static size_t convert_lineends(struct Curl_easy *data, - char *startPtr, size_t size) -{ - char *inPtr, *outPtr; - - /* sanity check */ - if((startPtr == NULL) || (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; +CURLcode Curl_client_write(struct Curl_easy *data, + int type, const char *buf, size_t blen) +{ + CURLcode result; + + /* it is one of those, at least */ + DEBUGASSERT(type & (CLIENTWRITE_BODY|CLIENTWRITE_HEADER|CLIENTWRITE_INFO)); + /* BODY is only BODY (with optional EOS) */ + DEBUGASSERT(!(type & CLIENTWRITE_BODY) || + ((type & ~(CLIENTWRITE_BODY|CLIENTWRITE_EOS)) == 0)); + /* INFO is only INFO (with optional EOS) */ + DEBUGASSERT(!(type & CLIENTWRITE_INFO) || + ((type & ~(CLIENTWRITE_INFO|CLIENTWRITE_EOS)) == 0)); + + if(!data->req.writer_stack) { + result = do_init_writer_stack(data); + if(result) + return result; + DEBUGASSERT(data->req.writer_stack); + } + + result = Curl_cwriter_write(data, data->req.writer_stack, type, buf, blen); + CURL_TRC_WRITE(data, "client_write(type=%x, len=%zu) -> %d", + type, blen, result); + return result; +} + +static void cl_reset_writer(struct Curl_easy *data) +{ + struct Curl_cwriter *writer = data->req.writer_stack; + while(writer) { + data->req.writer_stack = writer->next; + writer->cwt->do_close(data, writer); + free(writer); + writer = data->req.writer_stack; + } +} + +static void cl_reset_reader(struct Curl_easy *data) +{ + struct Curl_creader *reader = data->req.reader_stack; + while(reader) { + data->req.reader_stack = reader->next; + reader->crt->do_close(data, reader); + free(reader); + reader = data->req.reader_stack; + } +} + +void Curl_client_cleanup(struct Curl_easy *data) +{ + cl_reset_reader(data); + cl_reset_writer(data); + + data->req.bytecount = 0; + data->req.headerline = 0; +} + +void Curl_client_reset(struct Curl_easy *data) +{ + if(data->req.rewind_read) { + /* already requested */ + CURL_TRC_READ(data, "client_reset, will rewind reader"); + } + else { + CURL_TRC_READ(data, "client_reset, clear readers"); + cl_reset_reader(data); + } + cl_reset_writer(data); + + data->req.bytecount = 0; + data->req.headerline = 0; +} + +CURLcode Curl_client_start(struct Curl_easy *data) +{ + if(data->req.rewind_read) { + struct Curl_creader *r = data->req.reader_stack; + CURLcode result = CURLE_OK; + + CURL_TRC_READ(data, "client start, rewind readers"); + while(r) { + result = r->crt->rewind(data, r); + if(result) { + failf(data, "rewind of client reader '%s' failed: %d", + r->crt->name, result); + return result; } - outPtr++; + r = r->next; } - 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 */ - -#ifdef USE_RECV_BEFORE_SEND_WORKAROUND -bool Curl_recv_has_postponed_data(struct connectdata *conn, int sockindex) -{ - struct postponed_data * const psnd = &(conn->postponed[sockindex]); - return psnd->buffer && psnd->allocated_size && - psnd->recv_size > psnd->recv_processed; -} - -static void pre_receive_plain(struct connectdata *conn, int num) -{ - const curl_socket_t sockfd = conn->sock[num]; - struct postponed_data * const psnd = &(conn->postponed[num]); - size_t bytestorecv = psnd->allocated_size - psnd->recv_size; - /* WinSock will destroy unread received data if send() is - failed. - To avoid lossage of received data, recv() must be - performed before every send() if any incoming data is - available. However, skip this, if buffer is already full. */ - if((conn->handler->protocol&PROTO_FAMILY_HTTP) != 0 && - conn->recv[num] == Curl_recv_plain && - (!psnd->buffer || bytestorecv)) { - const int readymask = Curl_socket_check(sockfd, CURL_SOCKET_BAD, - CURL_SOCKET_BAD, 0); - if(readymask != -1 && (readymask & CURL_CSELECT_IN) != 0) { - /* Have some incoming data */ - if(!psnd->buffer) { - /* Use buffer double default size for intermediate buffer */ - psnd->allocated_size = 2 * BUFSIZE; - psnd->buffer = malloc(psnd->allocated_size); - psnd->recv_size = 0; - psnd->recv_processed = 0; -#ifdef DEBUGBUILD - psnd->bindsock = sockfd; /* Used only for DEBUGASSERT */ -#endif /* DEBUGBUILD */ - bytestorecv = psnd->allocated_size; - } - if(psnd->buffer) { - ssize_t recvedbytes; - DEBUGASSERT(psnd->bindsock == sockfd); - recvedbytes = sread(sockfd, psnd->buffer + psnd->recv_size, - bytestorecv); - if(recvedbytes > 0) - psnd->recv_size += recvedbytes; - } - else - psnd->allocated_size = 0; + data->req.rewind_read = FALSE; + cl_reset_reader(data); + } + return CURLE_OK; +} + +bool Curl_creader_will_rewind(struct Curl_easy *data) +{ + return data->req.rewind_read; +} + +void Curl_creader_set_rewind(struct Curl_easy *data, bool enable) +{ + data->req.rewind_read = !!enable; +} + +/* Write data using an unencoding writer stack. */ +CURLcode Curl_cwriter_write(struct Curl_easy *data, + struct Curl_cwriter *writer, int type, + const char *buf, size_t nbytes) +{ + if(!writer) + return CURLE_WRITE_ERROR; + return writer->cwt->do_write(data, writer, type, buf, nbytes); +} + +CURLcode Curl_cwriter_def_init(struct Curl_easy *data, + struct Curl_cwriter *writer) +{ + (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; +} + +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; } -static ssize_t get_pre_recved(struct connectdata *conn, int num, char *buf, - size_t len) +struct cw_download_ctx { + struct Curl_cwriter super; + BIT(started_response); +}; +/* 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) { - struct postponed_data * const psnd = &(conn->postponed[num]); - size_t copysize; - if(!psnd->buffer) - return 0; + struct cw_download_ctx *ctx = writer->ctx; + CURLcode result; + size_t nwrite, excess_len = 0; + bool is_connect = !!(type & CLIENTWRITE_CONNECT); + + if(!is_connect && !ctx->started_response) { + Curl_pgrsTime(data, TIMER_STARTTRANSFER); + ctx->started_response = TRUE; + } + + if(!(type & CLIENTWRITE_BODY)) { + if(is_connect && data->set.suppress_connect_headers) + return CURLE_OK; + result = Curl_cwriter_write(data, writer->next, type, buf, nbytes); + CURL_TRC_WRITE(data, "download_write header(type=%x, blen=%zu) -> %d", + type, nbytes, result); + return result; + } + + /* Here, we deal with REAL BODY bytes. All filtering and transfer + * encodings have been applied and only the true content, e.g. BODY, + * bytes are passed here. + * This allows us to check sizes, update stats, etc. independent + * from the protocol in play. */ + + if(data->req.no_body && nbytes > 0) { + /* BODY arrives although we want none, bail out */ + streamclose(data->conn, "ignoring body"); + CURL_TRC_WRITE(data, "download_write body(type=%x, blen=%zu), " + "did not want a BODY", type, nbytes); + data->req.download_done = TRUE; + if(data->info.header_size) + /* if headers have been received, this is fine */ + return CURLE_OK; + return CURLE_WEIRD_SERVER_REPLY; + } + + /* Determine if we see any bytes in excess to what is allowed. + * We write the allowed bytes and handle excess further below. + * This gives deterministic BODY writes on varying buffer receive + * lengths. */ + 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; + } + + if(nwrite == wmax) { + data->req.download_done = TRUE; + } + + if((type & CLIENTWRITE_EOS) && !data->req.no_body && + (data->req.maxdownload > data->req.bytecount)) { + failf(data, "end of response with %" FMT_OFF_T " bytes missing", + data->req.maxdownload - data->req.bytecount); + return CURLE_PARTIAL_FILE; + } + } + + /* Error on too large filesize is handled below, after writing + * the permitted bytes */ + if(data->set.max_filesize && !data->req.ignorebody) { + size_t wmax = get_max_body_write_len(data, data->set.max_filesize); + if(nwrite > wmax) { + nwrite = wmax; + } + } - DEBUGASSERT(psnd->allocated_size > 0); - DEBUGASSERT(psnd->recv_size <= psnd->allocated_size); - DEBUGASSERT(psnd->recv_processed <= psnd->recv_size); - /* Check and process data that already received and storied in internal - intermediate buffer */ - if(psnd->recv_size > psnd->recv_processed) { - DEBUGASSERT(psnd->bindsock == conn->sock[num]); - copysize = CURLMIN(len, psnd->recv_size - psnd->recv_processed); - memcpy(buf, psnd->buffer + psnd->recv_processed, copysize); - psnd->recv_processed += copysize; + if(!data->req.ignorebody && (nwrite || (type & CLIENTWRITE_EOS))) { + result = Curl_cwriter_write(data, writer->next, type, buf, nwrite); + CURL_TRC_WRITE(data, "download_write body(type=%x, blen=%zu) -> %d", + type, nbytes, result); + if(result) + return result; } - else - copysize = 0; /* buffer was allocated, but nothing was received */ + /* Update stats, write and report progress */ + data->req.bytecount += nwrite; +#ifdef USE_HYPER + data->req.bodywritten = TRUE; +#endif + result = Curl_pgrsSetDownloadCounter(data, data->req.bytecount); + if(result) + return result; - /* Free intermediate buffer if it has no unprocessed data */ - if(psnd->recv_processed == psnd->recv_size) { - free(psnd->buffer); - psnd->buffer = NULL; - psnd->allocated_size = 0; - psnd->recv_size = 0; - psnd->recv_processed = 0; -#ifdef DEBUGBUILD - psnd->bindsock = CURL_SOCKET_BAD; -#endif /* DEBUGBUILD */ + if(excess_len) { + if(!data->req.ignorebody) { + infof(data, + "Excess found writing body:" + " excess = %zu" + ", size = %" FMT_OFF_T + ", maxdownload = %" FMT_OFF_T + ", bytecount = %" FMT_OFF_T, + excess_len, data->req.size, data->req.maxdownload, + data->req.bytecount); + connclose(data->conn, "excess found in a read"); + } } - return (ssize_t)copysize; + else if((nwrite < nbytes) && !data->req.ignorebody) { + failf(data, "Exceeded the maximum allowed file size " + "(%" FMT_OFF_T ") with %" FMT_OFF_T " bytes", + data->set.max_filesize, data->req.bytecount); + return CURLE_FILESIZE_EXCEEDED; + } + + return CURLE_OK; } -#else /* ! USE_RECV_BEFORE_SEND_WORKAROUND */ -/* Use "do-nothing" macros instead of functions when workaround not used */ -bool Curl_recv_has_postponed_data(struct connectdata *conn, int sockindex) + +static const struct Curl_cwtype cw_download = { + "protocol", + NULL, + Curl_cwriter_def_init, + cw_download_write, + Curl_cwriter_def_close, + sizeof(struct cw_download_ctx) +}; + +/* 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) { - (void)conn; - (void)sockindex; - return false; + 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); } -#define pre_receive_plain(c,n) do {} WHILE_FALSE -#define get_pre_recved(c,n,b,l) 0 -#endif /* ! USE_RECV_BEFORE_SEND_WORKAROUND */ -/* Curl_infof() is for info message along the way */ +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 = NULL; + CURLcode result = CURLE_OUT_OF_MEMORY; + void *p; + + DEBUGASSERT(cwt->cwriter_size >= sizeof(struct Curl_cwriter)); + p = calloc(1, cwt->cwriter_size); + if(!p) + goto out; + + writer = (struct Curl_cwriter *)p; + writer->cwt = cwt; + writer->ctx = p; + writer->phase = phase; + result = cwt->do_init(data, writer); + +out: + *pwriter = result ? NULL : writer; + if(result) + free(writer); + return result; +} -void Curl_infof(struct Curl_easy *data, const char *fmt, ...) +void Curl_cwriter_free(struct Curl_easy *data, + struct Curl_cwriter *writer) { - if(data && data->set.verbose) { - va_list ap; - size_t len; - char print_buffer[2048 + 1]; - va_start(ap, fmt); - vsnprintf(print_buffer, sizeof(print_buffer), fmt, ap); - va_end(ap); - len = strlen(print_buffer); - Curl_debug(data, CURLINFO_TEXT, print_buffer, len, NULL); + if(writer) { + writer->cwt->do_close(data, writer); + free(writer); } } -/* Curl_failf() is for messages stating why we failed. - * The message SHALL NOT include any LF or CR. - */ +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; +} -void Curl_failf(struct Curl_easy *data, const char *fmt, ...) +static CURLcode do_init_writer_stack(struct Curl_easy *data) { - va_list ap; - size_t len; - va_start(ap, fmt); + struct Curl_cwriter *writer; + CURLcode result; - vsnprintf(data->state.buffer, BUFSIZE, fmt, ap); + DEBUGASSERT(!data->req.writer_stack); + result = Curl_cwriter_create(&data->req.writer_stack, + data, &Curl_cwt_out, CURL_CW_CLIENT); + if(result) + return result; - if(data->set.errorbuffer && !data->state.errorbuf) { - snprintf(data->set.errorbuffer, CURL_ERROR_SIZE, "%s", data->state.buffer); - data->state.errorbuf = TRUE; /* wrote error string */ - } - if(data->set.verbose) { - len = strlen(data->state.buffer); - if(len < BUFSIZE - 1) { - data->state.buffer[len] = '\n'; - data->state.buffer[++len] = '\0'; - } - Curl_debug(data, CURLINFO_TEXT, data->state.buffer, len, NULL); + 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); } - va_end(ap); + 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; } -/* Curl_sendf() sends formated data to the server */ -CURLcode Curl_sendf(curl_socket_t sockfd, struct connectdata *conn, - const char *fmt, ...) +CURLcode Curl_cwriter_add(struct Curl_easy *data, + struct Curl_cwriter *writer) { - struct Curl_easy *data = conn->data; - 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(conn, sockfd, sptr, write_len, &bytes_written); + CURLcode result; + struct Curl_cwriter **anchor = &data->req.writer_stack; + if(!*anchor) { + result = do_init_writer_stack(data); if(result) - break; + return result; + } - if(data->set.verbose) - Curl_debug(data, CURLINFO_DATA_OUT, sptr, (size_t)bytes_written, conn); + /* 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; +} - 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; +struct Curl_cwriter *Curl_cwriter_get_by_name(struct Curl_easy *data, + const char *name) +{ + struct Curl_cwriter *writer; + for(writer = data->req.writer_stack; writer; writer = writer->next) { + if(!strcmp(name, writer->cwt->name)) + return writer; } + return NULL; +} - free(s); /* free the output string */ +struct Curl_cwriter *Curl_cwriter_get_by_type(struct Curl_easy *data, + const struct Curl_cwtype *cwt) +{ + struct Curl_cwriter *writer; + for(writer = data->req.writer_stack; writer; writer = writer->next) { + if(writer->cwt == cwt) + return writer; + } + return NULL; +} - return result; +void Curl_cwriter_remove_by_name(struct Curl_easy *data, + const char *name) +{ + struct Curl_cwriter **anchor = &data->req.writer_stack; + + while(*anchor) { + if(!strcmp(name, (*anchor)->cwt->name)) { + struct Curl_cwriter *w = (*anchor); + *anchor = w->next; + Curl_cwriter_free(data, w); + continue; + } + anchor = &((*anchor)->next); + } } -/* - * 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 connectdata *conn, - curl_socket_t sockfd, - const void *mem, - size_t len, - ssize_t *written) +bool Curl_cwriter_is_paused(struct Curl_easy *data) { - ssize_t bytes_written; - CURLcode result = CURLE_OK; - int num = (sockfd == conn->sock[SECONDARYSOCKET]); + return Curl_cw_out_is_paused(data); +} - bytes_written = conn->send[num](conn, num, mem, len, &result); +CURLcode Curl_cwriter_unpause(struct Curl_easy *data) +{ + return Curl_cw_out_unpause(data); +} - *written = bytes_written; - if(bytes_written >= 0) - /* we completely ignore the curlcode value when subzero is not returned */ - return CURLE_OK; +CURLcode Curl_creader_read(struct Curl_easy *data, + struct Curl_creader *reader, + char *buf, size_t blen, size_t *nread, bool *eos) +{ + *nread = 0; + *eos = FALSE; + if(!reader) + return CURLE_READ_ERROR; + return reader->crt->do_read(data, reader, buf, blen, nread, eos); +} - /* handle CURLE_AGAIN or a send failure */ - switch(result) { - case CURLE_AGAIN: - *written = 0; - return CURLE_OK; +CURLcode Curl_creader_def_init(struct Curl_easy *data, + struct Curl_creader *reader) +{ + (void)data; + (void)reader; + return CURLE_OK; +} - case CURLE_OK: - /* general send failure */ - return CURLE_SEND_ERROR; +void Curl_creader_def_close(struct Curl_easy *data, + struct Curl_creader *reader) +{ + (void)data; + (void)reader; +} - default: - /* we got a specific curlcode, forward it */ - return result; +CURLcode Curl_creader_def_read(struct Curl_easy *data, + struct Curl_creader *reader, + char *buf, size_t blen, + size_t *nread, bool *eos) +{ + if(reader->next) + return reader->next->crt->do_read(data, reader->next, buf, blen, + nread, eos); + else { + *nread = 0; + *eos = FALSE; + return CURLE_READ_ERROR; } } -ssize_t Curl_send_plain(struct connectdata *conn, int num, - const void *mem, size_t len, CURLcode *code) +bool Curl_creader_def_needs_rewind(struct Curl_easy *data, + struct Curl_creader *reader) +{ + (void)data; + (void)reader; + return FALSE; +} + +curl_off_t Curl_creader_def_total_length(struct Curl_easy *data, + struct Curl_creader *reader) +{ + return reader->next ? + reader->next->crt->total_length(data, reader->next) : -1; +} + +CURLcode Curl_creader_def_resume_from(struct Curl_easy *data, + struct Curl_creader *reader, + curl_off_t offset) { - curl_socket_t sockfd = conn->sock[num]; - ssize_t bytes_written; - /* WinSock will destroy unread received data if send() is - failed. - To avoid lossage of received data, recv() must be - performed before every send() if any incoming data is - available. */ - pre_receive_plain(conn, num); + (void)data; + (void)reader; + (void)offset; + return CURLE_READ_ERROR; +} -#ifdef MSG_FASTOPEN /* Linux */ - if(conn->bits.tcp_fastopen) { - bytes_written = sendto(sockfd, mem, len, MSG_FASTOPEN, - conn->ip_addr->ai_addr, conn->ip_addr->ai_addrlen); - conn->bits.tcp_fastopen = FALSE; +CURLcode Curl_creader_def_rewind(struct Curl_easy *data, + struct Curl_creader *reader) +{ + (void)data; + (void)reader; + return CURLE_OK; +} + +CURLcode Curl_creader_def_unpause(struct Curl_easy *data, + struct Curl_creader *reader) +{ + (void)data; + (void)reader; + return CURLE_OK; +} + +bool Curl_creader_def_is_paused(struct Curl_easy *data, + struct Curl_creader *reader) +{ + (void)data; + (void)reader; + return FALSE; +} + +void Curl_creader_def_done(struct Curl_easy *data, + struct Curl_creader *reader, int premature) +{ + (void)data; + (void)reader; + (void)premature; +} + +struct cr_in_ctx { + struct Curl_creader super; + curl_read_callback read_cb; + void *cb_user_data; + curl_off_t total_len; + curl_off_t read_len; + CURLcode error_result; + BIT(seen_eos); + BIT(errored); + BIT(has_used_cb); + BIT(is_paused); +}; + +static CURLcode cr_in_init(struct Curl_easy *data, struct Curl_creader *reader) +{ + struct cr_in_ctx *ctx = reader->ctx; + (void)data; + ctx->read_cb = data->state.fread_func; + ctx->cb_user_data = data->state.in; + ctx->total_len = -1; + ctx->read_len = 0; + return CURLE_OK; +} + +/* Real client reader to installed client callbacks. */ +static CURLcode cr_in_read(struct Curl_easy *data, + struct Curl_creader *reader, + char *buf, size_t blen, + size_t *pnread, bool *peos) +{ + struct cr_in_ctx *ctx = reader->ctx; + size_t nread; + + ctx->is_paused = FALSE; + + /* Once we have errored, we will return the same error forever */ + if(ctx->errored) { + *pnread = 0; + *peos = FALSE; + return ctx->error_result; } - else -#endif - bytes_written = swrite(sockfd, mem, len); - - *code = CURLE_OK; - if(-1 == bytes_written) { - int err = SOCKERRNO; - - if( -#ifdef WSAEWOULDBLOCK - /* This is how Windows does it */ - (WSAEWOULDBLOCK == err) -#else - /* errno may be EWOULDBLOCK or on some systems EAGAIN when it returned - due to its inability to send off data without blocking. We therefor - treat both error codes the same here */ - (EWOULDBLOCK == err) || (EAGAIN == err) || (EINTR == err) || - (EINPROGRESS == err) -#endif - ) { - /* this is just a case of EWOULDBLOCK */ - bytes_written=0; - *code = CURLE_AGAIN; + if(ctx->seen_eos) { + *pnread = 0; + *peos = TRUE; + return CURLE_OK; + } + /* respect length limitations */ + if(ctx->total_len >= 0) { + curl_off_t remain = ctx->total_len - ctx->read_len; + if(remain <= 0) + blen = 0; + else if(remain < (curl_off_t)blen) + blen = (size_t)remain; + } + nread = 0; + if(ctx->read_cb && blen) { + Curl_set_in_callback(data, true); + nread = ctx->read_cb(buf, 1, blen, ctx->cb_user_data); + Curl_set_in_callback(data, false); + ctx->has_used_cb = TRUE; + } + + switch(nread) { + case 0: + if((ctx->total_len >= 0) && (ctx->read_len < ctx->total_len)) { + failf(data, "client read function EOF fail, " + "only %"FMT_OFF_T"/%"FMT_OFF_T " of needed bytes read", + ctx->read_len, ctx->total_len); + return CURLE_READ_ERROR; } - else { - failf(conn->data, "Send failure: %s", - Curl_strerror(conn, err)); - conn->data->state.os_errno = err; - *code = CURLE_SEND_ERROR; + *pnread = 0; + *peos = TRUE; + ctx->seen_eos = TRUE; + break; + + case CURL_READFUNC_ABORT: + failf(data, "operation aborted by callback"); + *pnread = 0; + *peos = FALSE; + ctx->errored = TRUE; + ctx->error_result = CURLE_ABORTED_BY_CALLBACK; + return CURLE_ABORTED_BY_CALLBACK; + + case CURL_READFUNC_PAUSE: + if(data->conn->handler->flags & PROTOPT_NONETWORK) { + /* protocols that work without network cannot be paused. This is + actually only FILE:// just now, and it cannot pause since the transfer + is not 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 */ + CURL_TRC_READ(data, "cr_in_read, callback returned CURL_READFUNC_PAUSE"); + ctx->is_paused = TRUE; + data->req.keepon |= KEEP_SEND_PAUSE; /* mark socket send as paused */ + *pnread = 0; + *peos = FALSE; + break; /* nothing was read */ + + default: + if(nread > blen) { + /* the read function returned a too large value */ + failf(data, "read function returned funny value"); + *pnread = 0; + *peos = FALSE; + ctx->errored = TRUE; + ctx->error_result = CURLE_READ_ERROR; + return CURLE_READ_ERROR; } + ctx->read_len += nread; + if(ctx->total_len >= 0) + ctx->seen_eos = (ctx->read_len >= ctx->total_len); + *pnread = nread; + *peos = ctx->seen_eos; + break; } - return bytes_written; + CURL_TRC_READ(data, "cr_in_read(len=%zu, total=%"FMT_OFF_T + ", read=%"FMT_OFF_T") -> %d, nread=%zu, eos=%d", + blen, ctx->total_len, ctx->read_len, CURLE_OK, + *pnread, *peos); + return CURLE_OK; } -/* - * Curl_write_plain() is an internal write function that sends data to the - * server using plain sockets only. Otherwise meant to have the exact same - * proto as Curl_write() - */ -CURLcode Curl_write_plain(struct connectdata *conn, - curl_socket_t sockfd, - const void *mem, - size_t len, - ssize_t *written) +static bool cr_in_needs_rewind(struct Curl_easy *data, + struct Curl_creader *reader) { - ssize_t bytes_written; - CURLcode result; - int num = (sockfd == conn->sock[SECONDARYSOCKET]); + struct cr_in_ctx *ctx = reader->ctx; + (void)data; + return ctx->has_used_cb; +} + +static curl_off_t cr_in_total_length(struct Curl_easy *data, + struct Curl_creader *reader) +{ + struct cr_in_ctx *ctx = reader->ctx; + (void)data; + return ctx->total_len; +} - bytes_written = Curl_send_plain(conn, num, mem, len, &result); +static CURLcode cr_in_resume_from(struct Curl_easy *data, + struct Curl_creader *reader, + curl_off_t offset) +{ + struct cr_in_ctx *ctx = reader->ctx; + int seekerr = CURL_SEEKFUNC_CANTSEEK; + + DEBUGASSERT(data->conn); + /* already started reading? */ + if(ctx->read_len) + return CURLE_READ_ERROR; + + if(data->set.seek_func) { + Curl_set_in_callback(data, true); + seekerr = data->set.seek_func(data->set.seek_client, offset, SEEK_SET); + Curl_set_in_callback(data, false); + } - *written = bytes_written; + if(seekerr != CURL_SEEKFUNC_OK) { + curl_off_t passed = 0; - return result; + if(seekerr != CURL_SEEKFUNC_CANTSEEK) { + failf(data, "Could not seek stream"); + return CURLE_READ_ERROR; + } + /* when seekerr == CURL_SEEKFUNC_CANTSEEK (cannot seek to offset) */ + do { + char scratch[4*1024]; + size_t readthisamountnow = + (offset - passed > (curl_off_t)sizeof(scratch)) ? + sizeof(scratch) : + curlx_sotouz(offset - passed); + size_t actuallyread; + + Curl_set_in_callback(data, true); + actuallyread = ctx->read_cb(scratch, 1, readthisamountnow, + ctx->cb_user_data); + 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, "Could only read %" FMT_OFF_T " bytes from the input", + passed); + return CURLE_READ_ERROR; + } + } while(passed < offset); + } + + /* now, decrease the size of the read */ + if(ctx->total_len > 0) { + ctx->total_len -= offset; + + if(ctx->total_len <= 0) { + failf(data, "File already completely uploaded"); + return CURLE_PARTIAL_FILE; + } + } + /* we have passed, proceed as normal */ + return CURLE_OK; } -ssize_t Curl_recv_plain(struct connectdata *conn, int num, char *buf, - size_t len, CURLcode *code) +static CURLcode cr_in_rewind(struct Curl_easy *data, + struct Curl_creader *reader) { - curl_socket_t sockfd = conn->sock[num]; - ssize_t nread; - /* Check and return data that already received and storied in internal - intermediate buffer */ - nread = get_pre_recved(conn, num, buf, len); - if(nread > 0) { - *code = CURLE_OK; - return nread; - } + struct cr_in_ctx *ctx = reader->ctx; - nread = sread(sockfd, buf, len); + /* If we never invoked the callback, there is noting to rewind */ + if(!ctx->has_used_cb) + return CURLE_OK; - *code = CURLE_OK; - if(-1 == nread) { - int err = SOCKERRNO; + if(data->set.seek_func) { + int err; - if( -#ifdef WSAEWOULDBLOCK - /* This is how Windows does it */ - (WSAEWOULDBLOCK == err) -#else - /* errno may be EWOULDBLOCK or on some systems EAGAIN when it returned - due to its inability to send off data without blocking. We therefor - treat both error codes the same here */ - (EWOULDBLOCK == err) || (EAGAIN == err) || (EINTR == err) -#endif - ) { - /* this is just a case of EWOULDBLOCK */ - *code = CURLE_AGAIN; + Curl_set_in_callback(data, true); + err = (data->set.seek_func)(data->set.seek_client, 0, SEEK_SET); + Curl_set_in_callback(data, false); + CURL_TRC_READ(data, "cr_in, rewind via set.seek_func -> %d", err); + if(err) { + failf(data, "seek callback returned error %d", (int)err); + return CURLE_SEND_FAIL_REWIND; } - else { - failf(conn->data, "Recv failure: %s", - Curl_strerror(conn, err)); - conn->data->state.os_errno = err; - *code = CURLE_RECV_ERROR; + } + 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); + CURL_TRC_READ(data, "cr_in, rewind via set.ioctl_func -> %d", (int)err); + if(err) { + failf(data, "ioctl callback returned error %d", (int)err); + return CURLE_SEND_FAIL_REWIND; } } - return nread; + 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) { + int err = fseek(data->state.in, 0, SEEK_SET); + CURL_TRC_READ(data, "cr_in, rewind via fseek -> %d(%d)", + (int)err, (int)errno); + if(-1 != err) + /* successful rewind */ + return CURLE_OK; + } + + /* no callback set or failure above, makes us fail at once */ + failf(data, "necessary data rewind was not possible"); + return CURLE_SEND_FAIL_REWIND; + } + return CURLE_OK; } -static CURLcode pausewrite(struct Curl_easy *data, - int type, /* what type of data */ - const char *ptr, - size_t len) +static CURLcode cr_in_unpause(struct Curl_easy *data, + struct Curl_creader *reader) { - /* 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; - char *dupl = malloc(len); - if(!dupl) - return CURLE_OUT_OF_MEMORY; + struct cr_in_ctx *ctx = reader->ctx; + (void)data; + ctx->is_paused = FALSE; + return CURLE_OK; +} - memcpy(dupl, ptr, len); +static bool cr_in_is_paused(struct Curl_easy *data, + struct Curl_creader *reader) +{ + struct cr_in_ctx *ctx = reader->ctx; + (void)data; + return ctx->is_paused; +} - /* store this information in the state struct for later use */ - data->state.tempwrite = dupl; - data->state.tempwritesize = len; - data->state.tempwritetype = type; +static const struct Curl_crtype cr_in = { + "cr-in", + cr_in_init, + cr_in_read, + Curl_creader_def_close, + cr_in_needs_rewind, + cr_in_total_length, + cr_in_resume_from, + cr_in_rewind, + cr_in_unpause, + cr_in_is_paused, + Curl_creader_def_done, + sizeof(struct cr_in_ctx) +}; + +CURLcode Curl_creader_create(struct Curl_creader **preader, + struct Curl_easy *data, + const struct Curl_crtype *crt, + Curl_creader_phase phase) +{ + struct Curl_creader *reader = NULL; + CURLcode result = CURLE_OUT_OF_MEMORY; + void *p; + + DEBUGASSERT(crt->creader_size >= sizeof(struct Curl_creader)); + p = calloc(1, crt->creader_size); + if(!p) + goto out; + + reader = (struct Curl_creader *)p; + reader->crt = crt; + reader->ctx = p; + reader->phase = phase; + result = crt->do_init(data, reader); + +out: + *preader = result ? NULL : reader; + if(result) + free(reader); + return result; +} - /* mark the connection as RECV paused */ - k->keepon |= KEEP_RECV_PAUSE; +void Curl_creader_free(struct Curl_easy *data, struct Curl_creader *reader) +{ + if(reader) { + reader->crt->do_close(data, reader); + free(reader); + } +} - DEBUGF(infof(data, "Pausing with %zu bytes in buffer for type %02x\n", - len, type)); +struct cr_lc_ctx { + struct Curl_creader super; + struct bufq buf; + BIT(read_eos); /* we read an EOS from the next reader */ + BIT(eos); /* we have returned an EOS */ + BIT(prev_cr); /* the last byte was a CR */ +}; +static CURLcode cr_lc_init(struct Curl_easy *data, struct Curl_creader *reader) +{ + struct cr_lc_ctx *ctx = reader->ctx; + (void)data; + Curl_bufq_init2(&ctx->buf, (16 * 1024), 1, BUFQ_OPT_SOFT_LIMIT); return CURLE_OK; } - -/* Curl_client_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. - */ -CURLcode Curl_client_chop_write(struct connectdata *conn, - int type, - char *ptr, - size_t len) +static void cr_lc_close(struct Curl_easy *data, struct Curl_creader *reader) { - struct Curl_easy *data = conn->data; - curl_write_callback writeheader = NULL; - curl_write_callback writebody = NULL; + struct cr_lc_ctx *ctx = reader->ctx; + (void)data; + Curl_bufq_free(&ctx->buf); +} - if(!len) - return CURLE_OK; +/* client reader doing line end conversions. */ +static CURLcode cr_lc_read(struct Curl_easy *data, + struct Curl_creader *reader, + char *buf, size_t blen, + size_t *pnread, bool *peos) +{ + struct cr_lc_ctx *ctx = reader->ctx; + CURLcode result; + size_t nread, i, start, n; + bool eos; - /* If reading is actually paused, we're forced to append this chunk of data - to the already held data, but only if it is the same type as otherwise it - can't work and it'll return error instead. */ - if(data->req.keepon & KEEP_RECV_PAUSE) { - size_t newlen; - char *newptr; - if(type != data->state.tempwritetype) - /* major internal confusion */ - return CURLE_RECV_ERROR; - - DEBUGASSERT(data->state.tempwrite); - - /* figure out the new size of the data to save */ - newlen = len + data->state.tempwritesize; - /* allocate the new memory area */ - newptr = realloc(data->state.tempwrite, newlen); - if(!newptr) - return CURLE_OUT_OF_MEMORY; - /* copy the new data to the end of the new area */ - memcpy(newptr + data->state.tempwritesize, ptr, len); - /* update the pointer and the size */ - data->state.tempwrite = newptr; - data->state.tempwritesize = newlen; + if(ctx->eos) { + *pnread = 0; + *peos = TRUE; return CURLE_OK; } - /* Determine the callback(s) to use. */ - if(type & CLIENTWRITE_BODY) - writebody = data->set.fwrite_func; - if((type & CLIENTWRITE_HEADER) && - (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 = writebody(ptr, 1, chunklen, data->set.out); - - 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; - } - else - return pausewrite(data, type, ptr, len); + if(Curl_bufq_is_empty(&ctx->buf)) { + if(ctx->read_eos) { + ctx->eos = TRUE; + *pnread = 0; + *peos = TRUE; + return CURLE_OK; + } + /* Still getting data form the next reader, ctx->buf is empty */ + result = Curl_creader_read(data, reader->next, buf, blen, &nread, &eos); + if(result) + return result; + ctx->read_eos = eos; + + if(!nread || !memchr(buf, '\n', nread)) { + /* nothing to convert, return this right away */ + if(ctx->read_eos) + ctx->eos = TRUE; + *pnread = nread; + *peos = ctx->eos; + goto out; + } + + /* at least one \n might need conversion to '\r\n', place into ctx->buf */ + for(i = start = 0; i < nread; ++i) { + /* if this byte is not an LF character, or if the preceding character is + a CR (meaning this already is a CRLF pair), go to next */ + if((buf[i] != '\n') || ctx->prev_cr) { + ctx->prev_cr = (buf[i] == '\r'); + continue; } - else if(wrote != chunklen) { - failf(data, "Failed writing body (%zu != %zu)", wrote, chunklen); - return CURLE_WRITE_ERROR; + ctx->prev_cr = false; + /* on a soft limit bufq, we do not need to check length */ + result = Curl_bufq_cwrite(&ctx->buf, buf + start, i - start, &n); + if(!result) + result = Curl_bufq_cwrite(&ctx->buf, STRCONST("\r\n"), &n); + if(result) + return result; + start = i + 1; + if(!data->set.crlf && (data->state.infilesize != -1)) { + /* we are here only because FTP is in ASCII mode... + bump infilesize for the LF we just added */ + data->state.infilesize++; + /* comment: this might work for FTP, but in HTTP we could not change + * the content length after having started the request... */ } } - if(writeheader) { - size_t wrote = writeheader(ptr, 1, chunklen, data->set.writeheader); - - if(CURL_WRITEFUNC_PAUSE == wrote) - /* here we pass in the HEADER bit only since if this was body as well - then it was passed already and clearly that didn't trigger the - pause, so this is saved for later with the HEADER bit only */ - return pausewrite(data, CLIENTWRITE_HEADER, ptr, len); - - if(wrote != chunklen) { - failf(data, "Failed writing header"); - return CURLE_WRITE_ERROR; - } + if(start < i) { /* leftover */ + result = Curl_bufq_cwrite(&ctx->buf, buf + start, i - start, &n); + if(result) + return result; } + } - ptr += chunklen; - len -= chunklen; + DEBUGASSERT(!Curl_bufq_is_empty(&ctx->buf)); + *peos = FALSE; + result = Curl_bufq_cread(&ctx->buf, buf, blen, pnread); + if(!result && ctx->read_eos && Curl_bufq_is_empty(&ctx->buf)) { + /* no more data, read all, done. */ + ctx->eos = TRUE; + *peos = TRUE; } - return CURLE_OK; +out: + CURL_TRC_READ(data, "cr_lc_read(len=%zu) -> %d, nread=%zu, eos=%d", + blen, result, *pnread, *peos); + return result; } +static curl_off_t cr_lc_total_length(struct Curl_easy *data, + struct Curl_creader *reader) +{ + /* this reader changes length depending on input */ + (void)data; + (void)reader; + return -1; +} -/* Curl_client_write() sends data to the write callback(s) +static const struct Curl_crtype cr_lc = { + "cr-lineconv", + cr_lc_init, + cr_lc_read, + cr_lc_close, + Curl_creader_def_needs_rewind, + cr_lc_total_length, + Curl_creader_def_resume_from, + Curl_creader_def_rewind, + Curl_creader_def_unpause, + Curl_creader_def_is_paused, + Curl_creader_def_done, + sizeof(struct cr_lc_ctx) +}; + +static CURLcode cr_lc_add(struct Curl_easy *data) +{ + struct Curl_creader *reader = NULL; + CURLcode result; - The bit pattern defines to what "streams" to write to. Body and/or header. - The defines are in sendf.h of course. + result = Curl_creader_create(&reader, data, &cr_lc, + CURL_CR_CONTENT_ENCODE); + if(!result) + result = Curl_creader_add(data, reader); - 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 connectdata *conn, - int type, - char *ptr, - size_t len) -{ - struct Curl_easy *data = conn->data; - - if(0 == len) - len = strlen(ptr); - - /* FTP data may need conversion. */ - if((type & CLIENTWRITE_BODY) && - (conn->handler->protocol & PROTO_FAMILY_FTP) && - conn->proto.ftpc.transfertype == 'A') { - /* convert from the network encoding */ - CURLcode result = Curl_convert_from_network(data, ptr, len); - /* Curl_convert_from_network calls failf if unsuccessful */ + if(result && reader) + Curl_creader_free(data, reader); + return result; +} + +static CURLcode do_init_reader_stack(struct Curl_easy *data, + struct Curl_creader *r) +{ + CURLcode result = CURLE_OK; + curl_off_t clen; + + DEBUGASSERT(r); + DEBUGASSERT(r->crt); + DEBUGASSERT(r->phase == CURL_CR_CLIENT); + DEBUGASSERT(!data->req.reader_stack); + + data->req.reader_stack = r; + clen = r->crt->total_length(data, r); + /* if we do not have 0 length init, and crlf conversion is wanted, + * add the reader for it */ + if(clen && (data->set.crlf +#ifdef CURL_PREFER_LF_LINEENDS + || data->state.prefer_ascii +#endif + )) { + result = cr_lc_add(data); if(result) return result; + } -#ifdef CURL_DO_LINEEND_CONV - /* convert end-of-line markers */ - len = convert_lineends(data, ptr, len); -#endif /* CURL_DO_LINEEND_CONV */ - } + return result; +} - return Curl_client_chop_write(conn, type, ptr, len); +CURLcode Curl_creader_set_fread(struct Curl_easy *data, curl_off_t len) +{ + CURLcode result; + struct Curl_creader *r; + struct cr_in_ctx *ctx; + + result = Curl_creader_create(&r, data, &cr_in, CURL_CR_CLIENT); + if(result) + goto out; + ctx = r->ctx; + ctx->total_len = len; + + cl_reset_reader(data); + result = do_init_reader_stack(data, r); +out: + CURL_TRC_READ(data, "add fread reader, len=%"FMT_OFF_T " -> %d", + len, result); + return result; } -CURLcode Curl_read_plain(curl_socket_t sockfd, - char *buf, - size_t bytesfromsocket, - ssize_t *n) +CURLcode Curl_creader_add(struct Curl_easy *data, + struct Curl_creader *reader) { - ssize_t nread = sread(sockfd, buf, bytesfromsocket); + CURLcode result; + struct Curl_creader **anchor = &data->req.reader_stack; - if(-1 == nread) { - int err = SOCKERRNO; - int return_error; -#ifdef USE_WINSOCK - return_error = WSAEWOULDBLOCK == err; -#else - return_error = EWOULDBLOCK == err || EAGAIN == err || EINTR == err; -#endif - if(return_error) - return CURLE_AGAIN; - else - return CURLE_RECV_ERROR; + if(!*anchor) { + result = Curl_creader_set_fread(data, data->state.infilesize); + if(result) + return result; } - /* we only return number of bytes read when we return OK */ - *n = nread; + /* Insert the writer as first in its phase. + * Skip existing readers of lower phases. */ + while(*anchor && (*anchor)->phase < reader->phase) + anchor = &((*anchor)->next); + reader->next = *anchor; + *anchor = reader; 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 connectdata *conn, /* connection data */ - 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; - - /* if HTTP/1 pipelining is both wanted and possible */ - bool pipelining = Curl_pipeline_wanted(conn->data->multi, CURLPIPE_HTTP1) && - (conn->bundle->multiuse == BUNDLE_PIPELINING); - - /* 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 */ - - /* If session can pipeline, check connection buffer */ - if(pipelining) { - size_t bytestocopy = CURLMIN(conn->buf_len - conn->read_pos, - sizerequested); - - /* Copy from our master buffer first if we have some unread data there*/ - if(bytestocopy > 0) { - memcpy(buf, conn->master_buffer + conn->read_pos, bytestocopy); - conn->read_pos += bytestocopy; - conn->bits.stream_was_rewound = FALSE; - - *n = (ssize_t)bytestocopy; - return CURLE_OK; - } - /* If we come here, it means that there is no data to read from the buffer, - * so we read from the socket */ - bytesfromsocket = CURLMIN(sizerequested, BUFSIZE * sizeof(char)); - buffertofill = conn->master_buffer; +CURLcode Curl_creader_set(struct Curl_easy *data, struct Curl_creader *r) +{ + CURLcode result; + + DEBUGASSERT(r); + DEBUGASSERT(r->crt); + DEBUGASSERT(r->phase == CURL_CR_CLIENT); + + cl_reset_reader(data); + result = do_init_reader_stack(data, r); + if(result) + Curl_creader_free(data, r); + return result; +} + +CURLcode Curl_client_read(struct Curl_easy *data, char *buf, size_t blen, + size_t *nread, bool *eos) +{ + CURLcode result; + + DEBUGASSERT(buf); + DEBUGASSERT(blen); + DEBUGASSERT(nread); + DEBUGASSERT(eos); + + if(!data->req.reader_stack) { + result = Curl_creader_set_fread(data, data->state.infilesize); + if(result) + return result; + DEBUGASSERT(data->req.reader_stack); } - else { - bytesfromsocket = CURLMIN((long)sizerequested, - conn->data->set.buffer_size ? - conn->data->set.buffer_size : BUFSIZE); - buffertofill = buf; + + result = Curl_creader_read(data, data->req.reader_stack, buf, blen, + nread, eos); + CURL_TRC_READ(data, "client_read(len=%zu) -> %d, nread=%zu, eos=%d", + blen, result, *nread, *eos); + return result; +} + +bool Curl_creader_needs_rewind(struct Curl_easy *data) +{ + struct Curl_creader *reader = data->req.reader_stack; + while(reader) { + if(reader->crt->needs_rewind(data, reader)) { + CURL_TRC_READ(data, "client reader needs rewind before next request"); + return TRUE; + } + reader = reader->next; } + return FALSE; +} + +static CURLcode cr_null_read(struct Curl_easy *data, + struct Curl_creader *reader, + char *buf, size_t blen, + size_t *pnread, bool *peos) +{ + (void)data; + (void)reader; + (void)buf; + (void)blen; + *pnread = 0; + *peos = TRUE; + return CURLE_OK; +} + +static curl_off_t cr_null_total_length(struct Curl_easy *data, + struct Curl_creader *reader) +{ + /* this reader changes length depending on input */ + (void)data; + (void)reader; + return 0; +} + +static const struct Curl_crtype cr_null = { + "cr-null", + Curl_creader_def_init, + cr_null_read, + Curl_creader_def_close, + Curl_creader_def_needs_rewind, + cr_null_total_length, + Curl_creader_def_resume_from, + Curl_creader_def_rewind, + Curl_creader_def_unpause, + Curl_creader_def_is_paused, + Curl_creader_def_done, + sizeof(struct Curl_creader) +}; + +CURLcode Curl_creader_set_null(struct Curl_easy *data) +{ + struct Curl_creader *r; + CURLcode result; - nread = conn->recv[num](conn, num, buffertofill, bytesfromsocket, &result); - if(nread < 0) + result = Curl_creader_create(&r, data, &cr_null, CURL_CR_CLIENT); + if(result) return result; - if(pipelining) { - memcpy(buf, conn->master_buffer, nread); - conn->buf_len = nread; - conn->read_pos = nread; + cl_reset_reader(data); + return do_init_reader_stack(data, r); +} + +struct cr_buf_ctx { + struct Curl_creader super; + const char *buf; + size_t blen; + size_t index; +}; + +static CURLcode cr_buf_read(struct Curl_easy *data, + struct Curl_creader *reader, + char *buf, size_t blen, + size_t *pnread, bool *peos) +{ + struct cr_buf_ctx *ctx = reader->ctx; + size_t nread = ctx->blen - ctx->index; + + (void)data; + if(!nread || !ctx->buf) { + *pnread = 0; + *peos = TRUE; + } + else { + if(nread > blen) + nread = blen; + memcpy(buf, ctx->buf + ctx->index, nread); + *pnread = nread; + ctx->index += nread; + *peos = (ctx->index == ctx->blen); } + CURL_TRC_READ(data, "cr_buf_read(len=%zu) -> 0, nread=%zu, eos=%d", + blen, *pnread, *peos); + return CURLE_OK; +} - *n += nread; +static bool cr_buf_needs_rewind(struct Curl_easy *data, + struct Curl_creader *reader) +{ + struct cr_buf_ctx *ctx = reader->ctx; + (void)data; + return ctx->index > 0; +} +static curl_off_t cr_buf_total_length(struct Curl_easy *data, + struct Curl_creader *reader) +{ + struct cr_buf_ctx *ctx = reader->ctx; + (void)data; + return (curl_off_t)ctx->blen; +} + +static CURLcode cr_buf_resume_from(struct Curl_easy *data, + struct Curl_creader *reader, + curl_off_t offset) +{ + struct cr_buf_ctx *ctx = reader->ctx; + size_t boffset; + + (void)data; + DEBUGASSERT(data->conn); + /* already started reading? */ + if(ctx->index) + return CURLE_READ_ERROR; + if(offset <= 0) + return CURLE_OK; + boffset = (size_t)offset; + if(boffset > ctx->blen) + return CURLE_READ_ERROR; + + ctx->buf += boffset; + ctx->blen -= boffset; return CURLE_OK; } -/* return 0 on success */ -static int showit(struct Curl_easy *data, curl_infotype type, - char *ptr, size_t size) +static const struct Curl_crtype cr_buf = { + "cr-buf", + Curl_creader_def_init, + cr_buf_read, + Curl_creader_def_close, + cr_buf_needs_rewind, + cr_buf_total_length, + cr_buf_resume_from, + Curl_creader_def_rewind, + Curl_creader_def_unpause, + Curl_creader_def_is_paused, + Curl_creader_def_done, + sizeof(struct cr_buf_ctx) +}; + +CURLcode Curl_creader_set_buf(struct Curl_easy *data, + const char *buf, size_t blen) { - static const char s_infotype[CURLINFO_END][3] = { - "* ", "< ", "> ", "{ ", "} ", "{ ", "} " }; + CURLcode result; + struct Curl_creader *r; + struct cr_buf_ctx *ctx; + + result = Curl_creader_create(&r, data, &cr_buf, CURL_CR_CLIENT); + if(result) + goto out; + ctx = r->ctx; + ctx->buf = buf; + ctx->blen = blen; + ctx->index = 0; + + cl_reset_reader(data); + result = do_init_reader_stack(data, r); +out: + CURL_TRC_READ(data, "add buf reader, len=%zu -> %d", blen, result); + return result; +} -#ifdef CURL_DOES_CONVERSIONS - char buf[BUFSIZE+1]; - size_t conv_size = 0; +curl_off_t Curl_creader_total_length(struct Curl_easy *data) +{ + struct Curl_creader *r = data->req.reader_stack; + return r ? r->crt->total_length(data, r) : -1; +} - switch(type) { - case CURLINFO_HEADER_OUT: - /* assume output headers are ASCII */ - /* copy the data into my buffer so the original is unchanged */ - if(size > BUFSIZE) { - size = BUFSIZE; /* truncate if necessary */ - buf[BUFSIZE] = '\0'; - } - conv_size = size; - memcpy(buf, ptr, size); - /* Special processing is needed for this block if it - * contains both headers and data (separated by CRLFCRLF). - * We want to convert just the headers, leaving the data as-is. - */ - if(size > 4) { - size_t i; - for(i = 0; i < size-4; i++) { - if(memcmp(&buf[i], "\x0d\x0a\x0d\x0a", 4) == 0) { - /* convert everything through this CRLFCRLF but no further */ - conv_size = i + 4; - break; - } - } - } +curl_off_t Curl_creader_client_length(struct Curl_easy *data) +{ + struct Curl_creader *r = data->req.reader_stack; + while(r && r->phase != CURL_CR_CLIENT) + r = r->next; + return r ? r->crt->total_length(data, r) : -1; +} - Curl_convert_from_network(data, buf, conv_size); - /* Curl_convert_from_network calls failf if unsuccessful */ - /* we might as well continue even if it fails... */ - ptr = buf; /* switch pointer to use my buffer instead */ - break; - default: - /* leave everything else as-is */ - break; +CURLcode Curl_creader_resume_from(struct Curl_easy *data, curl_off_t offset) +{ + struct Curl_creader *r = data->req.reader_stack; + while(r && r->phase != CURL_CR_CLIENT) + r = r->next; + return r ? r->crt->resume_from(data, r, offset) : CURLE_READ_ERROR; +} + +CURLcode Curl_creader_unpause(struct Curl_easy *data) +{ + struct Curl_creader *reader = data->req.reader_stack; + CURLcode result = CURLE_OK; + + while(reader) { + result = reader->crt->unpause(data, reader); + if(result) + break; + reader = reader->next; } -#endif /* CURL_DOES_CONVERSIONS */ - - if(data->set.fdebug) - return (*data->set.fdebug)(data, type, ptr, size, - data->set.debugdata); - - 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); -#ifdef CURL_DOES_CONVERSIONS - if(size != conv_size) { - /* we had untranslated data so we need an explicit newline */ - fwrite("\n", 1, 1, data->set.err); - } -#endif - break; - default: /* nada */ - break; + return result; +} + +bool Curl_creader_is_paused(struct Curl_easy *data) +{ + struct Curl_creader *reader = data->req.reader_stack; + + while(reader) { + if(reader->crt->is_paused(data, reader)) + return TRUE; + reader = reader->next; } - return 0; + return FALSE; } -int Curl_debug(struct Curl_easy *data, curl_infotype type, - char *ptr, size_t size, - struct connectdata *conn) -{ - int rc; - if(data->set.printhost && conn && conn->host.dispname) { - char buffer[160]; - const char *t=NULL; - const char *w="Data"; - switch(type) { - case CURLINFO_HEADER_IN: - w = "Header"; - /* FALLTHROUGH */ - case CURLINFO_DATA_IN: - t = "from"; - break; - case CURLINFO_HEADER_OUT: - w = "Header"; - /* FALLTHROUGH */ - case CURLINFO_DATA_OUT: - t = "to"; - break; - default: - break; - } +void Curl_creader_done(struct Curl_easy *data, int premature) +{ + struct Curl_creader *reader = data->req.reader_stack; + while(reader) { + reader->crt->done(data, reader, premature); + reader = reader->next; + } +} - if(t) { - snprintf(buffer, sizeof(buffer), "[%s %s %s]", w, t, - conn->host.dispname); - rc = showit(data, CURLINFO_TEXT, buffer, strlen(buffer)); - if(rc) - return rc; - } +struct Curl_creader *Curl_creader_get_by_type(struct Curl_easy *data, + const struct Curl_crtype *crt) +{ + struct Curl_creader *r; + for(r = data->req.reader_stack; r; r = r->next) { + if(r->crt == crt) + return r; } - rc = showit(data, type, ptr, size); - return rc; + return NULL; + } diff --git a/contrib/curl/lib/sendf.h b/contrib/curl/lib/sendf.h index fbe4f99c87..dc1b82edfe 100644 --- a/contrib/curl/lib/sendf.h +++ b/contrib/curl/lib/sendf.h @@ -7,11 +7,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2014, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -20,75 +20,396 @@ * 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" -CURLcode Curl_sendf(curl_socket_t sockfd, struct connectdata *, - const char *fmt, ...); -void Curl_infof(struct Curl_easy *, const char *fmt, ...); -void Curl_failf(struct Curl_easy *, const char *fmt, ...); +#include "curl_trc.h" -#if defined(CURL_DISABLE_VERBOSE_STRINGS) +/** + * 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 */ +#define CLIENTWRITE_EOS (1<<7) /* End Of transfer download Stream */ -#if defined(HAVE_VARIADIC_MACROS_C99) -#define infof(...) Curl_nop_stmt -#elif defined(HAVE_VARIADIC_MACROS_GCC) -#define infof(x...) Curl_nop_stmt -#else -#define infof (void) -#endif +/** + * 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, const char *ptr, + size_t len) WARN_UNUSED_RESULT; -#else /* CURL_DISABLE_VERBOSE_STRINGS */ +/** + * Free all resources related to client writing. + */ +void Curl_client_cleanup(struct Curl_easy *data); -#define infof Curl_infof +/** + * Reset readers and writer chains, keep rewind information + * when necessary. + */ +void Curl_client_reset(struct Curl_easy *data); -#endif /* CURL_DISABLE_VERBOSE_STRINGS */ +/** + * A new request is starting, perform any ops like rewinding + * previous readers when needed. + */ +CURLcode Curl_client_start(struct Curl_easy *data); -#define failf Curl_failf +/** + * 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. + */ -#define CLIENTWRITE_BODY (1<<0) -#define CLIENTWRITE_HEADER (1<<1) -#define CLIENTWRITE_BOTH (CLIENTWRITE_BODY|CLIENTWRITE_HEADER) +/* 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; -CURLcode Curl_client_chop_write(struct connectdata *conn, int type, char *ptr, - size_t len) WARN_UNUSED_RESULT; -CURLcode Curl_client_write(struct connectdata *conn, int type, char *ptr, - size_t len) WARN_UNUSED_RESULT; +/* 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, allocated on creation. + * `void *ctx` is the pointer from the allocation of + * the `struct Curl_cwriter` itself. This is suitable for "downcasting" + * by the writers implementation. See https://github.com/curl/curl/pull/13054 + * for the alignment problems that arise otherwise. + */ +struct Curl_cwriter { + const struct Curl_cwtype *cwt; /* type implementation */ + struct Curl_cwriter *next; /* Downstream writer. */ + void *ctx; /* allocated instance pointer */ + 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); + +/** + * Look up an installed client writer on `data` by its type. + * @return first writer with that type or NULL + */ +struct Curl_cwriter *Curl_cwriter_get_by_type(struct Curl_easy *data, + const struct Curl_cwtype *cwt); + +void Curl_cwriter_remove_by_name(struct Curl_easy *data, + const char *name); + +struct Curl_cwriter *Curl_cwriter_get_by_name(struct Curl_easy *data, + const char *name); + +/** + * Convenience method for calling `writer->do_write()` that + * checks for NULL writer. + */ +CURLcode Curl_cwriter_write(struct Curl_easy *data, + struct Curl_cwriter *writer, int type, + const char *buf, size_t nbytes); + +/** + * Return TRUE iff client writer is paused. + */ +bool Curl_cwriter_is_paused(struct Curl_easy *data); + +/** + * Unpause client writer and flush any buffered date to the client. + */ +CURLcode Curl_cwriter_unpause(struct Curl_easy *data); + +/** + * Default implementations for do_init, do_write, do_close that + * do nothing and pass the data through. + */ +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); + + + +/* Client Reader Type, provides the implementation */ +struct Curl_crtype { + const char *name; /* writer name. */ + CURLcode (*do_init)(struct Curl_easy *data, struct Curl_creader *reader); + CURLcode (*do_read)(struct Curl_easy *data, struct Curl_creader *reader, + char *buf, size_t blen, size_t *nread, bool *eos); + void (*do_close)(struct Curl_easy *data, struct Curl_creader *reader); + bool (*needs_rewind)(struct Curl_easy *data, struct Curl_creader *reader); + curl_off_t (*total_length)(struct Curl_easy *data, + struct Curl_creader *reader); + CURLcode (*resume_from)(struct Curl_easy *data, + struct Curl_creader *reader, curl_off_t offset); + CURLcode (*rewind)(struct Curl_easy *data, struct Curl_creader *reader); + CURLcode (*unpause)(struct Curl_easy *data, struct Curl_creader *reader); + bool (*is_paused)(struct Curl_easy *data, struct Curl_creader *reader); + void (*done)(struct Curl_easy *data, + struct Curl_creader *reader, int premature); + size_t creader_size; /* sizeof() allocated struct Curl_creader */ +}; + +/* Phase a reader operates at. */ +typedef enum { + CURL_CR_NET, /* data send to the network (connection filters) */ + CURL_CR_TRANSFER_ENCODE, /* add transfer-encodings */ + CURL_CR_PROTOCOL, /* before transfer, but after content decoding */ + CURL_CR_CONTENT_ENCODE, /* add content-encodings */ + CURL_CR_CLIENT /* data read from client */ +} Curl_creader_phase; + +/* Client reader instance, allocated on creation. + * `void *ctx` is the pointer from the allocation of + * the `struct Curl_cwriter` itself. This is suitable for "downcasting" + * by the writers implementation. See https://github.com/curl/curl/pull/13054 + * for the alignment problems that arise otherwise. + */ +struct Curl_creader { + const struct Curl_crtype *crt; /* type implementation */ + struct Curl_creader *next; /* Downstream reader. */ + void *ctx; + Curl_creader_phase phase; /* phase at which it operates */ +}; + +/** + * Default implementations for do_init, do_write, do_close that + * do nothing and pass the data through. + */ +CURLcode Curl_creader_def_init(struct Curl_easy *data, + struct Curl_creader *reader); +void Curl_creader_def_close(struct Curl_easy *data, + struct Curl_creader *reader); +CURLcode Curl_creader_def_read(struct Curl_easy *data, + struct Curl_creader *reader, + char *buf, size_t blen, + size_t *nread, bool *eos); +bool Curl_creader_def_needs_rewind(struct Curl_easy *data, + struct Curl_creader *reader); +curl_off_t Curl_creader_def_total_length(struct Curl_easy *data, + struct Curl_creader *reader); +CURLcode Curl_creader_def_resume_from(struct Curl_easy *data, + struct Curl_creader *reader, + curl_off_t offset); +CURLcode Curl_creader_def_rewind(struct Curl_easy *data, + struct Curl_creader *reader); +CURLcode Curl_creader_def_unpause(struct Curl_easy *data, + struct Curl_creader *reader); +bool Curl_creader_def_is_paused(struct Curl_easy *data, + struct Curl_creader *reader); +void Curl_creader_def_done(struct Curl_easy *data, + struct Curl_creader *reader, int premature); + +/** + * Convenience method for calling `reader->do_read()` that + * checks for NULL reader. + */ +CURLcode Curl_creader_read(struct Curl_easy *data, + struct Curl_creader *reader, + char *buf, size_t blen, size_t *nread, bool *eos); + +/** + * Create a new creader instance with given type and phase. Is not + * inserted into the writer chain by this call. + * Invokes `reader->do_init()`. + */ +CURLcode Curl_creader_create(struct Curl_creader **preader, + struct Curl_easy *data, + const struct Curl_crtype *cr_handler, + Curl_creader_phase phase); + +/** + * Free a creader instance. + * Invokes `reader->do_close()`. + */ +void Curl_creader_free(struct Curl_easy *data, struct Curl_creader *reader); + +/** + * Adds a reader to the transfer's reader chain. + * The readers `phase` determines where in the chain it is inserted. + */ +CURLcode Curl_creader_add(struct Curl_easy *data, + struct Curl_creader *reader); + +/** + * Set the given reader, which needs to be of type CURL_CR_CLIENT, + * as the new first reader. Discard any installed readers and init + * the reader chain anew. + * The function takes ownership of `r`. + */ +CURLcode Curl_creader_set(struct Curl_easy *data, struct Curl_creader *r); + +/** + * Read at most `blen` bytes at `buf` from the client. + * @param data the transfer to read client bytes for + * @param buf the memory location to read to + * @param blen the amount of memory at `buf` + * @param nread on return the number of bytes read into `buf` + * @param eos TRUE iff bytes are the end of data from client + * @return CURLE_OK on successful read (even 0 length) or error + */ +CURLcode Curl_client_read(struct Curl_easy *data, char *buf, size_t blen, + size_t *nread, bool *eos) WARN_UNUSED_RESULT; + +/** + * TRUE iff client reader needs rewing before it can be used for + * a retry request. + */ +bool Curl_creader_needs_rewind(struct Curl_easy *data); + +/** + * TRUE iff client reader will rewind at next start + */ +bool Curl_creader_will_rewind(struct Curl_easy *data); + +/** + * En-/disable rewind of client reader at next start. + */ +void Curl_creader_set_rewind(struct Curl_easy *data, bool enable); + +/** + * Get the total length of bytes provided by the installed readers. + * This is independent of the amount already delivered and is calculated + * by all readers in the stack. If a reader like "chunked" or + * "crlf conversion" is installed, the returned length will be -1. + * @return -1 if length is indeterminate + */ +curl_off_t Curl_creader_total_length(struct Curl_easy *data); + +/** + * Get the total length of bytes provided by the reader at phase + * CURL_CR_CLIENT. This may not match the amount of bytes read + * for a request, depending if other, encoding readers are also installed. + * However it allows for rough estimation of the overall length. + * @return -1 if length is indeterminate + */ +curl_off_t Curl_creader_client_length(struct Curl_easy *data); + +/** + * Ask the installed reader at phase CURL_CR_CLIENT to start + * reading from the given offset. On success, this will reduce + * the `total_length()` by the amount. + * @param data the transfer to read client bytes for + * @param offset the offset where to start reads from, negative + * values will be ignored. + * @return CURLE_OK if offset could be set + * CURLE_READ_ERROR if not supported by reader or seek/read failed + * of offset larger then total length + * CURLE_PARTIAL_FILE if offset led to 0 total length + */ +CURLcode Curl_creader_resume_from(struct Curl_easy *data, curl_off_t offset); + +/** + * Unpause all installed readers. + */ +CURLcode Curl_creader_unpause(struct Curl_easy *data); + +/** + * Return TRUE iff any of the installed readers is paused. + */ +bool Curl_creader_is_paused(struct Curl_easy *data); + +/** + * Tell all client readers that they are done. + */ +void Curl_creader_done(struct Curl_easy *data, int premature); + +/** + * Look up an installed client reader on `data` by its type. + * @return first reader with that type or NULL + */ +struct Curl_creader *Curl_creader_get_by_type(struct Curl_easy *data, + const struct Curl_crtype *crt); + + +/** + * Set the client reader to provide 0 bytes, immediate EOS. + */ +CURLcode Curl_creader_set_null(struct Curl_easy *data); -bool Curl_recv_has_postponed_data(struct connectdata *conn, int sockindex); - -/* internal read-function, does plain socket only */ -CURLcode Curl_read_plain(curl_socket_t sockfd, - char *buf, - size_t bytesfromsocket, - ssize_t *n); - -ssize_t Curl_recv_plain(struct connectdata *conn, int num, char *buf, - size_t len, CURLcode *code); -ssize_t Curl_send_plain(struct connectdata *conn, int num, - const void *mem, size_t len, CURLcode *code); - -/* internal read-function, does plain socket, SSL and krb4 */ -CURLcode Curl_read(struct connectdata *conn, 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 connectdata *conn, - curl_socket_t sockfd, - const void *mem, size_t len, - ssize_t *written); - -/* internal write-function, does plain sockets ONLY */ -CURLcode Curl_write_plain(struct connectdata *conn, - curl_socket_t sockfd, - const void *mem, size_t len, - ssize_t *written); - -/* the function used to output verbose information */ -int Curl_debug(struct Curl_easy *handle, curl_infotype type, - char *data, size_t size, - struct connectdata *conn); +/** + * Set the client reader the reads from fread callback. + */ +CURLcode Curl_creader_set_fread(struct Curl_easy *data, curl_off_t len); +/** + * Set the client reader the reads from the supplied buf (NOT COPIED). + */ +CURLcode Curl_creader_set_buf(struct Curl_easy *data, + const char *buf, size_t blen); #endif /* HEADER_CURL_SENDF_H */ diff --git a/contrib/curl/lib/setopt.c b/contrib/curl/lib/setopt.c new file mode 100644 index 0000000000..0ae8af4e98 --- /dev/null +++ b/contrib/curl/lib/setopt.c @@ -0,0 +1,3276 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#include + +#ifdef HAVE_NETINET_IN_H +#include +#endif + +#ifdef HAVE_LINUX_TCP_H +#include +#elif defined(HAVE_NETINET_TCP_H) +#include +#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" +#include "strdup.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" + +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) +{ + char *user = NULL; + char *passwd = NULL; + + DEBUGASSERT(userp); + DEBUGASSERT(passwdp); + + /* Parse the login details if specified. It not then we treat NULL as a hint + to clear the existing data */ + if(option) { + size_t len = strlen(option); + CURLcode result; + if(len > CURL_MAX_INPUT_LENGTH) + return CURLE_BAD_FUNCTION_ARGUMENT; + + result = Curl_parse_login_details(option, len, &user, &passwd, NULL); + if(result) + return result; + } + + free(*userp); + *userp = user; + + free(*passwdp); + *passwdp = passwd; + + return CURLE_OK; +} + +static CURLcode setstropt_interface(char *option, char **devp, + char **ifacep, char **hostp) +{ + char *dev = NULL; + char *iface = NULL; + char *host = NULL; + CURLcode result; + + DEBUGASSERT(devp); + DEBUGASSERT(ifacep); + DEBUGASSERT(hostp); + + if(option) { + /* Parse the interface details if set, otherwise clear them all */ + result = Curl_parse_interface(option, &dev, &iface, &host); + if(result) + return result; + } + free(*devp); + *devp = dev; + + free(*ifacep); + *ifacep = iface; + + free(*hostp); + *hostp = host; + + return CURLE_OK; +} + +#define C_SSLVERSION_VALUE(x) (x & 0xffff) +#define C_SSLVERSION_MAX_VALUE(x) ((unsigned long)x & 0xffff0000) + +static CURLcode protocol2num(const char *str, curl_prot_t *val) +{ + /* + * We are asked to cherry-pick protocols, so play it safe and disallow all + * protocols to start with, and re-add the wanted ones back in. + */ + *val = 0; + + if(!str) + return CURLE_BAD_FUNCTION_ARGUMENT; + + if(curl_strequal(str, "all")) { + *val = ~(curl_prot_t) 0; + return CURLE_OK; + } + + 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: + if(Curl_ssl_supports(data, SSLSUPP_CA_CACHE)) { + 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; + } + else + return CURLE_NOT_BUILT_IN; + break; + case CURLOPT_DNS_USE_GLOBAL_CACHE: + /* deprecated */ + break; + case CURLOPT_SSL_CIPHER_LIST: + if(Curl_ssl_supports(data, SSLSUPP_CIPHER_LIST)) { + /* set a list of cipher we want to use in the SSL connection */ + result = Curl_setstropt(&data->set.str[STRING_SSL_CIPHER_LIST], + va_arg(param, char *)); + } + else + return CURLE_NOT_BUILT_IN; + break; +#ifndef CURL_DISABLE_PROXY + case CURLOPT_PROXY_SSL_CIPHER_LIST: + if(Curl_ssl_supports(data, SSLSUPP_CIPHER_LIST)) { + /* set a list of cipher we want to use in the SSL connection for proxy */ + result = Curl_setstropt(&data->set.str[STRING_SSL_CIPHER_LIST_PROXY], + va_arg(param, char *)); + } + else + return CURLE_NOT_BUILT_IN; + 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: + /* + * Do not 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; + case CURLOPT_SERVER_RESPONSE_TIMEOUT_MS: + /* + * 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)) + data->set.server_response_timeout = (unsigned int)arg; + 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_MIN) + arg = 512; + else if(arg > TFTP_BLKSIZE_MAX) + arg = TFTP_BLKSIZE_MAX; + 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 = (long)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 { + /* 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. + */ + char *p = Curl_memdup0(argptr, (size_t)data->set.postfieldsize); + if(!p) + result = CURLE_OUT_OF_MEMORY; + else { + free(data->set.str[STRING_COPYPOSTFIELDS]); + 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. */ + Curl_safefree(data->set.str[STRING_COPYPOSTFIELDS]); + 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. */ + Curl_safefree(data->set.str[STRING_COPYPOSTFIELDS]); + 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. */ + Curl_safefree(data->set.str[STRING_COPYPOSTFIELDS]); + 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 do not 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 is not 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); + data->state.mimepost = NULL; + 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 filename to the list of filenames, 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 is not 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 filename to dump all cookies to when we are done. + */ + result = Curl_setstropt(&data->set.str[STRING_COOKIEJAR], + va_arg(param, char *)); + if(!result) { + /* + * Activate the cookie parser. This may or may not already + * have been made. + */ + struct CookieInfo *newcookies = + Curl_cookie_init(data, NULL, data->cookies, data->set.cookiesession); + if(!newcookies) + result = CURLE_OUT_OF_MEMORY; + data->cookies = newcookies; + } + break; + + case CURLOPT_COOKIESESSION: + /* + * 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 USE_HTTP3 + 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 = (long)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); + data->state.mimepost = NULL; +#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 cannot support */ +#ifndef USE_NTLM + auth &= ~CURLAUTH_NTLM; /* no NTLM 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 do not 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 cannot support */ +#ifndef USE_NTLM + auth &= ~CURLAUTH_NTLM; /* no NTLM 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 do not 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 do not 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=) 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; +#ifdef HAVE_GSSAPI + 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 +#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 username 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 are not 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 + */ + char *u = NULL; + char *p = NULL; + result = setstropt_userpwd(va_arg(param, char *), &u, &p); + + /* URL decode the components */ + if(!result && u) + result = Curl_urldecode(u, 0, &data->set.str[STRING_PROXYUSERNAME], NULL, + REJECT_ZERO); + if(!result && p) + result = Curl_urldecode(p, 0, &data->set.str[STRING_PROXYPASSWORD], NULL, + REJECT_ZERO); + free(u); + free(p); + } + break; + case CURLOPT_PROXYUSERNAME: + /* + * authentication username 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 will 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 filename 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 filename 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 filename 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 filename 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_safefree(data->set.str[STRING_SSL_ENGINE]); + 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 = setstropt_interface(va_arg(param, char *), + &data->set.str[STRING_DEVICE], + &data->set.str[STRING_INTERFACE], + &data->set.str[STRING_BINDHOST]); + 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)); + + /* Update the current connection proxy_ssl_config. */ + Curl_ssl_conn_config_update(data, TRUE); + break; +#endif + case CURLOPT_SSL_VERIFYHOST: + /* + * Enable verification of the hostname 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 was not 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 hostname 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 hostname in the peer certificate for proxy + */ + arg = va_arg(param, long); + + /* Treat both 1 and 2 as TRUE */ + data->set.proxy_ssl.primary.verifyhost = !!(arg & 3); + /* 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 filename 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 filename 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 filename 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 filename 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 filename 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 filename 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 will use it. + */ + 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; + 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 +#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.cache_session = (0 != va_arg(param, long)); +#ifndef CURL_DISABLE_PROXY + data->set.proxy_ssl.primary.cache_session = + data->set.ssl.primary.cache_session; +#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 = (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 filename 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)); + 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 does not 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 USE_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: { + argptr = va_arg(param, char *); + if(argptr) { + result = protocol2num(argptr, &data->set.allowed_protocols); + if(result) + return result; + } + else + /* make a NULL argument reset to default */ + data->set.allowed_protocols = (curl_prot_t) CURLPROTO_ALL; + break; + } + + case CURLOPT_REDIR_PROTOCOLS_STR: { + argptr = va_arg(param, char *); + if(argptr) { + result = protocol2num(argptr, &data->set.redir_protocols); + if(result) + return result; + } + else + /* make a NULL argument reset to default */ + data->set.redir_protocols = (curl_prot_t) CURLPROTO_REDIR; + break; + } + + case CURLOPT_DEFAULT_PROTOCOL: + /* Set the protocol to use when the URL does not 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 && !strcasecompare(argptr, "SRP")) + return CURLE_BAD_FUNCTION_ARGUMENT; + break; +#ifndef CURL_DISABLE_PROXY + case CURLOPT_PROXY_TLSAUTH_TYPE: + argptr = va_arg(param, char *); + if(argptr && !strcasecompare(argptr, "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_KEEPCNT: + arg = va_arg(param, long); + if(arg < 0) + return CURLE_BAD_FUNCTION_ARGUMENT; + else if(arg > INT_MAX) + arg = INT_MAX; + data->set.tcp_keepcnt = (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)); +#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)); + 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]); + 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 filenames 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); + if(!arg) { + DEBUGF(infof(data, "bad CURLOPT_ALTSVC_CTRL input")); + return CURLE_BAD_FUNCTION_ARGUMENT; + } + 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; +#ifndef CURL_DISABLE_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 +#ifdef USE_ECH + case CURLOPT_ECH: { + size_t plen = 0; + + argptr = va_arg(param, char *); + if(!argptr) { + data->set.tls_ech = CURLECH_DISABLE; + return CURLE_OK; + } + plen = strlen(argptr); + if(plen > CURL_MAX_INPUT_LENGTH) { + data->set.tls_ech = CURLECH_DISABLE; + return CURLE_BAD_FUNCTION_ARGUMENT; + } + /* set tls_ech flag value, preserving CLA_CFG bit */ + if(plen == 5 && !strcmp(argptr, "false")) + data->set.tls_ech = CURLECH_DISABLE | + (data->set.tls_ech & CURLECH_CLA_CFG); + else if(plen == 6 && !strcmp(argptr, "grease")) + data->set.tls_ech = CURLECH_GREASE | + (data->set.tls_ech & CURLECH_CLA_CFG); + else if(plen == 4 && !strcmp(argptr, "true")) + data->set.tls_ech = CURLECH_ENABLE | + (data->set.tls_ech & CURLECH_CLA_CFG); + else if(plen == 4 && !strcmp(argptr, "hard")) + data->set.tls_ech = CURLECH_HARD | + (data->set.tls_ech & CURLECH_CLA_CFG); + else if(plen > 5 && !strncmp(argptr, "ecl:", 4)) { + result = Curl_setstropt(&data->set.str[STRING_ECH_CONFIG], argptr + 4); + if(result) + return result; + data->set.tls_ech |= CURLECH_CLA_CFG; + } + else if(plen > 4 && !strncmp(argptr, "pn:", 3)) { + result = Curl_setstropt(&data->set.str[STRING_ECH_PUBLIC], argptr + 3); + if(result) + return result; + } + break; + } +#endif + case CURLOPT_QUICK_EXIT: + data->set.quick_exit = (0 != va_arg(param, long)) ? 1L : 0L; + break; + 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); +#ifdef DEBUGBUILD + if(result == CURLE_BAD_FUNCTION_ARGUMENT) + infof(data, "setopt arg 0x%x returned CURLE_BAD_FUNCTION_ARGUMENT", tag); +#endif + return result; +} diff --git a/contrib/curl/lib/setopt.h b/contrib/curl/lib/setopt.h new file mode 100644 index 0000000000..b0237467bd --- /dev/null +++ b/contrib/curl/lib/setopt.h @@ -0,0 +1,33 @@ +#ifndef HEADER_CURL_SETOPT_H +#define HEADER_CURL_SETOPT_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +CURLcode Curl_setstropt(char **charp, const char *s) WARN_UNUSED_RESULT; +CURLcode Curl_setblobopt(struct curl_blob **blobp, + const struct curl_blob *blob) WARN_UNUSED_RESULT; +CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option, va_list arg) + WARN_UNUSED_RESULT; + +#endif /* HEADER_CURL_SETOPT_H */ diff --git a/contrib/curl/lib/setup-os400.h b/contrib/curl/lib/setup-os400.h index a3c2a7bdc9..ef7baca67e 100644 --- a/contrib/curl/lib/setup-os400.h +++ b/contrib/curl/lib/setup-os400.h @@ -7,11 +7,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2016, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -20,6 +20,8 @@ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY * KIND, either express or implied. * + * SPDX-License-Identifier: curl + * ***************************************************************************/ @@ -32,6 +34,18 @@ /* 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 + + +/* Workaround bug in IBM QADRT runtime library: + * function puts() does not output the implicit trailing newline. + */ + +#include /* Be sure it is loaded. */ +#undef puts +#define puts(s) (fputs((s), stdout) == EOF? EOF: putchar('\n')) + /* System API wrapper prototypes & definitions to support ASCII parameters. */ @@ -41,108 +55,22 @@ typedef unsigned long u_int32_t; #include #include +#ifdef BUILDING_LIBCURL + 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, - curl_socklen_t salen, - char *nodename, curl_socklen_t nodenamelen, - char *servname, curl_socklen_t servnamelen, + socklen_t salen, + char *nodename, socklen_t nodenamelen, + char *servname, socklen_t servnamelen, int flags); #define getnameinfo Curl_getnameinfo_a - -/* GSKit wrappers. */ - -extern int Curl_gsk_environment_open(gsk_handle * my_env_handle); -#define gsk_environment_open Curl_gsk_environment_open - -extern int Curl_gsk_secure_soc_open(gsk_handle my_env_handle, - gsk_handle * my_session_handle); -#define gsk_secure_soc_open Curl_gsk_secure_soc_open - -extern int Curl_gsk_environment_close(gsk_handle * my_env_handle); -#define gsk_environment_close Curl_gsk_environment_close - -extern int Curl_gsk_secure_soc_close(gsk_handle * my_session_handle); -#define gsk_secure_soc_close Curl_gsk_secure_soc_close - -extern int Curl_gsk_environment_init(gsk_handle my_env_handle); -#define gsk_environment_init Curl_gsk_environment_init - -extern int Curl_gsk_secure_soc_init(gsk_handle my_session_handle); -#define gsk_secure_soc_init Curl_gsk_secure_soc_init - -extern int Curl_gsk_attribute_set_buffer_a(gsk_handle my_gsk_handle, - GSK_BUF_ID bufID, - const char *buffer, - int bufSize); -#define gsk_attribute_set_buffer Curl_gsk_attribute_set_buffer_a - -extern int Curl_gsk_attribute_set_enum(gsk_handle my_gsk_handle, - GSK_ENUM_ID enumID, - GSK_ENUM_VALUE enumValue); -#define gsk_attribute_set_enum Curl_gsk_attribute_set_enum - -extern int Curl_gsk_attribute_set_numeric_value(gsk_handle my_gsk_handle, - GSK_NUM_ID numID, - int numValue); -#define gsk_attribute_set_numeric_value Curl_gsk_attribute_set_numeric_value - -extern int Curl_gsk_attribute_set_callback(gsk_handle my_gsk_handle, - GSK_CALLBACK_ID callBackID, - void *callBackAreaPtr); -#define gsk_attribute_set_callback Curl_gsk_attribute_set_callback - -extern int Curl_gsk_attribute_get_buffer_a(gsk_handle my_gsk_handle, - GSK_BUF_ID bufID, - const char **buffer, - int *bufSize); -#define gsk_attribute_get_buffer Curl_gsk_attribute_get_buffer_a - -extern int Curl_gsk_attribute_get_enum(gsk_handle my_gsk_handle, - GSK_ENUM_ID enumID, - GSK_ENUM_VALUE *enumValue); -#define gsk_attribute_get_enum Curl_gsk_attribute_get_enum - -extern int Curl_gsk_attribute_get_numeric_value(gsk_handle my_gsk_handle, - GSK_NUM_ID numID, - int *numValue); -#define gsk_attribute_get_numeric_value Curl_gsk_attribute_get_numeric_value - -extern int Curl_gsk_attribute_get_cert_info(gsk_handle my_gsk_handle, - GSK_CERT_ID certID, - const gsk_cert_data_elem **certDataElem, - int *certDataElementCount); -#define gsk_attribute_get_cert_info Curl_gsk_attribute_get_cert_info - -extern int Curl_gsk_secure_soc_misc(gsk_handle my_session_handle, - GSK_MISC_ID miscID); -#define gsk_secure_soc_misc Curl_gsk_secure_soc_misc - -extern int Curl_gsk_secure_soc_read(gsk_handle my_session_handle, - char *readBuffer, - int readBufSize, int *amtRead); -#define gsk_secure_soc_read Curl_gsk_secure_soc_read - -extern int Curl_gsk_secure_soc_write(gsk_handle my_session_handle, - char *writeBuffer, - int writeBufSize, int *amtWritten); -#define gsk_secure_soc_write Curl_gsk_secure_soc_write - -extern const char * Curl_gsk_strerror_a(int gsk_return_value); -#define gsk_strerror Curl_gsk_strerror_a - -extern int Curl_gsk_secure_soc_startInit(gsk_handle my_session_handle, - int IOCompletionPort, - Qso_OverlappedIO_t * communicationsArea); -#define gsk_secure_soc_startInit Curl_gsk_secure_soc_startInit - - /* GSSAPI wrappers. */ extern OM_uint32 Curl_gss_import_name_a(OM_uint32 * minor_status, @@ -200,17 +128,21 @@ extern OM_uint32 Curl_gss_delete_sec_context_a(OM_uint32 * minor_status, /* 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_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, - struct sockaddr * dstaddr, int addrlen); + 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 @@ -220,4 +152,6 @@ extern int Curl_os400_recvfrom(int sd, char *buffer, int buflen, int flags, #define inflateEnd Curl_os400_inflateEnd #endif +#endif /* BUILDING_LIBCURL */ + #endif /* HEADER_CURL_SETUP_OS400_H */ diff --git a/contrib/curl/lib/setup-vms.h b/contrib/curl/lib/setup-vms.h index 6c454aee68..33b74db358 100644 --- a/contrib/curl/lib/setup-vms.h +++ b/contrib/curl/lib/setup-vms.h @@ -7,11 +7,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2016, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -20,6 +20,8 @@ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY * KIND, either express or implied. * + * SPDX-License-Identifier: curl + * ***************************************************************************/ /* */ @@ -73,7 +75,7 @@ char *decc$getenv(const char *__name); # endif #endif - struct passwd * decc_getpwuid(uid_t uid); + struct passwd *decc_getpwuid(uid_t uid); #ifdef __DECC # if __INITIAL_POINTER_SIZE == 32 @@ -87,7 +89,7 @@ static char *vms_translate_path(const char *path) /* 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 == NULL) { + if(!test_str) { return (char *)path; } @@ -99,7 +101,7 @@ static char *vms_translate_path(const char *path) } } # else - /* VMS translate path is actually not needed on the current 64 bit */ + /* 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 @@ -119,7 +121,7 @@ static char *vms_getenv(const char *envvar) /* first use the DECC getenv() function */ result = decc$getenv(envvar); - if(result == NULL) { + if(!result) { return result; } @@ -138,11 +140,11 @@ static char *vms_getenv(const char *envvar) static struct passwd vms_passwd_cache; -static struct passwd * vms_getpwuid(uid_t uid) +static struct passwd *vms_getpwuid(uid_t uid) { - struct passwd * my_passwd; + struct passwd *my_passwd; -/* Hack needed to support 64 bit builds, decc_getpwnam is 32 bit only */ +/* Hack needed to support 64-bit builds, decc_getpwnam is 32-bit only */ #ifdef __DECC # if __INITIAL_POINTER_SIZE __char_ptr32 unix_path; @@ -154,7 +156,7 @@ static struct passwd * vms_getpwuid(uid_t uid) #endif my_passwd = decc_getpwuid(uid); - if(my_passwd == NULL) { + if(!my_passwd) { return my_passwd; } @@ -260,7 +262,6 @@ static struct passwd * vms_getpwuid(uid_t uid) #define PKCS12_parse PKCS12_PARSE #define RAND_add RAND_ADD #define RAND_bytes RAND_BYTES -#define RAND_egd RAND_EGD #define RAND_file_name RAND_FILE_NAME #define RAND_load_file RAND_LOAD_FILE #define RAND_status RAND_STATUS @@ -373,8 +374,8 @@ static struct passwd * vms_getpwuid(uid_t uid) #ifdef HAVE_NETDB_H #include #ifndef AI_NUMERICHOST -#ifdef ENABLE_IPV6 -#undef ENABLE_IPV6 +#ifdef USE_IPV6 +#undef USE_IPV6 #endif #endif #endif diff --git a/contrib/curl/lib/setup-win32.h b/contrib/curl/lib/setup-win32.h new file mode 100644 index 0000000000..a297bdcff4 --- /dev/null +++ b/contrib/curl/lib/setup-win32.h @@ -0,0 +1,138 @@ +#ifndef HEADER_CURL_SETUP_WIN32_H +#define HEADER_CURL_SETUP_WIN32_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#undef USE_WINSOCK +/* ---------------------------------------------------------------- */ +/* Watt-32 TCP/IP SPECIFIC */ +/* ---------------------------------------------------------------- */ +#ifdef USE_WATT32 +# include +# undef byte +# undef word +# define HAVE_SYS_IOCTL_H +# define HAVE_SYS_SOCKET_H +# define HAVE_NETINET_IN_H +# define HAVE_NETDB_H +# define HAVE_ARPA_INET_H +# define SOCKET int +/* ---------------------------------------------------------------- */ +/* BSD-style lwIP TCP/IP stack SPECIFIC */ +/* ---------------------------------------------------------------- */ +#elif defined(USE_LWIPSOCK) + /* Define to use BSD-style lwIP TCP/IP stack. */ + /* #define USE_LWIPSOCK 1 */ +# undef HAVE_GETHOSTNAME +# undef LWIP_POSIX_SOCKETS_IO_NAMES +# undef RECV_TYPE_ARG1 +# undef RECV_TYPE_ARG3 +# undef SEND_TYPE_ARG1 +# undef SEND_TYPE_ARG3 +# define HAVE_GETHOSTBYNAME_R +# define HAVE_GETHOSTBYNAME_R_6 +# define LWIP_POSIX_SOCKETS_IO_NAMES 0 +# define RECV_TYPE_ARG1 int +# define RECV_TYPE_ARG3 size_t +# define SEND_TYPE_ARG1 int +# define SEND_TYPE_ARG3 size_t +#elif defined(_WIN32) +# define USE_WINSOCK 2 +#endif + +/* + * 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. ) and the Winsock headers should + * never be included when __CYGWIN__ is defined. + */ + +#ifdef _WIN32 +# 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 +/* + * Do not 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 +# include +# include +# include +# include +# include +# ifdef UNICODE + typedef wchar_t *(*curl_wcsdup_callback)(const wchar_t *str); +# endif +#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/contrib/curl/lib/sha256.c b/contrib/curl/lib/sha256.c new file mode 100644 index 0000000000..dedeae42e9 --- /dev/null +++ b/contrib/curl/lib/sha256.c @@ -0,0 +1,545 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Florin Petriuc, + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#if !defined(CURL_DISABLE_AWS) || !defined(CURL_DISABLE_DIGEST_AUTH) \ + || defined(USE_LIBSSH2) + +#include "warnless.h" +#include "curl_sha256.h" +#include "curl_hmac.h" + +#ifdef USE_WOLFSSL +#include +#ifndef NO_SHA256 +#define USE_OPENSSL_SHA256 +#endif +#endif + +#if defined(USE_OPENSSL) + +#include + +#if (OPENSSL_VERSION_NUMBER >= 0x0090800fL) +#define USE_OPENSSL_SHA256 +#endif + +#endif /* USE_OPENSSL */ + +#ifdef USE_MBEDTLS +#include + +#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 +#elif defined(USE_WOLFSSL) +#include +#endif + +#elif defined(USE_GNUTLS) +#include +#elif defined(USE_MBEDTLS) +#include +#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 +#define AN_APPLE_OS +#elif defined(USE_WIN32_CRYPTO) +#include +#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 ossl_sha256_ctx { + EVP_MD_CTX *openssl_ctx; +}; +typedef struct ossl_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 == CURL_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 Sha256_Ch(x,y,z) (z ^ (x & (y ^ z))) +#define Sha256_Maj(x,y,z) (((x | y) & z) | (x & y)) +#define Sha256_S(x, n) RORc((x), (n)) +#define Sha256_R(x, n) (((x)&0xFFFFFFFFUL)>>(n)) +#define Sigma0(x) (Sha256_S(x, 2) ^ Sha256_S(x, 13) ^ Sha256_S(x, 22)) +#define Sigma1(x) (Sha256_S(x, 6) ^ Sha256_S(x, 11) ^ Sha256_S(x, 25)) +#define Gamma0(x) (Sha256_S(x, 7) ^ Sha256_S(x, 18) ^ Sha256_R(x, 3)) +#define Gamma1(x) (Sha256_S(x, 17) ^ Sha256_S(x, 19) ^ Sha256_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) + Sha256_Ch(e, f, g) + K[i] + W[i]; \ + unsigned long t1 = Sigma0(a) + Sha256_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 CURL_SHA256_BLOCK_SIZE 64 + if(md->curlen > sizeof(md->buf)) + return -1; + while(inlen > 0) { + if(md->curlen == 0 && inlen >= CURL_SHA256_BLOCK_SIZE) { + if(sha256_compress(md, (unsigned char *)in) < 0) + return -1; + md->length += CURL_SHA256_BLOCK_SIZE * 8; + in += CURL_SHA256_BLOCK_SIZE; + inlen -= CURL_SHA256_BLOCK_SIZE; + } + else { + n = CURLMIN(inlen, (CURL_SHA256_BLOCK_SIZE - md->curlen)); + memcpy(md->buf + md->curlen, in, n); + md->curlen += n; + in += n; + inlen -= n; + if(md->curlen == CURL_SHA256_BLOCK_SIZE) { + if(sha256_compress(md, md->buf) < 0) + return -1; + md->length += 8 * CURL_SHA256_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/contrib/curl/lib/share.c b/contrib/curl/lib/share.c index 5b3957fcfb..ed4a209387 100644 --- a/contrib/curl/lib/share.c +++ b/contrib/curl/lib/share.c @@ -5,11 +5,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2016, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -18,17 +18,24 @@ * 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 #include "urldata.h" +#include "connect.h" #include "share.h" +#include "psl.h" #include "vtls/vtls.h" -#include "curl_memory.h" +#include "hsts.h" +#include "url.h" -/* The last #include file should be: */ +/* The last 3 #include files should be in this order */ +#include "curl_printf.h" +#include "curl_memory.h" #include "memdebug.h" struct Curl_share * @@ -36,12 +43,9 @@ curl_share_init(void) { struct Curl_share *share = calloc(1, sizeof(struct Curl_share)); if(share) { - share->specifier |= (1<hostcache)) { - free(share); - return NULL; - } + share->magic = CURL_GOOD_SHARE; + share->specifier |= (1 << CURL_LOCK_DATA_SHARE); + Curl_init_dnscache(&share->hostcache, 23); } return share; @@ -58,8 +62,11 @@ curl_share_setopt(struct Curl_share *share, CURLSHoption option, ...) 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 + /* do not allow setting options while one or more handles are already using this share */ return CURLSHE_IN_USE; @@ -69,7 +76,7 @@ curl_share_setopt(struct Curl_share *share, CURLSHoption option, ...) case CURLSHOPT_SHARE: /* this is a type this share will share */ type = va_arg(param, int); - share->specifier |= (1<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)); + sizeof(struct Curl_ssl_session)); share->sessionage = 0; if(!share->sslsession) res = CURLSHE_NOMEM; @@ -101,18 +120,32 @@ curl_share_setopt(struct Curl_share *share, CURLSHoption option, ...) #endif break; - case CURL_LOCK_DATA_CONNECT: /* not supported (yet) */ + case CURL_LOCK_DATA_CONNECT: + /* It is safe to set this option several times on a share. */ + if(!share->cpool.idata) { + if(Curl_cpool_init(&share->cpool, Curl_on_disconnect, + NULL, share, 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 |= (unsigned int)(1 << type); break; case CURLSHOPT_UNSHARE: /* this is a type this share will no longer share */ type = va_arg(param, int); - share->specifier &= ~(1<specifier &= ~(unsigned int)(1 << type); switch(type) { case CURL_LOCK_DATA_DNS: break; @@ -128,6 +161,16 @@ curl_share_setopt(struct Curl_share *share, CURLSHoption option, ...) #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); @@ -173,7 +216,7 @@ curl_share_setopt(struct Curl_share *share, CURLSHoption option, ...) CURLSHcode curl_share_cleanup(struct Curl_share *share) { - if(share == NULL) + if(!GOOD_SHARE_HANDLE(share)) return CURLSHE_INVALID; if(share->lockfunc) @@ -186,12 +229,19 @@ curl_share_cleanup(struct Curl_share *share) return CURLSHE_IN_USE; } + if(share->specifier & (1 << CURL_LOCK_DATA_CONNECT)) { + Curl_cpool_destroy(&share->cpool); + } 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; @@ -201,8 +251,11 @@ curl_share_cleanup(struct Curl_share *share) } #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; @@ -215,14 +268,14 @@ Curl_share_lock(struct Curl_easy *data, curl_lock_data type, { struct Curl_share *share = data->share; - if(share == NULL) + if(!share) return CURLSHE_INVALID; - if(share->specifier & (1<specifier & (unsigned int)(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 */ + /* else if we do not share this, pretend successful lock */ return CURLSHE_OK; } @@ -232,10 +285,10 @@ Curl_share_unlock(struct Curl_easy *data, curl_lock_data type) { struct Curl_share *share = data->share; - if(share == NULL) + if(!share) return CURLSHE_INVALID; - if(share->specifier & (1<specifier & (unsigned int)(1 << type)) { if(share->unlockfunc) /* only call this if set! */ share->unlockfunc (data, type, share->clientdata); } diff --git a/contrib/curl/lib/share.h b/contrib/curl/lib/share.h index c039a16cb7..124f7049f1 100644 --- a/contrib/curl/lib/share.h +++ b/contrib/curl/lib/share.h @@ -7,11 +7,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2016, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -20,38 +20,48 @@ * 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 #include "cookie.h" +#include "psl.h" #include "urldata.h" +#include "conncache.h" -/* SalfordC says "A structure member may not be volatile". Hence: - */ -#ifdef __SALFORDC__ -#define CURL_VOLATILE -#else -#define CURL_VOLATILE volatile -#endif +#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 */ +#define CURL_SHARE_KEEP_CONNECT(s) \ + ((s) && ((s)->specifier & (1<< CURL_LOCK_DATA_CONNECT))) + +/* this struct is libcurl-private, do not export details */ struct Curl_share { + unsigned int magic; /* CURL_GOOD_SHARE */ unsigned int specifier; - CURL_VOLATILE unsigned int dirty; + volatile unsigned int dirty; curl_lock_function lockfunc; curl_unlock_function unlockfunc; void *clientdata; - - struct curl_hash hostcache; + struct cpool cpool; + struct Curl_hash hostcache; #if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_COOKIES) struct CookieInfo *cookies; #endif - - struct curl_ssl_session *sslsession; +#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, diff --git a/contrib/curl/lib/sigpipe.h b/contrib/curl/lib/sigpipe.h index 800f9d3b4d..c57580f434 100644 --- a/contrib/curl/lib/sigpipe.h +++ b/contrib/curl/lib/sigpipe.h @@ -7,11 +7,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2013, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -20,10 +20,13 @@ * 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_SIGNAL_H) && defined(HAVE_SIGACTION) && defined(USE_OPENSSL) +#if defined(HAVE_SIGACTION) && \ + (defined(USE_OPENSSL) || defined(USE_MBEDTLS) || defined(USE_WOLFSSL)) #include struct sigpipe_ignore { @@ -32,6 +35,13 @@ struct sigpipe_ignore { }; #define SIGPIPE_VARIABLE(x) struct sigpipe_ignore x +#define SIGPIPE_MEMBER(x) struct sigpipe_ignore x + +static void sigpipe_init(struct sigpipe_ignore *ig) +{ + memset(ig, 0, sizeof(*ig)); + ig->no_signal = TRUE; +} /* * sigpipe_ignore() makes sure we ignore SIGPIPE while running libcurl @@ -47,7 +57,6 @@ static void sigpipe_ignore(struct Curl_easy *data, if(!data->set.no_signal) { struct sigaction action; /* first, extract the existing situation */ - memset(&ig->old_pipe_act, 0, sizeof(struct sigaction)); sigaction(SIGPIPE, NULL, &ig->old_pipe_act); action = ig->old_pipe_act; /* ignore this signal */ @@ -68,11 +77,23 @@ static void sigpipe_restore(struct sigpipe_ignore *ig) sigaction(SIGPIPE, &ig->old_pipe_act, NULL); } +static void sigpipe_apply(struct Curl_easy *data, + struct sigpipe_ignore *ig) +{ + if(data->set.no_signal != ig->no_signal) { + sigpipe_restore(ig); + sigpipe_ignore(data, ig); + } +} + #else /* for systems without sigaction */ #define sigpipe_ignore(x,y) Curl_nop_stmt +#define sigpipe_apply(x,y) Curl_nop_stmt +#define sigpipe_init(x) Curl_nop_stmt #define sigpipe_restore(x) Curl_nop_stmt #define SIGPIPE_VARIABLE(x) +#define SIGPIPE_MEMBER(x) bool x #endif #endif /* HEADER_CURL_SIGPIPE_H */ diff --git a/contrib/curl/lib/slist.c b/contrib/curl/lib/slist.c index e5adc0e71a..366b247609 100644 --- a/contrib/curl/lib/slist.c +++ b/contrib/curl/lib/slist.c @@ -5,11 +5,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2016, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -18,6 +18,8 @@ * 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" @@ -142,4 +144,3 @@ void curl_slist_free_all(struct curl_slist *list) item = next; } while(next); } - diff --git a/contrib/curl/lib/slist.h b/contrib/curl/lib/slist.h index b3f498c35f..9561fd0226 100644 --- a/contrib/curl/lib/slist.h +++ b/contrib/curl/lib/slist.h @@ -7,11 +7,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2013, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -20,6 +20,8 @@ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY * KIND, either express or implied. * + * SPDX-License-Identifier: curl + * ***************************************************************************/ /* @@ -37,4 +39,3 @@ struct curl_slist *Curl_slist_append_nodup(struct curl_slist *list, char *data); #endif /* HEADER_CURL_SLIST_H */ - diff --git a/contrib/curl/lib/smb.c b/contrib/curl/lib/smb.c index acf31bc563..f4fff9e615 100644 --- a/contrib/curl/lib/smb.c +++ b/contrib/curl/lib/smb.c @@ -5,12 +5,12 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 2014, Bill Nagel , Exacq Technologies - * Copyright (C) 2016-2017, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. + * Copyright (C) Bill Nagel , 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.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -19,30 +19,23 @@ * 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_NTLM) && \ - (CURL_SIZEOF_CURL_OFF_T > 4) - -#if !defined(USE_WINDOWS_SSPI) || defined(USE_WIN32_CRYPTO) - -#define BUILDING_CURL_SMB_C +#if !defined(CURL_DISABLE_SMB) && defined(USE_CURL_NTLM_CORE) -#ifdef HAVE_PROCESS_H -#include -#ifdef CURL_WINDOWS_APP +#ifdef _WIN32 #define getpid GetCurrentProcessId -#else -#define getpid _getpid -#endif #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" @@ -55,26 +48,221 @@ #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 connectdata *conn); -static CURLcode smb_connect(struct connectdata *conn, bool *done); -static CURLcode smb_connection_state(struct connectdata *conn, bool *done); -static CURLcode smb_request_state(struct connectdata *conn, bool *done); -static CURLcode smb_done(struct connectdata *conn, CURLcode status, - bool premature); -static CURLcode smb_disconnect(struct connectdata *conn, bool dead); -static int smb_getsock(struct connectdata *conn, curl_socket_t *socks, - int numsocks); -static CURLcode smb_parse_url_path(struct connectdata *conn); +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", /* scheme */ smb_setup_connection, /* setup_connection */ - ZERO_NULL, /* do_it */ - smb_done, /* done */ + smb_do, /* do_it */ + ZERO_NULL, /* done */ ZERO_NULL, /* do_more */ smb_connect, /* connect_it */ smb_connection_state, /* connecting */ @@ -84,9 +272,13 @@ const struct Curl_handler Curl_handler_smb = { ZERO_NULL, /* domore_getsock */ ZERO_NULL, /* perform_getsock */ smb_disconnect, /* disconnect */ - ZERO_NULL, /* readwrite */ + ZERO_NULL, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ + ZERO_NULL, /* connection_check */ + ZERO_NULL, /* attach connection */ PORT_SMB, /* defport */ CURLPROTO_SMB, /* protocol */ + CURLPROTO_SMB, /* family */ PROTOPT_NONE /* flags */ }; @@ -95,10 +287,10 @@ const struct Curl_handler Curl_handler_smb = { * SMBS handler interface */ const struct Curl_handler Curl_handler_smbs = { - "SMBS", /* scheme */ + "smbs", /* scheme */ smb_setup_connection, /* setup_connection */ - ZERO_NULL, /* do_it */ - smb_done, /* done */ + smb_do, /* do_it */ + ZERO_NULL, /* done */ ZERO_NULL, /* do_more */ smb_connect, /* connect_it */ smb_connection_state, /* connecting */ @@ -108,9 +300,13 @@ const struct Curl_handler Curl_handler_smbs = { ZERO_NULL, /* domore_getsock */ ZERO_NULL, /* perform_getsock */ smb_disconnect, /* disconnect */ - ZERO_NULL, /* readwrite */ + ZERO_NULL, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ + ZERO_NULL, /* connection_check */ + ZERO_NULL, /* attach connection */ PORT_SMBS, /* defport */ CURLPROTO_SMBS, /* protocol */ + CURLPROTO_SMB, /* family */ PROTOPT_SSL /* flags */ }; #endif @@ -122,13 +318,17 @@ const struct Curl_handler Curl_handler_smbs = { /* Append a string to an SMB message */ #define MSGCAT(str) \ - strcpy(p, (str)); \ - p += strlen(str); + do { \ + strcpy(p, (str)); \ + p += strlen(str); \ + } while(0) /* Append a null-terminated string to an SMB message */ #define MSGCATNULL(str) \ - strcpy(p, (str)); \ - p += strlen(str) + 1; + do { \ + strcpy(p, (str)); \ + p += strlen(str) + 1; \ + } while(0) /* SMB is mostly little endian */ #if (defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__) || \ @@ -144,19 +344,12 @@ static unsigned int smb_swap32(unsigned int x) ((x >> 24) & 0xff); } -#ifdef HAVE_LONGLONG -static unsigned long long smb_swap64(unsigned long long x) +static curl_off_t smb_swap64(curl_off_t x) { - return ((unsigned long long) smb_swap32((unsigned int) x) << 32) | + return ((curl_off_t) smb_swap32((unsigned int) x) << 32) | smb_swap32((unsigned int) (x >> 32)); } -#else -static unsigned __int64 smb_swap64(unsigned __int64 x) -{ - return ((unsigned __int64) smb_swap32((unsigned int) x) << 32) | - smb_swap32((unsigned int) (x >> 32)); -} -#endif + #else # define smb_swap16(x) (x) # define smb_swap32(x) (x) @@ -178,16 +371,15 @@ enum smb_req_state { /* SMB request data */ struct smb_request { enum smb_req_state state; - char *share; 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 connectdata *conn, enum smb_conn_state newstate) +static void conn_state(struct Curl_easy *data, enum smb_conn_state newstate) { - struct smb_conn *smb = &conn->proto.smbc; + struct smb_conn *smbc = &data->conn->proto.smbc; #if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS) /* For debug purposes */ static const char * const names[] = { @@ -199,18 +391,18 @@ static void conn_state(struct connectdata *conn, enum smb_conn_state newstate) /* LAST */ }; - if(smb->state != newstate) - infof(conn->data, "SMB conn %p state change from %s to %s\n", - (void *)smb, names[smb->state], names[newstate]); + if(smbc->state != newstate) + infof(data, "SMB conn %p state change from %s to %s", + (void *)smbc, names[smbc->state], names[newstate]); #endif - smb->state = newstate; + smbc->state = newstate; } -static void request_state(struct connectdata *conn, +static void request_state(struct Curl_easy *data, enum smb_req_state newstate) { - struct smb_request *req = conn->data->req.protop; + struct smb_request *req = data->req.p.smb; #if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS) /* For debug purposes */ static const char * const names[] = { @@ -226,43 +418,49 @@ static void request_state(struct connectdata *conn, }; if(req->state != newstate) - infof(conn->data, "SMB request %p state change from %s to %s\n", + infof(data, "SMB request %p state change from %s to %s", (void *)req, names[req->state], names[newstate]); #endif req->state = newstate; } -static CURLcode smb_setup_connection(struct connectdata *conn) +/* 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 */ - conn->data->req.protop = req = calloc(1, sizeof(struct smb_request)); + 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(conn); + return smb_parse_url_path(data, conn); } -static CURLcode smb_connect(struct connectdata *conn, bool *done) +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(!conn->bits.user_passwd) + if(!data->state.aptr.user) return CURLE_LOGIN_DENIED; /* Initialize the connection state */ - memset(smbc, 0, sizeof(*smbc)); smbc->state = SMB_CONNECTING; smbc->recv_buf = malloc(MAX_MESSAGE_SIZE); if(!smbc->recv_buf) return CURLE_OUT_OF_MEMORY; + smbc->send_buf = malloc(MAX_MESSAGE_SIZE); + if(!smbc->send_buf) + return CURLE_OUT_OF_MEMORY; /* Multiple requests are allowed with this connection */ connkeep(conn, "SMB default"); @@ -289,8 +487,9 @@ static CURLcode smb_connect(struct connectdata *conn, bool *done) return CURLE_OK; } -static CURLcode smb_recv_message(struct connectdata *conn, void **msg) +static CURLcode smb_recv_message(struct Curl_easy *data, void **msg) { + struct connectdata *conn = data->conn; struct smb_conn *smbc = &conn->proto.smbc; char *buf = smbc->recv_buf; ssize_t bytes_read; @@ -299,7 +498,7 @@ static CURLcode smb_recv_message(struct connectdata *conn, void **msg) size_t len = MAX_MESSAGE_SIZE - smbc->got; CURLcode result; - result = Curl_read(conn, FIRSTSOCKET, buf + smbc->got, len, &bytes_read); + result = Curl_xfer_recv(data, buf + smbc->got, len, &bytes_read); if(result) return result; @@ -343,11 +542,12 @@ static void smb_pop_message(struct connectdata *conn) smbc->got = 0; } -static void smb_format_message(struct connectdata *conn, struct smb_header *h, +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 = conn->data->req.protop; + struct smb_request *req = data->req.p.smb; unsigned int pid; memset(h, 0, sizeof(*h)); @@ -359,20 +559,20 @@ static void smb_format_message(struct connectdata *conn, struct smb_header *h, 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(); + pid = (unsigned int)getpid(); h->pid_high = smb_swap16((unsigned short)(pid >> 16)); h->pid = smb_swap16((unsigned short) pid); } -static CURLcode smb_send(struct connectdata *conn, ssize_t len, +static CURLcode smb_send(struct Curl_easy *data, size_t len, size_t upload_size) { + struct connectdata *conn = data->conn; struct smb_conn *smbc = &conn->proto.smbc; - ssize_t bytes_written; + size_t bytes_written; CURLcode result; - result = Curl_write(conn, FIRSTSOCKET, conn->data->state.uploadbuffer, - len, &bytes_written); + result = Curl_xfer_send(data, smbc->send_buf, len, FALSE, &bytes_written); if(result) return result; @@ -386,19 +586,19 @@ static CURLcode smb_send(struct connectdata *conn, ssize_t len, return CURLE_OK; } -static CURLcode smb_flush(struct connectdata *conn) +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; + size_t bytes_written; + size_t len = smbc->send_size - smbc->sent; CURLcode result; if(!smbc->send_size) return CURLE_OK; - result = Curl_write(conn, FIRSTSOCKET, - conn->data->state.uploadbuffer + smbc->sent, - len, &bytes_written); + result = Curl_xfer_send(data, smbc->send_buf + smbc->sent, len, FALSE, + &bytes_written); if(result) return result; @@ -410,26 +610,30 @@ static CURLcode smb_flush(struct connectdata *conn) return CURLE_OK; } -static CURLcode smb_send_message(struct connectdata *conn, unsigned char cmd, +static CURLcode smb_send_message(struct Curl_easy *data, unsigned char cmd, const void *msg, size_t msg_len) { - smb_format_message(conn, (struct smb_header *)conn->data->state.uploadbuffer, + struct connectdata *conn = data->conn; + struct smb_conn *smbc = &conn->proto.smbc; + + smb_format_message(data, (struct smb_header *)smbc->send_buf, cmd, msg_len); - memcpy(conn->data->state.uploadbuffer + sizeof(struct smb_header), - msg, msg_len); + DEBUGASSERT((sizeof(struct smb_header) + msg_len) <= MAX_MESSAGE_SIZE); + memcpy(smbc->send_buf + sizeof(struct smb_header), msg, msg_len); - return smb_send(conn, sizeof(struct smb_header) + msg_len, 0); + return smb_send(data, sizeof(struct smb_header) + msg_len, 0); } -static CURLcode smb_send_negotiate(struct connectdata *conn) +static CURLcode smb_send_negotiate(struct Curl_easy *data) { const char *msg = "\x00\x0c\x00\x02NT LM 0.12"; - return smb_send_message(conn, SMB_COM_NEGOTIATE, msg, 15); + return smb_send_message(data, SMB_COM_NEGOTIATE, msg, 15); } -static CURLcode smb_send_setup(struct connectdata *conn) +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; @@ -438,22 +642,18 @@ static CURLcode smb_send_setup(struct connectdata *conn) 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 */ + const size_t byte_count = sizeof(lm) + sizeof(nt) + + strlen(smbc->user) + strlen(smbc->domain) + + 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->data, conn->passwd, lm_hash); + Curl_ntlm_core_mk_lm_hash(conn->passwd, lm_hash); Curl_ntlm_core_lm_resp(lm_hash, smbc->challenge, lm); -#if USE_NTRESPONSES - Curl_ntlm_core_mk_nt_hash(conn->data, conn->passwd, nt_hash); + Curl_ntlm_core_mk_nt_hash(conn->passwd, nt_hash); Curl_ntlm_core_lm_resp(nt_hash, smbc->challenge, nt); -#else - memset(nt, 0, sizeof(nt)); -#endif - memset(&msg, 0, sizeof(msg)); + memset(&msg, 0, sizeof(msg) - sizeof(msg.bytes)); msg.word_count = SMB_WC_SETUP_ANDX; msg.andx.command = SMB_COM_NO_ANDX_COMMAND; msg.max_buffer_size = smb_swap16(MAX_MESSAGE_SIZE); @@ -471,56 +671,56 @@ static CURLcode smb_send_setup(struct connectdata *conn) MSGCATNULL(smbc->domain); MSGCATNULL(OS); MSGCATNULL(CLIENTNAME); - byte_count = p - msg.bytes; + DEBUGASSERT(byte_count == (size_t)(p - msg.bytes)); msg.byte_count = smb_swap16((unsigned short)byte_count); - return smb_send_message(conn, SMB_COM_SETUP_ANDX, &msg, + return smb_send_message(data, SMB_COM_SETUP_ANDX, &msg, sizeof(msg) - sizeof(msg.bytes) + byte_count); } -static CURLcode smb_send_tree_connect(struct connectdata *conn) +static CURLcode smb_send_tree_connect(struct Curl_easy *data) { - struct smb_request *req = conn->data->req.protop; 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(req->share); - byte_count += strlen(SERVICENAME) + 5; /* 2 nulls and 3 backslashes */ + const size_t byte_count = strlen(conn->host.name) + strlen(smbc->share) + + strlen(SERVICENAME) + 5; /* 2 nulls and 3 backslashes */ if(byte_count > sizeof(msg.bytes)) return CURLE_FILESIZE_EXCEEDED; - memset(&msg, 0, sizeof(msg)); + memset(&msg, 0, sizeof(msg) - sizeof(msg.bytes)); 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(req->share); + MSGCATNULL(smbc->share); MSGCATNULL(SERVICENAME); /* Match any type of service */ - byte_count = p - msg.bytes; + DEBUGASSERT(byte_count == (size_t)(p - msg.bytes)); msg.byte_count = smb_swap16((unsigned short)byte_count); - return smb_send_message(conn, SMB_COM_TREE_CONNECT_ANDX, &msg, + return smb_send_message(data, SMB_COM_TREE_CONNECT_ANDX, &msg, sizeof(msg) - sizeof(msg.bytes) + byte_count); } -static CURLcode smb_send_open(struct connectdata *conn) +static CURLcode smb_send_open(struct Curl_easy *data) { - struct smb_request *req = conn->data->req.protop; + struct smb_request *req = data->req.p.smb; struct smb_nt_create msg; - size_t byte_count; + const size_t byte_count = strlen(req->path) + 1; - if((strlen(req->path) + 1) > sizeof(msg.bytes)) + if(byte_count > sizeof(msg.bytes)) return CURLE_FILESIZE_EXCEEDED; - memset(&msg, 0, sizeof(msg)); + memset(&msg, 0, sizeof(msg) - sizeof(msg.bytes)); 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.name_length = smb_swap16((unsigned short)(byte_count - 1)); msg.share_access = smb_swap32(SMB_FILE_SHARE_ALL); - if(conn->data->set.upload) { + if(data->state.upload) { msg.access = smb_swap32(SMB_GENERIC_READ | SMB_GENERIC_WRITE); msg.create_disposition = smb_swap32(SMB_FILE_OVERWRITE_IF); } @@ -528,38 +728,38 @@ static CURLcode smb_send_open(struct connectdata *conn) msg.access = smb_swap32(SMB_GENERIC_READ); msg.create_disposition = smb_swap32(SMB_FILE_OPEN); } - msg.byte_count = smb_swap16((unsigned short) ++byte_count); + msg.byte_count = smb_swap16((unsigned short) byte_count); strcpy(msg.bytes, req->path); - return smb_send_message(conn, SMB_COM_NT_CREATE_ANDX, &msg, + return smb_send_message(data, SMB_COM_NT_CREATE_ANDX, &msg, sizeof(msg) - sizeof(msg.bytes) + byte_count); } -static CURLcode smb_send_close(struct connectdata *conn) +static CURLcode smb_send_close(struct Curl_easy *data) { - struct smb_request *req = conn->data->req.protop; + 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(conn, SMB_COM_CLOSE, &msg, sizeof(msg)); + return smb_send_message(data, SMB_COM_CLOSE, &msg, sizeof(msg)); } -static CURLcode smb_send_tree_disconnect(struct connectdata *conn) +static CURLcode smb_send_tree_disconnect(struct Curl_easy *data) { struct smb_tree_disconnect msg; memset(&msg, 0, sizeof(msg)); - return smb_send_message(conn, SMB_COM_TREE_DISCONNECT, &msg, sizeof(msg)); + return smb_send_message(data, SMB_COM_TREE_DISCONNECT, &msg, sizeof(msg)); } -static CURLcode smb_send_read(struct connectdata *conn) +static CURLcode smb_send_read(struct Curl_easy *data) { - struct smb_request *req = conn->data->req.protop; - curl_off_t offset = conn->data->req.offset; + struct smb_request *req = data->req.p.smb; + curl_off_t offset = data->req.offset; struct smb_read msg; memset(&msg, 0, sizeof(msg)); @@ -571,16 +771,19 @@ static CURLcode smb_send_read(struct connectdata *conn) msg.min_bytes = smb_swap16(MAX_PAYLOAD_SIZE); msg.max_bytes = smb_swap16(MAX_PAYLOAD_SIZE); - return smb_send_message(conn, SMB_COM_READ_ANDX, &msg, sizeof(msg)); + return smb_send_message(data, SMB_COM_READ_ANDX, &msg, sizeof(msg)); } -static CURLcode smb_send_write(struct connectdata *conn) +static CURLcode smb_send_write(struct Curl_easy *data) { - struct smb_write *msg = (struct smb_write *)conn->data->state.uploadbuffer; - struct smb_request *req = conn->data->req.protop; - curl_off_t offset = conn->data->req.offset; + struct connectdata *conn = data->conn; + struct smb_conn *smbc = &conn->proto.smbc; + 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; - curl_off_t upload_size = conn->data->req.size - conn->data->req.bytecount; + msg = (struct smb_write *)smbc->send_buf; if(upload_size >= MAX_PAYLOAD_SIZE - 1) /* There is one byte of padding */ upload_size = MAX_PAYLOAD_SIZE - 1; @@ -594,23 +797,26 @@ static CURLcode smb_send_write(struct connectdata *conn) msg->data_offset = smb_swap16(sizeof(*msg) - sizeof(unsigned int)); msg->byte_count = smb_swap16((unsigned short) (upload_size + 1)); - smb_format_message(conn, &msg->h, SMB_COM_WRITE_ANDX, + smb_format_message(data, &msg->h, SMB_COM_WRITE_ANDX, sizeof(*msg) - sizeof(msg->h) + (size_t) upload_size); - return smb_send(conn, sizeof(*msg), (size_t) upload_size); + return smb_send(data, sizeof(*msg), (size_t) upload_size); } -static CURLcode smb_send_and_recv(struct connectdata *conn, void **msg) +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) { - int nread = smbc->upload_size > BUFSIZE ? BUFSIZE : - (int) smbc->upload_size; - conn->data->req.upload_fromhere = conn->data->state.uploadbuffer; - result = Curl_fillreadbuffer(conn, nread, &nread); + size_t nread = smbc->upload_size > (size_t)MAX_MESSAGE_SIZE ? + (size_t)MAX_MESSAGE_SIZE : smbc->upload_size; + bool eos; + + result = Curl_client_read(data, smbc->send_buf, nread, &nread, &eos); if(result && result != CURLE_AGAIN) return result; if(!nread) @@ -623,7 +829,7 @@ static CURLcode smb_send_and_recv(struct connectdata *conn, void **msg) /* Check if there is data to send */ if(smbc->send_size) { - result = smb_flush(conn); + result = smb_flush(data); if(result) return result; } @@ -632,11 +838,12 @@ static CURLcode smb_send_and_recv(struct connectdata *conn, void **msg) if(smbc->send_size || smbc->upload_size) return CURLE_AGAIN; - return smb_recv_message(conn, msg); + return smb_recv_message(data, msg); } -static CURLcode smb_connection_state(struct connectdata *conn, bool *done) +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; @@ -646,8 +853,8 @@ static CURLcode smb_connection_state(struct connectdata *conn, bool *done) if(smbc->state == SMB_CONNECTING) { #ifdef USE_SSL if((conn->handler->flags & PROTOPT_SSL)) { - bool ssl_done; - result = Curl_ssl_connect_nonblocking(conn, FIRSTSOCKET, &ssl_done); + bool ssl_done = FALSE; + result = Curl_conn_connect(data, FIRSTSOCKET, FALSE, &ssl_done); if(result && result != CURLE_AGAIN) return result; if(!ssl_done) @@ -655,17 +862,17 @@ static CURLcode smb_connection_state(struct connectdata *conn, bool *done) } #endif - result = smb_send_negotiate(conn); + result = smb_send_negotiate(data); if(result) { connclose(conn, "SMB: failed to send negotiate message"); return result; } - conn_state(conn, SMB_NEGOTIATE); + conn_state(data, SMB_NEGOTIATE); } /* Send the previous message and check for a response */ - result = smb_send_and_recv(conn, &msg); + result = smb_send_and_recv(data, &msg); if(result && result != CURLE_AGAIN) { connclose(conn, "SMB: failed to communicate"); return result; @@ -678,19 +885,20 @@ static CURLcode smb_connection_state(struct connectdata *conn, bool *done) switch(smbc->state) { case SMB_NEGOTIATE: - if(h->status || smbc->got < sizeof(*nrsp) + sizeof(smbc->challenge) - 1) { + 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(conn); + result = smb_send_setup(data); if(result) { connclose(conn, "SMB: failed to send setup message"); return result; } - conn_state(conn, SMB_SETUP); + conn_state(data, SMB_SETUP); break; case SMB_SETUP: @@ -699,7 +907,7 @@ static CURLcode smb_connection_state(struct connectdata *conn, bool *done) return CURLE_LOGIN_DENIED; } smbc->uid = smb_swap16(h->uid); - conn_state(conn, SMB_CONNECTED); + conn_state(data, SMB_CONNECTED); *done = true; break; @@ -713,9 +921,28 @@ static CURLcode smb_connection_state(struct connectdata *conn, bool *done) return CURLE_OK; } -static CURLcode smb_request_state(struct connectdata *conn, bool *done) +/* + * 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 smb_request *req = conn->data->req.protop; + 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; @@ -723,20 +950,26 @@ static CURLcode smb_request_state(struct connectdata *conn, bool *done) 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(conn); + result = smb_send_tree_connect(data); if(result) { connclose(conn, "SMB: failed to send tree connect message"); return result; } - request_state(conn, SMB_TREE_CONNECT); + request_state(data, SMB_TREE_CONNECT); } /* Send the previous message and check for a response */ - result = smb_send_and_recv(conn, &msg); + result = smb_send_and_recv(data, &msg); if(result && result != CURLE_AGAIN) { connclose(conn, "SMB: failed to communicate"); return result; @@ -762,21 +995,31 @@ static CURLcode smb_request_state(struct connectdata *conn, bool *done) 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; } - req->fid = smb_swap16(((struct smb_nt_create_response *)msg)->fid); - conn->data->req.offset = 0; - if(conn->data->set.upload) { - conn->data->req.size = conn->data->state.infilesize; - Curl_pgrsSetUploadSize(conn->data, conn->data->req.size); + 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 { - conn->data->req.size = - smb_swap64(((struct smb_nt_create_response *)msg)->end_of_file); - Curl_pgrsSetDownloadSize(conn->data, conn->data->req.size); - next_state = SMB_DOWNLOAD; + 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; @@ -792,11 +1035,11 @@ static CURLcode smb_request_state(struct connectdata *conn, bool *done) sizeof(struct smb_header) + 13); if(len > 0) { if(off + sizeof(unsigned int) + len > smbc->got) { - failf(conn->data, "Invalid input packet"); + failf(data, "Invalid input packet"); result = CURLE_RECV_ERROR; } else - result = Curl_client_write(conn, CLIENTWRITE_BODY, + result = Curl_client_write(data, CLIENTWRITE_BODY, (char *)msg + off + sizeof(unsigned int), len); if(result) { @@ -805,9 +1048,7 @@ static CURLcode smb_request_state(struct connectdata *conn, bool *done) break; } } - conn->data->req.bytecount += len; - conn->data->req.offset += len; - Curl_pgrsSetDownloadCounter(conn->data, conn->data->req.bytecount); + data->req.offset += len; next_state = (len < MAX_PAYLOAD_SIZE) ? SMB_CLOSE : SMB_DOWNLOAD; break; @@ -819,17 +1060,17 @@ static CURLcode smb_request_state(struct connectdata *conn, bool *done) } len = Curl_read16_le(((const unsigned char *) msg) + sizeof(struct smb_header) + 5); - conn->data->req.bytecount += len; - conn->data->req.offset += len; - Curl_pgrsSetUploadCounter(conn->data, conn->data->req.bytecount); - if(conn->data->req.bytecount >= conn->data->req.size) + 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 */ + /* We do not care if the close failed, proceed to tree disconnect anyway */ next_state = SMB_TREE_DISCONNECT; break; @@ -846,23 +1087,23 @@ static CURLcode smb_request_state(struct connectdata *conn, bool *done) switch(next_state) { case SMB_OPEN: - result = smb_send_open(conn); + result = smb_send_open(data); break; case SMB_DOWNLOAD: - result = smb_send_read(conn); + result = smb_send_read(data); break; case SMB_UPLOAD: - result = smb_send_write(conn); + result = smb_send_write(data); break; case SMB_CLOSE: - result = smb_send_close(conn); + result = smb_send_close(data); break; case SMB_TREE_DISCONNECT: - result = smb_send_tree_disconnect(conn); + result = smb_send_tree_disconnect(data); break; case SMB_DONE: @@ -879,87 +1120,72 @@ static CURLcode smb_request_state(struct connectdata *conn, bool *done) return result; } - request_state(conn, next_state); + request_state(data, next_state); return CURLE_OK; } -static CURLcode smb_done(struct connectdata *conn, CURLcode status, - bool premature) -{ - struct smb_request *req = conn->data->req.protop; - - (void) premature; - - Curl_safefree(req->share); - Curl_safefree(conn->data->req.protop); - - return status; -} - -static CURLcode smb_disconnect(struct connectdata *conn, bool dead) +static CURLcode smb_disconnect(struct Curl_easy *data, + struct connectdata *conn, bool dead) { struct smb_conn *smbc = &conn->proto.smbc; - struct smb_request *req = conn->data->req.protop; - (void) dead; - + (void) data; + Curl_safefree(smbc->share); Curl_safefree(smbc->domain); Curl_safefree(smbc->recv_buf); - - /* smb_done is not always called, so cleanup the request */ - if(req) { - Curl_safefree(req->share); - } - + Curl_safefree(smbc->send_buf); return CURLE_OK; } -static int smb_getsock(struct connectdata *conn, curl_socket_t *socks, - int numsocks) +static int smb_getsock(struct Curl_easy *data, + struct connectdata *conn, curl_socket_t *socks) { - struct smb_conn *smbc = &conn->proto.smbc; - - if(!numsocks) - return GETSOCK_BLANK; - + (void)data; socks[0] = conn->sock[FIRSTSOCKET]; + return GETSOCK_READSOCK(0) | GETSOCK_WRITESOCK(0); +} - if(smbc->send_size || smbc->upload_size) - return 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; - return GETSOCK_READSOCK(0); + *done = FALSE; + if(smbc->share) { + return CURLE_OK; + } + return CURLE_URL_MALFORMAT; } -static CURLcode smb_parse_url_path(struct connectdata *conn) +static CURLcode smb_parse_url_path(struct Curl_easy *data, + struct connectdata *conn) { - CURLcode result = CURLE_OK; - struct Curl_easy *data = conn->data; - struct smb_request *req = data->req.protop; + struct smb_request *req = data->req.p.smb; + struct smb_conn *smbc = &conn->proto.smbc; char *path; char *slash; /* URL decode the path */ - result = Curl_urldecode(data, data->state.path, 0, &path, NULL, TRUE); + CURLcode result = Curl_urldecode(data->state.up.path, 0, &path, NULL, + REJECT_CTRL); if(result) return result; /* Parse the path for the share */ - req->share = strdup((*path == '/' || *path == '\\') ? path + 1 : path); - if(!req->share) { - free(path); - + smbc->share = strdup((*path == '/' || *path == '\\') ? path + 1 : path); + free(path); + if(!smbc->share) return CURLE_OUT_OF_MEMORY; - } - slash = strchr(req->share, '/'); + slash = strchr(smbc->share, '/'); if(!slash) - slash = strchr(req->share, '\\'); + slash = strchr(smbc->share, '\\'); /* The share must be present */ if(!slash) { - free(path); - + Curl_safefree(smbc->share); + failf(data, "missing share in URL path for SMB"); return CURLE_URL_MALFORMAT; } @@ -967,16 +1193,13 @@ static CURLcode smb_parse_url_path(struct connectdata *conn) backslashes */ *slash++ = 0; req->path = slash; + for(; *slash; slash++) { if(*slash == '/') *slash = '\\'; } - - free(path); - return CURLE_OK; } -#endif /* !USE_WINDOWS_SSPI || USE_WIN32_CRYPTO */ - -#endif /* CURL_DISABLE_SMB && USE_NTLM && CURL_SIZEOF_CURL_OFF_T > 4 */ +#endif /* CURL_DISABLE_SMB && USE_CURL_NTLM_CORE && + SIZEOF_CURL_OFF_T > 4 */ diff --git a/contrib/curl/lib/smb.h b/contrib/curl/lib/smb.h index 1a4f66e5a8..9ea2a8cc31 100644 --- a/contrib/curl/lib/smb.h +++ b/contrib/curl/lib/smb.h @@ -7,11 +7,12 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 2014, Bill Nagel , Exacq Technologies + * Copyright (C) Bill Nagel , Exacq Technologies + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -20,6 +21,8 @@ * 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 { @@ -34,238 +37,25 @@ 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; + char *send_buf; size_t upload_size; size_t send_size; size_t sent; size_t got; }; -/* - * Definitions for SMB protocol data structures - */ -#ifdef BUILDING_CURL_SMB_C - -#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; -#ifdef HAVE_LONGLONG - unsigned long long allocation_size; -#else - unsigned __int64 allocation_size; -#endif - 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; -#ifdef HAVE_LONGLONG - unsigned long long create_time; - unsigned long long last_access_time; - unsigned long long last_write_time; - unsigned long long last_change_time; -#else - unsigned __int64 create_time; - unsigned __int64 last_access_time; - unsigned __int64 last_write_time; - unsigned __int64 last_change_time; -#endif - unsigned int ext_file_attributes; -#ifdef HAVE_LONGLONG - unsigned long long allocation_size; - unsigned long long end_of_file; -#else - unsigned __int64 allocation_size; - unsigned __int64 end_of_file; -#endif -} 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 - -#endif /* BUILDING_CURL_SMB_C */ - -#if !defined(CURL_DISABLE_SMB) && defined(USE_NTLM) && \ - (CURL_SIZEOF_CURL_OFF_T > 4) - -#if !defined(USE_WINDOWS_SSPI) || defined(USE_WIN32_CRYPTO) +#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 /* !USE_WINDOWS_SSPI || USE_WIN32_CRYPTO */ - -#endif /* CURL_DISABLE_SMB && USE_NTLM && CURL_SIZEOF_CURL_OFF_T > 4 */ +#endif /* CURL_DISABLE_SMB && USE_CURL_NTLM_CORE && + SIZEOF_CURL_OFF_T > 4 */ #endif /* HEADER_CURL_SMB_H */ diff --git a/contrib/curl/lib/smtp.c b/contrib/curl/lib/smtp.c index adc346a693..3c5893284b 100644 --- a/contrib/curl/lib/smtp.c +++ b/contrib/curl/lib/smtp.c @@ -5,11 +5,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2017, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -18,6 +18,8 @@ * 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 @@ -27,7 +29,11 @@ * 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 LOGIN SASL Mechanism * @@ -43,9 +49,6 @@ #ifdef HAVE_ARPA_INET_H #include #endif -#ifdef HAVE_UTSNAME_H -#include -#endif #ifdef HAVE_NETDB_H #include #endif @@ -54,11 +57,6 @@ #include #endif -#if (defined(NETWARE) && defined(__NOVELL_LIBC__)) -#undef in_addr_t -#define in_addr_t unsigned long -#endif - #include #include "urldata.h" #include "sendf.h" @@ -67,50 +65,60 @@ #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 "strerror.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 connectdata *conn, bool *done); -static CURLcode smtp_do(struct connectdata *conn, bool *done); -static CURLcode smtp_done(struct connectdata *conn, CURLcode status, +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 connectdata *conn, bool *done); -static CURLcode smtp_disconnect(struct connectdata *conn, bool dead); -static CURLcode smtp_multi_statemach(struct connectdata *conn, bool *done); -static int smtp_getsock(struct connectdata *conn, curl_socket_t *socks, - int numsocks); -static CURLcode smtp_doing(struct connectdata *conn, bool *dophase_done); -static CURLcode smtp_setup_connection(struct connectdata *conn); +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 connectdata *conn); -static CURLcode smtp_parse_custom_request(struct connectdata *conn); -static CURLcode smtp_perform_auth(struct connectdata *conn, const char *mech, - const char *initresp); -static CURLcode smtp_continue_auth(struct connectdata *conn, const char *resp); -static void smtp_get_message(char *buffer, char **outptr); +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); +static CURLcode cr_eob_add(struct Curl_easy *data); /* * SMTP protocol handler. */ const struct Curl_handler Curl_handler_smtp = { - "SMTP", /* scheme */ + "smtp", /* scheme */ smtp_setup_connection, /* setup_connection */ smtp_do, /* do_it */ smtp_done, /* done */ @@ -123,9 +131,13 @@ const struct Curl_handler Curl_handler_smtp = { ZERO_NULL, /* domore_getsock */ ZERO_NULL, /* perform_getsock */ smtp_disconnect, /* disconnect */ - ZERO_NULL, /* readwrite */ + ZERO_NULL, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ + ZERO_NULL, /* connection_check */ + ZERO_NULL, /* attach connection */ PORT_SMTP, /* defport */ CURLPROTO_SMTP, /* protocol */ + CURLPROTO_SMTP, /* family */ PROTOPT_CLOSEACTION | PROTOPT_NOURLQUERY | /* flags */ PROTOPT_URLOPTIONS }; @@ -136,7 +148,7 @@ const struct Curl_handler Curl_handler_smtp = { */ const struct Curl_handler Curl_handler_smtps = { - "SMTPS", /* scheme */ + "smtps", /* scheme */ smtp_setup_connection, /* setup_connection */ smtp_do, /* do_it */ smtp_done, /* done */ @@ -149,75 +161,30 @@ const struct Curl_handler Curl_handler_smtps = { ZERO_NULL, /* domore_getsock */ ZERO_NULL, /* perform_getsock */ smtp_disconnect, /* disconnect */ - ZERO_NULL, /* readwrite */ + ZERO_NULL, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ + 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 -#ifndef CURL_DISABLE_HTTP -/* - * HTTP-proxyed SMTP protocol handler. - */ - -static const struct Curl_handler Curl_handler_smtp_proxy = { - "SMTP", /* scheme */ - Curl_http_setup_conn, /* setup_connection */ - Curl_http, /* do_it */ - Curl_http_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 */ - PORT_SMTP, /* defport */ - CURLPROTO_HTTP, /* protocol */ - PROTOPT_NONE /* flags */ -}; - -#ifdef USE_SSL -/* - * HTTP-proxyed SMTPS protocol handler. - */ - -static const struct Curl_handler Curl_handler_smtps_proxy = { - "SMTPS", /* scheme */ - Curl_http_setup_conn, /* setup_connection */ - Curl_http, /* do_it */ - Curl_http_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 */ - PORT_SMTPS, /* defport */ - CURLPROTO_HTTP, /* protocol */ - PROTOPT_NONE /* flags */ -}; -#endif -#endif - /* SASL parameters for the smtp protocol */ static const struct SASLproto saslsmtp = { - "smtp", /* The service name */ - 334, /* Code received when continuation is expected */ - 235, /* Code to receive upon authentication success */ - 512 - 8, /* Maximum initial response length (no max) */ - smtp_perform_auth, /* Send authentication command */ - smtp_continue_auth, /* Send authentication continuation */ - smtp_get_message /* Get SASL response message */ + "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 @@ -227,7 +194,7 @@ static void smtp_to_smtps(struct connectdata *conn) conn->handler = &Curl_handler_smtps; /* Set the connection's upgraded to TLS flag */ - conn->tls_upgraded = TRUE; + conn->bits.tls_upgraded = TRUE; } #else #define smtp_to_smtps(x) Curl_nop_stmt @@ -241,11 +208,12 @@ static void smtp_to_smtps(struct connectdata *conn) * also detects various capabilities from the EHLO response including the * supported authentication mechanisms. */ -static bool smtp_endofresp(struct connectdata *conn, char *line, size_t len, - int *resp) +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])) @@ -253,11 +221,15 @@ static bool smtp_endofresp(struct connectdata *conn, char *line, size_t len, /* 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 e-mail servers ignore this and + 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; - *resp = curlx_sltosi(strtol(line, NULL, 10)); + 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) @@ -279,39 +251,44 @@ static bool smtp_endofresp(struct connectdata *conn, char *line, size_t len, * * Gets the authentication message from the response buffer. */ -static void smtp_get_message(char *buffer, char **outptr) +static CURLcode smtp_get_message(struct Curl_easy *data, struct bufref *out) { - size_t len = 0; - char *message = NULL; - - /* Find the start of the message */ - for(message = buffer + 4; *message == ' ' || *message == '\t'; message++) - ; + char *message = Curl_dyn_ptr(&data->conn->proto.smtpc.pp.recvbuf); + size_t len = data->conn->proto.smtpc.pp.nfinal; - /* Find the end of the message */ - for(len = strlen(message); len--;) - if(message[len] != '\r' && message[len] != '\n' && message[len] != ' ' && - message[len] != '\t') - break; - - /* Terminate the message */ - if(++len) { - message[len] = '\0'; + 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); - *outptr = message; + return CURLE_OK; } /*********************************************************************** * - * state() + * smtp_state() * * This is the ONLY way to change SMTP state! */ -static void state(struct connectdata *conn, smtpstate newstate) +static void smtp_state(struct Curl_easy *data, smtpstate newstate) { - struct smtp_conn *smtpc = &conn->proto.smtpc; -#if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS) + struct smtp_conn *smtpc = &data->conn->proto.smtpc; +#if !defined(CURL_DISABLE_VERBOSE_STRINGS) /* for debug purposes */ static const char * const names[] = { "STOP", @@ -331,8 +308,8 @@ static void state(struct connectdata *conn, smtpstate newstate) }; if(smtpc->state != newstate) - infof(conn->data, "SMTP %p state change from %s to %s\n", - (void *)smtpc, names[smtpc->state], names[newstate]); + CURL_TRC_SMTP(data, "state change from %s to %s", + names[smtpc->state], names[newstate]); #endif smtpc->state = newstate; @@ -345,9 +322,10 @@ static void state(struct connectdata *conn, smtpstate newstate) * 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 connectdata *conn) +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 */ @@ -357,10 +335,10 @@ static CURLcode smtp_perform_ehlo(struct connectdata *conn) smtpc->auth_supported = FALSE; /* Clear the AUTH capability */ /* Send the EHLO command */ - result = Curl_pp_sendf(&smtpc->pp, "EHLO %s", smtpc->domain); + result = Curl_pp_sendf(data, &smtpc->pp, "EHLO %s", smtpc->domain); if(!result) - state(conn, SMTP_EHLO); + smtp_state(data, SMTP_EHLO); return result; } @@ -371,7 +349,8 @@ static CURLcode smtp_perform_ehlo(struct connectdata *conn) * * Sends the HELO command to initialise communication with the SMTP server. */ -static CURLcode smtp_perform_helo(struct connectdata *conn) +static CURLcode smtp_perform_helo(struct Curl_easy *data, + struct connectdata *conn) { CURLcode result = CURLE_OK; struct smtp_conn *smtpc = &conn->proto.smtpc; @@ -380,10 +359,10 @@ static CURLcode smtp_perform_helo(struct connectdata *conn) in smtp connections */ /* Send the HELO command */ - result = Curl_pp_sendf(&smtpc->pp, "HELO %s", smtpc->domain); + result = Curl_pp_sendf(data, &smtpc->pp, "HELO %s", smtpc->domain); if(!result) - state(conn, SMTP_HELO); + smtp_state(data, SMTP_HELO); return result; } @@ -394,15 +373,15 @@ static CURLcode smtp_perform_helo(struct connectdata *conn) * * Sends the STLS command to start the upgrade to TLS. */ -static CURLcode smtp_perform_starttls(struct connectdata *conn) +static CURLcode smtp_perform_starttls(struct Curl_easy *data, + struct connectdata *conn) { - CURLcode result = CURLE_OK; - /* Send the STARTTLS command */ - result = Curl_pp_sendf(&conn->proto.smtpc.pp, "%s", "STARTTLS"); + CURLcode result = Curl_pp_sendf(data, &conn->proto.smtpc.pp, + "%s", "STARTTLS"); if(!result) - state(conn, SMTP_STARTTLS); + smtp_state(data, SMTP_STARTTLS); return result; } @@ -413,24 +392,32 @@ static CURLcode smtp_perform_starttls(struct connectdata *conn) * * Performs the upgrade to TLS. */ -static CURLcode smtp_perform_upgrade_tls(struct connectdata *conn) +static CURLcode smtp_perform_upgrade_tls(struct Curl_easy *data) { - CURLcode result = CURLE_OK; + /* Start the SSL connection */ + struct connectdata *conn = data->conn; struct smtp_conn *smtpc = &conn->proto.smtpc; + CURLcode result; + bool ssldone = FALSE; - /* Start the SSL connection */ - result = Curl_ssl_connect_nonblocking(conn, FIRSTSOCKET, &smtpc->ssldone); + 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) - state(conn, SMTP_UPGRADETLS); + smtp_state(data, SMTP_UPGRADETLS); if(smtpc->ssldone) { smtp_to_smtps(conn); - result = smtp_perform_ehlo(conn); + result = smtp_perform_ehlo(data); } } - +out: return result; } @@ -441,20 +428,21 @@ static CURLcode smtp_perform_upgrade_tls(struct connectdata *conn) * Sends an AUTH command allowing the client to login with the given SASL * authentication mechanism. */ -static CURLcode smtp_perform_auth(struct connectdata *conn, +static CURLcode smtp_perform_auth(struct Curl_easy *data, const char *mech, - const char *initresp) + const struct bufref *initresp) { CURLcode result = CURLE_OK; - struct smtp_conn *smtpc = &conn->proto.smtpc; + struct smtp_conn *smtpc = &data->conn->proto.smtpc; + const char *ir = (const char *) Curl_bufref_ptr(initresp); - if(initresp) { /* AUTH ... */ + if(ir) { /* AUTH ... */ /* Send the AUTH command with the initial response */ - result = Curl_pp_sendf(&smtpc->pp, "AUTH %s %s", mech, initresp); + result = Curl_pp_sendf(data, &smtpc->pp, "AUTH %s %s", mech, ir); } else { /* Send the AUTH command */ - result = Curl_pp_sendf(&smtpc->pp, "AUTH %s", mech); + result = Curl_pp_sendf(data, &smtpc->pp, "AUTH %s", mech); } return result; @@ -464,13 +452,33 @@ static CURLcode smtp_perform_auth(struct connectdata *conn, * * smtp_continue_auth() * - * Sends SASL continuation data or cancellation. + * Sends SASL continuation data. */ -static CURLcode smtp_continue_auth(struct connectdata *conn, const char *resp) +static CURLcode smtp_continue_auth(struct Curl_easy *data, + const char *mech, + const struct bufref *resp) { - struct smtp_conn *smtpc = &conn->proto.smtpc; + 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; - return Curl_pp_sendf(&smtpc->pp, "%s", resp); + (void)mech; + + return Curl_pp_sendf(data, &smtpc->pp, "*"); } /*********************************************************************** @@ -480,29 +488,30 @@ static CURLcode smtp_continue_auth(struct connectdata *conn, const char *resp) * Initiates the authentication sequence, with the appropriate SASL * authentication mechanism. */ -static CURLcode smtp_perform_authentication(struct connectdata *conn) +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 authentiation, and end the connect phase if not */ + server supports authentication, and end the connect phase if not */ if(!smtpc->auth_supported || - !Curl_sasl_can_authenticate(&smtpc->sasl, conn)) { - state(conn, SMTP_STOP); + !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, conn, FALSE, &progress); + result = Curl_sasl_start(&smtpc->sasl, data, FALSE, &progress); if(!result) { if(progress == SASL_INPROGRESS) - state(conn, SMTP_AUTH); + smtp_state(data, SMTP_AUTH); else { /* Other mechanisms not supported */ - infof(conn->data, "No known authentication mechanisms supported!\n"); + infof(data, "No known authentication mechanisms supported"); result = CURLE_LOGIN_DENIED; } } @@ -516,25 +525,68 @@ static CURLcode smtp_perform_authentication(struct connectdata *conn) * * Sends a SMTP based command. */ -static CURLcode smtp_perform_command(struct connectdata *conn) +static CURLcode smtp_perform_command(struct Curl_easy *data) { CURLcode result = CURLE_OK; - struct Curl_easy *data = conn->data; - struct SMTP *smtp = data->req.protop; - - /* Send the command */ - if(smtp->rcpt) - result = Curl_pp_sendf(&conn->proto.smtpc.pp, "%s %s", - smtp->custom && smtp->custom[0] != '\0' ? - smtp->custom : "VRFY", - smtp->rcpt->data); + 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 hostname parts. This is regardless of + whether the hostname 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 hostname + parts, converting the hostname 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 hostname 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 - result = Curl_pp_sendf(&conn->proto.smtpc.pp, "%s", + /* 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) - state(conn, SMTP_COMMAND); + smtp_state(data, SMTP_COMMAND); return result; } @@ -545,73 +597,180 @@ static CURLcode smtp_perform_command(struct connectdata *conn) * * Sends an MAIL command to initiate the upload of a message. */ -static CURLcode smtp_perform_mail(struct connectdata *conn) +static CURLcode smtp_perform_mail(struct Curl_easy *data) { char *from = NULL; char *auth = NULL; char *size = NULL; CURLcode result = CURLE_OK; - struct Curl_easy *data = conn->data; + 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 hostname parts. This is regardless of + whether the hostname is encoded using IDN ACE */ + bool utf8 = FALSE; /* Calculate the FROM parameter */ - if(!data->set.str[STRING_MAIL_FROM]) + 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 hostname parts, + converting the hostname to an IDN A-label if necessary */ + result = smtp_parse_address(data->set.str[STRING_MAIL_FROM], + &address, &host); + if(result) + goto out; + + /* 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 will 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("<>"); - else if(data->set.str[STRING_MAIL_FROM][0] == '<') - from = aprintf("%s", data->set.str[STRING_MAIL_FROM]); - else - from = aprintf("<%s>", data->set.str[STRING_MAIL_FROM]); - if(!from) - return CURLE_OUT_OF_MEMORY; + if(!from) { + result = CURLE_OUT_OF_MEMORY; + goto out; + } /* 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') - auth = aprintf("%s", data->set.str[STRING_MAIL_AUTH]); + 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 hostname parts, + converting the hostname to an IDN A-label if necessary */ + result = smtp_parse_address(data->set.str[STRING_MAIL_AUTH], + &address, &host); + if(result) + goto out; + + /* 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 will 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; + result = CURLE_OUT_OF_MEMORY; + goto out; } } +#ifndef CURL_DISABLE_MIME + /* Prepare the mime data if some. */ + if(data->set.mimepost.kind != MIMEKIND_NONE) { + /* Use the whole structure as data. */ + data->set.mimepost.flags &= ~(unsigned int)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"); + + if(!result) + result = Curl_creader_set_mime(data, &data->set.mimepost); + if(result) + goto out; + data->state.infilesize = Curl_creader_total_length(data); + } + else +#endif + { + result = Curl_creader_set_fread(data, data->state.infilesize); + if(result) + goto out; + } + /* Calculate the optional SIZE parameter */ - if(conn->proto.smtpc.size_supported && conn->data->state.infilesize > 0) { - size = aprintf("%" CURL_FORMAT_CURL_OFF_T, data->state.infilesize); + if(conn->proto.smtpc.size_supported && data->state.infilesize > 0) { + size = aprintf("%" FMT_OFF_T, data->state.infilesize); if(!size) { - free(from); - free(auth); + result = CURLE_OUT_OF_MEMORY; + goto out; + } + } + + /* If the mailboxes in the FROM and AUTH parameters do not 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; - return CURLE_OUT_OF_MEMORY; + while(rcpt && !utf8) { + /* Does the hostname contain non-ASCII characters? */ + if(!Curl_is_ASCII_name(rcpt->data)) + utf8 = TRUE; + + rcpt = rcpt->next; } } - /* Send the MAIL command */ - if(!auth && !size) - result = Curl_pp_sendf(&conn->proto.smtpc.pp, - "MAIL FROM:%s", from); - else if(auth && !size) - result = Curl_pp_sendf(&conn->proto.smtpc.pp, - "MAIL FROM:%s AUTH=%s", from, auth); - else if(auth && size) - result = Curl_pp_sendf(&conn->proto.smtpc.pp, - "MAIL FROM:%s AUTH=%s SIZE=%s", from, auth, size); - else - result = Curl_pp_sendf(&conn->proto.smtpc.pp, - "MAIL FROM:%s SIZE=%s", from, size); + /* Add the client reader doing STMP EOB escaping */ + result = cr_eob_add(data); + if(result) + goto out; + /* 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 */ + +out: free(from); free(auth); free(size); if(!result) - state(conn, SMTP_MAIL); + smtp_state(data, SMTP_MAIL); return result; } @@ -623,21 +782,36 @@ static CURLcode smtp_perform_mail(struct connectdata *conn) * Sends a RCPT TO command for a given recipient as part of the message upload * process. */ -static CURLcode smtp_perform_rcpt_to(struct connectdata *conn) +static CURLcode smtp_perform_rcpt_to(struct Curl_easy *data) { CURLcode result = CURLE_OK; - struct Curl_easy *data = conn->data; - struct SMTP *smtp = data->req.protop; + 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 hostname parts, + converting the hostname 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(smtp->rcpt->data[0] == '<') - result = Curl_pp_sendf(&conn->proto.smtpc.pp, "RCPT TO:%s", - smtp->rcpt->data); + if(host.name) + result = Curl_pp_sendf(data, &conn->proto.smtpc.pp, "RCPT TO:<%s@%s>", + address, host.name); else - result = Curl_pp_sendf(&conn->proto.smtpc.pp, "RCPT TO:<%s>", - smtp->rcpt->data); + /* An invalid mailbox was provided but we will 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) - state(conn, SMTP_RCPT); + smtp_state(data, SMTP_RCPT); return result; } @@ -648,27 +822,24 @@ static CURLcode smtp_perform_rcpt_to(struct connectdata *conn) * * Performs the quit action prior to sclose() being called. */ -static CURLcode smtp_perform_quit(struct connectdata *conn) +static CURLcode smtp_perform_quit(struct Curl_easy *data, + struct connectdata *conn) { - CURLcode result = CURLE_OK; - /* Send the QUIT command */ - result = Curl_pp_sendf(&conn->proto.smtpc.pp, "%s", "QUIT"); + CURLcode result = Curl_pp_sendf(data, &conn->proto.smtpc.pp, "%s", "QUIT"); if(!result) - state(conn, SMTP_QUIT); + smtp_state(data, SMTP_QUIT); return result; } /* For the initial server greeting */ -static CURLcode smtp_state_servergreet_resp(struct connectdata *conn, +static CURLcode smtp_state_servergreet_resp(struct Curl_easy *data, int smtpcode, smtpstate instate) { CURLcode result = CURLE_OK; - struct Curl_easy *data = conn->data; - (void)instate; /* no use for this yet */ if(smtpcode/100 != 2) { @@ -676,57 +847,59 @@ static CURLcode smtp_state_servergreet_resp(struct connectdata *conn, result = CURLE_WEIRD_SERVER_REPLY; } else - result = smtp_perform_ehlo(conn); + result = smtp_perform_ehlo(data); return result; } /* For STARTTLS responses */ -static CURLcode smtp_state_starttls_resp(struct connectdata *conn, +static CURLcode smtp_state_starttls_resp(struct Curl_easy *data, int smtpcode, smtpstate instate) { CURLcode result = CURLE_OK; - struct Curl_easy *data = conn->data; - (void)instate; /* no use for this yet */ + /* Pipelining in response is forbidden. */ + if(data->conn->proto.smtpc.pp.overflow) + 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(conn); + result = smtp_perform_authentication(data); } else - result = smtp_perform_upgrade_tls(conn); + result = smtp_perform_upgrade_tls(data); return result; } /* For EHLO responses */ -static CURLcode smtp_state_ehlo_resp(struct connectdata *conn, int smtpcode, +static CURLcode smtp_state_ehlo_resp(struct Curl_easy *data, + struct connectdata *conn, int smtpcode, smtpstate instate) { CURLcode result = CURLE_OK; - struct Curl_easy *data = conn->data; struct smtp_conn *smtpc = &conn->proto.smtpc; - const char *line = data->state.buffer; - size_t len = strlen(line); - size_t wordlen; + const char *line = Curl_dyn_ptr(&smtpc->pp.recvbuf); + size_t len = smtpc->pp.nfinal; (void)instate; /* no use for this yet */ if(smtpcode/100 != 2 && smtpcode != 1) { - if(data->set.use_ssl <= CURLUSESSL_TRY || conn->ssl[FIRSTSOCKET].use) - result = smtp_perform_helo(conn); + 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 { + else if(len >= 4) { line += 4; len -= 4; @@ -738,6 +911,10 @@ static CURLcode smtp_state_ehlo_resp(struct connectdata *conn, int smtpcode, 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; @@ -749,7 +926,8 @@ static CURLcode smtp_state_ehlo_resp(struct connectdata *conn, int smtpcode, /* Loop through the data line */ for(;;) { size_t llen; - unsigned int mechbit; + size_t wordlen; + unsigned short mechbit; while(len && (*line == ' ' || *line == '\t' || @@ -779,34 +957,36 @@ static CURLcode smtp_state_ehlo_resp(struct connectdata *conn, int smtpcode, } if(smtpcode != 1) { - if(data->set.use_ssl && !conn->ssl[FIRSTSOCKET].use) { - /* We don't have a SSL/TLS connection yet, but SSL is requested */ + if(data->set.use_ssl && !Curl_conn_is_ssl(conn, FIRSTSOCKET)) { + /* We do not have a SSL/TLS connection yet, but SSL is requested */ if(smtpc->tls_supported) /* Switch to TLS connection now */ - result = smtp_perform_starttls(conn); + result = smtp_perform_starttls(data, conn); else if(data->set.use_ssl == CURLUSESSL_TRY) /* Fallback and carry on with authentication */ - result = smtp_perform_authentication(conn); + result = smtp_perform_authentication(data); else { failf(data, "STARTTLS not supported."); result = CURLE_USE_SSL_FAILED; } } else - result = smtp_perform_authentication(conn); + 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 connectdata *conn, int smtpcode, +static CURLcode smtp_state_helo_resp(struct Curl_easy *data, int smtpcode, smtpstate instate) { CURLcode result = CURLE_OK; - struct Curl_easy *data = conn->data; - (void)instate; /* no use for this yet */ if(smtpcode/100 != 2) { @@ -815,28 +995,28 @@ static CURLcode smtp_state_helo_resp(struct connectdata *conn, int smtpcode, } else /* End of connect phase */ - state(conn, SMTP_STOP); + smtp_state(data, SMTP_STOP); return result; } /* For SASL authentication responses */ -static CURLcode smtp_state_auth_resp(struct connectdata *conn, +static CURLcode smtp_state_auth_resp(struct Curl_easy *data, int smtpcode, smtpstate instate) { CURLcode result = CURLE_OK; - struct Curl_easy *data = conn->data; + 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, conn, smtpcode, &progress); + result = Curl_sasl_continue(&smtpc->sasl, data, smtpcode, &progress); if(!result) switch(progress) { case SASL_DONE: - state(conn, SMTP_STOP); /* Authenticated */ + smtp_state(data, SMTP_STOP); /* Authenticated */ break; case SASL_IDLE: /* No mechanism left after cancellation */ failf(data, "Authentication cancelled"); @@ -850,29 +1030,24 @@ static CURLcode smtp_state_auth_resp(struct connectdata *conn, } /* For command responses */ -static CURLcode smtp_state_command_resp(struct connectdata *conn, int smtpcode, +static CURLcode smtp_state_command_resp(struct Curl_easy *data, int smtpcode, smtpstate instate) { CURLcode result = CURLE_OK; - struct Curl_easy *data = conn->data; - struct SMTP *smtp = data->req.protop; - char *line = data->state.buffer; - size_t len = strlen(line); + struct SMTP *smtp = data->req.p.smtp; + char *line = Curl_dyn_ptr(&data->conn->proto.smtpc.pp.recvbuf); + size_t len = data->conn->proto.smtpc.pp.nfinal; (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_RECV_ERROR; + result = CURLE_WEIRD_SERVER_REPLY; } else { - /* Temporarily add the LF character back and send as body to the client */ - if(!data->set.opt_no_body) { - line[len] = '\n'; - result = Curl_client_write(conn, CLIENTWRITE_BODY, line, len + 1); - line[len] = '\0'; - } + if(!data->req.no_body) + result = Curl_client_write(data, CLIENTWRITE_BODY, line, len); if(smtpcode != 1) { if(smtp->rcpt) { @@ -880,15 +1055,15 @@ static CURLcode smtp_state_command_resp(struct connectdata *conn, int smtpcode, if(smtp->rcpt) { /* Send the next command */ - result = smtp_perform_command(conn); + result = smtp_perform_command(data); } else /* End of DO phase */ - state(conn, SMTP_STOP); + smtp_state(data, SMTP_STOP); } else /* End of DO phase */ - state(conn, SMTP_STOP); + smtp_state(data, SMTP_STOP); } } @@ -896,12 +1071,10 @@ static CURLcode smtp_state_command_resp(struct connectdata *conn, int smtpcode, } /* For MAIL responses */ -static CURLcode smtp_state_mail_resp(struct connectdata *conn, int smtpcode, +static CURLcode smtp_state_mail_resp(struct Curl_easy *data, int smtpcode, smtpstate instate) { CURLcode result = CURLE_OK; - struct Curl_easy *data = conn->data; - (void)instate; /* no use for this yet */ if(smtpcode/100 != 2) { @@ -910,37 +1083,65 @@ static CURLcode smtp_state_mail_resp(struct connectdata *conn, int smtpcode, } else /* Start the RCPT TO command */ - result = smtp_perform_rcpt_to(conn); + result = smtp_perform_rcpt_to(data); return result; } /* For RCPT responses */ -static CURLcode smtp_state_rcpt_resp(struct connectdata *conn, int smtpcode, +static CURLcode smtp_state_rcpt_resp(struct Curl_easy *data, + struct connectdata *conn, int smtpcode, smtpstate instate) { CURLcode result = CURLE_OK; - struct Curl_easy *data = conn->data; - struct SMTP *smtp = data->req.protop; + 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 */ - if(smtpcode/100 != 2) { - failf(data, "RCPT failed: %d", smtpcode); - result = CURLE_SEND_ERROR; + is_smtp_err = (smtpcode/100 != 2) ? TRUE : FALSE; + + /* If there is multiple RCPT TO to be issued, it is possible to ignore errors + and proceed with only the valid addresses. */ + is_smtp_blocking_err = + (is_smtp_err && !data->set.mail_rcpt_allowfails) ? 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(conn); + result = smtp_perform_rcpt_to(data); else { - /* Send the DATA command */ - result = Curl_pp_sendf(&conn->proto.smtpc.pp, "%s", "DATA"); + /* We were not 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) - state(conn, SMTP_DATA); + if(!result) + smtp_state(data, SMTP_DATA); + } } } @@ -948,12 +1149,10 @@ static CURLcode smtp_state_rcpt_resp(struct connectdata *conn, int smtpcode, } /* For DATA response */ -static CURLcode smtp_state_data_resp(struct connectdata *conn, int smtpcode, +static CURLcode smtp_state_data_resp(struct Curl_easy *data, int smtpcode, smtpstate instate) { CURLcode result = CURLE_OK; - struct Curl_easy *data = conn->data; - (void)instate; /* no use for this yet */ if(smtpcode != 354) { @@ -965,10 +1164,10 @@ static CURLcode smtp_state_data_resp(struct connectdata *conn, int smtpcode, Curl_pgrsSetUploadSize(data, data->state.infilesize); /* SMTP upload */ - Curl_setup_transfer(conn, -1, -1, FALSE, NULL, FIRSTSOCKET, NULL); + Curl_xfer_setup1(data, CURL_XFER_SEND, -1, FALSE); /* End of DO phase */ - state(conn, SMTP_STOP); + smtp_state(data, SMTP_STOP); } return result; @@ -976,7 +1175,7 @@ static CURLcode smtp_state_data_resp(struct connectdata *conn, int smtpcode, /* For POSTDATA responses, which are received after the entire DATA part has been sent to the server */ -static CURLcode smtp_state_postdata_resp(struct connectdata *conn, +static CURLcode smtp_state_postdata_resp(struct Curl_easy *data, int smtpcode, smtpstate instate) { @@ -985,35 +1184,35 @@ static CURLcode smtp_state_postdata_resp(struct connectdata *conn, (void)instate; /* no use for this yet */ if(smtpcode != 250) - result = CURLE_RECV_ERROR; + result = CURLE_WEIRD_SERVER_REPLY; /* End of DONE phase */ - state(conn, SMTP_STOP); + smtp_state(data, SMTP_STOP); return result; } -static CURLcode smtp_statemach_act(struct connectdata *conn) +static CURLcode smtp_statemachine(struct Curl_easy *data, + struct connectdata *conn) { CURLcode result = CURLE_OK; - curl_socket_t sock = conn->sock[FIRSTSOCKET]; - struct Curl_easy *data = conn->data; 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 */ +upgrade_tls: if(smtpc->state == SMTP_UPGRADETLS) - return smtp_perform_upgrade_tls(conn); + return smtp_perform_upgrade_tls(data); /* Flush any data that needs to be sent */ if(pp->sendleft) - return Curl_pp_flushsend(pp); + return Curl_pp_flushsend(data, pp); do { /* Read the response from the server */ - result = Curl_pp_readresp(sock, pp, &smtpcode, &nread); + result = Curl_pp_readresp(data, FIRSTSOCKET, pp, &smtpcode, &nread); if(result) return result; @@ -1027,50 +1226,53 @@ static CURLcode smtp_statemach_act(struct connectdata *conn) /* We have now received a full SMTP server response */ switch(smtpc->state) { case SMTP_SERVERGREET: - result = smtp_state_servergreet_resp(conn, smtpcode, smtpc->state); + result = smtp_state_servergreet_resp(data, smtpcode, smtpc->state); break; case SMTP_EHLO: - result = smtp_state_ehlo_resp(conn, smtpcode, smtpc->state); + result = smtp_state_ehlo_resp(data, conn, smtpcode, smtpc->state); break; case SMTP_HELO: - result = smtp_state_helo_resp(conn, smtpcode, smtpc->state); + result = smtp_state_helo_resp(data, smtpcode, smtpc->state); break; case SMTP_STARTTLS: - result = smtp_state_starttls_resp(conn, smtpcode, smtpc->state); + result = smtp_state_starttls_resp(data, smtpcode, smtpc->state); + /* During UPGRADETLS, leave the read loop as we need to connect + * (e.g. TLS handshake) before we continue sending/receiving. */ + if(!result && (smtpc->state == SMTP_UPGRADETLS)) + goto upgrade_tls; break; case SMTP_AUTH: - result = smtp_state_auth_resp(conn, smtpcode, smtpc->state); + result = smtp_state_auth_resp(data, smtpcode, smtpc->state); break; case SMTP_COMMAND: - result = smtp_state_command_resp(conn, smtpcode, smtpc->state); + result = smtp_state_command_resp(data, smtpcode, smtpc->state); break; case SMTP_MAIL: - result = smtp_state_mail_resp(conn, smtpcode, smtpc->state); + result = smtp_state_mail_resp(data, smtpcode, smtpc->state); break; case SMTP_RCPT: - result = smtp_state_rcpt_resp(conn, smtpcode, smtpc->state); + result = smtp_state_rcpt_resp(data, conn, smtpcode, smtpc->state); break; case SMTP_DATA: - result = smtp_state_data_resp(conn, smtpcode, smtpc->state); + result = smtp_state_data_resp(data, smtpcode, smtpc->state); break; case SMTP_POSTDATA: - result = smtp_state_postdata_resp(conn, smtpcode, smtpc->state); + result = smtp_state_postdata_resp(data, smtpcode, smtpc->state); break; case SMTP_QUIT: - /* fallthrough, just stop! */ default: /* internal error */ - state(conn, SMTP_STOP); + smtp_state(data, SMTP_STOP); break; } } while(!result && smtpc->state != SMTP_STOP && Curl_pp_moredata(pp)); @@ -1079,43 +1281,47 @@ static CURLcode smtp_statemach_act(struct connectdata *conn) } /* Called repeatedly until done from multi.c */ -static CURLcode smtp_multi_statemach(struct connectdata *conn, bool *done) +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) { - result = Curl_ssl_connect_nonblocking(conn, FIRSTSOCKET, &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(&smtpc->pp, FALSE); + result = Curl_pp_statemach(data, &smtpc->pp, FALSE, FALSE); *done = (smtpc->state == SMTP_STOP) ? TRUE : FALSE; return result; } -static CURLcode smtp_block_statemach(struct connectdata *conn) +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(&smtpc->pp, TRUE); + 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 connectdata *conn) +static CURLcode smtp_init(struct Curl_easy *data) { CURLcode result = CURLE_OK; - struct Curl_easy *data = conn->data; struct SMTP *smtp; - smtp = data->req.protop = calloc(sizeof(struct SMTP), 1); + smtp = data->req.p.smtp = calloc(1, sizeof(struct SMTP)); if(!smtp) result = CURLE_OUT_OF_MEMORY; @@ -1123,10 +1329,10 @@ static CURLcode smtp_init(struct connectdata *conn) } /* For the SMTP "protocol connect" and "doing" phases only */ -static int smtp_getsock(struct connectdata *conn, curl_socket_t *socks, - int numsocks) +static int smtp_getsock(struct Curl_easy *data, + struct connectdata *conn, curl_socket_t *socks) { - return Curl_pp_getsock(&conn->proto.smtpc.pp, socks, numsocks); + return Curl_pp_getsock(data, &conn->proto.smtpc.pp, socks); } /*********************************************************************** @@ -1139,9 +1345,10 @@ static int smtp_getsock(struct connectdata *conn, curl_socket_t *socks, * 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 connectdata *conn, bool *done) +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; @@ -1150,14 +1357,10 @@ static CURLcode smtp_connect(struct connectdata *conn, bool *done) /* We always support persistent connections in SMTP */ connkeep(conn, "SMTP default"); - /* Set the default response time-out */ - pp->response_time = RESP_TIMEOUT; - pp->statemach_act = smtp_statemach_act; - pp->endofresp = smtp_endofresp; - pp->conn = conn; + PINGPONG_SETUP(pp, smtp_statemachine, smtp_endofresp); /* Initialize the SASL storage */ - Curl_sasl_init(&smtpc->sasl, &saslsmtp); + Curl_sasl_init(&smtpc->sasl, data, &saslsmtp); /* Initialise the pingpong layer */ Curl_pp_init(pp); @@ -1168,14 +1371,14 @@ static CURLcode smtp_connect(struct connectdata *conn, bool *done) return result; /* Parse the URL path */ - result = smtp_parse_url_path(conn); + result = smtp_parse_url_path(data); if(result) return result; /* Start off waiting for the server greeting response */ - state(conn, SMTP_SERVERGREET); + smtp_state(data, SMTP_SERVERGREET); - result = smtp_multi_statemach(conn, done); + result = smtp_multi_statemach(data, done); return result; } @@ -1189,85 +1392,38 @@ static CURLcode smtp_connect(struct connectdata *conn, bool *done) * * Input argument is already checked for validity. */ -static CURLcode smtp_done(struct connectdata *conn, CURLcode status, +static CURLcode smtp_done(struct Curl_easy *data, CURLcode status, bool premature) { CURLcode result = CURLE_OK; - struct Curl_easy *data = conn->data; - struct SMTP *smtp = data->req.protop; - struct pingpong *pp = &conn->proto.smtpc.pp; - char *eob; - ssize_t len; - ssize_t bytes_written; + struct connectdata *conn = data->conn; + struct SMTP *smtp = data->req.p.smtp; (void)premature; - if(!smtp || !pp->conn) + 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.upload && data->set.mail_rcpt) { - /* 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 || !conn->data->state.infilesize) { - eob = strdup(SMTP_EOB + 2); - len = SMTP_EOB_LEN - 2; - } - else { - eob = strdup(SMTP_EOB); - len = SMTP_EOB_LEN; - } + else if(!data->set.connect_only && data->set.mail_rcpt && + (data->state.upload || IS_MIME_POST(data))) { - if(!eob) - return CURLE_OUT_OF_MEMORY; + smtp_state(data, SMTP_POSTDATA); - /* Send the end of block data */ - result = Curl_write(conn, 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_tvnow(); - - free(eob); - } - - state(conn, SMTP_POSTDATA); - - /* Run the state-machine - - TODO: when the multi interface is used, this _really_ should be using - the smtp_multi_statemach function but we have no general support for - non-blocking DONE operations! - */ - result = smtp_block_statemach(conn); + /* Run the state-machine */ + result = smtp_block_statemach(data, conn, FALSE); } - /* Cleanup our per-request based variables */ - Curl_safefree(smtp->custom); - /* Clear the transfer mode for the next request */ - smtp->transfer = FTPTRANSFER_BODY; - + smtp->transfer = PPTRANSFER_BODY; + CURL_TRC_SMTP(data, "smtp_done(status=%d, premature=%d) -> %d", + status, premature, result); return result; } @@ -1278,19 +1434,18 @@ static CURLcode smtp_done(struct connectdata *conn, CURLcode status, * 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 connectdata *conn, bool *connected, +static CURLcode smtp_perform(struct Curl_easy *data, bool *connected, bool *dophase_done) { /* This is SMTP and no proxy */ CURLcode result = CURLE_OK; - struct Curl_easy *data = conn->data; - struct SMTP *smtp = data->req.protop; + struct SMTP *smtp = data->req.p.smtp; - DEBUGF(infof(conn->data, "DO phase starts\n")); + CURL_TRC_SMTP(data, "smtp_perform(), start"); - if(data->set.opt_no_body) { + if(data->req.no_body) { /* Requested no body means no transfer */ - smtp->transfer = FTPTRANSFER_INFO; + smtp->transfer = PPTRANSFER_INFO; } *dophase_done = FALSE; /* not done yet */ @@ -1298,25 +1453,36 @@ static CURLcode smtp_perform(struct connectdata *conn, bool *connected, /* Store the first recipient (or NULL if not specified) */ smtp->rcpt = data->set.mail_rcpt; + /* Track of whether we have successfully sent at least one RCPT TO command */ + smtp->rcpt_had_ok = FALSE; + + /* Track of the last error we have 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->set.upload && data->set.mail_rcpt) + if((data->state.upload || IS_MIME_POST(data)) && data->set.mail_rcpt) /* MAIL transfer */ - result = smtp_perform_mail(conn); + result = smtp_perform_mail(data); else /* SMTP based command (VRFY, EXPN, NOOP, RSET or HELP) */ - result = smtp_perform_command(conn); + result = smtp_perform_command(data); if(result) - return result; + goto out; /* Run the state-machine */ - result = smtp_multi_statemach(conn, dophase_done); + result = smtp_multi_statemach(data, dophase_done); - *connected = conn->bits.tcpconnect[FIRSTSOCKET]; - - if(*dophase_done) - DEBUGF(infof(conn->data, "DO phase is complete\n")); + *connected = Curl_conn_is_connected(data->conn, FIRSTSOCKET); +out: + CURL_TRC_SMTP(data, "smtp_perform() -> %d, connected=%d, done=%d", + result, *connected, *dophase_done); return result; } @@ -1329,19 +1495,20 @@ static CURLcode smtp_perform(struct connectdata *conn, bool *connected, * * The input argument is already checked for validity. */ -static CURLcode smtp_do(struct connectdata *conn, bool *done) +static CURLcode smtp_do(struct Curl_easy *data, bool *done) { CURLcode result = CURLE_OK; - + DEBUGASSERT(data); + DEBUGASSERT(data->conn); *done = FALSE; /* default to false */ /* Parse the custom request */ - result = smtp_parse_custom_request(conn); + result = smtp_parse_custom_request(data); if(result) return result; - result = smtp_regular_transfer(conn, done); - + result = smtp_regular_transfer(data, done); + CURL_TRC_SMTP(data, "smtp_do() -> %d, done=%d", result, *done); return result; } @@ -1352,19 +1519,21 @@ static CURLcode smtp_do(struct connectdata *conn, bool *done) * Disconnect from an SMTP server. Cleanup protocol-specific per-connection * resources. BLOCKING. */ -static CURLcode smtp_disconnect(struct connectdata *conn, bool dead_connection) +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. */ - /* The SMTP session may or may not have been allocated/setup at this - point! */ - if(!dead_connection && smtpc->pp.conn && smtpc->pp.conn->bits.protoconnstart) - if(!smtp_perform_quit(conn)) - (void)smtp_block_statemach(conn); /* ignore errors on QUIT */ + 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); @@ -1374,37 +1543,39 @@ static CURLcode smtp_disconnect(struct connectdata *conn, bool dead_connection) /* Cleanup our connection based variables */ Curl_safefree(smtpc->domain); + CURL_TRC_SMTP(data, "smtp_disconnect(), finished"); return CURLE_OK; } /* Call this when the DO phase has completed */ -static CURLcode smtp_dophase_done(struct connectdata *conn, bool connected) +static CURLcode smtp_dophase_done(struct Curl_easy *data, bool connected) { - struct SMTP *smtp = conn->data->req.protop; + struct SMTP *smtp = data->req.p.smtp; (void)connected; - if(smtp->transfer != FTPTRANSFER_BODY) + if(smtp->transfer != PPTRANSFER_BODY) /* no data to transfer */ - Curl_setup_transfer(conn, -1, -1, FALSE, NULL, -1, NULL); + Curl_xfer_setup_nop(data); return CURLE_OK; } /* Called from multi.c while DOing */ -static CURLcode smtp_doing(struct connectdata *conn, bool *dophase_done) +static CURLcode smtp_doing(struct Curl_easy *data, bool *dophase_done) { - CURLcode result = smtp_multi_statemach(conn, dophase_done); + CURLcode result = smtp_multi_statemach(data, dophase_done); if(result) - DEBUGF(infof(conn->data, "DO phase failed\n")); + DEBUGF(infof(data, "DO phase failed")); else if(*dophase_done) { - result = smtp_dophase_done(conn, FALSE /* not connected */); + result = smtp_dophase_done(data, FALSE /* not connected */); - DEBUGF(infof(conn->data, "DO phase is complete\n")); + DEBUGF(infof(data, "DO phase is complete")); } + CURL_TRC_SMTP(data, "smtp_doing() -> %d, done=%d", result, *dophase_done); return result; } @@ -1417,12 +1588,11 @@ static CURLcode smtp_doing(struct connectdata *conn, bool *dophase_done) * Performs all commands done before a regular transfer between a local and a * remote host. */ -static CURLcode smtp_regular_transfer(struct connectdata *conn, +static CURLcode smtp_regular_transfer(struct Curl_easy *data, bool *dophase_done) { CURLcode result = CURLE_OK; bool connected = FALSE; - struct Curl_easy *data = conn->data; /* Make sure size is unknown at this point */ data->req.size = -1; @@ -1434,55 +1604,29 @@ static CURLcode smtp_regular_transfer(struct connectdata *conn, Curl_pgrsSetDownloadSize(data, -1); /* Carry out the perform */ - result = smtp_perform(conn, &connected, dophase_done); + result = smtp_perform(data, &connected, dophase_done); /* Perform post DO phase operations if necessary */ if(!result && *dophase_done) - result = smtp_dophase_done(conn, connected); + result = smtp_dophase_done(data, connected); + CURL_TRC_SMTP(data, "smtp_regular_transfer() -> %d, done=%d", + result, *dophase_done); return result; } -static CURLcode smtp_setup_connection(struct connectdata *conn) +static CURLcode smtp_setup_connection(struct Curl_easy *data, + struct connectdata *conn) { - struct Curl_easy *data = conn->data; CURLcode result; /* Clear the TLS upgraded flag */ - conn->tls_upgraded = FALSE; - - /* Set up the proxy if necessary */ - if(conn->bits.httpproxy && !data->set.tunnel_thru_httpproxy) { - /* Unless we have asked to tunnel SMTP operations through the proxy, we - switch and use HTTP operations only */ -#ifndef CURL_DISABLE_HTTP - if(conn->handler == &Curl_handler_smtp) - conn->handler = &Curl_handler_smtp_proxy; - else { -#ifdef USE_SSL - conn->handler = &Curl_handler_smtps_proxy; -#else - failf(data, "SMTPS not supported!"); - return CURLE_UNSUPPORTED_PROTOCOL; -#endif - } - /* set it up as a HTTP connection instead */ - return conn->handler->setup_connection(conn); - -#else - failf(data, "SMTP over http proxy requires HTTP support built-in!"); - return CURLE_UNSUPPORTED_PROTOCOL; -#endif - } + conn->bits.tls_upgraded = FALSE; /* Initialise the SMTP layer */ - result = smtp_init(conn); - if(result) - return result; - - data->state.path++; /* don't include the initial slash */ - - return CURLE_OK; + result = smtp_init(data); + CURL_TRC_SMTP(data, "smtp_setup_connection() -> %d", result); + return result; } /*********************************************************************** @@ -1497,8 +1641,6 @@ static CURLcode smtp_parse_url_options(struct connectdata *conn) struct smtp_conn *smtpc = &conn->proto.smtpc; const char *ptr = conn->options; - smtpc->sasl.resetprefs = TRUE; - while(!result && ptr && *ptr) { const char *key = ptr; const char *value; @@ -1530,12 +1672,12 @@ static CURLcode smtp_parse_url_options(struct connectdata *conn) * * Parse the URL path into separate path components. */ -static CURLcode smtp_parse_url_path(struct connectdata *conn) +static CURLcode smtp_parse_url_path(struct Curl_easy *data) { /* The SMTP struct is already initialised in smtp_connect() */ - struct Curl_easy *data = conn->data; + struct connectdata *conn = data->conn; struct smtp_conn *smtpc = &conn->proto.smtpc; - const char *path = data->state.path; + const char *path = &data->state.up.path[1]; /* skip leading path */ char localhost[HOSTNAME_MAX + 1]; /* Calculate the path if necessary */ @@ -1547,7 +1689,7 @@ static CURLcode smtp_parse_url_path(struct connectdata *conn) } /* URL decode the path and use it as the domain in our EHLO */ - return Curl_urldecode(conn->data, path, 0, &smtpc->domain, NULL, TRUE); + return Curl_urldecode(path, 0, &smtpc->domain, NULL, REJECT_CTRL); } /*********************************************************************** @@ -1556,118 +1698,259 @@ static CURLcode smtp_parse_url_path(struct connectdata *conn) * * Parse the custom request. */ -static CURLcode smtp_parse_custom_request(struct connectdata *conn) +static CURLcode smtp_parse_custom_request(struct Curl_easy *data) { CURLcode result = CURLE_OK; - struct Curl_easy *data = conn->data; - struct SMTP *smtp = data->req.protop; + 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(data, custom, 0, &smtp->custom, NULL, TRUE); + result = Curl_urldecode(custom, 0, &smtp->custom, NULL, REJECT_CTRL); return result; } -CURLcode Curl_smtp_escape_eob(struct connectdata *conn, const ssize_t nread) +/*********************************************************************** + * + * smtp_parse_address() + * + * Parse the fully qualified mailbox address into a local address part and the + * hostname, converting the hostname 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 hostname structure that holds the original, + * and optionally encoded, hostname. + * 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 hostname 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 hostname being NULL. + */ +static CURLcode smtp_parse_address(const char *fqma, char **address, + struct hostname *host) { - /* 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 Curl_easy *data = conn->data; - struct SMTP *smtp = data->req.protop; - 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 * BUFSIZE); - if(!newscratch) { - failf(data, "Failed to alloc scratch buffer!"); - - return CURLE_OUT_OF_MEMORY; - } + CURLcode result = CURLE_OK; + size_t length; + + /* Duplicate the fully qualified email address so we can manipulate it, + ensuring it does not 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'; } - /* Have we already sent part of the EOB? */ - eob_sent = smtp->eob; + /* Extract the hostname from the address (if we can) */ + host->name = strpbrk(dup, "@"); + if(host->name) { + *host->name = '\0'; + host->name = host->name + 1; - /* This loop can be improved by some kind of Boyer-Moore style of - approach but that is saved for later... */ - for(i = 0, si = 0; i < nread; i++) { - if(SMTP_EOB[smtp->eob] == data->req.upload_fromhere[i]) { - smtp->eob++; + /* Attempt to convert the hostname to IDN ACE */ + (void) Curl_idnconvert_hostname(host); - /* 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; + /* If Curl_idnconvert_hostname() fails then we shall attempt to continue + and send the hostname using UTF-8 rather than as 7-bit ACE (which is + our preference) */ + } - eob_sent = 0; + /* Extract the local address from the mailbox */ + *address = dup; - /* Reset the trailing CRLF flag as there was more data */ - smtp->trailing_crlf = FALSE; + return result; +} + +struct cr_eob_ctx { + struct Curl_creader super; + struct bufq buf; + size_t n_eob; /* how many EOB bytes we matched so far */ + size_t eob; /* Number of bytes of the EOB (End Of Body) that + have been received so far */ + BIT(read_eos); /* we read an EOS from the next reader */ + BIT(eos); /* we have returned an EOS */ +}; + +static CURLcode cr_eob_init(struct Curl_easy *data, + struct Curl_creader *reader) +{ + struct cr_eob_ctx *ctx = reader->ctx; + (void)data; + /* The first char we read is the first on a line, as if we had + * read CRLF just before */ + ctx->n_eob = 2; + Curl_bufq_init2(&ctx->buf, (16 * 1024), 1, BUFQ_OPT_SOFT_LIMIT); + return CURLE_OK; +} + +static void cr_eob_close(struct Curl_easy *data, struct Curl_creader *reader) +{ + struct cr_eob_ctx *ctx = reader->ctx; + (void)data; + Curl_bufq_free(&ctx->buf); +} + +/* this is the 5-bytes End-Of-Body marker for SMTP */ +#define SMTP_EOB "\r\n.\r\n" +#define SMTP_EOB_FIND_LEN 3 + +/* client reader doing SMTP End-Of-Body escaping. */ +static CURLcode cr_eob_read(struct Curl_easy *data, + struct Curl_creader *reader, + char *buf, size_t blen, + size_t *pnread, bool *peos) +{ + struct cr_eob_ctx *ctx = reader->ctx; + CURLcode result = CURLE_OK; + size_t nread, i, start, n; + bool eos; + + if(!ctx->read_eos && Curl_bufq_is_empty(&ctx->buf)) { + /* Get more and convert it when needed */ + result = Curl_creader_read(data, reader->next, buf, blen, &nread, &eos); + if(result) + return result; + + ctx->read_eos = eos; + if(nread) { + if(!ctx->n_eob && !memchr(buf, SMTP_EOB[0], nread)) { + /* not in the middle of a match, no EOB start found, just pass */ + *pnread = nread; + *peos = FALSE; + return CURLE_OK; + } + /* scan for EOB (continuation) and convert */ + for(i = start = 0; i < nread; ++i) { + if(ctx->n_eob >= SMTP_EOB_FIND_LEN) { + /* matched the EOB prefix and seeing additional char, add '.' */ + result = Curl_bufq_cwrite(&ctx->buf, buf + start, i - start, &n); + if(result) + return result; + result = Curl_bufq_cwrite(&ctx->buf, ".", 1, &n); + if(result) + return result; + ctx->n_eob = 0; + start = i; + if(data->state.infilesize > 0) + data->state.infilesize++; + } + + if(buf[i] != SMTP_EOB[ctx->n_eob]) + ctx->n_eob = 0; + + if(buf[i] == SMTP_EOB[ctx->n_eob]) { + /* matching another char of the EOB */ + ++ctx->n_eob; + } + } + + /* add any remainder to buf */ + if(start < nread) { + result = Curl_bufq_cwrite(&ctx->buf, buf + start, nread - start, &n); + if(result) + return result; + } } - /* 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; + if(ctx->read_eos) { + /* if we last matched a CRLF or if the data was empty, add ".\r\n" + * to end the body. If we sent something and it did not end with "\r\n", + * add "\r\n.\r\n" to end the body */ + const char *eob = SMTP_EOB; + switch(ctx->n_eob) { + case 2: + /* seen a CRLF at the end, just add the remainder */ + eob = &SMTP_EOB[2]; + break; + case 3: + /* ended with '\r\n.', we should escpe the last '.' */ + eob = "." SMTP_EOB; + break; + default: + break; + } + result = Curl_bufq_cwrite(&ctx->buf, eob, strlen(eob), &n); + if(result) + return result; } - 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; + *peos = FALSE; + if(!Curl_bufq_is_empty(&ctx->buf)) { + result = Curl_bufq_cread(&ctx->buf, buf, blen, pnread); + } + else + *pnread = 0; + + if(ctx->read_eos && Curl_bufq_is_empty(&ctx->buf)) { + /* no more data, read all, done. */ + ctx->eos = TRUE; } + *peos = ctx->eos; + DEBUGF(infof(data, "cr_eob_read(%zu) -> %d, %zd, %d", + blen, result, *pnread, *peos)); + return result; +} - /* Only use the new buffer if we replaced something */ - if(si != nread) { - /* Upload from the new (replaced) buffer instead */ - data->req.upload_fromhere = scratch; +static curl_off_t cr_eob_total_length(struct Curl_easy *data, + struct Curl_creader *reader) +{ + /* this reader changes length depending on input */ + (void)data; + (void)reader; + return -1; +} - /* Save the buffer so it can be freed later */ - data->state.scratch = scratch; +static const struct Curl_crtype cr_eob = { + "cr-smtp-eob", + cr_eob_init, + cr_eob_read, + cr_eob_close, + Curl_creader_def_needs_rewind, + cr_eob_total_length, + Curl_creader_def_resume_from, + Curl_creader_def_rewind, + Curl_creader_def_unpause, + Curl_creader_def_is_paused, + Curl_creader_def_done, + sizeof(struct cr_eob_ctx) +}; - /* Free the old scratch buffer */ - free(oldscratch); +static CURLcode cr_eob_add(struct Curl_easy *data) +{ + struct Curl_creader *reader = NULL; + CURLcode result; - /* Set the new amount too */ - data->req.upload_present = si; - } - else - free(newscratch); + result = Curl_creader_create(&reader, data, &cr_eob, + CURL_CR_CONTENT_ENCODE); + if(!result) + result = Curl_creader_add(data, reader); - return CURLE_OK; + if(result && reader) + Curl_creader_free(data, reader); + return result; } #endif /* CURL_DISABLE_SMTP */ diff --git a/contrib/curl/lib/smtp.h b/contrib/curl/lib/smtp.h index b67340a40c..7c2af68073 100644 --- a/contrib/curl/lib/smtp.h +++ b/contrib/curl/lib/smtp.h @@ -7,11 +7,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 2009 - 2014, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -20,6 +20,8 @@ * 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" @@ -55,37 +57,31 @@ 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 */ - bool trailing_crlf; /* Specifies if the tailing CRLF is present */ + 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! */ - bool ssldone; /* Is connect() over SSL done? */ char *domain; /* Client address/name to send in the EHLO */ - struct SASL sasl; /* SASL-related storage */ - bool tls_supported; /* StartTLS capability supported by server */ - bool size_supported; /* If server supports SIZE extension according to + 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 */ - bool auth_supported; /* AUTH capability supported by server */ + 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 connectdata *conn, const ssize_t nread); - #endif /* HEADER_CURL_SMTP_H */ diff --git a/contrib/curl/lib/sockaddr.h b/contrib/curl/lib/sockaddr.h index 95ba4c3c97..2e2d375e06 100644 --- a/contrib/curl/lib/sockaddr.h +++ b/contrib/curl/lib/sockaddr.h @@ -7,11 +7,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2009, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -20,6 +20,8 @@ * 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" @@ -28,7 +30,7 @@ struct Curl_sockaddr_storage { union { struct sockaddr sa; struct sockaddr_in sa_in; -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 struct sockaddr_in6 sa_in6; #endif #ifdef HAVE_STRUCT_SOCKADDR_STORAGE @@ -40,4 +42,3 @@ struct Curl_sockaddr_storage { }; #endif /* HEADER_CURL_SOCKADDR_H */ - diff --git a/contrib/curl/lib/socketpair.c b/contrib/curl/lib/socketpair.c new file mode 100644 index 0000000000..b14f5a5f14 --- /dev/null +++ b/contrib/curl/lib/socketpair.c @@ -0,0 +1,264 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" +#include "socketpair.h" +#include "urldata.h" +#include "rand.h" + +#if defined(USE_EVENTFD) +#ifdef HAVE_SYS_EVENTFD_H +#include +#endif + +int Curl_eventfd(curl_socket_t socks[2], bool nonblocking) +{ + int efd = eventfd(0, nonblocking ? EFD_CLOEXEC | EFD_NONBLOCK : EFD_CLOEXEC); + if(efd == -1) { + socks[0] = socks[1] = CURL_SOCKET_BAD; + return -1; + } + socks[0] = socks[1] = efd; + return 0; +} +#elif defined(HAVE_PIPE) +#ifdef HAVE_FCNTL +#include +#endif + +int Curl_pipe(curl_socket_t socks[2], bool nonblocking) +{ + if(pipe(socks)) + return -1; +#ifdef HAVE_FCNTL + if(fcntl(socks[0], F_SETFD, FD_CLOEXEC) || + fcntl(socks[1], F_SETFD, FD_CLOEXEC) ) { + close(socks[0]); + close(socks[1]); + socks[0] = socks[1] = CURL_SOCKET_BAD; + return -1; + } +#endif + if(nonblocking) { + if(curlx_nonblock(socks[0], TRUE) < 0 || + curlx_nonblock(socks[1], TRUE) < 0) { + close(socks[0]); + close(socks[1]); + socks[0] = socks[1] = CURL_SOCKET_BAD; + return -1; + } + } + + return 0; +} +#endif + + +#ifndef CURL_DISABLE_SOCKETPAIR +#ifdef HAVE_SOCKETPAIR +int Curl_socketpair(int domain, int type, int protocol, + curl_socket_t socks[2], bool nonblocking) +{ +#ifdef SOCK_NONBLOCK + type = nonblocking ? type | SOCK_NONBLOCK : type; +#endif + if(socketpair(domain, type, protocol, socks)) + return -1; +#ifndef SOCK_NONBLOCK + if(nonblocking) { + if(curlx_nonblock(socks[0], TRUE) < 0 || + curlx_nonblock(socks[1], TRUE) < 0) { + close(socks[0]); + close(socks[1]); + return -1; + } + } +#endif + return 0; +} +#else /* !HAVE_SOCKETPAIR */ +#ifdef _WIN32 +/* + * This is a socketpair() implementation for Windows. + */ +#include +#include +#else +#ifdef HAVE_NETDB_H +#include +#endif +#ifdef HAVE_NETINET_IN_H +#include /* IPPROTO_TCP */ +#endif +#ifdef HAVE_ARPA_INET_H +#include +#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], bool nonblocking) +{ + 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__) + /* do not 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 do not 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; + /* Do not 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); + } + + if(nonblocking) + if(curlx_nonblock(socks[0], TRUE) < 0 || + curlx_nonblock(socks[1], TRUE) < 0) + goto error; + sclose(listener); + return 0; + +error: + sclose(listener); + sclose(socks[0]); + sclose(socks[1]); + return -1; +} +#endif +#endif /* !CURL_DISABLE_SOCKETPAIR */ diff --git a/contrib/curl/lib/socketpair.h b/contrib/curl/lib/socketpair.h new file mode 100644 index 0000000000..3044f1122e --- /dev/null +++ b/contrib/curl/lib/socketpair.h @@ -0,0 +1,98 @@ +#ifndef HEADER_CURL_SOCKETPAIR_H +#define HEADER_CURL_SOCKETPAIR_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#if defined(HAVE_EVENTFD) && \ + defined(__x86_64__) && \ + defined(__aarch64__) && \ + defined(__ia64__) && \ + defined(__ppc64__) && \ + defined(__mips64) && \ + defined(__sparc64__) && \ + defined(__riscv_64e) && \ + defined(__s390x__) + +/* Use eventfd only with 64-bit CPU architectures because eventfd has a + * stringent rule of requiring the 8-byte buffer when calling read(2) and + * write(2) on it. In some rare cases, the C standard library implementation + * on a 32-bit system might choose to define uint64_t as a 32-bit type for + * various reasons (memory limitations, compatibility with older code), + * which makes eventfd broken. + */ +#define USE_EVENTFD 1 + +#define wakeup_write write +#define wakeup_read read +#define wakeup_close close +#define wakeup_create(p,nb) Curl_eventfd(p,nb) + +#include +int Curl_eventfd(curl_socket_t socks[2], bool nonblocking); + +#elif defined(HAVE_PIPE) + +#define wakeup_write write +#define wakeup_read read +#define wakeup_close close +#define wakeup_create(p,nb) Curl_pipe(p,nb) + +#include +int Curl_pipe(curl_socket_t socks[2], bool nonblocking); + +#else /* !USE_EVENTFD && !HAVE_PIPE */ + +#define wakeup_write swrite +#define wakeup_read sread +#define wakeup_close sclose + +#if defined(USE_UNIX_SOCKETS) && defined(HAVE_SOCKETPAIR) +#define SOCKETPAIR_FAMILY AF_UNIX +#elif !defined(HAVE_SOCKETPAIR) +#define SOCKETPAIR_FAMILY 0 /* not used */ +#else +#error "unsupported Unix domain and socketpair build combo" +#endif + +#ifdef SOCK_CLOEXEC +#define SOCKETPAIR_TYPE (SOCK_STREAM | SOCK_CLOEXEC) +#else +#define SOCKETPAIR_TYPE SOCK_STREAM +#endif + +#define wakeup_create(p,nb)\ +Curl_socketpair(SOCKETPAIR_FAMILY, SOCKETPAIR_TYPE, 0, p, nb) + +#endif /* USE_EVENTFD */ + +#ifndef CURL_DISABLE_SOCKETPAIR +#include + +int Curl_socketpair(int domain, int type, int protocol, + curl_socket_t socks[2], bool nonblocking); +#endif + +#endif /* HEADER_CURL_SOCKETPAIR_H */ diff --git a/contrib/curl/lib/socks.c b/contrib/curl/lib/socks.c index 774fb20b0f..733d047eb2 100644 --- a/contrib/curl/lib/socks.c +++ b/contrib/curl/lib/socks.c @@ -5,11 +5,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2016, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -18,6 +18,8 @@ * 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" @@ -34,47 +36,102 @@ #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 #include file should be: */ +/* 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 */ +}; + +#define CURL_SOCKS_BUF_SIZE 600 + +/* make sure we configure it not too low */ +#if CURL_SOCKS_BUF_SIZE < 600 +#error CURL_SOCKS_BUF_SIZE must be at least 600 +#endif + + +struct socks_state { + enum connect_t state; + ssize_t outstanding; /* send this many bytes more */ + unsigned char buffer[CURL_SOCKS_BUF_SIZE]; + 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 behaviour which we frown upon, but right now this - * is what we have... + * This is STUPID BLOCKING behavior. Only used by the SOCKS GSSAPI functions. */ -int Curl_blockread_all(struct connectdata *conn, /* connection data */ - curl_socket_t sockfd, /* read from this socket */ +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; + ssize_t nread = 0; ssize_t allread = 0; int result; - time_t timeleft; + CURLcode err = CURLE_OK; + *n = 0; for(;;) { - timeleft = Curl_timeleft(conn->data, NULL, TRUE); - if(timeleft < 0) { + timediff_t timeout_ms = Curl_timeleft(data, NULL, TRUE); + if(timeout_ms < 0) { /* we already got the timeout */ result = CURLE_OPERATION_TIMEDOUT; break; } - if(SOCKET_READABLE(sockfd, timeleft) <= 0) { + if(!timeout_ms) + timeout_ms = TIMEDIFF_T_MAX; + if(SOCKET_READABLE(cf->conn->sock[cf->sockindex], timeout_ms) <= 0) { result = ~CURLE_OK; break; } - result = Curl_read_plain(sockfd, buf, buffersize, &nread); - if(CURLE_AGAIN == result) - continue; - else if(result) - break; + nread = Curl_conn_cf_recv(cf->next, data, buf, buffersize, &err); + if(nread <= 0) { + result = (int)err; + if(CURLE_AGAIN == err) + continue; + if(err) { + break; + } + } if(buffersize == nread) { allread += nread; @@ -93,91 +150,235 @@ int Curl_blockread_all(struct connectdata *conn, /* connection data */ } 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) + /* do not 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, FALSE, &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, "SOCKS: 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 : -* http://socks.permeo.com/protocol/socks4.protocol +* https://www.openssh.com/txt/socks4.protocol * * Note : * Set protocol4a=true for "SOCKS 4A (Simple Extension to SOCKS 4 Protocol)" * Nonsupport "Identification Protocol (RFC1413)" */ -CURLcode Curl_SOCKS4(const char *proxy_name, - const char *hostname, - int remote_port, - int sockindex, - struct connectdata *conn) +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; -#define SOCKS4REQLEN 262 - unsigned char socksreq[SOCKS4REQLEN]; /* room for SOCKS4 request incl. user - id */ - int result; - CURLcode code; - curl_socket_t sock = conn->sock[sockindex]; - struct Curl_easy *data = conn->data; - - if(Curl_timeleft(data, NULL, TRUE) < 0) { - /* time-out, bail out, go home */ - failf(data, "Connection time-out"); - return CURLE_OPERATION_TIMEDOUT; - } - - if(conn->bits.httpproxy) - infof(conn->data, "SOCKS4%s: connecting to HTTP proxy %s port %d\n", - protocol4a ? "a" : "", hostname, remote_port); - - (void)curlx_nonblock(sock, FALSE); + unsigned char *socksreq = sx->buffer; + CURLcode result; + CURLproxycode presult; + struct Curl_dns_entry *dns = NULL; + + 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); - infof(data, "SOCKS4 communication to %s:%d\n", hostname, 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)((remote_port >> 8) & 0xff); /* PORT MSB */ - socksreq[3] = (unsigned char)(remote_port & 0xff); /* PORT LSB */ - - /* DNS resolve only for SOCKS4, not SOCKS4a */ - if(!protocol4a) { - struct Curl_dns_entry *dns; - Curl_addrinfo *hp=NULL; - int rc; + /* + * Compose socks4 request + * + * Request format + * + * +----+----+----+----+----+----+----+----+----+----+....+----+ + * | VN | CD | DSTPORT | DSTIP | USERID |NULL| + * +----+----+----+----+----+----+----+----+----+----+....+----+ + * # of bytes: 1 1 2 4 variable 1 + */ - rc = Curl_resolv(conn, hostname, remote_port, &dns); + 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; + } - if(rc == CURLRESOLV_ERROR) - return CURLE_COULDNT_RESOLVE_PROXY; + /* socks4a does not resolve anything locally */ + sxstate(sx, data, CONNECT_REQ_INIT); + goto CONNECT_REQ_INIT; - if(rc == CURLRESOLV_PENDING) - /* ignores the return code, but 'dns' remains NULL on failure */ - (void)Curl_resolver_wait_resolv(conn, &dns); + case CONNECT_RESOLVING: + /* check if we have the name resolved by now */ + dns = Curl_fetch_addr(data, sx->hostname, conn->primary.remote_port); + if(dns) { +#ifdef CURLRES_ASYNCH + data->state.async.dns = dns; + data->state.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(); + case CONNECT_RESOLVED: +CONNECT_RESOLVED: + { + struct Curl_addrinfo *hp = NULL; /* - * We cannot use 'hostent' as a struct that Curl_resolv() returns. It + * 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; - if(hp) { - char buf[64]; - Curl_printable_address(hp, buf, sizeof(buf)); + if(dns) { + hp = dns->addr; + + /* scan for the first IPv4 address */ + while(hp && (hp->ai_family != AF_INET)) + hp = hp->ai_next; - if(hp->ai_family == AF_INET) { + 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]; @@ -185,180 +386,183 @@ CURLcode Curl_SOCKS4(const char *proxy_name, 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)\n", buf); - } - else { - hp = NULL; /* fail! */ + infof(data, "SOCKS4 connect to IPv4 %s (locally resolved)", buf); - failf(data, "SOCKS4 connection to %s not supported\n", buf); + Curl_resolv_unlink(data, &dns); /* not used anymore from now on */ } - - Curl_resolv_unlock(data, dns); /* not used anymore from now on */ + else + failf(data, "SOCKS4 connection to %s not supported", sx->hostname); } - if(!hp) { + else failf(data, "Failed to resolve \"%s\" for SOCKS4 connect.", - hostname); - return CURLE_COULDNT_RESOLVE_HOST; - } - } + sx->hostname); - /* - * This is currently not supporting "Identification Protocol (RFC1413)". - */ - socksreq[8] = 0; /* ensure empty userid is NUL-terminated */ - if(proxy_name) { - size_t plen = strlen(proxy_name); - if(plen >= sizeof(socksreq) - 8) { - failf(data, "Too long SOCKS proxy name, can't use!\n"); - return CURLE_COULDNT_CONNECT; - } - /* copy the proxy name WITH trailing zero */ - memcpy(socksreq + 8, proxy_name, plen+1); + if(!hp) + return CURLPX_RESOLVE_HOST; } - - /* - * Make connection - */ - { - ssize_t actualread; - ssize_t written; - ssize_t hostnamelen = 0; - int packetsize = 9 + - (int)strlen((char *)socksreq + 8); /* size including NUL */ - - /* If SOCKS4a, set special invalid IP address 0.0.0.x */ - if(protocol4a) { - socksreq[4] = 0; - socksreq[5] = 0; - socksreq[6] = 0; - socksreq[7] = 1; - /* If still enough room in buffer, also append hostname */ - hostnamelen = (ssize_t)strlen(hostname) + 1; /* length including NUL */ - if(packetsize + hostnamelen <= SOCKS4REQLEN) - strcpy((char *)socksreq + packetsize, hostname); - else - hostnamelen = 0; /* Flag: hostname did not fit in buffer */ + FALLTHROUGH(); + case CONNECT_REQ_INIT: +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 username"); + return CURLPX_LONG_USER; + } + /* copy the proxy name WITH trailing zero */ + memcpy(socksreq + 8, sx->proxy_user, plen + 1); } - /* Send request */ - code = Curl_write_plain(conn, sock, (char *)socksreq, - packetsize + hostnamelen, - &written); - if(code || (written != packetsize + hostnamelen)) { - failf(data, "Failed to send SOCKS4 connect request."); - return CURLE_COULDNT_CONNECT; - } - if(protocol4a && hostnamelen == 0) { - /* SOCKS4a with very long hostname - send that name separately */ - hostnamelen = (ssize_t)strlen(hostname) + 1; - code = Curl_write_plain(conn, sock, (char *)hostname, hostnamelen, - &written); - if(code || (written != hostnamelen)) { - failf(data, "Failed to send SOCKS4 connect request."); - return CURLE_COULDNT_CONNECT; + /* + * 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 < sizeof(sx->buffer))) + strcpy((char *)socksreq + packetsize, sx->hostname); + else { + failf(data, "SOCKS4: too long hostname"); + return CURLPX_LONG_HOSTNAME; + } + packetsize += hostnamelen; } + sx->outp = socksreq; + DEBUGASSERT(packetsize <= sizeof(sx->buffer)); + 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); - packetsize = 8; /* receive data size */ - + FALLTHROUGH(); + case CONNECT_SOCKS_READ: /* Receive response */ - result = Curl_blockread_all(conn, sock, (char *)socksreq, packetsize, - &actualread); - if(result || (actualread != packetsize)) { - failf(data, "Failed to receive SOCKS4 connect request ack."); - return CURLE_COULDNT_CONNECT; + 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] != 0) { - failf(data, - "SOCKS4 reply has wrong version, version should be 4."); - return CURLE_COULDNT_CONNECT; - } + /* + * 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 + */ - /* Result */ - switch(socksreq[1]) { - case 90: - infof(data, "SOCKS4%s request granted.\n", protocol4a?"a":""); - break; - case 91: - failf(data, - "Can't complete SOCKS4 connection to %d.%d.%d.%d:%d. (%d)" - ", request rejected or failed.", - (unsigned char)socksreq[4], (unsigned char)socksreq[5], - (unsigned char)socksreq[6], (unsigned char)socksreq[7], - (((unsigned char)socksreq[8] << 8) | (unsigned char)socksreq[9]), - (unsigned char)socksreq[1]); - return CURLE_COULDNT_CONNECT; - 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.", - (unsigned char)socksreq[4], (unsigned char)socksreq[5], - (unsigned char)socksreq[6], (unsigned char)socksreq[7], - (((unsigned char)socksreq[8] << 8) | (unsigned char)socksreq[9]), - (unsigned char)socksreq[1]); - return CURLE_COULDNT_CONNECT; - 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.", - (unsigned char)socksreq[4], (unsigned char)socksreq[5], - (unsigned char)socksreq[6], (unsigned char)socksreq[7], - (((unsigned char)socksreq[8] << 8) | (unsigned char)socksreq[9]), - (unsigned char)socksreq[1]); - return CURLE_COULDNT_CONNECT; - default: - failf(data, - "Can't complete SOCKS4 connection to %d.%d.%d.%d:%d. (%d)" - ", Unknown.", - (unsigned char)socksreq[4], (unsigned char)socksreq[5], - (unsigned char)socksreq[6], (unsigned char)socksreq[7], - (((unsigned char)socksreq[8] << 8) | (unsigned char)socksreq[9]), - (unsigned char)socksreq[1]); - return CURLE_COULDNT_CONNECT; - } + /* wrong version ? */ + if(socksreq[0]) { + failf(data, + "SOCKS4 reply has wrong version, version should be 0."); + return CURLPX_BAD_VERSION; } - (void)curlx_nonblock(sock, TRUE); + /* Result */ + switch(socksreq[1]) { + case 90: + infof(data, "SOCKS4%s request granted.", protocol4a ? "a" : ""); + break; + case 91: + failf(data, + "cannot 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, + "cannot 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, + "cannot 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, + "cannot 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 CURLE_OK; /* Proxy was successful! */ + return CURLPX_OK; /* Proxy was successful! */ } /* * This function logs in to a SOCKS5 proxy and sends the specifics to the final * destination server. */ -CURLcode Curl_SOCKS5(const char *proxy_name, - const char *proxy_password, - const char *hostname, - int remote_port, - int sockindex, - struct connectdata *conn) +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 + According to the RFC1928, section "6. Replies". This is what a SOCK5 replies: +----+-----+-------+------+----------+----------+ @@ -373,134 +577,151 @@ CURLcode Curl_SOCKS5(const char *proxy_name, o REP Reply field: o X'00' succeeded */ - - unsigned char socksreq[600]; /* room for large user/pw (255 max each) */ - ssize_t actualread; - ssize_t written; - int result; - CURLcode code; - curl_socket_t sock = conn->sock[sockindex]; - struct Curl_easy *data = conn->data; - time_t timeout; + struct connectdata *conn = cf->conn; + unsigned char *socksreq = sx->buffer; + size_t idx; + CURLcode result; + CURLproxycode presult; bool socks5_resolve_local = (conn->socks_proxy.proxytype == CURLPROXY_SOCKS5) ? TRUE : FALSE; - const size_t hostname_len = strlen(hostname); - ssize_t len = 0; - - if(conn->bits.httpproxy) - infof(conn->data, "SOCKS5: connecting to HTTP proxy %s port %d\n", - hostname, remote_port); - - /* RFC1928 chapter 5 specifies max 255 chars for domain name in packet */ - if(!socks5_resolve_local && hostname_len > 255) { - infof(conn->data, "SOCKS5: server resolving disabled for hostnames of " - "length > 255 [actual len=%zu]\n", hostname_len); - socks5_resolve_local = TRUE; - } - - /* get timeout */ - timeout = Curl_timeleft(data, NULL, TRUE); - - if(timeout < 0) { - /* time-out, bail out, go home */ - failf(data, "Connection time-out"); - return CURLE_OPERATION_TIMEDOUT; - } - - (void)curlx_nonblock(sock, TRUE); - - /* wait until socket gets connected */ - result = SOCKET_WRITABLE(sock, timeout); - - if(-1 == result) { - failf(conn->data, "SOCKS5: no connection here"); - return CURLE_COULDNT_CONNECT; - } - else if(0 == result) { - failf(conn->data, "SOCKS5: connection timeout"); - return CURLE_OPERATION_TIMEDOUT; - } - - if(result & CURL_CSELECT_ERR) { - failf(conn->data, "SOCKS5: error occurred during connection"); - return CURLE_COULDNT_CONNECT; - } + const size_t hostname_len = strlen(sx->hostname); + size_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; + } - socksreq[0] = 5; /* version */ + 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) - socksreq[1] = (char)(proxy_name ? 3 : 2); /* number of methods (below) */ - socksreq[2] = 0; /* no authentication */ - socksreq[3] = 1; /* GSS-API */ - socksreq[4] = 2; /* username/password */ -#else - socksreq[1] = (char)(proxy_name ? 2 : 1); /* number of methods (below) */ - socksreq[2] = 0; /* no authentication */ - socksreq[3] = 2; /* username/password */ + if(auth & CURLAUTH_GSSAPI) + allow_gssapi = TRUE; #endif - (void)curlx_nonblock(sock, FALSE); - - infof(data, "SOCKS5 communication to %s:%d\n", hostname, remote_port); - - code = Curl_write_plain(conn, sock, (char *)socksreq, (2 + (int)socksreq[1]), - &written); - if(code || (written != (2 + (int)socksreq[1]))) { - failf(data, "Unable to send initial SOCKS5 request."); - return CURLE_COULDNT_CONNECT; - } - - (void)curlx_nonblock(sock, TRUE); - - result = SOCKET_READABLE(sock, timeout); - - if(-1 == result) { - failf(conn->data, "SOCKS5 nothing to read"); - return CURLE_COULDNT_CONNECT; - } - else if(0 == result) { - failf(conn->data, "SOCKS5 read timeout"); - return CURLE_OPERATION_TIMEDOUT; - } - - if(result & CURL_CSELECT_ERR) { - failf(conn->data, "SOCKS5 read error occurred"); - return CURLE_RECV_ERROR; - } - - (void)curlx_nonblock(sock, FALSE); - - result=Curl_blockread_all(conn, sock, (char *)socksreq, 2, &actualread); - if(result || (actualread != 2)) { - failf(data, "Unable to receive initial SOCKS5 response."); - return CURLE_COULDNT_CONNECT; - } - - if(socksreq[0] != 5) { - failf(data, "Received invalid version in initial SOCKS5 response."); - return CURLE_COULDNT_CONNECT; - } - if(socksreq[1] == 0) { - /* Nothing to do, no authentication needed */ - ; - } + 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; + DEBUGASSERT(idx <= sizeof(sx->buffer)); + 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(); + case CONNECT_SOCKS_READ_INIT: +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(socksreq[1] == 1) { - code = Curl_SOCKS5_gssapi_negotiate(sockindex, conn); - if(code) { - failf(data, "Unable to negotiate SOCKS5 GSS-API context."); - return CURLE_COULDNT_CONNECT; + 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 if(socksreq[1] == 2) { - /* Needs user name and password */ - size_t proxy_name_len, proxy_password_len; - if(proxy_name && proxy_password) { - proxy_name_len = strlen(proxy_name); - proxy_password_len = strlen(proxy_password); + 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 username 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_name_len = 0; + proxy_user_len = 0; proxy_password_len = 0; } @@ -513,272 +734,544 @@ CURLcode Curl_SOCKS5(const char *proxy_name, */ len = 0; socksreq[len++] = 1; /* username/pw subnegotiation version */ - socksreq[len++] = (unsigned char) proxy_name_len; - if(proxy_name && proxy_name_len) - memcpy(socksreq + len, proxy_name, proxy_name_len); - len += proxy_name_len; + 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 username 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(proxy_password && proxy_password_len) - memcpy(socksreq + len, proxy_password, 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; - - code = Curl_write_plain(conn, sock, (char *)socksreq, len, &written); - if(code || (len != written)) { - failf(data, "Failed to send SOCKS5 sub-negotiation request."); - return CURLE_COULDNT_CONNECT; + sxstate(sx, data, CONNECT_AUTH_SEND); + DEBUGASSERT(len <= sizeof(sx->buffer)); + 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; } - - result=Curl_blockread_all(conn, sock, (char *)socksreq, 2, &actualread); - if(result || (actualread != 2)) { - failf(data, "Unable to receive SOCKS5 sub-negotiation response."); - return CURLE_COULDNT_CONNECT; + 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 */ - if(socksreq[1] != 0) { /* status */ + else if(socksreq[1]) { /* status */ failf(data, "User was rejected by the SOCKS5 server (%d %d).", socksreq[0], socksreq[1]); - return CURLE_COULDNT_CONNECT; + return CURLPX_USER_REJECTED; } /* Everything is good so far, user was authenticated! */ - } - else { - /* error */ -#if defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI) - if(socksreq[1] == 255) { -#else - if(socksreq[1] == 1) { - failf(data, - "SOCKS5 GSSAPI per-message authentication is not supported."); - return CURLE_COULDNT_CONNECT; - } - else if(socksreq[1] == 255) { -#endif - if(!proxy_name || !*proxy_name) { - failf(data, - "No authentication method was acceptable. (It is quite likely" - " that the SOCKS5 server wanted a username/password, since none" - " was supplied to the server on this connection.)"); + sxstate(sx, data, CONNECT_REQ_INIT); + FALLTHROUGH(); + case CONNECT_REQ_INIT: +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; } - else { - failf(data, "No authentication method was acceptable."); - } - return CURLE_COULDNT_CONNECT; + sxstate(sx, data, CONNECT_RESOLVED); + goto CONNECT_RESOLVED; } - else { - failf(data, - "Undocumented SOCKS5 mode attempted to be used by server."); - return CURLE_COULDNT_CONNECT; - } - } + goto 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) { - socksreq[len++] = 3; /* ATYP: domain name = 3 */ - socksreq[len++] = (char) hostname_len; /* address length */ - memcpy(&socksreq[len], hostname, hostname_len); /* address str w/o NULL */ - len += hostname_len; - } - else { - struct Curl_dns_entry *dns; - Curl_addrinfo *hp = NULL; - int rc = Curl_resolv(conn, hostname, remote_port, &dns); - - if(rc == CURLRESOLV_ERROR) - return CURLE_COULDNT_RESOLVE_HOST; + case CONNECT_RESOLVING: + /* check if we have the name resolved by now */ + dns = Curl_fetch_addr(data, sx->hostname, sx->remote_port); - if(rc == CURLRESOLV_PENDING) { - /* this requires that we're in "wait for resolve" state */ - code = Curl_resolver_wait_resolv(conn, &dns); - if(code) - return code; + if(dns) { +#ifdef CURLRES_ASYNCH + data->state.async.dns = dns; + data->state.async.done = TRUE; +#endif + infof(data, "SOCKS5: hostname '%s' found", sx->hostname); } - /* - * 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) { + result = Curl_resolv_check(data, &dns); + if(!dns) { + if(result) + return CURLPX_RESOLVE_HOST; + return CURLPX_OK; + } + } + FALLTHROUGH(); + case CONNECT_RESOLVED: +CONNECT_RESOLVED: + { + char dest[MAX_IPADR_LEN]; /* printable address */ + struct Curl_addrinfo *hp = NULL; if(dns) - hp=dns->addr; - if(hp) { + hp = dns->addr; +#ifdef USE_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; - char buf[64]; - Curl_printable_address(hp, buf, sizeof(buf)); + struct sockaddr_in *saddr_in; + socksreq[len++] = 1; /* ATYP: IPv4 = 1 */ - if(hp->ai_family == AF_INET) { - 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]; + } - 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 USE_IPV6 + else if(hp->ai_family == AF_INET6) { + int i; + struct sockaddr_in6 *saddr_in6; + socksreq[len++] = 4; /* ATYP: IPv6 = 4 */ - infof(data, "SOCKS5 connect to IPv4 %s (locally resolved)\n", buf); + 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]; } -#ifdef ENABLE_IPV6 - else if(hp->ai_family == AF_INET6) { - 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 IPv6 %s (locally resolved)\n", buf); + 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_unlink(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 USE_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 { - hp = NULL; /* fail! */ - - failf(data, "SOCKS5 connection to %s not supported\n", buf); + 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(); - Curl_resolv_unlock(data, dns); /* not used anymore from now on */ + case CONNECT_REQ_SEND: +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; } - if(!hp) { - failf(data, "Failed to resolve \"%s\" for SOCKS5 connect.", - hostname); - return CURLE_COULDNT_RESOLVE_HOST; +#endif + sx->outp = socksreq; + DEBUGASSERT(len <= sizeof(sx->buffer)); + 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, "cannot 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; } - } - socksreq[len++] = (unsigned char)((remote_port >> 8) & 0xff); /* PORT MSB */ - socksreq[len++] = (unsigned char)(remote_port & 0xff); /* PORT LSB */ + /* 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) { - failf(data, "SOCKS5 GSS-API protection not yet implemented."); - } - else + if(!conn->socks5_gssapi_enctype) { + /* decrypt_gssapi_blockread already read the whole packet */ #endif - code = Curl_write_plain(conn, sock, (char *)socksreq, len, &written); - - if(code || (len != written)) { - failf(data, "Failed to send SOCKS5 connect request."); - return CURLE_COULDNT_CONNECT; + if(len > 10) { + DEBUGASSERT(len <= sizeof(sx->buffer)); + 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."); - len = 10; /* minimum packet size is 10 */ + return CURLPX_OK; /* Proxy was successful! */ +} -#if defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI) - if(conn->socks5_gssapi_enctype) { - failf(data, "SOCKS5 GSS-API protection not yet implemented."); +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; } - else -#endif - result = Curl_blockread_all(conn, sock, (char *)socksreq, - len, &actualread); - if(result || (len != actualread)) { - failf(data, "Failed to receive SOCKS5 connect request ack."); - return CURLE_COULDNT_CONNECT; + 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; } +} - if(socksreq[0] != 5) { /* version */ - failf(data, - "SOCKS5 reply has wrong version, version should be 5."); - return CURLE_COULDNT_CONNECT; +/* After a TCP connection to the proxy has been verified, this function does + the next magic steps. If 'done' is not set TRUE, it is not done yet and + must be called again. + + Note: this function's sub-functions call failf() + +*/ +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; } - /* 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. + result = cf->next->cft->do_connect(cf->next, data, blocking, done); + if(result || !*done) + return result; - +----+-----+-------+------+----------+----------+ - |VER | REP | RSV | ATYP | BND.ADDR | BND.PORT | - +----+-----+-------+------+----------+----------+ - | 1 | 1 | X'00' | 1 | Variable | 2 | - +----+-----+-------+------+----------+----------+ + if(!sx) { + sx = calloc(1, sizeof(*sx)); + if(!sx) + return CURLE_OUT_OF_MEMORY; + cf->ctx = sx; + } - 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 + 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) */ - - /* Calculate real packet size */ - if(socksreq[3] == 3) { - /* domain name */ - int addrlen = (int) socksreq[4]; - len = 5 + addrlen + 2; + 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; } - else if(socksreq[3] == 4) { - /* IPv6 */ - len = 4 + 16 + 2; + + result = connect_SOCKS(cf, sx, data); + if(!result && sx->state == CONNECT_DONE) { + cf->connected = TRUE; + Curl_verboseconnect(data, conn, cf->sockindex); + socks_proxy_cf_free(cf); } - /* 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) { - result = Curl_blockread_all(conn, sock, (char *)&socksreq[10], - len - 10, &actualread); - if(result || ((len - 10) != actualread)) { - failf(data, "Failed to receive SOCKS5 connect request ack."); - return CURLE_COULDNT_CONNECT; - } + *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; } -#if defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI) } -#endif +} - if(socksreq[1] != 0) { /* Anything besides 0 is an error */ - if(socksreq[3] == 1) { - failf(data, - "Can't complete SOCKS5 connection to %d.%d.%d.%d:%d. (%d)", - (unsigned char)socksreq[4], (unsigned char)socksreq[5], - (unsigned char)socksreq[6], (unsigned char)socksreq[7], - (((unsigned char)socksreq[8] << 8) | - (unsigned char)socksreq[9]), - (unsigned char)socksreq[1]); - } - else if(socksreq[3] == 3) { - unsigned char port_upper = (unsigned char)socksreq[len - 2]; - socksreq[len - 2] = 0; - failf(data, - "Can't complete SOCKS5 connection to %s:%d. (%d)", - (char *)&socksreq[5], - ((port_upper << 8) | - (unsigned char)socksreq[len - 1]), - (unsigned char)socksreq[1]); - socksreq[len - 2] = port_upper; - } - else if(socksreq[3] == 4) { - failf(data, - "Can't complete SOCKS5 connection to %02x%02x:%02x%02x:" - "%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%d. (%d)", - (unsigned char)socksreq[4], (unsigned char)socksreq[5], - (unsigned char)socksreq[6], (unsigned char)socksreq[7], - (unsigned char)socksreq[8], (unsigned char)socksreq[9], - (unsigned char)socksreq[10], (unsigned char)socksreq[11], - (unsigned char)socksreq[12], (unsigned char)socksreq[13], - (unsigned char)socksreq[14], (unsigned char)socksreq[15], - (unsigned char)socksreq[16], (unsigned char)socksreq[17], - (unsigned char)socksreq[18], (unsigned char)socksreq[19], - (((unsigned char)socksreq[20] << 8) | - (unsigned char)socksreq[21]), - (unsigned char)socksreq[1]); - } - return CURLE_COULDNT_CONNECT; +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 { - infof(data, "SOCKS5 request granted.\n"); + cf->next->cft->get_host(cf->next, data, phost, pdisplay_host, pport); } +} - (void)curlx_nonblock(sock, TRUE); - return CURLE_OK; /* Proxy was successful! */ +struct Curl_cftype Curl_cft_socks_proxy = { + "SOCKS-PROXYY", + CF_TYPE_IP_CONNECT|CF_TYPE_PROXY, + 0, + socks_proxy_cf_destroy, + socks_proxy_cf_connect, + socks_proxy_cf_close, + Curl_cf_def_shutdown, + socks_cf_get_host, + socks_cf_adjust_pollset, + Curl_cf_def_data_pending, + 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/contrib/curl/lib/socks.h b/contrib/curl/lib/socks.h index 348707e74d..a3adcc6e91 100644 --- a/contrib/curl/lib/socks.h +++ b/contrib/curl/lib/socks.h @@ -7,11 +7,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2011, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -20,6 +20,8 @@ * 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" @@ -27,50 +29,33 @@ #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 behaviour which we frown upon, but right now this - * is what we have... + * This is STUPID BLOCKING behavior */ -int Curl_blockread_all(struct connectdata *conn, - curl_socket_t sockfd, +int Curl_blockread_all(struct Curl_cfilter *cf, + struct Curl_easy *data, char *buf, ssize_t buffersize, ssize_t *n); -/* - * This function logs in to a SOCKS4(a) proxy and sends the specifics to the - * final destination server. - */ -CURLcode Curl_SOCKS4(const char *proxy_name, - const char *hostname, - int remote_port, - int sockindex, - struct connectdata *conn); - -/* - * This function logs in to a SOCKS5 proxy and sends the specifics to the - * final destination server. - */ -CURLcode Curl_SOCKS5(const char *proxy_name, - const char *proxy_password, - const char *hostname, - int remote_port, - int sockindex, - struct connectdata *conn); - #if defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI) /* - * This function handles the SOCKS5 GSS-API negotiation and initialisation + * This function handles the SOCKS5 GSS-API negotiation and initialization */ -CURLcode Curl_SOCKS5_gssapi_negotiate(int sockindex, - struct connectdata *conn); +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/contrib/curl/lib/socks_gssapi.c b/contrib/curl/lib/socks_gssapi.c index 54d0635049..f6fd55dcec 100644 --- a/contrib/curl/lib/socks_gssapi.c +++ b/contrib/curl/lib/socks_gssapi.c @@ -5,12 +5,12 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 2009, 2011, Markus Moeller, - * Copyright (C) 2012 - 2016, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. + * Copyright (C) Markus Moeller, * * This software is licensed 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. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -19,6 +19,8 @@ * 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" @@ -28,16 +30,20 @@ #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" +#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_GSS_LEN 1024 + static gss_ctx_id_t gss_context = GSS_C_NO_CONTEXT; /* @@ -51,11 +57,10 @@ static int check_gss_err(struct Curl_easy *data, if(GSS_ERROR(major_status)) { OM_uint32 maj_stat, min_stat; OM_uint32 msg_ctx = 0; - gss_buffer_desc status_string; - char buf[1024]; - size_t len; + gss_buffer_desc status_string = GSS_C_EMPTY_BUFFER; + struct dynbuf dbuf; - len = 0; + Curl_dyn_init(&dbuf, MAX_GSS_LEN); msg_ctx = 0; while(!msg_ctx) { /* convert major status code (GSS-API error) to text */ @@ -64,19 +69,16 @@ static int check_gss_err(struct Curl_easy *data, 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; - } + if(Curl_dyn_addn(&dbuf, status_string.value, + status_string.length)) + return 1; /* error */ 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; - } + if(Curl_dyn_addn(&dbuf, ".\n", 2)) + return 1; /* error */ msg_ctx = 0; while(!msg_ctx) { /* convert minor status code (underlying routine error) to text */ @@ -85,28 +87,30 @@ static int check_gss_err(struct Curl_easy *data, 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); + if(Curl_dyn_addn(&dbuf, status_string.value, + status_string.length)) + return 1; /* error */ gss_release_buffer(&min_stat, &status_string); break; } gss_release_buffer(&min_stat, &status_string); } - failf(data, "GSS-API error: %s failed:\n%s", function, buf); + failf(data, "GSS-API error: %s failed: %s", function, Curl_dyn_ptr(&dbuf)); + Curl_dyn_free(&dbuf); return 1; } return 0; } -CURLcode Curl_SOCKS5_gssapi_negotiate(int sockindex, - struct connectdata *conn) +CURLcode Curl_SOCKS5_gssapi_negotiate(struct Curl_cfilter *cf, + struct Curl_easy *data) { - struct Curl_easy *data = conn->data; - curl_socket_t sock = conn->sock[sockindex]; + struct connectdata *conn = cf->conn; + curl_socket_t sock = conn->sock[cf->sockindex]; CURLcode code; ssize_t actualread; - ssize_t written; + ssize_t nwritten; int result; OM_uint32 gss_major_status, gss_minor_status, gss_status; OM_uint32 gss_ret_flags; @@ -115,11 +119,11 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(int sockindex, 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_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; + 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"; @@ -136,22 +140,22 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(int sockindex, /* prepare service name */ if(strchr(serviceptr, '/')) { service.length = serviceptr_length; - service.value = malloc(service.length); + service.value = Curl_memdup(serviceptr, 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); + 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; - snprintf(service.value, service.length+1, "%s@%s", - serviceptr, conn->socks_proxy.host.name); + 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); @@ -166,7 +170,9 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(int sockindex, return CURLE_COULDNT_CONNECT; } - /* As long as we need to keep sending some context info, and there's no */ + (void)curlx_nonblock(sock, FALSE); + + /* As long as we need to keep sending some context info, and there is no */ /* errors, keep sending it... */ for(;;) { gss_major_status = Curl_gss_init_sec_context(data, @@ -192,14 +198,15 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(int sockindex, return CURLE_COULDNT_CONNECT; } - if(gss_send_token.length != 0) { + 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)); + us_length = htons((unsigned short)gss_send_token.length); + memcpy(socksreq + 2, &us_length, sizeof(short)); - code = Curl_write_plain(conn, sock, (char *)socksreq, 4, &written); - if(code || (4 != written)) { + nwritten = Curl_conn_cf_send(cf->next, data, (char *)socksreq, 4, + FALSE, &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); @@ -208,10 +215,10 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(int sockindex, return CURLE_COULDNT_CONNECT; } - code = Curl_write_plain(conn, sock, (char *)gss_send_token.value, - gss_send_token.length, &written); - - if(code || ((ssize_t)gss_send_token.length != written)) { + nwritten = Curl_conn_cf_send(cf->next, data, + (char *)gss_send_token.value, + gss_send_token.length, FALSE, &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); @@ -224,7 +231,8 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(int sockindex, 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; + if(gss_major_status != GSS_S_CONTINUE_NEEDED) + break; /* analyse response */ @@ -236,7 +244,7 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(int sockindex, * +----+------+-----+----------------+ */ - result=Curl_blockread_all(conn, sock, (char *)socksreq, 4, &actualread); + 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); @@ -253,7 +261,7 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(int sockindex, return CURLE_COULDNT_CONNECT; } - if(socksreq[1] != 1) { /* status / messgae type */ + 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); @@ -261,11 +269,11 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(int sockindex, return CURLE_COULDNT_CONNECT; } - memcpy(&us_length, socksreq+2, sizeof(short)); + 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); + 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 " @@ -275,8 +283,8 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(int sockindex, return CURLE_OUT_OF_MEMORY; } - result=Curl_blockread_all(conn, sock, (char *)gss_recv_token.value, - gss_recv_token.length, &actualread); + 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."); @@ -299,7 +307,7 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(int sockindex, 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."); + failf(data, "Failed to determine username."); return CURLE_COULDNT_CONNECT; } gss_major_status = gss_display_name(&gss_minor_status, gss_client_name, @@ -309,10 +317,10 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(int sockindex, 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."); + failf(data, "Failed to determine username."); return CURLE_COULDNT_CONNECT; } - user=malloc(gss_send_token.length+1); + 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); @@ -324,9 +332,9 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(int sockindex, 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 authencticated user %s with GSS-API.\n",user); + infof(data, "SOCKS5 server authenticated user %s with GSS-API.",user); free(user); - user=NULL; + user = NULL; /* Do encryption */ socksreq[0] = 1; /* GSS-API subnegotiation version */ @@ -340,8 +348,9 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(int sockindex, else if(gss_ret_flags & GSS_C_INTEG_FLAG) gss_enc = 1; - infof(data, "SOCKS5 server supports GSS-API %s data protection.\n", - (gss_enc==0)?"no":((gss_enc==1)?"integrity":"confidentiality")); + 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; /* @@ -370,22 +379,21 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(int sockindex, * * 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()/ + * 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)); + memcpy(socksreq + 2, &us_length, sizeof(short)); } else { gss_send_token.length = 1; - gss_send_token.value = malloc(1); + gss_send_token.value = Curl_memdup(&gss_enc, 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, @@ -400,12 +408,13 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(int sockindex, } gss_release_buffer(&gss_status, &gss_send_token); - us_length = htons((short)gss_w_token.length); - memcpy(socksreq+2, &us_length, sizeof(short)); + us_length = htons((unsigned short)gss_w_token.length); + memcpy(socksreq + 2, &us_length, sizeof(short)); } - code = Curl_write_plain(conn, sock, (char *)socksreq, 4, &written); - if(code || (4 != written)) { + nwritten = Curl_conn_cf_send(cf->next, data, (char *)socksreq, 4, FALSE, + &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); @@ -414,17 +423,19 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(int sockindex, if(data->set.socks5_gssapi_nec) { memcpy(socksreq, &gss_enc, 1); - code = Curl_write_plain(conn, sock, socksreq, 1, &written); - if(code || ( 1 != written)) { + nwritten = Curl_conn_cf_send(cf->next, data, (char *)socksreq, 1, FALSE, + &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 { - code = Curl_write_plain(conn, sock, (char *)gss_w_token.value, - gss_w_token.length, &written); - if(code || ((ssize_t)gss_w_token.length != written)) { + nwritten = Curl_conn_cf_send(cf->next, data, + (char *)gss_w_token.value, + gss_w_token.length, FALSE, &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); @@ -433,7 +444,7 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(int sockindex, gss_release_buffer(&gss_status, &gss_w_token); } - result=Curl_blockread_all(conn, sock, (char *)socksreq, 4, &actualread); + 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); @@ -448,27 +459,27 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(int sockindex, return CURLE_COULDNT_CONNECT; } - if(socksreq[1] != 2) { /* status / messgae type */ + 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)); + 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); + 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(conn, sock, (char *)gss_recv_token.value, - gss_recv_token.length, &actualread); + 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."); + failf(data, "Failed to receive GSS-API encryption type."); gss_release_buffer(&gss_status, &gss_recv_token); gss_delete_sec_context(&gss_status, &gss_context, NULL); return CURLE_COULDNT_CONNECT; @@ -489,7 +500,7 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(int sockindex, gss_release_buffer(&gss_status, &gss_recv_token); if(gss_w_token.length != 1) { - failf(data, "Invalid GSS-API encryption response length (%d).", + 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); @@ -501,7 +512,7 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(int sockindex, } else { if(gss_recv_token.length != 1) { - failf(data, "Invalid GSS-API encryption response length (%d).", + 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); @@ -512,9 +523,12 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(int sockindex, gss_release_buffer(&gss_status, &gss_recv_token); } - infof(data, "SOCKS5 access with%s protection granted.\n", - (socksreq[0]==0)?"out GSS-API data": - ((socksreq[0]==1)?" GSS-API integrity":" GSS-API confidentiality")); + (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) diff --git a/contrib/curl/lib/socks_sspi.c b/contrib/curl/lib/socks_sspi.c index edc73ad2ed..6d8e6ef730 100644 --- a/contrib/curl/lib/socks_sspi.c +++ b/contrib/curl/lib/socks_sspi.c @@ -5,12 +5,12 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 2012 - 2016, Daniel Stenberg, , et al. - * Copyright (C) 2009, 2011, Markus Moeller, + * Copyright (C) Daniel Stenberg, , et al. + * Copyright (C) Markus Moeller, * * This software is licensed 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. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -19,6 +19,8 @@ * 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" @@ -27,6 +29,7 @@ #include "urldata.h" #include "sendf.h" +#include "cfilters.h" #include "connect.h" #include "strerror.h" #include "timeval.h" @@ -43,7 +46,7 @@ /* * Helper sspi error functions. */ -static int check_sspi_err(struct connectdata *conn, +static int check_sspi_err(struct Curl_easy *data, SECURITY_STATUS status, const char *function) { @@ -51,19 +54,20 @@ static int check_sspi_err(struct connectdata *conn, status != SEC_I_COMPLETE_AND_CONTINUE && status != SEC_I_COMPLETE_NEEDED && status != SEC_I_CONTINUE_NEEDED) { - failf(conn->data, "SSPI error: %s failed: %s", function, - Curl_sspi_strerror(conn, status)); + 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(int sockindex, - struct connectdata *conn) +CURLcode Curl_SOCKS5_gssapi_negotiate(struct Curl_cfilter *cf, + struct Curl_easy *data) { - struct Curl_easy *data = conn->data; - curl_socket_t sock = conn->sock[sockindex]; + struct connectdata *conn = cf->conn; + curl_socket_t sock = conn->sock[cf->sockindex]; CURLcode code; ssize_t actualread; ssize_t written; @@ -85,7 +89,7 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(int sockindex, 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"; + data->set.str[STRING_PROXY_SERVICE_NAME] : "rcmd"; const size_t service_length = strlen(service); /* GSS-API request looks like @@ -107,9 +111,9 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(int sockindex, strlen(conn->socks_proxy.host.name) + 2); if(!service_name) return CURLE_OUT_OF_MEMORY; - snprintf(service_name, service_length + - strlen(conn->socks_proxy.host.name)+2, "%s/%s", - service, conn->socks_proxy.host.name); + msnprintf(service_name, service_length + + strlen(conn->socks_proxy.host.name) + 2, "%s/%s", + service, conn->socks_proxy.host.name); } input_desc.cBuffers = 1; @@ -135,7 +139,7 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(int sockindex, cred_handle.dwLower = 0; cred_handle.dwUpper = 0; - status = s_pSecFn->AcquireCredentialsHandle(NULL, + status = Curl_pSecFn->AcquireCredentialsHandle(NULL, (TCHAR *) TEXT("Kerberos"), SECPKG_CRED_OUTBOUND, NULL, @@ -145,23 +149,25 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(int sockindex, &cred_handle, &expiry); - if(check_sspi_err(conn, status, "AcquireCredentialsHandle")) { + if(check_sspi_err(data, status, "AcquireCredentialsHandle")) { failf(data, "Failed to acquire credentials."); free(service_name); - s_pSecFn->FreeCredentialsHandle(&cred_handle); + Curl_pSecFn->FreeCredentialsHandle(&cred_handle); return CURLE_COULDNT_CONNECT; } - /* As long as we need to keep sending some context info, and there's no */ + (void)curlx_nonblock(sock, FALSE); + + /* As long as we need to keep sending some context info, and there is no */ /* errors, keep sending it... */ for(;;) { TCHAR *sname; - sname = Curl_convert_UTF8_to_tchar(service_name); + sname = curlx_convert_UTF8_to_tchar(service_name); if(!sname) return CURLE_OUT_OF_MEMORY; - status = s_pSecFn->InitializeSecurityContext(&cred_handle, + status = Curl_pSecFn->InitializeSecurityContext(&cred_handle, context_handle, sname, ISC_REQ_MUTUAL_AUTH | @@ -177,67 +183,69 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(int sockindex, &sspi_ret_flags, &expiry); - Curl_unicodefree(sname); + curlx_unicodefree(sname); if(sspi_recv_token.pvBuffer) { - s_pSecFn->FreeContextBuffer(sspi_recv_token.pvBuffer); + Curl_pSecFn->FreeContextBuffer(sspi_recv_token.pvBuffer); sspi_recv_token.pvBuffer = NULL; sspi_recv_token.cbBuffer = 0; } - if(check_sspi_err(conn, status, "InitializeSecurityContext")) { + if(check_sspi_err(data, status, "InitializeSecurityContext")) { free(service_name); - s_pSecFn->FreeCredentialsHandle(&cred_handle); - s_pSecFn->DeleteSecurityContext(&sspi_context); + Curl_pSecFn->FreeCredentialsHandle(&cred_handle); + Curl_pSecFn->DeleteSecurityContext(&sspi_context); if(sspi_recv_token.pvBuffer) - s_pSecFn->FreeContextBuffer(sspi_recv_token.pvBuffer); + Curl_pSecFn->FreeContextBuffer(sspi_recv_token.pvBuffer); failf(data, "Failed to initialise security context."); return CURLE_COULDNT_CONNECT; } - if(sspi_send_token.cbBuffer != 0) { + 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)); + us_length = htons((unsigned short)sspi_send_token.cbBuffer); + memcpy(socksreq + 2, &us_length, sizeof(short)); - code = Curl_write_plain(conn, sock, (char *)socksreq, 4, &written); + written = Curl_conn_cf_send(cf->next, data, (char *)socksreq, 4, FALSE, + &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); + Curl_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); + Curl_pSecFn->FreeContextBuffer(sspi_recv_token.pvBuffer); + Curl_pSecFn->FreeCredentialsHandle(&cred_handle); + Curl_pSecFn->DeleteSecurityContext(&sspi_context); return CURLE_COULDNT_CONNECT; } - code = Curl_write_plain(conn, sock, (char *)sspi_send_token.pvBuffer, - sspi_send_token.cbBuffer, &written); + written = Curl_conn_cf_send(cf->next, data, + (char *)sspi_send_token.pvBuffer, + sspi_send_token.cbBuffer, FALSE, &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); + Curl_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); + Curl_pSecFn->FreeContextBuffer(sspi_recv_token.pvBuffer); + Curl_pSecFn->FreeCredentialsHandle(&cred_handle); + Curl_pSecFn->DeleteSecurityContext(&sspi_context); return CURLE_COULDNT_CONNECT; } } if(sspi_send_token.pvBuffer) { - s_pSecFn->FreeContextBuffer(sspi_send_token.pvBuffer); + Curl_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); + Curl_pSecFn->FreeContextBuffer(sspi_recv_token.pvBuffer); sspi_recv_token.pvBuffer = NULL; } sspi_recv_token.cbBuffer = 0; @@ -255,12 +263,12 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(int sockindex, * +----+------+-----+----------------+ */ - result = Curl_blockread_all(conn, sock, (char *)socksreq, 4, &actualread); + 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); + Curl_pSecFn->FreeCredentialsHandle(&cred_handle); + Curl_pSecFn->DeleteSecurityContext(&sspi_context); return CURLE_COULDNT_CONNECT; } @@ -269,21 +277,21 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(int sockindex, 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); + Curl_pSecFn->FreeCredentialsHandle(&cred_handle); + Curl_pSecFn->DeleteSecurityContext(&sspi_context); return CURLE_COULDNT_CONNECT; } - if(socksreq[1] != 1) { /* status / messgae type */ + 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); + Curl_pSecFn->FreeCredentialsHandle(&cred_handle); + Curl_pSecFn->DeleteSecurityContext(&sspi_context); return CURLE_COULDNT_CONNECT; } - memcpy(&us_length, socksreq+2, sizeof(short)); + memcpy(&us_length, socksreq + 2, sizeof(short)); us_length = ntohs(us_length); sspi_recv_token.cbBuffer = us_length; @@ -291,20 +299,20 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(int sockindex, if(!sspi_recv_token.pvBuffer) { free(service_name); - s_pSecFn->FreeCredentialsHandle(&cred_handle); - s_pSecFn->DeleteSecurityContext(&sspi_context); + Curl_pSecFn->FreeCredentialsHandle(&cred_handle); + Curl_pSecFn->DeleteSecurityContext(&sspi_context); return CURLE_OUT_OF_MEMORY; } - result = Curl_blockread_all(conn, sock, (char *)sspi_recv_token.pvBuffer, + 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); + Curl_pSecFn->FreeContextBuffer(sspi_recv_token.pvBuffer); + Curl_pSecFn->FreeCredentialsHandle(&cred_handle); + Curl_pSecFn->DeleteSecurityContext(&sspi_context); return CURLE_COULDNT_CONNECT; } @@ -314,19 +322,25 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(int sockindex, free(service_name); /* Everything is good so far, user was authenticated! */ - status = s_pSecFn->QueryCredentialsAttributes(&cred_handle, + status = Curl_pSecFn->QueryCredentialsAttributes(&cred_handle, SECPKG_CRED_ATTR_NAMES, &names); - s_pSecFn->FreeCredentialsHandle(&cred_handle); - if(check_sspi_err(conn, status, "QueryCredentialAttributes")) { - s_pSecFn->DeleteSecurityContext(&sspi_context); - s_pSecFn->FreeContextBuffer(names.sUserName); - failf(data, "Failed to determine user name."); + Curl_pSecFn->FreeCredentialsHandle(&cred_handle); + if(check_sspi_err(data, status, "QueryCredentialAttributes")) { + Curl_pSecFn->DeleteSecurityContext(&sspi_context); + Curl_pSecFn->FreeContextBuffer(names.sUserName); + failf(data, "Failed to determine username."); return CURLE_COULDNT_CONNECT; } - infof(data, "SOCKS5 server authencticated user %s with GSS-API.\n", - names.sUserName); - s_pSecFn->FreeContextBuffer(names.sUserName); + else { +#ifndef CURL_DISABLE_VERBOSE_STRINGS + char *user_utf8 = curlx_convert_tchar_to_UTF8(names.sUserName); + infof(data, "SOCKS5 server authenticated user %s with GSS-API.", + (user_utf8 ? user_utf8 : "(unknown)")); + curlx_unicodefree(user_utf8); +#endif + Curl_pSecFn->FreeContextBuffer(names.sUserName); + } /* Do encryption */ socksreq[0] = 1; /* GSS-API subnegotiation version */ @@ -340,8 +354,9 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(int sockindex, else if(sspi_ret_flags & ISC_REQ_INTEGRITY) gss_enc = 1; - infof(data, "SOCKS5 server supports GSS-API %s data protection.\n", - (gss_enc==0)?"no":((gss_enc==1)?"integrity":"confidentiality") ); + 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; /* @@ -370,21 +385,21 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(int sockindex, * * 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()/ + * 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)); + us_length = htons((unsigned short)1); + memcpy(socksreq + 2, &us_length, sizeof(short)); } else { - status = s_pSecFn->QueryContextAttributes(&sspi_context, + status = Curl_pSecFn->QueryContextAttributes(&sspi_context, SECPKG_ATTR_SIZES, &sspi_sizes); - if(check_sspi_err(conn, status, "QueryContextAttributes")) { - s_pSecFn->DeleteSecurityContext(&sspi_context); + if(check_sspi_err(data, status, "QueryContextAttributes")) { + Curl_pSecFn->DeleteSecurityContext(&sspi_context); failf(data, "Failed to query security context attributes."); return CURLE_COULDNT_CONNECT; } @@ -394,15 +409,15 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(int sockindex, sspi_w_token[0].pvBuffer = malloc(sspi_sizes.cbSecurityTrailer); if(!sspi_w_token[0].pvBuffer) { - s_pSecFn->DeleteSecurityContext(&sspi_context); + Curl_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); + Curl_pSecFn->FreeContextBuffer(sspi_w_token[0].pvBuffer); + Curl_pSecFn->DeleteSecurityContext(&sspi_context); return CURLE_OUT_OF_MEMORY; } @@ -411,20 +426,20 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(int sockindex, 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); + Curl_pSecFn->FreeContextBuffer(sspi_w_token[0].pvBuffer); + Curl_pSecFn->FreeContextBuffer(sspi_w_token[1].pvBuffer); + Curl_pSecFn->DeleteSecurityContext(&sspi_context); return CURLE_OUT_OF_MEMORY; } - status = s_pSecFn->EncryptMessage(&sspi_context, + status = Curl_pSecFn->EncryptMessage(&sspi_context, KERB_WRAP_NO_ENCRYPT, &wrap_desc, 0); - if(check_sspi_err(conn, 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); + if(check_sspi_err(data, status, "EncryptMessage")) { + Curl_pSecFn->FreeContextBuffer(sspi_w_token[0].pvBuffer); + Curl_pSecFn->FreeContextBuffer(sspi_w_token[1].pvBuffer); + Curl_pSecFn->FreeContextBuffer(sspi_w_token[2].pvBuffer); + Curl_pSecFn->DeleteSecurityContext(&sspi_context); failf(data, "Failed to query security context attributes."); return CURLE_COULDNT_CONNECT; } @@ -433,10 +448,10 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(int sockindex, + 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); + Curl_pSecFn->FreeContextBuffer(sspi_w_token[0].pvBuffer); + Curl_pSecFn->FreeContextBuffer(sspi_w_token[1].pvBuffer); + Curl_pSecFn->FreeContextBuffer(sspi_w_token[2].pvBuffer); + Curl_pSecFn->DeleteSecurityContext(&sspi_context); return CURLE_OUT_OF_MEMORY; } @@ -445,60 +460,63 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(int sockindex, 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[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); + Curl_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); + Curl_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); + Curl_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)); + us_length = htons((unsigned short)sspi_send_token.cbBuffer); + memcpy(socksreq + 2, &us_length, sizeof(short)); } - code = Curl_write_plain(conn, sock, (char *)socksreq, 4, &written); + written = Curl_conn_cf_send(cf->next, data, (char *)socksreq, 4, FALSE, + &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); + Curl_pSecFn->FreeContextBuffer(sspi_send_token.pvBuffer); + Curl_pSecFn->DeleteSecurityContext(&sspi_context); return CURLE_COULDNT_CONNECT; } if(data->set.socks5_gssapi_nec) { memcpy(socksreq, &gss_enc, 1); - code = Curl_write_plain(conn, sock, (char *)socksreq, 1, &written); + written = Curl_conn_cf_send(cf->next, data, (char *)socksreq, 1, FALSE, + &code); if(code || (1 != written)) { failf(data, "Failed to send SSPI encryption type."); - s_pSecFn->DeleteSecurityContext(&sspi_context); + Curl_pSecFn->DeleteSecurityContext(&sspi_context); return CURLE_COULDNT_CONNECT; } } else { - code = Curl_write_plain(conn, sock, (char *)sspi_send_token.pvBuffer, - sspi_send_token.cbBuffer, &written); + written = Curl_conn_cf_send(cf->next, data, + (char *)sspi_send_token.pvBuffer, + sspi_send_token.cbBuffer, FALSE, &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); + Curl_pSecFn->FreeContextBuffer(sspi_send_token.pvBuffer); + Curl_pSecFn->DeleteSecurityContext(&sspi_context); return CURLE_COULDNT_CONNECT; } if(sspi_send_token.pvBuffer) - s_pSecFn->FreeContextBuffer(sspi_send_token.pvBuffer); + Curl_pSecFn->FreeContextBuffer(sspi_send_token.pvBuffer); } - result = Curl_blockread_all(conn, sock, (char *)socksreq, 4, &actualread); + 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); + Curl_pSecFn->DeleteSecurityContext(&sspi_context); return CURLE_COULDNT_CONNECT; } @@ -506,34 +524,34 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(int sockindex, 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); + Curl_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); + Curl_pSecFn->DeleteSecurityContext(&sspi_context); return CURLE_COULDNT_CONNECT; } - memcpy(&us_length, socksreq+2, sizeof(short)); + 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); + Curl_pSecFn->DeleteSecurityContext(&sspi_context); return CURLE_OUT_OF_MEMORY; } - result = Curl_blockread_all(conn, sock, (char *)sspi_w_token[0].pvBuffer, + 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); + Curl_pSecFn->FreeContextBuffer(sspi_w_token[0].pvBuffer); + Curl_pSecFn->DeleteSecurityContext(&sspi_context); return CURLE_COULDNT_CONNECT; } @@ -545,17 +563,17 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(int sockindex, sspi_w_token[1].cbBuffer = 0; sspi_w_token[1].pvBuffer = NULL; - status = s_pSecFn->DecryptMessage(&sspi_context, + status = Curl_pSecFn->DecryptMessage(&sspi_context, &wrap_desc, 0, &qop); - if(check_sspi_err(conn, status, "DecryptMessage")) { + if(check_sspi_err(data, status, "DecryptMessage")) { if(sspi_w_token[0].pvBuffer) - s_pSecFn->FreeContextBuffer(sspi_w_token[0].pvBuffer); + Curl_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); + Curl_pSecFn->FreeContextBuffer(sspi_w_token[1].pvBuffer); + Curl_pSecFn->DeleteSecurityContext(&sspi_context); failf(data, "Failed to query security context attributes."); return CURLE_COULDNT_CONNECT; } @@ -564,39 +582,41 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(int sockindex, 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); + Curl_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); + Curl_pSecFn->FreeContextBuffer(sspi_w_token[1].pvBuffer); + Curl_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); + Curl_pSecFn->FreeContextBuffer(sspi_w_token[0].pvBuffer); + Curl_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); + Curl_pSecFn->FreeContextBuffer(sspi_w_token[0].pvBuffer); + Curl_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); + Curl_pSecFn->FreeContextBuffer(sspi_w_token[0].pvBuffer); } + (void)curlx_nonblock(sock, TRUE); - infof(data, "SOCKS5 access with%s protection granted.\n", - (socksreq[0]==0)?"out GSS-API data": - ((socksreq[0]==1)?" GSS-API integrity":" GSS-API confidentiality")); + 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); + Curl_pSecFn->DeleteSecurityContext(&sspi_context); conn->socks5_sspi_context = sspi_context; } */ diff --git a/contrib/curl/lib/speedcheck.c b/contrib/curl/lib/speedcheck.c index bc15d97be5..580efbde75 100644 --- a/contrib/curl/lib/speedcheck.c +++ b/contrib/curl/lib/speedcheck.c @@ -5,11 +5,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2016, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -18,6 +18,8 @@ * 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" @@ -30,45 +32,48 @@ void Curl_speedinit(struct Curl_easy *data) { - memset(&data->state.keeps_speed, 0, sizeof(struct timeval)); + memset(&data->state.keeps_speed, 0, sizeof(struct curltime)); } +/* + * @unittest: 1606 + */ CURLcode Curl_speedcheck(struct Curl_easy *data, - struct timeval now) + struct curltime now) { - if((data->progress.current_speed >= 0) && - data->set.low_speed_time && - (Curl_tvlong(data->state.keeps_speed) != 0) && - (data->progress.current_speed < data->set.low_speed_limit)) { - time_t howlong = Curl_tvdiff(now, data->state.keeps_speed); - time_t nextcheck = (data->set.low_speed_time * 1000) - howlong; + if(data->req.keepon & KEEP_RECV_PAUSE) + /* A paused transfer is not qualified for speed checks */ + return CURLE_OK; - /* We are now below the "low speed limit". If we are below it - for "low speed time" seconds we consider that enough reason - to abort the download. */ - if(nextcheck <= 0) { - /* we have been this slow for long enough, now die */ - 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 { - /* wait complete low_speed_time */ - Curl_expire_latest(data, nextcheck); + 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; } - else { - /* we keep up the required speed all right */ - data->state.keeps_speed = now; - if(data->set.low_speed_limit) - /* if there is a low speed limit enabled, we set the expire timer to - make this connection's speed get checked again no later than when - this time is up */ - Curl_expire_latest(data, data->set.low_speed_time*1000); - } + 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/contrib/curl/lib/speedcheck.h b/contrib/curl/lib/speedcheck.h index 7dbe3d6d73..bff2f32b77 100644 --- a/contrib/curl/lib/speedcheck.h +++ b/contrib/curl/lib/speedcheck.h @@ -7,11 +7,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2010, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -20,6 +20,8 @@ * 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" @@ -28,6 +30,6 @@ void Curl_speedinit(struct Curl_easy *data); CURLcode Curl_speedcheck(struct Curl_easy *data, - struct timeval now); + struct curltime now); #endif /* HEADER_CURL_SPEEDCHECK_H */ diff --git a/contrib/curl/lib/splay.c b/contrib/curl/lib/splay.c index 7aa2e4bac3..5e27b08a6c 100644 --- a/contrib/curl/lib/splay.c +++ b/contrib/curl/lib/splay.c @@ -5,11 +5,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1997 - 2015, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -18,10 +18,13 @@ * 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" #include "splay.h" /* @@ -31,34 +34,33 @@ * zero : when i is equal to j * positive when : when i is larger than j */ -#define compare(i,j) Curl_splaycomparekeys((i),(j)) +#define compare(i,j) Curl_timediff_us(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 timeval i, +struct Curl_tree *Curl_splay(struct curltime i, struct Curl_tree *t) { struct Curl_tree N, *l, *r, *y; - long comp; - if(t == NULL) - return t; + if(!t) + return NULL; N.smaller = N.larger = NULL; l = r = &N; for(;;) { - comp = compare(i, t->key); + timediff_t comp = compare(i, t->key); if(comp < 0) { - if(t->smaller == NULL) + 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 == NULL) + if(!t->smaller) break; } r->smaller = t; /* link smaller */ @@ -66,14 +68,14 @@ struct Curl_tree *Curl_splay(struct timeval i, t = t->smaller; } else if(comp > 0) { - if(t->larger == NULL) + 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 == NULL) + if(!t->larger) break; } l->larger = t; /* link larger */ @@ -92,44 +94,41 @@ struct Curl_tree *Curl_splay(struct timeval i, return t; } -/* Insert key i into the tree t. Return a pointer to the resulting tree or +/* 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 timeval i, +struct Curl_tree *Curl_splayinsert(struct curltime i, struct Curl_tree *t, struct Curl_tree *node) { - static const struct timeval KEY_NOTUSED = {-1, -1}; /* will *NEVER* appear */ + static const struct curltime KEY_NOTUSED = { + ~0, -1 + }; /* will *NEVER* appear */ - if(node == NULL) - return t; + DEBUGASSERT(node); - if(t != NULL) { + if(t) { t = Curl_splay(i, t); - if(compare(i, t->key)==0) { + DEBUGASSERT(t); + if(compare(i, t->key) == 0) { /* There already exists a node in the tree with the very same key. Build - a linked list of nodes. We make the new 'node' struct the new master - node and make the previous node the first one in the 'same' list. */ + a doubly-linked circular list of nodes. We add the new 'node' struct + to the end of this list. */ - node->same = t; - node->key = i; - node->smaller = t->smaller; - node->larger = t->larger; + 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; - t->smaller = node; /* in the sub node for this same key, we use the - smaller pointer to point back to the master - node */ - - t->key = KEY_NOTUSED; /* and we set the key in the sub node to NOTUSED - to quickly identify this node as a subnode */ - - return node; /* new root node */ + return t; /* the root node always stays the same */ } } - if(t == NULL) { + if(!t) { node->smaller = node->larger = NULL; } else if(compare(i, t->key) < 0) { @@ -145,16 +144,20 @@ struct Curl_tree *Curl_splayinsert(struct timeval i, } node->key = i; - node->same = NULL; /* no identical node (yet) */ + /* 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 node with the given or lower key */ -struct Curl_tree *Curl_splaygetbest(struct timeval i, + 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) { @@ -162,97 +165,88 @@ struct Curl_tree *Curl_splaygetbest(struct timeval i, return NULL; } - t = Curl_splay(i, t); + /* find smallest */ + t = Curl_splay(tv_zero, t); + DEBUGASSERT(t); if(compare(i, t->key) < 0) { - /* too big node, try the smaller chain */ - if(t->smaller) - t=Curl_splay(t->smaller->key, t); - else { - /* fail */ - *removed = NULL; - return t; - } + /* even the smallest is too big */ + *removed = NULL; + return t; } - if(compare(i, t->key) >= 0) { /* found it */ - /* FIRST! Check if there is a list with identical keys */ - x = t->same; - if(x) { - /* there is, pick one from the list */ + /* 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' is the new root node */ - *removed = t; - return x; /* new root */ - } + x->key = t->key; + x->larger = t->larger; + x->smaller = t->smaller; + x->samep = t->samep; + t->samep->samen = x; - if(t->smaller == NULL) { - x = t->larger; - } - else { - x = Curl_splay(i, t->smaller); - x->larger = t->larger; - } *removed = t; - - return x; - } - else { - *removed = NULL; /* no match */ - return t; /* It wasn't there */ + 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 +/* Deletes the very node we point out from the tree if it is there. Stores a * pointer to the new resulting tree in 'newroot'. * - * Returns zero on success and non-zero on errors! TODO: document error codes. + * 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 + * NOTE: when the last node of the tree is removed, there is no tree left so * 'newroot' will be made to point to NULL. * * @unittest: 1309 */ -int Curl_splayremovebyaddr(struct Curl_tree *t, - struct Curl_tree *removenode, - struct Curl_tree **newroot) +int Curl_splayremove(struct Curl_tree *t, + struct Curl_tree *removenode, + struct Curl_tree **newroot) { - static const struct timeval KEY_NOTUSED = {-1, -1}; /* will *NEVER* appear */ + static const struct curltime KEY_NOTUSED = { + ~0, -1 + }; /* will *NEVER* appear */ struct Curl_tree *x; - if(!t || !removenode) + if(!t) return 1; + DEBUGASSERT(removenode); + 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. The 'smaller' link of a subnode - links to the parent node. */ - if(removenode->smaller == NULL) + and thus we can unlink it easily. */ + if(removenode->samen == removenode) + /* A non-subnode should never be set to KEY_NOTUSED */ return 3; - removenode->smaller->same = removenode->same; - if(removenode->same) - removenode->same->smaller = removenode->smaller; + removenode->samep->samen = removenode->samen; + removenode->samen->samep = removenode->samep; /* Ensures that double-remove gets caught. */ - removenode->smaller = NULL; + removenode->samen = removenode; - /* voila, we're done! */ *newroot = t; /* return the same root */ return 0; } t = Curl_splay(removenode->key, t); + DEBUGASSERT(t); /* First make sure that we got the same root node as the one we want to remove, as otherwise we might be trying to remove a node that - isn't actually in the tree. + is not 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 @@ -260,23 +254,26 @@ int Curl_splayremovebyaddr(struct Curl_tree *t, if(t != removenode) return 2; - /* Check if there is a list with identical sizes, as then we're trying to + /* Check if there is a list with identical sizes, as then we are trying to remove the root node of a list of nodes with identical keys. */ - x = t->same; - if(x) { + 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 == NULL) + if(!t->smaller) x = t->larger; else { x = Curl_splay(removenode->key, t->smaller); + DEBUGASSERT(x); x->larger = t->larger; } } @@ -286,3 +283,15 @@ int Curl_splayremovebyaddr(struct Curl_tree *t, return 0; } +/* set and get the custom payload for this tree node */ +void Curl_splayset(struct Curl_tree *node, void *payload) +{ + DEBUGASSERT(node); + node->ptr = payload; +} + +void *Curl_splayget(struct Curl_tree *node) +{ + DEBUGASSERT(node); + return node->ptr; +} diff --git a/contrib/curl/lib/splay.h b/contrib/curl/lib/splay.h index 427bfc8eb4..b8c9360e57 100644 --- a/contrib/curl/lib/splay.h +++ b/contrib/curl/lib/splay.h @@ -7,11 +7,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1997 - 2016, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -20,47 +20,39 @@ * 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" +/* only use function calls to access this struct */ struct Curl_tree { struct Curl_tree *smaller; /* smaller node */ struct Curl_tree *larger; /* larger node */ - struct Curl_tree *same; /* points to a node with identical key */ - struct timeval key; /* this node's "sort" key */ - void *payload; /* data the splay code doesn't care about */ + struct Curl_tree *samen; /* points to the next node with identical key */ + struct Curl_tree *samep; /* points to the prev node with identical key */ + struct curltime key; /* this node's "sort" key */ + void *ptr; /* data the splay code does not care about */ }; -struct Curl_tree *Curl_splay(struct timeval i, +struct Curl_tree *Curl_splay(struct curltime i, struct Curl_tree *t); -struct Curl_tree *Curl_splayinsert(struct timeval key, +struct Curl_tree *Curl_splayinsert(struct curltime key, struct Curl_tree *t, struct Curl_tree *newnode); -#if 0 -struct Curl_tree *Curl_splayremove(struct timeval key, - struct Curl_tree *t, - struct Curl_tree **removed); -#endif - -struct Curl_tree *Curl_splaygetbest(struct timeval key, +struct Curl_tree *Curl_splaygetbest(struct curltime key, struct Curl_tree *t, struct Curl_tree **removed); -int Curl_splayremovebyaddr(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)))) +int Curl_splayremove(struct Curl_tree *t, + struct Curl_tree *removenode, + struct Curl_tree **newroot); -#ifdef DEBUGBUILD -void Curl_splayprint(struct Curl_tree * t, int d, char output); -#else -#define Curl_splayprint(x,y,z) Curl_nop_stmt -#endif +/* set and get the custom payload for this tree node */ +void Curl_splayset(struct Curl_tree *node, void *payload); +void *Curl_splayget(struct Curl_tree *node); #endif /* HEADER_CURL_SPLAY_H */ diff --git a/contrib/curl/lib/ssh.c b/contrib/curl/lib/ssh.c deleted file mode 100644 index 086d40ecdb..0000000000 --- a/contrib/curl/lib/ssh.c +++ /dev/null @@ -1,3464 +0,0 @@ -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) 1998 - 2016, Daniel Stenberg, , et al. - * - * This software is licensed as described in the file COPYING, which - * you should have received as part of this distribution. The terms - * are also available at https://curl.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. - * - ***************************************************************************/ - -/* #define CURL_LIBSSH2_DEBUG */ - -#include "curl_setup.h" - -#ifdef USE_LIBSSH2 - -#ifdef HAVE_LIMITS_H -# include -#endif - -#include -#include - -#ifdef HAVE_FCNTL_H -#include -#endif - -#ifdef HAVE_NETINET_IN_H -#include -#endif -#ifdef HAVE_ARPA_INET_H -#include -#endif -#ifdef HAVE_UTSNAME_H -#include -#endif -#ifdef HAVE_NETDB_H -#include -#endif -#ifdef __VMS -#include -#include -#endif - -#if (defined(NETWARE) && defined(__NOVELL_LIBC__)) -#undef in_addr_t -#define in_addr_t unsigned long -#endif - -#include -#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 "connect.h" -#include "strerror.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" - -/* The last 3 #include files should be in this order */ -#include "curl_printf.h" -#include "curl_memory.h" -#include "memdebug.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 - -#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_last_error(s) curlx_ultosi(libssh2_sftp_last_error(s)) - -#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(int err); -static LIBSSH2_ALLOC_FUNC(my_libssh2_malloc); -static LIBSSH2_REALLOC_FUNC(my_libssh2_realloc); -static LIBSSH2_FREE_FUNC(my_libssh2_free); - -static CURLcode get_pathname(const char **cpp, char **path); - -static CURLcode ssh_connect(struct connectdata *conn, bool *done); -static CURLcode ssh_multi_statemach(struct connectdata *conn, bool *done); -static CURLcode ssh_do(struct connectdata *conn, bool *done); - -static CURLcode ssh_getworkingpath(struct connectdata *conn, - char *homedir, /* when SFTP is used */ - char **path); - -static CURLcode scp_done(struct connectdata *conn, - CURLcode, bool premature); -static CURLcode scp_doing(struct connectdata *conn, - bool *dophase_done); -static CURLcode scp_disconnect(struct connectdata *conn, bool dead_connection); - -static CURLcode sftp_done(struct connectdata *conn, - CURLcode, bool premature); -static CURLcode sftp_doing(struct connectdata *conn, - bool *dophase_done); -static CURLcode sftp_disconnect(struct connectdata *conn, bool dead); -static -CURLcode sftp_perform(struct connectdata *conn, - bool *connected, - bool *dophase_done); - -static int ssh_getsock(struct connectdata *conn, - curl_socket_t *sock, /* points to numsocks number - of sockets */ - int numsocks); - -static int ssh_perform_getsock(const struct connectdata *conn, - curl_socket_t *sock, /* points to numsocks - number of sockets */ - int numsocks); - -static CURLcode ssh_setup_connection(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_perform_getsock, /* perform_getsock */ - scp_disconnect, /* disconnect */ - ZERO_NULL, /* readwrite */ - PORT_SSH, /* defport */ - CURLPROTO_SCP, /* protocol */ - 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_perform_getsock, /* perform_getsock */ - sftp_disconnect, /* disconnect */ - ZERO_NULL, /* readwrite */ - PORT_SSH, /* defport */ - CURLPROTO_SFTP, /* protocol */ - 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 connectdata *conn = (struct connectdata *)*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) { - responses[0].text = strdup(conn->passwd); - responses[0].length = curlx_uztoui(strlen(conn->passwd)); - } - (void)prompts; - (void)abstract; -} /* kbd_callback */ - -static CURLcode sftp_libssh2_error_to_CURLE(int 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; - - 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; - } - - /* TODO: map some more of the libssh2 errors to the more appropriate CURLcode - error code, and possibly add a few new SSH-related one. We must however - not return or even depend on libssh2 errors in the public libcurl API */ - - 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 connectdata *conn, sshstate nowstate) -{ - 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_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_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(conn->data, "SFTP %p state change from %s to %s\n", - (void *)sshc, names[sshc->state], names[nowstate]); - } -#endif - - sshc->state = nowstate; -} - -/* figure out the path to work with in this particular request */ -static CURLcode ssh_getworkingpath(struct connectdata *conn, - char *homedir, /* when SFTP is used */ - char **path) /* returns the allocated - real path to work with */ -{ - struct Curl_easy *data = conn->data; - char *real_path = NULL; - char *working_path; - size_t working_path_len; - CURLcode result = - Curl_urldecode(data, data->state.path, 0, &working_path, - &working_path_len, FALSE); - if(result) - return result; - - /* Check for /~/, indicating relative to the user's home directory */ - if(conn->handler->protocol & CURLPROTO_SCP) { - real_path = malloc(working_path_len+1); - if(real_path == NULL) { - free(working_path); - return CURLE_OUT_OF_MEMORY; - } - if((working_path_len > 3) && (!memcmp(working_path, "/~/", 3))) - /* It is referenced to the home directory, so strip the leading '/~/' */ - memcpy(real_path, working_path+3, 4 + working_path_len-3); - else - memcpy(real_path, working_path, 1 + working_path_len); - } - else if(conn->handler->protocol & CURLPROTO_SFTP) { - if((working_path_len > 1) && (working_path[1] == '~')) { - size_t homelen = strlen(homedir); - real_path = malloc(homelen + working_path_len + 1); - if(real_path == NULL) { - free(working_path); - return CURLE_OUT_OF_MEMORY; - } - /* It is referenced to the home directory, so strip the - leading '/' */ - memcpy(real_path, homedir, homelen); - real_path[homelen] = '/'; - real_path[homelen+1] = '\0'; - if(working_path_len > 3) { - memcpy(real_path+homelen+1, working_path + 3, - 1 + working_path_len -3); - } - } - else { - real_path = malloc(working_path_len+1); - if(real_path == NULL) { - free(working_path); - return CURLE_OUT_OF_MEMORY; - } - memcpy(real_path, working_path, 1+working_path_len); - } - } - - free(working_path); - - /* store the pointer for the caller to receive */ - *path = real_path; - - return CURLE_OK; -} - -#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 libssh2_session_startup(x,y) libssh2_session_handshake(x,y) -#endif - -static CURLcode ssh_knownhost(struct connectdata *conn) -{ - CURLcode result = CURLE_OK; - -#ifdef HAVE_LIBSSH2_KNOWNHOST_API - struct Curl_easy *data = conn->data; - - if(data->set.str[STRING_SSH_KNOWNHOSTS]) { - /* we're asked to verify the host against a file */ - struct ssh_conn *sshc = &conn->proto.sshc; - int rc; - int keytype; - size_t keylen; - const char *remotekey = libssh2_session_hostkey(sshc->ssh_session, - &keylen, &keytype); - 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? - */ - struct libssh2_knownhost *host; - 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; - - keybit = (keytype == LIBSSH2_HOSTKEY_TYPE_RSA)? - LIBSSH2_KNOWNHOST_KEY_SSHRSA:LIBSSH2_KNOWNHOST_KEY_SSHDSS; - -#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\n", keycheck, - (keycheck <= LIBSSH2_KNOWNHOST_CHECK_MISMATCH)? - host->key:""); - - /* setup 'knownkey' */ - if(keycheck <= LIBSSH2_KNOWNHOST_CHECK_MISMATCH) { - knownkey.key = host->key; - knownkey.len = 0; - knownkey.keytype = (keytype == LIBSSH2_HOSTKEY_TYPE_RSA)? - CURLKHTYPE_RSA : CURLKHTYPE_DSS; - knownkeyp = &knownkey; - } - - /* setup 'foundkey' */ - foundkey.key = remotekey; - foundkey.len = keylen; - foundkey.keytype = (keytype == LIBSSH2_HOSTKEY_TYPE_RSA)? - CURLKHTYPE_RSA : CURLKHTYPE_DSS; - - /* - * 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 */ - rc = func(data, knownkeyp, /* from the knownhosts file */ - &foundkey, /* from the remote host */ - keymatch, data->set.ssh_keyfunc_userp); - } - else - /* no remotekey means failure! */ - rc = CURLKHSTAT_REJECT; - - switch(rc) { - default: /* unknown return codes will equal reject */ - /* FALLTHROUGH */ - case CURLKHSTAT_REJECT: - state(conn, 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: - 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!\n", - conn->host.name); - else if(rc == CURLKHSTAT_FINE_ADD_TO_FILE) { - /* 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!\n", - data->set.str[STRING_SSH_KNOWNHOSTS]); - } - } - } - break; - } - } -#else /* HAVE_LIBSSH2_KNOWNHOST_API */ - (void)conn; -#endif - return result; -} - -static CURLcode ssh_check_fingerprint(struct connectdata *conn) -{ - struct ssh_conn *sshc = &conn->proto.sshc; - struct Curl_easy *data = conn->data; - const char *pubkey_md5 = data->set.str[STRING_SSH_HOST_PUBLIC_KEY_MD5]; - char md5buffer[33]; - int i; - - const char *fingerprint = libssh2_hostkey_hash(sshc->ssh_session, - LIBSSH2_HOSTKEY_HASH_MD5); - - if(fingerprint) { - /* The fingerprint points to static storage (!), don't free() it. */ - for(i = 0; i < 16; i++) - snprintf(&md5buffer[i*2], 3, "%02x", (unsigned char) fingerprint[i]); - infof(data, "SSH MD5 fingerprint: %s\n", md5buffer); - } - - /* Before we authenticate we check the hostkey's MD5 fingerprint - * against a known fingerprint, if available. - */ - if(pubkey_md5 && strlen(pubkey_md5) == 32) { - 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(conn, SSH_SESSION_FREE); - sshc->actualcode = CURLE_PEER_FAILED_VERIFICATION; - return sshc->actualcode; - } - else { - infof(data, "MD5 checksum match!\n"); - /* as we already matched, we skip the check for known hosts */ - return CURLE_OK; - } - } - else - return ssh_knownhost(conn); -} - -/* - * 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 connectdata *conn, bool *block) -{ - CURLcode result = CURLE_OK; - struct Curl_easy *data = conn->data; - struct SSHPROTO *sftp_scp = data->req.protop; - struct ssh_conn *sshc = &conn->proto.sshc; - curl_socket_t sock = conn->sock[FIRSTSOCKET]; - char *new_readdir_line; - int rc = LIBSSH2_ERROR_NONE; - int err; - int seekerr = CURL_SEEKFUNC_OK; - *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); - - state(conn, SSH_S_STARTUP); - /* fall-through */ - - case SSH_S_STARTUP: - rc = libssh2_session_startup(sshc->ssh_session, (int)sock); - if(rc == LIBSSH2_ERROR_EAGAIN) { - break; - } - else if(rc) { - failf(data, "Failure establishing ssh session"); - state(conn, SSH_SESSION_FREE); - sshc->actualcode = CURLE_FAILED_INIT; - break; - } - - state(conn, SSH_HOSTKEY); - - /* fall-through */ - 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(conn); - if(!result) - state(conn, 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\n"); - state(conn, SSH_AUTH_DONE); - break; - } - else { - err = libssh2_session_last_errno(sshc->ssh_session); - if(err == LIBSSH2_ERROR_EAGAIN) - rc = LIBSSH2_ERROR_EAGAIN; - else { - state(conn, SSH_SESSION_FREE); - sshc->actualcode = libssh2_session_error_to_CURLE(err); - } - break; - } - } - infof(data, "SSH authentication methods available: %s\n", - sshc->authlist); - - state(conn, 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)) { - char *home = NULL; - bool out_of_memory = FALSE; - - sshc->rsa_pub = sshc->rsa = NULL; - - /* To ponder about: should really the lib be messing about with the - HOME environment variable etc? */ - home = curl_getenv("HOME"); - - if(data->set.str[STRING_SSH_PRIVATE_KEY]) - sshc->rsa = strdup(data->set.str[STRING_SSH_PRIVATE_KEY]); - else { - /* 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); - } - } - } - 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 == NULL) { - free(home); - Curl_safefree(sshc->rsa); - Curl_safefree(sshc->rsa_pub); - state(conn, SSH_SESSION_FREE); - sshc->actualcode = CURLE_OUT_OF_MEMORY; - break; - } - - sshc->passphrase = data->set.ssl.key_passwd; - if(!sshc->passphrase) - sshc->passphrase = ""; - - free(home); - - if(sshc->rsa_pub) - infof(data, "Using SSH public key file '%s'\n", sshc->rsa_pub); - infof(data, "Using SSH private key file '%s'\n", sshc->rsa); - - state(conn, SSH_AUTH_PKEY); - } - else { - state(conn, 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\n"); - state(conn, SSH_AUTH_DONE); - } - else { - char *err_msg; - (void)libssh2_session_last_error(sshc->ssh_session, - &err_msg, NULL, 0); - infof(data, "SSH public key authentication failed: %s\n", err_msg); - state(conn, 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(conn, SSH_AUTH_PASS); - } - else { - state(conn, 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; - } - else if(rc == 0) { - sshc->authed = TRUE; - infof(data, "Initialized password authentication\n"); - state(conn, SSH_AUTH_DONE); - } - else { - state(conn, 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(conn, SSH_AUTH_HOST); - } - else { - state(conn, SSH_AUTH_AGENT_INIT); - } - break; - - case SSH_AUTH_HOST: - state(conn, 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\n"); - - state(conn, 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\n"); - state(conn, SSH_AUTH_KEY_INIT); - rc = 0; /* clear rc and continue */ - } - else { - state(conn, SSH_AUTH_AGENT_LIST); - } - } - else -#endif /* HAVE_LIBSSH2_AGENT_API */ - state(conn, 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\n"); - state(conn, SSH_AUTH_KEY_INIT); - rc = 0; /* clear rc and continue */ - } - else { - state(conn, 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; - else - break; - } - } - - if(rc < 0) - infof(data, "Failure requesting identities to agent\n"); - else if(rc == 1) - infof(data, "No identity would match\n"); - - if(rc == LIBSSH2_ERROR_NONE) { - sshc->authed = TRUE; - infof(data, "Agent based authentication successful\n"); - state(conn, SSH_AUTH_DONE); - } - else { - state(conn, 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(conn, SSH_AUTH_KEY); - } - else { - state(conn, 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; - } - else if(rc == 0) { - sshc->authed = TRUE; - infof(data, "Initialized keyboard interactive authentication\n"); - } - state(conn, SSH_AUTH_DONE); - break; - - case SSH_AUTH_DONE: - if(!sshc->authed) { - failf(data, "Authentication failure"); - state(conn, SSH_SESSION_FREE); - sshc->actualcode = CURLE_LOGIN_DENIED; - break; - } - - /* - * At this point we have an authenticated ssh session. - */ - infof(data, "Authentication complete\n"); - - Curl_pgrsTime(conn->data, TIMER_APPCONNECT); /* SSH is connected */ - - conn->sockfd = sock; - conn->writesockfd = CURL_SOCKET_BAD; - - if(conn->handler->protocol == CURLPROTO_SFTP) { - state(conn, SSH_SFTP_INIT); - break; - } - infof(data, "SSH CONNECT phase done\n"); - state(conn, 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) { - if(libssh2_session_last_errno(sshc->ssh_session) == - LIBSSH2_ERROR_EAGAIN) { - rc = LIBSSH2_ERROR_EAGAIN; - break; - } - else { - char *err_msg; - - (void)libssh2_session_last_error(sshc->ssh_session, - &err_msg, NULL, 0); - failf(data, "Failure initializing sftp session: %s", err_msg); - state(conn, SSH_SESSION_FREE); - sshc->actualcode = CURLE_FAILED_INIT; - break; - } - } - state(conn, 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; - } - else if(rc > 0) { - /* It seems that this string is not always NULL terminated */ - tempHome[rc] = '\0'; - sshc->homedir = strdup(tempHome); - if(!sshc->homedir) { - state(conn, SSH_SFTP_CLOSE); - sshc->actualcode = CURLE_OUT_OF_MEMORY; - break; - } - conn->data->state.most_recent_ftp_entrypath = sshc->homedir; - } - else { - /* Return the error type */ - err = sftp_libssh2_last_error(sshc->sftp_session); - if(err) - result = sftp_libssh2_error_to_CURLE(err); - 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 = %d makes libcurl = %d\n", - err, (int)result)); - state(conn, 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\n")); - state(conn, SSH_STOP); - break; - - case SSH_SFTP_QUOTE_INIT: - - result = ssh_getworkingpath(conn, sshc->homedir, &sftp_scp->path); - if(result) { - sshc->actualcode = result; - state(conn, SSH_STOP); - break; - } - - if(data->set.quote) { - infof(data, "Sending quote commands\n"); - sshc->quote_item = data->set.quote; - state(conn, SSH_SFTP_QUOTE); - } - else { - state(conn, SSH_SFTP_GETINFO); - } - break; - - case SSH_SFTP_POSTQUOTE_INIT: - if(data->set.postquote) { - infof(data, "Sending quote commands\n"); - sshc->quote_item = data->set.postquote; - state(conn, SSH_SFTP_QUOTE); - } - else { - state(conn, SSH_STOP); - } - break; - - case SSH_SFTP_QUOTE: - /* Send any quote commands */ - { - const char *cp; - - /* - * 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 reponds. */ - - 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", - sftp_scp->path); - if(!tmp) { - result = CURLE_OUT_OF_MEMORY; - state(conn, SSH_SFTP_CLOSE); - sshc->nextstate = SSH_NO_STATE; - break; - } - if(data->set.verbose) { - Curl_debug(data, CURLINFO_HEADER_OUT, (char *)"PWD\n", 4, conn); - Curl_debug(data, CURLINFO_HEADER_IN, tmp, strlen(tmp), conn); - } - /* 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(conn, CLIENTWRITE_HEADER, tmp, strlen(tmp)); - free(tmp); - if(result) { - state(conn, SSH_SFTP_CLOSE); - sshc->nextstate = SSH_NO_STATE; - sshc->actualcode = result; - } - else - state(conn, SSH_SFTP_NEXT_QUOTE); - break; - } - else if(cmd) { - /* - * 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 == NULL) { - failf(data, "Syntax error in SFTP command. Supply parameter(s)!"); - state(conn, 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 = get_pathname(&cp, &sshc->quote_path1); - if(result) { - if(result == CURLE_OUT_OF_MEMORY) - failf(data, "Out of memory"); - else - failf(data, "Syntax error: Bad first parameter"); - state(conn, 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) ) { - /* attribute change */ - - /* sshc->quote_path1 contains the mode to set */ - /* get the destination */ - result = get_pathname(&cp, &sshc->quote_path2); - if(result) { - if(result == CURLE_OUT_OF_MEMORY) - failf(data, "Out of memory"); - else - failf(data, "Syntax error in chgrp/chmod/chown: " - "Bad second parameter"); - Curl_safefree(sshc->quote_path1); - state(conn, SSH_SFTP_CLOSE); - sshc->nextstate = SSH_NO_STATE; - sshc->actualcode = result; - break; - } - memset(&sshc->quote_attrs, 0, sizeof(LIBSSH2_SFTP_ATTRIBUTES)); - state(conn, SSH_SFTP_QUOTE_STAT); - break; - } - else if(strncasecompare(cmd, "ln ", 3) || - strncasecompare(cmd, "symlink ", 8)) { - /* symbolic linking */ - /* sshc->quote_path1 is the source */ - /* get the destination */ - result = get_pathname(&cp, &sshc->quote_path2); - 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(conn, SSH_SFTP_CLOSE); - sshc->nextstate = SSH_NO_STATE; - sshc->actualcode = result; - break; - } - state(conn, SSH_SFTP_QUOTE_SYMLINK); - break; - } - else if(strncasecompare(cmd, "mkdir ", 6)) { - /* create dir */ - state(conn, 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 = get_pathname(&cp, &sshc->quote_path2); - 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(conn, SSH_SFTP_CLOSE); - sshc->nextstate = SSH_NO_STATE; - sshc->actualcode = result; - break; - } - state(conn, SSH_SFTP_QUOTE_RENAME); - break; - } - else if(strncasecompare(cmd, "rmdir ", 6)) { - /* delete dir */ - state(conn, SSH_SFTP_QUOTE_RMDIR); - break; - } - else if(strncasecompare(cmd, "rm ", 3)) { - state(conn, SSH_SFTP_QUOTE_UNLINK); - break; - } -#ifdef HAS_STATVFS_SUPPORT - else if(strncasecompare(cmd, "statvfs ", 8)) { - state(conn, SSH_SFTP_QUOTE_STATVFS); - break; - } -#endif - - failf(data, "Unknown SFTP command"); - Curl_safefree(sshc->quote_path1); - Curl_safefree(sshc->quote_path2); - state(conn, SSH_SFTP_CLOSE); - sshc->nextstate = SSH_NO_STATE; - sshc->actualcode = CURLE_QUOTE_ERROR; - break; - } - } - if(!sshc->quote_item) { - state(conn, SSH_SFTP_GETINFO); - } - 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(conn, SSH_SFTP_QUOTE); - } - else { - if(sshc->nextstate != SSH_NO_STATE) { - state(conn, sshc->nextstate); - sshc->nextstate = SSH_NO_STATE; - } - else { - state(conn, 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 reponds. */ - - 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, - &sshc->quote_attrs); - if(rc == LIBSSH2_ERROR_EAGAIN) { - break; - } - else if(rc != 0 && !sshc->acceptfail) { /* get those attributes */ - err = sftp_libssh2_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(err)); - state(conn, SSH_SFTP_CLOSE); - sshc->nextstate = SSH_NO_STATE; - sshc->actualcode = CURLE_QUOTE_ERROR; - break; - } - } - - /* Now set the new attributes... */ - if(strncasecompare(cmd, "chgrp", 5)) { - sshc->quote_attrs.gid = strtoul(sshc->quote_path1, NULL, 10); - sshc->quote_attrs.flags = LIBSSH2_SFTP_ATTR_UIDGID; - 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(conn, SSH_SFTP_CLOSE); - sshc->nextstate = SSH_NO_STATE; - sshc->actualcode = CURLE_QUOTE_ERROR; - break; - } - } - else if(strncasecompare(cmd, "chmod", 5)) { - sshc->quote_attrs.permissions = strtoul(sshc->quote_path1, NULL, 8); - sshc->quote_attrs.flags = LIBSSH2_SFTP_ATTR_PERMISSIONS; - /* permissions are octal */ - if(sshc->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(conn, SSH_SFTP_CLOSE); - sshc->nextstate = SSH_NO_STATE; - sshc->actualcode = CURLE_QUOTE_ERROR; - break; - } - } - else if(strncasecompare(cmd, "chown", 5)) { - sshc->quote_attrs.uid = strtoul(sshc->quote_path1, NULL, 10); - sshc->quote_attrs.flags = LIBSSH2_SFTP_ATTR_UIDGID; - 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(conn, SSH_SFTP_CLOSE); - sshc->nextstate = SSH_NO_STATE; - sshc->actualcode = CURLE_QUOTE_ERROR; - break; - } - } - - /* Now send the completed structure... */ - state(conn, 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, - &sshc->quote_attrs); - if(rc == LIBSSH2_ERROR_EAGAIN) { - break; - } - else if(rc != 0 && !sshc->acceptfail) { - err = sftp_libssh2_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(err)); - state(conn, SSH_SFTP_CLOSE); - sshc->nextstate = SSH_NO_STATE; - sshc->actualcode = CURLE_QUOTE_ERROR; - break; - } - state(conn, 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; - } - else if(rc != 0 && !sshc->acceptfail) { - err = sftp_libssh2_last_error(sshc->sftp_session); - Curl_safefree(sshc->quote_path1); - Curl_safefree(sshc->quote_path2); - failf(data, "symlink command failed: %s", - sftp_libssh2_strerror(err)); - state(conn, SSH_SFTP_CLOSE); - sshc->nextstate = SSH_NO_STATE; - sshc->actualcode = CURLE_QUOTE_ERROR; - break; - } - state(conn, 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; - } - else if(rc != 0 && !sshc->acceptfail) { - err = sftp_libssh2_last_error(sshc->sftp_session); - Curl_safefree(sshc->quote_path1); - failf(data, "mkdir command failed: %s", sftp_libssh2_strerror(err)); - state(conn, SSH_SFTP_CLOSE); - sshc->nextstate = SSH_NO_STATE; - sshc->actualcode = CURLE_QUOTE_ERROR; - break; - } - state(conn, 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; - } - else if(rc != 0 && !sshc->acceptfail) { - err = sftp_libssh2_last_error(sshc->sftp_session); - Curl_safefree(sshc->quote_path1); - Curl_safefree(sshc->quote_path2); - failf(data, "rename command failed: %s", sftp_libssh2_strerror(err)); - state(conn, SSH_SFTP_CLOSE); - sshc->nextstate = SSH_NO_STATE; - sshc->actualcode = CURLE_QUOTE_ERROR; - break; - } - state(conn, 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; - } - else if(rc != 0 && !sshc->acceptfail) { - err = sftp_libssh2_last_error(sshc->sftp_session); - Curl_safefree(sshc->quote_path1); - failf(data, "rmdir command failed: %s", sftp_libssh2_strerror(err)); - state(conn, SSH_SFTP_CLOSE); - sshc->nextstate = SSH_NO_STATE; - sshc->actualcode = CURLE_QUOTE_ERROR; - break; - } - state(conn, 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; - } - else if(rc != 0 && !sshc->acceptfail) { - err = sftp_libssh2_last_error(sshc->sftp_session); - Curl_safefree(sshc->quote_path1); - failf(data, "rm command failed: %s", sftp_libssh2_strerror(err)); - state(conn, SSH_SFTP_CLOSE); - sshc->nextstate = SSH_NO_STATE; - sshc->actualcode = CURLE_QUOTE_ERROR; - break; - } - state(conn, 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; - } - else if(rc != 0 && !sshc->acceptfail) { - err = sftp_libssh2_last_error(sshc->sftp_session); - Curl_safefree(sshc->quote_path1); - failf(data, "statvfs command failed: %s", sftp_libssh2_strerror(err)); - state(conn, SSH_SFTP_CLOSE); - sshc->nextstate = SSH_NO_STATE; - sshc->actualcode = CURLE_QUOTE_ERROR; - break; - } - else if(rc == 0) { - char *tmp = aprintf("statvfs:\n" - "f_bsize: %llu\n" "f_frsize: %llu\n" - "f_blocks: %llu\n" "f_bfree: %llu\n" - "f_bavail: %llu\n" "f_files: %llu\n" - "f_ffree: %llu\n" "f_favail: %llu\n" - "f_fsid: %llu\n" "f_flag: %llu\n" - "f_namemax: %llu\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(conn, SSH_SFTP_CLOSE); - sshc->nextstate = SSH_NO_STATE; - break; - } - - result = Curl_client_write(conn, CLIENTWRITE_HEADER, tmp, strlen(tmp)); - free(tmp); - if(result) { - state(conn, SSH_SFTP_CLOSE); - sshc->nextstate = SSH_NO_STATE; - sshc->actualcode = result; - } - } - state(conn, SSH_SFTP_NEXT_QUOTE); - break; - } -#endif - case SSH_SFTP_GETINFO: - { - if(data->set.get_filetime) { - state(conn, SSH_SFTP_FILETIME); - } - else { - state(conn, SSH_SFTP_TRANS_INIT); - } - break; - } - - case SSH_SFTP_FILETIME: - { - LIBSSH2_SFTP_ATTRIBUTES attrs; - - rc = libssh2_sftp_stat_ex(sshc->sftp_session, sftp_scp->path, - curlx_uztoui(strlen(sftp_scp->path)), - LIBSSH2_SFTP_STAT, &attrs); - if(rc == LIBSSH2_ERROR_EAGAIN) { - break; - } - else if(rc == 0) { - data->info.filetime = (long)attrs.mtime; - } - - state(conn, SSH_SFTP_TRANS_INIT); - break; - } - - case SSH_SFTP_TRANS_INIT: - if(data->set.upload) - state(conn, SSH_SFTP_UPLOAD_INIT); - else { - if(sftp_scp->path[strlen(sftp_scp->path)-1] == '/') - state(conn, SSH_SFTP_READDIR_INIT); - else - state(conn, 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 != 0) { - LIBSSH2_SFTP_ATTRIBUTES attrs; - if(data->state.resume_from < 0) { - rc = libssh2_sftp_stat_ex(sshc->sftp_session, sftp_scp->path, - curlx_uztoui(strlen(sftp_scp->path)), - LIBSSH2_SFTP_STAT, &attrs); - if(rc == LIBSSH2_ERROR_EAGAIN) { - break; - } - else 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.ftp_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 behaviour) */ - flags = LIBSSH2_FXF_WRITE|LIBSSH2_FXF_CREAT|LIBSSH2_FXF_TRUNC; - - sshc->sftp_handle = - libssh2_sftp_open_ex(sshc->sftp_session, sftp_scp->path, - curlx_uztoui(strlen(sftp_scp->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; - else { - if(LIBSSH2_ERROR_SFTP_PROTOCOL == rc) - /* only when there was an SFTP protocol error can we extract - the sftp error! */ - err = sftp_libssh2_last_error(sshc->sftp_session); - else - err = -1; /* not an sftp error at all */ - - if(sshc->secondCreateDirs) { - state(conn, SSH_SFTP_CLOSE); - sshc->actualcode = err>= LIBSSH2_FX_OK? - sftp_libssh2_error_to_CURLE(err):CURLE_SSH; - failf(data, "Creating the dir/file failed: %s", - sftp_libssh2_strerror(err)); - break; - } - else if(((err == LIBSSH2_FX_NO_SUCH_FILE) || - (err == LIBSSH2_FX_FAILURE) || - (err == LIBSSH2_FX_NO_SUCH_PATH)) && - (data->set.ftp_create_missing_dirs && - (strlen(sftp_scp->path) > 1))) { - /* try to create the path remotely */ - rc = 0; /* clear rc and continue */ - sshc->secondCreateDirs = 1; - state(conn, SSH_SFTP_CREATE_DIRS_INIT); - break; - } - state(conn, SSH_SFTP_CLOSE); - sshc->actualcode = err>= LIBSSH2_FX_OK? - sftp_libssh2_error_to_CURLE(err):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; - err=-1; - } - failf(data, "Upload failed: %s (%d/%d)", - err>= LIBSSH2_FX_OK?sftp_libssh2_strerror(err):"ssh error", - err, 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) { - seekerr = conn->seek_func(conn->seek_client, data->state.resume_from, - SEEK_SET); - } - - if(seekerr != CURL_SEEKFUNC_OK) { - - 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) */ - else { - curl_off_t passed=0; - do { - size_t readthisamountnow = - (data->state.resume_from - passed > CURL_OFF_T_C(BUFSIZE)) ? - BUFSIZE : 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; - 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(conn, -1, -1, FALSE, NULL, FIRSTSOCKET, NULL); - - /* not set by Curl_setup_transfer to preserve keepon bits */ - conn->sockfd = conn->writesockfd; - - if(result) { - state(conn, 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); - - state(conn, SSH_STOP); - } - break; - } - - case SSH_SFTP_CREATE_DIRS_INIT: - if(strlen(sftp_scp->path) > 1) { - sshc->slash_pos = sftp_scp->path + 1; /* ignore the leading '/' */ - state(conn, SSH_SFTP_CREATE_DIRS); - } - else { - state(conn, 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'\n", sftp_scp->path); - state(conn, SSH_SFTP_CREATE_DIRS_MKDIR); - break; - } - else { - state(conn, 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, sftp_scp->path, - curlx_uztoui(strlen(sftp_scp->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 - */ - err = sftp_libssh2_last_error(sshc->sftp_session); - if((err != LIBSSH2_FX_FILE_ALREADY_EXISTS) && - (err != LIBSSH2_FX_FAILURE) && - (err != LIBSSH2_FX_PERMISSION_DENIED)) { - result = sftp_libssh2_error_to_CURLE(err); - state(conn, SSH_SFTP_CLOSE); - sshc->actualcode = result?result:CURLE_SSH; - break; - } - else { - rc = 0; /* clear rc and continue */ - } - } - state(conn, SSH_SFTP_CREATE_DIRS); - break; - - case SSH_SFTP_READDIR_INIT: - Curl_pgrsSetDownloadSize(data, -1); - if(data->set.opt_no_body) { - state(conn, 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, - sftp_scp->path, - curlx_uztoui( - strlen(sftp_scp->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; - } - else { - err = sftp_libssh2_last_error(sshc->sftp_session); - failf(data, "Could not open directory for reading: %s", - sftp_libssh2_strerror(err)); - state(conn, SSH_SFTP_CLOSE); - result = sftp_libssh2_error_to_CURLE(err); - sshc->actualcode = result?result:CURLE_SSH; - break; - } - } - sshc->readdir_filename = malloc(PATH_MAX+1); - if(!sshc->readdir_filename) { - state(conn, SSH_SFTP_CLOSE); - sshc->actualcode = CURLE_OUT_OF_MEMORY; - break; - } - sshc->readdir_longentry = malloc(PATH_MAX+1); - if(!sshc->readdir_longentry) { - Curl_safefree(sshc->readdir_filename); - state(conn, SSH_SFTP_CLOSE); - sshc->actualcode = CURLE_OUT_OF_MEMORY; - break; - } - state(conn, SSH_SFTP_READDIR); - break; - - case SSH_SFTP_READDIR: - sshc->readdir_len = libssh2_sftp_readdir_ex(sshc->sftp_handle, - sshc->readdir_filename, - PATH_MAX, - sshc->readdir_longentry, - PATH_MAX, - &sshc->readdir_attrs); - if(sshc->readdir_len == LIBSSH2_ERROR_EAGAIN) { - rc = LIBSSH2_ERROR_EAGAIN; - break; - } - if(sshc->readdir_len > 0) { - sshc->readdir_filename[sshc->readdir_len] = '\0'; - - if(data->set.ftp_list_only) { - char *tmpLine; - - tmpLine = aprintf("%s\n", sshc->readdir_filename); - if(tmpLine == NULL) { - state(conn, SSH_SFTP_CLOSE); - sshc->actualcode = CURLE_OUT_OF_MEMORY; - break; - } - result = Curl_client_write(conn, CLIENTWRITE_BODY, - tmpLine, sshc->readdir_len+1); - free(tmpLine); - - if(result) { - state(conn, SSH_STOP); - break; - } - /* since this counts what we send to the client, we include the - newline in this counter */ - data->req.bytecount += sshc->readdir_len+1; - - /* output debug output if that is requested */ - if(data->set.verbose) { - Curl_debug(data, CURLINFO_DATA_OUT, sshc->readdir_filename, - sshc->readdir_len, conn); - } - } - else { - sshc->readdir_currLen = (int)strlen(sshc->readdir_longentry); - sshc->readdir_totalLen = 80 + sshc->readdir_currLen; - sshc->readdir_line = calloc(sshc->readdir_totalLen, 1); - if(!sshc->readdir_line) { - Curl_safefree(sshc->readdir_filename); - Curl_safefree(sshc->readdir_longentry); - state(conn, SSH_SFTP_CLOSE); - sshc->actualcode = CURLE_OUT_OF_MEMORY; - break; - } - - memcpy(sshc->readdir_line, sshc->readdir_longentry, - sshc->readdir_currLen); - if((sshc->readdir_attrs.flags & LIBSSH2_SFTP_ATTR_PERMISSIONS) && - ((sshc->readdir_attrs.permissions & LIBSSH2_SFTP_S_IFMT) == - LIBSSH2_SFTP_S_IFLNK)) { - sshc->readdir_linkPath = malloc(PATH_MAX + 1); - if(sshc->readdir_linkPath == NULL) { - Curl_safefree(sshc->readdir_filename); - Curl_safefree(sshc->readdir_longentry); - state(conn, SSH_SFTP_CLOSE); - sshc->actualcode = CURLE_OUT_OF_MEMORY; - break; - } - - snprintf(sshc->readdir_linkPath, PATH_MAX, "%s%s", sftp_scp->path, - sshc->readdir_filename); - state(conn, SSH_SFTP_READDIR_LINK); - break; - } - state(conn, SSH_SFTP_READDIR_BOTTOM); - break; - } - } - else if(sshc->readdir_len == 0) { - Curl_safefree(sshc->readdir_filename); - Curl_safefree(sshc->readdir_longentry); - state(conn, SSH_SFTP_READDIR_DONE); - break; - } - else if(sshc->readdir_len <= 0) { - err = sftp_libssh2_last_error(sshc->sftp_session); - result = sftp_libssh2_error_to_CURLE(err); - sshc->actualcode = result?result:CURLE_SSH; - failf(data, "Could not open remote file for reading: %s :: %d", - sftp_libssh2_strerror(err), - libssh2_session_last_errno(sshc->ssh_session)); - Curl_safefree(sshc->readdir_filename); - Curl_safefree(sshc->readdir_longentry); - state(conn, SSH_SFTP_CLOSE); - break; - } - break; - - case SSH_SFTP_READDIR_LINK: - sshc->readdir_len = - libssh2_sftp_symlink_ex(sshc->sftp_session, - sshc->readdir_linkPath, - curlx_uztoui(strlen(sshc->readdir_linkPath)), - sshc->readdir_filename, - PATH_MAX, LIBSSH2_SFTP_READLINK); - if(sshc->readdir_len == LIBSSH2_ERROR_EAGAIN) { - rc = LIBSSH2_ERROR_EAGAIN; - break; - } - Curl_safefree(sshc->readdir_linkPath); - - /* get room for the filename and extra output */ - sshc->readdir_totalLen += 4 + sshc->readdir_len; - new_readdir_line = Curl_saferealloc(sshc->readdir_line, - sshc->readdir_totalLen); - if(!new_readdir_line) { - sshc->readdir_line = NULL; - Curl_safefree(sshc->readdir_filename); - Curl_safefree(sshc->readdir_longentry); - state(conn, SSH_SFTP_CLOSE); - sshc->actualcode = CURLE_OUT_OF_MEMORY; - break; - } - sshc->readdir_line = new_readdir_line; - - sshc->readdir_currLen += snprintf(sshc->readdir_line + - sshc->readdir_currLen, - sshc->readdir_totalLen - - sshc->readdir_currLen, - " -> %s", - sshc->readdir_filename); - - state(conn, SSH_SFTP_READDIR_BOTTOM); - break; - - case SSH_SFTP_READDIR_BOTTOM: - sshc->readdir_currLen += snprintf(sshc->readdir_line + - sshc->readdir_currLen, - sshc->readdir_totalLen - - sshc->readdir_currLen, "\n"); - result = Curl_client_write(conn, CLIENTWRITE_BODY, - sshc->readdir_line, - sshc->readdir_currLen); - - if(!result) { - - /* output debug output if that is requested */ - if(data->set.verbose) { - Curl_debug(data, CURLINFO_DATA_OUT, sshc->readdir_line, - sshc->readdir_currLen, conn); - } - data->req.bytecount += sshc->readdir_currLen; - } - Curl_safefree(sshc->readdir_line); - if(result) { - state(conn, SSH_STOP); - } - else - state(conn, 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(sshc->readdir_filename); - Curl_safefree(sshc->readdir_longentry); - - /* no data to transfer */ - Curl_setup_transfer(conn, -1, -1, FALSE, NULL, -1, NULL); - state(conn, SSH_STOP); - break; - - case SSH_SFTP_DOWNLOAD_INIT: - /* - * Work on getting the specified file - */ - sshc->sftp_handle = - libssh2_sftp_open_ex(sshc->sftp_session, sftp_scp->path, - curlx_uztoui(strlen(sftp_scp->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; - } - else { - err = sftp_libssh2_last_error(sshc->sftp_session); - failf(data, "Could not open remote file for reading: %s", - sftp_libssh2_strerror(err)); - state(conn, SSH_SFTP_CLOSE); - result = sftp_libssh2_error_to_CURLE(err); - sshc->actualcode = result?result:CURLE_SSH; - break; - } - } - state(conn, SSH_SFTP_DOWNLOAD_STAT); - break; - - case SSH_SFTP_DOWNLOAD_STAT: - { - LIBSSH2_SFTP_ATTRIBUTES attrs; - - rc = libssh2_sftp_stat_ex(sshc->sftp_session, sftp_scp->path, - curlx_uztoui(strlen(sftp_scp->path)), - LIBSSH2_SFTP_STAT, &attrs); - if(rc == LIBSSH2_ERROR_EAGAIN) { - break; - } - else 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(conn->data->state.use_range) { - curl_off_t from, to; - char *ptr; - char *ptr2; - - from=curlx_strtoofft(conn->data->state.range, &ptr, 0); - while(*ptr && (ISSPACE(*ptr) || (*ptr=='-'))) - ptr++; - to=curlx_strtoofft(ptr, &ptr2, 0); - if((ptr == ptr2) /* no "to" value given */ - || (to >= size)) { - to = size - 1; - } - if(from < 0) { - /* from is relative to end of file */ - from += size; - } - if(from > size) { - failf(data, "Offset (%" - CURL_FORMAT_CURL_OFF_T ") was beyond file size (%" - CURL_FORMAT_CURL_OFF_T ")", from, attrs.filesize); - return CURLE_BAD_DOWNLOAD_RESUME; - } - if(from > to) { - from = to; - size = 0; - } - else { - size = to - from + 1; - } - - SFTP_SEEK(conn->proto.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, 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, attrs.filesize); - return CURLE_BAD_DOWNLOAD_RESUME; - } - } - /* Does a completed file need to be seeked and started or closed ? */ - /* 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(conn, -1, -1, FALSE, NULL, -1, NULL); - infof(data, "File already completely downloaded\n"); - state(conn, SSH_STOP); - break; - } - else { - Curl_setup_transfer(conn, FIRSTSOCKET, data->req.size, - FALSE, NULL, -1, NULL); - - /* 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(conn, SSH_SFTP_CLOSE); - sshc->actualcode = result; - } - else { - state(conn, SSH_STOP); - } - break; - - case SSH_SFTP_CLOSE: - if(sshc->sftp_handle) { - rc = libssh2_sftp_close(sshc->sftp_handle); - if(rc == LIBSSH2_ERROR_EAGAIN) { - break; - } - else if(rc < 0) { - infof(data, "Failed to close libssh2 file\n"); - } - sshc->sftp_handle = NULL; - } - if(sftp_scp) - Curl_safefree(sftp_scp->path); - - DEBUGF(infof(data, "SFTP DONE done\n")); - - /* 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(conn, sshc->nextstate); - sshc->nextstate = SSH_SFTP_CLOSE; - } - else { - state(conn, 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; - } - else if(rc < 0) { - infof(data, "Failed to close libssh2 file\n"); - } - sshc->sftp_handle = NULL; - } - if(sshc->sftp_session) { - rc = libssh2_sftp_shutdown(sshc->sftp_session); - if(rc == LIBSSH2_ERROR_EAGAIN) { - break; - } - else if(rc < 0) { - infof(data, "Failed to stop libssh2 sftp subsystem\n"); - } - sshc->sftp_session = NULL; - } - - Curl_safefree(sshc->homedir); - conn->data->state.most_recent_ftp_entrypath = NULL; - - state(conn, SSH_SESSION_DISCONNECT); - break; - - case SSH_SCP_TRANS_INIT: - result = ssh_getworkingpath(conn, sshc->homedir, &sftp_scp->path); - if(result) { - sshc->actualcode = result; - state(conn, SSH_STOP); - break; - } - - if(data->set.upload) { - if(data->state.infilesize < 0) { - failf(data, "SCP requires a known file size for upload"); - sshc->actualcode = CURLE_UPLOAD_FAILED; - state(conn, SSH_SCP_CHANNEL_FREE); - break; - } - state(conn, SSH_SCP_UPLOAD_INIT); - } - else { - state(conn, 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, sftp_scp->path, data->set.new_file_perms, - data->state.infilesize); - if(!sshc->ssh_channel) { - if(libssh2_session_last_errno(sshc->ssh_session) == - LIBSSH2_ERROR_EAGAIN) { - rc = LIBSSH2_ERROR_EAGAIN; - break; - } - else { - int ssh_err; - char *err_msg; - - ssh_err = (int)(libssh2_session_last_error(sshc->ssh_session, - &err_msg, NULL, 0)); - failf(conn->data, "%s", err_msg); - state(conn, SSH_SCP_CHANNEL_FREE); - sshc->actualcode = libssh2_session_error_to_CURLE(ssh_err); - break; - } - } - - /* upload data */ - Curl_setup_transfer(conn, -1, data->req.size, FALSE, NULL, - FIRSTSOCKET, NULL); - - /* not set by Curl_setup_transfer to preserve keepon bits */ - conn->sockfd = conn->writesockfd; - - if(result) { - state(conn, 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(conn, 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, - sftp_scp->path, &sb); -#else - libssh2_struct_stat sb; - memset(&sb, 0, sizeof(libssh2_struct_stat)); - sshc->ssh_channel = libssh2_scp_recv2(sshc->ssh_session, - sftp_scp->path, &sb); -#endif - - if(!sshc->ssh_channel) { - if(libssh2_session_last_errno(sshc->ssh_session) == - LIBSSH2_ERROR_EAGAIN) { - rc = LIBSSH2_ERROR_EAGAIN; - break; - } - else { - int ssh_err; - char *err_msg; - - ssh_err = (int)(libssh2_session_last_error(sshc->ssh_session, - &err_msg, NULL, 0)); - failf(conn->data, "%s", err_msg); - state(conn, 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(conn, FIRSTSOCKET, bytecount, FALSE, NULL, -1, NULL); - - /* 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(conn, SSH_SCP_CHANNEL_FREE); - sshc->actualcode = result; - } - else - state(conn, SSH_STOP); - } - break; - - case SSH_SCP_DONE: - if(data->set.upload) - state(conn, SSH_SCP_SEND_EOF); - else - state(conn, 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; - } - else if(rc) { - infof(data, "Failed to send libssh2 channel EOF\n"); - } - } - state(conn, 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; - } - else if(rc) { - infof(data, "Failed to get channel EOF: %d\n", rc); - } - } - state(conn, 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; - } - else if(rc) { - infof(data, "Channel failed to close: %d\n", rc); - } - } - state(conn, 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; - } - else if(rc < 0) { - infof(data, "Failed to free libssh2 scp subsystem\n"); - } - sshc->ssh_channel = NULL; - } - DEBUGF(infof(data, "SCP DONE phase complete\n")); -#if 0 /* PREV */ - state(conn, SSH_SESSION_DISCONNECT); -#endif - state(conn, 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; - } - else if(rc < 0) { - infof(data, "Failed to free libssh2 scp subsystem\n"); - } - sshc->ssh_channel = NULL; - } - - if(sshc->ssh_session) { - rc = libssh2_session_disconnect(sshc->ssh_session, "Shutdown"); - if(rc == LIBSSH2_ERROR_EAGAIN) { - break; - } - else if(rc < 0) { - infof(data, "Failed to disconnect libssh2 session\n"); - } - } - - Curl_safefree(sshc->homedir); - conn->data->state.most_recent_ftp_entrypath = NULL; - - state(conn, 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; - } - else if(rc < 0) { - infof(data, "Failed to disconnect from libssh2 agent\n"); - } - 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; - } - else if(rc < 0) { - infof(data, "Failed to free libssh2 session\n"); - } - 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); - - Curl_safefree(sshc->readdir_filename); - Curl_safefree(sshc->readdir_longentry); - Curl_safefree(sshc->readdir_line); - Curl_safefree(sshc->readdir_linkPath); - - /* 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(conn, SSH_STOP); - break; - - case SSH_QUIT: - /* fallthrough, just stop! */ - default: - /* internal error */ - sshc->nextstate = SSH_NO_STATE; - state(conn, 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_perform_getsock(const struct connectdata *conn, - curl_socket_t *sock, /* points to numsocks - number of sockets */ - int numsocks) -{ -#ifdef HAVE_LIBSSH2_SESSION_BLOCK_DIRECTION - int bitmap = GETSOCK_BLANK; - (void)numsocks; - - 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; -#else - /* if we don't know the direction we can use the generic *_getsock() - function even for the protocol_connect and doing states */ - return Curl_single_getsock(conn, sock, numsocks); -#endif -} - -/* 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 ssh_getsock(struct connectdata *conn, - curl_socket_t *sock, /* points to numsocks number - of sockets */ - int numsocks) -{ -#ifndef HAVE_LIBSSH2_SESSION_BLOCK_DIRECTION - (void)conn; - (void)sock; - (void)numsocks; - /* if we don't know any direction we can just play along as we used to and - not provide any sensible info */ - return GETSOCK_BLANK; -#else - /* if we know the direction we can use the generic *_getsock() function even - for the protocol_connect and doing states */ - return ssh_perform_getsock(conn, sock, numsocks); -#endif -} - -#ifdef HAVE_LIBSSH2_SESSION_BLOCK_DIRECTION -/* - * 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 connectdata *conn, bool block) -{ - 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; -} -#else - /* no libssh2 directional support so we simply don't know */ -#define ssh_block2waitfor(x,y) Curl_nop_stmt -#endif - -/* called repeatedly until done from multi.c */ -static CURLcode ssh_multi_statemach(struct connectdata *conn, bool *done) -{ - 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 */ - - result = ssh_statemach_act(conn, &block); - *done = (sshc->state == SSH_STOP) ? TRUE : FALSE; - ssh_block2waitfor(conn, block); - - return result; -} - -static CURLcode ssh_block_statemach(struct connectdata *conn, - bool duringconnect) -{ - struct ssh_conn *sshc = &conn->proto.sshc; - CURLcode result = CURLE_OK; - struct Curl_easy *data = conn->data; - - while((sshc->state != SSH_STOP) && !result) { - bool block; - long left; - - result = ssh_statemach_act(conn, &block); - if(result) - break; - - if(Curl_pgrsUpdate(conn)) - return CURLE_ABORTED_BY_CALLBACK; - else { - struct timeval now = Curl_tvnow(); - result = Curl_speedcheck(data, now); - if(result) - break; - } - - left = Curl_timeleft(data, NULL, duringconnect); - if(left < 0) { - failf(data, "Operation timed out"); - return CURLE_OPERATION_TIMEDOUT; - } - -#ifdef HAVE_LIBSSH2_SESSION_BLOCK_DIRECTION - if(!result && 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); /* ignore result */ - } -#endif - - } - - return result; -} - -/* - * SSH setup and connection - */ -static CURLcode ssh_setup_connection(struct connectdata *conn) -{ - struct SSHPROTO *ssh; - - conn->data->req.protop = 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; - -/* - * 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 connectdata *conn, bool *done) -{ -#ifdef CURL_LIBSSH2_DEBUG - curl_socket_t sock; -#endif - struct ssh_conn *ssh; - CURLcode result; - struct Curl_easy *data = conn->data; - - /* initialize per-handle data if not already */ - if(!data->req.protop) - ssh_setup_connection(conn); - - /* We default to persistent connections. We set this already in this connect - function to make the re-use 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; - -#ifdef CURL_LIBSSH2_DEBUG - if(conn->user) { - infof(data, "User: %s\n", conn->user); - } - if(conn->passwd) { - infof(data, "Password: %s\n", conn->passwd); - } - sock = conn->sock[FIRSTSOCKET]; -#endif /* CURL_LIBSSH2_DEBUG */ - - ssh->ssh_session = libssh2_session_init_ex(my_libssh2_malloc, - my_libssh2_free, - my_libssh2_realloc, conn); - if(ssh->ssh_session == NULL) { - failf(data, "Failure initialising ssh session"); - return CURLE_FAILED_INIT; - } - -#ifdef HAVE_LIBSSH2_KNOWNHOST_API - if(data->set.str[STRING_SSH_KNOWNHOSTS]) { - int rc; - ssh->kh = libssh2_knownhost_init(ssh->ssh_session); - if(!ssh->kh) { - /* eeek. TODO: free the ssh_session! */ - return CURLE_FAILED_INIT; - } - - /* read all known hosts from there */ - rc = libssh2_knownhost_readfile(ssh->kh, - data->set.str[STRING_SSH_KNOWNHOSTS], - LIBSSH2_KNOWNHOST_FILE_OPENSSH); - if(rc < 0) - infof(data, "Failed to read known hosts from %s\n", - data->set.str[STRING_SSH_KNOWNHOSTS]); - } -#endif /* HAVE_LIBSSH2_KNOWNHOST_API */ - -#ifdef CURL_LIBSSH2_DEBUG - libssh2_trace(ssh->ssh_session, ~0); - infof(data, "SSH socket: %d\n", (int)sock); -#endif /* CURL_LIBSSH2_DEBUG */ - - state(conn, SSH_INIT); - - result = ssh_multi_statemach(conn, 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 connectdata *conn, - bool *connected, - bool *dophase_done) -{ - CURLcode result = CURLE_OK; - - DEBUGF(infof(conn->data, "DO phase starts\n")); - - *dophase_done = FALSE; /* not done yet */ - - /* start the first command in the DO phase */ - state(conn, SSH_SCP_TRANS_INIT); - - /* run the state-machine */ - result = ssh_multi_statemach(conn, dophase_done); - - *connected = conn->bits.tcpconnect[FIRSTSOCKET]; - - if(*dophase_done) { - DEBUGF(infof(conn->data, "DO phase is complete\n")); - } - - return result; -} - -/* called from multi.c while DOing */ -static CURLcode scp_doing(struct connectdata *conn, - bool *dophase_done) -{ - CURLcode result; - result = ssh_multi_statemach(conn, dophase_done); - - if(*dophase_done) { - DEBUGF(infof(conn->data, "DO phase is complete\n")); - } - 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 connectdata *conn, bool *done) -{ - CURLcode result; - bool connected = 0; - struct Curl_easy *data = conn->data; - 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(conn, &connected, done); - else - result = sftp_perform(conn, &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 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(conn, SSH_SESSION_DISCONNECT); - - result = ssh_block_statemach(conn, FALSE); - } - - return result; -} - -/* generic done function for both SCP and SFTP called from their specific - done functions */ -static CURLcode ssh_done(struct connectdata *conn, CURLcode status) -{ - CURLcode result = CURLE_OK; - struct SSHPROTO *sftp_scp = conn->data->req.protop; - - if(!status) { - /* run the state-machine - - TODO: when the multi interface is used, this _really_ should be using - the ssh_multi_statemach function but we have no general support for - non-blocking DONE operations! - */ - result = ssh_block_statemach(conn, FALSE); - } - else - result = status; - - if(sftp_scp) - Curl_safefree(sftp_scp->path); - if(Curl_pgrsDone(conn)) - return CURLE_ABORTED_BY_CALLBACK; - - conn->data->req.keepon = 0; /* clear all bits */ - return result; -} - - -static CURLcode scp_done(struct connectdata *conn, CURLcode status, - bool premature) -{ - (void)premature; /* not used */ - - if(!status) - state(conn, SSH_SCP_DONE); - - return ssh_done(conn, status); - -} - -static ssize_t scp_send(struct connectdata *conn, int sockindex, - const void *mem, size_t len, CURLcode *err) -{ - ssize_t nwrite; - (void)sockindex; /* we only support SCP on the fixed known primary socket */ - - /* libssh2_channel_write() returns int! */ - nwrite = (ssize_t) - libssh2_channel_write(conn->proto.sshc.ssh_channel, mem, len); - - ssh_block2waitfor(conn, (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 connectdata *conn, int sockindex, - char *mem, size_t len, CURLcode *err) -{ - ssize_t nread; - (void)sockindex; /* we only support SCP on the fixed known primary socket */ - - /* libssh2_channel_read() returns int */ - nread = (ssize_t) - libssh2_channel_read(conn->proto.sshc.ssh_channel, mem, len); - - ssh_block2waitfor(conn, (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 connectdata *conn, - bool *connected, - bool *dophase_done) -{ - CURLcode result = CURLE_OK; - - DEBUGF(infof(conn->data, "DO phase starts\n")); - - *dophase_done = FALSE; /* not done yet */ - - /* start the first command in the DO phase */ - state(conn, SSH_SFTP_QUOTE_INIT); - - /* run the state-machine */ - result = ssh_multi_statemach(conn, dophase_done); - - *connected = conn->bits.tcpconnect[FIRSTSOCKET]; - - if(*dophase_done) { - DEBUGF(infof(conn->data, "DO phase is complete\n")); - } - - return result; -} - -/* called from multi.c while DOing */ -static CURLcode sftp_doing(struct connectdata *conn, - bool *dophase_done) -{ - CURLcode result = ssh_multi_statemach(conn, dophase_done); - - if(*dophase_done) { - DEBUGF(infof(conn->data, "DO phase is complete\n")); - } - 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 connectdata *conn, bool dead_connection) -{ - CURLcode result = CURLE_OK; - (void) dead_connection; - - DEBUGF(infof(conn->data, "SSH DISCONNECT starts now\n")); - - if(conn->proto.sshc.ssh_session) { - /* only if there's a session still around to use! */ - state(conn, SSH_SFTP_SHUTDOWN); - result = ssh_block_statemach(conn, FALSE); - } - - DEBUGF(infof(conn->data, "SSH DISCONNECT is done\n")); - - return result; - -} - -static CURLcode sftp_done(struct connectdata *conn, CURLcode status, - bool premature) -{ - 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(!status && !premature && conn->data->set.postquote) { - sshc->nextstate = SSH_SFTP_POSTQUOTE_INIT; - state(conn, SSH_SFTP_CLOSE); - } - else - state(conn, SSH_SFTP_CLOSE); - } - return ssh_done(conn, status); -} - -/* return number of sent bytes */ -static ssize_t sftp_send(struct connectdata *conn, int sockindex, - const void *mem, size_t len, CURLcode *err) -{ - ssize_t nwrite; /* libssh2_sftp_write() used to return size_t in 0.14 - but is changed to ssize_t in 0.15. These days we don't - support libssh2 0.15*/ - (void)sockindex; - - nwrite = libssh2_sftp_write(conn->proto.sshc.sftp_handle, mem, len); - - ssh_block2waitfor(conn, (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 connectdata *conn, int sockindex, - char *mem, size_t len, CURLcode *err) -{ - ssize_t nread; - (void)sockindex; - - nread = libssh2_sftp_read(conn->proto.sshc.sftp_handle, mem, len); - - ssh_block2waitfor(conn, (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; -} - -/* The get_pathname() function is being borrowed from OpenSSH sftp.c - version 4.6p1. */ -/* - * Copyright (c) 2001-2004 Damien Miller - * - * 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. - */ -static CURLcode -get_pathname(const char **cpp, char **path) -{ - const char *cp = *cpp, *end; - char quot; - unsigned int i, j; - static const char WHITESPACE[] = " \t\r\n"; - - cp += strspn(cp, WHITESPACE); - if(!*cp) { - *cpp = cp; - *path = NULL; - return CURLE_QUOTE_ERROR; - } - - *path = malloc(strlen(cp) + 1); - if(*path == NULL) - 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 */ - /*error("Unterminated quote");*/ - goto fail; - } - if(cp[i] == '\\') { /* Escaped characters */ - i++; - if(cp[i] != '\'' && cp[i] != '\"' && - cp[i] != '\\') { - /*error("Bad escaped character '\\%c'", - cp[i]);*/ - goto fail; - } - } - (*path)[j++] = cp[i]; - } - - if(j == 0) { - /*error("Empty quotes");*/ - goto fail; - } - *cpp = cp + i + strspn(cp + i, WHITESPACE); - } - else { - /* Read to end of filename */ - end = strpbrk(cp, WHITESPACE); - if(end == NULL) - end = strchr(cp, '\0'); - *cpp = end + strspn(end, WHITESPACE); - - memcpy(*path, cp, end - cp); - (*path)[end - cp] = '\0'; - } - return CURLE_OK; - - fail: - Curl_safefree(*path); - return CURLE_QUOTE_ERROR; -} - - -static const char *sftp_libssh2_strerror(int 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"; -} - -#endif /* USE_LIBSSH2 */ diff --git a/contrib/curl/lib/strcase.c b/contrib/curl/lib/strcase.c index a750f7b4dd..b22dd31fc8 100644 --- a/contrib/curl/lib/strcase.c +++ b/contrib/curl/lib/strcase.c @@ -5,11 +5,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2016, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -18,6 +18,8 @@ * 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" @@ -26,119 +28,99 @@ #include "strcase.h" -/* Portable, consistent toupper (remember EBCDIC). Do not use toupper() because - its behavior is altered by the current locale. */ +/* 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) { -#if !defined(CURL_DOES_CONVERSIONS) - if(in >= 'a' && in <= 'z') - return (char)('A' + in - 'a'); -#else - switch(in) { - case 'a': - return 'A'; - case 'b': - return 'B'; - case 'c': - return 'C'; - case 'd': - return 'D'; - case 'e': - return 'E'; - case 'f': - return 'F'; - case 'g': - return 'G'; - case 'h': - return 'H'; - case 'i': - return 'I'; - case 'j': - return 'J'; - case 'k': - return 'K'; - case 'l': - return 'L'; - case 'm': - return 'M'; - case 'n': - return 'N'; - case 'o': - return 'O'; - case 'p': - return 'P'; - case 'q': - return 'Q'; - case 'r': - return 'R'; - case 's': - return 'S'; - case 't': - return 'T'; - case 'u': - return 'U'; - case 'v': - return 'V'; - case 'w': - return 'W'; - case 'x': - return 'X'; - case 'y': - return 'Y'; - case 'z': - return 'Z'; - } -#endif + return (char)touppermap[(unsigned char) in]; +} - return in; + +/* Portable, consistent tolower. Do not use tolower() because its behavior is + altered by the current locale. */ +char Curl_raw_tolower(char in) +{ + return (char)tolowermap[(unsigned char) in]; } /* - * Curl_raw_equal() is for doing "raw" case insensitive strings. This is meant + * 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 - * some further explanation to why this function is necessary. - * - * The function is capable of comparing a-z case insensitively even for - * non-ascii. - * - * @unittest: 1301 + * this. See https://daniel.haxx.se/blog/2008/10/15/strcasecmp-in-turkish/ for + * further explanations as to why this function is necessary. */ -int Curl_strcasecompare(const char *first, const char *second) +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 */ - break; + /* get out of the loop as soon as they do not match */ + return 0; first++; second++; } - /* we do the comparison here (possibly again), just to make sure that if the - loop above is skipped because one of the strings reached zero, we must not - return this as a successful match */ - return (Curl_raw_toupper(*first) == Curl_raw_toupper(*second)); + /* If we are 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; } -int Curl_safe_strcasecompare(const char *first, const char *second) +/* --- public function --- */ +int curl_strequal(const char *first, const char *second) { if(first && second) /* both pointers point to something then compare them */ - return Curl_strcasecompare(first, second); - else - /* if both pointers are NULL then treat them as equal */ - return (NULL == first && NULL == second); + return casecompare(first, second); + + /* if both pointers are NULL then treat them as equal */ + return (NULL == first && NULL == second); } -/* - * @unittest: 1301 - */ -int Curl_strncasecompare(const char *first, const char *second, size_t max) +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)) { - break; - } + if(Curl_raw_toupper(*first) != Curl_raw_toupper(*second)) + return 0; max--; first++; second++; @@ -149,8 +131,18 @@ int Curl_strncasecompare(const char *first, const char *second, size_t max) return Curl_raw_toupper(*first) == Curl_raw_toupper(*second); } -/* 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 +/* --- 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. */ @@ -164,13 +156,49 @@ void Curl_strntoupper(char *dest, const char *src, size_t n) } while(*src++ && --n); } -/* --- public functions --- */ +/* 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; -int curl_strequal(const char *first, const char *second) + 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) { - return Curl_strcasecompare(first, second); + if(a && b) + return !strcmp(a, b); + return !a && !b; } -int curl_strnequal(const char *first, const char *second, size_t max) + +/* + * 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) { - return Curl_strncasecompare(first, second, max); + 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/contrib/curl/lib/strcase.h b/contrib/curl/lib/strcase.h index ea2abc8b60..8c50bbcba6 100644 --- a/contrib/curl/lib/strcase.h +++ b/contrib/curl/lib/strcase.h @@ -7,11 +7,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2016, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -20,6 +20,8 @@ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY * KIND, either express or implied. * + * SPDX-License-Identifier: curl + * ***************************************************************************/ #include @@ -28,24 +30,25 @@ * 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 even for - * non-ascii. + * 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_strcasecompare(a,b) -#define strncasecompare(a,b,c) Curl_strncasecompare(a,b,c) - -int Curl_strcasecompare(const char *first, const char *second); -int Curl_safe_strcasecompare(const char *first, const char *second); -int Curl_strncasecompare(const char *first, const char *second, size_t max); +#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 zero-byte terminated */ -#define checkprefix(a,b) curl_strnequal(a,b,strlen(a)) + 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); -char Curl_raw_toupper(char in); +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/contrib/curl/lib/strdup.c b/contrib/curl/lib/strdup.c index 136b693774..299c9cc36b 100644 --- a/contrib/curl/lib/strdup.c +++ b/contrib/curl/lib/strdup.c @@ -5,11 +5,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2016, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -18,12 +18,18 @@ * 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 +#ifdef _WIN32 +#include +#endif + #include "strdup.h" #include "curl_memory.h" @@ -31,7 +37,7 @@ #include "memdebug.h" #ifndef HAVE_STRDUP -char *curlx_strdup(const char *str) +char *Curl_strdup(const char *str) { size_t len; char *newstr; @@ -39,19 +45,36 @@ char *curlx_strdup(const char *str) if(!str) return (char *)NULL; - len = strlen(str); + len = strlen(str) + 1; - if(len >= ((size_t)-1) / sizeof(char)) - return (char *)NULL; - - newstr = malloc((len+1)*sizeof(char)); + newstr = malloc(len); if(!newstr) return (char *)NULL; - memcpy(newstr, str, (len+1)*sizeof(char)); - + 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 @@ -76,12 +99,32 @@ void *Curl_memdup(const void *src, size_t length) return buffer; } +/*************************************************************************** + * + * Curl_memdup0(source, length) + * + * Copies the 'source' string to a newly allocated buffer (that is returned). + * Copies 'length' bytes then adds a null terminator. + * + * Returns the new pointer or NULL on failure. + * + ***************************************************************************/ +void *Curl_memdup0(const char *src, size_t length) +{ + char *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 zero, it will free the data and return a failure. + * 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 diff --git a/contrib/curl/lib/strdup.h b/contrib/curl/lib/strdup.h index ae3d5d0115..238a2611f6 100644 --- a/contrib/curl/lib/strdup.h +++ b/contrib/curl/lib/strdup.h @@ -7,11 +7,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2016, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -20,13 +20,19 @@ * 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 -extern char *curlx_strdup(const char *str); +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_memdup0(const char *src, size_t length); #endif /* HEADER_CURL_STRDUP_H */ diff --git a/contrib/curl/lib/strerror.c b/contrib/curl/lib/strerror.c index 7e5cde47b5..76a8ba2d7e 100644 --- a/contrib/curl/lib/strerror.c +++ b/contrib/curl/lib/strerror.c @@ -5,11 +5,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 2004 - 2016, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -18,18 +18,17 @@ * 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_VXWORKS_STRERROR_R)) || \ - (defined(HAVE_POSIX_STRERROR_R) && defined(HAVE_VXWORKS_STRERROR_R)) || \ - (defined(HAVE_GLIBC_STRERROR_R) && defined(HAVE_VXWORKS_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 or vxworks-style" +# error "strerror_r MUST be either POSIX, glibc style" # endif #endif @@ -49,6 +48,10 @@ #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) { @@ -71,13 +74,13 @@ curl_easy_strerror(CURLcode error) " this libcurl due to a build-time decision."; case CURLE_COULDNT_RESOLVE_PROXY: - return "Couldn't resolve proxy name"; + return "Could not resolve proxy name"; case CURLE_COULDNT_RESOLVE_HOST: - return "Couldn't resolve host name"; + return "Could not resolve hostname"; case CURLE_COULDNT_CONNECT: - return "Couldn't connect to server"; + return "Could not connect to server"; case CURLE_WEIRD_SERVER_REPLY: return "Weird server reply"; @@ -104,19 +107,19 @@ curl_easy_strerror(CURLcode error) return "FTP: unknown 227 response format"; case CURLE_FTP_CANT_GET_HOST: - return "FTP: can't figure out the host in the PASV response"; + return "FTP: cannot 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"; + return "FTP: could not 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"; + return "FTP: could not retrieve (RETR failed) the specified file"; case CURLE_QUOTE_ERROR: return "Quote command returned error"; @@ -155,10 +158,10 @@ curl_easy_strerror(CURLcode error) return "SSL connect error"; case CURLE_BAD_DOWNLOAD_RESUME: - return "Couldn't resume download"; + return "Could not resume download"; case CURLE_FILE_COULDNT_READ_FILE: - return "Couldn't read a file:// file"; + return "Could not read a file:// file"; case CURLE_LDAP_CANNOT_BIND: return "LDAP: cannot bind"; @@ -178,17 +181,14 @@ curl_easy_strerror(CURLcode error) case CURLE_INTERFACE_FAILED: return "Failed binding local connection end"; - case CURLE_TOO_MANY_REDIRECTS : + 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_TELNET_OPTION_SYNTAX : - return "Malformed telnet option"; - - case CURLE_PEER_FAILED_VERIFICATION: - return "SSL peer certificate or SSH remote key was not OK"; + case CURLE_SETOPT_OPTION_SYNTAX: + return "Malformed option provided in a setopt"; case CURLE_GOT_NOTHING: return "Server returned nothing (no headers, no data)"; @@ -212,11 +212,10 @@ curl_easy_strerror(CURLcode error) return "Problem with the local SSL certificate"; case CURLE_SSL_CIPHER: - return "Couldn't use specified SSL cipher"; + return "Could not use specified SSL cipher"; - case CURLE_SSL_CACERT: - return "Peer certificate cannot be authenticated with given CA " - "certificates"; + 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?)"; @@ -224,9 +223,6 @@ curl_easy_strerror(CURLcode error) case CURLE_BAD_CONTENT_ENCODING: return "Unrecognized or bad HTTP Content or Transfer-Encoding"; - case CURLE_LDAP_INVALID_URL: - return "Invalid LDAP URL"; - case CURLE_FILESIZE_EXCEEDED: return "Maximum file size exceeded"; @@ -269,12 +265,6 @@ curl_easy_strerror(CURLcode error) case CURLE_TFTP_NOSUCHUSER: return "TFTP: No such user"; - case CURLE_CONV_FAILED: - return "Conversion failed"; - - case CURLE_CONV_REQD: - return "Caller must register CURLOPT_CONV_ callback options"; - case CURLE_REMOTE_FILE_NOT_FOUND: return "Remote file not found"; @@ -308,6 +298,33 @@ curl_easy_strerror(CURLcode error) 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"; + + case CURLE_TOO_LARGE: + return "A value or data field grew larger than allowed"; + + case CURLE_ECH_REQUIRED: + return "ECH attempted but failed"; + /* error codes not used by current libcurl */ case CURLE_OBSOLETE20: case CURLE_OBSOLETE24: @@ -317,23 +334,26 @@ curl_easy_strerror(CURLcode error) 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. + * By using gcc -Wall -Werror, you cannot 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? + * 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. Something is broken for the user's application, + * anyways, so does it matter how fast it _does not_ 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. + * 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 @@ -376,6 +396,21 @@ curl_multi_strerror(CURLMcode error) 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; } @@ -425,16 +460,142 @@ curl_share_strerror(CURLSHcode error) #endif } -#ifdef USE_WINSOCK +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"; -/* This function handles most / all (?) Winsock errors curl is able to produce. + case CURLUE_TOO_LARGE: + return "A value or data field is larger than allowed"; + + 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) +get_winsock_error(int err, char *buf, size_t len) { +#ifndef CURL_DISABLE_VERBOSE_STRINGS const char *p; + size_t alen; +#endif -#ifndef CURL_DISABLE_VERBOSE_STRINGS + if(!len) + return NULL; + + *buf = '\0'; + +#ifdef CURL_DISABLE_VERBOSE_STRINGS + (void)err; + return NULL; +#else switch(err) { case WSAEINTR: p = "Call interrupted"; @@ -603,66 +764,110 @@ get_winsock_error (int err, char *buf, size_t len) default: return NULL; } -#else - if(!err) - return NULL; - else - p = "error"; -#endif - strncpy(buf, p, len); - buf [len-1] = '\0'; + alen = strlen(p); + if(alen < len) + strcpy(buf, p); 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, (DWORD)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. + * be systems that do not do proper range checking in there themselves. * - * We don't do range checking (on systems other than Windows) since there is + * We do not 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(struct connectdata *conn, int err) +const char *Curl_strerror(int err, char *buf, size_t buflen) { - char *buf, *p; - size_t max; - int old_errno = ERRNO; +#ifdef PRESERVE_WINDOWS_ERROR_CODE + DWORD old_win_err = GetLastError(); +#endif + int old_errno = errno; + char *p; - DEBUGASSERT(conn); + if(!buflen) + return NULL; + +#ifndef _WIN32 DEBUGASSERT(err >= 0); +#endif - buf = conn->syserr_buf; - max = sizeof(conn->syserr_buf)-1; *buf = '\0'; -#ifdef USE_WINSOCK - -#ifdef _WIN32_WCE - { - wchar_t wbuf[256]; - wbuf[0] = L'\0'; - - FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, err, - LANG_NEUTRAL, wbuf, sizeof(wbuf)/sizeof(wchar_t), NULL); - wcstombs(buf, wbuf, max); - } -#else +#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, strerror(err), max); - else { - if(!get_winsock_error(err, buf, max) && - !FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM, NULL, err, - LANG_NEUTRAL, buf, (DWORD)max, NULL)) - snprintf(buf, max, "Unknown error %d (%#x)", err, err); - } + msnprintf(buf, buflen, "%s", sys_errlist[err]); + else #endif - -#else /* not USE_WINSOCK coming up */ + { + if( +#ifdef USE_WINSOCK + !get_winsock_error(err, buf, buflen) && +#endif + !get_winapi_error(err, buf, buflen)) + msnprintf(buf, buflen, "Unknown error %d (%#x)", err, err); + } +#else /* not Windows coming up */ #if defined(HAVE_STRERROR_R) && defined(HAVE_POSIX_STRERROR_R) /* @@ -670,9 +875,9 @@ const char *Curl_strerror(struct connectdata *conn, int err) * 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 != strerror_r(err, buf, buflen)) { if('\0' == buf[0]) - snprintf(buf, max, "Unknown error %d", err); + msnprintf(buf, buflen, "Unknown error %d", err); } #elif defined(HAVE_STRERROR_R) && defined(HAVE_GLIBC_STRERROR_R) /* @@ -684,35 +889,22 @@ const char *Curl_strerror(struct connectdata *conn, int err) char buffer[256]; char *msg = strerror_r(err, buffer, sizeof(buffer)); if(msg) - strncpy(buf, msg, max); - else - snprintf(buf, max, "Unknown error %d", err); - } -#elif defined(HAVE_STRERROR_R) && defined(HAVE_VXWORKS_STRERROR_R) - /* - * The vxworks-style strerror_r() does use the buffer we pass to the function. - * The buffer size should be at least NAME_MAX (256) - */ - { - char buffer[256]; - if(OK == strerror_r(err, buffer)) - strncpy(buf, buffer, max); + msnprintf(buf, buflen, "%s", msg); else - snprintf(buf, max, "Unknown error %d", err); + msnprintf(buf, buflen, "Unknown error %d", err); } #else { - char *msg = strerror(err); + /* !checksrc! disable STRERROR 1 */ + const char *msg = strerror(err); if(msg) - strncpy(buf, msg, max); + msnprintf(buf, buflen, "%s", msg); else - snprintf(buf, max, "Unknown error %d", err); + msnprintf(buf, buflen, "Unknown error %d", err); } #endif -#endif /* end of ! USE_WINSOCK */ - - buf[max] = '\0'; /* make sure the string is zero terminated */ +#endif /* end of not Windows */ /* strip trailing '\r\n' or '\n'. */ p = strrchr(buf, '\n'); @@ -722,351 +914,203 @@ const char *Curl_strerror(struct connectdata *conn, int err) if(p && (p - buf) >= 1) *p = '\0'; - if(old_errno != ERRNO) - SET_ERRNO(old_errno); + if(errno != old_errno) + errno = old_errno; + +#ifdef PRESERVE_WINDOWS_ERROR_CODE + if(old_win_err != GetLastError()) + SetLastError(old_win_err); +#endif return buf; } -#ifdef USE_WINDOWS_SSPI -const char *Curl_sspi_strerror (struct connectdata *conn, int err) +/* + * 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 - char txtbuf[80]; - char msgbuf[sizeof(conn->syserr_buf)]; - char *p, *str, *msg = NULL; - bool msg_formatted = FALSE; - int old_errno; + if(!get_winapi_error((int)err, buf, buflen)) { + msnprintf(buf, buflen, "Unknown error %lu (0x%08lX)", err, err); + } +#else + { + const char *txt = (err == ERROR_SUCCESS) ? "No error" : "Error"; + if(strlen(txt) < buflen) + strcpy(buf, txt); + } +#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; - char *outbuf; - size_t outmax; - DEBUGASSERT(conn); + if(!buflen) + return NULL; - outbuf = conn->syserr_buf; - outmax = sizeof(conn->syserr_buf)-1; - *outbuf = '\0'; + *buf = '\0'; #ifndef CURL_DISABLE_VERBOSE_STRINGS - old_errno = ERRNO; - switch(err) { case SEC_E_OK: txt = "No error"; break; - case CRYPT_E_REVOKED: - txt = "CRYPT_E_REVOKED"; - break; - case SEC_E_ALGORITHM_MISMATCH: - txt = "SEC_E_ALGORITHM_MISMATCH"; - break; - case SEC_E_BAD_BINDINGS: - txt = "SEC_E_BAD_BINDINGS"; - break; - case SEC_E_BAD_PKGID: - txt = "SEC_E_BAD_PKGID"; - break; - case SEC_E_BUFFER_TOO_SMALL: - txt = "SEC_E_BUFFER_TOO_SMALL"; - break; - case SEC_E_CANNOT_INSTALL: - txt = "SEC_E_CANNOT_INSTALL"; - break; - case SEC_E_CANNOT_PACK: - txt = "SEC_E_CANNOT_PACK"; - break; - case SEC_E_CERT_EXPIRED: - txt = "SEC_E_CERT_EXPIRED"; - break; - case SEC_E_CERT_UNKNOWN: - txt = "SEC_E_CERT_UNKNOWN"; - break; - case SEC_E_CERT_WRONG_USAGE: - txt = "SEC_E_CERT_WRONG_USAGE"; - break; - case SEC_E_CONTEXT_EXPIRED: - txt = "SEC_E_CONTEXT_EXPIRED"; - break; - case SEC_E_CROSSREALM_DELEGATION_FAILURE: - txt = "SEC_E_CROSSREALM_DELEGATION_FAILURE"; - break; - case SEC_E_CRYPTO_SYSTEM_INVALID: - txt = "SEC_E_CRYPTO_SYSTEM_INVALID"; - break; - case SEC_E_DECRYPT_FAILURE: - txt = "SEC_E_DECRYPT_FAILURE"; - break; - case SEC_E_DELEGATION_POLICY: - txt = "SEC_E_DELEGATION_POLICY"; - break; - case SEC_E_DELEGATION_REQUIRED: - txt = "SEC_E_DELEGATION_REQUIRED"; - break; - case SEC_E_DOWNGRADE_DETECTED: - txt = "SEC_E_DOWNGRADE_DETECTED"; - break; - case SEC_E_ENCRYPT_FAILURE: - txt = "SEC_E_ENCRYPT_FAILURE"; - break; - case SEC_E_ILLEGAL_MESSAGE: - txt = "SEC_E_ILLEGAL_MESSAGE"; - break; - case SEC_E_INCOMPLETE_CREDENTIALS: - txt = "SEC_E_INCOMPLETE_CREDENTIALS"; - break; - case SEC_E_INCOMPLETE_MESSAGE: - txt = "SEC_E_INCOMPLETE_MESSAGE"; - break; - case SEC_E_INSUFFICIENT_MEMORY: - txt = "SEC_E_INSUFFICIENT_MEMORY"; - break; - case SEC_E_INTERNAL_ERROR: - txt = "SEC_E_INTERNAL_ERROR"; - break; - case SEC_E_INVALID_HANDLE: - txt = "SEC_E_INVALID_HANDLE"; - break; - case SEC_E_INVALID_PARAMETER: - txt = "SEC_E_INVALID_PARAMETER"; - break; - case SEC_E_INVALID_TOKEN: - txt = "SEC_E_INVALID_TOKEN"; - break; - case SEC_E_ISSUING_CA_UNTRUSTED: - txt = "SEC_E_ISSUING_CA_UNTRUSTED"; - break; - case SEC_E_ISSUING_CA_UNTRUSTED_KDC: - txt = "SEC_E_ISSUING_CA_UNTRUSTED_KDC"; - break; - case SEC_E_KDC_CERT_EXPIRED: - txt = "SEC_E_KDC_CERT_EXPIRED"; - break; - case SEC_E_KDC_CERT_REVOKED: - txt = "SEC_E_KDC_CERT_REVOKED"; - break; - case SEC_E_KDC_INVALID_REQUEST: - txt = "SEC_E_KDC_INVALID_REQUEST"; - break; - case SEC_E_KDC_UNABLE_TO_REFER: - txt = "SEC_E_KDC_UNABLE_TO_REFER"; - break; - case SEC_E_KDC_UNKNOWN_ETYPE: - txt = "SEC_E_KDC_UNKNOWN_ETYPE"; - break; - case SEC_E_LOGON_DENIED: - txt = "SEC_E_LOGON_DENIED"; - break; - case SEC_E_MAX_REFERRALS_EXCEEDED: - txt = "SEC_E_MAX_REFERRALS_EXCEEDED"; - break; - case SEC_E_MESSAGE_ALTERED: - txt = "SEC_E_MESSAGE_ALTERED"; - break; - case SEC_E_MULTIPLE_ACCOUNTS: - txt = "SEC_E_MULTIPLE_ACCOUNTS"; - break; - case SEC_E_MUST_BE_KDC: - txt = "SEC_E_MUST_BE_KDC"; - break; - case SEC_E_NOT_OWNER: - txt = "SEC_E_NOT_OWNER"; - break; - case SEC_E_NO_AUTHENTICATING_AUTHORITY: - txt = "SEC_E_NO_AUTHENTICATING_AUTHORITY"; - break; - case SEC_E_NO_CREDENTIALS: - txt = "SEC_E_NO_CREDENTIALS"; - break; - case SEC_E_NO_IMPERSONATION: - txt = "SEC_E_NO_IMPERSONATION"; - break; - case SEC_E_NO_IP_ADDRESSES: - txt = "SEC_E_NO_IP_ADDRESSES"; - break; - case SEC_E_NO_KERB_KEY: - txt = "SEC_E_NO_KERB_KEY"; - break; - case SEC_E_NO_PA_DATA: - txt = "SEC_E_NO_PA_DATA"; - break; - case SEC_E_NO_S4U_PROT_SUPPORT: - txt = "SEC_E_NO_S4U_PROT_SUPPORT"; - break; - case SEC_E_NO_TGT_REPLY: - txt = "SEC_E_NO_TGT_REPLY"; - break; - case SEC_E_OUT_OF_SEQUENCE: - txt = "SEC_E_OUT_OF_SEQUENCE"; - break; - case SEC_E_PKINIT_CLIENT_FAILURE: - txt = "SEC_E_PKINIT_CLIENT_FAILURE"; - break; - case SEC_E_PKINIT_NAME_MISMATCH: - txt = "SEC_E_PKINIT_NAME_MISMATCH"; - break; - case SEC_E_POLICY_NLTM_ONLY: - txt = "SEC_E_POLICY_NLTM_ONLY"; - break; - case SEC_E_QOP_NOT_SUPPORTED: - txt = "SEC_E_QOP_NOT_SUPPORTED"; - break; - case SEC_E_REVOCATION_OFFLINE_C: - txt = "SEC_E_REVOCATION_OFFLINE_C"; - break; - case SEC_E_REVOCATION_OFFLINE_KDC: - txt = "SEC_E_REVOCATION_OFFLINE_KDC"; - break; - case SEC_E_SECPKG_NOT_FOUND: - txt = "SEC_E_SECPKG_NOT_FOUND"; - break; - case SEC_E_SECURITY_QOS_FAILED: - txt = "SEC_E_SECURITY_QOS_FAILED"; - break; - case SEC_E_SHUTDOWN_IN_PROGRESS: - txt = "SEC_E_SHUTDOWN_IN_PROGRESS"; - break; - case SEC_E_SMARTCARD_CERT_EXPIRED: - txt = "SEC_E_SMARTCARD_CERT_EXPIRED"; - break; - case SEC_E_SMARTCARD_CERT_REVOKED: - txt = "SEC_E_SMARTCARD_CERT_REVOKED"; - break; - case SEC_E_SMARTCARD_LOGON_REQUIRED: - txt = "SEC_E_SMARTCARD_LOGON_REQUIRED"; - break; - case SEC_E_STRONG_CRYPTO_NOT_SUPPORTED: - txt = "SEC_E_STRONG_CRYPTO_NOT_SUPPORTED"; - break; - case SEC_E_TARGET_UNKNOWN: - txt = "SEC_E_TARGET_UNKNOWN"; - break; - case SEC_E_TIME_SKEW: - txt = "SEC_E_TIME_SKEW"; - break; - case SEC_E_TOO_MANY_PRINCIPALS: - txt = "SEC_E_TOO_MANY_PRINCIPALS"; - break; - case SEC_E_UNFINISHED_CONTEXT_DELETED: - txt = "SEC_E_UNFINISHED_CONTEXT_DELETED"; - break; - case SEC_E_UNKNOWN_CREDENTIALS: - txt = "SEC_E_UNKNOWN_CREDENTIALS"; - break; - case SEC_E_UNSUPPORTED_FUNCTION: - txt = "SEC_E_UNSUPPORTED_FUNCTION"; - break; - case SEC_E_UNSUPPORTED_PREAUTH: - txt = "SEC_E_UNSUPPORTED_PREAUTH"; - break; - case SEC_E_UNTRUSTED_ROOT: - txt = "SEC_E_UNTRUSTED_ROOT"; - break; - case SEC_E_WRONG_CREDENTIAL_HANDLE: - txt = "SEC_E_WRONG_CREDENTIAL_HANDLE"; - break; - case SEC_E_WRONG_PRINCIPAL: - txt = "SEC_E_WRONG_PRINCIPAL"; - break; - case SEC_I_COMPLETE_AND_CONTINUE: - txt = "SEC_I_COMPLETE_AND_CONTINUE"; - break; - case SEC_I_COMPLETE_NEEDED: - txt = "SEC_I_COMPLETE_NEEDED"; - break; - case SEC_I_CONTEXT_EXPIRED: - txt = "SEC_I_CONTEXT_EXPIRED"; - break; - case SEC_I_CONTINUE_NEEDED: - txt = "SEC_I_CONTINUE_NEEDED"; - break; - case SEC_I_INCOMPLETE_CREDENTIALS: - txt = "SEC_I_INCOMPLETE_CREDENTIALS"; - break; - case SEC_I_LOCAL_LOGON: - txt = "SEC_I_LOCAL_LOGON"; - break; - case SEC_I_NO_LSA_CONTEXT: - txt = "SEC_I_NO_LSA_CONTEXT"; - break; - case SEC_I_RENEGOTIATE: - txt = "SEC_I_RENEGOTIATE"; - break; - case SEC_I_SIGNATURE_NEEDED: - txt = "SEC_I_SIGNATURE_NEEDED"; - 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_OK) - strncpy(outbuf, txt, outmax); - else if(err == SEC_E_ILLEGAL_MESSAGE) - snprintf(outbuf, outmax, - "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); + 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 { - str = txtbuf; - snprintf(txtbuf, sizeof(txtbuf), "%s (0x%08X)", txt, err); - txtbuf[sizeof(txtbuf)-1] = '\0'; - -#ifdef _WIN32_WCE - { - wchar_t wbuf[256]; - wbuf[0] = L'\0'; - - if(FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | - FORMAT_MESSAGE_IGNORE_INSERTS, - NULL, err, LANG_NEUTRAL, - wbuf, sizeof(wbuf)/sizeof(wchar_t), NULL)) { - wcstombs(msgbuf, wbuf, sizeof(msgbuf)-1); - msg_formatted = TRUE; - } - } -#else - if(FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM | - FORMAT_MESSAGE_IGNORE_INSERTS, - NULL, err, LANG_NEUTRAL, - msgbuf, sizeof(msgbuf)-1, NULL)) { - msg_formatted = TRUE; - } -#endif - if(msg_formatted) { - msgbuf[sizeof(msgbuf)-1] = '\0'; - /* strip trailing '\r\n' or '\n' */ - p = strrchr(msgbuf, '\n'); - if(p && (p - msgbuf) >= 2) - *p = '\0'; - p = strrchr(msgbuf, '\r'); - if(p && (p - msgbuf) >= 1) - *p = '\0'; - msg = msgbuf; - } - if(msg) - snprintf(outbuf, outmax, "%s - %s", str, msg); + char msgbuf[256]; + if(get_winapi_error(err, msgbuf, sizeof(msgbuf))) + msnprintf(buf, buflen, "%s (0x%08X) - %s", txt, err, msgbuf); else - strncpy(outbuf, str, outmax); + msnprintf(buf, buflen, "%s (0x%08X)", txt, err); } - if(old_errno != ERRNO) - SET_ERRNO(old_errno); - #else - if(err == SEC_E_OK) txt = "No error"; else txt = "Error"; + if(buflen > strlen(txt)) + strcpy(buf, txt); +#endif - strncpy(outbuf, txt, outmax); + if(errno != old_errno) + errno = old_errno; +#ifdef PRESERVE_WINDOWS_ERROR_CODE + if(old_win_err != GetLastError()) + SetLastError(old_win_err); #endif - outbuf[outmax] = '\0'; - - return outbuf; + return buf; } #endif /* USE_WINDOWS_SSPI */ diff --git a/contrib/curl/lib/strerror.h b/contrib/curl/lib/strerror.h index 627273eb26..6806867345 100644 --- a/contrib/curl/lib/strerror.h +++ b/contrib/curl/lib/strerror.h @@ -7,11 +7,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2016, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -20,18 +20,20 @@ * 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" -const char *Curl_strerror (struct connectdata *conn, int err); +#define STRERROR_LEN 256 /* a suitable length */ -#ifdef USE_LIBIDN2 -const char *Curl_idn_strerror (struct connectdata *conn, int err); +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 (struct connectdata *conn, int err); +const char *Curl_sspi_strerror(int err, char *buf, size_t buflen); #endif #endif /* HEADER_CURL_STRERROR_H */ diff --git a/contrib/curl/lib/strtok.c b/contrib/curl/lib/strtok.c index 460eb87e51..d2cc71c47d 100644 --- a/contrib/curl/lib/strtok.c +++ b/contrib/curl/lib/strtok.c @@ -5,11 +5,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2007, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -18,6 +18,8 @@ * 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" @@ -52,7 +54,7 @@ Curl_strtok_r(char *ptr, const char *sep, char **end) if(**end) { /* the end is not a null byte */ - **end = '\0'; /* zero terminate it! */ + **end = '\0'; /* null-terminate it! */ ++*end; /* advance the last pointer to beyond the null byte */ } @@ -63,4 +65,4 @@ Curl_strtok_r(char *ptr, const char *sep, char **end) return NULL; } -#endif /* this was only compiled if strtok_r wasn't present */ +#endif /* this was only compiled if strtok_r was not present */ diff --git a/contrib/curl/lib/strtok.h b/contrib/curl/lib/strtok.h index 90b831eb67..321cba2326 100644 --- a/contrib/curl/lib/strtok.h +++ b/contrib/curl/lib/strtok.h @@ -7,11 +7,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2010, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -20,6 +20,8 @@ * 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 diff --git a/contrib/curl/lib/strtoofft.c b/contrib/curl/lib/strtoofft.c index b854bf4de4..f1c7ba2711 100644 --- a/contrib/curl/lib/strtoofft.c +++ b/contrib/curl/lib/strtoofft.c @@ -5,11 +5,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2016, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -18,8 +18,11 @@ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY * KIND, either express or implied. * + * SPDX-License-Identifier: curl + * ***************************************************************************/ +#include #include "curl_setup.h" #include "strtoofft.h" @@ -28,11 +31,33 @@ * 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 - * http://www.opengroup.org/onlinepubs/009695399/functions/strtoimax.html + * could use in case strtoll() does not exist... See + * https://www.opengroup.org/onlinepubs/009695399/functions/strtoimax.html */ -#ifdef NEED_CURL_STRTOLL +#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. */ @@ -48,28 +73,26 @@ static const char valchars[] = static int get_char(char c, int base); /** - * Emulated version of the strtoll function. This extracts a long long + * Custom version of the strtooff function. This extracts a curl_off_t * value from the given input string and returns it. */ -curl_off_t -curlx_strtoll(const char *nptr, char **endptr, int base) +static curl_off_t strtooff(const char *nptr, char **endptr, int base) { char *end; - int is_negative = 0; - int overflow; + bool is_negative = FALSE; + bool overflow = FALSE; int i; curl_off_t value = 0; - curl_off_t newval; /* Skip leading whitespace. */ end = (char *)nptr; - while(ISSPACE(end[0])) { + while(ISBLANK(end[0])) { end++; } /* Handle the sign, if any. */ if(end[0] == '-') { - is_negative = 1; + is_negative = TRUE; end++; } else if(end[0] == '+') { @@ -97,27 +120,23 @@ curlx_strtoll(const char *nptr, char **endptr, int base) } } - /* 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. + /* Matching strtol, if the base is 0 and it does not look like + * the number is octal or hex, we assume it is 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; + + if(value > (CURL_OFF_T_MAX - i) / base) { + overflow = TRUE; break; } - else - value = newval; + value = base * value + i; } if(!overflow) { @@ -132,7 +151,7 @@ curlx_strtoll(const char *nptr, char **endptr, int base) else value = CURL_OFF_T_MAX; - SET_ERRNO(ERANGE); + errno = ERANGE; } if(endptr) @@ -149,7 +168,7 @@ curlx_strtoll(const char *nptr, char **endptr, int base) * @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 + * @return the value of c in base, or -1 if c is not in range */ static int get_char(char c, int base) { @@ -185,4 +204,37 @@ static int get_char(char c, int base) return value; } -#endif /* Only present if we need strtoll, but don't have it. */ +#endif /* Only present if we need strtoll, but do not 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 = NULL; + 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; /* did not 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/contrib/curl/lib/strtoofft.h b/contrib/curl/lib/strtoofft.h index f4039f3a3b..71808b719c 100644 --- a/contrib/curl/lib/strtoofft.h +++ b/contrib/curl/lib/strtoofft.h @@ -7,11 +7,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2014, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -20,6 +20,8 @@ * 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" @@ -28,7 +30,7 @@ * 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 + * Notice that curl_off_t might be 64 or 32 bits 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. * @@ -40,36 +42,13 @@ * of 'long' the conversion function to use is strtol(). */ -#if (CURL_SIZEOF_CURL_OFF_T > CURL_SIZEOF_LONG) -# ifdef HAVE_STRTOLL -# define curlx_strtoofft 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 curlx_strtoofft _strtoi64 -# else - curl_off_t curlx_strtoll(const char *nptr, char **endptr, int base); -# define curlx_strtoofft curlx_strtoll -# define NEED_CURL_STRTOLL 1 -# endif -# endif -#else -# define curlx_strtoofft strtol -#endif +typedef enum { + CURL_OFFT_OK, /* parsed fine */ + CURL_OFFT_FLOW, /* over or underflow */ + CURL_OFFT_INVAL /* nothing was parsed */ +} CURLofft; -#if (CURL_SIZEOF_CURL_OFF_T == 4) -# define CURL_OFF_T_MAX CURL_OFF_T_C(0x7FFFFFFF) -#else - /* assume CURL_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)) +CURLofft curlx_strtoofft(const char *str, char **endp, int base, + curl_off_t *num); #endif /* HEADER_CURL_STRTOOFFT_H */ diff --git a/contrib/curl/lib/system_win32.c b/contrib/curl/lib/system_win32.c index 78737593a4..5ab711871e 100644 --- a/contrib/curl/lib/system_win32.c +++ b/contrib/curl/lib/system_win32.c @@ -5,11 +5,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 2016, Steve Holme, . + * Copyright (C) Steve Holme, . * * This software is licensed 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. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -18,22 +18,124 @@ * 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) +#if defined(_WIN32) #include #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" -#if defined(USE_WINDOWS_SSPI) || (!defined(CURL_DISABLE_TELNET) && \ - defined(USE_WINSOCK)) +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 could not 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 could not find a usable */ + + /* winsock.dll. */ + WSACleanup(); + return CURLE_FAILED_INIT; + } + /* The Windows Sockets DLL is acceptable. Proceed. */ +#elif defined(USE_LWIPSOCK) + lwip_init(); +#endif + } /* 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 @@ -57,201 +159,6 @@ typedef HMODULE (APIENTRY *LOADLIBRARYEX_FN)(LPCTSTR, HANDLE, DWORD); # define LOADLIBARYEX "LoadLibraryExA" #endif -#endif /* USE_WINDOWS_SSPI || (!CURL_DISABLE_TELNET && USE_WINSOCK) */ - -/* - * Curl_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. - * platform [in] - The optional platform identifer. - * 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 Curl_verify_windows_version(const unsigned int majorVersion, - const unsigned int minorVersion, - const PlatformIdentifier platform, - const VersionCondition condition) -{ - bool matched = FALSE; - -#if defined(CURL_WINDOWS_APP) - /* 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)) - matched = TRUE; - break; - - case VERSION_LESS_THAN_EQUAL: - if(osver.dwMajorVersion <= majorVersion && - osver.dwMinorVersion <= minorVersion) - matched = TRUE; - break; - - case VERSION_EQUAL: - if(osver.dwMajorVersion == majorVersion && - osver.dwMinorVersion == minorVersion) - matched = TRUE; - break; - - case VERSION_GREATER_THAN_EQUAL: - if(osver.dwMajorVersion >= majorVersion && - osver.dwMinorVersion >= minorVersion) - matched = TRUE; - break; - - case VERSION_GREATER_THAN: - if(osver.dwMajorVersion > majorVersion || - (osver.dwMajorVersion == majorVersion && - osver.dwMinorVersion > minorVersion)) - 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; - - default: /* like platform == PLATFORM_DONT_CARE */ - break; - } - } - } -#else - ULONGLONG cm = 0; - OSVERSIONINFOEX osver; - BYTE majorCondition; - BYTE minorCondition; - BYTE spMajorCondition; - BYTE spMinorCondition; - - switch(condition) { - case VERSION_LESS_THAN: - majorCondition = VER_LESS; - minorCondition = VER_LESS; - spMajorCondition = VER_LESS_EQUAL; - spMinorCondition = VER_LESS_EQUAL; - break; - - case VERSION_LESS_THAN_EQUAL: - majorCondition = VER_LESS_EQUAL; - minorCondition = VER_LESS_EQUAL; - spMajorCondition = VER_LESS_EQUAL; - spMinorCondition = VER_LESS_EQUAL; - break; - - case VERSION_EQUAL: - majorCondition = VER_EQUAL; - minorCondition = VER_EQUAL; - spMajorCondition = VER_GREATER_EQUAL; - spMinorCondition = VER_GREATER_EQUAL; - break; - - case VERSION_GREATER_THAN_EQUAL: - majorCondition = VER_GREATER_EQUAL; - minorCondition = VER_GREATER_EQUAL; - spMajorCondition = VER_GREATER_EQUAL; - spMinorCondition = VER_GREATER_EQUAL; - break; - - case VERSION_GREATER_THAN: - majorCondition = VER_GREATER; - minorCondition = 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; - 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); - - if(VerifyVersionInfo(&osver, (VER_MAJORVERSION | VER_MINORVERSION | - VER_SERVICEPACKMAJOR | VER_SERVICEPACKMINOR), - cm)) - matched = TRUE; -#endif - - return matched; -} - -#if defined(USE_WINDOWS_SSPI) || (!defined(CURL_DISABLE_TELNET) && \ - defined(USE_WINSOCK)) - /* * Curl_load_library() * @@ -268,19 +175,22 @@ bool Curl_verify_windows_version(const unsigned int majorVersion, */ HMODULE Curl_load_library(LPCTSTR filename) { +#ifndef CURL_WINDOWS_UWP HMODULE hModule = NULL; LOADLIBRARYEX_FN pLoadLibraryEx = NULL; - /* Get a handle to kernel32 so we can access it's functions at runtime */ + /* Get a handle to kernel32 so we can access its 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 = (LOADLIBRARYEX_FN) GetProcAddress(hKernel32, LOADLIBARYEX); + pLoadLibraryEx = + CURLX_FUNCTION_CAST(LOADLIBRARYEX_FN, + (GetProcAddress(hKernel32, LOADLIBARYEX))); - /* Detect if there's already a path in the filename and load the library if + /* Detect if there is 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 */ @@ -290,7 +200,7 @@ HMODULE Curl_load_library(LPCTSTR filename) pLoadLibraryEx(filename, NULL, LOAD_WITH_ALTERED_SEARCH_PATH) : LoadLibrary(filename); } - /* Detect if KB2533623 is installed, as LOAD_LIBARY_SEARCH_SYSTEM32 is only + /* 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")) { @@ -320,10 +230,12 @@ HMODULE Curl_load_library(LPCTSTR filename) free(path); } } - return hModule; +#else + /* the Universal Windows Platform (UWP) cannot do this */ + (void)filename; + return NULL; +#endif } -#endif /* USE_WINDOWS_SSPI || (!CURL_DISABLE_TELNET && USE_WINSOCK) */ - -#endif /* WIN32 */ +#endif /* _WIN32 */ diff --git a/contrib/curl/lib/system_win32.h b/contrib/curl/lib/system_win32.h index 1e772856b2..024d959f32 100644 --- a/contrib/curl/lib/system_win32.h +++ b/contrib/curl/lib/system_win32.h @@ -7,11 +7,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 2016, Steve Holme, . + * Copyright (C) Steve Holme, . * * This software is licensed 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. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -20,42 +20,32 @@ * 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 Curl_verify_windows_version(const unsigned int majorVersion, - const unsigned int minorVersion, - const PlatformIdentifier platform, - const VersionCondition condition); - -#if defined(USE_WINDOWS_SSPI) || (!defined(CURL_DISABLE_TELNET) && \ - defined(USE_WINSOCK)) +#if defined(_WIN32) -/* This is used to dynamically load DLLs */ -HMODULE Curl_load_library(LPCTSTR filename); +#include + +extern LARGE_INTEGER Curl_freq; +extern bool Curl_isVistaOrGreater; -#endif /* USE_WINDOWS_SSPI || (!CURL_DISABLE_TELNET && USE_WINSOCK) */ +CURLcode Curl_win32_init(long flags); +void Curl_win32_cleanup(long init_flags); -#endif /* WIN32 */ +/* 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/contrib/curl/lib/telnet.c b/contrib/curl/lib/telnet.c index 162cab757c..3e6abad92e 100644 --- a/contrib/curl/lib/telnet.c +++ b/contrib/curl/lib/telnet.c @@ -5,11 +5,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2017, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -18,6 +18,8 @@ * 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" @@ -52,10 +54,6 @@ #include "connect.h" #include "progress.h" #include "system_win32.h" - -#define TELOPTS -#define TELCMDS - #include "arpa_telnet.h" #include "select.h" #include "strcase.h" @@ -73,29 +71,26 @@ do { \ x->subend = x->subpointer; \ CURL_SB_CLEAR(x); \ - } WHILE_FALSE -#define CURL_SB_ACCUM(x,c) \ - do { \ - if(x->subpointer < (x->subbuffer+sizeof x->subbuffer)) \ - *x->subpointer++ = (c); \ - } WHILE_FALSE + } 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_PEEK(x) ((*x->subpointer)&0xff) -#define CURL_SB_EOF(x) (x->subpointer >= x->subend) #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 -#ifdef USE_WINSOCK -typedef FARPROC WSOCK2_FUNC; -static CURLcode check_wsock2(struct Curl_easy *data); -#endif - static -CURLcode telrcv(struct connectdata *, +CURLcode telrcv(struct Curl_easy *data, const unsigned char *inbuf, /* Data received from socket */ ssize_t count); /* Number of bytes received */ @@ -105,21 +100,23 @@ static void printoption(struct Curl_easy *data, int cmd, int option); #endif -static void negotiate(struct connectdata *); -static void send_negotiation(struct connectdata *, int cmd, int option); -static void set_local_option(struct connectdata *, int cmd, int option); -static void set_remote_option(struct connectdata *, int cmd, int option); +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 connectdata *); -static void sendsuboption(struct connectdata *conn, int option); +static void suboption(struct Curl_easy *data); +static void sendsuboption(struct Curl_easy *data, int option); -static CURLcode telnet_do(struct connectdata *conn, bool *done); -static CURLcode telnet_done(struct connectdata *conn, +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 connectdata *conn, +static CURLcode send_telnet_data(struct Curl_easy *data, char *buffer, ssize_t nread); /* For negotiation compliant to RFC 1143 */ @@ -161,13 +158,13 @@ struct TELNET { 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 */ + struct dynbuf out; /* output buffer */ /* suboptions */ unsigned char subbuffer[SUBBUFSIZE]; unsigned char *subpointer, *subend; /* buffer for sub-options */ - - TelnetReceive telrcv_state; }; @@ -176,7 +173,7 @@ struct TELNET { */ const struct Curl_handler Curl_handler_telnet = { - "TELNET", /* scheme */ + "telnet", /* scheme */ ZERO_NULL, /* setup_connection */ telnet_do, /* do_it */ telnet_done, /* done */ @@ -189,55 +186,19 @@ const struct Curl_handler Curl_handler_telnet = { ZERO_NULL, /* domore_getsock */ ZERO_NULL, /* perform_getsock */ ZERO_NULL, /* disconnect */ - ZERO_NULL, /* readwrite */ + ZERO_NULL, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ + ZERO_NULL, /* connection_check */ + ZERO_NULL, /* attach connection */ PORT_TELNET, /* defport */ CURLPROTO_TELNET, /* protocol */ + CURLPROTO_TELNET, /* family */ PROTOPT_NONE | PROTOPT_NOURLQUERY /* flags */ }; -#ifdef USE_WINSOCK -static CURLcode -check_wsock2(struct Curl_easy *data) -{ - int err; - WORD wVersionRequested; - WSADATA wsaData; - - DEBUGASSERT(data); - - /* telnet requires at least WinSock 2.0 so ask for it. */ - wVersionRequested = MAKEWORD(2, 0); - - err = WSAStartup(wVersionRequested, &wsaData); - - /* We must've called this once already, so this call */ - /* should always succeed. But, just in case... */ - if(err != 0) { - failf(data,"WSAStartup failed (%d)",err); - return CURLE_FAILED_INIT; - } - - /* We have to have a WSACleanup call for every successful */ - /* WSAStartup call. */ - WSACleanup(); - - /* Check that our version is supported */ - if(LOBYTE(wsaData.wVersion) != LOBYTE(wVersionRequested) || - HIBYTE(wsaData.wVersion) != HIBYTE(wVersionRequested)) { - /* Our version isn't supported */ - failf(data, "insufficient winsock version to support " - "telnet"); - return CURLE_FAILED_INIT; - } - - /* Our version is supported */ - return CURLE_OK; -} -#endif - static -CURLcode init_telnet(struct connectdata *conn) +CURLcode init_telnet(struct Curl_easy *data) { struct TELNET *tn; @@ -245,7 +206,8 @@ CURLcode init_telnet(struct connectdata *conn) if(!tn) return CURLE_OUT_OF_MEMORY; - conn->data->req.protop = tn; /* make us known */ + Curl_dyn_init(&tn->out, 0xffff); + data->req.p.telnet = tn; /* make us known */ tn->telrcv_state = CURL_TS_DATA; @@ -257,7 +219,7 @@ CURLcode init_telnet(struct connectdata *conn) tn->him_preferred[CURL_TELOPT_SGA] = CURL_YES; /* To be compliant with previous releases of libcurl - we enable this option by default. This behaviour + we enable this option by default. This behavior can be changed thanks to the "BINARY" option in CURLOPT_TELNETOPTIONS */ @@ -287,20 +249,20 @@ CURLcode init_telnet(struct connectdata *conn) return CURLE_OK; } -static void negotiate(struct connectdata *conn) +static void negotiate(struct Curl_easy *data) { int i; - struct TELNET *tn = (struct TELNET *) conn->data->req.protop; + struct TELNET *tn = data->req.p.telnet; - for(i = 0;i < CURL_NTELOPTS;i++) { - if(i==CURL_TELOPT_ECHO) + for(i = 0; i < CURL_NTELOPTS; i++) { + if(i == CURL_TELOPT_ECHO) continue; if(tn->us_preferred[i] == CURL_YES) - set_local_option(conn, i, CURL_YES); + set_local_option(data, i, CURL_YES); if(tn->him_preferred[i] == CURL_YES) - set_remote_option(conn, i, CURL_YES); + set_remote_option(data, i, CURL_YES); } } @@ -308,20 +270,20 @@ static void negotiate(struct connectdata *conn) static void printoption(struct Curl_easy *data, const char *direction, int cmd, int option) { - const char *fmt; - const char *opt; - if(data->set.verbose) { if(cmd == CURL_IAC) { if(CURL_TELCMD_OK(option)) - infof(data, "%s IAC %s\n", direction, CURL_TELCMD(option)); + infof(data, "%s IAC %s", direction, CURL_TELCMD(option)); else - infof(data, "%s IAC %d\n", direction, option); + infof(data, "%s IAC %d", direction, option); } else { - fmt = (cmd == CURL_WILL) ? "WILL" : (cmd == CURL_WONT) ? "WONT" : - (cmd == CURL_DO) ? "DO" : (cmd == CURL_DONT) ? "DONT" : 0; + 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) @@ -330,46 +292,45 @@ static void printoption(struct Curl_easy *data, opt = NULL; if(opt) - infof(data, "%s %s %s\n", direction, fmt, opt); + infof(data, "%s %s %s", direction, fmt, opt); else - infof(data, "%s %s %d\n", direction, fmt, option); + infof(data, "%s %s %d", direction, fmt, option); } else - infof(data, "%s %d %d\n", direction, cmd, option); + infof(data, "%s %d %d", direction, cmd, option); } } } #endif -static void send_negotiation(struct connectdata *conn, int cmd, int option) +static void send_negotiation(struct Curl_easy *data, int cmd, int option) { - unsigned char buf[3]; - ssize_t bytes_written; - int err; - struct Curl_easy *data = conn->data; - - 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) { - err = SOCKERRNO; - failf(data,"Sending data failed (%d)",err); - } - - printoption(conn->data, "SENT", cmd, 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 connectdata *conn, int option, int newstate) +void set_remote_option(struct Curl_easy *data, int option, int newstate) { - struct TELNET *tn = (struct TELNET *)conn->data->req.protop; + 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(conn, CURL_DO, option); + send_negotiation(data, CURL_DO, option); break; case CURL_YES: @@ -408,7 +369,7 @@ void set_remote_option(struct connectdata *conn, int option, int newstate) case CURL_YES: tn->him[option] = CURL_WANTNO; - send_negotiation(conn, CURL_DONT, option); + send_negotiation(data, CURL_DONT, option); break; case CURL_WANTNO: @@ -436,17 +397,17 @@ void set_remote_option(struct connectdata *conn, int option, int newstate) } static -void rec_will(struct connectdata *conn, int option) +void rec_will(struct Curl_easy *data, int option) { - struct TELNET *tn = (struct TELNET *)conn->data->req.protop; + 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(conn, CURL_DO, option); + send_negotiation(data, CURL_DO, option); } else - send_negotiation(conn, CURL_DONT, option); + send_negotiation(data, CURL_DONT, option); break; @@ -476,7 +437,7 @@ void rec_will(struct connectdata *conn, int option) case CURL_OPPOSITE: tn->him[option] = CURL_WANTNO; tn->himq[option] = CURL_EMPTY; - send_negotiation(conn, CURL_DONT, option); + send_negotiation(data, CURL_DONT, option); break; } break; @@ -484,9 +445,9 @@ void rec_will(struct connectdata *conn, int option) } static -void rec_wont(struct connectdata *conn, int option) +void rec_wont(struct Curl_easy *data, int option) { - struct TELNET *tn = (struct TELNET *)conn->data->req.protop; + struct TELNET *tn = data->req.p.telnet; switch(tn->him[option]) { case CURL_NO: /* Already disabled */ @@ -494,7 +455,7 @@ void rec_wont(struct connectdata *conn, int option) case CURL_YES: tn->him[option] = CURL_NO; - send_negotiation(conn, CURL_DONT, option); + send_negotiation(data, CURL_DONT, option); break; case CURL_WANTNO: @@ -506,7 +467,7 @@ void rec_wont(struct connectdata *conn, int option) case CURL_OPPOSITE: tn->him[option] = CURL_WANTYES; tn->himq[option] = CURL_EMPTY; - send_negotiation(conn, CURL_DO, option); + send_negotiation(data, CURL_DO, option); break; } break; @@ -526,14 +487,14 @@ void rec_wont(struct connectdata *conn, int option) } static void -set_local_option(struct connectdata *conn, int option, int newstate) +set_local_option(struct Curl_easy *data, int option, int newstate) { - struct TELNET *tn = (struct TELNET *)conn->data->req.protop; + 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(conn, CURL_WILL, option); + send_negotiation(data, CURL_WILL, option); break; case CURL_YES: @@ -572,7 +533,7 @@ set_local_option(struct connectdata *conn, int option, int newstate) case CURL_YES: tn->us[option] = CURL_WANTNO; - send_negotiation(conn, CURL_WONT, option); + send_negotiation(data, CURL_WONT, option); break; case CURL_WANTNO: @@ -600,26 +561,26 @@ set_local_option(struct connectdata *conn, int option, int newstate) } static -void rec_do(struct connectdata *conn, int option) +void rec_do(struct Curl_easy *data, int option) { - struct TELNET *tn = (struct TELNET *)conn->data->req.protop; + 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(conn, CURL_WILL, option); + send_negotiation(data, CURL_WILL, option); if(tn->subnegotiation[option] == CURL_YES) /* transmission of data option */ - sendsuboption(conn, option); + sendsuboption(data, option); } else if(tn->subnegotiation[option] == CURL_YES) { - /* send information to achieve this option*/ + /* send information to achieve this option */ tn->us[option] = CURL_YES; - send_negotiation(conn, CURL_WILL, option); - sendsuboption(conn, option); + send_negotiation(data, CURL_WILL, option); + sendsuboption(data, option); } else - send_negotiation(conn, CURL_WONT, option); + send_negotiation(data, CURL_WONT, option); break; case CURL_YES: @@ -646,13 +607,13 @@ void rec_do(struct connectdata *conn, int option) tn->us[option] = CURL_YES; if(tn->subnegotiation[option] == CURL_YES) { /* transmission of data option */ - sendsuboption(conn, option); + sendsuboption(data, option); } break; case CURL_OPPOSITE: tn->us[option] = CURL_WANTNO; tn->himq[option] = CURL_EMPTY; - send_negotiation(conn, CURL_WONT, option); + send_negotiation(data, CURL_WONT, option); break; } break; @@ -660,9 +621,9 @@ void rec_do(struct connectdata *conn, int option) } static -void rec_dont(struct connectdata *conn, int option) +void rec_dont(struct Curl_easy *data, int option) { - struct TELNET *tn = (struct TELNET *)conn->data->req.protop; + struct TELNET *tn = data->req.p.telnet; switch(tn->us[option]) { case CURL_NO: /* Already disabled */ @@ -670,7 +631,7 @@ void rec_dont(struct connectdata *conn, int option) case CURL_YES: tn->us[option] = CURL_NO; - send_negotiation(conn, CURL_WONT, option); + send_negotiation(data, CURL_WONT, option); break; case CURL_WANTNO: @@ -682,7 +643,7 @@ void rec_dont(struct connectdata *conn, int option) case CURL_OPPOSITE: tn->us[option] = CURL_WANTYES; tn->usq[option] = CURL_EMPTY; - send_negotiation(conn, CURL_WILL, option); + send_negotiation(data, CURL_WILL, option); break; } break; @@ -707,11 +668,10 @@ static void printsub(struct Curl_easy *data, unsigned char *pointer, /* where suboption data is */ size_t length) /* length of suboption data */ { - unsigned int i = 0; - if(data->set.verbose) { + unsigned int i = 0; if(direction) { - infof(data, "%s IAC SB ", (direction == '<')? "RCVD":"SENT"); + infof(data, "%s IAC SB ", (direction == '<') ? "RCVD" : "SENT"); if(length >= 3) { int j; @@ -732,7 +692,7 @@ static void printsub(struct Curl_easy *data, infof(data, "%s", CURL_TELCMD(j)); else infof(data, "%d", j); - infof(data, ", not IAC SE!) "); + infof(data, ", not IAC SE) "); } } length -= 2; @@ -761,8 +721,8 @@ static void printsub(struct Curl_easy *data, switch(pointer[0]) { case CURL_TELOPT_NAWS: if(length > 4) - infof(data, "Width: %hu ; Height: %hu", (pointer[1]<<8) | pointer[2], - (pointer[3]<<8) | pointer[4]); + infof(data, "Width: %d ; Height: %d", (pointer[1] << 8) | pointer[2], + (pointer[3] << 8) | pointer[4]); break; default: switch(pointer[1]) { @@ -789,7 +749,7 @@ static void printsub(struct Curl_easy *data, case CURL_TELOPT_NEW_ENVIRON: if(pointer[1] == CURL_TELQUAL_IS) { infof(data, " "); - for(i = 3;i < length;i++) { + for(i = 3; i < length; i++) { switch(pointer[i]) { case CURL_NEW_ENV_VAR: infof(data, ", "); @@ -810,27 +770,44 @@ static void printsub(struct Curl_easy *data, break; } } - if(direction) - infof(data, "\n"); } } -static CURLcode check_telnet_options(struct connectdata *conn) +#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; - char option_keyword[128] = ""; - char option_arg[256] = ""; - struct Curl_easy *data = conn->data; - struct TELNET *tn = (struct TELNET *)conn->data->req.protop; + struct TELNET *tn = data->req.p.telnet; CURLcode result = CURLE_OK; - int binary_option; - /* Add the user name as an environment variable if it + /* Add the username as an environment variable if it was given on the command line */ - if(conn->bits.user_passwd) { - snprintf(option_arg, sizeof(option_arg), "USER,%s", conn->user); - beg = curl_slist_append(tn->telnet_vars, option_arg); + if(data->state.aptr.user) { + char buffer[256]; + if(str_is_nonascii(data->conn->user)) { + DEBUGF(infof(data, "set a non ASCII username in telnet")); + 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; @@ -840,69 +817,103 @@ static CURLcode check_telnet_options(struct connectdata *conn) tn->us_preferred[CURL_TELOPT_NEW_ENVIRON] = CURL_YES; } - for(head = data->set.telnet_options; head; head=head->next) { - if(sscanf(head->data, "%127[^= ]%*[ =]%255s", - option_keyword, option_arg) == 2) { - - /* Terminal type */ - if(strcasecompare(option_keyword, "TTYPE")) { - strncpy(tn->subopt_ttype, option_arg, 31); - tn->subopt_ttype[31] = 0; /* String termination */ - tn->us_preferred[CURL_TELOPT_TTYPE] = 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)) { + size_t l = strlen(arg); + if(l < sizeof(tn->subopt_ttype)) { + strcpy(tn->subopt_ttype, arg); + tn->us_preferred[CURL_TELOPT_TTYPE] = CURL_YES; + break; + } + } + result = CURLE_UNKNOWN_OPTION; + break; - /* Display variable */ - if(strcasecompare(option_keyword, "XDISPLOC")) { - strncpy(tn->subopt_xdisploc, option_arg, 127); - tn->subopt_xdisploc[127] = 0; /* String termination */ - tn->us_preferred[CURL_TELOPT_XDISPLOC] = CURL_YES; - continue; - } + case 8: + /* Display variable */ + if(strncasecompare(option, "XDISPLOC", 8)) { + size_t l = strlen(arg); + if(l < sizeof(tn->subopt_xdisploc)) { + strcpy(tn->subopt_xdisploc, arg); + tn->us_preferred[CURL_TELOPT_XDISPLOC] = CURL_YES; + break; + } + } + result = CURLE_UNKNOWN_OPTION; + break; - /* Environment variable */ - if(strcasecompare(option_keyword, "NEW_ENV")) { - beg = curl_slist_append(tn->telnet_vars, option_arg); - if(!beg) { - result = CURLE_OUT_OF_MEMORY; - 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; } - tn->telnet_vars = beg; - tn->us_preferred[CURL_TELOPT_NEW_ENVIRON] = CURL_YES; - continue; - } + else + result = CURLE_UNKNOWN_OPTION; + break; - /* Window Size */ - if(strcasecompare(option_keyword, "WS")) { - if(sscanf(option_arg, "%hu%*[xX]%hu", - &tn->subopt_wsx, &tn->subopt_wsy) == 2) - tn->us_preferred[CURL_TELOPT_NAWS] = CURL_YES; - else { - failf(data, "Syntax error in telnet option: %s", head->data); - result = CURLE_TELNET_OPTION_SYNTAX; - 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; + } } - continue; - } + else + result = CURLE_UNKNOWN_OPTION; + break; - /* To take care or not of the 8th bit in data exchange */ - if(strcasecompare(option_keyword, "BINARY")) { - binary_option=atoi(option_arg); - if(binary_option!=1) { - tn->us_preferred[CURL_TELOPT_BINARY] = CURL_NO; - tn->him_preferred[CURL_TELOPT_BINARY] = CURL_NO; + 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; + } } - continue; + else + result = CURLE_UNKNOWN_OPTION; + break; + default: + failf(data, "Unknown telnet option %s", head->data); + result = CURLE_UNKNOWN_OPTION; + break; } - - failf(data, "Unknown telnet option %s", head->data); - result = CURLE_UNKNOWN_TELNET_OPTION; - break; } else { failf(data, "Syntax error in telnet option: %s", head->data); - result = CURLE_TELNET_OPTION_SYNTAX; - break; + result = CURLE_SETOPT_OPTION_SYNTAX; } } @@ -921,26 +932,23 @@ static CURLcode check_telnet_options(struct connectdata *conn) * side. */ -static void suboption(struct connectdata *conn) +static void suboption(struct Curl_easy *data) { struct curl_slist *v; unsigned char temp[2048]; ssize_t bytes_written; size_t len; - size_t tmplen; int err; - char varname[128] = ""; - char varval[128] = ""; - struct Curl_easy *data = conn->data; - struct TELNET *tn = (struct TELNET *)data->req.protop; + struct TELNET *tn = data->req.p.telnet; + struct connectdata *conn = data->conn; - printsub(data, '<', (unsigned char *)tn->subbuffer, CURL_SB_LEN(tn)+2); + 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; - snprintf((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); + 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; @@ -950,9 +958,9 @@ static void suboption(struct connectdata *conn) break; case CURL_TELOPT_XDISPLOC: len = strlen(tn->subopt_xdisploc) + 4 + 2; - snprintf((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); + 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; @@ -961,25 +969,29 @@ static void suboption(struct connectdata *conn) printsub(data, '>', &temp[2], len-2); break; case CURL_TELOPT_NEW_ENVIRON: - snprintf((char *)temp, sizeof(temp), - "%c%c%c%c", CURL_IAC, CURL_SB, CURL_TELOPT_NEW_ENVIRON, - CURL_TELQUAL_IS); + 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) { - tmplen = (strlen(v->data) + 1); - /* Add the variable only if it fits */ + 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) { - if(sscanf(v->data, "%127[^,],%127s", varname, varval)) { - snprintf((char *)&temp[len], sizeof(temp) - len, - "%c%s%c%s", CURL_NEW_ENV_VAR, varname, - CURL_NEW_ENV_VALUE, varval); - len += tmplen; + 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); } } } - snprintf((char *)&temp[len], sizeof(temp) - len, - "%c%c", CURL_IAC, CURL_SE); + 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) { @@ -999,15 +1011,14 @@ static void suboption(struct connectdata *conn) * Send suboption information to the server side. */ -static void sendsuboption(struct connectdata *conn, int option) +static void sendsuboption(struct Curl_easy *data, int option) { ssize_t bytes_written; int err; unsigned short x, y; unsigned char *uc1, *uc2; - - struct Curl_easy *data = conn->data; - struct TELNET *tn = (struct TELNET *)data->req.protop; + struct TELNET *tn = data->req.p.telnet; + struct connectdata *conn = data->conn; switch(option) { case CURL_TELOPT_NAWS: @@ -1016,10 +1027,10 @@ static void sendsuboption(struct connectdata *conn, int option) CURL_SB_ACCUM(tn, CURL_IAC); CURL_SB_ACCUM(tn, CURL_SB); CURL_SB_ACCUM(tn, CURL_TELOPT_NAWS); - /* We must deal either with litte or big endien processors */ + /* 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); + x = htons(tn->subopt_wsx); + y = htons(tn->subopt_wsy); uc1 = (unsigned char *)&x; uc2 = (unsigned char *)&y; CURL_SB_ACCUM(tn, uc1[0]); @@ -1032,7 +1043,7 @@ static void sendsuboption(struct connectdata *conn, int option) CURL_SB_TERM(tn); /* data suboption is now ready */ - printsub(data, '>', (unsigned char *)tn->subbuffer+2, + printsub(data, '>', (unsigned char *)tn->subbuffer + 2, CURL_SB_LEN(tn)-2); /* we send the header of the suboption... */ @@ -1043,9 +1054,9 @@ static void sendsuboption(struct connectdata *conn, int option) } /* ... then the window size with the send_telnet_data() function to deal with 0xFF cases ... */ - send_telnet_data(conn, (char *)tn->subbuffer+3, 4); + send_telnet_data(data, (char *)tn->subbuffer + 3, 4); /* ... and the footer */ - bytes_written = swrite(conn->sock[FIRSTSOCKET], tn->subbuffer+7, 2); + bytes_written = swrite(conn->sock[FIRSTSOCKET], tn->subbuffer + 7, 2); if(bytes_written < 0) { err = SOCKERRNO; failf(data, "Sending data failed (%d)", err); @@ -1056,20 +1067,19 @@ static void sendsuboption(struct connectdata *conn, int option) static -CURLcode telrcv(struct connectdata *conn, +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 Curl_easy *data = conn->data; - struct TELNET *tn = (struct TELNET *)data->req.protop; + int startwrite = -1; + struct TELNET *tn = data->req.p.telnet; #define startskipping() \ if(startwrite >= 0) { \ - result = Curl_client_write(conn, \ + result = Curl_client_write(data, \ CLIENTWRITE_BODY, \ (char *)&inbuf[startwrite], \ in-startwrite); \ @@ -1109,7 +1119,7 @@ CURLcode telrcv(struct connectdata *conn, break; case CURL_TS_IAC: - process_iac: +process_iac: DEBUGASSERT(startwrite < 0); switch(c) { case CURL_WILL: @@ -1145,28 +1155,28 @@ CURLcode telrcv(struct connectdata *conn, case CURL_TS_WILL: printoption(data, "RCVD", CURL_WILL, c); tn->please_negotiate = 1; - rec_will(conn, c); + 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(conn, c); + 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(conn, c); + 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(conn, c); + rec_dont(data, c); tn->telrcv_state = CURL_TS_DATA; break; @@ -1181,12 +1191,12 @@ CURLcode telrcv(struct connectdata *conn, 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 + * 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 + * 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 + * 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); @@ -1195,20 +1205,19 @@ CURLcode telrcv(struct connectdata *conn, CURL_SB_TERM(tn); printoption(data, "In SUBOPTION processing, RCVD", CURL_IAC, c); - suboption(conn); /* handle sub-option */ + 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 - { + else { CURL_SB_ACCUM(tn, CURL_IAC); CURL_SB_ACCUM(tn, CURL_SE); tn->subpointer -= 2; CURL_SB_TERM(tn); - suboption(conn); /* handle sub-option */ + suboption(data); /* handle sub-option */ tn->telrcv_state = CURL_TS_DATA; } break; @@ -1220,50 +1229,66 @@ CURLcode telrcv(struct connectdata *conn, } /* Escape and send a telnet data block */ -/* TODO: write large chunks of data instead of one byte at a time */ -static CURLcode send_telnet_data(struct connectdata *conn, +static CURLcode send_telnet_data(struct Curl_easy *data, char *buffer, ssize_t nread) { - unsigned char outbuf[2]; - ssize_t bytes_written, total_written; - int out_count; + size_t i, outlen; + unsigned char *outbuf; CURLcode result = CURLE_OK; + size_t bytes_written; + size_t total_written = 0; + struct connectdata *conn = data->conn; + struct TELNET *tn = data->req.p.telnet; + + DEBUGASSERT(tn); + DEBUGASSERT(nread > 0); + if(nread < 0) + return CURLE_TOO_LARGE; + + if(memchr(buffer, CURL_IAC, nread)) { + /* only use the escape buffer when necessary */ + Curl_dyn_reset(&tn->out); + + for(i = 0; i < (size_t)nread && !result; i++) { + result = Curl_dyn_addn(&tn->out, &buffer[i], 1); + if(!result && ((unsigned char)buffer[i] == CURL_IAC)) + /* IAC is FF in hex */ + result = Curl_dyn_addn(&tn->out, "\xff", 1); + } - while(!result && nread--) { - outbuf[0] = *buffer++; - out_count = 1; - if(outbuf[0] == CURL_IAC) - outbuf[out_count++] = CURL_IAC; - - total_written = 0; - do { - /* 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_write(conn, conn->sock[FIRSTSOCKET], - outbuf+total_written, out_count-total_written, - &bytes_written); - total_written += bytes_written; - break; - } - /* handle partial write */ - } while(!result && total_written < out_count); + outlen = Curl_dyn_len(&tn->out); + outbuf = Curl_dyn_uptr(&tn->out); + } + else { + outlen = (size_t)nread; + outbuf = (unsigned char *)buffer; } + 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_xfer_send(data, outbuf + total_written, + outlen - total_written, FALSE, &bytes_written); + total_written += bytes_written; + break; + } + } + return result; } -static CURLcode telnet_done(struct connectdata *conn, - CURLcode status, bool premature) +static CURLcode telnet_done(struct Curl_easy *data, + CURLcode status, bool premature) { - struct TELNET *tn = (struct TELNET *)conn->data->req.protop; + struct TELNET *tn = data->req.p.telnet; (void)status; /* unused */ (void)premature; /* not used */ @@ -1272,124 +1297,65 @@ static CURLcode telnet_done(struct connectdata *conn, curl_slist_free_all(tn->telnet_vars); tn->telnet_vars = NULL; - - Curl_safefree(conn->data->req.protop); - + Curl_dyn_free(&tn->out); return CURLE_OK; } -static CURLcode telnet_do(struct connectdata *conn, bool *done) +static CURLcode telnet_do(struct Curl_easy *data, bool *done) { CURLcode result; - struct Curl_easy *data = conn->data; + struct connectdata *conn = data->conn; curl_socket_t sockfd = conn->sock[FIRSTSOCKET]; #ifdef USE_WINSOCK - HMODULE wsock2; - WSOCK2_FUNC close_event_func; - WSOCK2_FUNC create_event_func; - WSOCK2_FUNC event_select_func; - WSOCK2_FUNC enum_netevents_func; WSAEVENT event_handle; WSANETWORKEVENTS events; HANDLE stdin_handle; HANDLE objs[2]; DWORD obj_count; DWORD wait_timeout; - DWORD waitret; DWORD readfile_read; int err; #else - int interval_ms; + 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 timeval now; + struct curltime now; bool keepon = TRUE; - char *buf = data->state.buffer; + char buffer[4*1024]; struct TELNET *tn; *done = TRUE; /* unconditionally */ - result = init_telnet(conn); + result = init_telnet(data); if(result) return result; - tn = (struct TELNET *)data->req.protop; + tn = data->req.p.telnet; - result = check_telnet_options(conn); + result = check_telnet_options(data); if(result) return result; #ifdef USE_WINSOCK - /* - ** This functionality only works with WinSock >= 2.0. So, - ** make sure we have it. - */ - result = check_wsock2(data); - if(result) - return result; - - /* OK, so we have WinSock 2.0. We need to dynamically */ - /* load ws2_32.dll and get the function pointers we need. */ - wsock2 = Curl_load_library(TEXT("WS2_32.DLL")); - if(wsock2 == NULL) { - failf(data, "failed to load WS2_32.DLL (%d)", ERRNO); - return CURLE_FAILED_INIT; - } - - /* Grab a pointer to WSACreateEvent */ - create_event_func = GetProcAddress(wsock2, "WSACreateEvent"); - if(create_event_func == NULL) { - failf(data, "failed to find WSACreateEvent function (%d)", ERRNO); - FreeLibrary(wsock2); - return CURLE_FAILED_INIT; - } - - /* And WSACloseEvent */ - close_event_func = GetProcAddress(wsock2, "WSACloseEvent"); - if(close_event_func == NULL) { - failf(data, "failed to find WSACloseEvent function (%d)", ERRNO); - FreeLibrary(wsock2); - return CURLE_FAILED_INIT; - } - - /* And WSAEventSelect */ - event_select_func = GetProcAddress(wsock2, "WSAEventSelect"); - if(event_select_func == NULL) { - failf(data, "failed to find WSAEventSelect function (%d)", ERRNO); - FreeLibrary(wsock2); - return CURLE_FAILED_INIT; - } - - /* And WSAEnumNetworkEvents */ - enum_netevents_func = GetProcAddress(wsock2, "WSAEnumNetworkEvents"); - if(enum_netevents_func == NULL) { - failf(data, "failed to find WSAEnumNetworkEvents function (%d)", ERRNO); - FreeLibrary(wsock2); - return CURLE_FAILED_INIT; - } - /* We want to wait for both stdin and the socket. Since - ** the select() function in winsock only works on sockets + ** the select() function in Winsock only works on sockets ** we have to use the WaitForMultipleObjects() call. */ /* First, create a sockets event object */ - event_handle = (WSAEVENT)create_event_func(); + event_handle = WSACreateEvent(); if(event_handle == WSA_INVALID_EVENT) { failf(data, "WSACreateEvent failed (%d)", SOCKERRNO); - FreeLibrary(wsock2); return CURLE_FAILED_INIT; } - /* Tell winsock what events we want to listen to */ - if(event_select_func(sockfd, event_handle, FD_READ|FD_CLOSE) == - SOCKET_ERROR) { - close_event_func(event_handle); - FreeLibrary(wsock2); + /* 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; } @@ -1404,7 +1370,7 @@ static CURLcode telnet_do(struct connectdata *conn, bool *done) 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 */ + /* Do not wait for stdin_handle, just wait for event_handle */ obj_count = 1; /* Check stdin_handle per 100 milliseconds */ wait_timeout = 100; @@ -1416,29 +1382,32 @@ static CURLcode telnet_do(struct connectdata *conn, bool *done) /* Keep on listening and act on events */ while(keepon) { - const DWORD buf_size = (DWORD)CURL_BUFSIZE(data->set.buffer_size); - waitret = WaitForMultipleObjects(obj_count, objs, FALSE, wait_timeout); + const DWORD buf_size = (DWORD)sizeof(buffer); + 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 */ - result = (int)data->state.fread_func(buf, 1, BUFSIZE - 1, - data->state.in); - if(result == CURL_READFUNC_ABORT) { + n = data->state.fread_func(buffer, 1, buf_size, data->state.in); + if(n == CURL_READFUNC_ABORT) { keepon = FALSE; result = CURLE_READ_ERROR; break; } - if(result == CURL_READFUNC_PAUSE) + if(n == CURL_READFUNC_PAUSE) break; - if(result == 0) /* no bytes */ + if(n == 0) /* no bytes */ break; - readfile_read = result; /* fall thru with number of bytes read */ + /* fall through with number of bytes read */ + readfile_read = (DWORD)n; } else { /* read from stdin */ @@ -1452,7 +1421,7 @@ static CURLcode telnet_do(struct connectdata *conn, bool *done) if(!readfile_read) break; - if(!ReadFile(stdin_handle, buf, buf_size, + if(!ReadFile(stdin_handle, buffer, buf_size, &readfile_read, NULL)) { keepon = FALSE; result = CURLE_READ_ERROR; @@ -1460,7 +1429,7 @@ static CURLcode telnet_do(struct connectdata *conn, bool *done) } } - result = send_telnet_data(conn, buf, readfile_read); + result = send_telnet_data(data, buffer, readfile_read); if(result) { keepon = FALSE; break; @@ -1471,14 +1440,14 @@ static CURLcode telnet_do(struct connectdata *conn, bool *done) case WAIT_OBJECT_0 + 1: { - if(!ReadFile(stdin_handle, buf, buf_size, + if(!ReadFile(stdin_handle, buffer, buf_size, &readfile_read, NULL)) { keepon = FALSE; result = CURLE_READ_ERROR; break; } - result = send_telnet_data(conn, buf, readfile_read); + result = send_telnet_data(data, buffer, readfile_read); if(result) { keepon = FALSE; break; @@ -1487,9 +1456,9 @@ static CURLcode telnet_do(struct connectdata *conn, bool *done) break; case WAIT_OBJECT_0: - + { events.lNetworkEvents = 0; - if(SOCKET_ERROR == enum_netevents_func(sockfd, event_handle, &events)) { + if(WSAEnumNetworkEvents(sockfd, event_handle, &events) == SOCKET_ERROR) { err = SOCKERRNO; if(err != EINPROGRESS) { infof(data, "WSAEnumNetworkEvents failed (%d)", err); @@ -1500,8 +1469,8 @@ static CURLcode telnet_do(struct connectdata *conn, bool *done) } if(events.lNetworkEvents & FD_READ) { /* read data from network */ - result = Curl_read(conn, sockfd, buf, BUFSIZE - 1, &nread); - /* read would've blocked. Loop again */ + result = Curl_xfer_recv(data, buffer, sizeof(buffer), &nread); + /* read would have blocked. Loop again */ if(result == CURLE_AGAIN) break; /* returned not-zero, this an error */ @@ -1516,30 +1485,31 @@ static CURLcode telnet_do(struct connectdata *conn, bool *done) break; } - result = telrcv(conn, (unsigned char *) buf, nread); + result = telrcv(data, (unsigned char *) buffer, nread); if(result) { keepon = FALSE; break; } /* Negotiate if the peer has started negotiating, - otherwise don't. We don't want to speak telnet with + otherwise do not. We do not want to speak telnet with non-telnet servers, like POP or SMTP. */ if(tn->please_negotiate && !tn->already_negotiated) { - negotiate(conn); + negotiate(data); tn->already_negotiated = 1; } } if(events.lNetworkEvents & FD_CLOSE) { keepon = FALSE; } - break; + } + break; } if(data->set.timeout) { - now = Curl_tvnow(); - if(Curl_tvdiff(now, conn->created) >= 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; @@ -1548,19 +1518,9 @@ static CURLcode telnet_do(struct connectdata *conn, bool *done) } /* We called WSACreateEvent, so call WSACloseEvent */ - if(!close_event_func(event_handle)) { + if(!WSACloseEvent(event_handle)) { infof(data, "WSACloseEvent failed (%d)", SOCKERRNO); } - - /* "Forget" pointers into the library we're about to free */ - create_event_func = NULL; - close_event_func = NULL; - event_select_func = NULL; - enum_netevents_func = NULL; - - /* We called LoadLibrary, so call FreeLibrary */ - if(!FreeLibrary(wsock2)) - infof(data, "FreeLibrary(wsock2) failed (%d)", ERRNO); #else pfd[0].fd = sockfd; pfd[0].events = POLLIN; @@ -1575,27 +1535,40 @@ static CURLcode telnet_do(struct connectdata *conn, bool *done) pfd[1].events = POLLIN; poll_cnt = 2; interval_ms = 1 * 1000; + if(pfd[1].fd < 0) { + failf(data, "cannot read input"); + result = CURLE_RECV_ERROR; + keepon = FALSE; + } } while(keepon) { - switch(Curl_poll(pfd, poll_cnt, interval_ms)) { + DEBUGF(infof(data, "telnet_do, poll %d fds", poll_cnt)); + switch(Curl_poll(pfd, (unsigned int)poll_cnt, interval_ms)) { case -1: /* error, stop reading */ keepon = FALSE; continue; case 0: /* timeout */ pfd[0].revents = 0; pfd[1].revents = 0; - /* fall through */ + FALLTHROUGH(); default: /* read! */ if(pfd[0].revents & POLLIN) { /* read data from network */ - result = Curl_read(conn, sockfd, buf, BUFSIZE - 1, &nread); - /* read would've blocked. Loop again */ + result = Curl_xfer_recv(data, buffer, sizeof(buffer), &nread); + /* read would have blocked. Loop again */ if(result == CURLE_AGAIN) break; /* returned not-zero, this an error */ - else if(result) { + 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, @@ -1606,18 +1579,19 @@ static CURLcode telnet_do(struct connectdata *conn, bool *done) } total_dl += nread; - Curl_pgrsSetDownloadCounter(data, total_dl); - result = telrcv(conn, (unsigned char *)buf, nread); + result = Curl_pgrsSetDownloadCounter(data, total_dl); + if(!result) + result = telrcv(data, (unsigned char *)buffer, nread); if(result) { keepon = FALSE; break; } /* Negotiate if the peer has started negotiating, - otherwise don't. We don't want to speak telnet with + otherwise do not. We do not want to speak telnet with non-telnet servers, like POP or SMTP. */ if(tn->please_negotiate && !tn->already_negotiated) { - negotiate(conn); + negotiate(data); tn->already_negotiated = 1; } } @@ -1625,12 +1599,12 @@ static CURLcode telnet_do(struct connectdata *conn, bool *done) nread = 0; if(poll_cnt == 2) { if(pfd[1].revents & POLLIN) { /* read from in file */ - nread = read(pfd[1].fd, buf, BUFSIZE - 1); + nread = read(pfd[1].fd, buffer, sizeof(buffer)); } } else { /* read from user-supplied method */ - nread = (int)data->state.fread_func(buf, 1, BUFSIZE - 1, + nread = (int)data->state.fread_func(buffer, 1, sizeof(buffer), data->state.in); if(nread == CURL_READFUNC_ABORT) { keepon = FALSE; @@ -1641,7 +1615,7 @@ static CURLcode telnet_do(struct connectdata *conn, bool *done) } if(nread > 0) { - result = send_telnet_data(conn, buf, nread); + result = send_telnet_data(data, buffer, nread); if(result) { keepon = FALSE; break; @@ -1656,22 +1630,22 @@ static CURLcode telnet_do(struct connectdata *conn, bool *done) } /* poll switch statement */ if(data->set.timeout) { - now = Curl_tvnow(); - if(Curl_tvdiff(now, conn->created) >= 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(conn)) { + if(Curl_pgrsUpdate(data)) { result = CURLE_ABORTED_BY_CALLBACK; break; } } #endif /* mark this as "no further transfer wanted" */ - Curl_setup_transfer(conn, -1, -1, FALSE, NULL, -1, NULL); + Curl_xfer_setup_nop(data); return result; } diff --git a/contrib/curl/lib/telnet.h b/contrib/curl/lib/telnet.h index 419a399b7b..30782d8375 100644 --- a/contrib/curl/lib/telnet.h +++ b/contrib/curl/lib/telnet.h @@ -7,11 +7,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2007, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -20,10 +20,11 @@ * 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/contrib/curl/lib/tftp.c b/contrib/curl/lib/tftp.c index f2f834775e..7fb22a3738 100644 --- a/contrib/curl/lib/tftp.c +++ b/contrib/curl/lib/tftp.c @@ -5,11 +5,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2016, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -18,6 +18,8 @@ * 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" @@ -46,6 +48,7 @@ #include "urldata.h" #include +#include "cf-socket.h" #include "transfer.h" #include "sendf.h" #include "tftp.h" @@ -67,8 +70,6 @@ /* RFC2348 allows the block size to be negotiated */ #define TFTP_BLKSIZE_DEFAULT 512 -#define TFTP_BLKSIZE_MIN 8 -#define TFTP_BLKSIZE_MAX 65464 #define TFTP_OPTION_BLKSIZE "blksize" /* from RFC2349: */ @@ -76,12 +77,12 @@ #define TFTP_OPTION_INTERVAL "timeout" typedef enum { - TFTP_MODE_NETASCII=0, + TFTP_MODE_NETASCII = 0, TFTP_MODE_OCTET } tftp_mode_t; typedef enum { - TFTP_STATE_START=0, + TFTP_STATE_START = 0, TFTP_STATE_RX, TFTP_STATE_TX, TFTP_STATE_FIN @@ -100,7 +101,7 @@ typedef enum { } tftp_event_t; typedef enum { - TFTP_ERR_UNDEF=0, + TFTP_ERR_UNDEF = 0, TFTP_ERR_NOTFOUND, TFTP_ERR_PERM, TFTP_ERR_DISKFULL, @@ -115,24 +116,21 @@ typedef enum { TFTP_ERR_NORESPONSE } tftp_error_t; -typedef struct tftp_packet { +struct tftp_packet { unsigned char *data; -} tftp_packet_t; +}; -typedef struct tftp_state_data { +struct tftp_state_data { tftp_state_t state; tftp_mode_t mode; tftp_error_t error; tftp_event_t event; - struct connectdata *conn; + struct Curl_easy *data; curl_socket_t sockfd; int retries; int retry_time; int retry_max; - time_t start_time; - time_t max_time; time_t rx_time; - unsigned short block; struct Curl_sockaddr_storage local_addr; struct Curl_sockaddr_storage remote_addr; curl_socklen_t remote_addrlen; @@ -140,25 +138,28 @@ typedef struct tftp_state_data { int sbytes; int blksize; int requested_blksize; - tftp_packet_t rpacket; - tftp_packet_t spacket; -} tftp_state_data_t; + unsigned short block; + struct tftp_packet rpacket; + struct tftp_packet spacket; +}; /* Forward declarations */ -static CURLcode tftp_rx(tftp_state_data_t *state, tftp_event_t event); -static CURLcode tftp_tx(tftp_state_data_t *state, tftp_event_t event); -static CURLcode tftp_connect(struct connectdata *conn, bool *done); -static CURLcode tftp_disconnect(struct connectdata *conn, +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 connectdata *conn, bool *done); -static CURLcode tftp_done(struct connectdata *conn, +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 connectdata * conn); -static CURLcode tftp_multi_statemach(struct connectdata *conn, bool *done); -static CURLcode tftp_doing(struct connectdata *conn, bool *dophase_done); -static int tftp_getsock(struct connectdata *conn, curl_socket_t *socks, - int numsocks); +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); @@ -167,7 +168,7 @@ static CURLcode tftp_translate_code(tftp_error_t error); */ const struct Curl_handler Curl_handler_tftp = { - "TFTP", /* scheme */ + "tftp", /* scheme */ tftp_setup_connection, /* setup_connection */ tftp_do, /* do_it */ tftp_done, /* done */ @@ -180,10 +181,14 @@ const struct Curl_handler Curl_handler_tftp = { ZERO_NULL, /* domore_getsock */ ZERO_NULL, /* perform_getsock */ tftp_disconnect, /* disconnect */ - ZERO_NULL, /* readwrite */ + ZERO_NULL, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ + ZERO_NULL, /* connection_check */ + ZERO_NULL, /* attach connection */ PORT_TFTP, /* defport */ CURLPROTO_TFTP, /* protocol */ - PROTOPT_NONE | PROTOPT_NOURLQUERY /* flags */ + CURLPROTO_TFTP, /* family */ + PROTOPT_NOTCPPROXY | PROTOPT_NOURLQUERY /* flags */ }; /********************************************************** @@ -196,77 +201,50 @@ const struct Curl_handler Curl_handler_tftp = { * * **********************************************************/ -static CURLcode tftp_set_timeouts(tftp_state_data_t *state) +static CURLcode tftp_set_timeouts(struct tftp_state_data *state) { time_t maxtime, timeout; - time_t timeout_ms; + timediff_t timeout_ms; bool start = (state->state == TFTP_STATE_START) ? TRUE : FALSE; - time(&state->start_time); - /* Compute drop-dead time */ - timeout_ms = Curl_timeleft(state->conn->data, NULL, start); + timeout_ms = Curl_timeleft(state->data, NULL, start); if(timeout_ms < 0) { /* time-out, bail out, go home */ - failf(state->conn->data, "Connection time-out"); + failf(state->data, "Connection time-out"); return CURLE_OPERATION_TIMEDOUT; } - if(start) { - + if(timeout_ms > 0) maxtime = (time_t)(timeout_ms + 500) / 1000; - state->max_time = state->start_time+maxtime; - - /* Set per-block timeout to total */ - timeout = maxtime; - - /* Average restart after 5 seconds */ - state->retry_max = (int)timeout/5; - - if(state->retry_max < 1) - /* avoid division by zero below */ - state->retry_max = 1; - - /* Compute the re-start interval to suit the timeout */ - state->retry_time = (int)timeout/state->retry_max; - if(state->retry_time<1) - state->retry_time=1; - - } - else { - if(timeout_ms > 0) - maxtime = (time_t)(timeout_ms + 500) / 1000; - else - maxtime = 3600; + else + maxtime = 3600; /* use for calculating block timeouts */ - state->max_time = state->start_time+maxtime; + /* Set per-block timeout to total */ + timeout = maxtime; - /* Set per-block timeout to total */ - timeout = maxtime; + /* Average reposting an ACK after 5 seconds */ + state->retry_max = (int)timeout/5; - /* 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 < 3) + state->retry_max = 3; - if(state->retry_max>50) - state->retry_max=50; + 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; + if(state->retry_time < 1) + state->retry_time = 1; - infof(state->conn->data, - "set timeouts for state %d; Total %ld, retry %d maxtry %d\n", - (int)state->state, (long)(state->max_time-state->start_time), - state->retry_time, state->retry_max); + infof(state->data, + "set timeouts for state %d; Total % " FMT_OFF_T ", retry %d maxtry %d", + (int)state->state, timeout_ms, state->retry_time, state->retry_max); /* init RX time */ - time(&state->rx_time); + state->rx_time = time(NULL); return CURLE_OK; } @@ -279,30 +257,30 @@ static CURLcode tftp_set_timeouts(tftp_state_data_t *state) * **********************************************************/ -static void setpacketevent(tftp_packet_t *packet, unsigned short num) +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(tftp_packet_t *packet, unsigned short num) +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 tftp_packet_t *packet) +static unsigned short getrpacketevent(const struct tftp_packet *packet) { return (unsigned short)((packet->data[0] << 8) | packet->data[1]); } -static unsigned short getrpacketblock(const tftp_packet_t *packet) +static unsigned short getrpacketblock(const struct tftp_packet *packet) { return (unsigned short)((packet->data[2] << 8) | packet->data[3]); } -static size_t Curl_strnlen(const char *string, size_t maxlen) +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; @@ -313,14 +291,14 @@ static const char *tftp_option_get(const char *buf, size_t len, { size_t loc; - loc = Curl_strnlen(buf, len); + loc = tftp_strnlen(buf, len); loc++; /* NULL term */ if(loc >= len) return NULL; *option = buf; - loc += Curl_strnlen(buf+loc, len-loc); + loc += tftp_strnlen(buf + loc, len-loc); loc++; /* NULL term */ if(loc > len) @@ -330,27 +308,27 @@ static const char *tftp_option_get(const char *buf, size_t len, return &buf[loc]; } -static CURLcode tftp_parse_option_ack(tftp_state_data_t *state, +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->conn->data; + struct Curl_easy *data = state->data; - /* if OACK doesn't contain blksize option, the default (512) must be used */ + /* if OACK does not 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 == NULL) { + if(!tmp) { failf(data, "Malformed ACK packet, rejecting"); return CURLE_TFTP_ILLEGAL; } - infof(data, "got option=(%s) value=(%s)\n", option, value); + infof(data, "got option=(%s) value=(%s)", option, value); - if(checkprefix(option, TFTP_OPTION_BLKSIZE)) { + if(checkprefix(TFTP_OPTION_BLKSIZE, option)) { long blksize; blksize = strtol(value, NULL, 10); @@ -359,7 +337,7 @@ static CURLcode tftp_parse_option_ack(tftp_state_data_t *state, failf(data, "invalid blocksize value in OACK packet"); return CURLE_TFTP_ILLEGAL; } - else if(blksize > TFTP_BLKSIZE_MAX) { + if(blksize > TFTP_BLKSIZE_MAX) { failf(data, "%s (%d)", "blksize is larger than max supported", TFTP_BLKSIZE_MAX); return CURLE_TFTP_ILLEGAL; @@ -370,7 +348,7 @@ static CURLcode tftp_parse_option_ack(tftp_state_data_t *state, return CURLE_TFTP_ILLEGAL; } else if(blksize > state->requested_blksize) { - /* could realloc pkt buffers here, but the spec doesn't call out + /* could realloc pkt buffers here, but the spec does not call out * support for the server requesting a bigger blksize than the client * requests */ failf(data, "%s (%ld)", @@ -379,18 +357,18 @@ static CURLcode tftp_parse_option_ack(tftp_state_data_t *state, } state->blksize = (int)blksize; - infof(data, "%s (%d) %s (%d)\n", "blksize parsed from OACK", + infof(data, "%s (%d) %s (%d)", "blksize parsed from OACK", state->blksize, "requested", state->requested_blksize); } - else if(checkprefix(option, TFTP_OPTION_TSIZE)) { + else if(checkprefix(TFTP_OPTION_TSIZE, option)) { long tsize = 0; tsize = strtol(value, NULL, 10); - infof(data, "%s (%ld)\n", "tsize parsed from OACK", tsize); + 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->set.upload) { + if(!data->state.upload) { if(!tsize) { failf(data, "invalid tsize -:%s:- value in OACK packet", value); return CURLE_TFTP_ILLEGAL; @@ -403,23 +381,24 @@ static CURLcode tftp_parse_option_ack(tftp_state_data_t *state, return CURLE_OK; } -static size_t tftp_option_add(tftp_state_data_t *state, size_t csize, - char *buf, const char *option) +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 0; + if(( strlen(option) + *csize + 1) > (size_t)state->blksize) + return CURLE_TFTP_ILLEGAL; strcpy(buf, option); - return strlen(option) + 1; + *csize += strlen(option) + 1; + return CURLE_OK; } -static CURLcode tftp_connect_for_tx(tftp_state_data_t *state, +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->conn->data; + struct Curl_easy *data = state->data; - infof(data, "%s\n", "Connected for transmit"); + infof(data, "%s", "Connected for transmit"); #endif state->state = TFTP_STATE_TX; result = tftp_set_timeouts(state); @@ -428,14 +407,14 @@ static CURLcode tftp_connect_for_tx(tftp_state_data_t *state, return tftp_tx(state, event); } -static CURLcode tftp_connect_for_rx(tftp_state_data_t *state, +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->conn->data; + struct Curl_easy *data = state->data; - infof(data, "%s\n", "Connected for receive"); + infof(data, "%s", "Connected for receive"); #endif state->state = TFTP_STATE_RX; result = tftp_set_timeouts(state); @@ -444,18 +423,18 @@ static CURLcode tftp_connect_for_rx(tftp_state_data_t *state, return tftp_rx(state, event); } -static CURLcode tftp_send_first(tftp_state_data_t *state, tftp_event_t 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; - char buf[64]; - struct Curl_easy *data = state->conn->data; + struct Curl_easy *data = state->data; CURLcode result = CURLE_OK; - /* Set ascii mode if -B flag was used */ - if(data->set.prefer_ascii) + /* Set ASCII mode if -B flag was used */ + if(data->state.prefer_ascii) mode = "netascii"; switch(event) { @@ -464,17 +443,15 @@ static CURLcode tftp_send_first(tftp_state_data_t *state, tftp_event_t event) 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) { + if(state->retries > state->retry_max) { state->error = TFTP_ERR_NORESPONSE; state->state = TFTP_STATE_FIN; return result; } - if(data->set.upload) { + if(data->state.upload) { /* If we are uploading, send an WRQ */ setpacketevent(&state->spacket, TFTP_EVENT_WRQ); - state->conn->data->req.upload_fromhere = - (char *)state->spacket.data+4; if(data->state.infilesize != -1) Curl_pgrsSetUploadSize(data, data->state.infilesize); } @@ -483,63 +460,83 @@ static CURLcode tftp_send_first(tftp_state_data_t *state, tftp_event_t event) 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 + filename so we skip the always-present first letter of the path string. */ - result = Curl_urldecode(data, &state->conn->data->state.path[1], 0, - &filename, NULL, FALSE); + result = Curl_urldecode(&state->data->state.up.path[1], 0, + &filename, NULL, REJECT_ZERO); if(result) return result; - snprintf((char *)state->spacket.data+2, - state->blksize, - "%s%c%s%c", filename, '\0', mode, '\0'); + if(strlen(filename) > (state->blksize - strlen(mode) - 4)) { + failf(data, "TFTP filename too long"); + free(filename); + return CURLE_TFTP_ILLEGAL; /* too long filename 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->set.upload && (data->state.infilesize != -1)) - snprintf(buf, sizeof(buf), "%" CURL_FORMAT_CURL_OFF_T, - data->state.infilesize); + if(data->state.upload && (data->state.infilesize != -1)) + msnprintf(buf, sizeof(buf), "%" FMT_OFF_T, + data->state.infilesize); else strcpy(buf, "0"); /* the destination is large enough */ - sbytes += tftp_option_add(state, sbytes, - (char *)state->spacket.data+sbytes, - TFTP_OPTION_TSIZE); - sbytes += tftp_option_add(state, sbytes, - (char *)state->spacket.data+sbytes, buf); + 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 */ - snprintf(buf, sizeof(buf), "%d", state->requested_blksize); - sbytes += tftp_option_add(state, sbytes, - (char *)state->spacket.data+sbytes, - TFTP_OPTION_BLKSIZE); - sbytes += tftp_option_add(state, sbytes, - (char *)state->spacket.data+sbytes, buf); + 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 */ - snprintf(buf, sizeof(buf), "%d", state->retry_time); - sbytes += tftp_option_add(state, sbytes, - (char *)state->spacket.data+sbytes, - TFTP_OPTION_INTERVAL); - sbytes += tftp_option_add(state, sbytes, - (char *)state->spacket.data+sbytes, buf); + 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, - state->conn->ip_addr->ai_addr, - state->conn->ip_addr->ai_addrlen); + &data->conn->remote_addr->curl_sa_addr, + (curl_socklen_t)data->conn->remote_addr->addrlen); if(senddata != (ssize_t)sbytes) { - failf(data, "%s", Curl_strerror(state->conn, SOCKERRNO)); + char buffer[STRERROR_LEN]; + failf(data, "%s", Curl_strerror(SOCKERRNO, buffer, sizeof(buffer))); } free(filename); break; case TFTP_EVENT_OACK: - if(data->set.upload) { + if(data->state.upload) { result = tftp_connect_for_tx(state, event); } else { @@ -560,7 +557,7 @@ static CURLcode tftp_send_first(tftp_state_data_t *state, tftp_event_t event) break; default: - failf(state->conn->data, "tftp_send_first: internal error"); + failf(state->data, "tftp_send_first: internal error"); break; } @@ -569,7 +566,7 @@ static CURLcode tftp_send_first(tftp_state_data_t *state, tftp_event_t event) /* the next blocknum is x + 1 but it needs to wrap at an unsigned 16bit boundary */ -#define NEXT_BLOCKNUM(x) (((x)+1)&0xffff) +#define NEXT_BLOCKNUM(x) (((x) + 1)&0xffff) /********************************************************** * @@ -578,11 +575,13 @@ static CURLcode tftp_send_first(tftp_state_data_t *state, tftp_event_t event) * Event handler for the RX state * **********************************************************/ -static CURLcode tftp_rx(tftp_state_data_t *state, tftp_event_t event) +static CURLcode tftp_rx(struct tftp_state_data *state, + tftp_event_t event) { ssize_t sbytes; int rblock; - struct Curl_easy *data = state->conn->data; + struct Curl_easy *data = state->data; + char buffer[STRERROR_LEN]; switch(event) { @@ -590,18 +589,18 @@ static CURLcode tftp_rx(tftp_state_data_t *state, tftp_event_t event) /* 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. */ + /* 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.\n", rblock); + 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\n", + "Received unexpected DATA packet block %d, expecting block %d", rblock, NEXT_BLOCKNUM(state->block)); break; } @@ -615,18 +614,18 @@ static CURLcode tftp_rx(tftp_state_data_t *state, tftp_event_t event) (struct sockaddr *)&state->remote_addr, state->remote_addrlen); if(sbytes < 0) { - failf(data, "%s", Curl_strerror(state->conn, SOCKERRNO)); + 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) { + if(state->rbytes < (ssize_t)state->blksize + 4) { state->state = TFTP_STATE_FIN; } else { state->state = TFTP_STATE_RX; } - time(&state->rx_time); + state->rx_time = time(NULL); break; case TFTP_EVENT_OACK: @@ -640,20 +639,20 @@ static CURLcode tftp_rx(tftp_state_data_t *state, tftp_event_t event) (struct sockaddr *)&state->remote_addr, state->remote_addrlen); if(sbytes < 0) { - failf(data, "%s", Curl_strerror(state->conn, SOCKERRNO)); + failf(data, "%s", Curl_strerror(SOCKERRNO, buffer, sizeof(buffer))); return CURLE_SEND_ERROR; } - /* we're ready to RX data */ + /* we are ready to RX data */ state->state = TFTP_STATE_RX; - time(&state->rx_time); + state->rx_time = time(NULL); 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\n", + "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; @@ -665,8 +664,8 @@ static CURLcode tftp_rx(tftp_state_data_t *state, tftp_event_t event) 4, SEND_4TH_ARG, (struct sockaddr *)&state->remote_addr, state->remote_addrlen); - if(sbytes<0) { - failf(data, "%s", Curl_strerror(state->conn, SOCKERRNO)); + if(sbytes < 0) { + failf(data, "%s", Curl_strerror(SOCKERRNO, buffer, sizeof(buffer))); return CURLE_SEND_ERROR; } } @@ -679,8 +678,8 @@ static CURLcode tftp_rx(tftp_state_data_t *state, tftp_event_t event) 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 */ + /* do not 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 are done */ state->state = TFTP_STATE_FIN; break; @@ -699,14 +698,16 @@ static CURLcode tftp_rx(tftp_state_data_t *state, tftp_event_t event) * Event handler for the TX state * **********************************************************/ -static CURLcode tftp_tx(tftp_state_data_t *state, tftp_event_t event) +static CURLcode tftp_tx(struct tftp_state_data *state, tftp_event_t event) { - struct Curl_easy *data = state->conn->data; + struct Curl_easy *data = state->data; ssize_t sbytes; - int rblock; CURLcode result = CURLE_OK; struct SingleRequest *k = &data->req; - int cb; /* Bytes currently read */ + size_t cb; /* Bytes currently read */ + char buffer[STRERROR_LEN]; + char *bufptr; + bool eos; switch(event) { @@ -714,21 +715,21 @@ static CURLcode tftp_tx(tftp_state_data_t *state, tftp_event_t event) case TFTP_EVENT_OACK: if(event == TFTP_EVENT_ACK) { /* Ack the packet */ - rblock = getrpacketblock(&state->rpacket); + 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 + /* There is 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 are expecting * 0, also accept 65535. See - * http://syslinux.zytor.com/archives/2010-September/015253.html + * 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\n", + /* This is not 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) { + if(state->retries > state->retry_max) { failf(data, "tftp_tx: giving up waiting for block %d ack", state->block); result = CURLE_SEND_ERROR; @@ -736,21 +737,22 @@ static CURLcode tftp_tx(tftp_state_data_t *state, tftp_event_t event) else { /* Re-send the data packet */ sbytes = sendto(state->sockfd, (void *)state->spacket.data, - 4+state->sbytes, SEND_4TH_ARG, + 4 + (SEND_TYPE_ARG3)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(state->conn, SOCKERRNO)); + 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 + /* This is the expected packet. Reset the counters and send the next block */ - time(&state->rx_time); + state->rx_time = time(NULL); state->block++; } else @@ -759,7 +761,7 @@ static CURLcode tftp_tx(tftp_state_data_t *state, tftp_event_t event) state->retries = 0; setpacketevent(&state->spacket, TFTP_EVENT_DATA); setpacketblock(&state->spacket, state->block); - if(state->block > 1 && state->sbytes < (int)state->blksize) { + if(state->block > 1 && state->sbytes < state->blksize) { state->state = TFTP_STATE_FIN; return CURLE_OK; } @@ -769,23 +771,23 @@ static CURLcode tftp_tx(tftp_state_data_t *state, tftp_event_t event) * data block. * */ state->sbytes = 0; - state->conn->data->req.upload_fromhere = (char *)state->spacket.data+4; + bufptr = (char *)state->spacket.data + 4; do { - result = Curl_fillreadbuffer(state->conn, state->blksize - state->sbytes, - &cb); + result = Curl_client_read(data, bufptr, state->blksize - state->sbytes, + &cb, &eos); if(result) return result; - state->sbytes += cb; - state->conn->data->req.upload_fromhere += cb; - } while(state->sbytes < state->blksize && cb != 0); + state->sbytes += (int)cb; + bufptr += cb; + } while(state->sbytes < state->blksize && cb); sbytes = sendto(state->sockfd, (void *) state->spacket.data, - 4 + state->sbytes, SEND_4TH_ARG, + 4 + (SEND_TYPE_ARG3)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(state->conn, SOCKERRNO)); + if(sbytes < 0) { + failf(data, "%s", Curl_strerror(SOCKERRNO, buffer, sizeof(buffer))); return CURLE_SEND_ERROR; } /* Update the progress meter */ @@ -797,8 +799,8 @@ static CURLcode tftp_tx(tftp_state_data_t *state, tftp_event_t event) /* Increment the retry counter and log the timeout */ state->retries++; infof(data, "Timeout waiting for block %d ACK. " - " Retries = %d\n", NEXT_BLOCKNUM(state->block), state->retries); - /* Decide if we've had enough */ + " Retries = %d", NEXT_BLOCKNUM(state->block), state->retries); + /* Decide if we have had enough */ if(state->retries > state->retry_max) { state->error = TFTP_ERR_TIMEOUT; state->state = TFTP_STATE_FIN; @@ -806,12 +808,12 @@ static CURLcode tftp_tx(tftp_state_data_t *state, tftp_event_t event) else { /* Re-send the data packet */ sbytes = sendto(state->sockfd, (void *)state->spacket.data, - 4+state->sbytes, SEND_4TH_ARG, + 4 + (SEND_TYPE_ARG3)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(state->conn, SOCKERRNO)); + 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 */ @@ -826,8 +828,8 @@ static CURLcode tftp_tx(tftp_state_data_t *state, tftp_event_t event) (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 */ + /* do not 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 are done */ state->state = TFTP_STATE_FIN; break; @@ -898,30 +900,30 @@ static CURLcode tftp_translate_code(tftp_error_t error) * The tftp state machine event dispatcher * **********************************************************/ -static CURLcode tftp_state_machine(tftp_state_data_t *state, +static CURLcode tftp_state_machine(struct tftp_state_data *state, tftp_event_t event) { CURLcode result = CURLE_OK; - struct Curl_easy *data = state->conn->data; + struct Curl_easy *data = state->data; switch(state->state) { case TFTP_STATE_START: - DEBUGF(infof(data, "TFTP_STATE_START\n")); + DEBUGF(infof(data, "TFTP_STATE_START")); result = tftp_send_first(state, event); break; case TFTP_STATE_RX: - DEBUGF(infof(data, "TFTP_STATE_RX\n")); + DEBUGF(infof(data, "TFTP_STATE_RX")); result = tftp_rx(state, event); break; case TFTP_STATE_TX: - DEBUGF(infof(data, "TFTP_STATE_TX\n")); + DEBUGF(infof(data, "TFTP_STATE_TX")); result = tftp_tx(state, event); break; case TFTP_STATE_FIN: - infof(data, "%s\n", "TFTP finished"); + infof(data, "%s", "TFTP finished"); break; default: - DEBUGF(infof(data, "STATE: %d\n", state->state)); + DEBUGF(infof(data, "STATE: %d", state->state)); failf(data, "%s", "Internal state machine error"); result = CURLE_TFTP_ILLEGAL; break; @@ -937,9 +939,11 @@ static CURLcode tftp_state_machine(tftp_state_data_t *state, * The disconnect callback * **********************************************************/ -static CURLcode tftp_disconnect(struct connectdata *conn, bool dead_connection) +static CURLcode tftp_disconnect(struct Curl_easy *data, + struct connectdata *conn, bool dead_connection) { - tftp_state_data_t *state = conn->proto.tftpc; + struct tftp_state_data *state = conn->proto.tftpc; + (void) data; (void) dead_connection; /* done, free dynamically allocated pkt buffers */ @@ -959,51 +963,56 @@ static CURLcode tftp_disconnect(struct connectdata *conn, bool dead_connection) * The connect callback * **********************************************************/ -static CURLcode tftp_connect(struct connectdata *conn, bool *done) +static CURLcode tftp_connect(struct Curl_easy *data, bool *done) { - tftp_state_data_t *state; - int blksize, rc; + 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(tftp_state_data_t)); + 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(conn->data->set.tftp_blksize) { - blksize = (int)conn->data->set.tftp_blksize; - if(blksize > TFTP_BLKSIZE_MAX || blksize < TFTP_BLKSIZE_MIN) - return CURLE_TFTP_ILLEGAL; - } + 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, blksize + 2 + 2); + 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, blksize + 2 + 2); + 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 + /* we do not keep TFTP connections up basically because there is none or very * little gain for UDP */ connclose(conn, "TFTP"); - state->conn = conn; - state->sockfd = state->conn->sock[FIRSTSOCKET]; + state->data = data; + state->sockfd = conn->sock[FIRSTSOCKET]; state->state = TFTP_STATE_START; state->error = TFTP_ERR_NONE; - state->blksize = TFTP_BLKSIZE_DEFAULT; + state->blksize = TFTP_BLKSIZE_DEFAULT; /* Unless updated by OACK response */ state->requested_blksize = blksize; ((struct sockaddr *)&state->local_addr)->sa_family = - (unsigned short)(conn->ip_addr->ai_family); + (CURL_SA_FAMILY_T)(conn->remote_addr->family); tftp_set_timeouts(state); @@ -1021,17 +1030,18 @@ static CURLcode tftp_connect(struct connectdata *conn, bool *done) * assume uses the same IP version and thus hopefully this works for both * IPv4 and IPv6... */ - rc = bind(state->sockfd, (struct sockaddr *)&state->local_addr, - conn->ip_addr->ai_addrlen); + int rc = bind(state->sockfd, (struct sockaddr *)&state->local_addr, + (curl_socklen_t)conn->remote_addr->addrlen); if(rc) { - failf(conn->data, "bind() failed; %s", - Curl_strerror(conn, SOCKERRNO)); + 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(conn->data); + Curl_pgrsStartNow(data); *done = TRUE; @@ -1045,16 +1055,17 @@ static CURLcode tftp_connect(struct connectdata *conn, bool *done) * The done callback * **********************************************************/ -static CURLcode tftp_done(struct connectdata *conn, CURLcode status, +static CURLcode tftp_done(struct Curl_easy *data, CURLcode status, bool premature) { CURLcode result = CURLE_OK; - tftp_state_data_t *state = (tftp_state_data_t *)conn->proto.tftpc; + struct connectdata *conn = data->conn; + struct tftp_state_data *state = conn->proto.tftpc; (void)status; /* unused */ (void)premature; /* not used */ - if(Curl_pgrsDone(conn)) + if(Curl_pgrsDone(data)) return CURLE_ABORTED_BY_CALLBACK; /* If we have encountered an error */ @@ -1071,14 +1082,11 @@ static CURLcode tftp_done(struct connectdata *conn, CURLcode status, * The getsock callback * **********************************************************/ -static int tftp_getsock(struct connectdata *conn, curl_socket_t *socks, - int numsocks) +static int tftp_getsock(struct Curl_easy *data, + struct connectdata *conn, curl_socket_t *socks) { - if(!numsocks) - return GETSOCK_BLANK; - + (void)data; socks[0] = conn->sock[FIRSTSOCKET]; - return GETSOCK_READSOCK(0); } @@ -1089,24 +1097,23 @@ static int tftp_getsock(struct connectdata *conn, curl_socket_t *socks, * Called once select fires and data is ready on the socket * **********************************************************/ -static CURLcode tftp_receive_packet(struct connectdata *conn) +static CURLcode tftp_receive_packet(struct Curl_easy *data) { struct Curl_sockaddr_storage fromaddr; curl_socklen_t fromlen; CURLcode result = CURLE_OK; - struct Curl_easy *data = conn->data; - tftp_state_data_t *state = (tftp_state_data_t *)conn->proto.tftpc; - struct SingleRequest *k = &data->req; + 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, + (RECV_TYPE_ARG3)state->blksize + 4, 0, (struct sockaddr *)&fromaddr, &fromlen); - if(state->remote_addrlen==0) { + if(state->remote_addrlen == 0) { memcpy(&state->remote_addr, &fromaddr, fromlen); state->remote_addrlen = fromlen; } @@ -1119,33 +1126,38 @@ static CURLcode tftp_receive_packet(struct connectdata *conn) } else { /* The event is given by the TFTP packet time */ - state->event = (tftp_event_t)getrpacketevent(&state->rpacket); + 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 */ + /* Do not pass to the client empty or retransmitted packets */ if(state->rbytes > 4 && (NEXT_BLOCKNUM(state->block) == getrpacketblock(&state->rpacket))) { - result = Curl_client_write(conn, CLIENTWRITE_BODY, - (char *)state->rpacket.data+4, + 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; } - k->bytecount += state->rbytes-4; - Curl_pgrsSetDownloadCounter(data, (curl_off_t) k->bytecount); } break; case TFTP_EVENT_ERROR: - state->error = (tftp_error_t)getrpacketblock(&state->rpacket); - infof(data, "%s\n", (const char *)state->rpacket.data+4); + { + 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, + (const char *)state->rpacket.data + 2, state->rbytes-2); if(result) return result; @@ -1158,7 +1170,7 @@ static CURLcode tftp_receive_packet(struct connectdata *conn) } /* Update the progress meter */ - if(Curl_pgrsUpdate(conn)) { + if(Curl_pgrsUpdate(data)) { tftp_state_machine(state, TFTP_EVENT_ERROR); return CURLE_ABORTED_BY_CALLBACK; } @@ -1173,32 +1185,32 @@ static CURLcode tftp_receive_packet(struct connectdata *conn) * Check if timeouts have been reached * **********************************************************/ -static long tftp_state_timeout(struct connectdata *conn, tftp_event_t *event) +static timediff_t tftp_state_timeout(struct Curl_easy *data, + tftp_event_t *event) { - time_t current; - tftp_state_data_t *state = (tftp_state_data_t *)conn->proto.tftpc; + 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; - time(¤t); - if(current > state->max_time) { - DEBUGF(infof(conn->data, "timeout: %ld > %ld\n", - (long)current, (long)state->max_time)); + 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; } - else if(current > state->rx_time+state->retry_time) { + current = time(NULL); + if(current > state->rx_time + state->retry_time) { if(event) *event = TFTP_EVENT_TIMEOUT; - time(&state->rx_time); /* update even though we received nothing */ + state->rx_time = time(NULL); /* update even though we received nothing */ } - /* there's a typecast below here since 'time_t' may in fact be larger than - 'long', but we estimate that a 'long' will still be able to hold number - of seconds even if "only" 32 bit */ - return (long)(state->max_time - current); + return timeout_ms; } /********************************************************** @@ -1208,42 +1220,42 @@ static long tftp_state_timeout(struct connectdata *conn, tftp_event_t *event) * Handle single RX socket event and return * **********************************************************/ -static CURLcode tftp_multi_statemach(struct connectdata *conn, bool *done) +static CURLcode tftp_multi_statemach(struct Curl_easy *data, bool *done) { - int rc; - tftp_event_t event; - CURLcode result = CURLE_OK; - struct Curl_easy *data = conn->data; - tftp_state_data_t *state = (tftp_state_data_t *)conn->proto.tftpc; - long timeout_ms = tftp_state_timeout(conn, &event); + 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) { + if(timeout_ms < 0) { failf(data, "TFTP response timeout"); return CURLE_OPERATION_TIMEDOUT; } - else if(event != TFTP_EVENT_NONE) { + 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(conn, -1, -1, FALSE, NULL, -1, NULL); + /* Tell curl we are done */ + Curl_xfer_setup_nop(data); } else { /* no timeouts to handle, check our socket */ - rc = SOCKET_READABLE(state->sockfd, 0); + int rc = SOCKET_READABLE(state->sockfd, 0); if(rc == -1) { /* bail out */ int error = SOCKERRNO; - failf(data, "%s", Curl_strerror(conn, error)); + char buffer[STRERROR_LEN]; + failf(data, "%s", Curl_strerror(error, buffer, sizeof(buffer))); state->event = TFTP_EVENT_ERROR; } - else if(rc != 0) { - result = tftp_receive_packet(conn); + else if(rc) { + result = tftp_receive_packet(data); if(result) return result; result = tftp_state_machine(state, state->event); @@ -1251,8 +1263,8 @@ static CURLcode tftp_multi_statemach(struct connectdata *conn, bool *done) return result; *done = (state->state == TFTP_STATE_FIN) ? TRUE : FALSE; if(*done) - /* Tell curl we're done */ - Curl_setup_transfer(conn, -1, -1, FALSE, NULL, -1, NULL); + /* Tell curl we are done */ + Curl_xfer_setup_nop(data); } /* if rc == 0, then select() timed out */ } @@ -1267,37 +1279,38 @@ static CURLcode tftp_multi_statemach(struct connectdata *conn, bool *done) * Called from multi.c while DOing * **********************************************************/ -static CURLcode tftp_doing(struct connectdata *conn, bool *dophase_done) +static CURLcode tftp_doing(struct Curl_easy *data, bool *dophase_done) { CURLcode result; - result = tftp_multi_statemach(conn, dophase_done); + result = tftp_multi_statemach(data, dophase_done); if(*dophase_done) { - DEBUGF(infof(conn->data, "DO phase is complete\n")); + DEBUGF(infof(data, "DO phase is complete")); } else if(!result) { - /* The multi code doesn't have this logic for the DOING state so we + /* The multi code does not 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(conn)) + if(Curl_pgrsUpdate(data)) result = CURLE_ABORTED_BY_CALLBACK; else - result = Curl_speedcheck(conn->data, Curl_tvnow()); + result = Curl_speedcheck(data, Curl_now()); } return result; } /********************************************************** * - * tftp_peform + * tftp_perform * - * Entry point for transfer from tftp_do, sarts state mach + * Entry point for transfer from tftp_do, starts state mach * **********************************************************/ -static CURLcode tftp_perform(struct connectdata *conn, bool *dophase_done) +static CURLcode tftp_perform(struct Curl_easy *data, bool *dophase_done) { - CURLcode result = CURLE_OK; - tftp_state_data_t *state = (tftp_state_data_t *)conn->proto.tftpc; + CURLcode result = CURLE_OK; + struct connectdata *conn = data->conn; + struct tftp_state_data *state = conn->proto.tftpc; *dophase_done = FALSE; @@ -1306,10 +1319,10 @@ static CURLcode tftp_perform(struct connectdata *conn, bool *dophase_done) if((state->state == TFTP_STATE_FIN) || result) return result; - tftp_multi_statemach(conn, dophase_done); + tftp_multi_statemach(data, dophase_done); if(*dophase_done) - DEBUGF(infof(conn->data, "DO phase is complete\n")); + DEBUGF(infof(data, "DO phase is complete")); return result; } @@ -1325,24 +1338,25 @@ static CURLcode tftp_perform(struct connectdata *conn, bool *dophase_done) * **********************************************************/ -static CURLcode tftp_do(struct connectdata *conn, bool *done) +static CURLcode tftp_do(struct Curl_easy *data, bool *done) { - tftp_state_data_t *state; + struct tftp_state_data *state; CURLcode result; + struct connectdata *conn = data->conn; *done = FALSE; if(!conn->proto.tftpc) { - result = tftp_connect(conn, done); + result = tftp_connect(data, done); if(result) return result; } - state = (tftp_state_data_t *)conn->proto.tftpc; + state = conn->proto.tftpc; if(!state) - return CURLE_BAD_CALLING_ORDER; + return CURLE_TFTP_ILLEGAL; - result = tftp_perform(conn, done); + 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. */ @@ -1353,36 +1367,36 @@ static CURLcode tftp_do(struct connectdata *conn, bool *done) return result; } -static CURLcode tftp_setup_connection(struct connectdata * conn) +static CURLcode tftp_setup_connection(struct Curl_easy *data, + struct connectdata *conn) { - struct Curl_easy *data = conn->data; char *type; - char command; - conn->socktype = SOCK_DGRAM; /* UDP datagram based */ + conn->transport = TRNSPRT_UDP; /* TFTP URLs support an extension like ";mode=" that - * we'll try to get now! */ - type = strstr(data->state.path, ";mode="); + * we will 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->set.prefer_ascii = TRUE; + data->state.prefer_ascii = TRUE; break; case 'O': /* octet mode */ case 'I': /* binary mode */ default: /* switch off ASCII */ - data->set.prefer_ascii = FALSE; + data->state.prefer_ascii = FALSE; break; } } diff --git a/contrib/curl/lib/tftp.h b/contrib/curl/lib/tftp.h index c2325b2327..12404bf6d2 100644 --- a/contrib/curl/lib/tftp.h +++ b/contrib/curl/lib/tftp.h @@ -7,11 +7,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2007, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -20,10 +20,14 @@ * 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/contrib/curl/lib/timediff.c b/contrib/curl/lib/timediff.c new file mode 100644 index 0000000000..d0824d1448 --- /dev/null +++ b/contrib/curl/lib/timediff.c @@ -0,0 +1,88 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "timediff.h" + +#include + +/* + * 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/contrib/curl/lib/timediff.h b/contrib/curl/lib/timediff.h new file mode 100644 index 0000000000..75f996c55c --- /dev/null +++ b/contrib/curl/lib/timediff.h @@ -0,0 +1,52 @@ +#ifndef HEADER_CURL_TIMEDIFF_H +#define HEADER_CURL_TIMEDIFF_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +/* 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 FMT_TIMEDIFF_T FMT_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/contrib/curl/lib/timeval.c b/contrib/curl/lib/timeval.c index f3b207a37e..bb29bfdfee 100644 --- a/contrib/curl/lib/timeval.c +++ b/contrib/curl/lib/timeval.c @@ -5,11 +5,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2016, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -18,37 +18,49 @@ * 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) && !defined(MSDOS) +#if defined(_WIN32) + +#include +#include "system_win32.h" -struct timeval curlx_tvnow(void) +/* In case of bug fix this function has a counterpart in tool_util.c */ +struct curltime Curl_now(void) { - /* - ** GetTickCount() is available on _all_ Windows versions from W95 up - ** to nowadays. Returns milliseconds elapsed since last system boot, - ** increases monotonically and wraps once 49.7 days have elapsed. - */ - struct timeval now; -#if !defined(_WIN32_WINNT) || !defined(_WIN32_WINNT_VISTA) || \ - (_WIN32_WINNT < _WIN32_WINNT_VISTA) - DWORD milliseconds = GetTickCount(); - now.tv_sec = milliseconds / 1000; - now.tv_usec = (milliseconds % 1000) * 1000; -#else - ULONGLONG milliseconds = GetTickCount64(); - now.tv_sec = (long) (milliseconds / 1000); - now.tv_usec = (long) (milliseconds % 1000) * 1000; + 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 = (time_t)(milliseconds / 1000); + now.tv_usec = (int)((milliseconds % 1000) * 1000); + } return now; } -#elif defined(HAVE_CLOCK_GETTIME_MONOTONIC) +#elif defined(HAVE_CLOCK_GETTIME_MONOTONIC) || \ + defined(HAVE_CLOCK_GETTIME_MONOTONIC_RAW) -struct timeval curlx_tvnow(void) +struct curltime Curl_now(void) { /* ** clock_gettime() is granted to be increased monotonically when the @@ -57,32 +69,100 @@ struct timeval curlx_tvnow(void) ** 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; - if(0 == clock_gettime(CLOCK_MONOTONIC, &tsnow)) { - now.tv_sec = tsnow.tv_sec; - now.tv_usec = tsnow.tv_nsec / 1000; + + /* + ** clock_gettime() may be defined by Apple's SDK as weak symbol thus + ** code compiles but fails during runtime 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 = (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 = (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. + ** runtime. When this occurs simply fallback to other time source. */ #ifdef HAVE_GETTIMEOFDAY - else + else { (void)gettimeofday(&now, NULL); + cnow.tv_sec = now.tv_sec; + cnow.tv_usec = (int)now.tv_usec; + } #else else { - now.tv_sec = (long)time(NULL); - now.tv_usec = 0; + cnow.tv_sec = time(NULL); + cnow.tv_usec = 0; } #endif - return now; + return cnow; +} + +#elif defined(HAVE_MACH_ABSOLUTE_TIME) + +#include +#include + +struct curltime Curl_now(void) +{ + /* + ** Monotonic timer on macOS 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 timeval curlx_tvnow(void) +struct curltime Curl_now(void) { /* ** gettimeofday() is not granted to be increased monotonically, due to @@ -90,19 +170,22 @@ struct timeval curlx_tvnow(void) ** forward or backward in time. */ struct timeval now; + struct curltime ret; (void)gettimeofday(&now, NULL); - return now; + ret.tv_sec = now.tv_sec; + ret.tv_usec = (int)now.tv_usec; + return ret; } #else -struct timeval curlx_tvnow(void) +struct curltime Curl_now(void) { /* ** time() returns the value of time in seconds since the Epoch. */ - struct timeval now; - now.tv_sec = (long)time(NULL); + struct curltime now; + now.tv_sec = time(NULL); now.tv_usec = 0; return now; } @@ -110,41 +193,45 @@ struct timeval curlx_tvnow(void) #endif /* - * Make sure that the first argument is the more recent time, as otherwise - * we'll get a weird negative time-diff back... + * Returns: time difference in number of milliseconds. For too large diffs it + * returns max value. * - * Returns: the time difference in number of milliseconds. For large diffs it - * returns 0x7fffffff on 32bit time_t systems. + * @unittest: 1323 */ -time_t curlx_tvdiff(struct timeval newer, struct timeval older) +timediff_t Curl_timediff(struct curltime newer, struct curltime older) { -#if SIZEOF_TIME_T < 8 - /* for 32bit time_t systems, add a precaution to avoid overflow for really - big time differences */ - time_t diff = newer.tv_sec-older.tv_sec; - if(diff >= (0x7fffffff/1000)) - return 0x7fffffff; -#endif - return (newer.tv_sec-older.tv_sec)*1000+ - (time_t)(newer.tv_usec-older.tv_usec)/1000; + 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; } /* - * Same as curlx_tvdiff but with full usec resolution. - * - * Returns: the time difference in seconds with subsecond resolution. + * Returns: time difference in number of milliseconds, rounded up. + * For too large diffs it returns max value. */ -double curlx_tvdiff_secs(struct timeval newer, struct timeval older) +timediff_t Curl_timediff_ceil(struct curltime newer, struct curltime older) { - if(newer.tv_sec != older.tv_sec) - return (double)(newer.tv_sec-older.tv_sec)+ - (double)(newer.tv_usec-older.tv_usec)/1000000.0; - else - return (double)(newer.tv_usec-older.tv_usec)/1000000.0; + 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; } -/* return the number of seconds in the given input timeval struct */ -time_t Curl_tvlong(struct timeval t1) +/* + * 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) { - return t1.tv_sec; + 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/contrib/curl/lib/timeval.h b/contrib/curl/lib/timeval.h index 09f8b3a20e..33dfb5b10d 100644 --- a/contrib/curl/lib/timeval.h +++ b/contrib/curl/lib/timeval.h @@ -7,11 +7,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2016, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -20,39 +20,43 @@ * 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_setup.h" -struct timeval curlx_tvnow(void); +#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 (t1) is the more recent time and t2 is - * the older time, as otherwise you get a weird negative time-diff back... + * 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. */ -time_t curlx_tvdiff(struct timeval t1, struct timeval t2); +timediff_t Curl_timediff(struct curltime newer, struct curltime older); /* - * Same as curlx_tvdiff but with full usec resolution. + * 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 seconds with subsecond resolution. + * Returns: the time difference in number of milliseconds, rounded up. */ -double curlx_tvdiff_secs(struct timeval t1, struct timeval t2); +timediff_t Curl_timediff_ceil(struct curltime newer, struct curltime older); -time_t Curl_tvlong(struct timeval t1); - -/* These two defines below exist to provide the older API for library - internals only. */ -#define Curl_tvnow() curlx_tvnow() -#define Curl_tvdiff(x,y) curlx_tvdiff(x,y) -#define Curl_tvdiff_secs(x,y) curlx_tvdiff_secs(x,y) +/* + * 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/contrib/curl/lib/transfer.c b/contrib/curl/lib/transfer.c index a577bf7243..2f003529fd 100644 --- a/contrib/curl/lib/transfer.c +++ b/contrib/curl/lib/transfer.c @@ -5,11 +5,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2016, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -18,6 +18,8 @@ * 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" @@ -38,9 +40,7 @@ #ifdef HAVE_SYS_IOCTL_H #include #endif -#ifdef HAVE_SIGNAL_H #include -#endif #ifdef HAVE_SYS_PARAM_H #include @@ -48,10 +48,12 @@ #ifdef HAVE_SYS_SELECT_H #include +#elif defined(HAVE_UNISTD_H) +#include #endif #ifndef HAVE_SOCKET -#error "We can't compile without socket() support!" +#error "We cannot compile without socket() support!" #endif #include "urldata.h" @@ -60,6 +62,8 @@ #include "content_encoding.h" #include "hostip.h" +#include "cfilters.h" +#include "cw-out.h" #include "transfer.h" #include "sendf.h" #include "speedcheck.h" @@ -68,282 +72,60 @@ #include "url.h" #include "getinfo.h" #include "vtls/vtls.h" +#include "vquic/vquic.h" #include "select.h" #include "multiif.h" #include "connect.h" -#include "non-ascii.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) /* - * This function will call the read callback to fill our buffer with data - * to upload. + * 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. */ -CURLcode Curl_fillreadbuffer(struct connectdata *conn, int bytes, int *nreadp) +char *Curl_checkheaders(const struct Curl_easy *data, + const char *thisheader, + const size_t thislen) { - struct Curl_easy *data = conn->data; - size_t buffersize = (size_t)bytes; - int nread; -#ifdef CURL_DOES_CONVERSIONS - bool sending_http_headers = FALSE; - - if(conn->handler->protocol&(PROTO_FAMILY_HTTP|CURLPROTO_RTSP)) { - const struct HTTP *http = data->req.protop; - - if(http->sending == HTTPSEND_REQUEST) - /* We're sending the HTTP request headers, not the data. - Remember that so we don't re-translate them into garbage. */ - sending_http_headers = TRUE; - } -#endif - - if(data->req.upload_chunky) { - /* if chunked Transfer-Encoding */ - buffersize -= (8 + 2 + 2); /* 32bit hex + CRLF + CRLF */ - data->req.upload_fromhere += (8 + 2); /* 32bit hex + CRLF */ - } - - /* this function returns a size_t, so we typecast to int to prevent warnings - with picky compilers */ - nread = (int)data->state.fread_func(data->req.upload_fromhere, 1, - buffersize, data->state.in); - - if(nread == CURL_READFUNC_ABORT) { - failf(data, "operation aborted by callback"); - *nreadp = 0; - return CURLE_ABORTED_BY_CALLBACK; - } - else if(nread == CURL_READFUNC_PAUSE) { - - 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, "Read callback asked for PAUSE when not supported!"); - return CURLE_READ_ERROR; - } - else { - struct SingleRequest *k = &data->req; - /* 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((size_t)nread > buffersize) { - /* the read function returned a too large value */ - *nreadp = 0; - failf(data, "read function returned funny value"); - return CURLE_READ_ERROR; + 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; } - if(!data->req.forbidchunk && data->req.upload_chunky) { - /* if chunked Transfer-Encoding - * build chunk: - * - * CRLF - * CRLF - */ - /* On non-ASCII platforms the may or may not be - translated based on set.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. - */ - - char hexbuffer[11]; - const char *endofline_native; - const char *endofline_network; - int hexlen; - - if( -#ifdef CURL_DO_LINEEND_CONV - (data->set.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"; - } - hexlen = snprintf(hexbuffer, sizeof(hexbuffer), - "%x%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 */ - memcpy(data->req.upload_fromhere + nread, - endofline_network, - strlen(endofline_network)); - -#ifdef CURL_DOES_CONVERSIONS - CURLcode result; - int length; - if(data->set.prefer_ascii) { - /* translate the protocol and data */ - length = nread; - } - else { - /* just translate the protocol portion */ - length = strlen(hexbuffer); - } - result = Curl_convert_to_network(data, data->req.upload_fromhere, length); - /* Curl_convert_to_network calls failf if unsuccessful */ - if(result) - return result; -#endif /* CURL_DOES_CONVERSIONS */ - - if((nread - hexlen) == 0) - /* mark this as done once this chunk is transferred */ - data->req.upload_done = TRUE; - - nread+=(int)strlen(endofline_native); /* for the added end of line */ - } -#ifdef CURL_DOES_CONVERSIONS - else if((data->set.prefer_ascii) && (!sending_http_headers)) { - CURLcode result; - result = Curl_convert_to_network(data, data->req.upload_fromhere, nread); - /* Curl_convert_to_network calls failf if unsuccessful */ - if(result) - return result; - } -#endif /* CURL_DOES_CONVERSIONS */ - - *nreadp = nread; - - return CURLE_OK; + return NULL; } +#endif - -/* - * Curl_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. - */ -CURLcode Curl_readrewind(struct connectdata *conn) +static int data_pending(struct Curl_easy *data) { - struct Curl_easy *data = conn->data; - - conn->bits.rewindaftersend = 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 - */ - if(data->set.postfields || - (data->set.httpreq == HTTPREQ_POST_FORM)) - ; /* do nothing */ - else { - if(data->set.seek_func) { - int err; - - err = (data->set.seek_func)(data->set.seek_client, 0, SEEK_SET); - if(err) { - failf(data, "seek callback returned error %d", (int)err); - return CURLE_SEND_FAIL_REWIND; - } - } - else if(data->set.ioctl_func) { - curlioerr err; - - err = (data->set.ioctl_func)(data, CURLIOCMD_RESTARTREAD, - data->set.ioctl_client); - infof(data, "the ioctl callback returned %d\n", (int)err); - - if(err) { - /* FIXME: convert to a human readable error message */ - 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; - } + struct connectdata *conn = data->conn; - /* 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; -} + if(conn->handler->protocol&PROTO_FAMILY_FTP) + return Curl_conn_data_pending(data, SECONDARYSOCKET); -static int data_pending(const struct connectdata *conn) -{ /* 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) || -#if defined(USE_NGHTTP2) - Curl_ssl_data_pending(conn, FIRSTSOCKET) || - /* For HTTP/2, we may read up everything including responde body - with header fields in Curl_http_readwrite_headers. If no - content-length is provided, curl waits for the connection - close, which we emulate it using conn->proto.httpc.closed = - TRUE. The thing is if we read everything, then http2_recv won't - be called and we cannot signal the HTTP/2 stream has closed. As - a workaround, we return nonzero here to call http2_recv. */ - ((conn->handler->protocol&PROTO_FAMILY_HTTP) && conn->httpversion == 20); -#else - Curl_ssl_data_pending(conn, FIRSTSOCKET); -#endif -} - -static void read_rewind(struct connectdata *conn, - size_t thismuch) -{ - DEBUGASSERT(conn->read_pos >= thismuch); - - conn->read_pos -= thismuch; - conn->bits.stream_was_rewound = TRUE; - -#ifdef DEBUGBUILD - { - char buf[512 + 1]; - size_t show; - - show = CURLMIN(conn->buf_len - conn->read_pos, sizeof(buf)-1); - if(conn->master_buffer) { - memcpy(buf, conn->master_buffer + conn->read_pos, show); - buf[show] = '\0'; - } - else { - buf[0] = '\0'; - } - - DEBUGF(infof(conn->data, - "Buffer after stream rewind (read_pos = %zu): [%s]\n", - conn->read_pos, buf)); - } -#endif + Curl_conn_data_pending(data, FIRSTSOCKET); } /* @@ -360,7 +142,7 @@ bool Curl_meets_timecondition(struct Curl_easy *data, time_t timeofdoc) default: if(timeofdoc <= data->set.timevalue) { infof(data, - "The requested document is not new enough\n"); + "The requested document is not new enough"); data->info.timecond = TRUE; return FALSE; } @@ -368,7 +150,7 @@ bool Curl_meets_timecondition(struct Curl_easy *data, time_t timeofdoc) case CURL_TIMECOND_IFUNMODSINCE: if(timeofdoc >= data->set.timevalue) { infof(data, - "The requested document is not old enough\n"); + "The requested document is not old enough"); data->info.timecond = TRUE; return FALSE; } @@ -378,797 +160,340 @@ bool Curl_meets_timecondition(struct Curl_easy *data, time_t timeofdoc) return TRUE; } +static CURLcode xfer_recv_shutdown(struct Curl_easy *data, bool *done) +{ + int sockindex; + + if(!data || !data->conn) + return CURLE_FAILED_INIT; + if(data->conn->sockfd == CURL_SOCKET_BAD) + return CURLE_FAILED_INIT; + sockindex = (data->conn->sockfd == data->conn->sock[SECONDARYSOCKET]); + return Curl_conn_shutdown(data, sockindex, done); +} + +static bool xfer_recv_shutdown_started(struct Curl_easy *data) +{ + int sockindex; + + if(!data || !data->conn) + return CURLE_FAILED_INIT; + if(data->conn->sockfd == CURL_SOCKET_BAD) + return CURLE_FAILED_INIT; + sockindex = (data->conn->sockfd == data->conn->sock[SECONDARYSOCKET]); + return Curl_shutdown_started(data, sockindex); +} + +CURLcode Curl_xfer_send_shutdown(struct Curl_easy *data, bool *done) +{ + int sockindex; + + if(!data || !data->conn) + return CURLE_FAILED_INIT; + if(data->conn->writesockfd == CURL_SOCKET_BAD) + return CURLE_FAILED_INIT; + sockindex = (data->conn->writesockfd == data->conn->sock[SECONDARYSOCKET]); + return Curl_conn_shutdown(data, sockindex, done); +} + +/** + * Receive raw response data for the transfer. + * @param data the transfer + * @param buf buffer to keep response data received + * @param blen length of `buf` + * @param eos_reliable if EOS detection in underlying connection is reliable + * @param err error code in case of -1 return + * @return number of bytes read or -1 for error + */ +static ssize_t Curl_xfer_recv_resp(struct Curl_easy *data, + char *buf, size_t blen, + bool eos_reliable, + CURLcode *err) +{ + ssize_t nread; + + DEBUGASSERT(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(!eos_reliable && !data->req.header && data->req.size != -1) { + curl_off_t totalleft = data->req.size - data->req.bytecount; + if(totalleft <= 0) + blen = 0; + else if(totalleft < (curl_off_t)blen) + blen = (size_t)totalleft; + } + else if(xfer_recv_shutdown_started(data)) { + /* we already received everything. Do not try more. */ + blen = 0; + } + + if(!blen) { + /* want nothing more */ + *err = CURLE_OK; + nread = 0; + } + else { + *err = Curl_xfer_recv(data, buf, blen, &nread); + } + + if(*err) + return -1; + if(nread == 0) { + if(data->req.shutdown) { + bool done; + *err = xfer_recv_shutdown(data, &done); + if(*err) + return -1; + if(!done) { + *err = CURLE_AGAIN; + return -1; + } + } + DEBUGF(infof(data, "sendrecv_dl: we are done")); + } + DEBUGASSERT(nread >= 0); + return nread; +} + /* * 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) +static CURLcode sendrecv_dl(struct Curl_easy *data, + struct SingleRequest *k, + int *didwhat) { + struct connectdata *conn = data->conn; CURLcode result = CURLE_OK; - ssize_t nread; /* number of bytes read */ - size_t excess = 0; /* excess bytes read */ - bool is_empty_data = FALSE; - bool readmore = FALSE; /* used by RTP to signal for more data */ - int maxloops = 100; + char *buf, *xfer_buf; + size_t blen, xfer_blen; + int maxloops = 10; + curl_off_t total_received = 0; + bool is_multiplex = FALSE; - *done = FALSE; - *comeback = FALSE; + result = Curl_multi_xfer_buf_borrow(data, &xfer_buf, &xfer_blen); + if(result) + goto out; /* This is where we loop until we have read everything there is to read or we get a CURLE_AGAIN */ do { - size_t buffersize = data->set.buffer_size? - data->set.buffer_size : BUFSIZE; - size_t bytestoread = buffersize; - - if( -#if defined(USE_NGHTTP2) - /* For HTTP/2, 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. */ - !((conn->handler->protocol & PROTO_FAMILY_HTTP) && - conn->httpversion == 20) && -#endif - k->size != -1 && !k->header) { - /* make sure we don't read "too much" if we can help it since we - might be pipelining and then someone else might want to read what - follows! */ - curl_off_t totalleft = k->size - k->bytecount; - if(totalleft < (curl_off_t)bytestoread) - bytestoread = (size_t)totalleft; + bool is_eos = FALSE; + size_t bytestoread; + ssize_t nread; + + if(!is_multiplex) { + /* Multiplexed connection have inherent handling of EOF and we do not + * have to carefully restrict the amount we try to read. + * Multiplexed changes only in one direction. */ + is_multiplex = Curl_conn_is_multiplex(conn, FIRSTSOCKET); } - if(bytestoread) { - /* receive data from the network! */ - result = Curl_read(conn, conn->sockfd, k->buf, bytestoread, &nread); - - /* read would've blocked */ - if(CURLE_AGAIN == result) - break; /* get out of loop */ + buf = xfer_buf; + bytestoread = xfer_blen; - if(result>0) - return result; - } - 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!\n")); - nread = 0; + if(bytestoread && data->set.max_recv_speed > 0) { + /* In case of speed limit on receiving: if this loop already got + * data, break out. If not, limit the amount of bytes to receive. + * The overall, timed, speed limiting is done in multi.c */ + if(total_received) + break; + if(data->set.max_recv_speed < (curl_off_t)bytestoread) + bytestoread = (size_t)data->set.max_recv_speed; } - if((k->bytecount == 0) && (k->writebytecount == 0)) { - 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_tvnow(); + nread = Curl_xfer_recv_resp(data, buf, bytestoread, + is_multiplex, &result); + if(nread < 0) { + if(CURLE_AGAIN != result) + goto out; /* real error */ + result = CURLE_OK; + if(data->req.download_done && data->req.no_body && + !data->req.resp_trailer) { + DEBUGF(infof(data, "EAGAIN, download done, no trailer announced, " + "not waiting for EOS")); + nread = 0; + /* continue as if we read the EOS */ + } + else + break; /* get out of loop */ } + /* We only get a 0-length read on EndOfStream */ + blen = (size_t)nread; + is_eos = (blen == 0); *didwhat |= KEEP_RECV; - /* indicates data of zero size, i.e. empty file */ - is_empty_data = ((nread == 0) && (k->bodywrites == 0)) ? TRUE : FALSE; - - /* NUL terminate, allowing string ops to be used */ - if(0 < nread || is_empty_data) { - k->buf[nread] = 0; - } - else if(0 >= nread) { - /* if we receive 0 or less here, the server closed the connection - and we bail out from this! */ - DEBUGF(infof(data, "nread <= 0, server closed connection, bailing\n")); - k->keepon &= ~KEEP_RECV; - break; - } - /* Default buffer to use when we write the buffer, it may be changed - in the flow below before the actual storing is done. */ - k->str = k->buf; - - if(conn->handler->readwrite) { - result = conn->handler->readwrite(data, conn, &nread, &readmore); + 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(is_multiplex) + DEBUGF(infof(data, "nread == 0, stream closed, bailing")); + else + DEBUGF(infof(data, "nread <= 0, server closed connection, bailing")); + result = Curl_req_stop_send_recv(data); if(result) - return result; - if(readmore) + goto out; + if(k->eos_written) /* already did write this to client, leave */ break; } + total_received += blen; -#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) { - /* we are in parse-the-header-mode */ - bool stop_reading = FALSE; - result = Curl_http_readwrite_headers(data, conn, &nread, &stop_reading); - if(result) - return result; - - if(conn->handler->readwrite && - (k->maxdownload <= 0 && nread > 0)) { - result = conn->handler->readwrite(data, conn, &nread, &readmore); - if(result) - return result; - if(readmore) - break; - } - - if(stop_reading) { - /* We've stopped dealing with input, get out of the do-while loop */ - - if(nread > 0) { - if(Curl_pipeline_wanted(conn->data->multi, CURLPIPE_HTTP1)) { - infof(data, - "Rewinding stream by : %zd" - " bytes on url %s (zero-length body)\n", - nread, data->state.path); - read_rewind(conn, (size_t)nread); - } - else { - infof(data, - "Excess found in a non pipelined read:" - " excess = %zd" - " url = %s (zero-length body)\n", - nread, data->state.path); - } - } + result = Curl_xfer_write_resp(data, buf, blen, is_eos); + if(result || data->req.done) + goto out; - break; - } + /* if we are done, we stop receiving. On multiplexed connections, + * we should read the EOS. Which may arrive as meta data after + * the bytes. Not taking it in might lead to RST of streams. */ + if((!is_multiplex && data->req.download_done) || is_eos) { + data->req.keepon &= ~KEEP_RECV; } -#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->str && !k->header && (nread > 0 || is_empty_data)) { - - if(data->set.opt_no_body) { - /* data arrives although we want none, bail out */ - streamclose(conn, "ignoring body"); - *done = TRUE; - return CURLE_WEIRD_SERVER_REPLY; - } - -#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 */ - - 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 re-use this connection properly, we read the full - response in "ignore more" */ - k->ignorebody = TRUE; - infof(data, "Ignoring the response-body\n"); - } - if(data->state.resume_from && !k->content_range && - (data->set.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"); - connclose(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 a - HTTP/1.1 client */ - - if(!Curl_meets_timecondition(data, k->timeofdoc)) { - *done = TRUE; - /* We're simulating a http 304 from server so we return - what should have been returned from the server */ - data->info.httpcode = 304; - infof(data, "Simulate a HTTP 304 response!\n"); - /* we abort the transfer before it is completed == we ruin the - re-use ability. Close the connection */ - connclose(conn, "Simulated 304 handling"); - return CURLE_OK; - } - } /* we have a time condition */ - - } /* this is HTTP or RTSP */ - } /* this is the first time we write a body part */ -#endif /* CURL_DISABLE_HTTP */ - - k->bodywrites++; - - /* pass data to the debug function before it gets "dechunked" */ - if(data->set.verbose) { - if(k->badheader) { - Curl_debug(data, CURLINFO_DATA_IN, data->state.headerbuff, - (size_t)k->hbuflen, conn); - if(k->badheader == HEADER_PARTHEADER) - Curl_debug(data, CURLINFO_DATA_IN, - k->str, (size_t)nread, conn); - } - else - Curl_debug(data, CURLINFO_DATA_IN, - k->str, (size_t)nread, conn); - } - -#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. The returned 'nread' holds the number - * of actual data it wrote to the client. - */ - - CHUNKcode res = - Curl_httpchunk_read(conn, k->str, nread, &nread); - - if(CHUNKE_OK < res) { - if(CHUNKE_WRITE_ERROR == res) { - failf(data, "Failed writing data"); - return CURLE_WRITE_ERROR; - } - failf(data, "%s in chunked-encoding", Curl_chunked_strerror(res)); - return CURLE_RECV_ERROR; - } - else if(CHUNKE_STOP == res) { - size_t dataleft; - /* we're done reading chunks! */ - k->keepon &= ~KEEP_RECV; /* read no more */ - - /* There are now possibly N number of bytes at the end of the - str buffer that weren't written to the client. - - We DO care about this data if we are pipelining. - Push it back to be read on the next pass. */ - - dataleft = conn->chunk.dataleft; - if(dataleft != 0) { - infof(conn->data, "Leftovers after chunking: %zu bytes\n", - dataleft); - if(Curl_pipeline_wanted(conn->data->multi, CURLPIPE_HTTP1)) { - /* only attempt the rewind if we truly are pipelining */ - infof(conn->data, "Rewinding %zu bytes\n",dataleft); - read_rewind(conn, dataleft); - } - } - } - /* If it returned OK, we just keep going */ - } -#endif /* CURL_DISABLE_HTTP */ - - /* Account for body content stored in the header buffer */ - if(k->badheader && !k->ignorebody) { - DEBUGF(infof(data, "Increasing bytecount by %zu from hbuflen\n", - k->hbuflen)); - k->bytecount += k->hbuflen; - } - - if((-1 != k->maxdownload) && - (k->bytecount + nread >= k->maxdownload)) { - - excess = (size_t)(k->bytecount + nread - k->maxdownload); - if(excess > 0 && !k->ignorebody) { - if(Curl_pipeline_wanted(conn->data->multi, CURLPIPE_HTTP1)) { - /* The 'excess' amount below can't be more than BUFSIZE which - always will fit in a size_t */ - infof(data, - "Rewinding stream by : %zu" - " bytes on url %s (size = %" CURL_FORMAT_CURL_OFF_T - ", maxdownload = %" CURL_FORMAT_CURL_OFF_T - ", bytecount = %" CURL_FORMAT_CURL_OFF_T ", nread = %zd)\n", - excess, data->state.path, - k->size, k->maxdownload, k->bytecount, nread); - read_rewind(conn, excess); - } - else { - infof(data, - "Excess found in a non pipelined read:" - " excess = %zu" - ", size = %" CURL_FORMAT_CURL_OFF_T - ", maxdownload = %" CURL_FORMAT_CURL_OFF_T - ", bytecount = %" CURL_FORMAT_CURL_OFF_T "\n", - excess, k->size, k->maxdownload, k->bytecount); - } - } - - nread = (ssize_t) (k->maxdownload - k->bytecount); - if(nread < 0) /* this should be unusual */ - nread = 0; - - k->keepon &= ~KEEP_RECV; /* we're done reading */ - } - - k->bytecount += nread; - - Curl_pgrsSetDownloadCounter(data, k->bytecount); - - if(!k->chunk && (nread || k->badheader || is_empty_data)) { - /* If this is chunky transfer, it was already written */ - - if(k->badheader && !k->ignorebody) { - /* we parsed a piece of data wrongly assuming it was a header - and now we output it as body instead */ - - /* Don't let excess data pollute body writes */ - if(k->maxdownload == -1 || (curl_off_t)k->hbuflen <= k->maxdownload) - result = Curl_client_write(conn, CLIENTWRITE_BODY, - data->state.headerbuff, - k->hbuflen); - else - result = Curl_client_write(conn, CLIENTWRITE_BODY, - data->state.headerbuff, - (size_t)k->maxdownload); - - if(result) - return result; - } - if(k->badheader < HEADER_ALLBAD) { - /* This switch handles various content encodings. If there's an - error here, be sure to check over the almost identical code - in http_chunks.c. - Make sure that ALL_CONTENT_ENCODINGS contains all the - encodings handled here. */ -#ifdef HAVE_LIBZ - switch(conn->data->set.http_ce_skip ? - IDENTITY : k->auto_decoding) { - case IDENTITY: -#endif - /* This is the default when the server sends no - Content-Encoding header. See Curl_readwrite_init; the - memset() call initializes k->auto_decoding to zero. */ - if(!k->ignorebody) { - -#ifndef CURL_DISABLE_POP3 - if(conn->handler->protocol&PROTO_FAMILY_POP3) - result = Curl_pop3_write(conn, k->str, nread); - else -#endif /* CURL_DISABLE_POP3 */ - - result = Curl_client_write(conn, CLIENTWRITE_BODY, k->str, - nread); - } -#ifdef HAVE_LIBZ - break; - - case DEFLATE: - /* Assume CLIENTWRITE_BODY; headers are not encoded. */ - if(!k->ignorebody) - result = Curl_unencode_deflate_write(conn, k, nread); - break; - - case GZIP: - /* Assume CLIENTWRITE_BODY; headers are not encoded. */ - if(!k->ignorebody) - result = Curl_unencode_gzip_write(conn, k, nread); - break; - - default: - failf(data, "Unrecognized content encoding type. " - "libcurl understands `identity', `deflate' and `gzip' " - "content encodings."); - result = CURLE_BAD_CONTENT_ENCODING; - break; - } -#endif - } - k->badheader = HEADER_NORMAL; /* taken care of now */ - - if(result) - return result; - } - - } /* if(!header and data to read) */ - - if(conn->handler->readwrite && - (excess > 0 && !conn->bits.stream_was_rewound)) { - /* Parse the excess data */ - k->str += nread; - nread = (ssize_t)excess; - - result = conn->handler->readwrite(data, conn, &nread, &readmore); - if(result) - return result; - - if(readmore) - k->keepon |= KEEP_RECV; /* we're not done reading */ + /* if we are PAUSEd or stopped receiving, leave the loop */ + if((k->keepon & KEEP_RECV_PAUSE) || !(k->keepon & KEEP_RECV)) break; - } - - if(is_empty_data) { - /* if we received nothing, the server closed the connection and we - are done */ - k->keepon &= ~KEEP_RECV; - } - } while(data_pending(conn) && maxloops--); + } while(maxloops--); - if(maxloops <= 0) { - /* we mark it as read-again-please */ - conn->cselect_bits = CURL_CSELECT_IN; - *comeback = TRUE; + if((maxloops <= 0) || data_pending(data)) { + /* did not read until EAGAIN or there is still pending data, mark as + read-again-please */ + data->state.select_bits = CURL_CSELECT_IN; + if((k->keepon & KEEP_SENDBITS) == KEEP_SEND) + data->state.select_bits |= CURL_CSELECT_OUT; } if(((k->keepon & (KEEP_RECV|KEEP_SEND)) == KEEP_SEND) && - conn->bits.close) { - /* 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 + (conn->bits.close || is_multiplex)) { + /* When we have read the entire thing and the close bit is set, the server + may now close the connection. If there is 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\n"); - k->keepon &= ~KEEP_SEND; /* no writing anymore either */ + infof(data, "we are done reading and this is set to close, stop send"); + Curl_req_abort_sending(data); } - return CURLE_OK; +out: + Curl_multi_xfer_buf_release(data, xfer_buf); + if(result) + DEBUGF(infof(data, "sendrecv_dl() -> %d", result)); + return result; } -static CURLcode done_sending(struct connectdata *conn, - struct SingleRequest *k) +/* + * Send data to upload to the server, when the socket is writable. + */ +static CURLcode sendrecv_ul(struct Curl_easy *data, int *didwhat) { - k->keepon &= ~KEEP_SEND; /* we're done writing */ - - Curl_http2_done_sending(conn); - - if(conn->bits.rewindaftersend) { - CURLcode result = Curl_readrewind(conn); - if(result) - return result; + /* We should not get here when the sending is already done. It + * probably means that someone set `data-req.keepon |= KEEP_SEND` + * when it should not. */ + DEBUGASSERT(!Curl_req_done_sending(data)); + + if(!Curl_req_done_sending(data)) { + *didwhat |= KEEP_SEND; + return Curl_req_send_more(data); } return CURLE_OK; } - -/* - * Send data to upload to the server, when the socket is writable. - */ -static CURLcode readwrite_upload(struct Curl_easy *data, - struct connectdata *conn, - struct SingleRequest *k, - int *didwhat) +static int select_bits_paused(struct Curl_easy *data, int select_bits) { - ssize_t i, si; - ssize_t bytes_written; - CURLcode result; - ssize_t nread; /* number of bytes read */ - bool sending_http_headers = FALSE; - - if((k->bytecount == 0) && (k->writebytecount == 0)) - Curl_pgrsTime(data, TIMER_STARTTRANSFER); - - *didwhat |= KEEP_SEND; - - do { - - /* only read more data if there's no upload data already - present in the upload buffer */ - if(0 == data->req.upload_present) { - /* init the "upload from here" pointer */ - data->req.upload_fromhere = k->uploadbuf; - - if(!k->upload_done) { - /* HTTP pollution, this should be written nicer to become more - protocol agnostic. */ - int fillcount; - struct HTTP *http = data->req.protop; - - 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_tvnow(); /* 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); - 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; - } - - result = Curl_fillreadbuffer(conn, BUFSIZE, &fillcount); - if(result) - return result; - - nread = (ssize_t)fillcount; - } - else - nread = 0; /* we're done uploading/reading */ - - if(!nread && (k->keepon & KEEP_SEND_PAUSE)) { - /* this is a paused transfer */ - break; - } - else if(nread<=0) { - result = done_sending(conn, k); - if(result) - return result; - break; - } - - /* store number of bytes available for upload */ - data->req.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->set.prefer_ascii) || -#endif - (data->set.crlf))) { - /* Do we need to allocate a scratch buffer? */ - if(!data->state.scratch) { - data->state.scratch = malloc(2 * BUFSIZE); - 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. - */ - for(i = 0, si = 0; i < nread; i++, si++) { - if(data->req.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] = data->req.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 */ - data->req.upload_fromhere = data->state.scratch; - - /* set the new amount too */ - data->req.upload_present = nread; - } - } - -#ifndef CURL_DISABLE_SMTP - if(conn->handler->protocol & PROTO_FAMILY_SMTP) { - result = Curl_smtp_escape_eob(conn, nread); - if(result) - return result; - } -#endif /* CURL_DISABLE_SMTP */ - } /* if 0 == data->req.upload_present */ - 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(conn, - conn->writesockfd, /* socket to send to */ - data->req.upload_fromhere, /* buffer pointer */ - data->req.upload_present, /* buffer size */ - &bytes_written); /* actually sent */ - - if(result) - return result; - - if(data->set.verbose) - /* show the data before we change the pointer upload_fromhere */ - Curl_debug(data, CURLINFO_DATA_OUT, data->req.upload_fromhere, - (size_t)bytes_written, conn); - - k->writebytecount += bytes_written; - - if(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\n"); - } - - if(data->req.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 */ - data->req.upload_present -= bytes_written; - - /* advance the pointer where to find the buffer when the next send - is to happen */ - data->req.upload_fromhere += bytes_written; - } - else { - /* we've uploaded that buffer now */ - data->req.upload_fromhere = k->uploadbuf; - data->req.upload_present = 0; /* no more bytes left */ - - if(k->upload_done) { - result = done_sending(conn, k); - if(result) - return result; - } - } - - Curl_pgrsSetUploadCounter(data, k->writebytecount); - - } WHILE_FALSE; /* just to break out from! */ - - return CURLE_OK; + /* 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. */ + + /* if there is data in a direction not paused, return false */ + if(((select_bits & CURL_CSELECT_IN) && + !(data->req.keepon & KEEP_RECV_PAUSE)) || + ((select_bits & CURL_CSELECT_OUT) && + !(data->req.keepon & KEEP_SEND_PAUSE))) + return FALSE; + + return (data->req.keepon & (KEEP_RECV_PAUSE|KEEP_SEND_PAUSE)); } /* - * Curl_readwrite() is the low-level function to be called when data is to + * Curl_sendrecv() 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) +CURLcode Curl_sendrecv(struct Curl_easy *data, struct curltime *nowp) { struct SingleRequest *k = &data->req; - CURLcode result; - int didwhat=0; - - curl_socket_t fd_read; - curl_socket_t fd_write; - int select_res = conn->cselect_bits; - - conn->cselect_bits = 0; - - /* 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; - - if(conn->data->state.drain) { - select_res |= CURL_CSELECT_IN; - DEBUGF(infof(data, "Curl_readwrite: forcibly told to drain data\n")); + CURLcode result = CURLE_OK; + int didwhat = 0; + + DEBUGASSERT(nowp); + if(data->state.select_bits) { + if(select_bits_paused(data, data->state.select_bits)) { + /* leave the bits unchanged, so they'll tell us what to do when + * this transfer gets unpaused. */ + result = CURLE_OK; + goto out; + } + data->state.select_bits = 0; } - if(!select_res) /* Call for select()/poll() only, if read/write/error - status is not known. */ - select_res = Curl_socket_check(fd_read, CURL_SOCKET_BAD, fd_write, 0); - - if(select_res == CURL_CSELECT_ERR) { - failf(data, "select/poll returned error"); - return CURLE_SEND_ERROR; +#ifdef USE_HYPER + if(data->conn->datastream) { + result = data->conn->datastream(data, data->conn, &didwhat, + CURL_CSELECT_OUT|CURL_CSELECT_IN); + if(result || data->req.done) + goto out; } - - /* 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_res & CURL_CSELECT_IN) || conn->bits.stream_was_rewound)) { - - result = readwrite_data(data, conn, k, &didwhat, done, comeback); - if(result || *done) - return result; + 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) { + result = sendrecv_dl(data, k, &didwhat); + if(result || data->req.done) + goto out; } /* If we still have writing to do, we check if we have a writable socket. */ - if((k->keepon & KEEP_SEND) && (select_res & CURL_CSELECT_OUT)) { - /* write */ - - result = readwrite_upload(data, conn, k, &didwhat); + if(Curl_req_want_send(data) || (data->req.keepon & KEEP_SEND_TIMED)) { + result = sendrecv_ul(data, &didwhat); if(result) - return result; + goto out; } - - k->now = Curl_tvnow(); - if(didwhat) { - /* Update read/write counters */ - if(k->bytecountp) - *k->bytecountp = k->bytecount; /* read count */ - if(k->writebytecountp) - *k->writebytecountp = k->writebytecount; /* write count */ +#ifdef USE_HYPER } - else { - /* 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. - - */ - - time_t ms = Curl_tvdiff(k->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; - infof(data, "Done waiting for 100-continue\n"); - } - } +#endif + + if(!didwhat) { + /* Transfer wanted to send/recv, but nothing was possible. */ + result = Curl_conn_ev_data_idle(data); + if(result) + goto out; } - if(Curl_pgrsUpdate(conn)) + if(Curl_pgrsUpdate(data)) result = CURLE_ABORTED_BY_CALLBACK; else - result = Curl_speedcheck(data, k->now); + result = Curl_speedcheck(data, *nowp); if(result) - return result; + goto out; if(k->keepon) { - if(0 > Curl_timeleft(data, &k->now, FALSE)) { + if(0 > Curl_timeleft(data, nowp, FALSE)) { if(k->size != -1) { - failf(data, "Operation timed out after %ld milliseconds with %" - CURL_FORMAT_CURL_OFF_T " out of %" - CURL_FORMAT_CURL_OFF_T " bytes received", - Curl_tvdiff(k->now, data->progress.t_startsingle), k->bytecount, - k->size); + failf(data, "Operation timed out after %" FMT_TIMEDIFF_T + " milliseconds with %" FMT_OFF_T " out of %" + FMT_OFF_T " bytes received", + Curl_timediff(*nowp, data->progress.t_startsingle), + k->bytecount, k->size); } else { - failf(data, "Operation timed out after %ld milliseconds with %" - CURL_FORMAT_CURL_OFF_T " bytes received", - Curl_tvdiff(k->now, data->progress.t_startsingle), k->bytecount); + failf(data, "Operation timed out after %" FMT_TIMEDIFF_T + " milliseconds with %" FMT_OFF_T " bytes received", + Curl_timediff(*nowp, data->progress.t_startsingle), + k->bytecount); } - return CURLE_OPERATION_TIMEDOUT; + result = CURLE_OPERATION_TIMEDOUT; + goto out; } } else { @@ -1176,99 +501,27 @@ CURLcode Curl_readwrite(struct connectdata *conn, * The transfer has been performed. Just make some general checks before * returning. */ - - if(!(data->set.opt_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 */ - !data->req.newurl) { - failf(data, "transfer closed with %" CURL_FORMAT_CURL_OFF_T - " bytes remaining to read", - k->size - k->bytecount); - return CURLE_PARTIAL_FILE; + if(!(data->req.no_body) && (k->size != -1) && + (k->bytecount != k->size) && !k->newurl) { + failf(data, "transfer closed with %" FMT_OFF_T + " bytes remaining to read", k->size - k->bytecount); + result = CURLE_PARTIAL_FILE; + goto out; } - else if(!(data->set.opt_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"); - return CURLE_PARTIAL_FILE; + if(Curl_pgrsUpdate(data)) { + result = CURLE_ABORTED_BY_CALLBACK; + goto out; } - if(Curl_pgrsUpdate(conn)) - return CURLE_ABORTED_BY_CALLBACK; } - /* Now update the "done" boolean we return */ - *done = (0 == (k->keepon&(KEEP_RECV|KEEP_SEND| - KEEP_RECV_PAUSE|KEEP_SEND_PAUSE))) ? TRUE : FALSE; - - return CURLE_OK; -} - -/* - * 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(const struct connectdata *conn, - curl_socket_t *sock, /* points to numsocks number - of sockets */ - int numsocks) -{ - const struct Curl_easy *data = conn->data; - int bitmap = GETSOCK_BLANK; - unsigned sockindex = 0; - - if(conn->handler->perform_getsock) - return conn->handler->perform_getsock(conn, sock, numsocks); - - if(numsocks < 2) - /* simple check but we might need two slots */ - return GETSOCK_BLANK; - - /* 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; - } + /* If there is nothing more to send/recv, the request is done */ + if(0 == (k->keepon&(KEEP_RECVBITS|KEEP_SENDBITS))) + data->req.done = TRUE; - bitmap |= GETSOCK_WRITESOCK(sockindex); - } - - return bitmap; +out: + if(result) + DEBUGF(infof(data, "Curl_sendrecv() -> %d", result)); + return result; } /* Curl_init_CONNECT() gets called each time the handle switches to CONNECT @@ -1277,6 +530,7 @@ 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); } /* @@ -1287,12 +541,44 @@ void Curl_init_CONNECT(struct Curl_easy *data) CURLcode Curl_pretransfer(struct Curl_easy *data) { CURLcode result; - if(!data->change.url) { - /* we can't do anything without URL */ - failf(data, "No URL set!"); + + if(!data->state.url && !data->set.uh) { + /* we cannot 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 cannot */ + 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. */ @@ -1300,30 +586,39 @@ CURLcode Curl_pretransfer(struct Curl_easy *data) if(result) return result; - data->set.followlocation=0; /* reset the location-follow counter */ + 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.httpversion = 0; /* don't assume any particular server version */ - + 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); - data->info.wouldredirect = NULL; + Curl_data_priority_clear_state(data); - if(data->set.httpreq == HTTPREQ_PUT) + if(data->state.httpreq == HTTPREQ_PUT) data->state.infilesize = data->set.filesize; - else + 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! */ - if(data->change.cookielist) - Curl_cookie_loadfiles(data); + Curl_cookie_loadfiles(data); /* If there is a list of host pairs to deal with */ - if(data->change.resolve) + 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 @@ -1339,299 +634,80 @@ CURLcode Curl_pretransfer(struct Curl_easy *data) #endif Curl_initinfo(data); /* reset session-specific information "variables" */ - Curl_pgrsResetTimesSizes(data); + Curl_pgrsResetTransferSizes(data); Curl_pgrsStartNow(data); - if(data->set.timeout) - Curl_expire(data, data->set.timeout); - - if(data->set.connecttimeout) - Curl_expire(data, data->set.connecttimeout); - - /* In case the handle is re-used and an authentication method was picked + /* 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; - if(data->set.wildcardmatch) { - struct WildcardData *wc = &data->wildcard; - if(!wc->filelist) { +#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; } } - - } - - 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; -} - -#ifndef CURL_DISABLE_HTTP -/* - * strlen_url() returns the length of the given URL if the spaces within the - * URL were properly URL encoded. - */ -static size_t strlen_url(const char *url) -{ - const unsigned char *ptr; - size_t newlen=0; - bool left=TRUE; /* left side of the ? */ - - for(ptr=(unsigned char *)url; *ptr; ptr++) { - switch(*ptr) { - case '?': - left=FALSE; - /* fall through */ - default: - if(*ptr >= 0x80) - newlen += 2; - newlen++; - break; - case ' ': - if(left) - newlen+=3; - else - newlen++; - break; - } + result = Curl_hsts_loadcb(data, data->hsts); } - return newlen; -} -/* strcpy_url() copies a url to a output buffer and URL-encodes the spaces in - * the source URL accordingly. - */ -static void strcpy_url(char *output, const char *url) -{ - /* we must add this with whitespace-replacing */ - bool left=TRUE; - const unsigned char *iptr; - char *optr = output; - for(iptr = (unsigned char *)url; /* read from here */ - *iptr; /* until zero byte */ - iptr++) { - switch(*iptr) { - case '?': - left=FALSE; - /* fall through */ - default: - if(*iptr >= 0x80) { - snprintf(optr, 4, "%%%02x", *iptr); - optr += 3; - } - else - *optr++=*iptr; - break; - case ' ': - if(left) { - *optr++='%'; /* add a '%' */ - *optr++='2'; /* add a '2' */ - *optr++='0'; /* add a '0' */ - } - else - *optr++='+'; /* add a '+' here */ - break; - } - } - *optr=0; /* zero terminate output buffer */ - -} - -/* - * Returns true if the given URL is absolute (as opposed to relative) - */ -static bool is_absolute_url(const char *url) -{ - char prot[16]; /* URL protocol string storage */ - char letter; /* used for a silly sscanf */ - - return (2 == sscanf(url, "%15[^?&/:]://%c", prot, &letter)) ? TRUE : FALSE; -} - -/* - * 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). - */ -static char *concat_url(const 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... - */ - char *newest; - char *protsep; - char *pathsep; - size_t newlen; - - const char *useurl = relurl; - size_t urllen; - - /* we must make our own copy of the URL to play with, as it may - point to read-only data */ - char *url_clone=strdup(base); - - if(!url_clone) - return NULL; /* skip out of this NOW */ - - /* protsep points to the start of the host name */ - protsep=strstr(url_clone, "//"); - if(!protsep) - protsep=url_clone; - 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[0] == '/') && (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 */ - } - 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.url.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.url.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; - } - } - } - - /* If the new part contains a space, this is a mighty stupid redirect - but we still make an effort to do "right". To the left of a '?' - letter we replace each space with %20 while it is replaced with '+' - on the right side of the '?' letter. - */ - newlen = strlen_url(useurl); - - urllen = strlen(url_clone); - - newest = malloc(urllen + 1 + /* possible slash */ - newlen + 1 /* zero byte */); - - if(!newest) { - free(url_clone); /* don't leak this */ - return NULL; + /* + * Set user-agent. Used for HTTP, but since we can attempt to tunnel + * basically anything through an HTTP proxy we cannot 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; } - /* copy over the root url part */ - memcpy(newest, url_clone, urllen); - - /* check if we need to append a slash */ - if(('/' == useurl[0]) || (protsep && !*protsep) || ('?' == useurl[0])) - ; - else - newest[urllen++]='/'; - - /* then append the new piece on the right side */ - strcpy_url(&newest[urllen], useurl); - - free(url_clone); + 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]); +#ifndef CURL_DISABLE_PROXY + if(!result) + result = Curl_setstropt(&data->state.aptr.proxyuser, + data->set.str[STRING_PROXYUSERNAME]); + if(!result) + result = Curl_setstropt(&data->state.aptr.proxypasswd, + data->set.str[STRING_PROXYPASSWORD]); +#endif - return newest; + data->req.headerbytecount = 0; + Curl_headers_cleanup(data); + return result; } -#endif /* CURL_DISABLE_HTTP */ /* * 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, /* this 'newurl' is the Location: string, - and it must be malloc()ed before passed - here */ + char *newurl, /* the Location: string */ followtype type) /* see transfer.h */ { #ifdef CURL_DISABLE_HTTP @@ -1644,93 +720,173 @@ CURLcode Curl_follow(struct Curl_easy *data, /* 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->set.followlocation >= data->set.maxredirs)) { - failf(data, "Maximum (%ld) redirects followed", data->set.maxredirs); - return CURLE_TOO_MANY_REDIRECTS; + (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 */ - /* mark the next request as a followed location: */ - data->state.this_is_a_follow = TRUE; + if(data->set.http_auto_referer) { + CURLU *u; + char *referer = NULL; - data->set.followlocation++; /* count location-followers */ + /* 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->set.http_auto_referer) { - /* 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; + } - if(data->change.referer_alloc) { - Curl_safefree(data->change.referer); - data->change.referer_alloc = FALSE; - } + /* Make a copy of the URL without credentials and fragment */ + u = curl_url(); + if(!u) + return CURLE_OUT_OF_MEMORY; - data->change.referer = strdup(data->change.url); - if(!data->change.referer) - return CURLE_OUT_OF_MEMORY; - data->change.referer_alloc = TRUE; /* yes, free this later */ + 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(!is_absolute_url(newurl)) { - /*** - *DANG* this is an RFC 2068 violation. The URL is supposed - to be absolute and this doesn't seem to be that! - */ - char *absolute = concat_url(data->change.url, newurl); - if(!absolute) - return CURLE_OUT_OF_MEMORY; - free(newurl); - newurl = absolute; + 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: do not allow a custom port number */ + disallowport = TRUE; } - else { - /* The new URL MAY contain space or high byte values, that means a mighty - stupid redirect URL but we still make an effort to do "right". */ - char *newest; - size_t newlen = strlen_url(newurl); - /* This is an absolute URL, don't allow the custom port number */ - disallowport = TRUE; + DEBUGASSERT(data->state.uh); + uc = curl_url_set(data->state.uh, CURLUPART_URL, newurl, (unsigned int) + ((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); + } - newest = malloc(newlen+1); /* get memory for this */ - if(!newest) + /* 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; - strcpy_url(newest, newurl); /* create a space-free URL */ - - free(newurl); /* that was no good */ - newurl = newest; /* use this instead now */ + } + 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! */ + /* we are only figuring out the new URL if we would have followed locations + but now we are 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->change.url_alloc) { - Curl_safefree(data->change.url); - data->change.url_alloc = FALSE; - } - - data->change.url = newurl; - data->change.url_alloc = TRUE; - newurl = NULL; /* don't free! */ + if(data->state.url_alloc) + Curl_safefree(data->state.url); - infof(data, "Issue another request to this URL: '%s'\n", data->change.url); + data->state.url = newurl; + data->state.url_alloc = TRUE; + Curl_req_soft_reset(&data->req, data); + 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 - * a HTTP (proxy-) authentication scheme other than Basic. + * 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 @@ -1741,15 +897,15 @@ CURLcode Curl_follow(struct Curl_easy *data, /* 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. + /* Some codes are explicitly mentioned since I have 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 + * method from POST to GET for the subsequent request. If this * behavior is undesired, the 307 (Temporary Redirect) status code * can be used instead. * @@ -1759,21 +915,23 @@ CURLcode Curl_follow(struct Curl_easy *data, * 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 behaviour is forbidden by RFC1945 and the obsolete RFC2616, and + * This behavior is forbidden by RFC1945 and the obsolete RFC2616, and * can be overridden with CURLOPT_POSTREDIR. */ - if((data->set.httpreq == HTTPREQ_POST - || data->set.httpreq == HTTPREQ_POST_FORM) + 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\n"); - data->set.httpreq = HTTPREQ_GET; + infof(data, "Switch from POST to GET"); + data->state.httpreq = HTTPREQ_GET; + Curl_creader_set_rewind(data, FALSE); } 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 + * method from POST to GET for the subsequent request. If this * behavior is undesired, the 307 (Temporary Redirect) status code * can be used instead. * @@ -1783,44 +941,52 @@ CURLcode Curl_follow(struct Curl_easy *data, * 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 behaviour is forbidden by RFC1945 and the obsolete RFC2616, and + * This behavior is forbidden by RFC1945 and the obsolete RFC2616, and * can be overridden with CURLOPT_POSTREDIR. */ - if((data->set.httpreq == HTTPREQ_POST - || data->set.httpreq == HTTPREQ_POST_FORM) + 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\n"); - data->set.httpreq = HTTPREQ_GET; + infof(data, "Switch from POST to GET"); + data->state.httpreq = HTTPREQ_GET; + Curl_creader_set_rewind(data, FALSE); } break; case 303: /* See Other */ - /* Disable both types of POSTs, unless the user explicitely - asks for POST after POST */ - if(data->set.httpreq != HTTPREQ_GET - && !(data->set.keep_post & CURL_REDIR_POST_303)) { - data->set.httpreq = HTTPREQ_GET; /* enforce GET request */ - infof(data, "Disables POST, goes with %s\n", - data->set.opt_no_body?"HEAD":"GET"); + /* '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! + * We should not 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 + * 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_pgrsResetTimesSizes(data); + Curl_pgrsResetTransferSizes(data); return CURLE_OK; #endif /* CURL_DISABLE_HTTP */ @@ -1829,89 +995,116 @@ CURLcode Curl_follow(struct Curl_easy *data, /* Returns CURLE_OK *and* sets '*url' if a request retry is wanted. NOTE: that the *url is malloc()ed. */ -CURLcode Curl_retry_request(struct connectdata *conn, - char **url) +CURLcode Curl_retry_request(struct Curl_easy *data, char **url) { - struct Curl_easy *data = conn->data; - + 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->set.upload && + /* if we are talking upload, we cannot 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->set.opt_no_body - || (conn->handler->protocol & PROTO_FAMILY_HTTP)) && - (data->set.rtspreq != RTSPREQ_RECEIVE)) { - /* We got no data, we attempted to re-use a connection. For HTTP this + 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! */ - infof(conn->data, "Connection died, retrying a fresh connect\n"); - *url = strdup(conn->data->change.url); + 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 + conn->bits.retry = TRUE; /* mark this as a connection we are about to retry. Marking it this way should prevent i.e HTTP transfers to return error just because nothing has been transferred! */ - - - if(conn->handler->protocol&PROTO_FAMILY_HTTP) { - struct HTTP *http = data->req.protop; - if(http->writebytecount) - return Curl_readrewind(conn); - } + Curl_creader_set_rewind(data, TRUE); } return CURLE_OK; } /* - * Curl_setup_transfer() is called to setup some basic properties for the - * upcoming transfer. + * xfer_setup() is called to setup basic properties for the transfer. */ -void -Curl_setup_transfer( - struct connectdata *conn, /* connection data */ +static void xfer_setup( + 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 */ - curl_off_t *bytecountp, /* return number of bytes read or NULL */ int writesockindex, /* socket index to write to, it may very well be the same we read from. -1 disables */ - curl_off_t *writecountp /* return number of bytes written or NULL */ + bool shutdown, /* shutdown connection at transfer end. Only + * supported when sending OR receiving. */ + bool shutdown_err_ignore /* errors during shutdown do not fail the + * transfer */ ) { - struct Curl_easy *data; - struct SingleRequest *k; + struct SingleRequest *k = &data->req; + struct connectdata *conn = data->conn; + bool want_send = Curl_req_want_send(data); DEBUGASSERT(conn != NULL); - - data = conn->data; - k = &data->req; - DEBUGASSERT((sockindex <= 1) && (sockindex >= -1)); - - /* now copy all input parameters */ - conn->sockfd = sockindex == -1 ? + DEBUGASSERT((writesockindex <= 1) && (writesockindex >= -1)); + DEBUGASSERT(!shutdown || (sockindex == -1) || (writesockindex == -1)); + + if(conn->bits.multiplex || conn->httpversion >= 20 || want_send) { + /* 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(want_send) + /* 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; + conn->writesockfd = writesockindex == -1 ? + CURL_SOCKET_BAD : conn->sock[writesockindex]; + } + k->getheader = getheader; k->size = size; - k->bytecountp = bytecountp; - k->writebytecountp = writecountp; + k->shutdown = shutdown; + k->shutdown_err_ignore = shutdown_err_ignore; /* 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 @@ -1922,44 +1115,169 @@ Curl_setup_transfer( if(size > 0) Curl_pgrsSetDownloadSize(data, size); } - /* we want header and/or body, if neither then don't do this! */ - if(k->getheader || !data->set.opt_no_body) { + /* we want header and/or body, if neither then do not do this! */ + if(k->getheader || !data->req.no_body) { - if(conn->sockfd != CURL_SOCKET_BAD) + if(sockindex != -1) k->keepon |= KEEP_RECV; - if(conn->writesockfd != CURL_SOCKET_BAD) { - struct HTTP *http = data->req.protop; - /* 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_tvnow(); - - /* 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); - } - 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; + if(writesockindex != -1) + k->keepon |= KEEP_SEND; + } /* if(k->getheader || !data->req.no_body) */ - /* enable the write bit when we're not waiting for continue */ - k->keepon |= KEEP_SEND; - } - } /* if(conn->writesockfd != CURL_SOCKET_BAD) */ - } /* if(k->getheader || !data->set.opt_no_body) */ +} + +void Curl_xfer_setup_nop(struct Curl_easy *data) +{ + xfer_setup(data, -1, -1, FALSE, -1, FALSE, FALSE); +} + +void Curl_xfer_setup1(struct Curl_easy *data, + int send_recv, + curl_off_t recv_size, + bool getheader) +{ + int recv_index = (send_recv & CURL_XFER_RECV) ? FIRSTSOCKET : -1; + int send_index = (send_recv & CURL_XFER_SEND) ? FIRSTSOCKET : -1; + DEBUGASSERT((recv_index >= 0) || (recv_size == -1)); + xfer_setup(data, recv_index, recv_size, getheader, send_index, FALSE, FALSE); +} + +void Curl_xfer_setup2(struct Curl_easy *data, + int send_recv, + curl_off_t recv_size, + bool shutdown, + bool shutdown_err_ignore) +{ + int recv_index = (send_recv & CURL_XFER_RECV) ? SECONDARYSOCKET : -1; + int send_index = (send_recv & CURL_XFER_SEND) ? SECONDARYSOCKET : -1; + DEBUGASSERT((recv_index >= 0) || (recv_size == -1)); + xfer_setup(data, recv_index, recv_size, FALSE, send_index, + shutdown, shutdown_err_ignore); +} + +CURLcode Curl_xfer_write_resp(struct Curl_easy *data, + const char *buf, size_t blen, + bool is_eos) +{ + CURLcode result = CURLE_OK; + + if(data->conn->handler->write_resp) { + /* protocol handlers offering this function take full responsibility + * for writing all received download data to the client. */ + result = data->conn->handler->write_resp(data, buf, blen, is_eos); + } + else { + /* No special handling by protocol handler, write all received data + * as BODY to the client. */ + if(blen || is_eos) { + int cwtype = CLIENTWRITE_BODY; + if(is_eos) + cwtype |= CLIENTWRITE_EOS; + result = Curl_client_write(data, cwtype, buf, blen); + } + } + + if(!result && is_eos) { + /* If we wrote the EOS, we are definitely done */ + data->req.eos_written = TRUE; + data->req.download_done = TRUE; + } + CURL_TRC_WRITE(data, "xfer_write_resp(len=%zu, eos=%d) -> %d", + blen, is_eos, result); + return result; +} + +CURLcode Curl_xfer_write_resp_hd(struct Curl_easy *data, + const char *hd0, size_t hdlen, bool is_eos) +{ + if(data->conn->handler->write_resp_hd) { + /* protocol handlers offering this function take full responsibility + * for writing all received download data to the client. */ + return data->conn->handler->write_resp_hd(data, hd0, hdlen, is_eos); + } + /* No special handling by protocol handler, write as response bytes */ + return Curl_xfer_write_resp(data, hd0, hdlen, is_eos); +} + +CURLcode Curl_xfer_write_done(struct Curl_easy *data, bool premature) +{ + (void)premature; + return Curl_cw_out_done(data); +} + +bool Curl_xfer_needs_flush(struct Curl_easy *data) +{ + int sockindex; + sockindex = ((data->conn->writesockfd != CURL_SOCKET_BAD) && + (data->conn->writesockfd == data->conn->sock[SECONDARYSOCKET])); + return Curl_conn_needs_flush(data, sockindex); +} + +CURLcode Curl_xfer_flush(struct Curl_easy *data) +{ + int sockindex; + sockindex = ((data->conn->writesockfd != CURL_SOCKET_BAD) && + (data->conn->writesockfd == data->conn->sock[SECONDARYSOCKET])); + return Curl_conn_flush(data, sockindex); +} + +CURLcode Curl_xfer_send(struct Curl_easy *data, + const void *buf, size_t blen, bool eos, + size_t *pnwritten) +{ + CURLcode result; + int sockindex; + + DEBUGASSERT(data); + DEBUGASSERT(data->conn); + + sockindex = ((data->conn->writesockfd != CURL_SOCKET_BAD) && + (data->conn->writesockfd == data->conn->sock[SECONDARYSOCKET])); + result = Curl_conn_send(data, sockindex, buf, blen, eos, pnwritten); + if(result == CURLE_AGAIN) { + result = CURLE_OK; + *pnwritten = 0; + } + else if(!result && *pnwritten) + data->info.request_size += *pnwritten; + + DEBUGF(infof(data, "Curl_xfer_send(len=%zu, eos=%d) -> %d, %zu", + blen, eos, result, *pnwritten)); + return result; +} +CURLcode Curl_xfer_recv(struct Curl_easy *data, + char *buf, size_t blen, + ssize_t *pnrcvd) +{ + int sockindex; + + DEBUGASSERT(data); + DEBUGASSERT(data->conn); + DEBUGASSERT(data->set.buffer_size > 0); + + sockindex = ((data->conn->sockfd != CURL_SOCKET_BAD) && + (data->conn->sockfd == data->conn->sock[SECONDARYSOCKET])); + if((size_t)data->set.buffer_size < blen) + blen = (size_t)data->set.buffer_size; + return Curl_conn_recv(data, sockindex, buf, blen, pnrcvd); +} + +CURLcode Curl_xfer_send_close(struct Curl_easy *data) +{ + Curl_conn_ev_data_done_send(data); + return CURLE_OK; +} + +bool Curl_xfer_is_blocked(struct Curl_easy *data) +{ + bool want_send = ((data)->req.keepon & KEEP_SEND); + bool want_recv = ((data)->req.keepon & KEEP_RECV); + if(!want_send) + return (want_recv && Curl_cwriter_is_paused(data)); + else if(!want_recv) + return (want_send && Curl_creader_is_paused(data)); + else + return Curl_creader_is_paused(data) && Curl_cwriter_is_paused(data); } diff --git a/contrib/curl/lib/transfer.h b/contrib/curl/lib/transfer.h index 5189672605..87a5a389ff 100644 --- a/contrib/curl/lib/transfer.h +++ b/contrib/curl/lib/transfer.h @@ -7,11 +7,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2016, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -20,49 +20,137 @@ * 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_second_connect(struct connectdata *conn); -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 */ - FOLLOW_LAST /* never used */ + 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(const struct connectdata *conn, - curl_socket_t *socks, - int numsocks); -CURLcode Curl_readrewind(struct connectdata *conn); -CURLcode Curl_fillreadbuffer(struct connectdata *conn, int bytes, int *nreadp); -CURLcode Curl_retry_request(struct connectdata *conn, char **url); +CURLcode Curl_sendrecv(struct Curl_easy *data, struct curltime *nowp); +int Curl_single_getsock(struct Curl_easy *data, + struct connectdata *conn, curl_socket_t *socks); +CURLcode Curl_retry_request(struct Curl_easy *data, char **url); bool Curl_meets_timecondition(struct Curl_easy *data, time_t timeofdoc); -/* This sets up a forthcoming transfer */ -void -Curl_setup_transfer (struct connectdata *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 */ - curl_off_t *bytecountp, /* return number of bytes read */ - int writesockindex, /* socket index to write to, it may - very well be the same we read from. - -1 disables */ - curl_off_t *writecountp /* return number of bytes written */ -); +/** + * Write the transfer raw response bytes, as received from the connection. + * Will handle all passed bytes or return an error. By default, this will + * write the bytes as BODY to the client. Protocols may provide a + * "write_resp" callback in their handler to add specific treatment. E.g. + * HTTP parses response headers and passes them differently to the client. + * @param data the transfer + * @param buf the raw response bytes + * @param blen the amount of bytes in `buf` + * @param is_eos TRUE iff the connection indicates this to be the last + * bytes of the response + */ +CURLcode Curl_xfer_write_resp(struct Curl_easy *data, + const char *buf, size_t blen, + bool is_eos); -#endif /* HEADER_CURL_TRANSFER_H */ +/** + * Write a single "header" line from a server response. + * @param hd0 the 0-terminated, single header line + * @param hdlen the length of the header line + * @param is_eos TRUE iff this is the end of the response + */ +CURLcode Curl_xfer_write_resp_hd(struct Curl_easy *data, + const char *hd0, size_t hdlen, bool is_eos); + +#define CURL_XFER_NOP (0) +#define CURL_XFER_RECV (1<<(0)) +#define CURL_XFER_SEND (1<<(1)) +#define CURL_XFER_SENDRECV (CURL_XFER_RECV|CURL_XFER_SEND) + +/** + * The transfer is neither receiving nor sending now. + */ +void Curl_xfer_setup_nop(struct Curl_easy *data); + +/** + * The transfer will use socket 1 to send/recv. `recv_size` is + * the amount to receive or -1 if unknown. `getheader` indicates + * response header processing is expected. + */ +void Curl_xfer_setup1(struct Curl_easy *data, + int send_recv, + curl_off_t recv_size, + bool getheader); + +/** + * The transfer will use socket 2 to send/recv. `recv_size` is + * the amount to receive or -1 if unknown. With `shutdown` being + * set, the transfer is only allowed to either send OR receive + * and the socket 2 connection will be shutdown at the end of + * the transfer. An unclean shutdown will fail the transfer + * unless `shutdown_err_ignore` is TRUE. + */ +void Curl_xfer_setup2(struct Curl_easy *data, + int send_recv, + curl_off_t recv_size, + bool shutdown, bool shutdown_err_ignore); + +/** + * Multi has set transfer to DONE. Last chance to trigger + * missing response things like writing an EOS to the client. + */ +CURLcode Curl_xfer_write_done(struct Curl_easy *data, bool premature); +/** + * Return TRUE iff transfer has pending data to send. Checks involved + * connection filters. + */ +bool Curl_xfer_needs_flush(struct Curl_easy *data); + +/** + * Flush any pending send data on the transfer connection. + */ +CURLcode Curl_xfer_flush(struct Curl_easy *data); + +/** + * Send data on the socket/connection filter designated + * for transfer's outgoing data. + * Will return CURLE_OK on blocking with (*pnwritten == 0). + */ +CURLcode Curl_xfer_send(struct Curl_easy *data, + const void *buf, size_t blen, bool eos, + size_t *pnwritten); + +/** + * Receive data on the socket/connection filter designated + * for transfer's incoming data. + * Will return CURLE_AGAIN on blocking with (*pnrcvd == 0). + */ +CURLcode Curl_xfer_recv(struct Curl_easy *data, + char *buf, size_t blen, + ssize_t *pnrcvd); + +CURLcode Curl_xfer_send_close(struct Curl_easy *data); +CURLcode Curl_xfer_send_shutdown(struct Curl_easy *data, bool *done); + +/** + * Return TRUE iff the transfer is not done, but further progress + * is blocked. For example when it is only receiving and its writer + * is PAUSED. + */ +bool Curl_xfer_is_blocked(struct Curl_easy *data); + +#endif /* HEADER_CURL_TRANSFER_H */ diff --git a/contrib/curl/lib/url.c b/contrib/curl/lib/url.c index 38b9552f7a..8cebbcf15c 100644 --- a/contrib/curl/lib/url.c +++ b/contrib/curl/lib/url.c @@ -5,11 +5,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2017, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -18,6 +18,8 @@ * 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" @@ -34,10 +36,12 @@ #ifdef HAVE_NET_IF_H #include #endif +#ifdef HAVE_IPHLPAPI_H +#include +#endif #ifdef HAVE_SYS_IOCTL_H #include #endif - #ifdef HAVE_SYS_PARAM_H #include #endif @@ -52,25 +56,16 @@ #endif #ifndef HAVE_SOCKET -#error "We can't compile without socket() support!" +#error "We cannot compile without socket() support!" #endif -#ifdef HAVE_LIMITS_H #include -#endif - -#ifdef USE_LIBIDN2 -#include - -#elif defined(USE_WIN32_IDN) -/* prototype for curl_win32_idn_to_ascii() */ -bool curl_win32_idn_to_ascii(const char *in, char **out); -#endif /* USE_LIBIDN2 */ +#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" @@ -90,9 +85,13 @@ bool curl_win32_idn_to_ascii(const char *in, char **out); #include "easyif.h" #include "speedcheck.h" #include "warnless.h" -#include "non-ascii.h" -#include "inet_pton.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" @@ -103,319 +102,157 @@ bool curl_win32_idn_to_ascii(const char *in, char **out); #include "http2.h" #include "file.h" #include "curl_ldap.h" -#include "ssh.h" +#include "vssh/ssh.h" #include "imap.h" #include "url.h" #include "connect.h" #include "inet_ntop.h" #include "http_ntlm.h" -#include "curl_ntlm_wb.h" -#include "socks.h" #include "curl_rtmp.h" #include "gopher.h" +#include "mqtt.h" #include "http_proxy.h" #include "conncache.h" #include "multihandle.h" -#include "pipeline.h" -#include "dotdot.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" -/* Local static prototypes */ -static struct connectdata * -find_oldest_idle_connection_in_bundle(struct Curl_easy *data, - struct connectbundle *bundle); -static void conn_free(struct connectdata *conn); -static void free_fixed_hostname(struct hostname *host); -static void signalPipeClose(struct curl_llist *pipeline, bool pipe_broke); -static CURLcode parse_url_login(struct Curl_easy *data, - struct connectdata *conn, - char **userptr, char **passwdptr, - char **optionsptr); -static CURLcode parse_login_details(const char *login, const size_t len, - char **userptr, char **passwdptr, - char **optionsptr); -static unsigned int get_protocol_family(unsigned int protocol); - -/* - * Protocol table. - */ - -static const struct Curl_handler * const protocols[] = { - -#ifndef CURL_DISABLE_HTTP - &Curl_handler_http, -#endif - -#if defined(USE_SSL) && !defined(CURL_DISABLE_HTTP) - &Curl_handler_https, -#endif - -#ifndef CURL_DISABLE_FTP - &Curl_handler_ftp, -#endif - -#if defined(USE_SSL) && !defined(CURL_DISABLE_FTP) - &Curl_handler_ftps, -#endif - -#ifndef CURL_DISABLE_TELNET - &Curl_handler_telnet, -#endif - -#ifndef CURL_DISABLE_DICT - &Curl_handler_dict, -#endif - -#ifndef CURL_DISABLE_LDAP - &Curl_handler_ldap, -#if !defined(CURL_DISABLE_LDAPS) && \ - ((defined(USE_OPENLDAP) && defined(USE_SSL)) || \ - (!defined(USE_OPENLDAP) && defined(HAVE_LDAP_SSL))) - &Curl_handler_ldaps, -#endif -#endif - -#ifndef CURL_DISABLE_FILE - &Curl_handler_file, -#endif - -#ifndef CURL_DISABLE_TFTP - &Curl_handler_tftp, -#endif - -#ifdef USE_LIBSSH2 - &Curl_handler_scp, - &Curl_handler_sftp, -#endif - -#ifndef CURL_DISABLE_IMAP - &Curl_handler_imap, -#ifdef USE_SSL - &Curl_handler_imaps, -#endif -#endif - -#ifndef CURL_DISABLE_POP3 - &Curl_handler_pop3, -#ifdef USE_SSL - &Curl_handler_pop3s, -#endif -#endif - -#if !defined(CURL_DISABLE_SMB) && defined(USE_NTLM) && \ - (CURL_SIZEOF_CURL_OFF_T > 4) && \ - (!defined(USE_WINDOWS_SSPI) || defined(USE_WIN32_CRYPTO)) - &Curl_handler_smb, -#ifdef USE_SSL - &Curl_handler_smbs, -#endif -#endif - -#ifndef CURL_DISABLE_SMTP - &Curl_handler_smtp, -#ifdef USE_SSL - &Curl_handler_smtps, -#endif +#ifndef ARRAYSIZE +#define ARRAYSIZE(A) (sizeof(A)/sizeof((A)[0])) #endif -#ifndef CURL_DISABLE_RTSP - &Curl_handler_rtsp, +#ifdef USE_NGHTTP2 +static void data_priority_cleanup(struct Curl_easy *data); +#else +#define data_priority_cleanup(x) #endif -#ifndef CURL_DISABLE_GOPHER - &Curl_handler_gopher, +/* Some parts of the code (e.g. chunked encoding) assume this buffer has at + * more than just a few bytes to play with. Do not let it become too small or + * bad things will happen. + */ +#if READBUFFER_SIZE < READBUFFER_MIN +# error READBUFFER_SIZE is too small #endif -#ifdef USE_LIBRTMP - &Curl_handler_rtmp, - &Curl_handler_rtmpt, - &Curl_handler_rtmpe, - &Curl_handler_rtmpte, - &Curl_handler_rtmps, - &Curl_handler_rtmpts, +#ifdef USE_UNIX_SOCKETS +#define UNIX_SOCKET_PREFIX "localhost" #endif - (struct Curl_handler *) NULL -}; +/* Reject URLs exceeding this length */ +#define MAX_URL_LEN 0xffff /* - * Dummy handler for undefined protocol schemes. - */ - -static const struct Curl_handler Curl_handler_dummy = { - "", /* scheme */ - ZERO_NULL, /* setup_connection */ - ZERO_NULL, /* 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 */ - 0, /* defport */ - 0, /* protocol */ - PROTOPT_NONE /* flags */ -}; +* 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; - for(i=(enum dupstring)0; i < STRING_LAST; i++) { + enum dupblob j; + + for(i = (enum dupstring)0; i < STRING_LAST; i++) { Curl_safefree(data->set.str[i]); } - if(data->change.referer_alloc) { - Curl_safefree(data->change.referer); - data->change.referer_alloc = FALSE; - } - data->change.referer = NULL; - if(data->change.url_alloc) { - Curl_safefree(data->change.url); - data->change.url_alloc = FALSE; + for(j = (enum dupblob)0; j < BLOB_LAST; j++) { + Curl_safefree(data->set.blobs[j]); } - data->change.url = NULL; -} - -static CURLcode 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) { - char *str = strdup(s); - - if(!str) - return CURLE_OUT_OF_MEMORY; - - *charp = str; + if(data->state.referer_alloc) { + Curl_safefree(data->state.referer); + data->state.referer_alloc = FALSE; } - - 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) { - result = parse_login_details(option, strlen(option), - (userp ? &user : NULL), - (passwdp ? &passwd : NULL), - NULL); + data->state.referer = NULL; + if(data->state.url_alloc) { + Curl_safefree(data->state.url); + data->state.url_alloc = FALSE; } + data->state.url = 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; - } - } + Curl_mime_cleanpart(&data->set.mimepost); - return result; +#ifndef CURL_DISABLE_COOKIES + curl_slist_free_all(data->state.cookielist); + data->state.cookielist = NULL; +#endif } -CURLcode Curl_dupset(struct Curl_easy *dst, struct Curl_easy *src) +/* free the URL pieces */ +static void up_free(struct Curl_easy *data) { - CURLcode result = CURLE_OK; - enum dupstring i; - - /* Copy src->set into dst->set first, then deal with the strings - afterwards */ - dst->set = src->set; - - /* clear all string pointers first */ - memset(dst->set.str, 0, STRING_LAST * sizeof(char *)); - - /* duplicate all strings */ - for(i=(enum dupstring)0; i< STRING_LASTZEROTERMINATED; i++) { - result = setstropt(&dst->set.str[i], src->set.str[i]); - if(result) - return result; - } - - /* duplicate memory areas pointed to */ - i = STRING_COPYPOSTFIELDS; - if(src->set.postfieldsize && src->set.str[i]) { - /* 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]; - } - - return CURLE_OK; + 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. * - * NOTE: if we ever add something that attempts to write to a socket or - * similar here, we must ignore SIGPIPE first. It is currently only done - * when curl_easy_perform() is invoked. + * We ignore SIGPIPE when this is called from curl_easy_cleanup. */ -CURLcode Curl_close(struct Curl_easy *data) +CURLcode Curl_close(struct Curl_easy **datap) { - struct Curl_multi *m; + struct Curl_easy *data; - if(!data) + if(!datap || !*datap) return CURLE_OK; - Curl_expire_clear(data); /* shut off timers */ - - m = data->multi; + data = *datap; + *datap = NULL; - if(m) - /* 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); + /* 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); - - /* Destroy the timeout list that is held in the easy handle. It is - /normally/ done by curl_multi_remove_handle() but this is "just in - case" */ - if(data->state.timeoutlist) { - Curl_llist_destroy(data->state.timeoutlist, NULL); - data->state.timeoutlist = NULL; + 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; + } } + Curl_expire_clear(data); /* shut off any timers left */ + data->magic = 0; /* force a clear AFTER the possibly enforced removal from the multi handle, since that function uses the magic field! */ @@ -423,50 +260,44 @@ CURLcode Curl_close(struct Curl_easy *data) if(data->state.rangestringalloc) free(data->state.range); - /* Free the pathbuffer */ - Curl_safefree(data->state.pathbuffer); - data->state.path = NULL; - - /* freed here just in case DONE wasn't called */ - Curl_free_request_state(data); + /* freed here just in case DONE was not called */ + Curl_req_free(&data->req, 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->change.referer_alloc) { - Curl_safefree(data->change.referer); - data->change.referer_alloc = FALSE; - } - data->change.referer = NULL; - - if(data->change.url_alloc) { - Curl_safefree(data->change.url); - data->change.url_alloc = FALSE; + if(data->state.referer_alloc) { + Curl_safefree(data->state.referer); + data->state.referer_alloc = FALSE; } - data->change.url = NULL; - - Curl_safefree(data->state.buffer); - Curl_safefree(data->state.headerbuff); - - Curl_flush_cookies(data, 1); - - Curl_digest_cleanup(data); + data->state.referer = NULL; + up_free(data); + Curl_dyn_free(&data->state.headerb); + Curl_flush_cookies(data, TRUE); +#ifndef CURL_DISABLE_ALTSVC + Curl_altsvc_save(data, data->asi, data->set.str[STRING_ALTSVC]); + Curl_altsvc_cleanup(&data->asi); +#endif +#ifndef CURL_DISABLE_HSTS + Curl_hsts_save(data, data->hsts, data->set.str[STRING_HSTS]); + if(!data->share || !data->share->hsts) + Curl_hsts_cleanup(&data->hsts); + curl_slist_free_all(data->state.hstslist); /* clean up list */ +#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); /* this destroys the channel and we cannot use it anymore after this */ - Curl_resolver_cleanup(data->state.resolver); + Curl_resolver_cancel(data); + Curl_resolver_cleanup(data->state.async.resolver); - Curl_http2_cleanup_dependencies(data); - Curl_convert_close(data); + data_priority_cleanup(data); /* No longer a dirty share, if it exists */ if(data->share) { @@ -475,13 +306,38 @@ CURLcode Curl_close(struct Curl_easy *data) Curl_share_unlock(data, CURL_LOCK_DATA_SHARE); } - if(data->set.wildcardmatch) { - /* destruct wildcard structures if it is needed */ - struct WildcardData *wc = &data->wildcard; - Curl_wildcard_dtor(wc); - } +#ifndef CURL_DISABLE_PROXY + Curl_safefree(data->state.aptr.proxyuserpwd); +#endif + Curl_safefree(data->state.aptr.uagent); + Curl_safefree(data->state.aptr.userpwd); + Curl_safefree(data->state.aptr.accept_encoding); + Curl_safefree(data->state.aptr.te); + Curl_safefree(data->state.aptr.rangeline); + Curl_safefree(data->state.aptr.ref); + Curl_safefree(data->state.aptr.host); +#ifndef CURL_DISABLE_COOKIES + Curl_safefree(data->state.aptr.cookiehost); +#endif +#ifndef CURL_DISABLE_RTSP + Curl_safefree(data->state.aptr.rtsp_transport); +#endif + Curl_safefree(data->state.aptr.user); + Curl_safefree(data->state.aptr.passwd); +#ifndef CURL_DISABLE_PROXY + Curl_safefree(data->state.aptr.proxyuser); + Curl_safefree(data->state.aptr.proxypasswd); +#endif + +#if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_FORM_API) + Curl_mime_cleanpart(data->state.formp); + 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; } @@ -490,8 +346,9 @@ CURLcode Curl_close(struct Curl_easy *data) * 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 UserDefined *set) +CURLcode Curl_init_userdefined(struct Curl_easy *data) { + struct UserDefined *set = &data->set; CURLcode result = CURLE_OK; set->out = stdout; /* default output to stdout */ @@ -504,65 +361,60 @@ CURLcode Curl_init_userdefined(struct UserDefined *set) /* use fread as default function to read input */ set->fread_func_set = (curl_read_callback)fread; set->is_fread_set = 0; - set->is_fwrite_set = 0; - set->seek_func = ZERO_NULL; set->seek_client = ZERO_NULL; - /* conversion callbacks for non-ASCII hosts */ - set->convfromnetwork = ZERO_NULL; - set->convtonetwork = ZERO_NULL; - set->convfromutf8 = ZERO_NULL; - - set->filesize = -1; /* we don't know the size */ + set->filesize = -1; /* we do not know the size */ set->postfieldsize = -1; /* unknown size */ - set->maxredirs = -1; /* allow any amount by default */ + set->maxredirs = 30; /* sensible default */ - set->httpreq = HTTPREQ_GET; /* Default HTTP request */ + 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->httpauth = CURLAUTH_BASIC; /* defaults to basic */ 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 */ - /* - * libcurl 7.10 introduced SSL verification *by default*! This needs to be - * switched off unless wanted. - */ - set->ssl.primary.verifypeer = TRUE; - set->ssl.primary.verifyhost = TRUE; -#ifdef USE_TLS_SRP - set->ssl.authtype = CURL_TLSAUTH_NONE; -#endif - set->ssh_auth_types = CURLSSH_AUTH_DEFAULT; /* defaults to any auth - type */ - set->general_ssl.sessionid = TRUE; /* session ID caching enabled by - default */ - set->proxy_ssl = set->ssl; + Curl_mime_initpart(&set->mimepost); - set->new_file_perms = 0644; /* Default permissions */ + 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 - /* for the *protocols fields we don't use the CURLPROTO_ALL convenience - define since we internally only use the lower 16 bits for the passed - in bitmask to not conflict with the private bits */ - set->allowed_protocols = CURLPROTO_ALL; - set->redir_protocols = CURLPROTO_ALL & /* All except FILE, SCP and SMB */ - ~(CURLPROTO_FILE | CURLPROTO_SCP | CURLPROTO_SMB | - CURLPROTO_SMBS); + set->new_file_perms = 0644; /* Default permissions */ + set->allowed_protocols = (curl_prot_t) CURLPROTO_ALL; + set->redir_protocols = CURLPROTO_REDIR; #if defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI) /* @@ -572,46 +424,71 @@ CURLcode Curl_init_userdefined(struct UserDefined *set) set->socks5_gssapi_nec = FALSE; #endif - /* This is our preferred CA cert bundle/path since install time */ + /* Set the default CA cert bundle/path detected/specified at build time. + * + * If Schannel or Secure Transport is the selected SSL backend then these + * locations are ignored. We allow setting CA location for Schannel and + * Secure Transport when explicitly specified by the user via + * CURLOPT_CAINFO / --cacert. + */ + if(Curl_ssl_backend() != CURLSSLBACKEND_SCHANNEL && + Curl_ssl_backend() != CURLSSLBACKEND_SECURETRANSPORT) { #if defined(CURL_CA_BUNDLE) - result = setstropt(&set->str[STRING_SSL_CAFILE_ORIG], CURL_CA_BUNDLE); - if(result) - return result; - - result = setstropt(&set->str[STRING_SSL_CAFILE_PROXY], CURL_CA_BUNDLE); - if(result) - return result; + result = Curl_setstropt(&set->str[STRING_SSL_CAFILE], CURL_CA_BUNDLE); + if(result) + return result; +#ifndef CURL_DISABLE_PROXY + result = Curl_setstropt(&set->str[STRING_SSL_CAFILE_PROXY], + CURL_CA_BUNDLE); + if(result) + return result; +#endif #endif #if defined(CURL_CA_PATH) - result = setstropt(&set->str[STRING_SSL_CAPATH_ORIG], CURL_CA_PATH); - if(result) - return result; - - result = setstropt(&set->str[STRING_SSL_CAPATH_PROXY], CURL_CA_PATH); - if(result) - return result; + result = Curl_setstropt(&set->str[STRING_SSL_CAPATH], CURL_CA_PATH); + if(result) + return result; +#ifndef CURL_DISABLE_PROXY + result = Curl_setstropt(&set->str[STRING_SSL_CAPATH_PROXY], CURL_CA_PATH); + if(result) + return result; +#endif #endif + } - set->wildcardmatch = FALSE; +#ifndef CURL_DISABLE_FTP + set->wildcard_enabled = FALSE; set->chunk_bgn = ZERO_NULL; set->chunk_end = ZERO_NULL; - - /* tcp keepalives are disabled by default, but provide reasonable values for - * the interval and idle times. - */ + set->fnmatch = ZERO_NULL; +#endif set->tcp_keepalive = FALSE; set->tcp_keepintvl = 60; set->tcp_keepidle = 60; + set->tcp_keepcnt = 9; set->tcp_fastopen = FALSE; set->tcp_nodelay = TRUE; - - set->ssl_enable_npn = 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 */ - - Curl_http2_init_userset(set); + 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; } @@ -638,3471 +515,794 @@ CURLcode Curl_open(struct Curl_easy **curl) data->magic = CURLEASY_MAGIC_NUMBER; - result = Curl_resolver_init(&data->state.resolver); + Curl_req_init(&data->req); + + result = Curl_resolver_init(data, &data->state.async.resolver); if(result) { DEBUGF(fprintf(stderr, "Error: resolver_init failed\n")); + Curl_req_free(&data->req, data); free(data); return result; } - /* We do some initial setup here, all those fields that can't be just 0 */ - - data->state.buffer = malloc(BUFSIZE + 1); - if(!data->state.buffer) { - DEBUGF(fprintf(stderr, "Error: malloc of buffer failed\n")); - result = CURLE_OUT_OF_MEMORY; - } - - data->state.headerbuff = malloc(HEADERSIZE); - if(!data->state.headerbuff) { - DEBUGF(fprintf(stderr, "Error: malloc of headerbuff failed\n")); - result = CURLE_OUT_OF_MEMORY; - } - else { - result = Curl_init_userdefined(&data->set); - - data->state.headersize=HEADERSIZE; - - Curl_convert_init(data); - + 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 = NULL; + data->state.lastconnect_id = -1; + data->state.recent_conn_id = -1; + /* and not assigned an id yet */ + data->id = -1; + data->mid = -1; +#ifndef CURL_DISABLE_DOH + data->set.dohfor_mid = -1; +#endif data->progress.flags |= PGRS_HIDE; data->state.current_speed = -1; /* init to negative == impossible */ - - data->wildcard.state = CURLWC_INIT; - data->wildcard.filelist = NULL; - data->set.fnmatch = ZERO_NULL; - data->set.maxconnects = DEFAULT_CONNCACHE_SIZE; /* for easy handles */ - - Curl_http2_init_state(&data->state); +#ifndef CURL_DISABLE_HTTP + Curl_llist_init(&data->state.httphdrs, NULL); +#endif } if(result) { - Curl_resolver_cleanup(data->state.resolver); - free(data->state.buffer); - free(data->state.headerbuff); + Curl_resolver_cleanup(data->state.async.resolver); + Curl_dyn_free(&data->state.headerb); Curl_freeset(data); + Curl_req_free(&data->req, data); free(data); data = NULL; } else *curl = data; - return result; } -CURLcode Curl_setopt(struct Curl_easy *data, CURLoption option, - va_list param) +void Curl_conn_free(struct Curl_easy *data, struct connectdata *conn) { - char *argptr; - CURLcode result = CURLE_OK; - long arg; -#ifndef CURL_DISABLE_HTTP - curl_off_t bigsize; -#endif - - switch(option) { - case CURLOPT_DNS_CACHE_TIMEOUT: - data->set.dns_cache_timeout = va_arg(param, long); - break; - case CURLOPT_DNS_USE_GLOBAL_CACHE: - /* remember we want this enabled */ - arg = va_arg(param, long); - data->set.global_dns_cache = (0 != arg) ? TRUE : FALSE; - break; - case CURLOPT_SSL_CIPHER_LIST: - /* set a list of cipher we want to use in the SSL connection */ - result = setstropt(&data->set.str[STRING_SSL_CIPHER_LIST_ORIG], - va_arg(param, char *)); - break; - case CURLOPT_PROXY_SSL_CIPHER_LIST: - /* set a list of cipher we want to use in the SSL connection for proxy */ - result = setstropt(&data->set.str[STRING_SSL_CIPHER_LIST_PROXY], - va_arg(param, char *)); - break; - - case CURLOPT_RANDOM_FILE: - /* - * This is the path name to a file that contains random data to seed - * the random SSL stuff with. The file is only used for reading. - */ - result = setstropt(&data->set.str[STRING_SSL_RANDOM_FILE], - va_arg(param, char *)); - break; - case CURLOPT_EGDSOCKET: - /* - * The Entropy Gathering Daemon socket pathname - */ - result = setstropt(&data->set.str[STRING_SSL_EGDSOCKET], - va_arg(param, char *)); - break; - case CURLOPT_MAXCONNECTS: - /* - * Set the absolute number of maximum simultaneous alive connection that - * libcurl is allowed to have. - */ - data->set.maxconnects = va_arg(param, long); - 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)) ? TRUE : FALSE; - 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)) ? TRUE : FALSE; - 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)) ? TRUE : FALSE; - break; - case CURLOPT_HEADER: - /* - * Set to include the header in the general data output stream. - */ - data->set.include_header = (0 != va_arg(param, long)) ? TRUE : FALSE; - break; - case CURLOPT_NOPROGRESS: - /* - * Shut off the internal supported progress meter - */ - data->set.hide_progress = (0 != va_arg(param, long)) ? TRUE : FALSE; - 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)) ? TRUE : FALSE; - 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)) ? TRUE : FALSE; - break; - case CURLOPT_KEEP_SENDING_ON_ERROR: - data->set.http_keep_sending_on_error = (0 != va_arg(param, long)) ? - TRUE : FALSE; - 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. - */ - data->set.upload = (0 != va_arg(param, long)) ? TRUE : FALSE; - if(data->set.upload) { - /* If this is HTTP, PUT is what's needed to "upload" */ - data->set.httpreq = 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.httpreq = HTTPREQ_GET; - 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)) ? TRUE : FALSE; - break; - case CURLOPT_FTP_CREATE_MISSING_DIRS: - /* - * An FTP option that modifies an upload to create missing directories on - * the server. - */ - switch(va_arg(param, long)) { - case 0: - data->set.ftp_create_missing_dirs = 0; - break; - case 1: - data->set.ftp_create_missing_dirs = 1; - break; - case 2: - data->set.ftp_create_missing_dirs = 2; - break; - default: - /* reserve other values for future use */ - result = CURLE_UNKNOWN_OPTION; - break; - } - break; - case CURLOPT_SERVER_RESPONSE_TIMEOUT: - /* - * Option that specifies how quickly an server response must be obtained - * before it is considered failure. For pingpong protocols. - */ - data->set.server_response_timeout = va_arg(param, long) * 1000; - break; - 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. - */ - data->set.tftp_blksize = va_arg(param, long); - break; - case CURLOPT_DIRLISTONLY: - /* - * An option that changes the command to one that asks for a list - * only, no file info details. - */ - data->set.ftp_list_only = (0 != va_arg(param, long)) ? TRUE : FALSE; - break; - case CURLOPT_APPEND: - /* - * We want to upload and append to an existing file. - */ - data->set.ftp_append = (0 != va_arg(param, long)) ? TRUE : FALSE; - break; - case CURLOPT_FTP_FILEMETHOD: - /* - * How do access files over FTP. - */ - data->set.ftp_filemethod = (curl_ftpfile)va_arg(param, long); - break; - case CURLOPT_NETRC: - /* - * Parse the $HOME/.netrc file - */ - data->set.use_netrc = (enum CURL_NETRC_OPTION)va_arg(param, long); - break; - case CURLOPT_NETRC_FILE: - /* - * Use this file instead of the $HOME/.netrc file - */ - result = setstropt(&data->set.str[STRING_NETRC_FILE], - va_arg(param, char *)); - break; - 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)) ? TRUE : FALSE; - break; - case CURLOPT_TIMECONDITION: - /* - * Set HTTP time condition. This must be one of the defines in the - * curl/curl.h header file. - */ - data->set.timecondition = (curl_TimeCond)va_arg(param, long); - 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_SSLVERSION: - /* - * Set explicit SSL version to try to connect with, as some SSL - * implementations are lame. - */ -#ifdef USE_SSL - data->set.ssl.primary.version = va_arg(param, long); -#else - result = CURLE_UNKNOWN_OPTION; -#endif - break; - case CURLOPT_PROXY_SSLVERSION: - /* - * Set explicit SSL version to try to connect with for proxy, as some SSL - * implementations are lame. - */ -#ifdef USE_SSL - data->set.proxy_ssl.primary.version = va_arg(param, long); -#else - result = CURLE_UNKNOWN_OPTION; -#endif - break; - -#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)) ? TRUE : FALSE; - 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 *); - result = setstropt(&data->set.str[STRING_ENCODING], - (argptr && !*argptr)? - ALL_CONTENT_ENCODINGS: argptr); - break; - - case CURLOPT_TRANSFER_ENCODING: - data->set.http_transfer_encoding = (0 != va_arg(param, long)) ? - TRUE : FALSE; - break; - - case CURLOPT_FOLLOWLOCATION: - /* - * Follow Location: header hints on a HTTP-server. - */ - data->set.http_follow_location = (0 != va_arg(param, long)) ? TRUE : FALSE; - break; - - case CURLOPT_UNRESTRICTED_AUTH: - /* - * Send authentication (user+password) when following locations, even when - * hostname changed. - */ - data->set.http_disable_hostname_check_before_authentication = - (0 != va_arg(param, long)) ? TRUE : FALSE; - 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. - */ - data->set.maxredirs = va_arg(param, long); - break; - - case CURLOPT_POSTREDIR: - { - /* - * Set the behaviour 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 - */ - int postRedir = curlx_sltosi(va_arg(param, long)); - data->set.keep_post = postRedir & 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.httpreq = HTTPREQ_POST; - data->set.opt_no_body = FALSE; /* this is implied */ - } - else - data->set.httpreq = HTTPREQ_GET; - break; - - 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 = 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) 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.httpreq = 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) setstropt(&data->set.str[STRING_COPYPOSTFIELDS], NULL); - data->set.httpreq = 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(data->set.postfieldsize < bigsize && - data->set.postfields == data->set.str[STRING_COPYPOSTFIELDS]) { - /* Previous CURLOPT_COPYPOSTFIELDS is no longer valid. */ - (void) 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(data->set.postfieldsize < bigsize && - data->set.postfields == data->set.str[STRING_COPYPOSTFIELDS]) { - /* Previous CURLOPT_COPYPOSTFIELDS is no longer valid. */ - (void) setstropt(&data->set.str[STRING_COPYPOSTFIELDS], NULL); - data->set.postfields = NULL; - } - - data->set.postfieldsize = bigsize; - break; - - case CURLOPT_HTTPPOST: - /* - * Set to make us do HTTP POST - */ - data->set.httppost = va_arg(param, struct curl_httppost *); - data->set.httpreq = HTTPREQ_POST_FORM; - data->set.opt_no_body = FALSE; /* this is implied */ - break; - - case CURLOPT_REFERER: - /* - * String to set in the HTTP Referer: field. - */ - if(data->change.referer_alloc) { - Curl_safefree(data->change.referer); - data->change.referer_alloc = FALSE; - } - result = setstropt(&data->set.str[STRING_SET_REFERER], - va_arg(param, char *)); - data->change.referer = data->set.str[STRING_SET_REFERER]; - break; - - case CURLOPT_USERAGENT: - /* - * String to use in the HTTP User-Agent field - */ - result = setstropt(&data->set.str[STRING_USERAGENT], - va_arg(param, char *)); - break; - - 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; - - 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; - - case CURLOPT_HEADEROPT: - /* - * Set header option. - */ - arg = va_arg(param, long); - data->set.sep_headers = (arg & CURLHEADER_SEPARATE)? TRUE: FALSE; - 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; - -#if !defined(CURL_DISABLE_COOKIES) - case CURLOPT_COOKIE: - /* - * Cookie string to send to the remote server in the request. - */ - result = 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; - /* append the cookie file name to the list of file names, and deal with - them later */ - cl = curl_slist_append(data->change.cookielist, argptr); - if(!cl) { - curl_slist_free_all(data->change.cookielist); - data->change.cookielist = NULL; - return CURLE_OUT_OF_MEMORY; - } - data->change.cookielist = cl; /* store the list for later use */ - } - break; - - case CURLOPT_COOKIEJAR: - /* - * Set cookie file name to dump all cookies to when we're done. - */ - { - struct CookieInfo *newcookies; - result = 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. - * - * In the original Netscape cookie spec, "session cookies" are cookies - * with no expire date set. RFC2109 describes the same action if no - * 'Max-Age' is set and RFC2965 includes the RFC2109 description and adds - * a 'Discard' action that can enforce the discard even for cookies that - * have a Max-Age. - * - * We run mostly with the original cookie spec, as hardly anyone implements - * anything else. - */ - data->set.cookiesession = (0 != va_arg(param, long)) ? TRUE : FALSE; - break; - - case CURLOPT_COOKIELIST: - argptr = va_arg(param, char *); - - if(argptr == NULL) - 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, 0); - } - 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); - - 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, argptr + 11, NULL, NULL); - - else - /* Netscape format line */ - Curl_cookie_add(data, data->cookies, FALSE, argptr, NULL, NULL); - - 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.httpreq = HTTPREQ_GET; - data->set.upload = FALSE; /* switch off upload */ - 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); -#ifndef USE_NGHTTP2 - if(arg >= CURL_HTTP_VERSION_2) - return CURLE_UNSUPPORTED_PROTOCOL; -#endif - data->set.httpversion = arg; - break; - - 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) ? TRUE : FALSE; - - 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_EXPECT_100_TIMEOUT_MS: - /* - * Time to wait for a response to a HTTP request containing an - * Expect: 100-continue header before sending the data anyway. - */ - data->set.expect_100_timeout = va_arg(param, long); - break; - -#endif /* CURL_DISABLE_HTTP */ - - case CURLOPT_CUSTOMREQUEST: - /* - * Set a custom string to use as request - */ - result = setstropt(&data->set.str[STRING_CUSTOMREQUEST], - va_arg(param, char *)); - - /* we don't set - data->set.httpreq = 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)) ? - TRUE : FALSE; - break; - - case CURLOPT_PROXYPORT: - /* - * Explicitly set HTTP proxy port number. - */ - data->set.proxyport = va_arg(param, long); - 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) ? TRUE : FALSE; - - 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 = 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 = setstropt(&data->set.str[STRING_PRE_PROXY], - va_arg(param, char *)); - break; - - case CURLOPT_PROXYTYPE: - /* - * Set proxy type. HTTP/HTTP_1_0/SOCKS4/SOCKS4a/SOCKS5/SOCKS5_HOSTNAME - */ - data->set.proxytype = (curl_proxytype)va_arg(param, long); - break; - - case CURLOPT_PROXY_TRANSFER_MODE: - /* - * set transfer mode (;type=) 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_UNKNOWN_OPTION; - break; - } - 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)) ? TRUE : FALSE; - break; - - case CURLOPT_SOCKS5_GSSAPI_SERVICE: - case CURLOPT_PROXY_SERVICE_NAME: - /* - * Set proxy authentication service name for Kerberos 5 and SPNEGO - */ - result = setstropt(&data->set.str[STRING_PROXY_SERVICE_NAME], - va_arg(param, char *)); - break; -#endif - -#if !defined(CURL_DISABLE_CRYPTO_AUTH) || defined(USE_KERBEROS5) || \ - defined(USE_SPNEGO) - case CURLOPT_SERVICE_NAME: - /* - * Set authentication service name for DIGEST-MD5, Kerberos 5 and SPNEGO - */ - result = setstropt(&data->set.str[STRING_SERVICE_NAME], - va_arg(param, char *)); - break; - -#endif - - 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; - case CURLOPT_FTPPORT: - /* - * Use FTP PORT, this also specifies which IP address to use - */ - result = setstropt(&data->set.str[STRING_FTPPORT], - va_arg(param, char *)); - data->set.ftp_use_port = (data->set.str[STRING_FTPPORT]) ? TRUE : FALSE; - break; - - case CURLOPT_FTP_USE_EPRT: - data->set.ftp_use_eprt = (0 != va_arg(param, long)) ? TRUE : FALSE; - break; - - case CURLOPT_FTP_USE_EPSV: - data->set.ftp_use_epsv = (0 != va_arg(param, long)) ? TRUE : FALSE; - break; - - case CURLOPT_FTP_USE_PRET: - data->set.ftp_use_pret = (0 != va_arg(param, long)) ? TRUE : FALSE; - break; - - case CURLOPT_FTP_SSL_CCC: - data->set.ftp_ccc = (curl_ftpccc)va_arg(param, long); - 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)) ? TRUE : FALSE; - break; - - 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. - */ - data->set.filesize = va_arg(param, long); - break; - case CURLOPT_INFILESIZE_LARGE: - /* - * If known, this should inform curl about the file size of the - * to-be-uploaded file. - */ - data->set.filesize = va_arg(param, curl_off_t); - 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. - */ - data->set.low_speed_limit=va_arg(param, long); - 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.. - */ - data->set.max_send_speed=va_arg(param, curl_off_t); - break; - case CURLOPT_MAX_RECV_SPEED_LARGE: - /* - * When receiving data faster than CURLOPT_MAX_RECV_SPEED_LARGE bytes per - * second the transfer is throttled.. - */ - data->set.max_recv_speed=va_arg(param, curl_off_t); - 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. - */ - data->set.low_speed_time=va_arg(param, long); - break; - case CURLOPT_URL: - /* - * The URL to fetch. - */ - if(data->change.url_alloc) { - /* the already set URL is allocated, free it first! */ - Curl_safefree(data->change.url); - data->change.url_alloc = FALSE; - } - result = setstropt(&data->set.str[STRING_SET_URL], - va_arg(param, char *)); - data->change.url = data->set.str[STRING_SET_URL]; - break; - case CURLOPT_PORT: - /* - * The port number to use when getting the URL - */ - data->set.use_port = va_arg(param, long); - break; - case CURLOPT_TIMEOUT: - /* - * The maximum time you allow curl to use for a single transfer - * operation. - */ - data->set.timeout = va_arg(param, long) * 1000L; - break; - - case CURLOPT_TIMEOUT_MS: - data->set.timeout = va_arg(param, long); - break; - - case CURLOPT_CONNECTTIMEOUT: - /* - * The maximum time you allow curl to use to connect. - */ - data->set.connecttimeout = va_arg(param, long) * 1000L; - break; - - case CURLOPT_CONNECTTIMEOUT_MS: - data->set.connecttimeout = va_arg(param, long); - break; - - case CURLOPT_ACCEPTTIMEOUT_MS: - /* - * The maximum time you allow curl to wait for server connect - */ - data->set.accepttimeout = va_arg(param, long); - break; - - 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 = setstropt(&data->set.str[STRING_USERNAME], - va_arg(param, char *)); - break; - - case CURLOPT_PASSWORD: - /* - * authentication password to use in the operation - */ - result = setstropt(&data->set.str[STRING_PASSWORD], - va_arg(param, char *)); - break; - - case CURLOPT_LOGIN_OPTIONS: - /* - * authentication options to use in the operation - */ - result = 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 = setstropt(&data->set.str[STRING_BEARER], - va_arg(param, char *)); - 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; - case CURLOPT_RESOLVE: - /* - * List of NAME:[address] names to populate the DNS cache with - * Prefix the NAME with dash (-) to _remove_ the name from the cache. - * - * Names added with this API will remain in the cache until explicitly - * removed or the handle is cleaned up. - * - * This API can remove any name 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->change.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 = setstropt(&data->set.str[STRING_PROXYUSERNAME], - va_arg(param, char *)); - break; - case CURLOPT_PROXYPASSWORD: - /* - * authentication password to use in the operation - */ - result = setstropt(&data->set.str[STRING_PROXYPASSWORD], - va_arg(param, char *)); - break; - case CURLOPT_NOPROXY: - /* - * proxy exception list - */ - result = 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 = setstropt(&data->set.str[STRING_SET_RANGE], - va_arg(param, char *)); - break; - case CURLOPT_RESUME_FROM: - /* - * Resume transfer at the give file position - */ - data->set.set_resume_from = va_arg(param, long); - break; - case CURLOPT_RESUME_FROM_LARGE: - /* - * Resume transfer at the give file position - */ - data->set.set_resume_from = va_arg(param, curl_off_t); - 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) { - data->set.is_fwrite_set = 0; - /* When set to NULL, reset to our internal default function */ - data->set.fwrite_func = (curl_write_callback)fwrite; - } - else - data->set.is_fwrite_set = 1; - 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_CONV_FROM_NETWORK_FUNCTION: - /* - * "Convert from network encoding" callback - */ - data->set.convfromnetwork = va_arg(param, curl_conv_callback); - break; - case CURLOPT_CONV_TO_NETWORK_FUNCTION: - /* - * "Convert to network encoding" callback - */ - data->set.convtonetwork = va_arg(param, curl_conv_callback); - break; - case CURLOPT_CONV_FROM_UTF8_FUNCTION: - /* - * "Convert from UTF-8 encoding" callback - */ - data->set.convfromutf8 = va_arg(param, curl_conv_callback); - 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 = setstropt(&data->set.str[STRING_CERT_ORIG], - va_arg(param, char *)); - break; - case CURLOPT_PROXY_SSLCERT: - /* - * String that holds file name of the SSL certificate to use for proxy - */ - result = setstropt(&data->set.str[STRING_CERT_PROXY], - va_arg(param, char *)); - break; - case CURLOPT_SSLCERTTYPE: - /* - * String that holds file type of the SSL certificate to use - */ - result = setstropt(&data->set.str[STRING_CERT_TYPE_ORIG], - va_arg(param, char *)); - break; - case CURLOPT_PROXY_SSLCERTTYPE: - /* - * String that holds file type of the SSL certificate to use for proxy - */ - result = setstropt(&data->set.str[STRING_CERT_TYPE_PROXY], - va_arg(param, char *)); - break; - case CURLOPT_SSLKEY: - /* - * String that holds file name of the SSL key to use - */ - result = setstropt(&data->set.str[STRING_KEY_ORIG], - va_arg(param, char *)); - break; - case CURLOPT_PROXY_SSLKEY: - /* - * String that holds file name of the SSL key to use for proxy - */ - result = setstropt(&data->set.str[STRING_KEY_PROXY], - va_arg(param, char *)); - break; - case CURLOPT_SSLKEYTYPE: - /* - * String that holds file type of the SSL key to use - */ - result = setstropt(&data->set.str[STRING_KEY_TYPE_ORIG], - va_arg(param, char *)); - break; - case CURLOPT_PROXY_SSLKEYTYPE: - /* - * String that holds file type of the SSL key to use for proxy - */ - result = setstropt(&data->set.str[STRING_KEY_TYPE_PROXY], - va_arg(param, char *)); - break; - case CURLOPT_KEYPASSWD: - /* - * String that holds the SSL or SSH private key password. - */ - result = setstropt(&data->set.str[STRING_KEY_PASSWD_ORIG], - va_arg(param, char *)); - break; - case CURLOPT_PROXY_KEYPASSWD: - /* - * String that holds the SSL private key password for proxy. - */ - result = setstropt(&data->set.str[STRING_KEY_PASSWD_PROXY], - va_arg(param, char *)); - break; - case CURLOPT_SSLENGINE: - /* - * String that holds the SSL crypto engine. - */ - argptr = va_arg(param, char *); - if(argptr && argptr[0]) - result = Curl_ssl_set_engine(data, argptr); - break; - - case CURLOPT_SSLENGINE_DEFAULT: - /* - * flag to set engine as default. - */ - 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)) ? TRUE : FALSE; - break; - - 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 = setstropt(&data->set.str[STRING_DEVICE], - va_arg(param, char *)); - break; - case CURLOPT_LOCALPORT: - /* - * Set what local port to bind the socket to when performing an operation. - */ - data->set.localport = curlx_sltous(va_arg(param, long)); - break; - case CURLOPT_LOCALPORTRANGE: - /* - * Set number of local ports to try, starting with CURLOPT_LOCALPORT. - */ - data->set.localportrange = curlx_sltosi(va_arg(param, long)); - break; - case CURLOPT_KRBLEVEL: - /* - * A string that defines the kerberos security level. - */ - result = setstropt(&data->set.str[STRING_KRB_LEVEL], - va_arg(param, char *)); - data->set.krb = (data->set.str[STRING_KRB_LEVEL]) ? TRUE : FALSE; - break; - case CURLOPT_GSSAPI_DELEGATION: - /* - * GSS-API credential delegation - */ - data->set.gssapi_delegation = va_arg(param, long); - break; - case CURLOPT_SSL_VERIFYPEER: - /* - * Enable peer SSL verifying. - */ - data->set.ssl.primary.verifypeer = (0 != va_arg(param, long)) ? - TRUE : FALSE; - break; - case CURLOPT_PROXY_SSL_VERIFYPEER: - /* - * Enable peer SSL verifying for proxy. - */ - data->set.proxy_ssl.primary.verifypeer = - (0 != va_arg(param, long))?TRUE:FALSE; - break; - 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. We thus ban - 1 as a sensible input and we warn about its use. Then we only have the - 2 action internally stored as TRUE. */ - - if(1 == arg) { - failf(data, "CURLOPT_SSL_VERIFYHOST no longer supports 1 as value!"); - return CURLE_BAD_FUNCTION_ARGUMENT; - } - - data->set.ssl.primary.verifyhost = (0 != arg) ? TRUE : FALSE; - break; - case CURLOPT_PROXY_SSL_VERIFYHOST: - /* - * Enable verification of the host name in the peer certificate for proxy - */ - 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. We thus ban - 1 as a sensible input and we warn about its use. Then we only have the - 2 action internally stored as TRUE. */ - - if(1 == arg) { - failf(data, "CURLOPT_SSL_VERIFYHOST no longer supports 1 as value!"); - return CURLE_BAD_FUNCTION_ARGUMENT; - } - - data->set.proxy_ssl.primary.verifyhost = (0 != arg)?TRUE:FALSE; - break; - 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)) ? - TRUE : FALSE; - break; - case CURLOPT_SSL_CTX_FUNCTION: -#ifdef have_curlssl_ssl_ctx - /* - * Set a SSL_CTX callback - */ - data->set.ssl.fsslctx = va_arg(param, curl_ssl_ctx_callback); -#else - result = CURLE_NOT_BUILT_IN; -#endif - break; - case CURLOPT_SSL_CTX_DATA: -#ifdef have_curlssl_ssl_ctx - /* - * Set a SSL_CTX callback parameter pointer - */ - data->set.ssl.fsslctxp = va_arg(param, void *); -#else - result = CURLE_NOT_BUILT_IN; -#endif - break; - case CURLOPT_SSL_FALSESTART: - /* - * Enable TLS false start. - */ - if(!Curl_ssl_false_start()) { - result = CURLE_NOT_BUILT_IN; - break; - } - - data->set.ssl.falsestart = (0 != va_arg(param, long)) ? TRUE : FALSE; - break; - case CURLOPT_CERTINFO: -#ifdef have_curlssl_certinfo - data->set.ssl.certinfo = (0 != va_arg(param, long)) ? TRUE : FALSE; -#else - result = CURLE_NOT_BUILT_IN; -#endif - break; - case CURLOPT_PINNEDPUBLICKEY: -#ifdef have_curlssl_pinnedpubkey /* only by supported backends */ - /* - * Set pinned public key for SSL connection. - * Specify file name of the public key in DER format. - */ - result = setstropt(&data->set.str[STRING_SSL_PINNEDPUBLICKEY_ORIG], - va_arg(param, char *)); -#else - result = CURLE_NOT_BUILT_IN; -#endif - break; - case CURLOPT_PROXY_PINNEDPUBLICKEY: -#ifdef have_curlssl_pinnedpubkey /* only by supported backends */ - /* - * Set pinned public key for SSL connection. - * Specify file name of the public key in DER format. - */ - result = setstropt(&data->set.str[STRING_SSL_PINNEDPUBLICKEY_PROXY], - va_arg(param, char *)); -#else - result = CURLE_NOT_BUILT_IN; -#endif - break; - case CURLOPT_CAINFO: - /* - * Set CA info for SSL connection. Specify file name of the CA certificate - */ - result = setstropt(&data->set.str[STRING_SSL_CAFILE_ORIG], - va_arg(param, char *)); - break; - case CURLOPT_PROXY_CAINFO: - /* - * Set CA info SSL connection for proxy. Specify file name of the - * CA certificate - */ - result = setstropt(&data->set.str[STRING_SSL_CAFILE_PROXY], - va_arg(param, char *)); - break; - case CURLOPT_CAPATH: -#ifdef have_curlssl_ca_path /* not supported by all backends */ - /* - * Set CA path info for SSL connection. Specify directory name of the CA - * certificates which have been prepared using openssl c_rehash utility. - */ - /* This does not work on windows. */ - result = setstropt(&data->set.str[STRING_SSL_CAPATH_ORIG], - va_arg(param, char *)); -#else - result = CURLE_NOT_BUILT_IN; -#endif - break; - case CURLOPT_PROXY_CAPATH: -#ifdef have_curlssl_ca_path /* not supported by all backends */ - /* - * Set CA path info for SSL connection proxy. Specify directory name of the - * CA certificates which have been prepared using openssl c_rehash utility. - */ - /* This does not work on windows. */ - result = setstropt(&data->set.str[STRING_SSL_CAPATH_PROXY], - va_arg(param, char *)); -#else - result = CURLE_NOT_BUILT_IN; -#endif - break; - case CURLOPT_CRLFILE: - /* - * Set CRL file info for SSL connection. Specify file name of the CRL - * to check certificates revocation - */ - result = setstropt(&data->set.str[STRING_SSL_CRLFILE_ORIG], - va_arg(param, char *)); - break; - case CURLOPT_PROXY_CRLFILE: - /* - * Set CRL file info for SSL connection for proxy. Specify file name of the - * CRL to check certificates revocation - */ - result = setstropt(&data->set.str[STRING_SSL_CRLFILE_PROXY], - va_arg(param, char *)); - break; - case CURLOPT_ISSUERCERT: - /* - * Set Issuer certificate file - * to check certificates issuer - */ - result = setstropt(&data->set.str[STRING_SSL_ISSUERCERT_ORIG], - va_arg(param, char *)); - break; - case CURLOPT_TELNETOPTIONS: - /* - * Set a linked list of telnet options - */ - data->set.telnet_options = va_arg(param, struct curl_slist *); - break; - - case CURLOPT_BUFFERSIZE: - /* - * The application kindly asks for a differently sized receive buffer. - * If it seems reasonable, we'll use it. - */ - data->set.buffer_size = va_arg(param, long); - - if(data->set.buffer_size > MAX_BUFSIZE) - data->set.buffer_size = MAX_BUFSIZE; /* huge internal default */ - else if(data->set.buffer_size < 1) - data->set.buffer_size = BUFSIZE; - - /* Resize only if larger than default buffer size. */ - if(data->set.buffer_size > BUFSIZE) { - data->state.buffer = realloc(data->state.buffer, - data->set.buffer_size + 1); - if(!data->state.buffer) { - DEBUGF(fprintf(stderr, "Error: realloc of buffer failed\n")); - result = CURLE_OUT_OF_MEMORY; - } - } - - 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)) ? TRUE : FALSE; - 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 - - if(data->share->sslsession == data->state.session) - data->state.session = NULL; - - data->share->dirty--; - - Curl_share_unlock(data, CURL_LOCK_DATA_SHARE); - data->share = NULL; - } - - /* 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 */ - if(data->share->sslsession) { - data->set.general_ssl.max_ssl_sessions = data->share->max_ssl_sessions; - data->state.session = data->share->sslsession; - } - 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. - */ - data->set.max_filesize = va_arg(param, long); - break; - -#ifdef USE_SSL - case CURLOPT_USE_SSL: - /* - * Make transfers attempt to use SSL/TLS. - */ - data->set.use_ssl = (curl_usessl)va_arg(param, long); - break; - - case CURLOPT_SSL_OPTIONS: - arg = va_arg(param, long); - data->set.ssl.enable_beast = arg&CURLSSLOPT_ALLOW_BEAST?TRUE:FALSE; - data->set.ssl.no_revoke = !!(arg & CURLSSLOPT_NO_REVOKE); - break; - - case CURLOPT_PROXY_SSL_OPTIONS: - arg = va_arg(param, long); - data->set.proxy_ssl.enable_beast = arg&CURLSSLOPT_ALLOW_BEAST?TRUE:FALSE; - data->set.proxy_ssl.no_revoke = !!(arg & CURLSSLOPT_NO_REVOKE); - break; - -#endif - case CURLOPT_FTPSSLAUTH: - /* - * Set a specific auth for FTP-SSL transfers. - */ - data->set.ftpsslauth = (curl_ftpauth)va_arg(param, long); - break; - - case CURLOPT_IPRESOLVE: - data->set.ipver = va_arg(param, long); - break; - - case CURLOPT_MAXFILESIZE_LARGE: - /* - * Set the maximum size of a file to download. - */ - data->set.max_filesize = va_arg(param, curl_off_t); - 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)) ? TRUE : FALSE; - break; - - case CURLOPT_FTP_ACCOUNT: - result = setstropt(&data->set.str[STRING_FTP_ACCOUNT], - va_arg(param, char *)); - break; - - case CURLOPT_IGNORE_CONTENT_LENGTH: - data->set.ignorecl = (0 != va_arg(param, long)) ? TRUE : FALSE; - break; - - case CURLOPT_CONNECT_ONLY: - /* - * No data transfer, set up connection and let application use the socket - */ - data->set.connect_only = (0 != va_arg(param, long)) ? TRUE : FALSE; - break; - - case CURLOPT_FTP_ALTERNATIVE_TO_USER: - result = setstropt(&data->set.str[STRING_FTP_ALTERNATIVE_TO_USER], - va_arg(param, char *)); - 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_CLOSESOCKETDATA: - /* - * socket callback data pointer. Might be NULL. - */ - data->set.closesocket_client = va_arg(param, void *); - break; - - case CURLOPT_SSL_SESSIONID_CACHE: - data->set.general_ssl.sessionid = (0 != va_arg(param, long)) ? - TRUE : FALSE; - break; - -#ifdef USE_LIBSSH2 - /* we only include SSH options if explicitly built to support SSH */ - case CURLOPT_SSH_AUTH_TYPES: - data->set.ssh_auth_types = va_arg(param, long); - break; - - case CURLOPT_SSH_PUBLIC_KEYFILE: - /* - * Use this file instead of the $HOME/.ssh/id_dsa.pub file - */ - result = 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 = 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 = setstropt(&data->set.str[STRING_SSH_HOST_PUBLIC_KEY_MD5], - va_arg(param, char *)); - break; -#ifdef HAVE_LIBSSH2_KNOWNHOST_API - case CURLOPT_SSH_KNOWNHOSTS: - /* - * Store the file name to read known hosts from. - */ - result = setstropt(&data->set.str[STRING_SSH_KNOWNHOSTS], - va_arg(param, char *)); - break; - - case CURLOPT_SSH_KEYFUNCTION: - /* setting to NULL is fine since the ssh.c functions themselves will - then rever 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; -#endif /* HAVE_LIBSSH2_KNOWNHOST_API */ - -#endif /* USE_LIBSSH2 */ - - case CURLOPT_HTTP_TRANSFER_DECODING: - /* - * disable libcurl transfer encoding is used - */ - data->set.http_te_skip = (0 == va_arg(param, long)) ? TRUE : FALSE; - break; - - 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)) ? TRUE : FALSE; - break; - - case CURLOPT_NEW_FILE_PERMS: - /* - * Uses these permissions instead of 0644 - */ - data->set.new_file_perms = va_arg(param, long); - break; - - case CURLOPT_NEW_DIRECTORY_PERMS: - /* - * Uses these permissions instead of 0755 - */ - data->set.new_directory_perms = va_arg(param, long); - break; - - case CURLOPT_ADDRESS_SCOPE: - /* - * We always get longs when passed plain numericals, but for this value we - * know that an unsigned int will always hold the value so we blindly - * typecast to this type - */ - data->set.scope_id = curlx_sltoui(va_arg(param, long)); - break; - - 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 = 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. Defaults - to all protocols except FILE and SCP. */ - data->set.redir_protocols = va_arg(param, long); - break; - - case CURLOPT_DEFAULT_PROTOCOL: - /* Set the protocol to use when the URL doesn't include any protocol */ - result = setstropt(&data->set.str[STRING_DEFAULT_PROTOCOL], - va_arg(param, char *)); - break; - - case CURLOPT_MAIL_FROM: - /* Set the SMTP mail originator */ - result = setstropt(&data->set.str[STRING_MAIL_FROM], - va_arg(param, char *)); - break; - - case CURLOPT_MAIL_AUTH: - /* Set the SMTP auth originator */ - result = 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_SASL_IR: - /* Enable/disable SASL initial response */ - data->set.sasl_ir = (0 != va_arg(param, long)) ? TRUE : FALSE; - break; - - 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 curl_rtspreq = va_arg(param, long); - Curl_RtspReq rtspreq = RTSPREQ_NONE; - switch(curl_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 = 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 = 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 = 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_client_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; - - case CURLOPT_WILDCARDMATCH: - data->set.wildcardmatch = (0 != va_arg(param, long)) ? TRUE : FALSE; - 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->wildcard.customptr = va_arg(param, void *); - break; - case CURLOPT_FNMATCH_DATA: - data->set.fnmatch_data = va_arg(param, void *); - break; -#ifdef USE_TLS_SRP - case CURLOPT_TLSAUTH_USERNAME: - result = setstropt(&data->set.str[STRING_TLSAUTH_USERNAME_ORIG], - va_arg(param, char *)); - if(data->set.str[STRING_TLSAUTH_USERNAME_ORIG] && !data->set.ssl.authtype) - data->set.ssl.authtype = CURL_TLSAUTH_SRP; /* default to SRP */ - break; - case CURLOPT_PROXY_TLSAUTH_USERNAME: - result = setstropt(&data->set.str[STRING_TLSAUTH_USERNAME_PROXY], - va_arg(param, char *)); - if(data->set.str[STRING_TLSAUTH_USERNAME_PROXY] && - !data->set.proxy_ssl.authtype) - data->set.proxy_ssl.authtype = CURL_TLSAUTH_SRP; /* default to SRP */ - break; - case CURLOPT_TLSAUTH_PASSWORD: - result = setstropt(&data->set.str[STRING_TLSAUTH_PASSWORD_ORIG], - va_arg(param, char *)); - if(data->set.str[STRING_TLSAUTH_USERNAME_ORIG] && !data->set.ssl.authtype) - data->set.ssl.authtype = CURL_TLSAUTH_SRP; /* default to SRP */ - break; - case CURLOPT_PROXY_TLSAUTH_PASSWORD: - result = setstropt(&data->set.str[STRING_TLSAUTH_PASSWORD_PROXY], - va_arg(param, char *)); - if(data->set.str[STRING_TLSAUTH_USERNAME_PROXY] && - !data->set.proxy_ssl.authtype) - data->set.proxy_ssl.authtype = CURL_TLSAUTH_SRP; /* default to SRP */ - break; - case CURLOPT_TLSAUTH_TYPE: - if(strncasecompare((char *)va_arg(param, char *), "SRP", strlen("SRP"))) - data->set.ssl.authtype = CURL_TLSAUTH_SRP; - else - data->set.ssl.authtype = CURL_TLSAUTH_NONE; - break; - case CURLOPT_PROXY_TLSAUTH_TYPE: - if(strncasecompare((char *)va_arg(param, char *), "SRP", strlen("SRP"))) - data->set.proxy_ssl.authtype = CURL_TLSAUTH_SRP; - else - data->set.proxy_ssl.authtype = CURL_TLSAUTH_NONE; - break; -#endif - case CURLOPT_DNS_SERVERS: - result = Curl_set_dns_servers(data, va_arg(param, char *)); - break; - case CURLOPT_DNS_INTERFACE: - result = Curl_set_dns_interface(data, va_arg(param, char *)); - break; - case CURLOPT_DNS_LOCAL_IP4: - result = Curl_set_dns_local_ip4(data, va_arg(param, char *)); - break; - case CURLOPT_DNS_LOCAL_IP6: - result = Curl_set_dns_local_ip6(data, va_arg(param, char *)); - break; - - case CURLOPT_TCP_KEEPALIVE: - data->set.tcp_keepalive = (0 != va_arg(param, long)) ? TRUE : FALSE; - break; - case CURLOPT_TCP_KEEPIDLE: - data->set.tcp_keepidle = va_arg(param, long); - break; - case CURLOPT_TCP_KEEPINTVL: - data->set.tcp_keepintvl = va_arg(param, long); - break; - case CURLOPT_TCP_FASTOPEN: -#if defined(CONNECT_DATA_IDEMPOTENT) || defined(MSG_FASTOPEN) - data->set.tcp_fastopen = (0 != va_arg(param, long))?TRUE:FALSE; -#else - result = CURLE_NOT_BUILT_IN; -#endif - break; - case CURLOPT_SSL_ENABLE_NPN: - data->set.ssl_enable_npn = (0 != va_arg(param, long)) ? TRUE : FALSE; - break; - case CURLOPT_SSL_ENABLE_ALPN: - data->set.ssl_enable_alpn = (0 != va_arg(param, long)) ? TRUE : FALSE; - break; - -#ifdef USE_UNIX_SOCKETS - case CURLOPT_UNIX_SOCKET_PATH: - data->set.abstract_unix_socket = FALSE; - result = 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 = 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)) ? TRUE : FALSE; - break; - case CURLOPT_PIPEWAIT: - data->set.pipewait = (0 != va_arg(param, long)) ? TRUE : FALSE; - break; - case CURLOPT_STREAM_WEIGHT: -#ifndef USE_NGHTTP2 - return CURLE_NOT_BUILT_IN; -#else - arg = va_arg(param, long); - if((arg>=1) && (arg <= 256)) - data->set.stream_weight = (int)arg; - break; -#endif - case CURLOPT_STREAM_DEPENDS: - case CURLOPT_STREAM_DEPENDS_E: - { -#ifndef USE_NGHTTP2 - return CURLE_NOT_BUILT_IN; -#else - struct Curl_easy *dep = va_arg(param, struct Curl_easy *); - if(!dep || GOOD_EASY_HANDLE(dep)) { - if(data->set.stream_depends_on) { - Curl_http2_remove_child(data->set.stream_depends_on, data); - } - Curl_http2_add_child(dep, data, (option == CURLOPT_STREAM_DEPENDS_E)); - } - break; -#endif - } - case CURLOPT_CONNECT_TO: - data->set.connect_to = va_arg(param, struct curl_slist *); - break; - default: - /* unknown tag and its companion, just ignore: */ - result = CURLE_UNKNOWN_OPTION; - break; - } - - return result; -} - -#ifdef USE_RECV_BEFORE_SEND_WORKAROUND -static void conn_reset_postponed_data(struct connectdata *conn, int num) -{ - struct postponed_data * const psnd = &(conn->postponed[num]); - if(psnd->buffer) { - DEBUGASSERT(psnd->allocated_size > 0); - DEBUGASSERT(psnd->recv_size <= psnd->allocated_size); - DEBUGASSERT(psnd->recv_size ? - (psnd->recv_processed < psnd->recv_size) : - (psnd->recv_processed == 0)); - DEBUGASSERT(psnd->bindsock != CURL_SOCKET_BAD); - free(psnd->buffer); - psnd->buffer = NULL; - psnd->allocated_size = 0; - psnd->recv_size = 0; - psnd->recv_processed = 0; -#ifdef DEBUGBUILD - psnd->bindsock = CURL_SOCKET_BAD; /* used only for DEBUGASSERT */ -#endif /* DEBUGBUILD */ - } - else { - DEBUGASSERT(psnd->allocated_size == 0); - DEBUGASSERT(psnd->recv_size == 0); - DEBUGASSERT(psnd->recv_processed == 0); - DEBUGASSERT(psnd->bindsock == CURL_SOCKET_BAD); - } -} + size_t i; -static void conn_reset_all_postponed_data(struct connectdata *conn) -{ - conn_reset_postponed_data(conn, 0); - conn_reset_postponed_data(conn, 1); -} -#else /* ! USE_RECV_BEFORE_SEND_WORKAROUND */ -/* Use "do-nothing" macros instead of functions when workaround not used */ -#define conn_reset_postponed_data(c,n) do {} WHILE_FALSE -#define conn_reset_all_postponed_data(c) do {} WHILE_FALSE -#endif /* ! USE_RECV_BEFORE_SEND_WORKAROUND */ - -static void conn_free(struct connectdata *conn) -{ - if(!conn) - return; - - /* possible left-overs from the async name resolvers */ - Curl_resolver_cancel(conn); - - /* close the SSL stuff before we close any sockets since they will/may - write to the sockets */ - Curl_ssl_close(conn, FIRSTSOCKET); - Curl_ssl_close(conn, SECONDARYSOCKET); - - /* close possibly still open sockets */ - if(CURL_SOCKET_BAD != conn->sock[SECONDARYSOCKET]) - Curl_closesocket(conn, conn->sock[SECONDARYSOCKET]); - if(CURL_SOCKET_BAD != conn->sock[FIRSTSOCKET]) - Curl_closesocket(conn, conn->sock[FIRSTSOCKET]); - if(CURL_SOCKET_BAD != conn->tempsock[0]) - Curl_closesocket(conn, conn->tempsock[0]); - if(CURL_SOCKET_BAD != conn->tempsock[1]) - Curl_closesocket(conn, conn->tempsock[1]); + DEBUGASSERT(conn); -#if !defined(CURL_DISABLE_HTTP) && defined(USE_NTLM) && \ - defined(NTLM_WB_ENABLED) - Curl_ntlm_wb_cleanup(conn); -#endif + for(i = 0; i < ARRAYSIZE(conn->cfilter); ++i) { + Curl_conn_cf_discard_all(data, conn, (int)i); + } - Curl_safefree(conn->user); - Curl_safefree(conn->passwd); - Curl_safefree(conn->oauth_bearer); - Curl_safefree(conn->options); + 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->allocptr.proxyuserpwd); - Curl_safefree(conn->allocptr.uagent); - Curl_safefree(conn->allocptr.userpwd); - Curl_safefree(conn->allocptr.accept_encoding); - Curl_safefree(conn->allocptr.te); - Curl_safefree(conn->allocptr.rangeline); - Curl_safefree(conn->allocptr.ref); - Curl_safefree(conn->allocptr.host); - Curl_safefree(conn->allocptr.cookiehost); - Curl_safefree(conn->allocptr.rtsp_transport); - Curl_safefree(conn->trailer); - Curl_safefree(conn->host.rawalloc); /* host name buffer */ - Curl_safefree(conn->conn_to_host.rawalloc); /* host name buffer */ - Curl_safefree(conn->secondaryhostname); Curl_safefree(conn->http_proxy.host.rawalloc); /* http proxy name buffer */ Curl_safefree(conn->socks_proxy.host.rawalloc); /* socks proxy name buffer */ - Curl_safefree(conn->master_buffer); - - conn_reset_all_postponed_data(conn); - - Curl_llist_destroy(conn->send_pipe, NULL); - Curl_llist_destroy(conn->recv_pipe, NULL); - - conn->send_pipe = NULL; - conn->recv_pipe = NULL; - +#endif + Curl_safefree(conn->user); + Curl_safefree(conn->passwd); + Curl_safefree(conn->sasl_authzid); + Curl_safefree(conn->options); + Curl_safefree(conn->oauth_bearer); + Curl_safefree(conn->host.rawalloc); /* hostname buffer */ + Curl_safefree(conn->conn_to_host.rawalloc); /* hostname buffer */ + Curl_safefree(conn->hostname_resolve); + Curl_safefree(conn->secondaryhostname); Curl_safefree(conn->localdev); - Curl_free_primary_ssl_config(&conn->ssl_config); - Curl_free_primary_ssl_config(&conn->proxy_ssl_config); + Curl_ssl_conn_config_cleanup(conn); #ifdef USE_UNIX_SOCKETS Curl_safefree(conn->unix_domain_socket); #endif + Curl_safefree(conn->destination); 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 + * primary connection, like when freeing room in the connection pool or * killing of a dead old connection. * - * 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. + * 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 + * is not strictly bound to the life-time of *this* particular connection. */ - -CURLcode Curl_disconnect(struct connectdata *conn, bool dead_connection) +bool Curl_on_disconnect(struct Curl_easy *data, + struct connectdata *conn, bool aborted) { - struct Curl_easy *data; - if(!conn) - return CURLE_OK; /* this is closed and fine already */ - data = conn->data; - - if(!data) { - DEBUGF(fprintf(stderr, "DISCONNECT without easy handle, ignoring\n")); - return CURLE_OK; - } - - /* - * If this connection isn't marked to force-close, leave it open if there - * are other users of it - */ - if(!conn->bits.close && - (conn->send_pipe->size + conn->recv_pipe->size)) { - DEBUGF(infof(data, "Curl_disconnect, usecounter: %d\n", - conn->send_pipe->size + conn->recv_pipe->size)); - return CURLE_OK; - } + /* there must be a connection to close */ + DEBUGASSERT(conn); - if(conn->dns_entry != NULL) { - Curl_resolv_unlock(data, conn->dns_entry); - conn->dns_entry = NULL; - } - - Curl_hostcache_prune(data); /* kill old DNS cache entries */ - -#if !defined(CURL_DISABLE_HTTP) && defined(USE_NTLM) - /* Cleanup NTLM connection-related data */ - Curl_http_ntlm_cleanup(conn); -#endif - - if(conn->handler->disconnect) - /* This is set if protocol-specific cleanups should be made */ - conn->handler->disconnect(conn, dead_connection); + /* it must be removed from the connection pool */ + DEBUGASSERT(!conn->bits.in_cpool); - /* unlink ourselves! */ - infof(data, "Closing connection %ld\n", conn->connection_id); - Curl_conncache_remove_conn(data->state.conn_cache, conn); + /* there must be an associated transfer */ + DEBUGASSERT(data); - free_fixed_hostname(&conn->host); - free_fixed_hostname(&conn->conn_to_host); - free_fixed_hostname(&conn->http_proxy.host); - free_fixed_hostname(&conn->socks_proxy.host); + /* the transfer must be detached from the connection */ + DEBUGASSERT(!data->conn); - Curl_ssl_close(conn, FIRSTSOCKET); - - /* Indicate to all handles on the pipe that we're dead */ - if(Curl_pipeline_wanted(data->multi, CURLPIPE_ANY)) { - signalPipeClose(conn->send_pipe, TRUE); - signalPipeClose(conn->recv_pipe, TRUE); - } + DEBUGF(infof(data, "Curl_disconnect(conn #%" FMT_OFF_T ", aborted=%d)", + conn->connection_id, aborted)); - conn_free(conn); + if(conn->dns_entry) + Curl_resolv_unlink(data, &conn->dns_entry); - return CURLE_OK; -} + /* Cleanup NTLM connection-related data */ + Curl_http_auth_cleanup_ntlm(conn); -/* - * This function should return TRUE if the socket is to be assumed to - * be dead. Most commonly this happens when the server has closed the - * connection due to inactivity. - */ -static bool SocketIsDead(curl_socket_t sock) -{ - int sval; - bool ret_val = TRUE; + /* Cleanup NEGOTIATE connection-related data */ + Curl_http_auth_cleanup_negotiate(conn); - sval = SOCKET_READABLE(sock, 0); - if(sval == 0) - /* timeout */ - ret_val = FALSE; + if(conn->connect_only) + /* treat the connection as aborted in CONNECT_ONLY situations */ + aborted = TRUE; - return ret_val; + return aborted; } /* - * IsPipeliningPossible() returns TRUE if the options set would allow - * pipelining/multiplexing and the connection is using a HTTP protocol. + * Curl_xfer_may_multiplex() + * + * Return a TRUE, iff the transfer can be done over an (appropriate) + * multiplexed connection. */ -static bool IsPipeliningPossible(const struct Curl_easy *handle, - const struct connectdata *conn) +static bool Curl_xfer_may_multiplex(const struct Curl_easy *data, + const struct connectdata *conn) { - /* If a HTTP protocol and pipelining is enabled */ + /* If an HTTP protocol and multiplexing is enabled */ if((conn->handler->protocol & PROTO_FAMILY_HTTP) && (!conn->bits.protoconnstart || !conn->bits.close)) { - if(Curl_pipeline_wanted(handle->multi, CURLPIPE_HTTP1) && - (handle->set.httpversion != CURL_HTTP_VERSION_1_0) && - (handle->set.httpreq == HTTPREQ_GET || - handle->set.httpreq == HTTPREQ_HEAD)) - /* didn't ask for HTTP/1.0 and a GET or HEAD */ - return TRUE; - - if(Curl_pipeline_wanted(handle->multi, CURLPIPE_MULTIPLEX) && - (handle->set.httpversion >= CURL_HTTP_VERSION_2)) - /* allows HTTP/2 */ + if(Curl_multiplex_wanted(data->multi) && + (data->state.httpwant >= CURL_HTTP_VERSION_2)) + /* allows HTTP/2 or newer */ return TRUE; } return FALSE; } -int Curl_removeHandleFromPipeline(struct Curl_easy *handle, - struct curl_llist *pipeline) +#ifndef CURL_DISABLE_PROXY +static bool +proxy_info_matches(const struct proxy_info *data, + const struct proxy_info *needle) { - if(pipeline) { - struct curl_llist_element *curr; - - curr = pipeline->head; - while(curr) { - if(curr->ptr == handle) { - Curl_llist_remove(pipeline, curr, NULL); - return 1; /* we removed a handle */ - } - curr = curr->next; - } - } + if((data->proxytype == needle->proxytype) && + (data->port == needle->port) && + strcasecompare(data->host.name, needle->host.name)) + return TRUE; - return 0; + return FALSE; } -#if 0 /* this code is saved here as it is useful for debugging purposes */ -static void Curl_printPipeline(struct curl_llist *pipeline) +static bool +socks_proxy_info_matches(const struct proxy_info *data, + const struct proxy_info *needle) { - struct curl_llist_element *curr; - - curr = pipeline->head; - while(curr) { - struct Curl_easy *data = (struct Curl_easy *) curr->ptr; - infof(data, "Handle in pipeline: %s\n", data->state.path); - curr = curr->next; - } + 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, will not get called */ +#define proxy_info_matches(x,y) FALSE +#define socks_proxy_info_matches(x,y) FALSE #endif -static struct Curl_easy* gethandleathead(struct curl_llist *pipeline) -{ - struct curl_llist_element *curr = pipeline->head; - if(curr) { - return (struct Curl_easy *) curr->ptr; - } - - return NULL; -} - -/* remove the specified connection from all (possible) pipelines and related - queues */ -void Curl_getoff_all_pipelines(struct Curl_easy *data, - struct connectdata *conn) -{ - bool recv_head = (conn->readchannel_inuse && - Curl_recvpipe_head(data, conn)); - bool send_head = (conn->writechannel_inuse && - Curl_sendpipe_head(data, conn)); - - if(Curl_removeHandleFromPipeline(data, conn->recv_pipe) && recv_head) - Curl_pipeline_leave_read(conn); - if(Curl_removeHandleFromPipeline(data, conn->send_pipe) && send_head) - Curl_pipeline_leave_write(conn); -} +/* 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 void signalPipeClose(struct curl_llist *pipeline, bool pipe_broke) +static bool conn_maxage(struct Curl_easy *data, + struct connectdata *conn, + struct curltime now) { - struct curl_llist_element *curr; + timediff_t idletime, lifetime; - if(!pipeline) - return; + idletime = Curl_timediff(now, conn->lastused); + idletime /= 1000; /* integer seconds is fine */ - curr = pipeline->head; - while(curr) { - struct curl_llist_element *next = curr->next; - struct Curl_easy *data = (struct Curl_easy *) curr->ptr; - -#ifdef DEBUGBUILD /* debug-only code */ - if(data->magic != CURLEASY_MAGIC_NUMBER) { - /* MAJOR BADNESS */ - infof(data, "signalPipeClose() found BAAD easy handle\n"); - } -#endif - - if(pipe_broke) - data->state.pipe_broke = TRUE; - Curl_multi_handlePipeBreak(data); - Curl_llist_remove(pipeline, curr, NULL); - curr = next; + if(idletime > data->set.maxage_conn) { + infof(data, "Too old connection (%" FMT_TIMEDIFF_T + " seconds idle), disconnect it", idletime); + return TRUE; } -} - -/* - * This function finds the connection in the connection - * cache that has been unused for the longest time. - * - * Returns the pointer to the oldest idle connection, or NULL if none was - * found. - */ -struct connectdata * -Curl_oldest_idle_connection(struct Curl_easy *data) -{ - struct conncache *bc = data->state.conn_cache; - struct curl_hash_iterator iter; - struct curl_llist_element *curr; - struct curl_hash_element *he; - time_t highscore=-1; - time_t score; - struct timeval now; - struct connectdata *conn_candidate = NULL; - struct connectbundle *bundle; - - now = Curl_tvnow(); - - Curl_hash_start_iterate(&bc->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) { - /* Set higher score for the age passed since the connection was used */ - score = Curl_tvdiff(now, conn->now); + lifetime = Curl_timediff(now, conn->created); + lifetime /= 1000; /* integer seconds is fine */ - if(score > highscore) { - highscore = score; - conn_candidate = conn; - } - } - curr = curr->next; - } - - he = Curl_hash_next_element(&iter); + if(data->set.maxlifetime_conn && lifetime > data->set.maxlifetime_conn) { + infof(data, + "Too old connection (%" FMT_TIMEDIFF_T + " seconds since creation), disconnect it", lifetime); + return TRUE; } - return conn_candidate; -} - -static bool -proxy_info_matches(const struct proxy_info* data, - const struct proxy_info* needle) -{ - if((data->proxytype == needle->proxytype) && - (data->port == needle->port) && - Curl_safe_strcasecompare(data->host.name, needle->host.name) && - Curl_safe_strcasecompare(data->user, needle->user) && - Curl_safe_strcasecompare(data->passwd, needle->passwd)) - return TRUE; return FALSE; } - -/* - * This function finds the connection in the connection - * bundle that has been unused for the longest time. - * - * Returns the pointer to the oldest idle connection, or NULL if none was - * found. - */ -static struct connectdata * -find_oldest_idle_connection_in_bundle(struct Curl_easy *data, - struct connectbundle *bundle) -{ - struct curl_llist_element *curr; - time_t highscore=-1; - time_t score; - struct timeval now; - struct connectdata *conn_candidate = NULL; - struct connectdata *conn; - - (void)data; - - now = Curl_tvnow(); - - curr = bundle->conn_list->head; - while(curr) { - conn = curr->ptr; - - if(!conn->inuse) { - /* Set higher score for the age passed since the connection was used */ - score = Curl_tvdiff(now, conn->now); - - if(score > highscore) { - highscore = score; - conn_candidate = conn; - } - } - curr = curr->next; - } - - return conn_candidate; -} - /* - * This function checks if given connection is dead and disconnects if so. - * (That also removes it from the connection cache.) - * - * Returns TRUE if the connection actually was dead and disconnected. + * Return TRUE iff the given connection is considered dead. */ -static bool disconnect_if_dead(struct connectdata *conn, - struct Curl_easy *data) +bool Curl_conn_seems_dead(struct connectdata *conn, + struct Curl_easy *data, + struct curltime *pnow) { - size_t pipeLen = conn->send_pipe->size + conn->recv_pipe->size; - if(!pipeLen && !conn->inuse) { - /* The check for a dead socket makes sense only if there are no - handles in pipeline and the connection isn't already marked in + DEBUGASSERT(!data->conn); + if(!CONN_INUSE(conn)) { + /* The check for a dead socket makes sense only if the connection is not in use */ bool dead; - if(conn->handler->protocol & CURLPROTO_RTSP) - /* RTSP is a special case due to RTP interleaving */ - dead = Curl_rtsp_connisdead(conn); - else - dead = SocketIsDead(conn->sock[FIRSTSOCKET]); - - if(dead) { - conn->data = data; - infof(data, "Connection %ld seems to be dead!\n", conn->connection_id); - - /* disconnect resources */ - Curl_disconnect(conn, /* dead_connection */TRUE); - return TRUE; + struct curltime now; + if(!pnow) { + now = Curl_now(); + pnow = &now; } - } - return FALSE; -} - -/* - * Wrapper to use disconnect_if_dead() function in Curl_conncache_foreach() - * - * Returns always 0. - */ -static int call_disconnect_if_dead(struct connectdata *conn, - void *param) -{ - struct Curl_easy* data = (struct Curl_easy*)param; - disconnect_if_dead(conn, data); - 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. - */ -static void prune_dead_connections(struct Curl_easy *data) -{ - struct timeval now = Curl_tvnow(); - time_t elapsed = Curl_tvdiff(now, data->state.conn_cache->last_cleanup); - - if(elapsed >= 1000L) { - Curl_conncache_foreach(data->state.conn_cache, data, - call_disconnect_if_dead); - data->state.conn_cache->last_cleanup = now; - } -} - - -static size_t max_pipeline_length(struct Curl_multi *multi) -{ - return multi ? multi->max_pipeline_length : 0; -} - - -/* - * 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, even if - * the pipelining strategy wants to open a new connection instead of reusing. - */ -static bool -ConnectionExists(struct Curl_easy *data, - struct connectdata *needle, - struct connectdata **usethis, - bool *force_reuse, - bool *waitpipe) -{ - struct connectdata *check; - struct connectdata *chosen = 0; - bool foundPendingCandidate = FALSE; - bool canPipeline = IsPipeliningPossible(data, needle); - struct connectbundle *bundle; -#ifdef USE_NTLM - bool wantNTLMhttp = ((data->state.authhost.want & - (CURLAUTH_NTLM | CURLAUTH_NTLM_WB)) && - (needle->handler->protocol & PROTO_FAMILY_HTTP)); - bool wantProxyNTLMhttp = (needle->bits.proxy_user_passwd && - ((data->state.authproxy.want & - (CURLAUTH_NTLM | CURLAUTH_NTLM_WB)) && - (needle->handler->protocol & PROTO_FAMILY_HTTP))); -#endif - - *force_reuse = FALSE; - *waitpipe = FALSE; - - /* We can't pipe if the site is blacklisted */ - if(canPipeline && Curl_pipeline_site_blacklisted(data, needle)) { - canPipeline = FALSE; - } - - /* Look up the bundle with all the connections to this - particular host */ - bundle = Curl_conncache_find_bundle(needle, data->state.conn_cache); - if(bundle) { - /* Max pipe length is zero (unlimited) for multiplexed connections */ - size_t max_pipe_len = (bundle->multiuse != BUNDLE_MULTIPLEX)? - max_pipeline_length(data->multi):0; - size_t best_pipe_len = max_pipe_len; - struct curl_llist_element *curr; - - infof(data, "Found bundle for host %s: %p [%s]\n", - (needle->bits.conn_to_host ? needle->conn_to_host.name : - needle->host.name), (void *)bundle, - (bundle->multiuse == BUNDLE_PIPELINING ? - "can pipeline" : - (bundle->multiuse == BUNDLE_MULTIPLEX ? - "can multiplex" : "serially"))); - - /* We can't pipe if we don't know anything about the server */ - if(canPipeline) { - if(bundle->multiuse <= BUNDLE_UNKNOWN) { - if((bundle->multiuse == BUNDLE_UNKNOWN) && data->set.pipewait) { - infof(data, "Server doesn't support multi-use yet, wait\n"); - *waitpipe = TRUE; - return FALSE; /* no re-use */ - } - - infof(data, "Server doesn't support multi-use (yet)\n"); - canPipeline = FALSE; - } - if((bundle->multiuse == BUNDLE_PIPELINING) && - !Curl_pipeline_wanted(data->multi, CURLPIPE_HTTP1)) { - /* not asked for, switch off */ - infof(data, "Could pipeline, but not asked to!\n"); - canPipeline = FALSE; - } - else if((bundle->multiuse == BUNDLE_MULTIPLEX) && - !Curl_pipeline_wanted(data->multi, CURLPIPE_MULTIPLEX)) { - infof(data, "Could multiplex, but not asked to!\n"); - canPipeline = FALSE; - } + if(conn_maxage(data, conn, *pnow)) { + /* 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; - curr = bundle->conn_list->head; - while(curr) { - bool match = FALSE; - size_t pipeLen; - - /* - * Note that if we use a HTTP proxy in normal mode (no tunneling), we - * check connections to that proxy and not to the actual remote server. - */ - check = curr->ptr; - curr = curr->next; - - if(disconnect_if_dead(check, data)) - continue; - - pipeLen = check->send_pipe->size + check->recv_pipe->size; - - if(canPipeline) { - if(check->bits.protoconnstart && check->bits.close) - continue; - - if(!check->bits.multiplex) { - /* If not multiplexing, make sure the pipe has only GET requests */ - struct Curl_easy* sh = gethandleathead(check->send_pipe); - struct Curl_easy* rh = gethandleathead(check->recv_pipe); - if(sh) { - if(!IsPipeliningPossible(sh, check)) - continue; - } - else if(rh) { - if(!IsPipeliningPossible(rh, check)) - continue; - } - } - } - else { - if(pipeLen > 0) { - /* can only happen within multi handles, and means that another easy - handle is using this connection */ - continue; - } - - if(Curl_resolver_asynch()) { - /* ip_addr_str[0] is NUL only if the resolving of the name hasn't - completed yet and until then we don't re-use this connection */ - if(!check->ip_addr_str[0]) { - infof(data, - "Connection #%ld is still name resolving, can't reuse\n", - check->connection_id); - continue; - } - } - - if((check->sock[FIRSTSOCKET] == CURL_SOCKET_BAD) || - check->bits.close) { - if(!check->bits.close) - foundPendingCandidate = TRUE; - /* Don't pick a connection that hasn't connected yet or that is going - to get closed. */ - infof(data, "Connection #%ld isn't open enough, can't reuse\n", - check->connection_id); -#ifdef DEBUGBUILD - if(check->recv_pipe->size > 0) { - infof(data, - "BAD! Unconnected #%ld has a non-empty recv pipeline!\n", - check->connection_id); - } -#endif - 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->abstract_unix_socket != check->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->protocol) != - needle->handler->protocol || !check->tls_upgraded) - /* except protocols that have been upgraded via TLS */ - continue; - - if(needle->bits.httpproxy != check->bits.httpproxy || - needle->bits.socksproxy != check->bits.socksproxy) - continue; - - if(needle->bits.socksproxy && !proxy_info_matches(&needle->socks_proxy, - &check->socks_proxy)) - 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; - - if(needle->bits.httpproxy) { - if(!proxy_info_matches(&needle->http_proxy, &check->http_proxy)) - continue; - - if(needle->bits.tunnel_proxy != check->bits.tunnel_proxy) - continue; - - if(needle->http_proxy.proxytype == CURLPROXY_HTTPS) { - /* use https proxy */ - if(needle->handler->flags&PROTOPT_SSL) { - /* use double layer ssl */ - if(!Curl_ssl_config_matches(&needle->proxy_ssl_config, - &check->proxy_ssl_config)) - continue; - if(check->proxy_ssl[FIRSTSOCKET].state != ssl_connection_complete) - continue; - } - else { - if(!Curl_ssl_config_matches(&needle->ssl_config, - &check->ssl_config)) - continue; - if(check->ssl[FIRSTSOCKET].state != ssl_connection_complete) - continue; - } - } - } - - if(!canPipeline && check->inuse) - /* this request can't be pipelined but the checked connection is - already in use so we skip it */ - continue; - - if(needle->localdev || needle->localport) { - /* If we are bound to a specific local end (IP+port), we must not - re-use 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 re-uses of bound connections - will most likely also re-use 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(strcmp(needle->user, check->user) || - strcmp(needle->passwd, check->passwd)) { - /* one of them was different */ - continue; - } - } - - if(!needle->bits.httpproxy || (needle->handler->flags&PROTOPT_SSL) || - needle->bits.tunnel_proxy) { - /* The requested connection does not use a HTTP proxy or it uses SSL or - it is a non-SSL protocol tunneled or it is a non-SSL protocol which - is allowed to be upgraded via TLS */ - - if((strcasecompare(needle->handler->scheme, check->handler->scheme) || - (get_protocol_family(check->handler->protocol) == - needle->handler->protocol && check->tls_upgraded)) && - (!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) && - strcasecompare(needle->host.name, check->host.name) && - needle->remote_port == check->remote_port) { - /* The schemes match or the the protocol family is the same and the - previous connection was TLS upgraded, and the hostname and host - port match */ - if(needle->handler->flags & PROTOPT_SSL) { - /* This is a SSL connection so verify that we're using the same - SSL options as well */ - if(!Curl_ssl_config_matches(&needle->ssl_config, - &check->ssl_config)) { - DEBUGF(infof(data, - "Connection #%ld has different SSL parameters, " - "can't reuse\n", - check->connection_id)); - continue; - } - else if(check->ssl[FIRSTSOCKET].state != ssl_connection_complete) { - foundPendingCandidate = TRUE; - DEBUGF(infof(data, - "Connection #%ld has not started SSL connect, " - "can't reuse\n", - check->connection_id)); - continue; - } - } - match = TRUE; - } - } - else { - /* The requested connection is using the same HTTP proxy in normal - mode (no tunneling) */ - match = TRUE; - } - - if(match) { -#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(strcmp(needle->user, check->user) || - strcmp(needle->passwd, check->passwd)) - continue; - } - else if(check->ntlm.state != NTLMSTATE_NONE) { - /* Connection is using NTLM auth but we don't want NTLM */ - continue; - } - - /* 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; + /* briefly attach the connection to this transfer for the purpose of + checking it */ + Curl_attach_connection(data, conn); - if(strcmp(needle->http_proxy.user, check->http_proxy.user) || - strcmp(needle->http_proxy.passwd, check->http_proxy.passwd)) - continue; - } - else if(check->proxyntlm.state != NTLMSTATE_NONE) { - /* Proxy connection is using NTLM auth but we don't want NTLM */ - continue; - } + state = conn->handler->connection_check(data, conn, CONNCHECK_ISDEAD); + dead = (state & CONNRESULT_DEAD); + /* detach the connection again */ + Curl_detach_connection(data); - if(wantNTLMhttp || wantProxyNTLMhttp) { - /* Credentials are already checked, we can use this connection */ - chosen = check; - - if((wantNTLMhttp && - (check->ntlm.state != NTLMSTATE_NONE)) || - (wantProxyNTLMhttp && - (check->proxyntlm.state != NTLMSTATE_NONE))) { - /* We must use this connection, no other */ - *force_reuse = TRUE; - break; - } - - /* Continue look up for a better connection */ - continue; - } -#endif - if(canPipeline) { - /* We can pipeline if we want to. Let's continue looking for - the optimal connection to use, i.e the shortest pipe that is not - blacklisted. */ - - if(pipeLen == 0) { - /* We have the optimal connection. Let's stop looking. */ - chosen = check; - break; - } - - /* We can't use the connection if the pipe is full */ - if(max_pipe_len && (pipeLen >= max_pipe_len)) { - infof(data, "Pipe is full, skip (%zu)\n", pipeLen); - continue; - } -#ifdef USE_NGHTTP2 - /* If multiplexed, make sure we don't go over concurrency limit */ - if(check->bits.multiplex) { - /* Multiplexed connections can only be HTTP/2 for now */ - struct http_conn *httpc = &check->proto.httpc; - if(pipeLen >= httpc->settings.max_concurrent_streams) { - infof(data, "MAX_CONCURRENT_STREAMS reached, skip (%zu)\n", - pipeLen); - continue; - } - } -#endif - /* We can't use the connection if the pipe is penalized */ - if(Curl_pipeline_penalized(data, check)) { - infof(data, "Penalized, skip\n"); - continue; - } - - if(max_pipe_len) { - if(pipeLen < best_pipe_len) { - /* This connection has a shorter pipe so far. We'll pick this - and continue searching */ - chosen = check; - best_pipe_len = pipeLen; - continue; - } - } - else { - /* When not pipelining (== multiplexed), we have a match here! */ - chosen = check; - infof(data, "Multiplexed connection found!\n"); - break; - } - } - else { - /* We have found a connection. Let's stop searching. */ - chosen = check; - break; - } + } + else { + bool input_pending = FALSE; + + Curl_attach_connection(data, conn); + dead = !Curl_conn_is_alive(data, conn, &input_pending); + if(input_pending) { + /* For reuse, we want a "clean" connection state. 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. + */ + DEBUGF(infof(data, "connection has input pending, not reusable")); + dead = TRUE; } + Curl_detach_connection(data); } - } - - if(chosen) { - *usethis = chosen; - return TRUE; /* yes, we found one to use! */ - } - if(foundPendingCandidate && data->set.pipewait) { - infof(data, - "Found pending candidate for reuse and CURLOPT_PIPEWAIT is set\n"); - *waitpipe = TRUE; + if(dead) { + /* remove connection from cpool */ + infof(data, "Connection %" FMT_OFF_T " seems to be dead", + conn->connection_id); + return TRUE; + } } - - return FALSE; /* no matching connecting exists */ + return FALSE; } -/* after a TCP connection to the proxy has been verified, this function does - the next magic step. - - Note: this function's sub-functions call failf() - -*/ -CURLcode Curl_connected_proxy(struct connectdata *conn, int sockindex) +CURLcode Curl_conn_upkeep(struct Curl_easy *data, + struct connectdata *conn, + struct curltime *now) { CURLcode result = CURLE_OK; + if(Curl_timediff(*now, conn->keepalive) <= data->set.upkeep_interval_ms) + return result; - if(conn->bits.socksproxy) { -#ifndef CURL_DISABLE_PROXY - /* for the secondary socket (FTP), use the "connect to host" - * but ignore the "connect to port" (use the secondary port) - */ - const char * const host = 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; - const int 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; - conn->bits.socksproxy_connecting = TRUE; - switch(conn->socks_proxy.proxytype) { - case CURLPROXY_SOCKS5: - case CURLPROXY_SOCKS5_HOSTNAME: - result = Curl_SOCKS5(conn->socks_proxy.user, conn->socks_proxy.passwd, - host, port, sockindex, conn); - break; - - case CURLPROXY_SOCKS4: - case CURLPROXY_SOCKS4A: - result = Curl_SOCKS4(conn->socks_proxy.user, host, port, sockindex, - conn); - break; - - default: - failf(conn->data, "unknown proxytype option given"); - result = CURLE_COULDNT_CONNECT; - } /* switch proxytype */ - conn->bits.socksproxy_connecting = FALSE; -#else - (void)sockindex; -#endif /* CURL_DISABLE_PROXY */ + /* briefly attach for action */ + Curl_attach_connection(data, conn); + if(conn->handler->connection_check) { + /* Do a protocol-specific keepalive check on the connection. */ + unsigned int rc; + rc = conn->handler->connection_check(data, conn, CONNCHECK_KEEPALIVE); + if(rc & CONNRESULT_DEAD) + result = CURLE_RECV_ERROR; } + else { + /* Do the generic action on the FIRSTSOCKET filter chain */ + result = Curl_conn_keep_alive(data, conn, FIRSTSOCKET); + } + Curl_detach_connection(data); - return result; -} - -/* - * verboseconnect() displays verbose information after a connect - */ -#ifndef CURL_DISABLE_VERBOSE_STRINGS -void Curl_verboseconnect(struct connectdata *conn) -{ - if(conn->data->set.verbose) - infof(conn->data, "Connected to %s (%s) port %ld (#%ld)\n", - conn->bits.socksproxy ? conn->socks_proxy.host.dispname : - conn->bits.httpproxy ? conn->http_proxy.host.dispname : - conn->bits.conn_to_host ? conn->conn_to_host.dispname : - conn->host.dispname, - conn->ip_addr_str, conn->port, conn->connection_id); -} -#endif - -int Curl_protocol_getsock(struct connectdata *conn, - curl_socket_t *socks, - int numsocks) -{ - if(conn->handler->proto_getsock) - return conn->handler->proto_getsock(conn, socks, numsocks); - return GETSOCK_BLANK; + conn->keepalive = *now; + return result; } -int Curl_doing_getsock(struct connectdata *conn, - curl_socket_t *socks, - int numsocks) +#ifdef USE_SSH +static bool ssh_config_matches(struct connectdata *one, + struct connectdata *two) { - if(conn && conn->handler->doing_getsock) - return conn->handler->doing_getsock(conn, socks, numsocks); - return GETSOCK_BLANK; + 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 -/* - * 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. - */ +struct url_conn_match { + struct connectdata *found; + struct Curl_easy *data; + struct connectdata *needle; + BIT(may_multiplex); + BIT(want_ntlm_http); + BIT(want_proxy_ntlm_http); + + BIT(wait_pipe); + BIT(force_reuse); + BIT(seen_pending_conn); + BIT(seen_single_use_conn); + BIT(seen_multiplex_conn); +}; -CURLcode Curl_protocol_connecting(struct connectdata *conn, - bool *done) +static bool url_match_conn(struct connectdata *conn, void *userdata) { - CURLcode result=CURLE_OK; + struct url_conn_match *match = userdata; + struct Curl_easy *data = match->data; + struct connectdata *needle = match->needle; + + /* Check if `conn` can be used for transfer `data` */ - if(conn && conn->handler->connecting) { - *done = FALSE; - result = conn->handler->connecting(conn, done); + if(conn->connect_only || conn->bits.close) + /* connect-only or to-be-closed connections will not be reused */ + return FALSE; + + if(data->set.ipver != CURL_IPRESOLVE_WHATEVER + && data->set.ipver != conn->ip_version) { + /* skip because the connection is not via the requested IP version */ + return FALSE; } - else - *done = TRUE; - return result; -} + 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 did not ask for a + particular one we can reuse one that was bound. -/* - * We are DOING this is being called over and over from the multi interface - * until the DOING phase is done on protocol layer. - */ + 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 should not hurt anyone very much. + */ + if((conn->localport != needle->localport) || + (conn->localportrange != needle->localportrange) || + (needle->localdev && + (!conn->localdev || strcmp(conn->localdev, needle->localdev)))) + return FALSE; + } -CURLcode Curl_protocol_doing(struct connectdata *conn, bool *done) -{ - CURLcode result=CURLE_OK; + if(needle->bits.conn_to_host != conn->bits.conn_to_host) + /* do not mix connections that use the "connect to host" feature and + * connections that do not use this feature */ + return FALSE; - if(conn && conn->handler->doing) { - *done = FALSE; - result = conn->handler->doing(conn, done); + if(needle->bits.conn_to_port != conn->bits.conn_to_port) + /* do not mix connections that use the "connect to port" feature and + * connections that do not use this feature */ + return FALSE; + + if(!Curl_conn_is_connected(conn, FIRSTSOCKET) || + conn->bits.asks_multiplex) { + /* Not yet connected, or not yet decided if it multiplexes. The later + * happens for HTTP/2 Upgrade: requests that need a response. */ + if(match->may_multiplex) { + match->seen_pending_conn = TRUE; + /* Do not pick a connection that has not connected yet */ + infof(data, "Connection #%" FMT_OFF_T + " is not open enough, cannot reuse", conn->connection_id); + } + /* Do not pick a connection that has not connected yet */ + return FALSE; } - else - *done = TRUE; + /* `conn` is connected. If it has transfers, can we add ours to it? */ - return result; -} + if(CONN_INUSE(conn)) { + if(!conn->bits.multiplex) { + /* conn busy and conn cannot take more transfers */ + match->seen_single_use_conn = TRUE; + return FALSE; + } + match->seen_multiplex_conn = TRUE; + if(!match->may_multiplex) + /* conn busy and transfer cannot be multiplexed */ + return FALSE; + else { + /* transfer and conn multiplex. Are they on the same multi? */ + struct Curl_llist_node *e = Curl_llist_head(&conn->easyq); + struct Curl_easy *entry = Curl_node_elem(e); + if(entry->multi != data->multi) + return FALSE; + } + } + /* `conn` is connected and we could add the transfer to it, if + * all the other criteria do match. */ -/* - * We have discovered that the TCP connection has been successful, we can now - * proceed with some action. - * - */ -CURLcode Curl_protocol_connect(struct connectdata *conn, - bool *protocol_done) -{ - CURLcode result=CURLE_OK; + /* Does `conn` use the correct protocol? */ +#ifdef USE_UNIX_SOCKETS + if(needle->unix_domain_socket) { + if(!conn->unix_domain_socket) + return FALSE; + if(strcmp(needle->unix_domain_socket, conn->unix_domain_socket)) + return FALSE; + if(needle->bits.abstract_unix_socket != conn->bits.abstract_unix_socket) + return FALSE; + } + else if(conn->unix_domain_socket) + return FALSE; +#endif - *protocol_done = FALSE; + if((needle->handler->flags&PROTOPT_SSL) != + (conn->handler->flags&PROTOPT_SSL)) + /* do not do mixed SSL and non-SSL connections */ + if(get_protocol_family(conn->handler) != + needle->handler->protocol || !conn->bits.tls_upgraded) + /* except protocols that have been upgraded via TLS */ + return FALSE; + +#ifndef CURL_DISABLE_PROXY + if(needle->bits.httpproxy != conn->bits.httpproxy || + needle->bits.socksproxy != conn->bits.socksproxy) + return FALSE; - if(conn->bits.tcpconnect[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. + if(needle->bits.socksproxy && + !socks_proxy_info_matches(&needle->socks_proxy, + &conn->socks_proxy)) + return FALSE; - Unless this protocol doesn't have any protocol-connect callback, as - then we know we're done. */ - if(!conn->handler->connecting) - *protocol_done = TRUE; + if(needle->bits.httpproxy) { + if(needle->bits.tunnel_proxy != conn->bits.tunnel_proxy) + return FALSE; - return CURLE_OK; + if(!proxy_info_matches(&needle->http_proxy, &conn->http_proxy)) + return FALSE; + + if(IS_HTTPS_PROXY(needle->http_proxy.proxytype)) { + /* https proxies come in different types, http/1.1, h2, ... */ + if(needle->http_proxy.proxytype != conn->http_proxy.proxytype) + return FALSE; + /* match SSL config to proxy */ + if(!Curl_ssl_conn_config_match(data, conn, TRUE)) { + DEBUGF(infof(data, + "Connection #%" FMT_OFF_T + " has different SSL proxy parameters, cannot reuse", + conn->connection_id)); + return FALSE; + } + /* the SSL config to the server, which may apply here is checked + * further below */ + } } +#endif - if(!conn->bits.protoconnstart) { + if(match->may_multiplex && + (data->state.httpwant == CURL_HTTP_VERSION_2_0) && + (needle->handler->protocol & CURLPROTO_HTTP) && + !conn->httpversion) { + if(data->set.pipewait) { + infof(data, "Server upgrade does not support multiplex yet, wait"); + match->found = NULL; + match->wait_pipe = TRUE; + return TRUE; /* stop searching, we want to wait */ + } + infof(data, "Server upgrade cannot be used"); + return FALSE; + } - result = Curl_proxy_connect(conn, FIRSTSOCKET); - if(result) - return result; + if(!(needle->handler->flags & PROTOPT_CREDSPERREQUEST)) { + /* This protocol requires credentials per connection, + so verify that we are using the same name and password as well */ + if(Curl_timestrcmp(needle->user, conn->user) || + Curl_timestrcmp(needle->passwd, conn->passwd) || + Curl_timestrcmp(needle->sasl_authzid, conn->sasl_authzid) || + Curl_timestrcmp(needle->oauth_bearer, conn->oauth_bearer)) { + /* one of them was different */ + return FALSE; + } + } - if(CONNECT_FIRSTSOCKET_PROXY_SSL()) - /* wait for HTTPS proxy SSL initialization to complete */ - return CURLE_OK; + /* GSS delegation differences do not actually affect every connection + and auth method, but this check takes precaution before efficiency */ + if(needle->gssapi_delegation != conn->gssapi_delegation) + return FALSE; + + /* If looking for HTTP and the HTTP version we want is less + * than the HTTP version of conn, continue looking. + * CURL_HTTP_VERSION_2TLS is default which indicates no preference, + * so we take any existing connection. */ + if((needle->handler->protocol & PROTO_FAMILY_HTTP) && + (data->state.httpwant != CURL_HTTP_VERSION_2TLS)) { + if((conn->httpversion >= 20) && + (data->state.httpwant < CURL_HTTP_VERSION_2_0)) { + DEBUGF(infof(data, "nor reusing conn #%" CURL_FORMAT_CURL_OFF_T + " with httpversion=%d, we want a version less than h2", + conn->connection_id, conn->httpversion)); + } + if((conn->httpversion >= 30) && + (data->state.httpwant < CURL_HTTP_VERSION_3)) { + DEBUGF(infof(data, "nor reusing conn #%" CURL_FORMAT_CURL_OFF_T + " with httpversion=%d, we want a version less than h3", + conn->connection_id, conn->httpversion)); + return FALSE; + } + } +#ifdef USE_SSH + else if(get_protocol_family(needle->handler) & PROTO_FAMILY_SSH) { + if(!ssh_config_matches(needle, conn)) + return FALSE; + } +#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, + conn->proto.ftpc.account) || + Curl_timestrcmp(needle->proto.ftpc.alternative_to_user, + conn->proto.ftpc.alternative_to_user) || + (needle->proto.ftpc.use_ssl != conn->proto.ftpc.use_ssl) || + (needle->proto.ftpc.ccc != conn->proto.ftpc.ccc)) + return FALSE; + } +#endif - if(conn->bits.tunnel_proxy && conn->bits.httpproxy && - (conn->tunnel_state[FIRSTSOCKET] != TUNNEL_COMPLETE)) - /* when using an HTTP tunnel proxy, await complete tunnel establishment - before proceeding further. Return CURLE_OK so we'll be called again */ - return CURLE_OK; + /* Additional match requirements if talking TLS OR + * not talking to an 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, conn->handler->scheme) && + (get_protocol_family(conn->handler) != + needle->handler->protocol || !conn->bits.tls_upgraded)) + return FALSE; + + /* If needle has "conn_to_*" set, conn must match this */ + if((needle->bits.conn_to_host && !strcasecompare( + needle->conn_to_host.name, conn->conn_to_host.name)) || + (needle->bits.conn_to_port && + needle->conn_to_port != conn->conn_to_port)) + return FALSE; - if(conn->handler->connect_it) { - /* is there a protocol-specific connect() procedure? */ + /* hostname and port must match */ + if(!strcasecompare(needle->host.name, conn->host.name) || + needle->remote_port != conn->remote_port) + return FALSE; - /* Call the protocol-specific connect function */ - result = conn->handler->connect_it(conn, protocol_done); + /* If talking TLS, conn needs to use the same SSL options. */ + if((needle->handler->flags & PROTOPT_SSL) && + !Curl_ssl_conn_config_match(data, conn, FALSE)) { + DEBUGF(infof(data, + "Connection #%" FMT_OFF_T + " has different SSL parameters, cannot reuse", + conn->connection_id)); + return FALSE; } - 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; +#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(match->want_ntlm_http) { + if(Curl_timestrcmp(needle->user, conn->user) || + Curl_timestrcmp(needle->passwd, conn->passwd)) { + + /* we prefer a credential match, but this is at least a connection + that can be reused and "upgraded" to NTLM */ + if(conn->http_ntlm_state == NTLMSTATE_NONE) + match->found = conn; + return FALSE; + } + } + else if(conn->http_ntlm_state != NTLMSTATE_NONE) { + /* Connection is using NTLM auth but we do not want NTLM */ + return FALSE; } - return result; /* pass back status */ -} +#ifndef CURL_DISABLE_PROXY + /* Same for Proxy NTLM authentication */ + if(match->want_proxy_ntlm_http) { + /* Both conn->http_proxy.user and conn->http_proxy.passwd can be + * NULL */ + if(!conn->http_proxy.user || !conn->http_proxy.passwd) + return FALSE; -/* - * Helpers for IDNA convertions. - */ -static bool is_ASCII_name(const char *hostname) -{ - const unsigned char *ch = (const unsigned char *)hostname; + if(Curl_timestrcmp(needle->http_proxy.user, + conn->http_proxy.user) || + Curl_timestrcmp(needle->http_proxy.passwd, + conn->http_proxy.passwd)) + return FALSE; + } + else if(conn->proxy_ntlm_state != NTLMSTATE_NONE) { + /* Proxy connection is using NTLM auth but we do not want NTLM */ + return FALSE; + } +#endif + if(match->want_ntlm_http || match->want_proxy_ntlm_http) { + /* 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. */ + match->found = conn; + + if((match->want_ntlm_http && + (conn->http_ntlm_state != NTLMSTATE_NONE)) || + (match->want_proxy_ntlm_http && + (conn->proxy_ntlm_state != NTLMSTATE_NONE))) { + /* We must use this connection, no other */ + match->force_reuse = TRUE; + return TRUE; + } + /* Continue look up for a better connection */ + return FALSE; + } +#endif - while(*ch) { - if(*ch++ & 0x80) + if(CONN_INUSE(conn)) { + DEBUGASSERT(match->may_multiplex); + DEBUGASSERT(conn->bits.multiplex); + /* If multiplexed, make sure we do not go over concurrency limit */ + if(CONN_INUSE(conn) >= + Curl_multi_max_concurrent_streams(data->multi)) { + infof(data, "client side MAX_CONCURRENT_STREAMS reached" + ", skip (%zu)", CONN_INUSE(conn)); return FALSE; + } + if(CONN_INUSE(conn) >= + Curl_conn_get_max_concurrent(data, conn, FIRSTSOCKET)) { + infof(data, "MAX_CONCURRENT_STREAMS reached, skip (%zu)", + CONN_INUSE(conn)); + return FALSE; + } + /* When not multiplexed, we have a match here! */ + infof(data, "Multiplexed connection found"); + } + else if(Curl_conn_seems_dead(conn, data, NULL)) { + /* removed and disconnect. Do not treat as aborted. */ + Curl_cpool_disconnect(data, conn, FALSE); + return FALSE; } + + /* We have found a connection. Let's stop searching. */ + match->found = conn; return TRUE; } +static bool url_match_result(bool result, void *userdata) +{ + struct url_conn_match *match = userdata; + (void)result; + if(match->found) { + /* Attach it now while still under lock, so the connection does + * no longer appear idle and can be reaped. */ + Curl_attach_connection(match->data, match->found); + return TRUE; + } + else if(match->seen_single_use_conn && !match->seen_multiplex_conn) { + /* We've seen a single-use, existing connection to the destination and + * no multiplexed one. It seems safe to assume that the server does + * not support multiplexing. */ + match->wait_pipe = FALSE; + } + else if(match->seen_pending_conn && match->data->set.pipewait) { + infof(match->data, + "Found pending candidate for reuse and CURLOPT_PIPEWAIT is set"); + match->wait_pipe = TRUE; + } + match->force_reuse = FALSE; + return FALSE; +} + /* - * Perform any necessary IDN conversion of hostname + * 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 void fix_hostname(struct connectdata *conn, struct hostname *host) +static bool +ConnectionExists(struct Curl_easy *data, + struct connectdata *needle, + struct connectdata **usethis, + bool *force_reuse, + bool *waitpipe) { - size_t len; - struct Curl_easy *data = conn->data; + struct url_conn_match match; + bool result; -#ifndef USE_LIBIDN2 - (void)data; - (void)conn; -#elif defined(CURL_DISABLE_VERBOSE_STRINGS) - (void)conn; -#endif - - /* set the name we use to display the host name */ - host->dispname = host->name; - - len = strlen(host->name); - if(len && (host->name[len-1] == '.')) - /* strip off a single trailing dot if present, primarily for SNI but - there's no use for it */ - host->name[len-1]=0; - - /* Check name for non-ASCII and convert hostname to ACE form if we can */ - if(!is_ASCII_name(host->name)) { -#ifdef USE_LIBIDN2 - if(idn2_check_version(IDN2_VERSION)) { - char *ace_hostname = NULL; -#if IDN2_VERSION_NUMBER >= 0x00140000 - /* IDN2_NFC_INPUT: Normalize input string using normalization form C. - IDN2_NONTRANSITIONAL: Perform Unicode TR46 non-transitional - processing. */ - int flags = IDN2_NFC_INPUT | IDN2_NONTRANSITIONAL; -#else - int flags = IDN2_NFC_INPUT; -#endif - int rc = idn2_lookup_ul((const char *)host->name, &ace_hostname, flags); - if(rc == IDN2_OK) { - host->encalloc = (char *)ace_hostname; - /* change the name pointer to point to the encoded hostname */ - host->name = host->encalloc; - } - else - infof(data, "Failed to convert %s to ACE; %s\n", host->name, - idn2_strerror(rc)); - } -#elif defined(USE_WIN32_IDN) - char *ace_hostname = NULL; + memset(&match, 0, sizeof(match)); + match.data = data; + match.needle = needle; + match.may_multiplex = Curl_xfer_may_multiplex(data, needle); - if(curl_win32_idn_to_ascii(host->name, &ace_hostname)) { - host->encalloc = ace_hostname; - /* change the name pointer to point to the encoded hostname */ - host->name = host->encalloc; - } - else - infof(data, "Failed to convert %s to ACE;\n", host->name); -#else - infof(data, "IDN support not present, can't parse Unicode domains\n"); +#ifdef USE_NTLM + match.want_ntlm_http = ((data->state.authhost.want & CURLAUTH_NTLM) && + (needle->handler->protocol & PROTO_FAMILY_HTTP)); +#ifndef CURL_DISABLE_PROXY + match.want_proxy_ntlm_http = + (needle->bits.proxy_user_passwd && + (data->state.authproxy.want & CURLAUTH_NTLM) && + (needle->handler->protocol & PROTO_FAMILY_HTTP)); #endif - } +#endif + + /* Find a connection in the pool that matches what "data + needle" + * requires. If a suitable candidate is found, it is attached to "data". */ + result = Curl_cpool_find(data, needle->destination, needle->destination_len, + url_match_conn, url_match_result, &match); + + /* wait_pipe is TRUE if we encounter a bundle that is undecided. There + * is no matching connection then, yet. */ + *usethis = match.found; + *force_reuse = match.force_reuse; + *waitpipe = match.wait_pipe; + return result; } /* - * Frees data allocated by fix_hostname() + * verboseconnect() displays verbose information after a connect */ -static void free_fixed_hostname(struct hostname *host) +#ifndef CURL_DISABLE_VERBOSE_STRINGS +void Curl_verboseconnect(struct Curl_easy *data, + struct connectdata *conn, int sockindex) { -#if defined(USE_LIBIDN2) - if(host->encalloc) { - idn2_free(host->encalloc); /* must be freed with idn2_free() since this was - allocated by libidn */ - host->encalloc = NULL; - } -#elif defined(USE_WIN32_IDN) - free(host->encalloc); /* must be freed withidn_free() since this was - allocated by curl_win32_idn_to_ascii */ - host->encalloc = NULL; -#else - (void)host; + if(data->set.verbose && sockindex == SECONDARYSOCKET) + infof(data, "Connected 2nd connection to %s port %u", + conn->secondary.remote_ip, conn->secondary.remote_port); + else + infof(data, "Connected to %s (%s) port %u", + CURL_CONN_HOST_DISPNAME(conn), conn->primary.remote_ip, + conn->primary.remote_port); +#if !defined(CURL_DISABLE_HTTP) + if(conn->handler->protocol & PROTO_FAMILY_HTTP) { + switch(conn->alpn) { + case CURL_HTTP_VERSION_3: + infof(data, "using HTTP/3"); + break; + case CURL_HTTP_VERSION_2: + infof(data, "using HTTP/2"); + break; + default: + infof(data, "using HTTP/1.x"); + break; + } + } #endif } - -static void llist_dtor(void *user, void *element) -{ - (void)user; - (void)element; - /* Do nothing */ -} +#endif /* * Allocate and initialize a new connectdata object. @@ -4113,48 +1313,31 @@ static struct connectdata *allocate_conn(struct Curl_easy *data) if(!conn) return NULL; - conn->handler = &Curl_handler_dummy; /* Be sure we have a handler defined - already from start to avoid NULL - situations and checks */ - /* 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->tempsock[0] = CURL_SOCKET_BAD; /* no file descriptor */ - conn->tempsock[1] = CURL_SOCKET_BAD; /* no file descriptor */ + conn->sockfd = CURL_SOCKET_BAD; + conn->writesockfd = CURL_SOCKET_BAD; conn->connection_id = -1; /* no ID */ - conn->port = -1; /* unknown at this point */ + conn->primary.remote_port = -1; /* unknown at this point */ conn->remote_port = -1; /* unknown at this point */ -#if defined(USE_RECV_BEFORE_SEND_WORKAROUND) && defined(DEBUGBUILD) - conn->postponed[0].bindsock = CURL_SOCKET_BAD; /* no file descriptor */ - conn->postponed[1].bindsock = CURL_SOCKET_BAD; /* no file descriptor */ -#endif /* USE_RECV_BEFORE_SEND_WORKAROUND && DEBUGBUILD */ - /* Default protocol-independent behavior doesn't support persistent + /* Default protocol-independent behavior does not 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_tvnow(); + conn->created = Curl_now(); - conn->data = data; /* Setup the association between this connection - and the Curl_easy */ + /* 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; -#ifdef CURL_DISABLE_PROXY - - conn->bits.proxy = FALSE; - conn->bits.httpproxy = FALSE; - conn->bits.socksproxy = FALSE; - conn->bits.proxy_user_passwd = FALSE; - conn->bits.tunnel_proxy = FALSE; - -#else /* CURL_DISABLE_PROXY */ - /* 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] && @@ -4162,8 +1345,8 @@ static struct connectdata *allocate_conn(struct Curl_easy *data) conn->bits.httpproxy = (conn->bits.proxy && (conn->http_proxy.proxytype == CURLPROXY_HTTP || conn->http_proxy.proxytype == CURLPROXY_HTTP_1_0 || - conn->http_proxy.proxytype == CURLPROXY_HTTPS)) ? - TRUE : FALSE; + IS_HTTPS_PROXY(conn->http_proxy.proxytype))) ? + TRUE : FALSE; conn->bits.socksproxy = (conn->bits.proxy && !conn->bits.httpproxy) ? TRUE : FALSE; @@ -4173,77 +1356,268 @@ static struct connectdata *allocate_conn(struct Curl_easy *data) } conn->bits.proxy_user_passwd = - (data->set.str[STRING_PROXYUSERNAME]) ? TRUE : FALSE; + (data->state.aptr.proxyuser) ? TRUE : FALSE; conn->bits.tunnel_proxy = data->set.tunnel_thru_httpproxy; - #endif /* CURL_DISABLE_PROXY */ - conn->bits.user_passwd = (data->set.str[STRING_USERNAME]) ? TRUE : FALSE; +#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 */ - conn->ssl_config.verifystatus = data->set.ssl.primary.verifystatus; - conn->ssl_config.verifypeer = data->set.ssl.primary.verifypeer; - conn->ssl_config.verifyhost = data->set.ssl.primary.verifyhost; - conn->proxy_ssl_config.verifystatus = - data->set.proxy_ssl.primary.verifystatus; - conn->proxy_ssl_config.verifypeer = data->set.proxy_ssl.primary.verifypeer; - conn->proxy_ssl_config.verifyhost = data->set.proxy_ssl.primary.verifyhost; + /* Initialize the easy handle list */ + Curl_llist_init(&conn->easyq, NULL); - conn->ip_version = data->set.ipver; +#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; +} -#if !defined(CURL_DISABLE_HTTP) && defined(USE_NTLM) && \ - defined(NTLM_WB_ENABLED) - conn->ntlm_auth_hlpr_socket = CURL_SOCKET_BAD; - conn->ntlm_auth_hlpr_pid = 0; - conn->challenge_header = NULL; - conn->response_header = 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(CURL_DISABLE_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 - - if(Curl_pipeline_wanted(data->multi, CURLPIPE_HTTP1) && - !conn->master_buffer) { - /* Allocate master_buffer to be used for HTTP/1 pipelining */ - conn->master_buffer = calloc(BUFSIZE, sizeof(char)); - if(!conn->master_buffer) - goto error; - } - - /* Initialize the pipeline lists */ - conn->send_pipe = Curl_llist_alloc((curl_llist_dtor) llist_dtor); - conn->recv_pipe = Curl_llist_alloc((curl_llist_dtor) llist_dtor); - if(!conn->send_pipe || !conn->recv_pipe) - goto error; - -#ifdef HAVE_GSSAPI - conn->data_prot = PROT_CLEAR; + 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(CURL_DISABLE_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 + }; - /* 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; - } - conn->localportrange = data->set.localportrange; - conn->localport = data->set.localport; - - /* 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; - - return conn; - error: - - Curl_llist_destroy(conn->send_pipe, NULL); - Curl_llist_destroy(conn->recv_pipe, NULL); - - conn->send_pipe = NULL; - conn->recv_pipe = NULL; + 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 += (unsigned int)Curl_raw_tolower(*s); + s++; + l--; + } - free(conn->master_buffer); - free(conn->localdev); - free(conn); + h = protocols[c % 67]; + if(h && strncasecompare(scheme, h->scheme, len) && !h->scheme[len]) + return h; + } return NULL; } @@ -4251,477 +1625,327 @@ static CURLcode findprotocol(struct Curl_easy *data, struct connectdata *conn, const char *protostr) { - const struct Curl_handler * const *pp; - const struct Curl_handler *p; - - /* Scan protocol handler table and match against 'protostr' to set a few - variables based on the URL. Now that the handler may be changed later - when the protocol specific setup function is called. */ - for(pp = protocols; (p = *pp) != NULL; pp++) { - if(strcasecompare(p->scheme, protostr)) { - /* Protocol found in table. Check if allowed */ - if(!(data->set.allowed_protocols & p->protocol)) - /* nope, get out */ - break; + const struct Curl_handler *p = Curl_get_scheme_handler(protostr); - /* 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 */ - break; + 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 + /* The protocol was not found in the table, but we do not 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); + failf(data, "Protocol \"%s\" %s%s", protostr, + p ? "disabled" : "not supported", + data->state.this_is_a_follow ? " (in redirect)":""); return CURLE_UNSUPPORTED_PROTOCOL; } -/* - * Parse URL and fill in the relevant members of the connection struct. - */ -static CURLcode parseurlandfillconn(struct Curl_easy *data, - struct connectdata *conn, - bool *prot_missing, - char **userp, char **passwdp, - char **optionsp) -{ - char *at; - char *fragment; - char *path = data->state.path; - char *query; - int i; - int rc; - const char *protop = ""; - CURLcode result; - bool rebuild_url = FALSE; - bool url_has_scheme = FALSE; - char protobuf[16]; - - *prot_missing = FALSE; - - /* We might pass the entire URL into the request so we need to make sure - * there are no bad characters in there.*/ - if(strpbrk(data->change.url, "\r\n")) { - failf(data, "Illegal characters found in URL"); - return CURLE_URL_MALFORMAT; - } - /************************************************************* - * Parse the URL. - * - * We need to parse the url even when using the proxy, because we will need - * the hostname and port in case we are trying to SSL connect through the - * proxy -- and we don't know if we will need to use SSL until we parse the - * url ... - ************************************************************/ - if(data->change.url[0] == ':') { - failf(data, "Bad URL, colon is first character"); +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; } +} - /* Make sure we don't mistake a drive letter for a scheme, for example: - curld --proto-default file c:/foo/bar.txt */ - if((('a' <= data->change.url[0] && data->change.url[0] <= 'z') || - ('A' <= data->change.url[0] && data->change.url[0] <= 'Z')) && - data->change.url[1] == ':' && data->set.str[STRING_DEFAULT_PROTOCOL] && - strcasecompare(data->set.str[STRING_DEFAULT_PROTOCOL], "file")) { - ; /* do nothing */ - } - else { /* check for a scheme */ - for(i = 0; i < 16 && data->change.url[i]; ++i) { - if(data->change.url[i] == '/') - break; - if(data->change.url[i] == ':') { - url_has_scheme = TRUE; - break; - } - } - } - - /* handle the file: scheme */ - if((url_has_scheme && strncasecompare(data->change.url, "file:", 5)) || - (!url_has_scheme && data->set.str[STRING_DEFAULT_PROTOCOL] && - strcasecompare(data->set.str[STRING_DEFAULT_PROTOCOL], "file"))) { - bool path_has_drive = FALSE; - - if(url_has_scheme) - rc = sscanf(data->change.url, "%*15[^\n/:]:%[^\n]", path); - else - rc = sscanf(data->change.url, "%[^\n]", path); - - if(rc != 1) { - failf(data, "Bad URL"); - return CURLE_URL_MALFORMAT; - } - - if(url_has_scheme && path[0] == '/' && path[1] == '/') { - /* Allow omitted hostname (e.g. file:/). This is not strictly - * speaking a valid file: URL by RFC 1738, but treating file:/ as - * file://localhost/ is similar to how other schemes treat missing - * hostnames. See RFC 1808. */ +#ifdef USE_IPV6 +/* + * If the URL was set with an IPv6 numerical address with a zone id part, set + * the scope_id based on that! + */ - /* This cannot be done with strcpy() in a portable manner, since the - memory areas overlap! */ - memmove(path, path + 2, strlen(path + 2)+1); - } +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 - /* the path may start with a drive letter. for backwards compatibility - we skip some processing on those paths. */ - path_has_drive = (('a' <= path[0] && path[0] <= 'z') || - ('A' <= path[0] && path[0] <= 'Z')) && path[1] == ':'; + 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 - /* - * we deal with file:/// differently since it supports no - * hostname other than "localhost" and "127.0.0.1", which is unique among - * the URL protocols specified in RFC 1738 - */ - if(path[0] != '/' && !path_has_drive) { - /* the URL includes a host name, it must match "localhost" or - "127.0.0.1" to be valid */ - char *ptr; - if(!checkprefix("localhost/", path) && - !checkprefix("127.0.0.1/", path)) { - failf(data, "Invalid file://hostname/, " - "expected localhost or 127.0.0.1 or none"); - return CURLE_URL_MALFORMAT; +#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 } - ptr = &path[9]; /* now points to the slash after the host */ - - /* there was a host name and slash present - - RFC1738 (section 3.1, page 5) says: - - The rest of the locator consists of data specific to the scheme, - and is known as the "url-path". It supplies the details of how the - specified resource can be accessed. Note that the "/" between the - host (or port) and the url-path is NOT part of the url-path. - - As most agents use file://localhost/foo to get '/foo' although the - slash preceding foo is a separator and not a slash for the path, - a URL as file://localhost//foo must be valid as well, to refer to - the same file with an absolute path. - */ - - if('/' == ptr[1]) - /* if there was two slashes, we skip the first one as that is then - used truly as a separator */ - ptr++; - - /* This cannot be made with strcpy, as the memory chunks overlap! */ - memmove(path, ptr, strlen(ptr)+1); - - path_has_drive = (('a' <= path[0] && path[0] <= 'z') || - ('A' <= path[0] && path[0] <= 'Z')) && path[1] == ':'; - } - -#if !defined(MSDOS) && !defined(WIN32) && !defined(__CYGWIN__) - if(path_has_drive) { - failf(data, "File drive letters are only accepted in MSDOS/Windows."); - return CURLE_URL_MALFORMAT; + else + conn->scope_id = scopeidx; } -#endif +#endif /* HAVE_IF_NAMETOINDEX || _WIN32 */ - protop = "file"; /* protocol string */ + free(zoneid); } - else { - /* clear path */ - char slashbuf[4]; - path[0]=0; - - rc = sscanf(data->change.url, - "%15[^\n/:]:%3[/]%[^\n/?#]%[^\n]", - protobuf, slashbuf, conn->host.name, path); - if(2 == rc) { - failf(data, "Bad URL"); - return CURLE_URL_MALFORMAT; - } - if(3 > rc) { - - /* - * The URL was badly formatted, let's try the browser-style _without_ - * protocol specified like 'http://'. - */ - rc = sscanf(data->change.url, "%[^\n/?#]%[^\n]", conn->host.name, path); - if(1 > rc) { - /* - * We couldn't even get this format. - * djgpp 2.04 has a sscanf() bug where 'conn->host.name' is - * assigned, but the return value is EOF! - */ -#if defined(__DJGPP__) && (DJGPP_MINOR == 4) - if(!(rc == -1 && *conn->host.name)) +} +#else +#define zonefrom_url(a,b,c) Curl_nop_stmt #endif - { - failf(data, " malformed"); - return CURLE_URL_MALFORMAT; - } - } - /* - * Since there was no protocol part specified in the URL use the - * user-specified default protocol. If we weren't given a default make a - * guess by matching some protocols against the host's outermost - * sub-domain name. Finally if there was no match use HTTP. - */ +/* + * 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); - protop = data->set.str[STRING_DEFAULT_PROTOCOL]; - if(!protop) { - /* Note: if you add a new protocol, please update the list in - * lib/version.c too! */ - if(checkprefix("FTP.", conn->host.name)) - protop = "ftp"; - else if(checkprefix("DICT.", conn->host.name)) - protop = "DICT"; - else if(checkprefix("LDAP.", conn->host.name)) - protop = "LDAP"; - else if(checkprefix("IMAP.", conn->host.name)) - protop = "IMAP"; - else if(checkprefix("SMTP.", conn->host.name)) - protop = "smtp"; - else if(checkprefix("POP3.", conn->host.name)) - protop = "pop3"; - else - protop = "http"; - } + up_free(data); /* cleanup previous leftovers first */ - *prot_missing = TRUE; /* not given in URL */ - } - else { - size_t s = strlen(slashbuf); - protop = protobuf; - if(s != 2) { - infof(data, "Unwillingly accepted illegal URL using %d slash%s!\n", - s, s>1?"es":""); - - if(data->change.url_alloc) - free(data->change.url); - /* repair the URL to use two slashes */ - data->change.url = aprintf("%s://%s%s", - protobuf, conn->host.name, path); - if(!data->change.url) - return CURLE_OUT_OF_MEMORY; - data->change.url_alloc = TRUE; - } - } + /* parse the URL */ + if(use_set_uh) { + uh = data->state.uh = curl_url_dup(data->set.uh); + } + else { + uh = data->state.uh = curl_url(); } - /* We search for '?' in the host name (but only on the right side of a - * @-letter to allow ?-letters in username and password) to handle things - * like http://example.com?param= (notice the missing '/'). - */ - at = strchr(conn->host.name, '@'); - if(at) - query = strchr(at+1, '?'); - else - query = strchr(conn->host.name, '?'); - - if(query) { - /* We must insert a slash before the '?'-letter in the URL. If the URL had - a slash after the '?', that is where the path currently begins and the - '?string' is still part of the host name. - - We must move the trailing part from the host name and put it first in - the path. And have it all prefixed with a slash. - */ - - size_t hostlen = strlen(query); - size_t pathlen = strlen(path); - - /* move the existing path plus the zero byte forward, to make room for - the host-name part */ - memmove(path+hostlen+1, path, pathlen+1); - - /* now copy the trailing host part in front of the existing path */ - memcpy(path+1, query, hostlen); - - path[0]='/'; /* prepend the missing slash */ - rebuild_url = TRUE; + if(!uh) + return CURLE_OUT_OF_MEMORY; - *query=0; /* now cut off the hostname at the ? */ + 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, (unsigned int) + (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(!path[0]) { - /* if there's no path set, use a single slash */ - strcpy(path, "/"); - rebuild_url = TRUE; + else if(strlen(data->state.up.hostname) > MAX_URL_LEN) { + failf(data, "Too long hostname (maximum is %d)", MAX_URL_LEN); + return CURLE_URL_MALFORMAT; } + hostname = data->state.up.hostname; - /* If the URL is malformatted (missing a '/' after hostname before path) we - * insert a slash here. The only letters except '/' that can start a path is - * '?' and '#' - as controlled by the two sscanf() patterns above. - */ - if(path[0] != '/') { - /* We need this function to deal with overlapping memory areas. We know - that the memory area 'path' points to is 'urllen' bytes big and that - is bigger than the path. Use +1 to move the zero byte too. */ - memmove(&path[1], path, strlen(path)+1); - path[0] = '/'; - rebuild_url = TRUE; - } - else if(!data->set.path_as_is) { - /* sanitise paths and remove ../ and ./ sequences according to RFC3986 */ - char *newp = Curl_dedotdotify(path); - if(!newp) - return CURLE_OUT_OF_MEMORY; + 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; - if(strcmp(newp, path)) { - rebuild_url = TRUE; - free(data->state.pathbuffer); - data->state.pathbuffer = newp; - data->state.path = newp; - path = newp; - } - else - free(newp); + zonefrom_url(uh, data, conn); } - /* - * "rebuild_url" means that one or more URL components have been modified so - * we need to generate an updated full version. We need the corrected URL - * when communicating over HTTP proxy and we don't know at this point if - * we're using a proxy or not. - */ - if(rebuild_url) { - char *reurl; - - size_t plen = strlen(path); /* new path, should be 1 byte longer than - the original */ - size_t urllen = strlen(data->change.url); /* original URL length */ - - size_t prefixlen = strlen(conn->host.name); - - if(!*prot_missing) - prefixlen += strlen(protop) + strlen("://"); - - reurl = malloc(urllen + 2); /* 2 for zerobyte + slash */ - if(!reurl) - return CURLE_OUT_OF_MEMORY; - - /* copy the prefix */ - memcpy(reurl, data->change.url, prefixlen); + /* make sure the connect struct gets its own copy of the hostname */ + conn->host.rawalloc = strdup(hostname ? hostname : ""); + if(!conn->host.rawalloc) + return CURLE_OUT_OF_MEMORY; + conn->host.name = conn->host.rawalloc; - /* append the trailing piece + zerobyte */ - memcpy(&reurl[prefixlen], path, plen + 1); + /************************************************************* + * IDN-convert the hostnames + *************************************************************/ + result = Curl_idnconvert_hostname(&conn->host); + if(result) + return result; - /* possible free the old one */ - if(data->change.url_alloc) { - Curl_safefree(data->change.url); - data->change.url_alloc = FALSE; +#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); } - - infof(data, "Rebuilt URL to: %s\n", reurl); - - data->change.url = reurl; - data->change.url_alloc = TRUE; /* free this later */ } +#endif - result = findprotocol(data, conn, protop); + result = findprotocol(data, conn, data->state.up.scheme); if(result) return result; /* - * Parse the login details from the URL and strip them out of - * the host name + * username and password set with their own options override the + * credentials possibly set in the URL. */ - result = parse_url_login(data, conn, userp, passwdp, optionsp); - if(result) - return result; + 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 do not 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; + } - if(conn->host.name[0] == '[') { - /* This looks like an IPv6 address literal. See if there is an address - scope if there is no location header */ - char *percent = strchr(conn->host.name, '%'); - if(percent) { - unsigned int identifier_offset = 3; - char *endp; - unsigned long scope; - if(strncmp("%25", percent, 3) != 0) { - infof(data, - "Please URL encode %% as %%25, see RFC 6874.\n"); - identifier_offset = 1; - } - scope = strtoul(percent + identifier_offset, &endp, 10); - if(*endp == ']') { - /* The address scope was well formed. Knock it out of the - hostname. */ - memmove(percent, endp, strlen(endp)+1); - conn->scope_id = (unsigned int)scope; - } - else { - /* Zone identifier is not numeric */ -#if defined(HAVE_NET_IF_H) && defined(IFNAMSIZ) && defined(HAVE_IF_NAMETOINDEX) - char ifname[IFNAMSIZ + 2]; - char *square_bracket; - unsigned int scopeidx = 0; - strncpy(ifname, percent + identifier_offset, IFNAMSIZ + 2); - /* Ensure nullbyte termination */ - ifname[IFNAMSIZ + 1] = '\0'; - square_bracket = strchr(ifname, ']'); - if(square_bracket) { - /* Remove ']' */ - *square_bracket = '\0'; - scopeidx = if_nametoindex(ifname); - if(scopeidx == 0) { - infof(data, "Invalid network interface: %s; %s\n", ifname, - strerror(errno)); - } - } - if(scopeidx > 0) { - char *p = percent + identifier_offset + strlen(ifname); + 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); - /* Remove zone identifier from hostname */ - memmove(percent, p, strlen(p) + 1); - conn->scope_id = scopeidx; - } - else -#endif /* HAVE_NET_IF_H && IFNAMSIZ */ - infof(data, "Invalid IPv6 address format\n"); - } - } + 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->primary.remote_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 USE_IPV6 if(data->set.scope_id) /* Override any scope that was set above. */ conn->scope_id = data->set.scope_id; +#endif - /* Remove the fragment part of the path. Per RFC 2396, this is always the - last part of the URI. We are looking for the first '#' so that we deal - gracefully with non conformant URI such as http://example.com#foo#bar. */ - fragment = strchr(path, '#'); - if(fragment) { - *fragment = 0; - - /* we know the path part ended with a fragment, so we know the full URL - string does too and we need to cut it off from there so it isn't used - over proxy */ - fragment = strchr(data->change.url, '#'); - if(fragment) - *fragment = 0; - } - - /* - * So if the URL was A://B/C#D, - * protop is A - * conn->host.name is B - * data->state.path is /C - */ return CURLE_OK; } + /* - * If we're doing a resumed transfer, we need to setup our stuff + * If we are doing a resumed transfer, we need to setup our stuff * properly. */ static CURLcode setup_range(struct Curl_easy *data) @@ -4733,7 +1957,7 @@ static CURLcode setup_range(struct Curl_easy *data) free(s->range); if(s->resume_from) - s->range = aprintf("%" CURL_FORMAT_CURL_OFF_TU "-", s->resume_from); + s->range = aprintf("%" FMT_OFF_T "-", s->resume_from); else s->range = strdup(data->set.str[STRING_SET_RANGE]); @@ -4761,29 +1985,19 @@ static CURLcode setup_range(struct Curl_easy *data) * * This MUST get called after proxy magic has been figured out. */ -static CURLcode setup_connection_internals(struct connectdata *conn) +static CURLcode setup_connection_internals(struct Curl_easy *data, + struct connectdata *conn) { - const struct Curl_handler * p; + const struct Curl_handler *p; + const char *hostname; + int port; CURLcode result; - struct Curl_easy *data = conn->data; - - /* in some case in the multi state-machine, we go back to the CONNECT state - and then a second (or third or...) call to this function will be made - without doing a DISCONNECT or DONE in between (since the connection is - yet in place) and therefore this function needs to first make sure - there's no lingering previous data allocated. */ - Curl_free_request_state(data); - - memset(&data->req, 0, sizeof(struct SingleRequest)); - data->req.maxdownload = -1; - - conn->socktype = SOCK_STREAM; /* most of them are TCP streams */ /* Perform setup complement if some. */ p = conn->handler; if(p->setup_connection) { - result = (*p->setup_connection)(conn); + result = (*p->setup_connection)(data, conn); if(result) return result; @@ -4791,107 +2005,53 @@ static CURLcode setup_connection_internals(struct connectdata *conn) p = conn->handler; /* May have changed. */ } - if(conn->port < 0) + if(conn->primary.remote_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.protop); - Curl_safefree(data->req.newurl); -} - + conn->primary.remote_port = p->defport; + /* Now create the destination name */ #ifndef CURL_DISABLE_PROXY -/**************************************************************** -* Checks if the host is in the noproxy list. returns true if it matches -* and therefore the proxy should NOT be used. -****************************************************************/ -static bool check_noproxy(const char *name, const char *no_proxy) -{ - /* 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) - */ - size_t tok_start; - size_t tok_end; - const char *separator = ", "; - size_t no_proxy_len; - size_t namelen; - char *endptr; - - if(no_proxy && no_proxy[0]) { - if(strcasecompare("*", no_proxy)) { - return TRUE; - } - - /* NO_PROXY was specified and it wasn't just an asterisk */ - - no_proxy_len = strlen(no_proxy); - endptr = strchr(name, ':'); - if(endptr) - namelen = endptr - name; + if(conn->bits.httpproxy && !conn->bits.tunnel_proxy) { + hostname = conn->http_proxy.host.name; + port = conn->primary.remote_port; + } + else +#endif + { + port = conn->remote_port; + if(conn->bits.conn_to_host) + hostname = conn->conn_to_host.name; else - namelen = strlen(name); + hostname = conn->host.name; + } - for(tok_start = 0; tok_start < no_proxy_len; tok_start = tok_end + 1) { - while(tok_start < no_proxy_len && - strchr(separator, no_proxy[tok_start]) != NULL) { - /* Look for the beginning of the token. */ - ++tok_start; - } +#ifdef USE_IPV6 + conn->destination = aprintf("%u/%d/%s", conn->scope_id, port, hostname); +#else + conn->destination = aprintf("%d/%s", port, hostname); +#endif + if(!conn->destination) + return CURLE_OUT_OF_MEMORY; - if(tok_start == no_proxy_len) - break; /* It was all trailing separator chars, no more tokens. */ + conn->destination_len = strlen(conn->destination) + 1; + Curl_strntolower(conn->destination, conn->destination, + conn->destination_len - 1); - for(tok_end = tok_start; tok_end < no_proxy_len && - strchr(separator, no_proxy[tok_end]) == NULL; ++tok_end) - /* Look for the end of the token. */ - ; + return CURLE_OK; +} - /* To match previous behaviour, where it was necessary to specify - * ".local.com" to prevent matching "notlocal.com", we will leave - * the '.' off. - */ - if(no_proxy[tok_start] == '.') - ++tok_start; - - if((tok_end - tok_start) <= namelen) { - /* Match the last part of the name to the domain we are checking. */ - const char *checkn = name + namelen - (tok_end - tok_start); - if(strncasecompare(no_proxy + tok_start, checkn, - tok_end - tok_start)) { - if((tok_end - tok_start) == namelen || *(checkn - 1) == '.') { - /* We either have an exact match, or the previous character is a . - * so it is within the same domain, so no proxy for this host. - */ - return TRUE; - } - } - } /* if((tok_end - tok_start) <= namelen) */ - } /* for(tok_start = 0; tok_start < no_proxy_len; - tok_start = tok_end + 1) */ - } /* NO_PROXY was specified and it wasn't just an asterisk */ - return FALSE; -} +#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 connectdata *conn) +static char *detect_proxy(struct Curl_easy *data, + struct connectdata *conn) { char *proxy = NULL; @@ -4910,25 +2070,21 @@ static char *detect_proxy(struct connectdata *conn) * the first to check for.) * * For compatibility, the all-uppercase versions of these variables are - * checked if the lowercase versions don't exist. + * checked if the lowercase versions do not exist. */ - char proxy_env[128]; - const char *protop = conn->handler->scheme; + char proxy_env[20]; char *envp = proxy_env; - char *prox; - - /* Now, build _proxy and check for such a one to use */ - while(*protop) - *envp++ = (char)tolower((int)*protop++); +#ifdef CURL_DISABLE_VERBOSE_STRINGS + (void)data; +#endif - /* append _proxy */ - strcpy(envp, "_proxy"); + msnprintf(proxy_env, sizeof(proxy_env), "%s_proxy", conn->handler->scheme); /* read the protocol proxy: */ - prox=curl_getenv(proxy_env); + proxy = curl_getenv(proxy_env); /* - * We don't try the uppercase version of HTTP_PROXY because of + * We do not try the uppercase version of HTTP_PROXY because of * security reasons: * * When curl is used in a webserver application @@ -4939,220 +2095,247 @@ static char *detect_proxy(struct connectdata *conn) * This can cause 'internal' http/ftp requests to be * arbitrarily redirected by any external attacker. */ - if(!prox && !strcasecompare("http_proxy", proxy_env)) { + 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)); - prox=curl_getenv(proxy_env); + proxy = curl_getenv(proxy_env); } - if(prox) - proxy = prox; /* use this */ - else { - proxy = curl_getenv("all_proxy"); /* default proxy to use */ - if(!proxy) - proxy=curl_getenv("ALL_PROXY"); + if(!proxy) { +#ifndef CURL_DISABLE_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); + } +#ifndef CURL_DISABLE_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 re-use an existing connection + * hostname, 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 *prox_portno; - char *endofprot; - - /* We use 'proxyptr' to point to the proxy name from now on... */ - char *proxyptr; - char *portptr; - char *atsign; - long port = -1; + 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 - /* We do the proxy host string parsing here. We want the host name and the - * port name. Accept a protocol:// prefix - */ - /* Parse the protocol part if present */ - endofprot = strstr(proxy, "://"); - if(endofprot) { - proxyptr = endofprot+3; - if(checkprefix("https", proxy)) - proxytype = CURLPROXY_HTTPS; - else if(checkprefix("socks5h", proxy)) + 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(checkprefix("socks5", proxy)) + else if(strcasecompare("socks5", scheme)) proxytype = CURLPROXY_SOCKS5; - else if(checkprefix("socks4a", proxy)) + else if(strcasecompare("socks4a", scheme)) proxytype = CURLPROXY_SOCKS4A; - else if(checkprefix("socks4", proxy) || checkprefix("socks", proxy)) + else if(strcasecompare("socks4", scheme) || + strcasecompare("socks", scheme)) proxytype = CURLPROXY_SOCKS4; - else if(checkprefix("http:", proxy)) + 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); - return CURLE_COULDNT_CONNECT; + result = CURLE_COULDNT_CONNECT; + goto error; } } - else - proxyptr = proxy; /* No xxx:// head: It's a HTTP proxy */ - -#ifndef HTTPS_PROXY_SUPPORT - if(proxytype == CURLPROXY_HTTPS) { - failf(data, "Unsupported proxy \'%s\'" - ", libcurl is built without the HTTPS-proxy support.", proxy); - return CURLE_NOT_BUILT_IN; + 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; + sockstype = + proxytype == CURLPROXY_SOCKS5_HOSTNAME || + proxytype == CURLPROXY_SOCKS5 || + proxytype == CURLPROXY_SOCKS4A || + proxytype == CURLPROXY_SOCKS4; - /* Is there a username and password given in this proxy url? */ - atsign = strchr(proxyptr, '@'); - if(atsign) { - CURLcode result = - parse_login_details(proxyptr, atsign - proxyptr, - &proxyuser, &proxypasswd, NULL); - if(result) - return result; - proxyptr = atsign + 1; - } + proxyinfo = sockstype ? &conn->socks_proxy : &conn->http_proxy; + proxyinfo->proxytype = (unsigned char)proxytype; - /* start scanning for port number at this point */ - portptr = proxyptr; + /* 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; - /* detect and extract RFC6874-style IPv6-addresses */ - if(*proxyptr == '[') { - char *ptr = ++proxyptr; /* 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.\n"); - ptr++; - /* Allow unreserved characters as defined in RFC 3986 */ - while(*ptr && (ISALPHA(*ptr) || ISXDIGIT(*ptr) || (*ptr == '-') || - (*ptr == '.') || (*ptr == '_') || (*ptr == '~'))) - ptr++; + 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; + } } - if(*ptr == ']') - /* yeps, it ended nicely with a bracket as well */ - *ptr++ = 0; - else - infof(data, "Invalid IPv6 address format\n"); - portptr = ptr; - /* Note that if this didn't end with a bracket, we still advanced the - * proxyptr first, but I can't see anything wrong with that as no host - * name nor a numeric can legally start with a bracket. - */ + 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 */ } - /* Get port number off proxy.server.com:1080 */ - prox_portno = strchr(portptr, ':'); - if(prox_portno) { - char *endp = NULL; + (void)curl_url_get(uhp, CURLUPART_PORT, &portptr, 0); - *prox_portno = 0x0; /* cut off number from host name */ - prox_portno ++; - /* now set the local port number */ - port = strtol(prox_portno, &endp, 10); - if((endp && *endp && (*endp != '/') && (*endp != ' ')) || - (port < 0) || (port > 65535)) { - /* meant to detect for example invalid IPv6 numerical addresses without - brackets: "2a00:fac0:a000::7:13". Accept a trailing slash only - because we then allow "URL style" with the number followed by a - slash, used in curl test cases already. Space is also an acceptable - terminating symbol. */ - infof(data, "No valid port number in proxy string (%s)\n", - prox_portno); - } - else - conn->port = port; + if(portptr) { + port = (int)strtol(portptr, NULL, 10); + free(portptr); } else { - if(proxyptr[0]=='/') - /* If the first character in the proxy string is a slash, fail - immediately. The following code will otherwise clear the string which - will lead to code running as if no proxy was set! */ - return CURLE_COULDNT_RESOLVE_PROXY; - - /* without a port number after the host name, some people seem to use - a slash so we strip everything from the first slash */ - atsign = strchr(proxyptr, '/'); - if(atsign) - *atsign = '\0'; /* cut off path part from host name */ - if(data->set.proxyport) /* None given in the proxy string, then get the default one if it is given */ - port = data->set.proxyport; + port = (int)data->set.proxyport; else { - if(proxytype == CURLPROXY_HTTPS) + 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->primary.remote_port < 0 || sockstype || + !conn->socks_proxy.host.rawalloc) + conn->primary.remote_port = port; + } - if(*proxyptr) { - struct proxy_info *proxyinfo = - sockstype ? &conn->socks_proxy : &conn->http_proxy; - proxyinfo->proxytype = proxytype; - - if(proxyuser) { - /* found user and password, rip them out. note that we are unescaping - them, as there is otherwise no way to have a username or password - with reserved characters like ':' in them. */ - Curl_safefree(proxyinfo->user); - proxyinfo->user = curl_easy_unescape(data, proxyuser, 0, NULL); - - if(!proxyinfo->user) - return CURLE_OUT_OF_MEMORY; - - Curl_safefree(proxyinfo->passwd); - if(proxypasswd && strlen(proxypasswd) < MAX_CURL_PASSWORD_LENGTH) - proxyinfo->passwd = curl_easy_unescape(data, proxypasswd, 0, NULL); - else - proxyinfo->passwd = strdup(""); - - if(!proxyinfo->passwd) - return CURLE_OUT_OF_MEMORY; - - conn->bits.proxy_user_passwd = TRUE; /* enable it */ + /* now, clone the proxy hostname */ + 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; } - - if(port >= 0) { - proxyinfo->port = port; - if(conn->port < 0 || sockstype || !conn->socks_proxy.host.rawalloc) - conn->port = port; + /* 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; } + } - /* now, clone the cleaned proxy host name */ + if(!is_unix_proxy) { +#endif Curl_safefree(proxyinfo->host.rawalloc); - proxyinfo->host.rawalloc = strdup(proxyptr); - proxyinfo->host.name = proxyinfo->host.rawalloc; - - if(!proxyinfo->host.rawalloc) - return CURLE_OUT_OF_MEMORY; + 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 - Curl_safefree(proxyuser); - Curl_safefree(proxypasswd); - - return CURLE_OK; +error: + free(proxyuser); + free(proxypasswd); + free(host); + free(scheme); +#ifdef USE_UNIX_SOCKETS + free(path); +#endif + curl_url_cleanup(uhp); + return result; } /* @@ -5161,151 +2344,200 @@ static CURLcode parse_proxy(struct Curl_easy *data, static CURLcode parse_proxy_auth(struct Curl_easy *data, struct connectdata *conn) { - char proxyuser[MAX_CURL_USER_LENGTH]=""; - char proxypasswd[MAX_CURL_PASSWORD_LENGTH]=""; - CURLcode result; - - if(data->set.str[STRING_PROXYUSERNAME] != NULL) { - strncpy(proxyuser, data->set.str[STRING_PROXYUSERNAME], - MAX_CURL_USER_LENGTH); - proxyuser[MAX_CURL_USER_LENGTH-1] = '\0'; /*To be on safe side*/ - } - if(data->set.str[STRING_PROXYPASSWORD] != NULL) { - strncpy(proxypasswd, data->set.str[STRING_PROXYPASSWORD], - MAX_CURL_PASSWORD_LENGTH); - proxypasswd[MAX_CURL_PASSWORD_LENGTH-1] = '\0'; /*To be on safe side*/ + const char *proxyuser = data->state.aptr.proxyuser ? + data->state.aptr.proxyuser : ""; + const char *proxypasswd = data->state.aptr.proxypasswd ? + data->state.aptr.proxypasswd : ""; + CURLcode result = CURLE_OUT_OF_MEMORY; + + conn->http_proxy.user = strdup(proxyuser); + if(conn->http_proxy.user) { + conn->http_proxy.passwd = strdup(proxypasswd); + if(conn->http_proxy.passwd) + result = CURLE_OK; + else + Curl_safefree(conn->http_proxy.user); } - - result = Curl_urldecode(data, proxyuser, 0, &conn->http_proxy.user, NULL, - FALSE); - if(!result) - result = Curl_urldecode(data, proxypasswd, 0, &conn->http_proxy.passwd, - NULL, FALSE); return result; } -#endif /* CURL_DISABLE_PROXY */ -/* - * parse_url_login() - * - * Parse the login details (user name, password and options) from the URL and - * strip them out of the host name - * - * Inputs: data->set.use_netrc (CURLOPT_NETRC) - * conn->host.name - * - * Outputs: (almost :- all currently undefined) - * conn->bits.user_passwd - non-zero if non-default passwords exist - * user - non-zero length if defined - * passwd - non-zero length if defined - * options - non-zero length if defined - * conn->host.name - remove user name and password - */ -static CURLcode parse_url_login(struct Curl_easy *data, - struct connectdata *conn, - char **user, char **passwd, char **options) +/* 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; - char *userp = NULL; - char *passwdp = NULL; - char *optionsp = NULL; - - /* At this point, we're hoping all the other special cases have - * been taken care of, so conn->host.name is at most - * [user[:password][;options]]@]hostname - * - * We need somewhere to put the embedded details, so do that first. - */ - - char *ptr = strchr(conn->host.name, '@'); - char *login = conn->host.name; - - DEBUGASSERT(!**user); - DEBUGASSERT(!**passwd); - DEBUGASSERT(!**options); - DEBUGASSERT(conn->handler); - - 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 */ - conn->host.name = ++ptr; - /* So the hostname is sane. Only bother interpreting the - * results if we could care. It could still be wasted - * work because it might be overtaken by the programmatically - * set user/passwd, but doing that first adds more cases here :-( - */ + /************************************************************* + * 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; + } - if(data->set.use_netrc == CURL_NETRC_REQUIRED) - 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; + } + } - /* We could use the login information in the URL so extract it. Only parse - options if the handler says we should. */ - result = parse_login_details(login, ptr - login - 1, - &userp, &passwdp, - (conn->handler->flags & PROTOPT_URLOPTIONS)? - &optionsp:NULL); - if(result) - 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(userp) { - char *newname; + 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); + } + } - /* We have a user in the URL */ - conn->bits.userpwd_in_url = TRUE; - conn->bits.user_passwd = TRUE; /* enable user+password */ + if(Curl_check_noproxy(conn->host.name, data->set.str[STRING_NOPROXY] ? + data->set.str[STRING_NOPROXY] : no_proxy)) { + 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 */ + Curl_safefree(no_proxy); - /* Decode the user */ - result = Curl_urldecode(data, userp, 0, &newname, NULL, FALSE); - if(result) { - goto out; - } +#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 - free(*user); - *user = newname; + if(proxy && (!*proxy || (conn->handler->flags & PROTOPT_NONETWORK))) { + free(proxy); /* Do not bother with an empty proxy string or if the + protocol does not work with network */ + proxy = NULL; + } + if(socksproxy && (!*socksproxy || + (conn->handler->flags & PROTOPT_NONETWORK))) { + free(socksproxy); /* Do not bother with an empty socks proxy string or if + the protocol does not work with network */ + socksproxy = NULL; } - if(passwdp) { - /* We have a password in the URL so decode it */ - char *newpasswd; - result = Curl_urldecode(data, passwdp, 0, &newpasswd, NULL, FALSE); - if(result) { - goto out; + /*********************************************************************** + * 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; } - free(*passwd); - *passwd = newpasswd; - } + if(socksproxy) { + result = parse_proxy(data, conn, socksproxy, ptype); + /* parse_proxy copies the socks proxy string */ + Curl_safefree(socksproxy); + if(result) + goto out; + } - if(optionsp) { - /* We have an options list in the URL so decode it */ - char *newoptions; - result = Curl_urldecode(data, optionsp, 0, &newoptions, NULL, FALSE); - if(result) { + 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 */ } - free(*options); - *options = newoptions; + 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 are not 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(userp); - free(passwdp); - free(optionsp); +out: + free(socksproxy); + free(proxy); return result; } +#endif /* CURL_DISABLE_PROXY */ /* - * parse_login_details() + * Curl_parse_login_details() * - * This is used to parse a login string for user name, password and options in + * This is used to parse a login string for username, password and options in * the following formats: * * user @@ -5320,48 +2552,39 @@ static CURLcode parse_url_login(struct Curl_easy *data, * * Parameters: * - * login [in] - The login string. - * len [in] - The length of the login string. - * userp [in/out] - The address where a pointer to newly allocated memory + * login [in] - login string. + * len [in] - length of the login string. + * userp [in/out] - address where a pointer to newly allocated memory * holding the user will be stored upon completion. - * passdwp [in/out] - The address where a pointer to newly allocated memory + * passwdp [in/out] - address where a pointer to newly allocated memory * holding the password will be stored upon completion. - * optionsp [in/out] - The address where a pointer to newly allocated memory - * holding the options will be stored upon completion. + * optionsp [in/out] - OPTIONAL address where a pointer to newly allocated + * memory holding the options will be stored upon + * completion. * * Returns CURLE_OK on success. */ -static CURLcode parse_login_details(const char *login, const size_t len, - char **userp, char **passwdp, - char **optionsp) +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 = strchr(login, ':'); + DEBUGASSERT(userp); + DEBUGASSERT(passwdp); - /* Within the constraint of the login string */ - if(psep >= login + len) - psep = NULL; - } + /* Attempt to find the password separator */ + psep = memchr(login, ':', len); /* Attempt to find the options separator */ - if(optionsp) { - osep = strchr(login, ';'); - - /* Within the constraint of the login string */ - if(osep >= login + len) - osep = NULL; - } + if(optionsp) + osep = memchr(login, ';', len); /* Calculate the portion lengths */ ulen = (psep ? @@ -5369,64 +2592,40 @@ static CURLcode parse_login_details(const char *login, const size_t len, (osep ? (size_t)(osep - login) : len)); plen = (psep ? (osep && osep > psep ? (size_t)(osep - psep) : - (size_t)(login + len - psep)) - 1 : 0); + (size_t)(login + len - psep)) - 1 : 0); olen = (osep ? - (psep && psep > osep ? (size_t)(psep - osep) : - (size_t)(login + len - osep)) - 1 : 0); - - /* Allocate the user portion buffer */ - if(userp && ulen) { - ubuf = malloc(ulen + 1); - if(!ubuf) - result = CURLE_OUT_OF_MEMORY; - } - - /* Allocate the password portion buffer */ - if(!result && passwdp && plen) { - 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; - } - } + (psep && psep > osep ? (size_t)(psep - osep) : + (size_t)(login + len - osep)) - 1 : 0); - if(!result) { - /* Store the user portion if necessary */ - if(ubuf) { - memcpy(ubuf, login, ulen); - ubuf[ulen] = '\0'; - Curl_safefree(*userp); - *userp = ubuf; - } + /* Clone the user portion buffer, which can be zero length */ + ubuf = Curl_memdup0(login, ulen); + if(!ubuf) + goto error; - /* Store the password portion if necessary */ - if(pbuf) { - memcpy(pbuf, psep + 1, plen); - pbuf[plen] = '\0'; - Curl_safefree(*passwdp); - *passwdp = pbuf; - } + /* Clone the password portion buffer */ + if(psep) { + pbuf = Curl_memdup0(&psep[1], plen); + if(!pbuf) + goto error; + } - /* Store the options portion if necessary */ - if(obuf) { - memcpy(obuf, osep + 1, olen); - obuf[olen] = '\0'; - Curl_safefree(*optionsp); - *optionsp = obuf; + /* Allocate the options portion buffer */ + if(optionsp) { + char *obuf = NULL; + if(olen) { + obuf = Curl_memdup0(&osep[1], olen); + if(!obuf) + goto error; } + *optionsp = obuf; } - - return result; + *userp = ubuf; + *passwdp = pbuf; + return CURLE_OK; +error: + free(ubuf); + free(pbuf); + return CURLE_OUT_OF_MEMORY; } /************************************************************* @@ -5435,124 +2634,23 @@ static CURLcode parse_login_details(const char *login, const size_t len, * No matter if we use a proxy or not, we have to figure out the remote * port number of various reasons. * - * To be able to detect port number flawlessly, we must not confuse them - * IPv6-specified addresses in the [0::1] style. (RFC2732) - * - * The conn->host.name is currently [user:passwd@]host[:port] where host - * could be a hostname, IPv4 address or IPv6 address. - * * The port number embedded in the URL is replaced, if necessary. *************************************************************/ static CURLcode parse_remote_port(struct Curl_easy *data, struct connectdata *conn) { - char *portptr; - char endbracket; - - /* Note that at this point, the IPv6 address cannot contain any scope - suffix as that has already been removed in the parseurlandfillconn() - function */ - if((1 == sscanf(conn->host.name, "[%*45[0123456789abcdefABCDEF:.]%c", - &endbracket)) && - (']' == endbracket)) { - /* this is a RFC2732-style specified IP-address */ - conn->bits.ipv6_ip = TRUE; - - conn->host.name++; /* skip over the starting bracket */ - portptr = strchr(conn->host.name, ']'); - if(portptr) { - *portptr++ = '\0'; /* zero terminate, killing the bracket */ - if(':' != *portptr) - portptr = NULL; /* no port number available */ - } - } - else { -#ifdef ENABLE_IPV6 - struct in6_addr in6; - if(Curl_inet_pton(AF_INET6, conn->host.name, &in6) > 0) { - /* This is a numerical IPv6 address, meaning this is a wrongly formatted - URL */ - failf(data, "IPv6 numerical address used in URL without brackets"); - return CURLE_URL_MALFORMAT; - } -#endif - - portptr = strrchr(conn->host.name, ':'); - } if(data->set.use_port && data->state.allow_port) { - /* if set, we use this and ignore the port possibly given in the URL */ - conn->remote_port = (unsigned short)data->set.use_port; - if(portptr) - *portptr = '\0'; /* cut off the name there anyway - if there was a port - number - since the port number is to be ignored! */ - if(conn->bits.httpproxy) { - /* we need to create new URL with the new port number */ - char *url; - char type[12]=""; - - if(conn->bits.type_set) - snprintf(type, sizeof(type), ";type=%c", - data->set.prefer_ascii?'A': - (data->set.ftp_list_only?'D':'I')); - - /* - * This synthesized URL isn't always right--suffixes like ;type=A are - * stripped off. It would be better to work directly from the original - * URL and simply replace the port part of it. - */ - url = aprintf("%s://%s%s%s:%hu%s%s%s", conn->given->scheme, - conn->bits.ipv6_ip?"[":"", conn->host.name, - conn->bits.ipv6_ip?"]":"", conn->remote_port, - data->state.slash_removed?"/":"", data->state.path, - type); - if(!url) - return CURLE_OUT_OF_MEMORY; - - if(data->change.url_alloc) { - Curl_safefree(data->change.url); - data->change.url_alloc = FALSE; - } - - data->change.url = url; - data->change.url_alloc = TRUE; - } - } - else if(portptr) { - /* no CURLOPT_PORT given, extract the one from the URL */ - - char *rest; - long port; - - port=strtol(portptr+1, &rest, 10); /* Port number must be decimal */ - - if((port < 0) || (port > 0xffff)) { - /* Single unix standard says port numbers are 16 bits long */ - failf(data, "Port number out of range"); - return CURLE_URL_MALFORMAT; - } - - else if(rest != &portptr[1]) { - *portptr = '\0'; /* cut off the name there */ - conn->remote_port = curlx_ultous(port); - } - else { - if(rest[0]) { - failf(data, "Illegal port number"); - return CURLE_URL_MALFORMAT; - } - /* 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 and Chrome both do that. */ - *portptr = '\0'; - } + /* 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; } - /* only if remote_port was not already parsed off the URL we use the - default port number */ - if(conn->remote_port < 0) - conn->remote_port = (unsigned short)conn->given->defport; - return CURLE_OK; } @@ -5561,22 +2659,12 @@ static CURLcode parse_remote_port(struct Curl_easy *data, * option or a .netrc file, if applicable. */ static CURLcode override_login(struct Curl_easy *data, - struct connectdata *conn, - char **userp, char **passwdp, char **optionsp) + struct connectdata *conn) { - if(data->set.str[STRING_USERNAME]) { - free(*userp); - *userp = strdup(data->set.str[STRING_USERNAME]); - if(!*userp) - return CURLE_OUT_OF_MEMORY; - } - - if(data->set.str[STRING_PASSWORD]) { - free(*passwdp); - *passwdp = strdup(data->set.str[STRING_PASSWORD]); - if(!*passwdp) - return CURLE_OUT_OF_MEMORY; - } + CURLUcode uc; + char **userp = &conn->user; + char **passwdp = &conn->passwd; + char **optionsp = &conn->options; if(data->set.str[STRING_OPTIONS]) { free(*optionsp); @@ -5585,26 +2673,91 @@ static CURLcode override_login(struct Curl_easy *data, 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 != CURL_NETRC_IGNORED) { - int ret = Curl_parsenetrc(conn->host.name, - userp, passwdp, - data->set.str[STRING_NETRC_FILE]); + if(data->set.use_netrc && !data->set.str[STRING_USERNAME]) { + int ret; + bool url_provided = FALSE; + + if(data->state.aptr.user) { + /* there was a username 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 " - DOT_CHAR "netrc file; using defaults\n", - conn->host.name); + 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) { - return CURLE_OUT_OF_MEMORY; + 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 - conn->bits.user_passwd = TRUE; /* enable user+password */ + /* 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; } } @@ -5612,46 +2765,33 @@ static CURLcode override_login(struct Curl_easy *data, } /* - * Set the login details so they're available in the connection + * Set the login details so they are available in the connection */ -static CURLcode set_login(struct connectdata *conn, - const char *user, const char *passwd, - const char *options) +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) && !conn->bits.user_passwd) { - /* Store the default user */ - conn->user = strdup(CURL_DEFAULT_USER); - - /* Store the default password */ - if(conn->user) - conn->passwd = strdup(CURL_DEFAULT_PASSWORD); - else - conn->passwd = NULL; - - /* This is the default password, so DON'T set conn->bits.user_passwd */ - } + if((conn->handler->flags & PROTOPT_NEEDSPWD) && !data->state.aptr.user) + ; else { - /* Store the user, zero-length if not set */ - conn->user = strdup(user); - - /* Store the password (only if user is present), zero-length if not set */ - if(conn->user) - conn->passwd = strdup(passwd); - else - conn->passwd = NULL; + setuser = ""; + setpasswd = ""; + } + /* Store the default user */ + if(!conn->user) { + conn->user = strdup(setuser); + if(!conn->user) + return CURLE_OUT_OF_MEMORY; } - if(!conn->user || !conn->passwd) - result = CURLE_OUT_OF_MEMORY; - - /* Store the options, null if not set */ - if(!result && options[0]) { - conn->options = strdup(options); - - if(!conn->options) + /* Store the default password */ + if(!conn->passwd) { + conn->passwd = strdup(setpasswd); + if(!conn->passwd) result = CURLE_OUT_OF_MEMORY; } @@ -5673,6 +2813,7 @@ static CURLcode parse_connect_to_host_port(struct Curl_easy *data, char *host_portno; char *portptr; int port = -1; + CURLcode result = CURLE_OK; #if defined(CURL_DISABLE_VERBOSE_STRINGS) (void) data; @@ -5695,13 +2836,14 @@ static CURLcode parse_connect_to_host_port(struct Curl_easy *data, /* detect and extract RFC6874-style IPv6-addresses */ if(*hostptr == '[') { +#ifdef USE_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.\n"); + 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 == '-') || @@ -5712,46 +2854,51 @@ static CURLcode parse_connect_to_host_port(struct Curl_easy *data, /* yeps, it ended nicely with a bracket as well */ *ptr++ = '\0'; else - infof(data, "Invalid IPv6 address format\n"); + 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 + /* Note that if this did not end with a bracket, we still advanced the + * hostptr first, but I cannot 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 = '\0'; /* cut off number from hostname */ host_portno++; if(*host_portno) { long portparse = strtol(host_portno, &endp, 10); if((endp && *endp) || (portparse < 0) || (portparse > 65535)) { - infof(data, "No valid port number in connect to host string (%s)\n", + failf(data, "No valid port number in connect to host string (%s)", host_portno); - hostptr = NULL; - port = -1; + result = CURLE_SETOPT_OPTION_SYNTAX; + goto error; } else port = (int)portparse; /* we know it will fit */ } } - /* now, clone the cleaned host name */ - if(hostptr) { - *hostname_result = strdup(hostptr); - if(!*hostname_result) { - free(host_dup); - return CURLE_OUT_OF_MEMORY; - } + /* now, clone the cleaned hostname */ + DEBUGASSERT(hostptr); + *hostname_result = strdup(hostptr); + if(!*hostname_result) { + result = CURLE_OUT_OF_MEMORY; + goto error; } *port_result = port; +error: free(host_dup); - return CURLE_OK; + return result; } /* @@ -5847,7 +2994,7 @@ static CURLcode parse_connect_to_slist(struct Curl_easy *data, conn->conn_to_host.name = host; conn->bits.conn_to_host = TRUE; - infof(data, "Connecting to hostname: %s\n", host); + infof(data, "Connecting to hostname: %s", host); } else { /* no "connect to host" */ @@ -5858,7 +3005,7 @@ static CURLcode parse_connect_to_slist(struct Curl_easy *data, if(port >= 0) { conn->conn_to_port = port; conn->bits.conn_to_port = TRUE; - infof(data, "Connecting to port: %d\n", port); + infof(data, "Connecting to port: %d", port); } else { /* no "connect to port" */ @@ -5869,9 +3016,159 @@ static CURLcode parse_connect_to_slist(struct Curl_easy *data, conn_to_host = conn_to_host->next; } +#ifndef CURL_DISABLE_ALTSVC + if(data->asi && !host && (port == -1) && + ((conn->handler->protocol == CURLPROTO_HTTPS) || +#ifdef DEBUGBUILD + /* 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 = ALPN_none; + bool use_alt_svc = FALSE; + bool hit = FALSE; + struct altsvc *as = NULL; + const int allowed_versions = ( ALPN_h1 +#ifdef USE_HTTP2 + | ALPN_h2 +#endif +#ifdef USE_HTTP3 + | ALPN_h3 +#endif + ) & data->asi->flags; + static enum alpnid alpn_ids[] = { +#ifdef USE_HTTP3 + ALPN_h3, +#endif +#ifdef USE_HTTP2 + ALPN_h2, +#endif + ALPN_h1, + }; + size_t i; + + switch(data->state.httpwant) { + case CURL_HTTP_VERSION_1_0: + break; + case CURL_HTTP_VERSION_1_1: + use_alt_svc = TRUE; + srcalpnid = ALPN_h1; /* only regard alt-svc advice for http/1.1 */ + break; + case CURL_HTTP_VERSION_2_0: + use_alt_svc = TRUE; + srcalpnid = ALPN_h2; /* only regard alt-svc advice for h2 */ + break; + case CURL_HTTP_VERSION_2_PRIOR_KNOWLEDGE: + break; + case CURL_HTTP_VERSION_3: + use_alt_svc = TRUE; + srcalpnid = ALPN_h3; /* only regard alt-svc advice for h3 */ + break; + case CURL_HTTP_VERSION_3ONLY: + break; + default: /* no specific HTTP version wanted, look at all of alt-svc */ + use_alt_svc = TRUE; + srcalpnid = ALPN_none; + break; + } + if(!use_alt_svc) + return CURLE_OK; + + host = conn->host.rawalloc; + DEBUGF(infof(data, "check Alt-Svc for host %s", host)); + if(srcalpnid == ALPN_none) { + /* scan all alt-svc protocol ids in order or relevance */ + for(i = 0; !hit && (i < ARRAYSIZE(alpn_ids)); ++i) { + srcalpnid = alpn_ids[i]; + hit = Curl_altsvc_lookup(data->asi, + srcalpnid, host, conn->remote_port, /* from */ + &as /* to */, + allowed_versions); + } + } + else { + /* look for a specific alt-svc protocol id */ + 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: /* should not 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->refcount = 1; /* connection is the only one holding this */ + conn->dns_entry = hostaddr; + return CURLE_OK; +} +#endif + /************************************************************* * Resolve the address of the server or proxy *************************************************************/ @@ -5879,208 +3176,148 @@ static CURLcode resolve_server(struct Curl_easy *data, struct connectdata *conn, bool *async) { - CURLcode result=CURLE_OK; - time_t timeout_ms = Curl_timeleft(data, NULL, TRUE); - - /************************************************************* - * Resolve the name of the server or proxy - *************************************************************/ - if(conn->bits.reuse) - /* We're reusing the connection - no need to resolve anything, and - fix_hostname() was called already in create_conn() for the re-use - case. */ - *async = FALSE; - - else { - /* this is a fresh connect */ - int rc; - struct Curl_dns_entry *hostaddr; - + struct hostname *ehost; + timediff_t timeout_ms = Curl_timeleft(data, NULL, TRUE); + const char *peertype = "host"; + int rc; #ifdef USE_UNIX_SOCKETS - if(conn->unix_domain_socket) { - /* 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 */ - const char *path = conn->unix_domain_socket; - - hostaddr = calloc(1, sizeof(struct Curl_dns_entry)); - if(!hostaddr) - result = CURLE_OUT_OF_MEMORY; - else { - bool longpath = FALSE; - hostaddr->addr = Curl_unix2addr(path, &longpath, - conn->abstract_unix_socket); - if(hostaddr->addr) - hostaddr->inuse++; - else { - /* Long paths are not supported for now */ - if(longpath) { - failf(data, "Unix socket path too long: '%s'", path); - result = CURLE_COULDNT_RESOLVE_HOST; - } - else - result = CURLE_OUT_OF_MEMORY; - free(hostaddr); - hostaddr = NULL; - } - } - } - else -#endif - if(!conn->bits.proxy) { - struct hostname *connhost; - if(conn->bits.conn_to_host) - connhost = &conn->conn_to_host; - else - connhost = &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. */ - if(conn->bits.conn_to_port) - conn->port = conn->conn_to_port; - else - conn->port = conn->remote_port; + char *unix_path = conn->unix_domain_socket; - /* Resolve target host right on */ - rc = Curl_resolv_timeout(conn, connhost->name, (int)conn->port, - &hostaddr, timeout_ms); - if(rc == CURLRESOLV_PENDING) - *async = TRUE; - - else if(rc == CURLRESOLV_TIMEDOUT) - result = CURLE_OPERATION_TIMEDOUT; - - else if(!hostaddr) { - failf(data, "Couldn't resolve host '%s'", connhost->dispname); - result = CURLE_COULDNT_RESOLVE_HOST; - /* don't return yet, we need to clean up the timeout first */ - } - } - else { - /* This is a proxy that hasn't been resolved yet. */ +#ifndef CURL_DISABLE_PROXY + if(!unix_path && CONN_IS_PROXIED(conn) && 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 - struct hostname * const host = conn->bits.socksproxy ? - &conn->socks_proxy.host : &conn->http_proxy.host; + if(unix_path) { + /* TODO, this only works if previous transport is TRNSPRT_TCP. Check it? */ + conn->transport = TRNSPRT_UNIX; + return resolve_unix(data, conn, unix_path); + } +#endif - /* resolve proxy */ - rc = Curl_resolv_timeout(conn, host->name, (int)conn->port, - &hostaddr, timeout_ms); + DEBUGASSERT(conn->dns_entry == NULL); - if(rc == CURLRESOLV_PENDING) - *async = TRUE; +#ifndef CURL_DISABLE_PROXY + if(CONN_IS_PROXIED(conn)) { + ehost = conn->bits.socksproxy ? &conn->socks_proxy.host : + &conn->http_proxy.host; + peertype = "proxy"; + } + else +#endif + { + ehost = 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->primary.remote_port = conn->bits.conn_to_port ? conn->conn_to_port : + conn->remote_port; + } - else if(rc == CURLRESOLV_TIMEDOUT) - result = CURLE_OPERATION_TIMEDOUT; + /* Resolve target host right on */ + conn->hostname_resolve = strdup(ehost->name); + if(!conn->hostname_resolve) + return CURLE_OUT_OF_MEMORY; - else if(!hostaddr) { - failf(data, "Couldn't resolve proxy '%s'", host->dispname); - result = CURLE_COULDNT_RESOLVE_PROXY; - /* don't return yet, we need to clean up the timeout first */ - } - } - DEBUGASSERT(conn->dns_entry == NULL); - conn->dns_entry = hostaddr; + rc = Curl_resolv_timeout(data, conn->hostname_resolve, + conn->primary.remote_port, + &conn->dns_entry, timeout_ms); + if(rc == CURLRESOLV_PENDING) + *async = TRUE; + else if(rc == CURLRESOLV_TIMEDOUT) { + failf(data, "Failed to resolve %s '%s' with timeout after %" + FMT_TIMEDIFF_T " ms", peertype, ehost->dispname, + Curl_timediff(Curl_now(), data->progress.t_startsingle)); + return CURLE_OPERATION_TIMEDOUT; + } + else if(!conn->dns_entry) { + failf(data, "Could not resolve %s: %s", peertype, ehost->dispname); + return CURLE_COULDNT_RESOLVE_HOST; } - return result; + return CURLE_OK; } /* - * Cleanup the connection just allocated before we can move along and use the - * previously existing one. All relevant data is copied over and old_conn is - * ready for freeing once this function returns. + * 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 connectdata *old_conn, - struct connectdata *conn) +static void reuse_conn(struct Curl_easy *data, + struct connectdata *temp, + struct connectdata *existing) { - free_fixed_hostname(&old_conn->http_proxy.host); - free_fixed_hostname(&old_conn->socks_proxy.host); - - free(old_conn->http_proxy.host.rawalloc); - free(old_conn->socks_proxy.host.rawalloc); - - /* free the SSL config struct from this connection struct as this was - allocated in vain and is targeted for destruction */ - Curl_free_primary_ssl_config(&old_conn->ssl_config); - Curl_free_primary_ssl_config(&old_conn->proxy_ssl_config); - - conn->data = old_conn->data; - - /* get the user+password information from the old_conn struct since it may - * be new for this request even when we re-use an existing connection */ - conn->bits.user_passwd = old_conn->bits.user_passwd; - if(conn->bits.user_passwd) { - /* use the new user name and password though */ - Curl_safefree(conn->user); - Curl_safefree(conn->passwd); - conn->user = old_conn->user; - conn->passwd = old_conn->passwd; - old_conn->user = NULL; - old_conn->passwd = NULL; + /* 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 username 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; } - conn->bits.proxy_user_passwd = old_conn->bits.proxy_user_passwd; - if(conn->bits.proxy_user_passwd) { - /* use the new proxy user name and proxy password though */ - Curl_safefree(conn->http_proxy.user); - Curl_safefree(conn->socks_proxy.user); - Curl_safefree(conn->http_proxy.passwd); - Curl_safefree(conn->socks_proxy.passwd); - conn->http_proxy.user = old_conn->http_proxy.user; - conn->socks_proxy.user = old_conn->socks_proxy.user; - conn->http_proxy.passwd = old_conn->http_proxy.passwd; - conn->socks_proxy.passwd = old_conn->socks_proxy.passwd; - old_conn->http_proxy.user = NULL; - old_conn->socks_proxy.user = NULL; - old_conn->http_proxy.passwd = NULL; - old_conn->socks_proxy.passwd = NULL; - } - - /* host can change, when doing keepalive with a proxy or if the case is - different this time etc */ - free_fixed_hostname(&conn->host); - free_fixed_hostname(&conn->conn_to_host); - Curl_safefree(conn->host.rawalloc); - Curl_safefree(conn->conn_to_host.rawalloc); - conn->host=old_conn->host; - conn->bits.conn_to_host = old_conn->bits.conn_to_host; - conn->conn_to_host = old_conn->conn_to_host; - conn->bits.conn_to_port = old_conn->bits.conn_to_port; - conn->conn_to_port = old_conn->conn_to_port; - - /* persist connection info in session handle */ - Curl_persistconninfo(conn); - - conn_reset_all_postponed_data(old_conn); /* free buffers */ - conn_reset_all_postponed_data(conn); /* reset unprocessed data */ - - /* re-use init */ - conn->bits.reuse = TRUE; /* yes, we're re-using here */ - - Curl_safefree(old_conn->user); - Curl_safefree(old_conn->passwd); - Curl_safefree(old_conn->http_proxy.user); - Curl_safefree(old_conn->socks_proxy.user); - Curl_safefree(old_conn->http_proxy.passwd); - Curl_safefree(old_conn->socks_proxy.passwd); - Curl_safefree(old_conn->localdev); - - Curl_llist_destroy(old_conn->send_pipe, NULL); - Curl_llist_destroy(old_conn->recv_pipe, NULL); - - old_conn->send_pipe = NULL; - old_conn->recv_pipe = NULL; - - Curl_safefree(old_conn->master_buffer); - -#ifdef USE_UNIX_SOCKETS - Curl_safefree(old_conn->unix_domain_socket); -#endif +#ifndef CURL_DISABLE_PROXY + existing->bits.proxy_user_passwd = temp->bits.proxy_user_passwd; + if(existing->bits.proxy_user_passwd) { + /* use the new proxy username 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 cpool 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 are reusing here */ + + Curl_conn_free(data, temp); } /** - * create_conn() sets up a new connectdata struct, or re-uses an already - * existing one, and resolves host name. + * create_conn() sets up a new connectdata struct, or reuses an already + * existing one, and resolves hostname. * * 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 @@ -6091,7 +3328,6 @@ static void reuse_conn(struct connectdata *old_conn, * @param async is set TRUE when an async DNS resolution is pending * @see Curl_setup_conn() * - * *NOTE* this function assigns the conn->data pointer! */ static CURLcode create_conn(struct Curl_easy *data, @@ -6100,29 +3336,19 @@ static CURLcode create_conn(struct Curl_easy *data, { CURLcode result = CURLE_OK; struct connectdata *conn; - struct connectdata *conn_temp = NULL; - size_t urllen; - char *user = NULL; - char *passwd = NULL; - char *options = NULL; + struct connectdata *existing = NULL; bool reuse; - char *proxy = NULL; - char *socksproxy = NULL; - char *no_proxy = NULL; - bool prot_missing = FALSE; 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->change.url) { + if(!data->state.url) { result = CURLE_URL_MALFORMAT; goto out; } @@ -6141,271 +3367,45 @@ static CURLcode create_conn(struct Curl_easy *data, /* 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; - - /* This initing continues below, see the comment "Continue connectdata - * initialization here" */ - - /*********************************************************** - * We need to allocate memory to store the path in. We get the size of the - * full URL to be sure, and we need to make it at least 256 bytes since - * other parts of the code will rely on this fact - ***********************************************************/ -#define LEAST_PATH_ALLOC 256 - urllen=strlen(data->change.url); - if(urllen < LEAST_PATH_ALLOC) - urllen=LEAST_PATH_ALLOC; - - /* - * We malloc() the buffers below urllen+2 to make room for 2 possibilities: - * 1 - an extra terminating zero - * 2 - an extra slash (in case a syntax like "www.host.com?moo" is used) - */ - - Curl_safefree(data->state.pathbuffer); - data->state.path = NULL; - - data->state.pathbuffer = malloc(urllen+2); - if(NULL == data->state.pathbuffer) { - result = CURLE_OUT_OF_MEMORY; /* really bad error */ - goto out; - } - data->state.path = data->state.pathbuffer; - - conn->host.rawalloc = malloc(urllen+2); - if(NULL == conn->host.rawalloc) { - Curl_safefree(data->state.pathbuffer); - data->state.path = NULL; - result = CURLE_OUT_OF_MEMORY; - goto out; - } - - conn->host.name = conn->host.rawalloc; - conn->host.name[0] = 0; - - user = strdup(""); - passwd = strdup(""); - options = strdup(""); - if(!user || !passwd || !options) { - result = CURLE_OUT_OF_MEMORY; - goto out; - } - - result = parseurlandfillconn(data, conn, &prot_missing, &user, &passwd, - &options); - if(result) - goto out; - - /************************************************************* - * No protocol part in URL was used, add it! - *************************************************************/ - if(prot_missing) { - /* We're guessing prefixes here and if we're told to use a proxy or if - we're gonna follow a Location: later or... then we need the protocol - part added so that we have a valid URL. */ - char *reurl; - char *ch_lower; - - reurl = aprintf("%s://%s", conn->handler->scheme, data->change.url); - - if(!reurl) { - result = CURLE_OUT_OF_MEMORY; - goto out; - } - - /* Change protocol prefix to lower-case */ - for(ch_lower = reurl; *ch_lower != ':'; ch_lower++) - *ch_lower = (char)TOLOWER(*ch_lower); - - if(data->change.url_alloc) { - Curl_safefree(data->change.url); - data->change.url_alloc = FALSE; - } - - data->change.url = reurl; - data->change.url_alloc = TRUE; /* free this later */ - } - - /************************************************************* - * If the protocol can't handle url query strings, then cut - * off the unhandable part - *************************************************************/ - if((conn->given->flags&PROTOPT_NOURLQUERY)) { - char *path_q_sep = strchr(conn->data->state.path, '?'); - if(path_q_sep) { - /* according to rfc3986, allow the query (?foo=bar) - also on protocols that can't handle it. - - cut the string-part after '?' - */ - - /* terminate the string */ - path_q_sep[0] = 0; - } - } - - 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; - } - } - -#ifndef CURL_DISABLE_PROXY - /************************************************************* - * 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; - } + *in_connect = conn; - /************************************************************* - * 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(NULL == proxy) { - failf(data, "memory shortage"); + 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_PRE_PROXY]) { - socksproxy = strdup(data->set.str[STRING_PRE_PROXY]); - /* if global socks proxy is set, this is it */ - if(NULL == socksproxy) { - failf(data, "memory shortage"); + 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; } } - no_proxy = curl_getenv("no_proxy"); - if(!no_proxy) - no_proxy = curl_getenv("NO_PROXY"); - - if(check_noproxy(conn->host.name, data->set.str[STRING_NOPROXY]) || - (!data->set.str[STRING_NOPROXY] && - check_noproxy(conn->host.name, no_proxy))) { - Curl_safefree(proxy); - Curl_safefree(socksproxy); - } - else if(!proxy && !socksproxy) -#ifndef CURL_DISABLE_HTTP - /* if the host is not in the noproxy list, detect proxy. */ - proxy = detect_proxy(conn); -#else /* !CURL_DISABLE_HTTP */ - proxy = NULL; -#endif /* CURL_DISABLE_HTTP */ - - Curl_safefree(no_proxy); - #ifdef USE_UNIX_SOCKETS if(data->set.str[STRING_UNIX_SOCKET_PATH]) { - if(proxy) { - free(proxy); /* Unix domain sockets cannot be proxied, so disable it */ - proxy = NULL; - } conn->unix_domain_socket = strdup(data->set.str[STRING_UNIX_SOCKET_PATH]); - if(conn->unix_domain_socket == NULL) { + if(!conn->unix_domain_socket) { result = CURLE_OUT_OF_MEMORY; goto out; } - conn->abstract_unix_socket = data->set.abstract_unix_socket; + conn->bits.abstract_unix_socket = data->set.abstract_unix_socket; } #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 re-use an existing - * connection that may exist registered to the same proxy host. - ***********************************************************************/ - if(proxy || socksproxy) { - if(proxy) { - result = parse_proxy(data, conn, proxy, conn->http_proxy.proxytype); - Curl_safefree(proxy); /* parse_proxy copies the proxy string */ - if(result) - goto out; - } - - if(socksproxy) { - result = parse_proxy(data, conn, socksproxy, - conn->socks_proxy.proxytype); - /* 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 a 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 not already - compatible - if it isn't tunneling through */ - if(!(conn->handler->protocol & PROTO_FAMILY_HTTP) && - !conn->bits.tunnel_proxy) - conn->handler = &Curl_handler_http; - - conn->bits.httpproxy = TRUE; -#endif - } - else { - conn->bits.httpproxy = FALSE; /* not a 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; - } - -#endif /* CURL_DISABLE_PROXY */ + /* 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 @@ -6413,6 +3413,7 @@ static CURLcode create_conn(struct Curl_easy *data, *************************************************************/ 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 @@ -6421,12 +3422,13 @@ static CURLcode create_conn(struct Curl_easy *data, if(result) goto out; - /* Check for overridden login details and set them accordingly so they + /* 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, &user, &passwd, &options); + result = override_login(data, conn); if(result) goto out; - result = set_login(conn, user, passwd, options); + + result = set_login(data, conn); /* default credentials */ if(result) goto out; @@ -6439,19 +3441,29 @@ static CURLcode create_conn(struct Curl_easy *data, goto out; /************************************************************* - * IDN-fix the hostnames + * IDN-convert the proxy hostnames *************************************************************/ - fix_hostname(conn, &conn->host); - if(conn->bits.conn_to_host) - fix_hostname(conn, &conn->conn_to_host); - if(conn->bits.httpproxy) - fix_hostname(conn, &conn->http_proxy.host); - if(conn->bits.socksproxy) - fix_hostname(conn, &conn->socks_proxy.host); +#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-fixed. + * Do this after the hostnames have been IDN-converted. *************************************************************/ if(conn->bits.conn_to_host && strcasecompare(conn->conn_to_host.name, conn->host.name)) { @@ -6466,6 +3478,7 @@ static CURLcode create_conn(struct Curl_easy *data, 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. @@ -6473,24 +3486,18 @@ static CURLcode create_conn(struct Curl_easy *data, 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(conn); + result = setup_connection_internals(data, conn); if(result) goto out; - conn->recv[FIRSTSOCKET] = Curl_recv_plain; - conn->send[FIRSTSOCKET] = Curl_send_plain; - conn->recv[SECONDARYSOCKET] = Curl_recv_plain; - conn->send[SECONDARYSOCKET] = Curl_send_plain; - - conn->bits.tcp_fastopen = data->set.tcp_fastopen; - /*********************************************************************** - * file: is a special case in that it doesn't need a network connection + * file: is a special case in that it does not need a network connection ***********************************************************************/ #ifndef CURL_DISABLE_FILE if(conn->handler->flags & PROTOPT_NONETWORK) { @@ -6498,14 +3505,17 @@ static CURLcode create_conn(struct Curl_easy *data, /* 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); - result = conn->handler->connect_it(conn, &done); + data->info.conn_scheme = conn->handler->scheme; + /* conn_protocol can only provide "old" protocols */ + data->info.conn_protocol = (conn->handler->protocol) & CURLPROTO_MASK; + result = conn->handler->connect_it(data, &done); - /* Setup a "faked" transfer that'll do nothing */ + /* Setup a "faked" transfer that will do nothing */ if(!result) { - conn->data = data; - conn->bits.tcpconnect[FIRSTSOCKET] = TRUE; /* we are "connected */ - - Curl_conncache_add_conn(data->state.conn_cache, conn); + Curl_attach_connection(data, conn); + result = Curl_cpool_add_conn(data, conn); + if(result) + goto out; /* * Setup whatever necessary for a resumed transfer @@ -6514,12 +3524,10 @@ static CURLcode create_conn(struct Curl_easy *data, if(result) { DEBUGASSERT(conn->handler->done); /* we ignore the return code for the protocol-specific DONE */ - (void)conn->handler->done(conn, result, FALSE); + (void)conn->handler->done(data, result, FALSE); goto out; } - - Curl_setup_transfer(conn, -1, -1, FALSE, NULL, /* no download */ - -1, NULL); /* no upload */ + Curl_xfer_setup_nop(data); } /* since we skip do_init() */ @@ -6529,184 +3537,103 @@ static CURLcode create_conn(struct Curl_easy *data, } #endif - /* Get a cloned copy of the SSL config situation stored in the - connection struct. But to get this going nicely, we must first make - sure that the strings in the master copy are pointing to the correct - strings in the session handle strings array! - - Keep in mind that the pointers in the master copy are pointing to strings - that will be freed as part of the Curl_easy struct, but all cloned - copies will be separately allocated. - */ - data->set.ssl.primary.CApath = data->set.str[STRING_SSL_CAPATH_ORIG]; - data->set.proxy_ssl.primary.CApath = data->set.str[STRING_SSL_CAPATH_PROXY]; - data->set.ssl.primary.CAfile = data->set.str[STRING_SSL_CAFILE_ORIG]; - data->set.proxy_ssl.primary.CAfile = data->set.str[STRING_SSL_CAFILE_PROXY]; - data->set.ssl.primary.random_file = data->set.str[STRING_SSL_RANDOM_FILE]; - data->set.proxy_ssl.primary.random_file = - data->set.str[STRING_SSL_RANDOM_FILE]; - data->set.ssl.primary.egdsocket = data->set.str[STRING_SSL_EGDSOCKET]; - data->set.proxy_ssl.primary.egdsocket = data->set.str[STRING_SSL_EGDSOCKET]; - data->set.ssl.primary.cipher_list = - data->set.str[STRING_SSL_CIPHER_LIST_ORIG]; - data->set.proxy_ssl.primary.cipher_list = - data->set.str[STRING_SSL_CIPHER_LIST_PROXY]; - - data->set.ssl.CRLfile = data->set.str[STRING_SSL_CRLFILE_ORIG]; - data->set.proxy_ssl.CRLfile = data->set.str[STRING_SSL_CRLFILE_PROXY]; - data->set.ssl.issuercert = data->set.str[STRING_SSL_ISSUERCERT_ORIG]; - data->set.proxy_ssl.issuercert = data->set.str[STRING_SSL_ISSUERCERT_PROXY]; - data->set.ssl.cert = data->set.str[STRING_CERT_ORIG]; - data->set.proxy_ssl.cert = data->set.str[STRING_CERT_PROXY]; - data->set.ssl.cert_type = data->set.str[STRING_CERT_TYPE_ORIG]; - data->set.proxy_ssl.cert_type = data->set.str[STRING_CERT_TYPE_PROXY]; - data->set.ssl.key = data->set.str[STRING_KEY_ORIG]; - data->set.proxy_ssl.key = data->set.str[STRING_KEY_PROXY]; - data->set.ssl.key_type = data->set.str[STRING_KEY_TYPE_ORIG]; - data->set.proxy_ssl.key_type = data->set.str[STRING_KEY_TYPE_PROXY]; - data->set.ssl.key_passwd = data->set.str[STRING_KEY_PASSWD_ORIG]; - data->set.proxy_ssl.key_passwd = data->set.str[STRING_KEY_PASSWD_PROXY]; - data->set.ssl.primary.clientcert = data->set.str[STRING_CERT_ORIG]; - data->set.proxy_ssl.primary.clientcert = data->set.str[STRING_CERT_PROXY]; -#ifdef USE_TLS_SRP - data->set.ssl.username = data->set.str[STRING_TLSAUTH_USERNAME_ORIG]; - data->set.proxy_ssl.username = data->set.str[STRING_TLSAUTH_USERNAME_PROXY]; - data->set.ssl.password = data->set.str[STRING_TLSAUTH_PASSWORD_ORIG]; - data->set.proxy_ssl.password = data->set.str[STRING_TLSAUTH_PASSWORD_PROXY]; -#endif - - if(!Curl_clone_primary_ssl_config(&data->set.ssl.primary, - &conn->ssl_config)) { - result = CURLE_OUT_OF_MEMORY; - goto out; - } + /* Setup filter for network connections */ + conn->recv[FIRSTSOCKET] = Curl_cf_recv; + conn->send[FIRSTSOCKET] = Curl_cf_send; + conn->recv[SECONDARYSOCKET] = Curl_cf_recv; + conn->send[SECONDARYSOCKET] = Curl_cf_send; + conn->bits.tcp_fastopen = data->set.tcp_fastopen; - if(!Curl_clone_primary_ssl_config(&data->set.proxy_ssl.primary, - &conn->proxy_ssl_config)) { - result = CURLE_OUT_OF_MEMORY; + /* 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); + /* FIXME: do we really want to run this every time we add a transfer? */ + Curl_cpool_prune_dead(data); /************************************************************* * Check the current list of connections to see if we can - * re-use an already existing one or if we have to create a + * 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 re-used connection - already (which happens due to follow-location or during a HTTP - authentication phase). */ - if(data->set.reuse_fresh && !data->state.this_is_a_follow) + 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, &conn_temp, &force_reuse, &waitpipe); - - /* If we found a reusable connection, we may still want to - open a new connection if we are pipelining. */ - if(reuse && !force_reuse && IsPipeliningPossible(data, conn_temp)) { - size_t pipelen = conn_temp->send_pipe->size + conn_temp->recv_pipe->size; - if(pipelen > 0) { - infof(data, "Found connection %ld, with requests in the pipe (%zu)\n", - conn_temp->connection_id, pipelen); - - if(conn_temp->bundle->num_connections < max_host_connections && - data->state.conn_cache->num_connections < max_total_connections) { - /* We want a new connection anyway */ - reuse = FALSE; - - infof(data, "We can reuse, but we want a new connection anyway\n"); - } - } - } + reuse = ConnectionExists(data, conn, &existing, &force_reuse, &waitpipe); if(reuse) { /* - * We already have a connection for this, we got the former connection - * in the conn_temp variable and thus we need to cleanup the one we - * just allocated before we can move along and use the previously - * existing one. + * 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`. */ - conn_temp->inuse = TRUE; /* mark this as being in use so that no other - handle in a multi stack may nick it */ - reuse_conn(conn, conn_temp); - free(conn); /* we don't need this anymore */ - conn = conn_temp; + reuse_conn(data, conn, existing); + conn = existing; *in_connect = conn; - infof(data, "Re-using existing connection! (#%ld) with %s %s\n", - conn->connection_id, - conn->bits.proxy?"proxy":"host", +#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); + 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. */ - struct connectbundle *bundle = NULL; - if(conn->handler->flags & PROTOPT_ALPN_NPN) { + 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(data->set.ssl_enable_npn) - conn->bits.tls_enable_npn = TRUE; } if(waitpipe) - /* There is a connection that *might* become usable for pipelining + /* There is a connection that *might* become usable for multiplexing "soon", and we wait for that */ connections_available = FALSE; - else - bundle = Curl_conncache_find_bundle(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. Let's see if we can kill a connection. */ - conn_candidate = find_oldest_idle_connection_in_bundle(data, bundle); - - if(conn_candidate) { - /* Set the connection's owner correctly, then kill it */ - conn_candidate->data = data; - (void)Curl_disconnect(conn_candidate, /* dead_connection */ FALSE); - } - else { - infof(data, "No more connections allowed to host: %d\n", - max_host_connections); - connections_available = FALSE; - } - } - - if(connections_available && - (max_total_connections > 0) && - (data->state.conn_cache->num_connections >= max_total_connections)) { - struct connectdata *conn_candidate; - - /* The cache is full. Let's see if we can kill a connection. */ - conn_candidate = Curl_oldest_idle_connection(data); - - if(conn_candidate) { - /* Set the connection's owner correctly, then kill it */ - conn_candidate->data = data; - (void)Curl_disconnect(conn_candidate, /* dead_connection */ FALSE); - } - else { - infof(data, "No connections available in cache\n"); + else { + switch(Curl_cpool_check_limits(data, conn)) { + case CPOOL_LIMIT_DEST: + infof(data, "No more connections allowed to host"); connections_available = FALSE; + break; + case CPOOL_LIMIT_TOTAL: +#ifndef CURL_DISABLE_DOH + if(data->set.dohfor_mid >= 0) + infof(data, "Allowing DoH to override max connection limit"); + else +#endif + { + infof(data, "No connections available in cache"); + connections_available = FALSE; + } + break; + default: + break; } } if(!connections_available) { - infof(data, "No connections available.\n"); + infof(data, "No connections available."); - conn_free(conn); + Curl_conn_free(data, conn); *in_connect = NULL; result = CURLE_NO_CONNECTION_AVAILABLE; @@ -6717,32 +3644,38 @@ static CURLcode create_conn(struct Curl_easy *data, * This is a brand new connection, so let's store it in the connection * cache of ours! */ - Curl_conncache_add_conn(data->state.conn_cache, conn); + result = Curl_ssl_conn_config_init(data, conn); + if(result) { + DEBUGF(fprintf(stderr, "Error: init connection ssl config\n")); + goto out; + } + + Curl_attach_connection(data, conn); + result = Curl_cpool_add_conn(data, conn); + if(result) + goto out; } #if defined(USE_NTLM) - /* If NTLM is requested in a part of this connection, make sure we don't + /* If NTLM is requested in a part of this connection, make sure we do not assume the state is fine as this is a fresh connection and NTLM is connection based. */ - if((data->state.authhost.picked & (CURLAUTH_NTLM | CURLAUTH_NTLM_WB)) && + if((data->state.authhost.picked & CURLAUTH_NTLM) && data->state.authhost.done) { - infof(data, "NTLM picked AND auth done set, clear picked!\n"); + infof(data, "NTLM picked AND auth done set, clear picked"); data->state.authhost.picked = CURLAUTH_NONE; data->state.authhost.done = FALSE; } - if((data->state.authproxy.picked & (CURLAUTH_NTLM | CURLAUTH_NTLM_WB)) && + if((data->state.authproxy.picked & CURLAUTH_NTLM) && data->state.authproxy.done) { - infof(data, "NTLM-proxy picked AND auth done set, clear picked!\n"); + infof(data, "NTLM-proxy picked AND auth done set, clear picked"); data->state.authproxy.picked = CURLAUTH_NONE; data->state.authproxy.done = FALSE; } #endif } - /* Mark the connection as used */ - conn->inuse = TRUE; - /* Setup and init stuff before DO starts, in preparing for the transfer. */ Curl_init_do(data, conn); @@ -6755,25 +3688,38 @@ static CURLcode create_conn(struct Curl_easy *data, /* 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; + if(conn->bits.reuse) { + /* We are reusing the connection - no need to resolve anything, and + idnconvert_hostname() was called already in create_conn() for the reuse + case. */ + *async = FALSE; + } + else { + /************************************************************* + * Resolve the address of the server or proxy + *************************************************************/ + result = resolve_server(data, conn, async); + if(result) + goto out; + } - /************************************************************* - * Resolve the address of the server or proxy - *************************************************************/ - result = resolve_server(data, conn, async); + /* persist the scheme and handler the transfer is using */ + 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.used_proxy = +#ifdef CURL_DISABLE_PROXY + 0 +#else + conn->bits.proxy +#endif + ; - out: + /* Everything general done, inform filters that they need + * to prepare for a data transfer. */ + result = Curl_conn_ev_data_setup(data); - free(options); - free(passwd); - free(user); - free(socksproxy); - free(proxy); +out: return result; } @@ -6781,15 +3727,12 @@ static CURLcode create_conn(struct Curl_easy *data, * create_conn() is all done. * * Curl_setup_conn() also handles reused connections - * - * conn->data MUST already have been setup fine (in create_conn) */ - -CURLcode Curl_setup_conn(struct connectdata *conn, +CURLcode Curl_setup_conn(struct Curl_easy *data, bool *protocol_done) { CURLcode result = CURLE_OK; - struct Curl_easy *data = conn->data; + struct connectdata *conn = data->conn; Curl_pgrsTime(data, TIMER_NAMELOOKUP); @@ -6798,106 +3741,56 @@ CURLcode Curl_setup_conn(struct connectdata *conn, *protocol_done = TRUE; return result; } - *protocol_done = FALSE; /* default to not done */ - - /* 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; - - /* - * Set user-agent. Used for HTTP, but since we can attempt to tunnel - * basically anything through a http proxy we can't limit this based on - * protocol. - */ - if(data->set.str[STRING_USERAGENT]) { - Curl_safefree(conn->allocptr.uagent); - conn->allocptr.uagent = - aprintf("User-Agent: %s\r\n", data->set.str[STRING_USERAGENT]); - if(!conn->allocptr.uagent) - return CURLE_OUT_OF_MEMORY; - } - - data->req.headerbytecount = 0; - -#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_tvnow(); - - if(CURL_SOCKET_BAD == conn->sock[FIRSTSOCKET]) { - conn->bits.tcpconnect[FIRSTSOCKET] = FALSE; - result = Curl_connecthost(conn, conn->dns_entry); - if(result) - return result; - } - else { - Curl_pgrsTime(data, TIMER_CONNECT); /* we're connected already */ - Curl_pgrsTime(data, TIMER_APPCONNECT); /* we're connected already */ - conn->bits.tcpconnect[FIRSTSOCKET] = TRUE; - *protocol_done = TRUE; - Curl_updateconninfo(conn, conn->sock[FIRSTSOCKET]); - Curl_verboseconnect(conn); - } - - conn->now = Curl_tvnow(); /* time this *after* the connect is done, we - set this here perhaps a second time */ - -#ifdef __EMX__ - /* - * This check is quite a hack. We're calling _fsetmode to fix the problem - * with fwrite converting newline characters (you get mangled text files, - * and corrupted binary files when you download to stdout and redirect it to - * a file). - */ - - if((data->set.out)->_handle == NULL) { - _fsetmode(stdout, "b"); - } -#endif + conn->now = Curl_now(); + if(!conn->bits.reuse) + result = Curl_conn_setup(data, conn, FIRSTSOCKET, conn->dns_entry, + CURL_CF_SSL_DEFAULT); + if(!result) + result = Curl_headers_init(data); + /* not sure we need this flag to be passed around any more */ + *protocol_done = FALSE; return result; } CURLcode Curl_connect(struct Curl_easy *data, - struct connectdata **in_connect, bool *asyncp, bool *protocol_done) { CURLcode result; + struct connectdata *conn; *asyncp = FALSE; /* assume synchronous resolves by default */ + /* Set the request to virgin state based on transfer settings */ + Curl_req_hard_reset(&data->req, data); + /* call the stuff that needs to be called */ - result = create_conn(data, in_connect, asyncp); + result = create_conn(data, &conn, asyncp); if(!result) { - /* no error */ - if((*in_connect)->send_pipe->size || (*in_connect)->recv_pipe->size) - /* pipelining */ + if(CONN_INUSE(conn) > 1) + /* multiplexed */ *protocol_done = TRUE; else if(!*asyncp) { - /* DNS resolution is done: that's either because this is a reused + /* DNS resolution is done: that is 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(*in_connect, protocol_done); + result = Curl_setup_conn(data, protocol_done); } } if(result == CURLE_NO_CONNECTION_AVAILABLE) { - *in_connect = NULL; return result; } - - if(result && *in_connect) { - /* We're not allowed to return failure with memory left allocated - in the connectdata struct, free those here */ - Curl_disconnect(*in_connect, FALSE); /* close the connection */ - *in_connect = NULL; /* return a NULL */ + else if(result && conn) { + /* We are not allowed to return failure with memory left allocated in the + connectdata struct, free those here */ + Curl_detach_connection(data); + Curl_cpool_disconnect(data, conn, TRUE); } return result; @@ -6915,151 +3808,128 @@ CURLcode Curl_connect(struct Curl_easy *data, 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; - if(conn) - conn->bits.do_more = FALSE; /* by default there's no curl_do_more() to - * use */ + if(conn) { + conn->bits.do_more = FALSE; /* by default there is no curl_do_more() to + use */ + /* if the protocol used does not 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->set.opt_no_body) + if(data->req.no_body) /* in HTTP lingo, no body means using the HEAD request... */ - data->set.httpreq = HTTPREQ_HEAD; - else if(HTTPREQ_HEAD == data->set.httpreq) - /* ... but if unset there really is no perfect method that is the - "opposite" of HEAD but in reality most people probably think GET - then. The important thing is that we can't let it remain HEAD if the - opt_no_body is set FALSE since then we'll behave wrong when getting - HTTP. */ - data->set.httpreq = HTTPREQ_GET; + data->state.httpreq = HTTPREQ_HEAD; - k->start = Curl_tvnow(); /* start time */ - k->now = k->start; /* current time is now */ - k->header = TRUE; /* assume header */ + result = Curl_req_start(&data->req, data); + if(!result) { + Curl_speedinit(data); + Curl_pgrsSetUploadCounter(data, 0); + Curl_pgrsSetDownloadCounter(data, 0); + } + return result; +} - k->bytecount = 0; +#if defined(USE_HTTP2) || defined(USE_HTTP3) - k->buf = data->state.buffer; - k->uploadbuf = data->state.uploadbuffer; - k->hbufp = data->state.headerbuff; - k->ignorebody=FALSE; +#ifdef USE_NGHTTP2 - Curl_speedinit(data); +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; - Curl_pgrsSetUploadCounter(data, 0); - Curl_pgrsSetDownloadCounter(data, 0); + DEBUGASSERT(child->set.priority.parent == parent); + while(pnode && pnode->data != child) { + pnext = &pnode->next; + pnode = pnode->next; + } - return CURLE_OK; -} + DEBUGASSERT(pnode); + if(pnode) { + *pnext = pnode->next; + free(pnode); + } -/* -* get_protocol_family() -* -* This is used to return the protocol family for a given protocol. -* -* Parameters: -* -* protocol [in] - A single bit protocol identifier such as HTTP or HTTPS. -* -* Returns the family as a single bit protocol identifier. -*/ + child->set.priority.parent = 0; + child->set.priority.exclusive = FALSE; +} -unsigned int get_protocol_family(unsigned int protocol) +CURLcode Curl_data_priority_add_child(struct Curl_easy *parent, + struct Curl_easy *child, + bool exclusive) { - unsigned int family; - - switch(protocol) { - case CURLPROTO_HTTP: - case CURLPROTO_HTTPS: - family = CURLPROTO_HTTP; - break; - - case CURLPROTO_FTP: - case CURLPROTO_FTPS: - family = CURLPROTO_FTP; - break; - - case CURLPROTO_SCP: - family = CURLPROTO_SCP; - break; - - case CURLPROTO_SFTP: - family = CURLPROTO_SFTP; - break; - - case CURLPROTO_TELNET: - family = CURLPROTO_TELNET; - break; - - case CURLPROTO_LDAP: - case CURLPROTO_LDAPS: - family = CURLPROTO_LDAP; - break; - - case CURLPROTO_DICT: - family = CURLPROTO_DICT; - break; - - case CURLPROTO_FILE: - family = CURLPROTO_FILE; - break; - - case CURLPROTO_TFTP: - family = CURLPROTO_TFTP; - break; - - case CURLPROTO_IMAP: - case CURLPROTO_IMAPS: - family = CURLPROTO_IMAP; - break; - - case CURLPROTO_POP3: - case CURLPROTO_POP3S: - family = CURLPROTO_POP3; - break; - - case CURLPROTO_SMTP: - case CURLPROTO_SMTPS: - family = CURLPROTO_SMTP; - break; + 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; - case CURLPROTO_RTSP: - family = CURLPROTO_RTSP; - break; + 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; + } - case CURLPROTO_RTMP: - case CURLPROTO_RTMPS: - family = CURLPROTO_RTMP; - break; + tail = &child->set.priority.children; + while(*tail) + tail = &(*tail)->next; - case CURLPROTO_RTMPT: - case CURLPROTO_RTMPTS: - family = CURLPROTO_RTMPT; - break; + DEBUGASSERT(!*tail); + *tail = parent->set.priority.children; + parent->set.priority.children = 0; + } - case CURLPROTO_RTMPE: - family = CURLPROTO_RTMPE; - break; + tail = &parent->set.priority.children; + while(*tail) { + (*tail)->data->set.priority.exclusive = FALSE; + tail = &(*tail)->next; + } - case CURLPROTO_RTMPTE: - family = CURLPROTO_RTMPTE; - break; + DEBUGASSERT(!*tail); + *tail = pnode; + } - case CURLPROTO_GOPHER: - family = CURLPROTO_GOPHER; - break; + child->set.priority.parent = parent; + child->set.priority.exclusive = exclusive; + return CURLE_OK; +} - case CURLPROTO_SMB: - case CURLPROTO_SMBS: - family = CURLPROTO_SMB; - break; +#endif /* USE_NGHTTP2 */ - default: - family = 0; - break; +#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); } - return family; + 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/contrib/curl/lib/url.h b/contrib/curl/lib/url.h index f13c8e6640..47c1db44f3 100644 --- a/contrib/curl/lib/url.h +++ b/contrib/curl/lib/url.h @@ -7,11 +7,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2016, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -20,6 +20,8 @@ * 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" @@ -29,65 +31,67 @@ CURLcode Curl_init_do(struct Curl_easy *data, struct connectdata *conn); CURLcode Curl_open(struct Curl_easy **curl); -CURLcode Curl_init_userdefined(struct UserDefined *set); -CURLcode Curl_setopt(struct Curl_easy *data, CURLoption option, - va_list arg); -CURLcode Curl_dupset(struct Curl_easy * dst, struct Curl_easy * src); -void Curl_freeset(struct Curl_easy * data); -CURLcode Curl_close(struct Curl_easy *data); /* opposite of curl_open() */ -CURLcode Curl_connect(struct Curl_easy *, struct connectdata **, - bool *async, bool *protocol_connect); -CURLcode Curl_disconnect(struct connectdata *, bool dead_connection); -CURLcode Curl_protocol_connect(struct connectdata *conn, bool *done); -CURLcode Curl_protocol_connecting(struct connectdata *conn, bool *done); -CURLcode Curl_protocol_doing(struct connectdata *conn, bool *done); -CURLcode Curl_setup_conn(struct connectdata *conn, - bool *protocol_done); -void Curl_free_request_state(struct Curl_easy *data); - -int Curl_protocol_getsock(struct connectdata *conn, - curl_socket_t *socks, - int numsocks); -int Curl_doing_getsock(struct connectdata *conn, - curl_socket_t *socks, - int numsocks); +CURLcode Curl_init_userdefined(struct Curl_easy *data); -bool Curl_isPipeliningEnabled(const struct Curl_easy *handle); -CURLcode Curl_addHandleToPipeline(struct Curl_easy *handle, - struct curl_llist *pipeline); -int Curl_removeHandleFromPipeline(struct Curl_easy *handle, - struct curl_llist *pipeline); -struct connectdata * -Curl_oldest_idle_connection(struct Curl_easy *data); -/* remove the specified connection from all (possible) pipelines and related - queues */ -void Curl_getoff_all_pipelines(struct Curl_easy *data, - struct connectdata *conn); +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); +bool Curl_on_disconnect(struct Curl_easy *data, + struct connectdata *, bool aborted); +CURLcode Curl_setup_conn(struct Curl_easy *data, + bool *protocol_done); +void Curl_conn_free(struct Curl_easy *data, struct connectdata *conn); +CURLcode Curl_parse_login_details(const char *login, const size_t len, + char **userptr, char **passwdptr, + char **optionsptr); -void Curl_close_connections(struct Curl_easy *data); +/* 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 */ -CURLcode Curl_connected_proxy(struct connectdata *conn, int sockindex); - #ifdef CURL_DISABLE_VERBOSE_STRINGS -#define Curl_verboseconnect(x) Curl_nop_stmt +#define Curl_verboseconnect(x,y,z) Curl_nop_stmt #else -void Curl_verboseconnect(struct connectdata *conn); +void Curl_verboseconnect(struct Curl_easy *data, struct connectdata *conn, + int sockindex); #endif -#define CONNECT_PROXY_SSL()\ - (conn->http_proxy.proxytype == CURLPROXY_HTTPS &&\ - !conn->bits.proxy_ssl_connected[sockindex]) +/** + * Return TRUE iff the given connection is considered dead. + * @param nowp NULL or pointer to time being checked against. + */ +bool Curl_conn_seems_dead(struct connectdata *conn, + struct Curl_easy *data, + struct curltime *nowp); + +/** + * Perform upkeep operations on the connection. + */ +CURLcode Curl_conn_upkeep(struct Curl_easy *data, + struct connectdata *conn, + struct curltime *now); -#define CONNECT_FIRSTSOCKET_PROXY_SSL()\ - (conn->http_proxy.proxytype == CURLPROXY_HTTPS &&\ - !conn->bits.proxy_ssl_connected[FIRSTSOCKET]) +#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)) */ -#define CONNECT_SECONDARYSOCKET_PROXY_SSL()\ - (conn->http_proxy.proxytype == CURLPROXY_HTTPS &&\ - !conn->bits.proxy_ssl_connected[SECONDARYSOCKET]) +#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/contrib/curl/lib/urlapi-int.h b/contrib/curl/lib/urlapi-int.h new file mode 100644 index 0000000000..fcffab2e95 --- /dev/null +++ b/contrib/curl/lib/urlapi-int.h @@ -0,0 +1,38 @@ +#ifndef HEADER_CURL_URLAPI_INT_H +#define HEADER_CURL_URLAPI_INT_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ +#include "curl_setup.h" + +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); + +#ifdef UNITTESTS +UNITTEST CURLUcode Curl_parse_port(struct Curl_URL *u, struct dynbuf *host, + bool has_scheme); +#endif + +#endif /* HEADER_CURL_URLAPI_INT_H */ diff --git a/contrib/curl/lib/urlapi.c b/contrib/curl/lib/urlapi.c new file mode 100644 index 0000000000..c6a5095847 --- /dev/null +++ b/contrib/curl/lib/urlapi.c @@ -0,0 +1,2020 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#include "urldata.h" +#include "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" + +/* The last 3 #include files should be in this order */ +#include "curl_printf.h" +#include "curl_memory.h" +#include "memdebug.h" + + /* MS-DOS/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] == ':')) + + /* MS-DOS/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 USE_IPV6 is disabled, we still want to parse IPv6 addresses, so make + * sure we have _some_ value for AF_INET6 without polluting our fake value + * everywhere. + */ +#if !defined(USE_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; + unsigned short portnum; /* the numerical version (if 'port' is set) */ + BIT(query_present); /* to support blank */ + BIT(fragment_present); /* to support blank */ + BIT(guessed_scheme); /* when a URL without scheme is parsed */ +}; + +#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 hostname, 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; +} + +/* convert CURLcode to CURLUcode */ +#define cc2cu(x) ((x) == CURLE_TOO_LARGE ? CURLUE_TOO_LARGE : \ + CURLUE_OUT_OF_MEMORY) +/* + * 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 hostnames, 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; + CURLcode result; + + 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) { + result = Curl_dyn_addn(o, iptr, 1); + if(result) + return cc2cu(result); + continue; + } + + if(*iptr == ' ') { + if(left) + result = Curl_dyn_addn(o, "%20", 3); + else + result = Curl_dyn_addn(o, "+", 1); + if(result) + return cc2cu(result); + continue; + } + + if(*iptr == '?') + left = FALSE; + + if(urlchar_needs_escaping(*iptr)) { + char out[3]={'%'}; + out[1] = hexdigits[*iptr >> 4]; + out[2] = hexdigits[*iptr & 0xf]; + result = Curl_dyn_addn(o, out, 3); + } + else + result = Curl_dyn_addn(o, iptr, 1); + if(result) + return cc2cu(result); + } + + 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) +{ + size_t 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 hostname "data" with a specified port number. */ + + /* the length of the scheme is the name part only */ + size_t len = i; + if(buf) { + Curl_strntolower(buf, url, i); + buf[i] = 0; + } + 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 CURLcode concat_url(char *base, const char *relurl, char **newurl) +{ + /*** + 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; + CURLcode result = CURLE_OK; + CURLUcode uc; + bool skip_slash = FALSE; + *newurl = NULL; + + /* protsep points to the start of the hostname */ + 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 is 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 is one + available, or the new URL is just a query string (starts with a '?') or + a fragment (starts with '#') we append the new one at the end of the + current URL */ + if((useurl[0] != '?') && (useurl[0] != '#')) { + pathsep = strrchr(protsep, '/'); + if(pathsep) + *pathsep = 0; + + /* Check if there is any slash after the hostname, 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 + skip_slash = TRUE; + } + 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 is 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 does + not 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 */ + result = Curl_dyn_add(&newest, base); + if(result) + return result; + + /* check if we need to append a slash */ + if(('/' == useurl[0]) || (protsep && !*protsep) || skip_slash) + ; + else { + result = Curl_dyn_addn(&newest, "/", 1); + if(result) + return result; + } + + /* then append the new piece on the right side */ + uc = urlencode_str(&newest, useurl, strlen(useurl), !host_changed, + FALSE); + if(uc) + return (uc == CURLUE_TOO_LARGE) ? CURLE_TOO_LARGE : CURLE_OUT_OF_MEMORY; + + *newurl = Curl_dyn_ptr(&newest); + return CURLE_OK; +} + +/* 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 (username, password and options) from the URL and + * strip them out of the hostname + * + */ +static CURLUcode parse_hostname_login(struct Curl_URL *u, + const char *login, + size_t len, + unsigned int flags, + size_t *offset) /* to the hostname */ +{ + 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 hostname 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 = NULL; + unsigned long port; + size_t keep = portptr - hostname; + + /* Browser behavior adaptation. If there is 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. + + Do not 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; + + errno = 0; + port = strtoul(portptr, &rest, 10); /* Port number must be decimal */ + + if(errno || (port > 0xffff) || *rest) + return CURLUE_BAD_PORT_NUMBER; + + u->portnum = (unsigned short) port; + /* generate a new port number string to get rid of leading zeroes etc */ + free(u->port); + u->port = aprintf("%ld", port); + 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_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; + + errno = 0; /* for strtoul */ + while(!done) { + char *endp = NULL; + unsigned long l; + if(!ISDIGIT(*c)) + /* most importantly this does not allow a leading plus or minus */ + return HOST_NAME; + l = strtoul(c, &endp, 0); + if(errno) + return HOST_NAME; +#if SIZEOF_LONG > 4 + /* a value larger than 32 bits */ + if(l > UINT_MAX) + return HOST_NAME; +#endif + + parts[n] = l; + c = endp; + + switch(*c) { + case '.': + if(n == 3) + return HOST_NAME; + n++; + c++; + break; + + case '\0': + done = TRUE; + break; + + default: + return HOST_NAME; + } + } + + switch(n) { + case 0: /* a -- 32 bits */ + Curl_dyn_reset(host); + + result = Curl_dyn_addf(host, "%u.%u.%u.%u", + (unsigned int)(parts[0] >> 24), + (unsigned int)((parts[0] >> 16) & 0xff), + (unsigned int)((parts[0] >> 8) & 0xff), + (unsigned int)(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", + (unsigned int)(parts[0]), + (unsigned int)((parts[1] >> 16) & 0xff), + (unsigned int)((parts[1] >> 8) & 0xff), + (unsigned int)(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", + (unsigned int)(parts[0]), + (unsigned int)(parts[1]), + (unsigned int)((parts[2] >> 8) & 0xff), + (unsigned int)(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", + (unsigned int)(parts[0]), + (unsigned int)(parts[1]), + (unsigned int)(parts[2]), + (unsigned int)(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 cc2cu(result); + } + + 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 uc; + CURLcode result; + + /* + * Parse the login details and strip them out of the hostname. + */ + uc = parse_hostname_login(u, auth, authlen, flags, &offset); + if(uc) + goto out; + + result = Curl_dyn_addn(host, auth + offset, authlen - offset); + if(result) { + uc = cc2cu(result); + goto out; + } + + uc = Curl_parse_port(u, host, has_scheme); + if(uc) + goto out; + + if(!Curl_dyn_len(host)) + return CURLUE_NO_HOST; + + switch(ipv4_normalize(host)) { + case HOST_IPV4: + break; + case HOST_IPV6: + uc = ipv6_parse(u, Curl_dyn_ptr(host), Curl_dyn_len(host)); + break; + case HOST_NAME: + uc = urldecode_host(host); + if(!uc) + uc = hostname_check(u, Curl_dyn_ptr(host), Curl_dyn_len(host)); + break; + case HOST_ERROR: + uc = CURLUE_OUT_OF_MEMORY; + break; + default: + uc = CURLUE_BAD_HOSTNAME; /* Bad IPv4 address even */ + break; + } + +out: + return uc; +} + +/* used for HTTP/2 server push */ +CURLUcode Curl_url_set_authority(CURLU *u, const char *authority) +{ + CURLUcode result; + struct dynbuf host; + + DEBUGASSERT(authority); + Curl_dyn_init(&host, CURL_MAX_INPUT_LENGTH); + + result = parse_authority(u, authority, strlen(authority), + CURLU_DISALLOW_USER, &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:/) -- 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 hostname, 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 hostname, NetBIOS computer name, can not contain disallowed + chars, and the delimiting slash character must be appended to the + hostname */ + path = strpbrk(ptr, "/\\:*?\"<>|"); + if(!path || *path != '/') { + result = CURLUE_BAD_FILE_URL; + goto fail; + } + + len = path - ptr; + if(len) { + CURLcode code = Curl_dyn_addn(&host, ptr, len); + if(code) { + result = cc2cu(code); + 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__) + /* Do not 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 MS-DOS/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; /* hostname 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 hostname + 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 hostname */ + 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; + } + u->guessed_scheme = TRUE; + } + } + 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); + u->fragment_present = TRUE; + 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); + result = urlencode_str(&enc, fragment + 1, fraglen - 1, TRUE, FALSE); + if(result) + goto fail; + u->fragment = Curl_dyn_ptr(&enc); + } + else { + u->fragment = Curl_memdup0(fragment + 1, fraglen - 1); + if(!u->fragment) { + result = CURLUE_OUT_OF_MEMORY; + goto fail; + } + } + } + /* after this, pathlen still contains the query */ + pathlen -= fraglen; + } + + query = memchr(path, '?', pathlen); + if(query) { + size_t qlen = fragment ? (size_t)(fragment - query) : + pathlen - (query - path); + pathlen -= qlen; + u->query_present = TRUE; + if(qlen > 1) { + if(flags & CURLU_URLENCODE) { + struct dynbuf enc; + Curl_dyn_init(&enc, CURL_MAX_INPUT_LENGTH); + /* skip the leading question mark */ + result = urlencode_str(&enc, query + 1, qlen - 1, TRUE, TRUE); + if(result) + goto fail; + u->query = Curl_dyn_ptr(&enc); + } + else { + u->query = Curl_memdup0(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); + result = urlencode_str(&enc, path, pathlen, TRUE, FALSE); + if(result) + 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_memdup0(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; + u->fragment_present = in->fragment_present; + u->query_present = in->query_present; + } + 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 */ + if((flags & CURLU_NO_GUESS_SCHEME) && u->guessed_scheme) + return CURLUE_NO_SCHEME; + 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 is 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; + if(ptr && !ptr[0] && !(flags & CURLU_GET_EMPTY)) + /* there was a blank query and the user do not ask for it */ + ptr = NULL; + break; + case CURLUPART_FRAGMENT: + ptr = u->fragment; + ifmissing = CURLUE_NO_FRAGMENT; + if(!ptr && u->fragment_present && flags & CURLU_GET_EMPTY) + /* there was a blank fragment and the user asks for it */ + ptr = ""; + break; + case CURLUPART_URL: { + char *url; + char *scheme; + char *options = u->options; + char *port = u->port; + char *allochost = NULL; + bool show_fragment = + u->fragment || (u->fragment_present && flags & CURLU_GET_EMPTY); + bool show_query = + (u->query && u->query[0]) || + (u->query_present && flags & CURLU_GET_EMPTY); + punycode = (flags & CURLU_PUNYCODE) ? 1 : 0; + depunyfy = (flags & CURLU_PUNY2IDN) ? 1 : 0; + if(u->scheme && strcasecompare("file", u->scheme)) { + url = aprintf("file://%s%s%s", + u->path, + show_fragment ? "#": "", + u->fragment ? u->fragment : ""); + } + else if(!u->host) + return CURLUE_NO_HOST; + else { + const struct Curl_handler *h = NULL; + char schemebuf[MAX_SCHEME_LEN + 5]; + 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 is 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 + } + } + + if(!(flags & CURLU_NO_GUESS_SCHEME) || !u->guessed_scheme) + msnprintf(schemebuf, sizeof(schemebuf), "%s://", scheme); + else + schemebuf[0] = 0; + + url = aprintf("%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s", + schemebuf, + 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 : "/", + show_query ? "?": "", + u->query ? u->query : "", + show_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_memdup0(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; + CURLUcode uc; + Curl_dyn_init(&enc, CURL_MAX_INPUT_LENGTH); + uc = urlencode_str(&enc, *part, partlen, TRUE, what == CURLUPART_QUERY); + if(uc) + return uc; + 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; + 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; + u->guessed_scheme = FALSE; + 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; + u->query_present = FALSE; + break; + case CURLUPART_FRAGMENT: + storep = &u->fragment; + u->fragment_present = FALSE; + 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; + u->guessed_scheme = FALSE; + 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: + if(!ISDIGIT(part[0])) + /* not a number */ + return CURLUE_BAD_PORT_NUMBER; + else { + char *tmp; + char *endp; + unsigned long port; + errno = 0; + port = strtoul(part, &endp, 10); /* must be decimal */ + if(errno || (port > 0xffff) || *endp) + /* weirdly provided number, not good! */ + return CURLUE_BAD_PORT_NUMBER; + tmp = strdup(part); + if(!tmp) + return CURLUE_OUT_OF_MEMORY; + free(u->port); + u->port = tmp; + u->portnum = (unsigned short)port; + return CURLUE_OK; + } + case CURLUPART_PATH: + urlskipslash = TRUE; + leadingslash = TRUE; /* enforce */ + storep = &u->path; + break; + case CURLUPART_QUERY: + plusencode = urlencode; + appendquery = (flags & CURLU_APPENDQUERY) ? 1 : 0; + equalsencode = appendquery; + storep = &u->query; + u->query_present = TRUE; + break; + case CURLUPART_FRAGMENT: + storep = &u->fragment; + u->fragment_present = TRUE; + break; + case CURLUPART_URL: { + /* + * 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. + */ + CURLcode result; + CURLUcode uc; + 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. */ + result = concat_url(oldurl, part, &redired_url); + free(oldurl); + if(result) + return cc2cu(result); + + uc = parseurl_and_replace(redired_url, u, flags); + free(redired_url); + return uc; + } + 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 cc2cu(result); + } + 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 cc2cu(result); + } + else { + char out[3]={'%'}; + out[1] = hexdigits[*i >> 4]; + out[2] = hexdigits[*i & 0xf]; + result = Curl_dyn_addn(&enc, out, 3); + if(result) + return cc2cu(result); + } + } + } + else { + char *p; + CURLcode result = Curl_dyn_add(&enc, part); + if(result) + return cc2cu(result); + 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 is allowed to be empty. */ + } + else { + bool bad = FALSE; + if(!n) + bad = TRUE; /* empty hostname is not okay */ + else if(!urlencode) { + /* if the host name part was not URL encoded here, it was set ready + URL encoded so we need to decode it to check */ + size_t dlen; + char *decoded = NULL; + CURLcode result = + Curl_urldecode(newp, n, &decoded, &dlen, REJECT_CTRL); + if(result || hostname_check(u, decoded, dlen)) + bad = TRUE; + free(decoded); + } + else if(hostname_check(u, (char *)newp, n)) + bad = TRUE; + if(bad) { + Curl_dyn_free(&enc); + return CURLUE_BAD_HOSTNAME; + } + } + } + + free(*storep); + *storep = (char *)newp; + } + return CURLUE_OK; +} diff --git a/contrib/curl/lib/urldata.h b/contrib/curl/lib/urldata.h index 7f87913a95..028ac0aff5 100644 --- a/contrib/curl/lib/urldata.h +++ b/contrib/curl/lib/urldata.h @@ -7,11 +7,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2017, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -20,6 +20,8 @@ * 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 */ @@ -49,6 +51,49 @@ #define PORT_RTMPT PORT_HTTP #define PORT_RTMPS PORT_HTTPS #define PORT_GOPHER 70 +#define PORT_MQTT 1883 + +struct curl_trc_featt; + +#ifdef USE_ECH +/* CURLECH_ bits for the tls_ech option */ +# define CURLECH_DISABLE (1<<0) +# define CURLECH_GREASE (1<<1) +# define CURLECH_ENABLE (1<<2) +# define CURLECH_HARD (1<<3) +# define CURLECH_CLA_CFG (1<<4) +#endif + +#ifndef CURL_DISABLE_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 + +/* the default protocols accepting a redirect to */ +#define CURLPROTO_REDIR (CURLPROTO_HTTP | CURLPROTO_HTTPS | CURLPROTO_FTP | \ + CURLPROTO_FTPS) + +/* 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:" @@ -60,173 +105,156 @@ #define CURL_DEFAULT_USER "anonymous" #define CURL_DEFAULT_PASSWORD "ftp@example.com" +#if !defined(_WIN32) && !defined(MSDOS) && !defined(__EMX__) +/* do FTP line-end CRLF => LF conversions on platforms that prefer LF-only. It + also means: keep CRLF line endings on the CRLF platforms */ +#define CURL_PREFER_LF_LINEENDS +#endif + /* 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) +#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) || !defined(CURL_DISABLE_FILE) +/* 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. - Symbian OS panics when given a timeout much greater than 1/2 hour. -*/ -#define RESP_TIMEOUT (1800*1000) - -#include "cookie.h" -#include "formdata.h" +/* Default FTP/IMAP etc response timeout in milliseconds */ +#define RESP_TIMEOUT (120*1000) -#ifdef USE_OPENSSL -#include -#ifdef HAVE_OPENSSL_ENGINE_H -#include -#endif -#endif /* USE_OPENSSL */ - -#ifdef USE_GNUTLS -#include -#endif - -#ifdef USE_MBEDTLS - -#include -#include -#include -#include - -#elif defined USE_POLARSSL - -#include -#include -#if POLARSSL_VERSION_NUMBER<0x01010000 -#include -#else -#include -#include -#endif /* POLARSSL_VERSION_NUMBER<0x01010000 */ +/* Max string input length is a precaution against abuse and to detect junk + input easier and better. */ +#define CURL_MAX_INPUT_LENGTH 8000000 -#endif /* USE_POLARSSL */ -#ifdef USE_CYASSL -#undef OCSP_REQUEST /* avoid cyassl/openssl/ssl.h clash with wincrypt.h */ -#undef OCSP_RESPONSE /* avoid cyassl/openssl/ssl.h clash with wincrypt.h */ -#include -#endif - -#ifdef USE_NSS -#include -#include -#endif - -#ifdef USE_GSKIT -#include -#endif - -#ifdef USE_AXTLS -#include -#include -#undef malloc -#undef calloc -#undef realloc -#endif /* USE_AXTLS */ - -#if defined(USE_SCHANNEL) || defined(USE_WINDOWS_SSPI) -#include "curl_sspi.h" -#endif -#ifdef USE_SCHANNEL -#include -#include -#endif - -#ifdef USE_DARWINSSL -#include -/* For some reason, when building for iOS, the omnibus header above does - * not include SecureTransport.h as of iOS SDK 5.1. */ -#include -#endif +#include "cookie.h" +#include "psl.h" +#include "formdata.h" #ifdef HAVE_NETINET_IN_H #include #endif +#ifdef HAVE_NETINET_IN6_H +#include +#endif #include "timeval.h" -#ifdef HAVE_ZLIB_H -#include /* for content-encoding */ -#ifdef __SYMBIAN32__ -/* zlib pollutes the namespace with this definition */ -#undef WIN32 -#endif -#endif - #include #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" +#include "request.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 */ + bool eos, /* last chunk */ + 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, + int select_res); +#endif + +#include "mime.h" #include "imap.h" #include "pop3.h" #include "smtp.h" #include "ftp.h" #include "file.h" -#include "ssh.h" +#include "vssh/ssh.h" #include "http.h" #include "rtsp.h" #include "smb.h" -#include "wildcard.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 -# elif defined HAVE_GSSMIT +# elif defined HAVE_GSSAPI_GSSAPI_H # include -# include # else # include # endif +# ifdef HAVE_GSSAPI_GSSAPI_GENERIC_H +# include +# endif #endif -#ifdef HAVE_LIBSSH2_H +#ifdef USE_LIBSSH2 #include #include -#endif /* HAVE_LIBSSH2_H */ +#endif /* USE_LIBSSH2 */ -/* Download buffer size, keep it fairly big for speed reasons */ -#undef BUFSIZE -#define BUFSIZE CURL_MAX_WRITE_SIZE -#undef MAX_BUFSIZE -#define MAX_BUFSIZE CURL_MAX_READ_SIZE -#define CURL_BUFSIZE(x) ((x)?(x):(BUFSIZE)) +#define READBUFFER_SIZE CURL_MAX_WRITE_SIZE +#define READBUFFER_MAX CURL_MAX_READ_SIZE +#define READBUFFER_MIN 1024 -/* Initial size of the buffer to store headers in, it'll be enlarged in case - of need. */ -#define HEADERSIZE 256 +/* 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)) - -/* 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)) +#endif #ifdef HAVE_GSSAPI /* Types needed for krb5-ftp connections */ struct krb5buffer { - void *data; - size_t size; + struct dynbuf buf; size_t index; - int eof_flag; + BIT(eof_flag); }; enum protection_level { @@ -240,189 +268,123 @@ enum protection_level { }; #endif -#ifdef USE_SCHANNEL -/* Structs to store Schannel handles */ -struct curl_schannel_cred { - CredHandle cred_handle; - TimeStamp time_stamp; - int refcount; -}; - -struct curl_schannel_ctxt { - CtxtHandle ctxt_handle; - TimeStamp time_stamp; -}; -#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; +/* SSL backend-specific data; declared differently by each SSL backend */ +struct ssl_backend_data; typedef enum { - ssl_connection_none, - ssl_connection_negotiating, - ssl_connection_complete -} ssl_connection_state; - -/* struct for data related to each SSL connection */ -struct ssl_connect_data { - /* Use ssl encrypted communications TRUE/FALSE, not necessarily using it atm - but at least asked to or meaning to use it. See 'state' for the exact - current state of the connection. */ - bool use; - ssl_connection_state state; - ssl_connect_state connecting_state; -#if defined(USE_OPENSSL) - /* these ones requires specific SSL-types */ - SSL_CTX* ctx; - SSL* handle; - X509* server_cert; -#elif defined(USE_GNUTLS) - gnutls_session_t session; - gnutls_certificate_credentials_t cred; -#ifdef USE_TLS_SRP - gnutls_srp_client_credentials_t srp_client_cred; -#endif -#elif defined(USE_MBEDTLS) - mbedtls_ctr_drbg_context ctr_drbg; - mbedtls_entropy_context entropy; - mbedtls_ssl_context ssl; - int server_fd; - mbedtls_x509_crt cacert; - mbedtls_x509_crt clicert; - mbedtls_x509_crl crl; - mbedtls_pk_context pk; - mbedtls_ssl_config config; - const char *protocols[3]; -#elif defined(USE_POLARSSL) - ctr_drbg_context ctr_drbg; - entropy_context entropy; - ssl_context ssl; - int server_fd; - x509_crt cacert; - x509_crt clicert; - x509_crl crl; - rsa_context rsa; -#elif defined(USE_CYASSL) - SSL_CTX* ctx; - SSL* handle; -#elif defined(USE_NSS) - PRFileDesc *handle; - char *client_nickname; - struct Curl_easy *data; - struct curl_llist *obj_list; - PK11GenericObject *obj_clicert; -#elif defined(USE_GSKIT) - gsk_handle handle; - int iocport; - int localfd; - int remotefd; -#elif defined(USE_AXTLS) - SSL_CTX* ssl_ctx; - SSL* ssl; -#elif defined(USE_SCHANNEL) - 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; - 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 use_alpn; /* true if ALPN is used for this connection */ -#elif defined(USE_DARWINSSL) - SSLContextRef ssl_ctx; - curl_socket_t ssl_sockfd; - bool ssl_direction; /* true if writing, false if reading */ - size_t ssl_write_buffered_length; -#elif defined(USE_SSL) -#error "SSL backend specific information missing from ssl_connect_data" -#endif + CURL_SSL_PEER_DNS, + CURL_SSL_PEER_IPV4, + CURL_SSL_PEER_IPV6 +} ssl_peer_type; + +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 */ + ssl_peer_type type; /* type of the peer information */ + int port; /* port we are talking to */ + int transport; /* one of TRNSPRT_* defines */ }; struct ssl_primary_config { - long version; /* what version the client wants to use */ - bool verifypeer; /* set TRUE if this is desired */ - bool verifyhost; /* set TRUE if CN/SAN must match hostname */ - bool verifystatus; /* set TRUE if certificate status must be checked */ - char *CApath; /* certificate dir (doesn't work on windows) */ + char *CApath; /* certificate dir (does not work on Windows) */ char *CAfile; /* certificate to verify peer against */ + char *issuercert; /* optional issuer certificate filename */ char *clientcert; - char *random_file; /* path to file containing "random" data */ - char *egdsocket; /* path to file containing the EGD daemon socket */ 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(cache_session); /* cache session or not */ }; struct ssl_config_data { struct ssl_primary_config primary; - bool enable_beast; /* especially allow this flaw for interoperability's - sake*/ - bool no_revoke; /* disable SSL certificate revocation checks */ long certverifyresult; /* result from the certificate verification */ - char *CRLfile; /* CRL to check certificate revocation */ - char *issuercert;/* optional issuer certificate filename */ curl_ssl_ctx_callback fsslctx; /* function to initialize ssl ctx */ void *fsslctxp; /* parameter for call back */ - bool certinfo; /* gather lots of certificate info */ - bool falsestart; - - char *cert; /* client certificate file name */ char *cert_type; /* format for certificate (default: PEM)*/ - char *key; /* private key file name */ + char *key; /* private key filename */ + struct curl_blob *key_blob; char *key_type; /* format for private key (default: PEM) */ char *key_passwd; /* plain text private key password */ - -#ifdef USE_TLS_SRP - char *username; /* TLS username (for, e.g., SRP) */ - char *password; /* TLS password (for, e.g., SRP) */ - enum CURL_TLSAUTH authtype; /* TLS authentication type (default SRP) */ -#endif + 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); /* do not accept partial certificate chains */ + BIT(revoke_best_effort); /* ignore SSL revocation offline/missing revocation + list errors */ + BIT(native_ca_store); /* use the native ca store of operating system */ + BIT(auto_client_cert); /* automatically locate and use a client + certificate for authentication (Schannel) */ }; struct ssl_general_config { - bool sessionid; /* cache session IDs or not */ size_t max_ssl_sessions; /* SSL session id cache size */ + int ca_cache_timeout; /* Certificate store cache timeout (seconds) */ }; +typedef void Curl_ssl_sessionid_dtor(void *sessionid, size_t idsize); + /* information stored about one single SSL session */ -struct curl_ssl_session { - char *name; /* host name for which this ID was used */ - char *conn_to_host; /* host name for the connection (may be NULL) */ +struct Curl_ssl_session { + char *name; /* hostname for which this ID was used */ + char *conn_to_host; /* hostname 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 */ + Curl_ssl_sessionid_dtor *sessionid_free; /* free `sessionid` callback */ long age; /* just a number, the higher the more recent */ int remote_port; /* remote port */ int conn_to_port; /* remote port for the connection (may be -1) */ + int transport; /* TCP or QUIC */ struct ssl_primary_config ssl_config; /* setup for this session */ }; +#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; - int algo; - bool stale; /* set true for re-negotiation */ char *opaque; char *qop; char *algorithm; - int nc; /* nounce count */ + int nc; /* nonce count */ + unsigned char algo; + BIT(stale); /* set true for re-negotiation */ + BIT(userhash); #endif }; +#endif typedef enum { NTLMSTATE_NONE, @@ -432,13 +394,13 @@ typedef enum { NTLMSTATE_LAST } curlntlm; -#ifdef USE_WINDOWS_SSPI -#include "curl_sspi.h" -#endif - -#if defined(CURL_DOES_CONVERSIONS) && defined(HAVE_ICONV) -#include -#endif +typedef enum { + GSS_AUTHNONE, + GSS_AUTHRECV, + GSS_AUTHSENT, + GSS_AUTHDONE, + GSS_AUTHSUCC +} curlnegotiate; /* Struct used for GSSAPI (Kerberos V5) authentication */ #if defined(USE_KERBEROS5) @@ -458,11 +420,25 @@ struct kerberos5data { }; #endif +/* Struct used for SCRAM-SHA-1 authentication */ +#ifdef USE_GSASL +#include +struct gsasldata { + Gsasl *ctx; + Gsasl_session *client; +}; +#endif + /* Struct used for NTLM challenge-response authentication */ #if defined(USE_NTLM) struct ntlmdata { - curlntlm state; #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; @@ -471,27 +447,30 @@ struct ntlmdata { BYTE *output_token; BYTE *input_token; size_t input_token_len; + TCHAR *spn; #else unsigned int flags; unsigned char nonce[8]; - void *target_info; /* TargetInfo received in the ntlm type-2 message */ unsigned int target_info_len; + void *target_info; /* TargetInfo received in the NTLM type-2 message */ #endif }; #endif +/* Struct used for Negotiate (SPNEGO) authentication */ #ifdef USE_SPNEGO struct negotiatedata { - /* When doing Negotiate (SPNEGO) auth, we first need to send a token - and then validate the received one. */ - enum { GSS_AUTHNONE, GSS_AUTHRECV, GSS_AUTHSENT } state; #ifdef HAVE_GSSAPI OM_uint32 status; gss_ctx_id_t context; gss_name_t spn; gss_buffer_desc output_token; + struct dynbuf channel_binding_data; #else #ifdef USE_WINDOWS_SSPI +#ifdef SECPKG_ATTR_ENDPOINT_BINDINGS + CtxtHandle *sslContext; +#endif DWORD status; CredHandle *credentials; CtxtHandle *context; @@ -503,78 +482,84 @@ struct negotiatedata { 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); /* if set, this transfer is done through a proxy - any type */ +#endif /* always modify bits.close with the connclose() and connkeep() macros! */ - bool close; /* if set, we close the connection after this request */ - bool reuse; /* if set, this is a re-used connection */ - bool conn_to_host; /* if set, this connection has a "connect to host" + 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 */ - bool conn_to_port; /* if set, this connection has a "connect to port" + BIT(conn_to_port); /* if set, this connection has a "connect to port" that overrides the port in the URL (remote port) */ - bool proxy; /* if set, this transfer is done through a proxy - any type */ - bool httpproxy; /* if set, this transfer is done through a http proxy */ - bool socksproxy; /* if set, this transfer is done through a socks proxy */ - bool user_passwd; /* do we use user+password for this connection? */ - bool proxy_user_passwd; /* user+password for the proxy? */ - bool ipv6_ip; /* we communicate with a remote site specified with pure IPv6 + BIT(ipv6_ip); /* we communicate with a remote site specified with pure IPv6 IP address */ - bool ipv6; /* we communicate with a site using an IPv6 address */ - - bool do_more; /* this is set TRUE if the ->curl_do_more() function is + 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() */ - bool tcpconnect[2]; /* the TCP layer (or similar) is connected, this is set - the first time on the first connect function call */ - bool protoconnstart;/* the protocol layer has STARTED its operation after + BIT(protoconnstart);/* the protocol layer has STARTED its operation after the TCP layer connect */ - - bool retry; /* this connection is about to get closed and then + BIT(retry); /* this connection is about to get closed and then re-attempted at another connection. */ - bool 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 */ - bool 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. */ - bool rewindaftersend;/* TRUE when the sending couldn't be stopped even - though it will be discarded. When the whole send - operation is done, we must call the data rewind - callback. */ - bool 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 +#ifndef CURL_DISABLE_FTP + BIT(ftp_use_epsv); /* As set with CURLOPT_FTP_USE_EPSV, but if we find out + EPSV does not work we disable it for the forthcoming requests */ - - bool 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 + BIT(ftp_use_eprt); /* As set with CURLOPT_FTP_USE_EPRT, but if we find out + EPRT does not work we disable it for the forthcoming requests */ - bool ftp_use_data_ssl; /* Enabled SSL for the data connection */ - bool netrc; /* name+password provided by netrc */ - bool userpwd_in_url; /* name+password found in url */ - bool stream_was_rewound; /* Indicates that the stream was rewound after a - request read past the end of its response byte - boundary */ - bool proxy_connect_closed; /* set true if a proxy disconnected the - connection in a CONNECT request with auth, so - that libcurl should reconnect and continue. */ - bool bound; /* set true if bind() has already been done on this socket/ + 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 */ - bool type_set; /* type= was used in the URL */ - bool multiplex; /* connection is multiplexed */ - - bool tcp_fastopen; /* use TCP Fast Open */ - bool tls_enable_npn; /* TLS NPN extension? */ - bool tls_enable_alpn; /* TLS ALPN extension? */ - bool proxy_ssl_connected[2]; /* TRUE when SSL initialization for HTTPS proxy - is complete */ - bool socksproxy_connecting; /* connecting through a socks proxy */ + BIT(asks_multiplex); /* connection asks for multiplexing, but is not yet */ + 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) */ + BIT(aborted); /* connection was aborted, e.g. in unclean state */ + BIT(shutdown_handler); /* connection shutdown: handler shut down */ + BIT(shutdown_filters); /* connection shutdown: filters shut down */ + BIT(in_cpool); /* connection is kept in a connection pool */ }; struct hostname { @@ -598,161 +583,49 @@ struct hostname { #define KEEP_RECV_PAUSE (1<<4) /* reading is paused */ #define KEEP_SEND_PAUSE (1<<5) /* writing is paused */ +/* KEEP_SEND_TIMED is set when the transfer should attempt sending + * at timer (or other) events. A transfer waiting on a timer will + * remove KEEP_SEND to suppress POLLOUTs of the connection. + * Adding KEEP_SEND_TIMED will then attempt to send whenever the transfer + * enters the "readwrite" loop, e.g. when a timer fires. + * This is used in HTTP for 'Expect: 100-continue' waiting. */ +#define KEEP_SEND_TIMED (1<<6) + #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_RECVBITS) == KEEP_RECV) -#ifdef HAVE_LIBZ -typedef enum { - ZLIB_UNINIT, /* uninitialized */ - ZLIB_INIT, /* initialized */ - ZLIB_GZIP_HEADER, /* reading gzip header */ - ZLIB_GZIP_INFLATING, /* inflating gzip stream */ - ZLIB_INIT_GZIP /* initialized in transparent gzip mode */ -} zlibInitState; -#endif - -#ifdef CURLRES_ASYNCH +#if defined(CURLRES_ASYNCH) || !defined(CURL_DISABLE_DOH) +#define USE_CURL_ASYNC struct Curl_async { char *hostname; - int port; struct Curl_dns_entry *dns; - bool done; /* set TRUE when the lookup is complete */ + 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 */ - void *os_specific; /* 'struct thread_data' for Windows */ + BIT(done); /* set TRUE when the lookup is complete */ }; + #endif #define FIRSTSOCKET 0 #define SECONDARYSOCKET 1 -/* These function pointer types are here only to allow easier typecasting - within the source when we need to cast between data pointers (such as NULL) - and function pointers. */ -typedef CURLcode (*Curl_do_more_func)(struct connectdata *, int *); -typedef CURLcode (*Curl_done_func)(struct connectdata *, CURLcode, bool); - -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_REQUESTED, /* upgrade requested */ - UPGR101_RECEIVED, /* response received */ - UPGR101_WORKING /* talking upgraded protocol */ -}; - -/* - * 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 +/* Polling requested by an easy handle. + * `action` is CURL_POLL_IN, CURL_POLL_OUT or CURL_POLL_INOUT. */ -struct SingleRequest { - curl_off_t size; /* -1 if unknown at this point */ - curl_off_t *bytecountp; /* return number of bytes read or NULL */ - - curl_off_t maxdownload; /* in bytes, the maximum amount of data to fetch, - -1 means unlimited */ - curl_off_t *writebytecountp; /* return number of bytes written or NULL */ - - curl_off_t bytecount; /* total number of bytes read */ - curl_off_t writebytecount; /* number of bytes written */ - - long headerbytecount; /* only count received headers */ - long 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 */ - - struct timeval start; /* transfer started at this time */ - struct timeval now; /* current time */ - bool header; /* incoming data has HTTP header */ - enum { - HEADER_NORMAL, /* no bad header at all */ - HEADER_PARTHEADER, /* part of the chunk is a bad header, the rest - is normal data */ - HEADER_ALLBAD /* all was believed to be header */ - } badheader; /* the header was deemed bad and will be - written as body */ - int headerline; /* counts header lines to better track the - first one */ - char *hbufp; /* points at *end* of header line */ - size_t hbuflen; - char *str; /* within buf */ - char *str_start; /* within buf */ - char *end_ptr; /* within buf */ - char *p; /* within headerbuff */ - bool content_range; /* set TRUE if Content-Range: was found */ - 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 */ - struct timeval start100; /* time stamp to wait for the 100 code from */ - enum expect100 exp100; /* expect 100 continue state */ - enum upgrade101 upgr101; /* 101 upgrade state */ - - int auto_decoding; /* What content encoding. sec 3.5, RFC2616. */ - -#define IDENTITY 0 /* No encoding */ -#define DEFLATE 1 /* zlib deflate [RFC 1950 & 1951] */ -#define GZIP 2 /* gzip algorithm [RFC 1952] */ - -#ifdef HAVE_LIBZ - zlibInitState zlib_init; /* possible zlib init state; - undefined if Content-Encoding header. */ - z_stream z; /* State structure for zlib. */ -#endif - - time_t timeofdoc; - long bodywrites; - - char *buf; - char *uploadbuf; - curl_socket_t maxfd; - - int keepon; - - bool upload_done; /* set to TRUE when doing chunked transfer-encoding upload - and we're uploading the last chunk */ - - bool ignorebody; /* we read a response-body but we ignore it! */ - bool ignorecl; /* This HTTP response has no body so we ignore the Content- - Length: header */ - - 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; - - bool chunk; /* if set, this is a chunked transfer-encoding */ - bool upload_chunky; /* set TRUE if we are doing chunked transfer-encoding - on upload */ - bool getheader; /* TRUE if header parsing is wanted */ - - bool forbidchunk; /* used only to explicitly forbid chunk-upload for - specific upload buffers. See readmoredata() in - http.c for details. */ - - void *protop; /* Allocated protocol-specific data. Each protocol - handler makes sure this points to data it needs. */ +struct easy_pollset { + curl_socket_t sockets[MAX_SOCKSPEREASYHANDLE]; + unsigned int num; + unsigned char actions[MAX_SOCKSPEREASYHANDLE]; }; /* @@ -760,74 +633,94 @@ struct SingleRequest { */ struct Curl_handler { - const char *scheme; /* URL scheme name. */ + const char *scheme; /* URL scheme name in lowercase */ - /* Complement to setup_connection_internals(). */ - CURLcode (*setup_connection)(struct connectdata *); + /* 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 connectdata *, bool *done); - Curl_done_func done; + 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. */ - Curl_do_more_func do_more; + 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 curl_connecting() function until it is. + * function completes before return. If it does not complete, the caller + * should call the ->connecting() function until it is. */ - CURLcode (*connect_it)(struct connectdata *, bool *done); + CURLcode (*connect_it)(struct Curl_easy *data, bool *done); - /* See above. Currently only used for FTP. */ - CURLcode (*connecting)(struct connectdata *, bool *done); - CURLcode (*doing)(struct connectdata *, 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 connectdata *conn, - curl_socket_t *socks, - int numsocks); + 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 connectdata *conn, - curl_socket_t *socks, - int numsocks); + 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 connectdata *conn, - curl_socket_t *socks, - int numsocks); + 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)(const struct connectdata *conn, - curl_socket_t *socks, - int numsocks); + 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. + * 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 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, - ssize_t *nread, bool *readmore); - - long defport; /* Default port. */ - unsigned int protocol; /* See CURLPROTO_* - this needs to be the single - specific protocol bit */ + CURLcode (*disconnect)(struct Curl_easy *, struct connectdata *, + bool dead_connection); + + /* If used, this function gets called from transfer.c to + allow the protocol to do extra handling in writing response to + the client. */ + CURLcode (*write_resp)(struct Curl_easy *data, const char *buf, size_t blen, + bool is_eos); + + /* If used, this function gets called from transfer.c to + allow the protocol to do extra handling in writing a single response + header line to the client. */ + CURLcode (*write_resp_hd)(struct Curl_easy *data, + const char *hd, size_t hdlen, bool is_eos); + + /* This function can perform various checks on the connection. See + CONNCHECK_* for more information about the checks that can be performed, + and CONNRESULT_* for the results that can be returned. */ + 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 */ @@ -839,177 +732,138 @@ struct Curl_handler { 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_NONETWORK (1<<4) /* protocol does not 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_NOURLQUERY (1<<6) /* protocol cannot handle + URL query strings (?foo=bar) ! */ #define PROTOPT_CREDSPERREQUEST (1<<7) /* requires login credentials per request instead of per connection */ -#define PROTOPT_ALPN_NPN (1<<8) /* set ALPN and/or NPN for this */ -#define PROTOPT_STREAM (1<<9) /* a protocol with individual logical streams */ +#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 */ - -/* return the count of bytes sent, or -1 on error */ -typedef ssize_t (Curl_send)(struct connectdata *conn, /* connection data */ - 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 connectdata *conn, /* connection data */ - int sockindex, /* socketindex */ - char *buf, /* store data here */ - size_t len, /* max amount to read */ - CURLcode *err); /* error to return */ - -#ifdef USE_RECV_BEFORE_SEND_WORKAROUND -struct postponed_data { - char *buffer; /* Temporal store for received data during - sending, must be freed */ - size_t allocated_size; /* Size of temporal store */ - size_t recv_size; /* Size of received data during sending */ - size_t recv_processed; /* Size of processed part of postponed data */ -#ifdef DEBUGBUILD - curl_socket_t bindsock;/* Structure must be bound to specific socket, - used only for DEBUGASSERT */ -#endif /* DEBUGBUILD */ +#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 + username and password */ +#define PROTOPT_NOTCPPROXY (1<<14) /* this protocol cannot 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 ip_quadruple { + char remote_ip[MAX_IPADR_LEN]; + char local_ip[MAX_IPADR_LEN]; + int remote_port; + int local_port; }; -#endif /* USE_RECV_BEFORE_SEND_WORKAROUND */ struct proxy_info { struct hostname host; - long port; - curl_proxytype proxytype; /* what kind of proxy that is in use */ - char *user; /* proxy user name string, allocated */ + int port; + unsigned char proxytype; /* curl_proxytype: what kind of proxy that is in + use */ + char *user; /* proxy username 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 { - /* 'data' is the CURRENT Curl_easy using this connection -- take great - caution that this might very well vary between different times this - connection is used! */ - struct Curl_easy *data; - - /* chunk is for HTTP chunked encoding, but is in the general connectdata - struct only because we can do just about any protocol through a HTTP proxy - and a HTTP proxy may in fact respond using chunked encoding */ - struct Curl_chunker chunk; + struct Curl_llist_node cpool_node; /* conncache lists */ curl_closesocket_callback fclosesocket; /* function closing the socket(s) */ void *closesocket_client; - bool inuse; /* This is a marker for the connection cache logic. If this is - TRUE this handle is being used by an easy handle and cannot - be used by any other easy handle without careful - consideration (== only for pipelining). */ + /* This is used by the connection pool logic. If this returns TRUE, this + handle is still used by one or more easy handles and can only used by any + other easy handle without careful consideration (== only for + multiplexing) and it cannot be used by another multi handle! */ +#define CONN_INUSE(c) Curl_llist_count(&(c)->easyq) /**** Fields set when inited and not modified again */ - long connection_id; /* Contains a unique number to make it easier to - track the connections in the log output */ + curl_off_t connection_id; /* Contains a unique number to make it easier to + track the connections in the log output */ + char *destination; /* string carrying normalized hostname+port+scope */ + size_t destination_len; /* strlen(destination) + 1 */ /* '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 - Curl_done(). This entry will be NULL if the connection is re-used as then + 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; - /* 'ip_addr' is the particular IP we connected to. It points to a struct - within the DNS cache, so this pointer is only valid as long as the DNS - cache entry remains locked. It gets unlocked in Curl_done() */ - Curl_addrinfo *ip_addr; - Curl_addrinfo *tempaddr[2]; /* for happy eyeballs */ - - /* 'ip_addr_str' is the ip_addr data as a human readable string. - It remains available as long as the connection does, which is longer than - the ip_addr itself. */ - char ip_addr_str[MAX_IPADR_LEN]; - - unsigned int scope_id; /* Scope id for IPv6 */ - - int socktype; /* SOCK_STREAM or SOCK_DGRAM */ + /* '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 *secondaryhostname; /* secondary socket host name (ftp) */ + char *hostname_resolve; /* hostname to resolve to address, allocated */ + char *secondaryhostname; /* secondary socket hostname (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; - - long port; /* which port to use locally */ - 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 */ - unsigned short secondary_port; /* secondary socket remote port to connect to - (ftp) */ - - /* '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 +#endif + /* 'primary' and 'secondary' get filled with IP quadruple + (local/remote numerical ip address and port) whenever a connect is + *attempted*. + 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]; - long primary_port; - - /* 'local_ip' and 'local_port' get filled with local's numerical - ip address and port number whenever an outgoing connection is - **established** from the primary socket to a remote address. */ - - char local_ip[MAX_IPADR_LEN]; - long local_port; - - char *user; /* user name string, allocated */ + struct ip_quadruple primary; + struct ip_quadruple secondary; + char *user; /* username string, allocated */ char *passwd; /* password string, allocated */ char *options; /* options string, allocated */ - - char *oauth_bearer; /* bearer token for OAuth 2.0, allocated */ - - int httpversion; /* the HTTP version*10 reported by the server */ - int rtspversion; /* the RTSP version*10 reported by the server */ - - struct timeval now; /* "current" time */ - struct timeval created; /* creation time */ + 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 poolas idle */ curl_socket_t sock[2]; /* two sockets, the second is used for the data transfer when doing FTP */ - curl_socket_t tempsock[2]; /* temporary sockets for happy eyeballs */ - bool sock_accepted[2]; /* TRUE if the socket on this index was created with - accept() */ Curl_recv *recv[2]; Curl_send *send[2]; + struct Curl_cfilter *cfilter[2]; /* connection filters */ + struct { + struct curltime start[2]; /* when filter shutdown started */ + unsigned int timeout_ms; /* 0 means no timeout */ + } shutdown; + /* Last pollset used in connection shutdown. Used to detect changes + * for multi_socket API. */ + struct easy_pollset shutdown_poll; -#ifdef USE_RECV_BEFORE_SEND_WORKAROUND - struct postponed_data postponed[2]; /* two buffers for two sockets */ -#endif /* USE_RECV_BEFORE_SEND_WORKAROUND */ - struct ssl_connect_data ssl[2]; /* this is for ssl-stuff */ - struct ssl_connect_data proxy_ssl[2]; /* this is for proxy ssl-stuff */ struct ssl_primary_config ssl_config; +#ifndef CURL_DISABLE_PROXY struct ssl_primary_config proxy_ssl_config; - bool tls_upgraded; - +#endif struct ConnectBits bits; /* various state-flags for this connection */ - /* connecttime: when connect() is called on the current IP address. Used to - be able to track when to move on to try next IP - but only when the multi - interface is used. */ - struct timeval connecttime; - /* The two fields below get set in Curl_connecthost */ - int num_addr; /* number of addresses to try to connect to */ - time_t timeoutms_per_addr; /* how long time in milliseconds to spend on - trying to connect to each IP address */ - const struct Curl_handler *handler; /* Connection's protocol handler */ const struct Curl_handler *given; /* The protocol first given */ - long ip_version; /* copied from the Curl_easy at creation time */ + /* 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 */ @@ -1018,26 +872,11 @@ struct connectdata { well be the same we read from. CURL_SOCKET_BAD disables */ - /** Dynamicly 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 */ - } allocptr; - #ifdef HAVE_GSSAPI - int sec_complete; /* if Kerberos is enabled for this connection */ - enum protection_level command_prot; - enum protection_level data_prot; - enum protection_level request_data_prot; + 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; @@ -1049,101 +888,127 @@ struct connectdata { struct kerberos5data krb5; /* variables into the structure definition, */ #endif /* however, some of them are ftp specific. */ - /* the two following *_inuse fields are only flags, not counters in any way. - If TRUE it means the channel is in use, and if FALSE it means the channel - is up for grabs by one. */ - - bool readchannel_inuse; /* whether the read channel is in use by an easy - handle */ - bool writechannel_inuse; /* whether the write channel is in use by an easy - handle */ - struct curl_llist *send_pipe; /* List of handles waiting to - send on this pipeline */ - struct curl_llist *recv_pipe; /* List of handles waiting to read - their responses on this pipeline */ - char *master_buffer; /* The master buffer allocated on-demand; - used for pipelining. */ - size_t read_pos; /* Current read position in the master buffer */ - size_t buf_len; /* Length of the buffer?? */ - - - curl_seek_callback seek_func; /* function that seeks the input */ - void *seek_client; /* pointer to pass to the seek() above */ + struct Curl_llist easyq; /* List of easy handles using this connection */ /*************** 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 */ - -#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_header; - char *response_header; -#endif #endif - char syserr_buf [256]; /* buffer for Curl_strerror() */ +#ifdef USE_SPNEGO + curlnegotiate http_negotiate_state; + curlnegotiate proxy_negotiate_state; -#ifdef CURLRES_ASYNCH - /* data used for the asynch name resolve callback */ - struct Curl_async async; + struct negotiatedata negotiate; /* state data for host Negotiate auth */ + struct negotiatedata proxyneg; /* state data for proxy Negotiate auth */ #endif - /* These three are used for chunked-encoding trailer support */ - char *trailer; /* allocated buffer to store trailer in */ - int trlMax; /* allocated buffer size */ - int trlPos; /* index of where to store data */ - union { +#ifndef CURL_DISABLE_FTP struct ftp_conn ftpc; - struct http_conn httpc; +#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; - void *generic; /* RTMP and LDAP use this */ +#endif +#ifdef USE_LIBRTMP + void *rtmp; +#endif +#ifdef USE_OPENLDAP + struct ldapconninfo *ldapc; +#endif +#ifndef CURL_DISABLE_MQTT + struct mqtt_conn mqtt; +#endif +#ifndef CURL_DISABLE_WEBSOCKETS + struct websocket *ws; +#endif + unsigned int unused:1; /* avoids empty union */ } proto; - int cselect_bits; /* bitmask of socket events */ - int waitfor; /* current READ/WRITE bits to wait for */ - -#if defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI) - int socks5_gssapi_enctype; +#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 re-using + that subsequent bound-requested connections are not 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 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 USE_IPV6 + unsigned int scope_id; /* Scope id for IPv6 */ +#endif unsigned short localport; - int localportrange; - - /* tunnel as in tunnel through a HTTP proxy with CONNECT */ - enum { - TUNNEL_INIT, /* init/default/no tunnel state */ - TUNNEL_CONNECT, /* CONNECT has been sent off */ - TUNNEL_COMPLETE /* CONNECT response received completely */ - } tunnel_state[2]; /* two separate ones to allow FTP */ - struct connectbundle *bundle; /* The bundle we are member of */ - - int negnpn; /* APLN or NPN TLS negotiated protocol, CURL_HTTP_VERSION* */ - -#ifdef USE_UNIX_SOCKETS - char *unix_domain_socket; - bool abstract_unix_socket; + unsigned short secondary_port; /* secondary socket remote port to connect to + (ftp) */ + 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. */ /* @@ -1154,99 +1019,85 @@ 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 */ - long filetime; /* If requested, this is might get set. Set to -1 if the time - was unretrievable. We cannot have this of type time_t, - since time_t is unsigned on several platforms such as - OpenVMS. */ - bool timecond; /* set to TRUE if the time condition didn't match, which - thus made the document NOT get fetched */ - long header_size; /* size of read header(s) in bytes */ - long request_size; /* the amount of bytes sent in the request(s) */ + 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 */ - - /* 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]; - long conn_primary_port; - - char conn_local_ip[MAX_IPADR_LEN]; - long conn_local_port; - + char *wouldredirect; /* URL this would have 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 primary ip_quadruple is copied over from the connectdata + struct in order to allow curl_easy_getinfo() to return this information + even when the session handle is no longer associated with a connection, + 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 pool. */ + struct ip_quadruple primary; + int conn_remote_port; /* this is the "remote port", which is the port + number of the used URL, independent of proxy or + not */ const char *conn_scheme; unsigned int conn_protocol; - - struct curl_certinfo certs; /* info about the certs, only populated in - OpenSSL builds. Asked for with + 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 did not match, which + thus made the document NOT get fetched */ + BIT(used_proxy); /* the transfer used a proxy */ }; +struct pgrs_measure { + struct curltime start; /* when measure started */ + curl_off_t start_size; /* the 'cur_size' the measure started at */ +}; + +struct pgrs_dir { + curl_off_t total_size; /* total expected bytes */ + curl_off_t cur_size; /* transferred bytes so far */ + curl_off_t speed; /* bytes per second transferred */ + struct pgrs_measure limit; +}; 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 */ + struct pgrs_dir ul; + struct pgrs_dir dl; curl_off_t current_speed; /* uses the currently fastest transfer */ - bool callback; /* set when progress callback is used */ int width; /* screen width at download start */ int flags; /* see progress.h */ - double timespent; - - curl_off_t dlspeed; - curl_off_t ulspeed; + timediff_t timespent; - double t_nslookup; - double t_connect; - double t_appconnect; - double t_pretransfer; - double t_starttransfer; - double t_redirect; + timediff_t t_postqueue; + timediff_t t_nslookup; + timediff_t t_connect; + timediff_t t_appconnect; + timediff_t t_pretransfer; + timediff_t t_posttransfer; + timediff_t t_starttransfer; + timediff_t t_redirect; - struct timeval start; - struct timeval t_startsingle; - struct timeval t_startop; - struct timeval t_acceptdata; + struct curltime start; + struct curltime t_startsingle; + struct curltime t_startop; + struct curltime t_acceptdata; - /* upload speed limit */ - struct timeval ul_limit_start; - curl_off_t ul_limit_size; - /* download speed limit */ - struct timeval dl_limit_start; - curl_off_t dl_limit_size; - -#define CURR_TIME (5+1) /* 6 entries for 5 seconds */ +#define CURR_TIME (5 + 1) /* 6 entries for 5 seconds */ curl_off_t speeder[ CURR_TIME ]; - struct timeval speeder_time[ 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 { - HTTPREQ_NONE, /* first in list */ - HTTPREQ_GET, - HTTPREQ_POST, - HTTPREQ_POST_FORM, /* we make a difference internally */ - HTTPREQ_PUT, - HTTPREQ_HEAD, - HTTPREQ_CUSTOM, - HTTPREQ_LAST /* last in list */ -} Curl_HttpReq; - typedef enum { RTSPREQ_NONE, /* first in list */ RTSPREQ_OPTIONS, @@ -1263,182 +1114,274 @@ typedef enum { RTSPREQ_LAST /* last in list */ } Curl_RtspReq; -/* - * Values that are generated, temporary or calculated internally for a - * "session handle" must be defined within the 'struct UrlState'. This struct - * will be used within the Curl_easy struct. When the 'Curl_easy' - * struct is cloned, this data MUST NOT be copied. - * - * Remember that any "state" information goes globally for the curl handle. - * Session-data MUST be put in the connectdata struct and here. */ -#define MAX_CURL_USER_LENGTH 256 -#define MAX_CURL_PASSWORD_LENGTH 256 - 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 */ - bool done; /* TRUE when the auth phase is done and ready to do the *actual* - request */ - bool multi; /* TRUE if this is not yet authenticated but within the auth - multipass negotiation */ - bool iestyle; /* TRUE if digest should be done IE-style or FALSE if it should - be RFC compliant */ + 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 */ }; -struct Curl_http2_dep { - struct Curl_http2_dep *next; +#ifdef USE_NGHTTP2 +struct Curl_data_prio_node { + struct Curl_data_prio_node *next; struct Curl_easy *data; }; +#endif -struct UrlState { +/** + * 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 +}; - /* Points to the connection cache */ - struct conncache *conn_cache; +/* 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; - /* 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! */ - bool multi_owned_by_easy; - /* buffers to store authentication data in, as parsed from input options */ - struct timeval keeps_speed; /* for the progress meter really */ +typedef enum { + TRAILERS_NONE, + TRAILERS_INITIALIZED, + TRAILERS_SENDING, + TRAILERS_DONE +} trailers_state; + - struct connectdata *lastconnect; /* The last connection, NULL if undefined */ +/* + * One instance for each timeout an easy handle can set. + */ +struct time_node { + struct Curl_llist_node list; + struct curltime time; + expire_id eid; +}; - char *headerbuff; /* allocated buffer to store headers in */ - size_t headersize; /* size of the allocation */ +/* individual pieces of the URL */ +struct urlpieces { + char *scheme; + char *hostname; + char *port; + char *user; + char *password; + char *options; + char *path; + char *query; +}; - char *buffer; /* download buffer */ - char uploadbuffer[BUFSIZE+1]; /* upload buffer */ - curl_off_t current_speed; /* the ProgressShow() funcion sets this, +struct UrlState { + /* buffers to store authentication data in, as parsed from input options */ + struct curltime keeps_speed; /* for the progress meter really */ + + curl_off_t lastconnect_id; /* The last connection, -1 if undefined */ + curl_off_t recent_conn_id; /* The most recent connection used, might no + * longer exist */ + struct dynbuf headerb; /* buffer to store headers in */ + struct curl_slist *hstslist; /* list of HSTS files set by + curl_easy_setopt(HSTS) calls */ + curl_off_t current_speed; /* the ProgressShow() function sets this, bytes / second */ - bool this_is_a_follow; /* this is a followed Location: request */ - - char *first_host; /* host name 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() data. - */ - int first_remote_port; /* remote port of the first (not followed) request */ - struct curl_ssl_session *session; /* array of 'max_ssl_sessions' size */ + + /* hostname, port number and protocol of the first (not followed) request. + if set, this should be the hostname 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 */ - char *tempwrite; /* allocated buffer to keep data in when a write - callback returns to make the connection paused */ - size_t tempwritesize; /* size of the 'tempwrite' allocated buffer */ - int tempwritetype; /* type of the 'tempwrite' buffer as a bitmask that is - used with Curl_client_write() */ - char *scratch; /* huge buffer[BUFSIZE*2] when doing upload CRLF replacing */ - bool errorbuf; /* Set to TRUE if the error buffer is already filled in. - This must be set to FALSE every time _easy_perform() is - called. */ int os_errno; /* filled in with errno whenever an error occurs */ + 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 - bool allow_port; /* Is set.use_port allowed to take effect or not. This - is always set TRUE when curl_easy_perform() is called. */ +#ifndef CURL_DISABLE_DIGEST_AUTH struct digestdata digest; /* state data for host Digest auth */ struct digestdata proxydigest; /* state data for proxy Digest auth */ - -#ifdef USE_SPNEGO - struct negotiatedata negotiate; /* state data for host Negotiate auth */ - struct negotiatedata proxyneg; /* state data for proxy Negotiate auth */ #endif - struct auth authhost; /* auth details for host */ struct auth authproxy; /* auth details for proxy */ +#ifdef USE_CURL_ASYNC + struct Curl_async async; /* asynchronous name resolver data */ +#endif - bool authproblem; /* TRUE if there's some problem authenticating */ - - void *resolver; /* resolver state, if it is used in the URL state - - ares_channel f.e. */ - -#if defined(USE_OPENSSL) && defined(HAVE_OPENSSL_ENGINE_H) - ENGINE *engine; +#if defined(USE_OPENSSL) + /* void instead of ENGINE to avoid bleeding OpenSSL into this header */ + void *engine; #endif /* USE_OPENSSL */ - struct timeval expiretime; /* set this with Curl_expire() only */ + 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 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 FTP entrypath */ + /* a place to store the most recently set (S)FTP entrypath */ char *most_recent_ftp_entrypath; - - /* set after initial USER failure, to prevent an authentication loop */ - bool ftp_trying_alternative; - - int httpversion; /* the lowest HTTP version*10 reported by any server - involved in this request */ - bool expect100header; /* TRUE if we added Expect: 100-continue */ - - bool pipe_broke; /* TRUE if the connection we were pipelined on broke - and we need to restart from the beginning */ - -#if !defined(WIN32) && !defined(MSDOS) && !defined(__EMX__) && \ - !defined(__SYMBIAN32__) -/* do FTP line-end conversions on most platforms */ -#define CURL_DO_LINEEND_CONV - /* for FTP downloads: track CRLF sequences that span blocks */ - bool prev_block_had_trailing_cr; - /* for FTP downloads: how many CRLFs did we converted to LFs? */ - curl_off_t crlf_conversions; -#endif - char *pathbuffer;/* allocated buffer to store the URL's path part in */ - char *path; /* path to use, points to somewhere within the pathbuffer - area */ - bool slash_removed; /* set TRUE if the 'path' points to a path where the - initial URL slash separator has been taken off */ - bool use_range; - bool rangestringalloc; /* the range string is malloc()'ed */ - 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 */ - - size_t drain; /* Increased when this stream has data to read, even if its - socket is not necessarily is readable. Decreased when - checked. */ - bool 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. */ +#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 */ - - struct Curl_easy *stream_depends_on; - bool stream_depends_e; /* set or don't set the Exclusive bit */ - int stream_weight; -}; - - -/* - * This 'DynamicStatic' struct defines dynamic states that actually change - * values in the 'UserDefined' area, which MUST be taken into consideration - * if the UserDefined struct is cloned or similar. You can probably just - * copy these, but each one indicate a special action on other data. - */ - -struct DynamicStatic { + CURLU *uh; /* URL handle for the current parsed URL */ + struct urlpieces up; char *url; /* work URL, copied from UserDefined */ - bool url_alloc; /* URL string is malloc()'ed */ char *referer; /* referer string */ - bool referer_alloc; /* referer sting is malloc()ed */ - struct curl_slist *cookielist; /* list of cookie files set by - curl_easy_setopt(COOKIEFILE) calls */ 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; +#ifndef CURL_DISABLE_FORM_API + curl_mimepart *formp; /* storage for old API form-posting, allocated on + demand */ +#endif + size_t trailers_bytes_sent; + struct dynbuf trailers_buf; /* a buffer containing the compiled trailing + headers */ + struct Curl_llist httphdrs; /* received headers */ + struct curl_header headerout[2]; /* for external purposes */ + struct Curl_header_store *prevhead; /* the latest added header */ + 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 + +#ifndef CURL_DISABLE_VERBOSE_STRINGS + struct curl_trc_feat *feat; /* opt. trace feature transfer is part of */ +#endif + + /* Dynamically allocated strings, MUST be freed before this struct is + killed. */ + struct dynamically_allocated_data { + char *uagent; + char *accept_encoding; + char *userpwd; + char *rangeline; + char *ref; + char *host; +#ifndef CURL_DISABLE_COOKIES + char *cookiehost; +#endif +#ifndef CURL_DISABLE_RTSP + char *rtsp_transport; +#endif + char *te; /* TE: request header */ + + /* transfer credentials */ + char *user; + char *passwd; +#ifndef CURL_DISABLE_PROXY + char *proxyuserpwd; + char *proxyuser; + char *proxypasswd; +#endif + } aptr; + + unsigned char httpwant; /* when non-zero, a specific HTTP version requested + 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 select_bits; /* != 0 -> bitmask of socket events for this + transfer overriding anything the socket may + report */ + /* 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 is some problem authenticating */ + /* set after initial USER failure, to prevent an authentication loop */ + BIT(wildcardmatch); /* enable wildcard matching */ + 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. */ +#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(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. */ }; /* @@ -1450,136 +1393,190 @@ struct DynamicStatic { * Character pointer fields point to dynamic storage, unless otherwise stated. */ -struct Curl_multi; /* declared and used only in multi.c */ +struct Curl_multi; /* declared in multihandle.c */ enum dupstring { - STRING_CERT_ORIG, /* client certificate file name */ - STRING_CERT_PROXY, /* client certificate file name */ - STRING_CERT_TYPE_ORIG, /* format for certificate (default: PEM)*/ + STRING_CERT, /* client certificate filename */ + STRING_CERT_TYPE, /* format for certificate (default: PEM)*/ + STRING_KEY, /* private key filename */ + STRING_KEY_PASSWD, /* plain text private key password */ + STRING_KEY_TYPE, /* format for private key (default: PEM) */ + STRING_SSL_CAPATH, /* CA directory name (does not work on Windows) */ + STRING_SSL_CAFILE, /* certificate file to verify peer against */ + STRING_SSL_PINNEDPUBLICKEY, /* public key file to verify peer against */ + STRING_SSL_CIPHER_LIST, /* list of ciphers to use */ + STRING_SSL_CIPHER13_LIST, /* list of TLS 1.3 ciphers to use */ + STRING_SSL_CRLFILE, /* crl file to check certificate */ + STRING_SSL_ISSUERCERT, /* issuer cert file to check certificate */ + STRING_SERVICE_NAME, /* Service name */ +#ifndef CURL_DISABLE_PROXY + STRING_CERT_PROXY, /* client certificate filename */ STRING_CERT_TYPE_PROXY, /* format for certificate (default: PEM)*/ + STRING_KEY_PROXY, /* private key filename */ + STRING_KEY_PASSWD_PROXY, /* plain text private key password */ + STRING_KEY_TYPE_PROXY, /* format for private key (default: PEM) */ + STRING_SSL_CAPATH_PROXY, /* CA directory name (does not work on Windows) */ + STRING_SSL_CAFILE_PROXY, /* certificate file to verify peer against */ + STRING_SSL_PINNEDPUBLICKEY_PROXY, /* public key file to verify proxy */ + STRING_SSL_CIPHER_LIST_PROXY, /* list of ciphers to use */ + STRING_SSL_CIPHER13_LIST_PROXY, /* list of TLS 1.3 ciphers to use */ + STRING_SSL_CRLFILE_PROXY, /* crl file to check certificate */ + STRING_SSL_ISSUERCERT_PROXY, /* issuer cert file to check certificate */ + STRING_PROXY_SERVICE_NAME, /* Proxy service name */ +#endif +#ifndef CURL_DISABLE_COOKIES STRING_COOKIE, /* HTTP cookie string to send */ STRING_COOKIEJAR, /* dump all cookies to this file */ +#endif STRING_CUSTOMREQUEST, /* HTTP/FTP/RTSP request/method to use */ - STRING_DEFAULT_PROTOCOL, /* Protocol to use when the URL doesn't specify */ + STRING_DEFAULT_PROTOCOL, /* Protocol to use when the URL does not specify */ STRING_DEVICE, /* local network interface/address to use */ + STRING_INTERFACE, /* local network interface to use */ + STRING_BINDHOST, /* local address to use */ STRING_ENCODING, /* Accept-Encoding string */ +#ifndef CURL_DISABLE_FTP STRING_FTP_ACCOUNT, /* ftp account data */ STRING_FTP_ALTERNATIVE_TO_USER, /* command to send if USER/PASS fails */ STRING_FTPPORT, /* port to send with the FTP PORT command */ - STRING_KEY_ORIG, /* private key file name */ - STRING_KEY_PROXY, /* private key file name */ - STRING_KEY_PASSWD_ORIG, /* plain text private key password */ - STRING_KEY_PASSWD_PROXY, /* plain text private key password */ - STRING_KEY_TYPE_ORIG, /* format for private key (default: PEM) */ - STRING_KEY_TYPE_PROXY, /* format for private key (default: PEM) */ +#endif +#if defined(HAVE_GSSAPI) STRING_KRB_LEVEL, /* krb security level */ +#endif +#ifndef CURL_DISABLE_NETRC STRING_NETRC_FILE, /* if not NULL, use this instead of trying to find $HOME/.netrc */ +#endif +#ifndef CURL_DISABLE_PROXY STRING_PROXY, /* proxy to use */ STRING_PRE_PROXY, /* pre socks proxy to use */ +#endif STRING_SET_RANGE, /* range, if used */ STRING_SET_REFERER, /* custom string for the HTTP referer field */ STRING_SET_URL, /* what original URL to work on */ - STRING_SSL_CAPATH_ORIG, /* CA directory name (doesn't work on windows) */ - STRING_SSL_CAPATH_PROXY, /* CA directory name (doesn't work on windows) */ - STRING_SSL_CAFILE_ORIG, /* certificate file to verify peer against */ - STRING_SSL_CAFILE_PROXY, /* certificate file to verify peer against */ - STRING_SSL_PINNEDPUBLICKEY_ORIG, /* public key file to verify peer against */ - STRING_SSL_PINNEDPUBLICKEY_PROXY, /* public key file to verify proxy */ - STRING_SSL_CIPHER_LIST_ORIG, /* list of ciphers to use */ - STRING_SSL_CIPHER_LIST_PROXY, /* list of ciphers to use */ - STRING_SSL_EGDSOCKET, /* path to file containing the EGD daemon socket */ - STRING_SSL_RANDOM_FILE, /* path to file containing "random" data */ STRING_USERAGENT, /* User-Agent string */ - STRING_SSL_CRLFILE_ORIG, /* crl file to check certificate */ - STRING_SSL_CRLFILE_PROXY, /* crl file to check certificate */ - STRING_SSL_ISSUERCERT_ORIG, /* 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, /* , if used */ STRING_PASSWORD, /* , if used */ STRING_OPTIONS, /* , if used */ +#ifndef CURL_DISABLE_PROXY STRING_PROXYUSERNAME, /* Proxy , if used */ STRING_PROXYPASSWORD, /* Proxy , if used */ STRING_NOPROXY, /* List of hosts which should not use the proxy, if used */ +#endif +#ifndef CURL_DISABLE_RTSP STRING_RTSP_SESSION_ID, /* Session ID to use */ STRING_RTSP_STREAM_URI, /* Stream URI for this request */ STRING_RTSP_TRANSPORT, /* Transport for this session */ -#ifdef USE_LIBSSH2 +#endif +#ifdef USE_SSH STRING_SSH_PRIVATE_KEY, /* path to the private key file for auth */ STRING_SSH_PUBLIC_KEY, /* path to the public key file for auth */ - STRING_SSH_HOST_PUBLIC_KEY_MD5, /* md5 of host public key in ascii hex */ - STRING_SSH_KNOWNHOSTS, /* file name of knownhosts file */ -#endif -#if defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI) - STRING_PROXY_SERVICE_NAME, /* Proxy service name */ -#endif -#if !defined(CURL_DISABLE_CRYPTO_AUTH) || defined(USE_KERBEROS5) || \ - defined(USE_SPNEGO) - STRING_SERVICE_NAME, /* Service name */ + 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, /* filename of knownhosts file */ #endif +#ifndef CURL_DISABLE_SMTP STRING_MAIL_FROM, STRING_MAIL_AUTH, - +#endif #ifdef USE_TLS_SRP - STRING_TLSAUTH_USERNAME_ORIG, /* TLS auth */ + STRING_TLSAUTH_USERNAME, /* TLS auth */ + STRING_TLSAUTH_PASSWORD, /* TLS auth */ +#ifndef CURL_DISABLE_PROXY STRING_TLSAUTH_USERNAME_PROXY, /* TLS auth */ - STRING_TLSAUTH_PASSWORD_ORIG, /* TLS auth */ STRING_TLSAUTH_PASSWORD_PROXY, /* TLS auth */ +#endif #endif STRING_BEARER, /* , if used */ #ifdef USE_UNIX_SOCKETS STRING_UNIX_SOCKET_PATH, /* path to Unix socket, if used */ #endif + STRING_TARGET, /* CURLOPT_REQUEST_TARGET */ +#ifndef CURL_DISABLE_DOH + STRING_DOH, /* CURLOPT_DOH_URL */ +#endif +#ifndef CURL_DISABLE_ALTSVC + STRING_ALTSVC, /* CURLOPT_ALTSVC */ +#endif +#ifndef CURL_DISABLE_HSTS + STRING_HSTS, /* CURLOPT_HSTS */ +#endif + STRING_SASL_AUTHZID, /* CURLOPT_SASL_AUTHZID */ +#ifdef USE_ARES + STRING_DNS_SERVERS, + STRING_DNS_INTERFACE, + STRING_DNS_LOCAL_IP4, + STRING_DNS_LOCAL_IP6, +#endif + STRING_SSL_EC_CURVES, +#ifndef CURL_DISABLE_AWS + STRING_AWS_SIGV4, /* Parameters for V4 signature */ +#endif +#ifndef CURL_DISABLE_PROXY + STRING_HAPROXY_CLIENT_IP, /* CURLOPT_HAPROXY_CLIENT_IP */ +#endif + STRING_ECH_CONFIG, /* CURLOPT_ECH_CONFIG */ + STRING_ECH_PUBLIC, /* CURLOPT_ECH_PUBLIC */ - /* -- end of zero-terminated strings -- */ + /* -- end of null-terminated strings -- */ STRING_LASTZEROTERMINATED, - /* -- below this are pointers to binary data that cannot be strdup'ed. - Each such pointer must be added manually to Curl_dupset() --- */ + /* -- 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_KEY, + BLOB_SSL_ISSUERCERT, + BLOB_CAINFO, +#ifndef CURL_DISABLE_PROXY + BLOB_CERT_PROXY, + BLOB_KEY_PROXY, + BLOB_SSL_ISSUERCERT_PROXY, + BLOB_CAINFO_PROXY, +#endif + 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 */ - long proxyport; /* If non-zero, use this port number by default. If the - proxy string features a ":[port]" that one will override - this. */ void *out; /* CURLOPT_WRITEDATA */ void *in_set; /* CURLOPT_READDATA */ void *writeheader; /* write the header to this if non-NULL */ - void *rtp_out; /* write RTP to this if non-NULL */ - long use_port; /* which port to use (when not using default) */ + 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 followlocation; /* as in HTTP Location: */ long maxredirs; /* maximum no. of http(s) redirects to follow, set to -1 for infinity */ - int keep_post; /* keep POSTs as POSTs after a 30x request; each - bit represents a request, from 301 to 303 */ - bool free_referer; /* set TRUE if 'referer' points to a string we - allocated */ 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 */ - int localportrange; /* number of additional port numbers to test in case the - 'localport' one can't be bind()ed */ + unsigned short localportrange; /* number of additional port numbers to test + in case the 'localport' one cannot 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 */ - int is_fread_set; /* boolean, has read callback been set to non-NULL? */ - int is_fwrite_set; /* boolean, has write callback been set to non-NULL? */ curl_progress_callback fprogress; /* OLD and deprecated progress callback */ curl_xferinfo_callback fxferinfo; /* progress callback */ curl_debug_callback fdebug; /* function that write informational data */ @@ -1593,24 +1590,30 @@ struct UserDefined { 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 */ - /* the 3 curl_conv_callback functions below are used on non-ASCII hosts */ - /* function to convert from the network encoding: */ - curl_conv_callback convfromnetwork; - /* function to convert to the network encoding: */ - curl_conv_callback convtonetwork; - /* function to convert from UTF-8 encoding: */ - curl_conv_callback convfromutf8; - +#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 */ - long timeout; /* in milliseconds, 0 means no timeout */ - long connecttimeout; /* in milliseconds, 0 means no timeout */ - long accepttimeout; /* in milliseconds, 0 means no timeout */ - long server_response_timeout; /* in milliseconds, 0 means no timeout */ + unsigned int timeout; /* ms, 0 means no timeout */ + unsigned int connecttimeout; /* ms, 0 means default timeout */ + unsigned int happy_eyeballs_timeout; /* ms, 0 is a valid value */ + unsigned int server_response_timeout; /* ms, 0 means no timeout */ + unsigned int shutdowntimeout; /* ms, 0 means default timeout */ + long maxage_conn; /* in seconds, max idle time to allow a connection that + is to be reused */ + long maxlifetime_conn; /* in seconds, max time since creation to allow a + connection that is to be reused */ +#ifndef CURL_DISABLE_TFTP long tftp_blksize; /* in bytes, 0 means use default */ - bool tftp_no_options; /* do not send TFTP options requests */ +#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 */ @@ -1619,118 +1622,87 @@ struct UserDefined { download */ curl_off_t set_resume_from; /* continue [ftp] transfer from here */ struct curl_slist *headers; /* linked list of extra headers */ - struct curl_slist *proxyheaders; /* linked list of extra CONNECT headers */ - struct curl_httppost *httppost; /* linked list of POST data */ - bool sep_headers; /* handle host and proxy headers separately */ - bool cookiesession; /* new cookie session? */ - bool crlf; /* convert crlf on ftp upload(?) */ - struct curl_slist *quote; /* after connection is established */ - struct curl_slist *postquote; /* after the transfer */ - struct curl_slist *prequote; /* before the transfer, after type */ - struct curl_slist *source_quote; /* 3rd party quote */ - struct curl_slist *source_prequote; /* in 3rd party transfer mode - before - the transfer on source host */ - struct curl_slist *source_postquote; /* in 3rd party transfer mode - after - the transfer on source host */ + struct curl_httppost *httppost; /* linked list of old POST data */ +#if !defined(CURL_DISABLE_MIME) || !defined(CURL_DISABLE_FORM_API) + curl_mimepart mimepost; /* MIME/POST data. */ +#endif +#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 */ - curl_TimeCond timecondition; /* kind of time/date comparison */ time_t timevalue; /* what time to compare with */ - Curl_HttpReq httpreq; /* what kind of HTTP request (if any) is this */ - long httpversion; /* when non-zero, a specific HTTP version requested to - be used in the library's request(s) */ + 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 */ - curl_proxytype proxytype; /* what kind of proxy that is in use */ - long dns_cache_timeout; /* DNS cache timeout */ - long buffer_size; /* size of receive buffer to use */ + 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 */ - - long ipver; /* the CURL_IPRESOLVE_* defines in the public header file - 0 - whatever, 1 - v2, 2 - v6 */ - +#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 */ - - curl_ftpfile ftp_filemethod; /* how to get to a file when FTP is used */ - - int ftp_create_missing_dirs; /* 1 - create directories that don't exist - 2 - the same but also allow MKD to fail once - */ - +#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 do not 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 */ - -/* 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. */ - - bool printhost; /* printing host name in debug info */ - bool get_filetime; /* get the time and get of the remote file */ - bool tunnel_thru_httpproxy; /* use CONNECT through a HTTP proxy */ - bool prefer_ascii; /* ASCII rather than binary */ - bool ftp_append; /* append, not overwrite, on upload */ - bool ftp_list_only; /* switch FTP command for listing directories */ - bool ftp_use_port; /* use the FTP PORT command */ - bool hide_progress; /* don't use the progress meter */ - bool http_fail_on_error; /* fail on HTTP error codes >= 400 */ - bool http_keep_sending_on_error; /* for HTTP status codes >= 300 */ - bool http_follow_location; /* follow HTTP redirects */ - bool http_transfer_encoding; /* request compressed HTTP transfer-encoding */ - bool http_disable_hostname_check_before_authentication; - bool include_header; /* include received protocol headers in data output */ - bool http_set_referer; /* is a custom referer used */ - bool http_auto_referer; /* set "correct" referer when following location: */ - bool opt_no_body; /* as set with CURLOPT_NOBODY */ - bool upload; /* upload request */ - enum CURL_NETRC_OPTION - use_netrc; /* defined in include/curl.h */ - bool verbose; /* output verbosity */ - bool krb; /* Kerberos connection requested */ - bool reuse_forbid; /* forbidden to be reused, close after use */ - bool reuse_fresh; /* do not re-use an existing connection */ - bool ftp_use_epsv; /* if EPSV is to be attempted or not */ - bool ftp_use_eprt; /* if EPRT is to be attempted or not */ - bool ftp_use_pret; /* if PRET is to be used before PASV or not */ - - curl_usessl use_ssl; /* if AUTH TLS is to be attempted etc, for FTP or - IMAP or POP3 or others! */ - curl_ftpauth ftpsslauth; /* what AUTH XXX to be attempted */ - curl_ftpccc ftp_ccc; /* FTP CCC options */ - bool no_signal; /* do not use any signal/alarm handler */ - bool global_dns_cache; /* subject for future removal */ - bool tcp_nodelay; /* whether to enable TCP_NODELAY or not */ - bool ignorecl; /* ignore content length */ - bool ftp_skip_ip; /* skip the IP address the FTP server passes on to - us */ - bool connect_only; /* make connection, let application use the socket */ - long ssh_auth_types; /* allowed SSH auth types */ - bool http_te_skip; /* pass the raw body data to the user, even when - transfer-encoded (chunked, compressed) */ - bool http_ce_skip; /* pass the raw body data to the user, even when - content-encoded (chunked, compressed) */ - long new_file_perms; /* Permissions to use when creating remote files */ - long new_directory_perms; /* Permissions to use when creating remote dirs */ - bool proxy_transfer_mode; /* set transfer mode (;type=) when doing FTP - via an HTTP proxy */ + 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 USE_IPV6 unsigned int scope_id; /* Scope id for IPv6 */ - long allowed_protocols; - long redir_protocols; -#if defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI) - bool socks5_gssapi_nec; /* Flag to support NEC SOCKS5 server */ #endif - struct curl_slist *mail_rcpt; /* linked list of mail recipients */ - bool sasl_ir; /* Enable/disable SASL initial response */ + 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 */ - long rtspversion; /* like httpversion, for RTSP */ - bool wildcardmatch; /* enable wildcard matching */ +#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 @@ -1738,38 +1710,158 @@ struct UserDefined { 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; - long gssapi_delegation; /* GSS-API credential delegation, see the - documentation of CURLOPT_GSSAPI_DELEGATION */ - - bool tcp_keepalive; /* use TCP keepalives */ - long tcp_keepidle; /* seconds in idle before sending keepalive probe */ - long tcp_keepintvl; /* seconds between TCP keepalive probes */ - bool tcp_fastopen; /* use TCP Fast Open */ - - size_t maxconnects; /* Max idle connections in the connection cache */ + int tcp_keepidle; /* seconds in idle before sending keepalive probe */ + int tcp_keepintvl; /* seconds between TCP keepalive probes */ + int tcp_keepcnt; /* maximum number of keepalive probes */ - bool ssl_enable_npn; /* TLS NPN extension? */ - bool ssl_enable_alpn; /* TLS ALPN extension? */ - bool path_as_is; /* allow dotdots? */ - bool pipewait; /* wait for pipe/multiplex status before starting a - new connection */ 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 + curl_off_t dohfor_mid; /* 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(?) */ +#ifdef USE_SSH + BIT(ssh_compression); /* enable SSH compression */ +#endif - struct Curl_easy *stream_depends_on; - bool stream_depends_e; /* set or don't set the Exclusive bit */ - int stream_weight; - - struct Curl_http2_dep *stream_dependents; - - bool abstract_unix_socket; +/* 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 do not change during operations. */ + BIT(quick_exit); /* set 1L when it is okay to leak things (like + threads), as we are about to exit() anyway and + do not want lengthy cleanups to delay termination, + e.g. after a DNS timeout */ + BIT(get_filetime); /* get the time and get of the remote file */ +#ifndef CURL_DISABLE_PROXY + BIT(tunnel_thru_httpproxy); /* use CONNECT through an HTTP proxy */ +#endif + BIT(prefer_ascii); /* ASCII rather than binary */ + BIT(remote_append); /* append, not overwrite, on upload */ +#ifdef CURL_LIST_ONLY_PROTOCOL + 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); /* do not 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 */ +#if defined(HAVE_GSSAPI) + BIT(krb); /* Kerberos connection requested */ +#endif + BIT(reuse_forbid); /* forbidden to be reused, close after use */ + BIT(reuse_fresh); /* do not reuse an existing connection */ + BIT(no_signal); /* do not use any signal/alarm handler */ + 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=) 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 */ +#ifndef CURL_DISABLE_PROXY + BIT(haproxyprotocol); /* whether to send HAProxy PROXY protocol v1 + header */ +#endif +#ifdef USE_UNIX_SOCKETS + BIT(abstract_unix_socket); +#endif + BIT(disallow_username_in_url); /* disallow username in URL */ +#ifndef CURL_DISABLE_DOH + BIT(doh); /* DNS-over-HTTPS enabled */ + 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 */ +#ifndef CURL_DISABLE_WEBSOCKETS + BIT(ws_raw_mode); +#endif +#ifdef USE_ECH + int tls_ech; /* TLS ECH configuration */ +#endif }; +#ifndef CURL_DISABLE_MIME +#define IS_MIME_POST(a) ((a)->set.mimepost.kind != MIMEKIND_NONE) +#else +#define IS_MIME_POST(a) FALSE +#endif + struct Names { - struct curl_hash *hostcache; + struct Curl_hash *hostcache; enum { HCACHE_NONE, /* not pointing to anything */ - HCACHE_GLOBAL, /* points to the (shrug) global one */ HCACHE_MULTI, /* points to a shared one in the multi handle */ HCACHE_SHARED /* points to a shared one in a shared object */ } hostcachetype; @@ -1786,11 +1878,26 @@ struct Names { */ struct Curl_easy { - /* first, two fields for the linked list of these */ - struct Curl_easy *next; - struct Curl_easy *prev; - - struct connectdata *easy_conn; /* the "unit's" connection */ + /* 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 pool + a non-negative number to distinguish this transfer from + other using the same pool. For easier tracking + in log output. + This may wrap around after LONG_MAX to 0 again, so it + has no uniqueness guarantee for very large processings. + Note: it has no uniqueness either IFF more than one connection pool + is used by the libcurl application. */ + curl_off_t id; + /* once an easy handle is added to a multi, either explicitly by the + * libcurl application or implicitly during `curl_easy_perform()`, + * a unique identifier inside this one multi instance. */ + curl_off_t mid; + + struct connectdata *conn; + struct Curl_llist_node multi_queue; /* for multihandle list management */ + struct Curl_llist_node conn_queue; /* list per connectdata */ CURLMstate mstate; /* the handle's state */ CURLcode result; /* previous result */ @@ -1801,8 +1908,7 @@ struct Curl_easy { 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(). */ - curl_socket_t sockets[MAX_SOCKSPEREASYHANDLE]; - int numsocks; + struct easy_pollset last_poll; struct Names dns; struct Curl_multi *multi; /* if non-NULL, points to the multi handle @@ -1812,26 +1918,35 @@ struct Curl_easy { 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 */ - struct DynamicStatic change; /* possibly modified userdefined data */ +#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 */ - struct WildcardData wildcard; /* wildcard download state info */ +#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 */ -#if defined(CURL_DOES_CONVERSIONS) && defined(HAVE_ICONV) - iconv_t outbound_cd; /* for translating to the network encoding */ - iconv_t inbound_cd; /* for translating from the network encoding */ - iconv_t utf8_cd; /* for translating to UTF8 */ -#endif /* CURL_DOES_CONVERSIONS && HAVE_ICONV */ - unsigned int magic; /* set to a CURLEASY_MAGIC_NUMBER */ +#ifdef USE_HYPER + struct hyptransfer hyp; +#endif }; #define LIBCURL_NAME "libcurl" diff --git a/contrib/curl/lib/vauth/.checksrc b/contrib/curl/lib/vauth/.checksrc new file mode 100644 index 0000000000..9066946c89 --- /dev/null +++ b/contrib/curl/lib/vauth/.checksrc @@ -0,0 +1,2 @@ +enable STRERROR +enable STRNCPY diff --git a/contrib/curl/lib/vauth/cleartext.c b/contrib/curl/lib/vauth/cleartext.c index a761ae7846..cf8108ac5b 100644 --- a/contrib/curl/lib/vauth/cleartext.c +++ b/contrib/curl/lib/vauth/cleartext.c @@ -5,11 +5,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2016, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -18,6 +18,8 @@ * 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 * @@ -25,12 +27,14 @@ #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 #include "urldata.h" #include "vauth/vauth.h" -#include "curl_base64.h" -#include "curl_md5.h" #include "warnless.h" #include "strtok.h" #include "sendf.h" @@ -48,118 +52,86 @@ * * Parameters: * - * data [in] - The session handle. - * userp [in] - The user name. - * passdwp [in] - The user's password. - * 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. + * 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(struct Curl_easy *data, - const char *userp, - const char *passwdp, - char **outptr, size_t *outlen) +CURLcode Curl_auth_create_plain_message(const char *authzid, + const char *authcid, + const char *passwd, + struct bufref *out) { - CURLcode result; char *plainauth; - size_t ulen; - size_t plen; size_t plainlen; + size_t zlen; + size_t clen; + size_t plen; - *outlen = 0; - *outptr = NULL; - ulen = strlen(userp); - plen = strlen(passwdp); + zlen = (authzid == NULL ? 0 : strlen(authzid)); + clen = strlen(authcid); + plen = strlen(passwd); - /* Compute binary message length, checking for overflows. */ - plainlen = 2 * ulen; - if(plainlen < ulen) - return CURLE_OUT_OF_MEMORY; - plainlen += plen; - if(plainlen < plen) - return CURLE_OUT_OF_MEMORY; - plainlen += 2; - if(plainlen < 2) + /* 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); + plainauth = malloc(plainlen + 1); if(!plainauth) return CURLE_OUT_OF_MEMORY; /* Calculate the reply */ - memcpy(plainauth, userp, ulen); - plainauth[ulen] = '\0'; - memcpy(plainauth + ulen + 1, userp, ulen); - plainauth[2 * ulen + 1] = '\0'; - memcpy(plainauth + 2 * ulen + 2, passwdp, plen); - - /* Base64 encode the reply */ - result = Curl_base64_encode(data, plainauth, plainlen, outptr, outlen); - free(plainauth); - - return result; + 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. + * username or password ready for sending to the recipient. * * Parameters: * - * data [in] - The session handle. - * valuep [in] - The user name or user's password. - * 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. + * valuep [in] - The username or user's password. + * out [out] - The result storage. * - * Returns CURLE_OK on success. + * Returns void. */ -CURLcode Curl_auth_create_login_message(struct Curl_easy *data, - const char *valuep, char **outptr, - size_t *outlen) +void Curl_auth_create_login_message(const char *valuep, struct bufref *out) { - size_t vlen = strlen(valuep); - - if(!vlen) { - /* Calculate an empty reply */ - *outptr = strdup("="); - if(*outptr) { - *outlen = (size_t) 1; - return CURLE_OK; - } - - *outlen = 0; - return CURLE_OUT_OF_MEMORY; - } - - /* Base64 encode the value */ - return Curl_base64_encode(data, valuep, vlen, outptr, outlen); + Curl_bufref_set(out, valuep, strlen(valuep), NULL); } /* * 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. + * the username ready for sending to the recipient. * * Parameters: * - * data [in] - The session handle. - * user [in] - The user name. - * 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. + * user [in] - The username. + * out [out] - The result storage. * - * Returns CURLE_OK on success. + * Returns void. */ -CURLcode Curl_auth_create_external_message(struct Curl_easy *data, - const char *user, char **outptr, - size_t *outlen) +void Curl_auth_create_external_message(const char *user, + struct bufref *out) { /* This is the same formatting as the login message */ - return Curl_auth_create_login_message(data, user, outptr, outlen); + Curl_auth_create_login_message(user, out); } + +#endif /* if no users */ diff --git a/contrib/curl/lib/vauth/cram.c b/contrib/curl/lib/vauth/cram.c index 3074a163a1..f8bdd5458d 100644 --- a/contrib/curl/lib/vauth/cram.c +++ b/contrib/curl/lib/vauth/cram.c @@ -5,11 +5,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2016, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -18,19 +18,20 @@ * 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" -#if !defined(CURL_DISABLE_CRYPTO_AUTH) +#ifndef CURL_DISABLE_DIGEST_AUTH #include #include "urldata.h" #include "vauth/vauth.h" -#include "curl_base64.h" #include "curl_hmac.h" #include "curl_md5.h" #include "warnless.h" @@ -40,69 +41,31 @@ #include "curl_memory.h" #include "memdebug.h" -/* - * Curl_auth_decode_cram_md5_message() - * - * This is used to decode an already encoded CRAM-MD5 challenge message. - * - * Parameters: - * - * chlg64 [in] - The base64 encoded challenge message. - * 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_decode_cram_md5_message(const char *chlg64, char **outptr, - size_t *outlen) -{ - CURLcode result = CURLE_OK; - size_t chlg64len = strlen(chlg64); - - *outptr = NULL; - *outlen = 0; - - /* Decode the challenge if necessary */ - if(chlg64len && *chlg64 != '=') - result = Curl_base64_decode(chlg64, (unsigned char **) outptr, outlen); - - return result; -} /* * Curl_auth_create_cram_md5_message() * - * This is used to generate an already encoded CRAM-MD5 response message ready - * for sending to the recipient. + * This is used to generate a CRAM-MD5 response message ready for sending to + * the recipient. * * Parameters: * - * data [in] - The session handle. * chlg [in] - The challenge. - * userp [in] - The user name. - * passdwp [in] - The user's password. - * 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. + * userp [in] - The username. + * passwdp [in] - The user's password. + * out [out] - The result storage. * * Returns CURLE_OK on success. */ -CURLcode Curl_auth_create_cram_md5_message(struct Curl_easy *data, - const char *chlg, +CURLcode Curl_auth_create_cram_md5_message(const struct bufref *chlg, const char *userp, const char *passwdp, - char **outptr, size_t *outlen) + struct bufref *out) { - CURLcode result = CURLE_OK; - size_t chlglen = 0; - HMAC_context *ctxt; + struct HMAC_context *ctxt; unsigned char digest[MD5_DIGEST_LEN]; char *response; - if(chlg) - chlglen = strlen(chlg); - /* Compute the digest using the password as the key */ ctxt = Curl_HMAC_init(Curl_HMAC_MD5, (const unsigned char *) passwdp, @@ -111,9 +74,9 @@ CURLcode Curl_auth_create_cram_md5_message(struct Curl_easy *data, return CURLE_OUT_OF_MEMORY; /* Update the digest with the given challenge */ - if(chlglen > 0) - Curl_HMAC_update(ctxt, (const unsigned char *) chlg, - curlx_uztoui(chlglen)); + 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); @@ -127,12 +90,8 @@ CURLcode Curl_auth_create_cram_md5_message(struct Curl_easy *data, if(!response) return CURLE_OUT_OF_MEMORY; - /* Base64 encode the response */ - result = Curl_base64_encode(data, response, 0, outptr, outlen); - - free(response); - - return result; + Curl_bufref_set(out, response, strlen(response), curl_free); + return CURLE_OK; } -#endif /* !CURL_DISABLE_CRYPTO_AUTH */ +#endif /* !CURL_DISABLE_DIGEST_AUTH */ diff --git a/contrib/curl/lib/vauth/digest.c b/contrib/curl/lib/vauth/digest.c index 7d9200ad06..4fc5b1c28f 100644 --- a/contrib/curl/lib/vauth/digest.c +++ b/contrib/curl/lib/vauth/digest.c @@ -5,11 +5,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2016, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -18,13 +18,16 @@ * 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" -#if !defined(CURL_DISABLE_CRYPTO_AUTH) +#ifndef CURL_DISABLE_DIGEST_AUTH #include @@ -34,11 +37,12 @@ #include "curl_base64.h" #include "curl_hmac.h" #include "curl_md5.h" +#include "curl_sha256.h" +#include "curl_sha512_256.h" #include "vtls/vtls.h" #include "warnless.h" #include "strtok.h" #include "strcase.h" -#include "non-ascii.h" /* included for Curl_convert_... prototypes */ #include "curl_printf.h" #include "rand.h" @@ -46,6 +50,15 @@ #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) @@ -54,18 +67,7 @@ #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" - -/* The CURL_OUTPUT_DIGEST_CONV macro below is for non-ASCII machines. - It converts digest text to ASCII so the MD5 will be correct for - what ultimately goes over the network. -*/ -#define CURL_OUTPUT_DIGEST_CONV(a, b) \ - result = Curl_convert_to_network(a, (char *)b, strlen((const char *)b)); \ - if(result) { \ - free(b); \ - return result; \ - } -#endif /* !USE_WINDOWS_SSPI */ +#endif bool Curl_auth_digest_get_pair(const char *str, char *value, char *content, const char **endptr) @@ -89,44 +91,49 @@ bool Curl_auth_digest_get_pair(const char *str, char *value, char *content, } for(c = DIGEST_MAX_CONTENT_LENGTH - 1; *str && c--; str++) { - switch(*str) { - case '\\': - if(!escape) { - /* possibly the start of an escaped quote */ - escape = TRUE; - *content++ = '\\'; /* Even though this is an escape character, we still - store it as-is in the target buffer */ - 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 */ - c = 0; - continue; + 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 did not get a starting + quote and then we do "sloppy" parsing */ + c = 0; /* the end */ + continue; + } + break; - case '\"': - if(!escape && starts_with_quote) { + 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; } - break; } escape = FALSE; *content++ = *str; } + if(escape) + return FALSE; /* No character after backslash */ *content = 0; *endptr = str; @@ -135,19 +142,28 @@ bool Curl_auth_digest_get_pair(const char *str, char *value, char *content, } #if !defined(USE_WINDOWS_SSPI) -/* Convert md5 chunk to RFC2617 (section 3.1.3) -suitable ascii string*/ +/* 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++) - snprintf((char *) &dest[i * 2], 3, "%02x", source[i]); + msnprintf((char *) &dest[i * 2], 3, "%02x", source[i]); +} + +/* Convert sha256 or SHA-512/256 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, *d; + char *dest; const char *s = source; size_t n = 1; /* null terminator */ @@ -162,15 +178,15 @@ static char *auth_digest_string_quoted(const char *source) dest = malloc(n); if(dest) { + char *d = dest; s = source; - d = dest; while(*s) { if(*s == '"' || *s == '\\') { *d++ = '\\'; } *d++ = *s++; } - *d = 0; + *d = '\0'; } return dest; @@ -205,7 +221,7 @@ static CURLcode auth_digest_get_qop_values(const char *options, int *value) { char *tmp; char *token; - char *tok_buf; + char *tok_buf = NULL; /* Initialise the output */ *value = 0; @@ -217,7 +233,7 @@ static CURLcode auth_digest_get_qop_values(const char *options, int *value) return CURLE_OUT_OF_MEMORY; token = strtok_r(tmp, ",", &tok_buf); - while(token != NULL) { + 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)) @@ -237,11 +253,11 @@ static CURLcode auth_digest_get_qop_values(const char *options, int *value) * auth_decode_digest_md5_message() * * This is used internally to decode an already encoded DIGEST-MD5 challenge - * message into the seperate attributes. + * message into the separate attributes. * * Parameters: * - * chlg64 [in] - The base64 encoded challenge message. + * 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. @@ -253,55 +269,35 @@ static CURLcode auth_digest_get_qop_values(const char *options, int *value) * * Returns CURLE_OK on success. */ -static CURLcode auth_decode_digest_md5_message(const char *chlg64, +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) { - CURLcode result = CURLE_OK; - unsigned char *chlg = NULL; - size_t chlglen = 0; - size_t chlg64len = strlen(chlg64); - - /* Decode the base-64 encoded challenge message */ - if(chlg64len && *chlg64 != '=') { - result = Curl_base64_decode(chlg64, &chlg, &chlglen); - if(result) - return result; - } + const char *chlg = (const char *) Curl_bufref_ptr(chlgref); /* Ensure we have a valid challenge message */ - if(!chlg) + if(!Curl_bufref_len(chlgref)) return CURLE_BAD_CONTENT_ENCODING; /* Retrieve nonce string from the challenge */ - if(!auth_digest_get_key_value((char *) chlg, "nonce=\"", nonce, nlen, - '\"')) { - free(chlg); + 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((char *) chlg, "realm=\"", realm, rlen, - '\"')) { + if(!auth_digest_get_key_value(chlg, "realm=\"", realm, rlen, '\"')) { /* Challenge does not have a realm, set empty string [RFC2831] page 6 */ - strcpy(realm, ""); + *realm = '\0'; } /* Retrieve algorithm string from the challenge */ - if(!auth_digest_get_key_value((char *) chlg, "algorithm=", alg, alen, ',')) { - free(chlg); + 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((char *) chlg, "qop=\"", qop, qlen, '\"')) { - free(chlg); + if(!auth_digest_get_key_value(chlg, "qop=\"", qop, qlen, '\"')) return CURLE_BAD_CONTENT_ENCODING; - } - - free(chlg); return CURLE_OK; } @@ -329,26 +325,23 @@ bool Curl_auth_is_digest_supported(void) * Parameters: * * data [in] - The session handle. - * chlg64 [in] - The base64 encoded challenge message. - * userp [in] - The user name. - * passdwp [in] - The user's password. + * chlg [in] - The challenge message. + * userp [in] - The username. + * passwdp [in] - The user's password. * service [in] - The service type such as http, smtp, pop or imap. - * 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. + * out [out] - The result storage. * * Returns CURLE_OK on success. */ CURLcode Curl_auth_create_digest_md5_message(struct Curl_easy *data, - const char *chlg64, + const struct bufref *chlg, const char *userp, const char *passwdp, const char *service, - char **outptr, size_t *outlen) + struct bufref *out) { - CURLcode result = CURLE_OK; size_t i; - MD5_context *ctxt; + struct MD5_context *ctxt; char *response = NULL; unsigned char digest[MD5_DIGEST_LEN]; char HA1_hex[2 * MD5_DIGEST_LEN + 1]; @@ -360,17 +353,19 @@ CURLcode Curl_auth_create_digest_md5_message(struct Curl_easy *data, char qop_options[64]; int qop_values; char cnonce[33]; - unsigned int entropy[4]; char nonceCount[] = "00000001"; char method[] = "AUTHENTICATE"; char qop[] = DIGEST_QOP_VALUE_STRING_AUTH; char *spn = NULL; - /* Decode the challange message */ - result = auth_decode_digest_md5_message(chlg64, nonce, sizeof(nonce), - realm, sizeof(realm), - algorithm, sizeof(algorithm), - qop_options, sizeof(qop_options)); + /* 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; @@ -387,15 +382,11 @@ CURLcode Curl_auth_create_digest_md5_message(struct Curl_easy *data, if(!(qop_values & DIGEST_QOP_VALUE_AUTH)) return CURLE_BAD_CONTENT_ENCODING; - /* Generate 16 bytes of random data */ - result = Curl_rand(data, &entropy[0], 4); + /* Generate 32 random hex chars, 32 bytes + 1 null-termination */ + result = Curl_rand_hex(data, (unsigned char *)cnonce, sizeof(cnonce)); if(result) return result; - /* Convert the random data into a 32 byte hex string */ - snprintf(cnonce, sizeof(cnonce), "%08x%08x%08x%08x", - entropy[0], entropy[1], entropy[2], entropy[3]); - /* So far so good, now calculate A1 and H(A1) according to RFC 2831 */ ctxt = Curl_MD5_init(Curl_DIGEST_MD5); if(!ctxt) @@ -426,10 +417,10 @@ CURLcode Curl_auth_create_digest_md5_message(struct Curl_easy *data, /* Convert calculated 16 octet hex into 32 bytes string */ for(i = 0; i < MD5_DIGEST_LEN; i++) - snprintf(&HA1_hex[2 * i], 3, "%02x", digest[i]); + msnprintf(&HA1_hex[2 * i], 3, "%02x", digest[i]); /* Generate our SPN */ - spn = Curl_auth_build_spn(service, realm, NULL); + spn = Curl_auth_build_spn(service, data->conn->host.name, NULL); if(!spn) return CURLE_OUT_OF_MEMORY; @@ -449,7 +440,7 @@ CURLcode Curl_auth_create_digest_md5_message(struct Curl_easy *data, Curl_MD5_final(ctxt, digest); for(i = 0; i < MD5_DIGEST_LEN; i++) - snprintf(&HA2_hex[2 * i], 3, "%02x", digest[i]); + msnprintf(&HA2_hex[2 * i], 3, "%02x", digest[i]); /* Now calculate the response hash */ ctxt = Curl_MD5_init(Curl_DIGEST_MD5); @@ -479,7 +470,7 @@ CURLcode Curl_auth_create_digest_md5_message(struct Curl_easy *data, Curl_MD5_final(ctxt, digest); for(i = 0; i < MD5_DIGEST_LEN; i++) - snprintf(&resp_hash_hex[2 * i], 3, "%02x", digest[i]); + msnprintf(&resp_hash_hex[2 * i], 3, "%02x", digest[i]); /* Generate the response */ response = aprintf("username=\"%s\",realm=\"%s\",nonce=\"%s\"," @@ -491,18 +482,15 @@ CURLcode Curl_auth_create_digest_md5_message(struct Curl_easy *data, if(!response) return CURLE_OUT_OF_MEMORY; - /* Base64 encode the response */ - result = Curl_base64_encode(data, response, 0, outptr, outlen); - - free(response); - + /* 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 a HTTP DIGEST challenge message into the seperate + * This is used to decode an HTTP DIGEST challenge message into the separate * attributes. * * Parameters: @@ -533,7 +521,7 @@ CURLcode Curl_auth_decode_digest_http_message(const char *chlg, char content[DIGEST_MAX_CONTENT_LENGTH]; /* Pass all additional spaces here */ - while(*chlg && ISSPACE(*chlg)) + while(*chlg && ISBLANK(*chlg)) chlg++; /* Extract a value=content pair */ @@ -563,7 +551,7 @@ CURLcode Curl_auth_decode_digest_http_message(const char *chlg, return CURLE_OUT_OF_MEMORY; } else if(strcasecompare(value, "qop")) { - char *tok_buf; + 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); @@ -571,7 +559,10 @@ CURLcode Curl_auth_decode_digest_http_message(const char *chlg, return CURLE_OUT_OF_MEMORY; token = strtok_r(tmp, ",", &tok_buf); - while(token != NULL) { + while(token) { + /* Pass additional spaces here */ + while(*token && ISBLANK(*token)) + token++; if(strcasecompare(token, DIGEST_QOP_VALUE_STRING_AUTH)) { foundAuth = TRUE; } @@ -604,21 +595,44 @@ CURLcode Curl_auth_decode_digest_http_message(const char *chlg, return CURLE_OUT_OF_MEMORY; if(strcasecompare(content, "MD5-sess")) - digest->algo = CURLDIGESTALGO_MD5SESS; + digest->algo = ALGO_MD5SESS; else if(strcasecompare(content, "MD5")) - digest->algo = CURLDIGESTALGO_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")) { +#ifdef CURL_HAVE_SHA512_256 + digest->algo = ALGO_SHA512_256; +#else /* ! CURL_HAVE_SHA512_256 */ + return CURLE_NOT_BUILT_IN; +#endif /* ! CURL_HAVE_SHA512_256 */ + } + else if(strcasecompare(content, "SHA-512-256-SESS")) { +#ifdef CURL_HAVE_SHA512_256 + digest->algo = ALGO_SHA512_256SESS; +#else /* ! CURL_HAVE_SHA512_256 */ + return CURLE_NOT_BUILT_IN; +#endif /* ! CURL_HAVE_SHA512_256 */ + } 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 */ + break; /* We are done here */ /* Pass all additional spaces here */ - while(*chlg && ISSPACE(*chlg)) + while(*chlg && ISBLANK(*chlg)) chlg++; /* Allow the list to be comma-separated */ @@ -632,24 +646,28 @@ CURLcode Curl_auth_decode_digest_http_message(const char *chlg, if(before && !digest->stale) return CURLE_BAD_CONTENT_ENCODING; - /* We got this header without a nonce, that's a bad Digest line! */ + /* We got this header without a nonce, that is a bad Digest line! */ if(!digest->nonce) return CURLE_BAD_CONTENT_ENCODING; + /* "-sess" protocol versions require "auth" or "auth-int" qop */ + if(!digest->qop && (digest->algo & SESSION_ALGO)) + return CURLE_BAD_CONTENT_ENCODING; + return CURLE_OK; } /* - * Curl_auth_create_digest_http_message() + * auth_create_digest_http_message() * - * This is used to generate a HTTP DIGEST response message ready for sending + * 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. - * passdwp [in] - The user's password. + * userp [in] - The username. + * 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. @@ -659,39 +677,45 @@ CURLcode Curl_auth_decode_digest_http_message(const char *chlg, * * 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) +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 md5buf[16]; /* 16 bytes/128 bits */ - unsigned char request_digest[33]; - unsigned char *md5this; - unsigned char ha1[33]; /* 32 digits and 1 zero byte */ - unsigned char ha2[33]; /* 32 digits and 1 zero byte */ - char cnoncebuf[33]; + 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) { - unsigned int rnd[4]; - result = Curl_rand(data, &rnd[0], 4); + char cnoncebuf[33]; + result = Curl_rand_hex(data, (unsigned char *)cnoncebuf, + sizeof(cnoncebuf)); if(result) return result; - snprintf(cnoncebuf, sizeof(cnoncebuf), "%08x%08x%08x%08x", - rnd[0], rnd[1], rnd[2], rnd[3]); - result = Curl_base64_encode(data, cnoncebuf, strlen(cnoncebuf), + result = Curl_base64_encode(cnoncebuf, strlen(cnoncebuf), &cnonce, &cnonce_sz); if(result) return result; @@ -699,6 +723,18 @@ CURLcode Curl_auth_create_digest_http_message(struct Curl_easy *data, digest->cnonce = cnonce; } + if(digest->userhash) { + hashthis = aprintf("%s:%s", userp, digest->realm ? digest->realm : ""); + if(!hashthis) + return CURLE_OUT_OF_MEMORY; + + result = hash(hashbuf, (unsigned char *) hashthis, strlen(hashthis)); + free(hashthis); + if(result) + return result; + convert_to_ascii(hashbuf, (unsigned char *)userh); + } + /* If the algorithm is "MD5" or unspecified (which then defaults to MD5): @@ -710,26 +746,28 @@ CURLcode Curl_auth_create_digest_http_message(struct Curl_easy *data, unq(nonce-value) ":" unq(cnonce-value) */ - md5this = (unsigned char *) - aprintf("%s:%s:%s", userp, digest->realm, passwdp); - if(!md5this) + hashthis = aprintf("%s:%s:%s", userp, digest->realm ? digest->realm : "", + passwdp); + if(!hashthis) return CURLE_OUT_OF_MEMORY; - CURL_OUTPUT_DIGEST_CONV(data, md5this); /* convert on non-ASCII machines */ - Curl_md5it(md5buf, md5this); - free(md5this); - auth_digest_md5_to_ascii(md5buf, ha1); + result = hash(hashbuf, (unsigned char *) hashthis, strlen(hashthis)); + free(hashthis); + if(result) + return result; + convert_to_ascii(hashbuf, ha1); - if(digest->algo == CURLDIGESTALGO_MD5SESS) { + 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; - CURL_OUTPUT_DIGEST_CONV(data, tmp); /* Convert on non-ASCII machines */ - Curl_md5it(md5buf, (unsigned char *) tmp); + result = hash(hashbuf, (unsigned char *) tmp, strlen(tmp)); free(tmp); - auth_digest_md5_to_ascii(md5buf, ha1); + if(result) + return result; + convert_to_ascii(hashbuf, ha1); } /* @@ -745,65 +783,86 @@ CURLcode Curl_auth_create_digest_http_message(struct Curl_easy *data, 5.1.1 of RFC 2616) */ - md5this = (unsigned char *) aprintf("%s:%s", request, uripath); + 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 at the moment. - TODO: replace md5 of empty string with entity-body for PUT/POST */ - unsigned char *md5this2 = (unsigned char *) - aprintf("%s:%s", md5this, "d41d8cd98f00b204e9800998ecf8427e"); - free(md5this); - md5this = md5this2; + /* We do not support auth-int for PUT or POST */ + char hashed[65]; + char *hashthis2; + + result = hash(hashbuf, (const unsigned char *)"", 0); + if(result) { + free(hashthis); + return result; + } + convert_to_ascii(hashbuf, (unsigned char *)hashed); + + hashthis2 = aprintf("%s:%s", hashthis, hashed); + free(hashthis); + hashthis = hashthis2; } - if(!md5this) + if(!hashthis) return CURLE_OUT_OF_MEMORY; - CURL_OUTPUT_DIGEST_CONV(data, md5this); /* convert on non-ASCII machines */ - Curl_md5it(md5buf, md5this); - free(md5this); - auth_digest_md5_to_ascii(md5buf, ha2); + result = hash(hashbuf, (unsigned char *) hashthis, strlen(hashthis)); + free(hashthis); + if(result) + return result; + convert_to_ascii(hashbuf, ha2); if(digest->qop) { - md5this = (unsigned char *) aprintf("%s:%s:%08x:%s:%s:%s", - ha1, - digest->nonce, - digest->nc, - digest->cnonce, - digest->qop, - ha2); + hashthis = aprintf("%s:%s:%08x:%s:%s:%s", ha1, digest->nonce, digest->nc, + digest->cnonce, digest->qop, ha2); } else { - md5this = (unsigned char *) aprintf("%s:%s:%s", - ha1, - digest->nonce, - ha2); + hashthis = aprintf("%s:%s:%s", ha1, digest->nonce, ha2); } - if(!md5this) + if(!hashthis) return CURLE_OUT_OF_MEMORY; - CURL_OUTPUT_DIGEST_CONV(data, md5this); /* convert on non-ASCII machines */ - Curl_md5it(md5buf, md5this); - free(md5this); - auth_digest_md5_to_ascii(md5buf, request_digest); + result = hash(hashbuf, (unsigned char *) hashthis, strlen(hashthis)); + free(hashthis); + if(result) + return result; + 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. For - the other fields, this shouldn't be an issue. realm, nonce, and opaque - are copied as is from the server, escapes and all. 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 + 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(userp); + 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\", " @@ -815,18 +874,16 @@ CURLcode Curl_auth_create_digest_http_message(struct Curl_easy *data, "qop=%s, " "response=\"%s\"", userp_quoted, - digest->realm, - digest->nonce, + realm_quoted, + nonce_quoted, uripath, digest->cnonce, digest->nc, digest->qop, request_digest); - if(strcasecompare(digest->qop, "auth")) - digest->nc++; /* The nc (from RFC) has to be a 8 hex digit number 0 - padded which tells to the server how many times you are - using the same nonce in the qop=auth mode */ + /* Increment nonce-count to use another nc value for the next request */ + digest->nc++; } else { response = aprintf("username=\"%s\", " @@ -835,20 +892,29 @@ CURLcode Curl_auth_create_digest_http_message(struct Curl_easy *data, "uri=\"%s\", " "response=\"%s\"", userp_quoted, - digest->realm, - digest->nonce, + 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 */ - tmp = aprintf("%s, opaque=\"%s\"", response, digest->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; @@ -857,7 +923,17 @@ CURLcode Curl_auth_create_digest_http_message(struct Curl_easy *data, if(digest->algorithm) { /* Append the algorithm */ - tmp = aprintf("%s, algorithm=\"%s\"", response, digest->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; @@ -872,6 +948,60 @@ CURLcode Curl_auth_create_digest_http_message(struct Curl_easy *data, 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 username. + * 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); + + if(digest->algo <= ALGO_SHA256SESS) + return auth_create_digest_http_message(data, userp, passwdp, + request, uripath, digest, + outptr, outlen, + auth_digest_sha256_to_ascii, + Curl_sha256it); +#ifdef CURL_HAVE_SHA512_256 + if(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_sha512_256it); +#endif /* CURL_HAVE_SHA512_256 */ + + /* Should be unreachable */ + return CURLE_BAD_CONTENT_ENCODING; +} + /* * Curl_auth_digest_cleanup() * @@ -892,9 +1022,10 @@ void Curl_auth_digest_cleanup(struct digestdata *digest) Curl_safefree(digest->algorithm); digest->nc = 0; - digest->algo = CURLDIGESTALGO_MD5; /* default algorithm */ + digest->algo = ALGO_MD5; /* default algorithm */ digest->stale = FALSE; /* default means normal, not stale */ + digest->userhash = FALSE; } #endif /* !USE_WINDOWS_SSPI */ -#endif /* CURL_DISABLE_CRYPTO_AUTH */ +#endif /* !CURL_DISABLE_DIGEST_AUTH */ diff --git a/contrib/curl/lib/vauth/digest.h b/contrib/curl/lib/vauth/digest.h index 5722dceced..99ce1f9138 100644 --- a/contrib/curl/lib/vauth/digest.h +++ b/contrib/curl/lib/vauth/digest.h @@ -7,11 +7,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2016, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -20,20 +20,17 @@ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY * KIND, either express or implied. * + * SPDX-License-Identifier: curl + * ***************************************************************************/ #include -#if !defined(CURL_DISABLE_CRYPTO_AUTH) +#ifndef CURL_DISABLE_DIGEST_AUTH #define DIGEST_MAX_VALUE_LENGTH 256 #define DIGEST_MAX_CONTENT_LENGTH 1024 -enum { - CURLDIGESTALGO_MD5, - CURLDIGESTALGO_MD5SESS -}; - /* 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); diff --git a/contrib/curl/lib/vauth/digest_sspi.c b/contrib/curl/lib/vauth/digest_sspi.c index 15f3d8c14b..39a0c306d8 100644 --- a/contrib/curl/lib/vauth/digest_sspi.c +++ b/contrib/curl/lib/vauth/digest_sspi.c @@ -5,12 +5,12 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 2014 - 2016, Steve Holme, . - * Copyright (C) 2015, Daniel Stenberg, , et al. + * Copyright (C) Steve Holme, . + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -19,25 +19,27 @@ * 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_CRYPTO_AUTH) +#if defined(USE_WINDOWS_SSPI) && !defined(CURL_DISABLE_DIGEST_AUTH) #include #include "vauth/vauth.h" #include "vauth/digest.h" #include "urldata.h" -#include "curl_base64.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" @@ -58,8 +60,14 @@ bool Curl_auth_is_digest_supported(void) SECURITY_STATUS status; /* Query the security package for Digest */ - status = s_pSecFn->QuerySecurityPackageInfo((TCHAR *) TEXT(SP_NAME_DIGEST), - &SecurityPackage); + status = + Curl_pSecFn->QuerySecurityPackageInfo((TCHAR *) TEXT(SP_NAME_DIGEST), + &SecurityPackage); + + /* Release the package buffer as it is not required anymore */ + if(status == SEC_E_OK) { + Curl_pSecFn->FreeContextBuffer(SecurityPackage); + } return (status == SEC_E_OK ? TRUE : FALSE); } @@ -73,28 +81,24 @@ bool Curl_auth_is_digest_supported(void) * Parameters: * * data [in] - The session handle. - * chlg64 [in] - The base64 encoded challenge message. - * userp [in] - The user name in the format User or Domain\User. - * passdwp [in] - The user's password. + * chlg [in] - The challenge message. + * userp [in] - The username 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. - * 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. + * out [out] - The result storage. * * Returns CURLE_OK on success. */ CURLcode Curl_auth_create_digest_md5_message(struct Curl_easy *data, - const char *chlg64, + const struct bufref *chlg, const char *userp, const char *passwdp, const char *service, - char **outptr, size_t *outlen) + struct bufref *out) { CURLcode result = CURLE_OK; TCHAR *spn = NULL; - size_t chlglen = 0; size_t token_max = 0; - unsigned char *input_token = NULL; unsigned char *output_token = NULL; CredHandle credentials; CtxtHandle context; @@ -109,48 +113,35 @@ CURLcode Curl_auth_create_digest_md5_message(struct Curl_easy *data, unsigned long attrs; TimeStamp expiry; /* For Windows 9x compatibility of SSPI calls */ - /* Decode the base-64 encoded challenge message */ - if(strlen(chlg64) && *chlg64 != '=') { - result = Curl_base64_decode(chlg64, &input_token, &chlglen); - if(result) - return result; - } - /* Ensure we have a valid challenge message */ - if(!input_token) { - infof(data, "DIGEST-MD5 handshake failure (empty challenge message)\n"); - + 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); + status = + Curl_pSecFn->QuerySecurityPackageInfo((TCHAR *) TEXT(SP_NAME_DIGEST), + &SecurityPackage); if(status != SEC_E_OK) { - free(input_token); - - return CURLE_NOT_BUILT_IN; + failf(data, "SSPI: could not 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); + Curl_pSecFn->FreeContextBuffer(SecurityPackage); /* Allocate our response buffer */ output_token = malloc(token_max); - if(!output_token) { - free(input_token); - + if(!output_token) return CURLE_OUT_OF_MEMORY; - } /* Generate our SPN */ - spn = Curl_auth_build_spn(service, data->easy_conn->host.name, NULL); + spn = Curl_auth_build_spn(service, data->conn->host.name, NULL); if(!spn) { free(output_token); - free(input_token); - return CURLE_OUT_OF_MEMORY; } @@ -160,8 +151,6 @@ CURLcode Curl_auth_create_digest_md5_message(struct Curl_easy *data, if(result) { free(spn); free(output_token); - free(input_token); - return result; } @@ -173,7 +162,7 @@ CURLcode Curl_auth_create_digest_md5_message(struct Curl_easy *data, p_identity = NULL; /* Acquire our credentials handle */ - status = s_pSecFn->AcquireCredentialsHandle(NULL, + status = Curl_pSecFn->AcquireCredentialsHandle(NULL, (TCHAR *) TEXT(SP_NAME_DIGEST), SECPKG_CRED_OUTBOUND, NULL, p_identity, NULL, NULL, @@ -183,8 +172,6 @@ CURLcode Curl_auth_create_digest_md5_message(struct Curl_easy *data, Curl_sspi_free_identity(p_identity); free(spn); free(output_token); - free(input_token); - return CURLE_LOGIN_DENIED; } @@ -193,8 +180,8 @@ CURLcode Curl_auth_create_digest_md5_message(struct Curl_easy *data, chlg_desc.cBuffers = 1; chlg_desc.pBuffers = &chlg_buf; chlg_buf.BufferType = SECBUFFER_TOKEN; - chlg_buf.pvBuffer = input_token; - chlg_buf.cbBuffer = curlx_uztoul(chlglen); + 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; @@ -205,31 +192,41 @@ CURLcode Curl_auth_create_digest_md5_message(struct Curl_easy *data, resp_buf.cbBuffer = curlx_uztoul(token_max); /* Generate our response message */ - status = s_pSecFn->InitializeSecurityContext(&credentials, NULL, spn, + status = Curl_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); + Curl_pSecFn->CompleteAuthToken(&credentials, &resp_desc); else if(status != SEC_E_OK && status != SEC_I_CONTINUE_NEEDED) { - s_pSecFn->FreeCredentialsHandle(&credentials); +#if !defined(CURL_DISABLE_VERBOSE_STRINGS) + char buffer[STRERROR_LEN]; +#endif + + Curl_pSecFn->FreeCredentialsHandle(&credentials); Curl_sspi_free_identity(p_identity); free(spn); free(output_token); - free(input_token); - return CURLE_RECV_ERROR; + if(status == SEC_E_INSUFFICIENT_MEMORY) + return CURLE_OUT_OF_MEMORY; + +#if !defined(CURL_DISABLE_VERBOSE_STRINGS) + infof(data, "schannel: InitializeSecurityContext failed: %s", + Curl_sspi_strerror(status, buffer, sizeof(buffer))); +#endif + + return CURLE_AUTH_ERROR; } - /* Base64 encode the response */ - result = Curl_base64_encode(data, (char *) output_token, resp_buf.cbBuffer, - outptr, outlen); + /* 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); + Curl_pSecFn->DeleteSecurityContext(&context); + Curl_pSecFn->FreeCredentialsHandle(&credentials); /* Free the identity structure */ Curl_sspi_free_identity(p_identity); @@ -237,12 +234,6 @@ CURLcode Curl_auth_create_digest_md5_message(struct Curl_easy *data, /* Free the SPN */ free(spn); - /* Free the response buffer */ - free(output_token); - - /* Free the decoded challenge message */ - free(input_token); - return result; } @@ -272,7 +263,7 @@ CURLcode Curl_override_sspi_http_realm(const char *chlg, char content[DIGEST_MAX_CONTENT_LENGTH]; /* Pass all additional spaces here */ - while(*chlg && ISSPACE(*chlg)) + while(*chlg && ISBLANK(*chlg)) chlg++; /* Extract a value=content pair */ @@ -280,13 +271,13 @@ CURLcode Curl_override_sspi_http_realm(const char *chlg, if(strcasecompare(value, "realm")) { /* Setup identity's domain and length */ - domain.tchar_ptr = Curl_convert_UTF8_to_tchar((char *) content); + 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) { - Curl_unicodefree(domain.tchar_ptr); + curlx_unicodefree(domain.tchar_ptr); return CURLE_OUT_OF_MEMORY; } @@ -295,17 +286,17 @@ CURLcode Curl_override_sspi_http_realm(const char *chlg, identity->DomainLength = curlx_uztoul(_tcslen(dup_domain.tchar_ptr)); dup_domain.tchar_ptr = NULL; - Curl_unicodefree(domain.tchar_ptr); + curlx_unicodefree(domain.tchar_ptr); } else { /* Unknown specifier, ignore it! */ } } else - break; /* We're done here */ + break; /* We are done here */ /* Pass all additional spaces here */ - while(*chlg && ISSPACE(*chlg)) + while(*chlg && ISBLANK(*chlg)) chlg++; /* Allow the list to be comma-separated */ @@ -320,7 +311,7 @@ CURLcode Curl_override_sspi_http_realm(const char *chlg, /* * Curl_auth_decode_digest_http_message() * - * This is used to decode a HTTP DIGEST challenge message into the seperate + * This is used to decode an HTTP DIGEST challenge message into the separate * attributes. * * Parameters: @@ -335,8 +326,8 @@ CURLcode Curl_auth_decode_digest_http_message(const char *chlg, { 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. */ + /* We had an input token before so if there is another one now that means we + provided bad credentials in the previous request or it is stale. */ if(digest->input_token) { bool stale = false; const char *p = chlg; @@ -346,19 +337,19 @@ CURLcode Curl_auth_decode_digest_http_message(const char *chlg, char value[DIGEST_MAX_VALUE_LENGTH]; char content[DIGEST_MAX_CONTENT_LENGTH]; - while(*p && ISSPACE(*p)) + while(*p && ISBLANK(*p)) p++; if(!Curl_auth_digest_get_pair(p, value, content, &p)) break; - if(Curl_strcasecompare(value, "stale") - && Curl_strcasecompare(content, "true")) { + if(strcasecompare(value, "stale") && + strcasecompare(content, "true")) { stale = true; break; } - while(*p && ISSPACE(*p)) + while(*p && ISBLANK(*p)) p++; if(',' == *p) @@ -384,14 +375,14 @@ CURLcode Curl_auth_decode_digest_http_message(const char *chlg, /* * Curl_auth_create_digest_http_message() * - * This is used to generate a HTTP DIGEST response message ready for sending + * 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. - * passdwp [in] - The user's password. + * userp [in] - The username 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. @@ -412,7 +403,7 @@ CURLcode Curl_auth_create_digest_http_message(struct Curl_easy *data, size_t token_max; char *resp; BYTE *output_token; - size_t output_token_len; + size_t output_token_len = 0; PSecPkgInfo SecurityPackage; SecBuffer chlg_buf[5]; SecBufferDesc chlg_desc; @@ -421,15 +412,18 @@ CURLcode Curl_auth_create_digest_http_message(struct Curl_easy *data, (void) data; /* Query the security package for DigestSSP */ - status = s_pSecFn->QuerySecurityPackageInfo((TCHAR *) TEXT(SP_NAME_DIGEST), - &SecurityPackage); - if(status != SEC_E_OK) - return CURLE_NOT_BUILT_IN; + status = + Curl_pSecFn->QuerySecurityPackageInfo((TCHAR *) TEXT(SP_NAME_DIGEST), + &SecurityPackage); + if(status != SEC_E_OK) { + failf(data, "SSPI: could not 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); + Curl_pSecFn->FreeContextBuffer(SecurityPackage); /* Allocate the output buffer according to the max token size as indicated by the security package */ @@ -438,6 +432,20 @@ CURLcode Curl_auth_create_digest_http_message(struct Curl_easy *data, 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) { + Curl_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; @@ -458,13 +466,14 @@ CURLcode Curl_auth_create_digest_http_message(struct Curl_easy *data, 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); + status = Curl_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\n", + infof(data, "digest_sspi: MakeSignature failed, error 0x%08lx", (long)status); - s_pSecFn->DeleteSecurityContext(digest->http_context); + Curl_pSecFn->DeleteSecurityContext(digest->http_context); Curl_safefree(digest->http_context); } } @@ -479,6 +488,10 @@ CURLcode Curl_auth_create_digest_http_message(struct Curl_easy *data, 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)) { @@ -500,8 +513,27 @@ CURLcode Curl_auth_create_digest_http_message(struct Curl_easy *data, /* 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, + status = Curl_pSecFn->AcquireCredentialsHandle(NULL, (TCHAR *) TEXT(SP_NAME_DIGEST), SECPKG_CRED_OUTBOUND, NULL, p_identity, NULL, NULL, @@ -535,9 +567,9 @@ CURLcode Curl_auth_create_digest_http_message(struct Curl_easy *data, resp_buf.pvBuffer = output_token; resp_buf.cbBuffer = curlx_uztoul(token_max); - spn = Curl_convert_UTF8_to_tchar((char *) uripath); + spn = curlx_convert_UTF8_to_tchar((char *) uripath); if(!spn) { - s_pSecFn->FreeCredentialsHandle(&credentials); + Curl_pSecFn->FreeCredentialsHandle(&credentials); Curl_sspi_free_identity(p_identity); free(output_token); @@ -550,32 +582,44 @@ CURLcode Curl_auth_create_digest_http_message(struct Curl_easy *data, if(!digest->http_context) return CURLE_OUT_OF_MEMORY; - /* Generate our reponse message */ - status = s_pSecFn->InitializeSecurityContext(&credentials, NULL, + /* Generate our response message */ + status = Curl_pSecFn->InitializeSecurityContext(&credentials, NULL, spn, ISC_REQ_USE_HTTP_STYLE, 0, 0, &chlg_desc, 0, digest->http_context, &resp_desc, &attrs, &expiry); - Curl_unicodefree(spn); + curlx_unicodefree(spn); if(status == SEC_I_COMPLETE_NEEDED || status == SEC_I_COMPLETE_AND_CONTINUE) - s_pSecFn->CompleteAuthToken(&credentials, &resp_desc); + Curl_pSecFn->CompleteAuthToken(&credentials, &resp_desc); else if(status != SEC_E_OK && status != SEC_I_CONTINUE_NEEDED) { - s_pSecFn->FreeCredentialsHandle(&credentials); +#if !defined(CURL_DISABLE_VERBOSE_STRINGS) + char buffer[STRERROR_LEN]; +#endif + + Curl_pSecFn->FreeCredentialsHandle(&credentials); Curl_sspi_free_identity(p_identity); free(output_token); Curl_safefree(digest->http_context); - return CURLE_OUT_OF_MEMORY; + if(status == SEC_E_INSUFFICIENT_MEMORY) + return CURLE_OUT_OF_MEMORY; + +#if !defined(CURL_DISABLE_VERBOSE_STRINGS) + infof(data, "schannel: InitializeSecurityContext failed: %s", + Curl_sspi_strerror(status, buffer, sizeof(buffer))); +#endif + + return CURLE_AUTH_ERROR; } output_token_len = resp_buf.cbBuffer; - s_pSecFn->FreeCredentialsHandle(&credentials); + Curl_pSecFn->FreeCredentialsHandle(&credentials); Curl_sspi_free_identity(p_identity); } @@ -586,7 +630,7 @@ CURLcode Curl_auth_create_digest_http_message(struct Curl_easy *data, return CURLE_OUT_OF_MEMORY; } - /* Copy the generated reponse */ + /* Copy the generated response */ memcpy(resp, output_token, output_token_len); resp[output_token_len] = 0; @@ -620,9 +664,13 @@ void Curl_auth_digest_cleanup(struct digestdata *digest) /* Delete security context */ if(digest->http_context) { - s_pSecFn->DeleteSecurityContext(digest->http_context); + Curl_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_CRYPTO_AUTH */ +#endif /* USE_WINDOWS_SSPI && !CURL_DISABLE_DIGEST_AUTH */ diff --git a/contrib/curl/lib/vauth/gsasl.c b/contrib/curl/lib/vauth/gsasl.c new file mode 100644 index 0000000000..c7d0a8d3b2 --- /dev/null +++ b/contrib/curl/lib/vauth/gsasl.c @@ -0,0 +1,127 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Simon Josefsson, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the 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 + +#include "vauth/vauth.h" +#include "urldata.h" +#include "sendf.h" + +#include + +/* 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/contrib/curl/lib/vauth/krb5_gssapi.c b/contrib/curl/lib/vauth/krb5_gssapi.c index c754fae452..e792782838 100644 --- a/contrib/curl/lib/vauth/krb5_gssapi.c +++ b/contrib/curl/lib/vauth/krb5_gssapi.c @@ -5,12 +5,12 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 2014 - 2016, Steve Holme, . - * Copyright (C) 2015, Daniel Stenberg, , et al. + * Copyright (C) Steve Holme, . + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -19,6 +19,8 @@ * 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 * ***************************************************************************/ @@ -32,7 +34,6 @@ #include "vauth/vauth.h" #include "curl_sasl.h" #include "urldata.h" -#include "curl_base64.h" #include "curl_gssapi.h" #include "sendf.h" #include "curl_printf.h" @@ -64,18 +65,15 @@ bool Curl_auth_is_gssapi_supported(void) * Parameters: * * data [in] - The session handle. - * userp [in] - The user name. - * passdwp [in] - The user's password. + * userp [in] - The username. + * 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 specifing whether or not mutual authentication + * host [in[ - The hostname. + * mutual_auth [in] - Flag specifying whether or not mutual authentication * is enabled. - * chlg64 [in] - Pointer to the optional base64 encoded challenge - * message. + * chlg [in] - Optional challenge message. * krb5 [in/out] - The Kerberos 5 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. + * out [out] - The result storage. * * Returns CURLE_OK on success. */ @@ -85,13 +83,11 @@ CURLcode Curl_auth_create_gssapi_user_message(struct Curl_easy *data, const char *service, const char *host, const bool mutual_auth, - const char *chlg64, + const struct bufref *chlg, struct kerberos5data *krb5, - char **outptr, size_t *outlen) + struct bufref *out) { CURLcode result = CURLE_OK; - size_t chlglen = 0; - unsigned char *chlg = NULL; OM_uint32 major_status; OM_uint32 minor_status; OM_uint32 unused_status; @@ -121,30 +117,19 @@ CURLcode Curl_auth_create_gssapi_user_message(struct Curl_easy *data, free(spn); - return CURLE_OUT_OF_MEMORY; + 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, "GSSAPI handshake failure (empty challenge message)\n"); - + 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 */ - input_token.value = chlg; - input_token.length = chlglen; + input_token.value = (void *) Curl_bufref_ptr(chlg); + input_token.length = Curl_bufref_len(chlg); } major_status = Curl_gss_init_sec_context(data, @@ -158,9 +143,6 @@ CURLcode Curl_auth_create_gssapi_user_message(struct Curl_easy *data, mutual_auth, NULL); - /* Free the decoded challenge as it is not required anymore */ - free(input_token.value); - if(GSS_ERROR(major_status)) { if(output_token.value) gss_release_buffer(&unused_status, &output_token); @@ -168,21 +150,15 @@ CURLcode Curl_auth_create_gssapi_user_message(struct Curl_easy *data, Curl_gss_log_error(data, "gss_init_sec_context() failed: ", major_status, minor_status); - return CURLE_RECV_ERROR; + return CURLE_AUTH_ERROR; } if(output_token.value && output_token.length) { - /* Base64 encode the response */ - result = Curl_base64_encode(data, (char *) output_token.value, - output_token.length, outptr, outlen); - + result = Curl_bufref_memdup(out, output_token.value, output_token.length); gss_release_buffer(&unused_status, &output_token); } - else if(mutual_auth) { - *outptr = strdup(""); - if(!*outptr) - result = CURLE_OUT_OF_MEMORY; - } + else + Curl_bufref_set(out, mutual_auth ? "": NULL, 0, NULL); return result; } @@ -196,80 +172,41 @@ CURLcode Curl_auth_create_gssapi_user_message(struct Curl_easy *data, * Parameters: * * data [in] - The session handle. - * chlg64 [in] - Pointer to the optional base64 encoded challenge message. + * authzid [in] - The authorization identity if some. + * chlg [in] - Optional challenge message. * krb5 [in/out] - The Kerberos 5 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. + * out [out] - The result storage. * * Returns CURLE_OK on success. */ CURLcode Curl_auth_create_gssapi_security_message(struct Curl_easy *data, - const char *chlg64, + const char *authzid, + const struct bufref *chlg, struct kerberos5data *krb5, - char **outptr, - size_t *outlen) + struct bufref *out) { CURLcode result = CURLE_OK; - size_t chlglen = 0; size_t messagelen = 0; - unsigned char *chlg = NULL; 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 int indata = 0; - unsigned int outdata = 0; + unsigned char *indata; gss_qop_t qop = GSS_C_QOP_DEFAULT; unsigned int sec_layer = 0; unsigned int max_size = 0; - gss_name_t username = GSS_C_NO_NAME; - gss_buffer_desc username_token; - - /* Decode the base-64 encoded input message */ - if(strlen(chlg64) && *chlg64 != '=') { - result = Curl_base64_decode(chlg64, &chlg, &chlglen); - if(result) - return result; - } /* Ensure we have a valid challenge message */ - if(!chlg) { - infof(data, "GSSAPI handshake failure (empty security message)\n"); - + if(!Curl_bufref_len(chlg)) { + infof(data, "GSSAPI handshake failure (empty security message)"); return CURLE_BAD_CONTENT_ENCODING; } - /* Get the fully qualified username back from the context */ - major_status = gss_inquire_context(&minor_status, krb5->context, - &username, NULL, NULL, NULL, NULL, - NULL, NULL); - if(GSS_ERROR(major_status)) { - Curl_gss_log_error(data, "gss_inquire_context() failed: ", - major_status, minor_status); - - free(chlg); - - return CURLE_OUT_OF_MEMORY; - } - - /* Convert the username from internal format to a displayable token */ - major_status = gss_display_name(&minor_status, username, - &username_token, NULL); - if(GSS_ERROR(major_status)) { - Curl_gss_log_error(data, "gss_display_name() failed: ", - major_status, minor_status); - - free(chlg); - - return CURLE_OUT_OF_MEMORY; - } - /* Setup the challenge "input" security buffer */ - input_token.value = chlg; - input_token.length = chlglen; + 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, @@ -277,69 +214,59 @@ CURLcode Curl_auth_create_gssapi_security_message(struct Curl_easy *data, if(GSS_ERROR(major_status)) { Curl_gss_log_error(data, "gss_unwrap() failed: ", major_status, minor_status); - - gss_release_buffer(&unused_status, &username_token); - free(chlg); - 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)\n"); - - gss_release_buffer(&unused_status, &username_token); - free(chlg); - + infof(data, "GSSAPI handshake failure (invalid security data)"); return CURLE_BAD_CONTENT_ENCODING; } - /* Copy the data out and free the challenge as it is not required anymore */ - memcpy(&indata, output_token.value, 4); + /* Extract the security layer and the maximum message size */ + indata = output_token.value; + sec_layer = indata[0]; + max_size = ((unsigned int)indata[1] << 16) | + ((unsigned int)indata[2] << 8) | indata[3]; + + /* Free the challenge as it is not required anymore */ gss_release_buffer(&unused_status, &output_token); - free(chlg); - /* Extract the security layer */ - sec_layer = indata & 0x000000FF; + /* Process the security layer */ if(!(sec_layer & GSSAUTH_P_NONE)) { - infof(data, "GSSAPI handshake failure (invalid security layer)\n"); - - gss_release_buffer(&unused_status, &username_token); + 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 */ - /* Extract the maximum message size the server can receive */ - max_size = ntohl(indata & 0xFFFFFF00); + /* 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 + we do not require one unless we are encrypting data, we tell the server our receive buffer is zero. */ max_size = 0; } /* Allocate our message */ - messagelen = sizeof(outdata) + username_token.length + 1; + messagelen = 4; + if(authzid) + messagelen += strlen(authzid); message = malloc(messagelen); - if(!message) { - gss_release_buffer(&unused_status, &username_token); - + if(!message) return CURLE_OUT_OF_MEMORY; - } - /* Populate the message with the security layer, client supported receive - message size and authorization identity including the 0x00 based - terminator. Note: Despite RFC4752 Section 3.1 stating "The authorization - identity is not terminated with the zero-valued (%x00) octet." it seems - necessary to include it. */ - outdata = htonl(max_size) | sec_layer; - memcpy(message, &outdata, sizeof(outdata)); - memcpy(message + sizeof(outdata), username_token.value, - username_token.length); - message[messagelen - 1] = '\0'; + /* 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. */ - /* Free the username token as it is not required anymore */ - gss_release_buffer(&unused_status, &username_token); + if(authzid && *authzid) + memcpy(message + 4, authzid, messagelen - 4); /* Setup the "authentication data" security buffer */ input_token.value = message; @@ -352,16 +279,12 @@ CURLcode Curl_auth_create_gssapi_security_message(struct Curl_easy *data, if(GSS_ERROR(major_status)) { Curl_gss_log_error(data, "gss_wrap() failed: ", major_status, minor_status); - free(message); - - return CURLE_OUT_OF_MEMORY; + return CURLE_AUTH_ERROR; } - /* Base64 encode the response */ - result = Curl_base64_encode(data, (char *) output_token.value, - output_token.length, outptr, outlen); - + /* 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); @@ -372,7 +295,7 @@ CURLcode Curl_auth_create_gssapi_security_message(struct Curl_easy *data, } /* - * Curl_auth_gssapi_cleanup() + * Curl_auth_cleanup_gssapi() * * This is used to clean up the GSSAPI (Kerberos V5) specific data. * @@ -381,7 +304,7 @@ CURLcode Curl_auth_create_gssapi_security_message(struct Curl_easy *data, * krb5 [in/out] - The Kerberos 5 data struct being cleaned up. * */ -void Curl_auth_gssapi_cleanup(struct kerberos5data *krb5) +void Curl_auth_cleanup_gssapi(struct kerberos5data *krb5) { OM_uint32 minor_status; diff --git a/contrib/curl/lib/vauth/krb5_sspi.c b/contrib/curl/lib/vauth/krb5_sspi.c index 151794e619..b168a27ad8 100644 --- a/contrib/curl/lib/vauth/krb5_sspi.c +++ b/contrib/curl/lib/vauth/krb5_sspi.c @@ -5,11 +5,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 2014 - 2016, Steve Holme, . + * Copyright (C) Steve Holme, . * * This software is licensed 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. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -18,6 +18,8 @@ * 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 * ***************************************************************************/ @@ -30,7 +32,6 @@ #include "vauth/vauth.h" #include "urldata.h" -#include "curl_base64.h" #include "warnless.h" #include "curl_multibyte.h" #include "sendf.h" @@ -54,10 +55,15 @@ bool Curl_auth_is_gssapi_supported(void) SECURITY_STATUS status; /* Query the security package for Kerberos */ - status = s_pSecFn->QuerySecurityPackageInfo((TCHAR *) + status = Curl_pSecFn->QuerySecurityPackageInfo((TCHAR *) TEXT(SP_NAME_KERBEROS), &SecurityPackage); + /* Release the package buffer as it is not required anymore */ + if(status == SEC_E_OK) { + Curl_pSecFn->FreeContextBuffer(SecurityPackage); + } + return (status == SEC_E_OK ? TRUE : FALSE); } @@ -70,17 +76,15 @@ bool Curl_auth_is_gssapi_supported(void) * Parameters: * * data [in] - The session handle. - * userp [in] - The user name in the format User or Domain\User. - * passdwp [in] - The user's password. + * userp [in] - The username 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 specifing whether or not mutual authentication + * host [in] - The hostname. + * mutual_auth [in] - Flag specifying whether or not mutual authentication * is enabled. - * chlg64 [in] - The optional base64 encoded challenge message. + * chlg [in] - Optional challenge message. * krb5 [in/out] - The Kerberos 5 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. + * out [out] - The result storage. * * Returns CURLE_OK on success. */ @@ -90,13 +94,11 @@ CURLcode Curl_auth_create_gssapi_user_message(struct Curl_easy *data, const char *service, const char *host, const bool mutual_auth, - const char *chlg64, + const struct bufref *chlg, struct kerberos5data *krb5, - char **outptr, size_t *outlen) + struct bufref *out) { CURLcode result = CURLE_OK; - size_t chlglen = 0; - unsigned char *chlg = NULL; CtxtHandle context; PSecPkgInfo SecurityPackage; SecBuffer chlg_buf; @@ -116,17 +118,18 @@ CURLcode Curl_auth_create_gssapi_user_message(struct Curl_easy *data, if(!krb5->output_token) { /* Query the security package for Kerberos */ - status = s_pSecFn->QuerySecurityPackageInfo((TCHAR *) + status = Curl_pSecFn->QuerySecurityPackageInfo((TCHAR *) TEXT(SP_NAME_KERBEROS), &SecurityPackage); if(status != SEC_E_OK) { - return CURLE_NOT_BUILT_IN; + failf(data, "SSPI: could not 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); + Curl_pSecFn->FreeContextBuffer(SecurityPackage); /* Allocate our response buffer */ krb5->output_token = malloc(krb5->token_max); @@ -135,7 +138,7 @@ CURLcode Curl_auth_create_gssapi_user_message(struct Curl_easy *data, } if(!krb5->credentials) { - /* Do we have credientials to use or are we using single sign-on? */ + /* 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); @@ -150,14 +153,12 @@ CURLcode Curl_auth_create_gssapi_user_message(struct Curl_easy *data, krb5->p_identity = NULL; /* Allocate our credentials handle */ - krb5->credentials = malloc(sizeof(CredHandle)); + krb5->credentials = calloc(1, sizeof(CredHandle)); if(!krb5->credentials) return CURLE_OUT_OF_MEMORY; - memset(krb5->credentials, 0, sizeof(CredHandle)); - /* Acquire our credentials handle */ - status = s_pSecFn->AcquireCredentialsHandle(NULL, + status = Curl_pSecFn->AcquireCredentialsHandle(NULL, (TCHAR *) TEXT(SP_NAME_KERBEROS), SECPKG_CRED_OUTBOUND, NULL, @@ -167,25 +168,14 @@ CURLcode Curl_auth_create_gssapi_user_message(struct Curl_easy *data, return CURLE_LOGIN_DENIED; /* Allocate our new context handle */ - krb5->context = malloc(sizeof(CtxtHandle)); + krb5->context = calloc(1, sizeof(CtxtHandle)); if(!krb5->context) return CURLE_OUT_OF_MEMORY; - - memset(krb5->context, 0, sizeof(CtxtHandle)); } - 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, "GSSAPI handshake failure (empty challenge message)\n"); - + if(chlg) { + if(!Curl_bufref_len(chlg)) { + infof(data, "GSSAPI handshake failure (empty challenge message)"); return CURLE_BAD_CONTENT_ENCODING; } @@ -194,8 +184,8 @@ CURLcode Curl_auth_create_gssapi_user_message(struct Curl_easy *data, chlg_desc.cBuffers = 1; chlg_desc.pBuffers = &chlg_buf; chlg_buf.BufferType = SECBUFFER_TOKEN; - chlg_buf.pvBuffer = chlg; - chlg_buf.cbBuffer = curlx_uztoul(chlglen); + chlg_buf.pvBuffer = (void *) Curl_bufref_ptr(chlg); + chlg_buf.cbBuffer = curlx_uztoul(Curl_bufref_len(chlg)); } /* Setup the response "output" security buffer */ @@ -207,7 +197,7 @@ CURLcode Curl_auth_create_gssapi_user_message(struct Curl_easy *data, resp_buf.cbBuffer = curlx_uztoul(krb5->token_max); /* Generate our challenge-response message */ - status = s_pSecFn->InitializeSecurityContext(krb5->credentials, + status = Curl_pSecFn->InitializeSecurityContext(krb5->credentials, chlg ? krb5->context : NULL, krb5->spn, (mutual_auth ? @@ -218,29 +208,25 @@ CURLcode Curl_auth_create_gssapi_user_message(struct Curl_easy *data, &resp_desc, &attrs, &expiry); - /* Free the decoded challenge as it is not required anymore */ - free(chlg); + if(status == SEC_E_INSUFFICIENT_MEMORY) + return CURLE_OUT_OF_MEMORY; - if(status != SEC_E_OK && status != SEC_I_CONTINUE_NEEDED) { - return CURLE_RECV_ERROR; - } + 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); + Curl_pSecFn->DeleteSecurityContext(krb5->context); memcpy(krb5->context, &context, sizeof(context)); } if(resp_buf.cbBuffer) { - /* Base64 encode the response */ - result = Curl_base64_encode(data, (char *) resp_buf.pvBuffer, - resp_buf.cbBuffer, outptr, outlen); - } - else if(mutual_auth) { - *outptr = strdup(""); - if(!*outptr) - result = CURLE_OUT_OF_MEMORY; + 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; } @@ -254,26 +240,22 @@ CURLcode Curl_auth_create_gssapi_user_message(struct Curl_easy *data, * Parameters: * * data [in] - The session handle. - * chlg64 [in] - The optional base64 encoded challenge message. + * 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. - * 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. + * out [out] - The result storage. * * Returns CURLE_OK on success. */ CURLcode Curl_auth_create_gssapi_security_message(struct Curl_easy *data, - const char *chlg64, + const char *authzid, + const struct bufref *chlg, struct kerberos5data *krb5, - char **outptr, - size_t *outlen) + struct bufref *out) { - CURLcode result = CURLE_OK; size_t offset = 0; - size_t chlglen = 0; size_t messagelen = 0; size_t appdatalen = 0; - unsigned char *chlg = NULL; unsigned char *trailer = NULL; unsigned char *message = NULL; unsigned char *padding = NULL; @@ -282,98 +264,78 @@ CURLcode Curl_auth_create_gssapi_security_message(struct Curl_easy *data, SecBuffer wrap_buf[3]; SecBufferDesc input_desc; SecBufferDesc wrap_desc; - unsigned long indata = 0; - unsigned long outdata = 0; + unsigned char *indata; unsigned long qop = 0; unsigned long sec_layer = 0; unsigned long max_size = 0; SecPkgContext_Sizes sizes; - SecPkgCredentials_Names names; SECURITY_STATUS status; - char *user_name; - /* Decode the base-64 encoded input message */ - if(strlen(chlg64) && *chlg64 != '=') { - result = Curl_base64_decode(chlg64, &chlg, &chlglen); - if(result) - return result; - } +#if defined(CURL_DISABLE_VERBOSE_STRINGS) + (void) data; +#endif /* Ensure we have a valid challenge message */ - if(!chlg) { - infof(data, "GSSAPI handshake failure (empty security message)\n"); - + 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, + status = Curl_pSecFn->QueryContextAttributes(krb5->context, SECPKG_ATTR_SIZES, &sizes); - if(status != SEC_E_OK) { - free(chlg); + if(status == SEC_E_INSUFFICIENT_MEMORY) return CURLE_OUT_OF_MEMORY; - } - /* Get the fully qualified username back from the context */ - status = s_pSecFn->QueryCredentialsAttributes(krb5->credentials, - SECPKG_CRED_ATTR_NAMES, - &names); - if(status != SEC_E_OK) { - free(chlg); - - return CURLE_RECV_ERROR; - } + 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 = chlg; - input_buf[0].cbBuffer = curlx_uztoul(chlglen); + 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); + status = Curl_pSecFn->DecryptMessage(krb5->context, &input_desc, 0, &qop); if(status != SEC_E_OK) { - infof(data, "GSSAPI handshake failure (empty security message)\n"); - - free(chlg); - + 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)\n"); - - free(chlg); - + infof(data, "GSSAPI handshake failure (invalid security data)"); return CURLE_BAD_CONTENT_ENCODING; } - /* Copy the data out and free the challenge as it is not required anymore */ - memcpy(&indata, input_buf[1].pvBuffer, 4); - s_pSecFn->FreeContextBuffer(input_buf[1].pvBuffer); - free(chlg); + /* Extract the security layer and the maximum message size */ + indata = input_buf[1].pvBuffer; + sec_layer = indata[0]; + max_size = ((unsigned long)indata[1] << 16) | + ((unsigned long)indata[2] << 8) | indata[3]; - /* Extract the security layer */ - sec_layer = indata & 0x000000FF; - if(!(sec_layer & KERB_WRAP_NO_ENCRYPT)) { - infof(data, "GSSAPI handshake failure (invalid security layer)\n"); + /* Free the challenge as it is not required anymore */ + Curl_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 */ - /* Extract the maximum message size the server can receive */ - max_size = ntohl(indata & 0xFFFFFF00); + /* 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 + we do not require one unless we are encrypting data, we tell the server our receive buffer is zero. */ max_size = 0; } @@ -383,33 +345,28 @@ CURLcode Curl_auth_create_gssapi_security_message(struct Curl_easy *data, if(!trailer) return CURLE_OUT_OF_MEMORY; - /* Convert the user name to UTF8 when operating with Unicode */ - user_name = Curl_convert_tchar_to_UTF8(names.sUserName); - if(!user_name) { - free(trailer); - - return CURLE_OUT_OF_MEMORY; - } - /* Allocate our message */ - messagelen = sizeof(outdata) + strlen(user_name) + 1; + messagelen = 4; + if(authzid) + messagelen += strlen(authzid); message = malloc(messagelen); if(!message) { free(trailer); - Curl_unicodefree(user_name); return CURLE_OUT_OF_MEMORY; } - /* Populate the message with the security layer, client supported receive - message size and authorization identity including the 0x00 based - terminator. Note: Despite RFC4752 Section 3.1 stating "The authorization - identity is not terminated with the zero-valued (%x00) octet." it seems - necessary to include it. */ - outdata = htonl(max_size) | sec_layer; - memcpy(message, &outdata, sizeof(outdata)); - strcpy((char *) message + sizeof(outdata), user_name); - Curl_unicodefree(user_name); + /* 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); @@ -435,14 +392,17 @@ CURLcode Curl_auth_create_gssapi_security_message(struct Curl_easy *data, wrap_buf[2].cbBuffer = sizes.cbBlockSize; /* Encrypt the data */ - status = s_pSecFn->EncryptMessage(krb5->context, KERB_WRAP_NO_ENCRYPT, + status = Curl_pSecFn->EncryptMessage(krb5->context, KERB_WRAP_NO_ENCRYPT, &wrap_desc, 0); if(status != SEC_E_OK) { free(padding); free(message); free(trailer); - return CURLE_OUT_OF_MEMORY; + if(status == SEC_E_INSUFFICIENT_MEMORY) + return CURLE_OUT_OF_MEMORY; + + return CURLE_AUTH_ERROR; } /* Allocate the encryption (wrap) buffer */ @@ -464,21 +424,18 @@ CURLcode Curl_auth_create_gssapi_security_message(struct Curl_easy *data, offset += wrap_buf[1].cbBuffer; memcpy(appdata + offset, wrap_buf[2].pvBuffer, wrap_buf[2].cbBuffer); - /* Base64 encode the response */ - result = Curl_base64_encode(data, (char *) appdata, appdatalen, outptr, - outlen); - /* Free all of our local buffers */ - free(appdata); free(padding); free(message); free(trailer); - return result; + /* Return the response. */ + Curl_bufref_set(out, appdata, appdatalen, curl_free); + return CURLE_OK; } /* - * Curl_auth_gssapi_cleanup() + * Curl_auth_cleanup_gssapi() * * This is used to clean up the GSSAPI (Kerberos V5) specific data. * @@ -487,18 +444,18 @@ CURLcode Curl_auth_create_gssapi_security_message(struct Curl_easy *data, * krb5 [in/out] - The Kerberos 5 data struct being cleaned up. * */ -void Curl_auth_gssapi_cleanup(struct kerberos5data *krb5) +void Curl_auth_cleanup_gssapi(struct kerberos5data *krb5) { /* Free our security context */ if(krb5->context) { - s_pSecFn->DeleteSecurityContext(krb5->context); + Curl_pSecFn->DeleteSecurityContext(krb5->context); free(krb5->context); krb5->context = NULL; } /* Free our credentials handle */ if(krb5->credentials) { - s_pSecFn->FreeCredentialsHandle(krb5->credentials); + Curl_pSecFn->FreeCredentialsHandle(krb5->credentials); free(krb5->credentials); krb5->credentials = NULL; } @@ -515,4 +472,4 @@ void Curl_auth_gssapi_cleanup(struct kerberos5data *krb5) krb5->token_max = 0; } -#endif /* USE_WINDOWS_SSPI && USE_KERBEROS5*/ +#endif /* USE_WINDOWS_SSPI && USE_KERBEROS5 */ diff --git a/contrib/curl/lib/vauth/ntlm.c b/contrib/curl/lib/vauth/ntlm.c index 96861c9358..0e3d98aa9c 100644 --- a/contrib/curl/lib/vauth/ntlm.c +++ b/contrib/curl/lib/vauth/ntlm.c @@ -5,11 +5,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2016, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -18,6 +18,8 @@ * 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" @@ -27,26 +29,22 @@ /* * NTLM details: * - * https://davenport.sourceforge.io/ntlm.html + * https://davenport.sourceforge.net/ntlm.html * https://www.innovation.ch/java/ntlm.html */ #define DEBUG_ME 0 #include "urldata.h" -#include "non-ascii.h" #include "sendf.h" -#include "curl_base64.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" - -#ifdef USE_NSS -#include "vtls/nssg.h" /* for Curl_nss_force_init() */ -#endif +#include "strdup.h" #define BUILDING_CURL_NTLM_MSGS_C #include "vauth/vauth.h" @@ -61,10 +59,6 @@ /* "NTLMSSP" signature is always in ASCII regardless of the platform */ #define NTLMSSP_SIGNATURE "\x4e\x54\x4c\x4d\x53\x53\x50" -#define SHORTPAIR(x) ((x) & 0xff), (((x) >> 8) & 0xff) -#define LONGQUARTET(x) ((x) & 0xff), (((x) >> 8) & 0xff), \ - (((x) >> 16) & 0xff), (((x) >> 24) & 0xff) - #if DEBUG_ME # define DEBUG_OUT(x) x static void ntlm_print_flags(FILE *handle, unsigned long flags) @@ -75,7 +69,7 @@ static void ntlm_print_flags(FILE *handle, unsigned long flags) fprintf(handle, "NTLMFLAG_NEGOTIATE_OEM "); if(flags & NTLMFLAG_REQUEST_TARGET) fprintf(handle, "NTLMFLAG_REQUEST_TARGET "); - if(flags & (1<<3)) + if(flags & (1 << 3)) fprintf(handle, "NTLMFLAG_UNKNOWN_3 "); if(flags & NTLMFLAG_NEGOTIATE_SIGN) fprintf(handle, "NTLMFLAG_NEGOTIATE_SIGN "); @@ -85,11 +79,9 @@ static void ntlm_print_flags(FILE *handle, unsigned long flags) fprintf(handle, "NTLMFLAG_NEGOTIATE_DATAGRAM_STYLE "); if(flags & NTLMFLAG_NEGOTIATE_LM_KEY) fprintf(handle, "NTLMFLAG_NEGOTIATE_LM_KEY "); - if(flags & NTLMFLAG_NEGOTIATE_NETWARE) - fprintf(handle, "NTLMFLAG_NEGOTIATE_NETWARE "); if(flags & NTLMFLAG_NEGOTIATE_NTLM_KEY) fprintf(handle, "NTLMFLAG_NEGOTIATE_NTLM_KEY "); - if(flags & (1<<10)) + if(flags & (1 << 10)) fprintf(handle, "NTLMFLAG_UNKNOWN_10 "); if(flags & NTLMFLAG_NEGOTIATE_ANONYMOUS) fprintf(handle, "NTLMFLAG_NEGOTIATE_ANONYMOUS "); @@ -117,15 +109,15 @@ static void ntlm_print_flags(FILE *handle, unsigned long flags) fprintf(handle, "NTLMFLAG_REQUEST_NONNT_SESSION_KEY "); if(flags & NTLMFLAG_NEGOTIATE_TARGET_INFO) fprintf(handle, "NTLMFLAG_NEGOTIATE_TARGET_INFO "); - if(flags & (1<<24)) + if(flags & (1 << 24)) fprintf(handle, "NTLMFLAG_UNKNOWN_24 "); - if(flags & (1<<25)) + if(flags & (1 << 25)) fprintf(handle, "NTLMFLAG_UNKNOWN_25 "); - if(flags & (1<<26)) + if(flags & (1 << 26)) fprintf(handle, "NTLMFLAG_UNKNOWN_26 "); - if(flags & (1<<27)) + if(flags & (1 << 27)) fprintf(handle, "NTLMFLAG_UNKNOWN_27 "); - if(flags & (1<<28)) + if(flags & (1 << 28)) fprintf(handle, "NTLMFLAG_UNKNOWN_28 "); if(flags & NTLMFLAG_NEGOTIATE_128) fprintf(handle, "NTLMFLAG_NEGOTIATE_128 "); @@ -158,40 +150,41 @@ static void ntlm_print_hex(FILE *handle, const char *buf, size_t len) * Parameters: * * data [in] - The session handle. - * buffer [in] - The decoded type-2 message. - * size [in] - The input buffer size, at least 32 bytes. + * 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, - unsigned char *buffer, - size_t size, + 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(size >= 48) { - target_info_len = Curl_read16_le(&buffer[40]); - target_info_offset = Curl_read32_le(&buffer[44]); + 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 + target_info_len) > size) || - (target_info_offset < 48)) { + 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\n"); + "Target Info Offset Len is set incorrect by the peer"); return CURLE_BAD_CONTENT_ENCODING; } - ntlm->target_info = malloc(target_info_len); + free(ntlm->target_info); /* replace any previous data */ + ntlm->target_info = Curl_memdup(&type2[target_info_offset], + target_info_len); if(!ntlm->target_info) return CURLE_OUT_OF_MEMORY; - - memcpy(ntlm->target_info, &buffer[target_info_offset], target_info_len); } } @@ -233,21 +226,20 @@ bool Curl_auth_is_ntlm_supported(void) /* * Curl_auth_decode_ntlm_type2_message() * - * This is used to decode an already encoded NTLM type-2 message. The message - * is first decoded from a base64 string into a raw NTLM message and checked - * for validity before the appropriate data for creating a type-3 message is - * written to the given NTLM data structure. + * 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. - * type2msg [in] - The base64 encoded type-2 message. + * 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 char *type2msg, + const struct bufref *type2ref, struct ntlmdata *ntlm) { static const char type2_marker[] = { 0x02, 0x00, 0x00, 0x00 }; @@ -269,39 +261,20 @@ CURLcode Curl_auth_decode_ntlm_type2_message(struct Curl_easy *data, */ CURLcode result = CURLE_OK; - unsigned char *type2 = NULL; - size_t type2_len = 0; - -#if defined(USE_NSS) - /* Make sure the crypto backend is initialized */ - result = Curl_nss_force_init(data); - if(result) - return result; -#elif defined(CURL_DISABLE_VERBOSE_STRINGS) + const unsigned char *type2 = Curl_bufref_ptr(type2ref); + size_t type2len = Curl_bufref_len(type2ref); + +#if defined(CURL_DISABLE_VERBOSE_STRINGS) (void)data; #endif - /* Decode the base-64 encoded type-2 message */ - if(strlen(type2msg) && *type2msg != '=') { - result = Curl_base64_decode(type2msg, &type2, &type2_len); - if(result) - return result; - } - - /* Ensure we have a valid type-2 message */ - if(!type2) { - infof(data, "NTLM handshake failure (empty type-2 message)\n"); - return CURLE_BAD_CONTENT_ENCODING; - } - ntlm->flags = 0; - if((type2_len < 32) || + 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 */ - free(type2); - infof(data, "NTLM handshake failure (bad type-2 message)\n"); + infof(data, "NTLM handshake failure (bad type-2 message)"); return CURLE_BAD_CONTENT_ENCODING; } @@ -309,10 +282,9 @@ CURLcode Curl_auth_decode_ntlm_type2_message(struct Curl_easy *data, memcpy(ntlm->nonce, &type2[24], 8); if(ntlm->flags & NTLMFLAG_NEGOTIATE_TARGET_INFO) { - result = ntlm_decode_type2_target(data, type2, type2_len, ntlm); + result = ntlm_decode_type2_target(data, type2ref, ntlm); if(result) { - free(type2); - infof(data, "NTLM handshake failure (bad type-2 message)\n"); + infof(data, "NTLM handshake failure (bad type-2 message)"); return result; } } @@ -326,8 +298,6 @@ CURLcode Curl_auth_decode_ntlm_type2_message(struct Curl_easy *data, fprintf(stderr, "**** Header %s\n ", header); }); - free(type2); - return result; } @@ -345,24 +315,28 @@ static void unicodecpy(unsigned char *dest, const char *src, size_t length) /* * Curl_auth_create_ntlm_type1_message() * - * This is used to generate an already encoded NTLM type-1 message ready for - * sending to the recipient using the appropriate compile time crypto API. + * This is used to generate an NTLM type-1 message ready for sending to the + * recipient using the appropriate compile time crypto API. * * Parameters: * - * userp [in] - The user name in the format User or Domain\User. - * passdwp [in] - The user's password. + * data [in] - The session handle. + * userp [in] - The username 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 hostname. * ntlm [in/out] - The NTLM 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. + * out [out] - The result storage. * * Returns CURLE_OK on success. */ -CURLcode Curl_auth_create_ntlm_type1_message(const char *userp, +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, - char **outptr, size_t *outlen) + struct bufref *out) { /* NTLM type-1 message structure: @@ -380,7 +354,7 @@ CURLcode Curl_auth_create_ntlm_type1_message(const char *userp, size_t size; - unsigned char ntlmbuf[NTLM_BUFSIZE]; + char *ntlmbuf; const char *host = ""; /* empty */ const char *domain = ""; /* empty */ size_t hostlen = 0; @@ -388,49 +362,49 @@ CURLcode Curl_auth_create_ntlm_type1_message(const char *userp, 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_ntlm_cleanup(ntlm); - -#if USE_NTRESPONSES && USE_NTLM2SESSION -#define NTLM2FLAG NTLMFLAG_NEGOTIATE_NTLM2_KEY -#else -#define NTLM2FLAG 0 -#endif - snprintf((char *)ntlmbuf, NTLM_BUFSIZE, - 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 | - NTLM2FLAG | - 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 */); + 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" /* hostname offset */ + "%c%c" /* 2 zeroes */ + "%s" /* hostname */ + "%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; @@ -441,24 +415,24 @@ CURLcode Curl_auth_create_ntlm_type1_message(const char *userp, LONGQUARTET(NTLMFLAG_NEGOTIATE_OEM | NTLMFLAG_REQUEST_TARGET | NTLMFLAG_NEGOTIATE_NTLM_KEY | - NTLM2FLAG | + NTLMFLAG_NEGOTIATE_NTLM2_KEY | NTLMFLAG_NEGOTIATE_ALWAYS_SIGN), NTLMFLAG_NEGOTIATE_OEM | NTLMFLAG_REQUEST_TARGET | NTLMFLAG_NEGOTIATE_NTLM_KEY | - NTLM2FLAG | + NTLMFLAG_NEGOTIATE_NTLM2_KEY | NTLMFLAG_NEGOTIATE_ALWAYS_SIGN); ntlm_print_flags(stderr, NTLMFLAG_NEGOTIATE_OEM | NTLMFLAG_REQUEST_TARGET | NTLMFLAG_NEGOTIATE_NTLM_KEY | - NTLM2FLAG | + NTLMFLAG_NEGOTIATE_NTLM2_KEY | NTLMFLAG_NEGOTIATE_ALWAYS_SIGN); fprintf(stderr, "\n****\n"); }); - /* Return with binary blob encoded into base64 */ - return Curl_base64_encode(NULL, (char *)ntlmbuf, size, outptr, outlen); + Curl_bufref_set(out, ntlmbuf, size, curl_free); + return CURLE_OK; } /* @@ -470,12 +444,10 @@ CURLcode Curl_auth_create_ntlm_type1_message(const char *userp, * Parameters: * * data [in] - The session handle. - * userp [in] - The user name in the format User or Domain\User. - * passdwp [in] - The user's password. + * userp [in] - The username in the format User or Domain\User. + * passwdp [in] - The user's password. * ntlm [in/out] - The NTLM 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. + * out [out] - The result storage. * * Returns CURLE_OK on success. */ @@ -483,8 +455,7 @@ CURLcode Curl_auth_create_ntlm_type3_message(struct Curl_easy *data, const char *userp, const char *passwdp, struct ntlmdata *ntlm, - char **outptr, size_t *outlen) - + struct bufref *out) { /* NTLM type-3 message structure: @@ -495,7 +466,7 @@ CURLcode Curl_auth_create_ntlm_type3_message(struct Curl_easy *data, 12 LM/LMv2 Response security buffer 20 NTLM/NTLMv2 Response security buffer 28 Target Name security buffer - 36 User Name security buffer + 36 username security buffer 44 Workstation Name security buffer (52) Session Key security buffer (*) (60) Flags long (*) @@ -507,17 +478,17 @@ CURLcode Curl_auth_create_ntlm_type3_message(struct Curl_easy *data, CURLcode result = CURLE_OK; size_t size; unsigned char ntlmbuf[NTLM_BUFSIZE]; - int lmrespoff; + unsigned int lmrespoff; unsigned char lmresp[24]; /* fixed-size */ -#if USE_NTRESPONSES - int ntrespoff; + unsigned int ntrespoff; unsigned int ntresplen = 24; unsigned char ntresp[24]; /* fixed-size */ unsigned char *ptr_ntresp = &ntresp[0]; unsigned char *ntlmv2resp = NULL; -#endif bool unicode = (ntlm->flags & NTLMFLAG_NEGOTIATE_UNICODE) ? TRUE : FALSE; - char host[HOSTNAME_MAX + 1] = ""; + /* The fixed hostname we provide, in order to not leak our real local host + name. Copy the name used by Firefox. */ + static const char host[] = "WORKSTATION"; const char *user; const char *domain = ""; size_t hostoff = 0; @@ -527,6 +498,8 @@ CURLcode Curl_auth_create_ntlm_type3_message(struct Curl_easy *data, 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, '/'); @@ -539,30 +512,23 @@ CURLcode Curl_auth_create_ntlm_type3_message(struct Curl_easy *data, else user = userp; - if(user) - userlen = strlen(user); + userlen = strlen(user); + hostlen = sizeof(host) - 1; - /* 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!\n"); - hostlen = 0; - } - else { - hostlen = strlen(host); - } - -#if USE_NTRESPONSES && USE_NTLM_V2 - if(ntlm->target_info_len) { + if(ntlm->flags & NTLMFLAG_NEGOTIATE_NTLM2_KEY) { unsigned char ntbuffer[0x18]; - unsigned int entropy[2]; + unsigned char entropy[8]; unsigned char ntlmv2hash[0x18]; - result = Curl_rand(data, &entropy[0], 2); + /* 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(data, passwdp, ntbuffer); + result = Curl_ntlm_core_mk_nt_hash(passwdp, ntbuffer); if(result) return result; @@ -572,86 +538,42 @@ CURLcode Curl_auth_create_ntlm_type3_message(struct Curl_easy *data, return result; /* LMv2 response */ - result = Curl_ntlm_core_mk_lmv2_resp(ntlmv2hash, - (unsigned char *)&entropy[0], + 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, - (unsigned char *)&entropy[0], + result = Curl_ntlm_core_mk_ntlmv2_resp(ntlmv2hash, entropy, ntlm, &ntlmv2resp, &ntresplen); if(result) return result; ptr_ntresp = ntlmv2resp; } - else -#endif - -#if USE_NTRESPONSES && USE_NTLM2SESSION - /* We don't support NTLM2 if we don't have USE_NTRESPONSES */ - if(ntlm->flags & NTLMFLAG_NEGOTIATE_NTLM2_KEY) { - unsigned char ntbuffer[0x18]; - unsigned char tmp[0x18]; - unsigned char md5sum[MD5_DIGEST_LENGTH]; - unsigned int entropy[2]; - - /* Need to create 8 bytes random data */ - result = Curl_rand(data, &entropy[0], 2); - if(result) - return result; - - /* 8 bytes random data as challenge in lmresp */ - memcpy(lmresp, entropy, 8); - - /* Pad with zeros */ - memset(lmresp + 8, 0, 0x10); - - /* Fill tmp with challenge(nonce?) + entropy */ - memcpy(tmp, &ntlm->nonce[0], 8); - memcpy(tmp + 8, entropy, 8); - - result = Curl_ssl_md5sum(tmp, 16, md5sum, MD5_DIGEST_LENGTH); - if(!result) - /* We shall only use the first 8 bytes of md5sum, but the des code in - Curl_ntlm_core_lm_resp only encrypt the first 8 bytes */ - result = Curl_ntlm_core_mk_nt_hash(data, passwdp, ntbuffer); - if(result) - return result; - - Curl_ntlm_core_lm_resp(ntbuffer, md5sum, ntresp); - - /* End of NTLM2 Session code */ - - } - else -#endif - { + else { -#if USE_NTRESPONSES unsigned char ntbuffer[0x18]; -#endif unsigned char lmbuffer[0x18]; -#if USE_NTRESPONSES - result = Curl_ntlm_core_mk_nt_hash(data, passwdp, ntbuffer); + /* 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); -#endif - result = Curl_ntlm_core_mk_lm_hash(data, passwdp, lmbuffer); + 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 &= ~(unsigned int)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.io/ntlm.html#ntlmVersion2 */ + * See https://davenport.sourceforge.net/ntlm.html#ntlmVersion2 */ } if(unicode) { @@ -661,98 +583,88 @@ CURLcode Curl_auth_create_ntlm_type3_message(struct Curl_easy *data, } lmrespoff = 64; /* size of the message header */ -#if USE_NTRESPONSES ntrespoff = lmrespoff + 0x18; domoff = ntrespoff + ntresplen; -#else - domoff = lmrespoff + 0x18; -#endif useroff = domoff + domlen; hostoff = useroff + userlen; /* Create the big type-3 message binary blob */ - size = snprintf((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, /* zero termination */ - 0, 0, 0, /* type-3 long, the 24 upper bits */ - - SHORTPAIR(0x18), /* LanManager response length, twice */ - SHORTPAIR(0x18), - SHORTPAIR(lmrespoff), - 0x0, 0x0, - -#if USE_NTRESPONSES - SHORTPAIR(ntresplen), /* NT-response length, twice */ - SHORTPAIR(ntresplen), - SHORTPAIR(ntrespoff), - 0x0, 0x0, -#else - 0x0, 0x0, - 0x0, 0x0, - 0x0, 0x0, - 0x0, 0x0, -#endif - 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)); + 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); @@ -768,12 +680,14 @@ CURLcode Curl_auth_create_ntlm_type3_message(struct Curl_easy *data, ntlm_print_hex(stderr, (char *)&ntlmbuf[lmrespoff], 0x18); }); -#if USE_NTRESPONSES - if(size < (NTLM_BUFSIZE - ntresplen)) { - DEBUGASSERT(size == (size_t)ntrespoff); - memcpy(&ntlmbuf[size], ptr_ntresp, ntresplen); - size += ntresplen; + /* 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="); @@ -782,8 +696,6 @@ CURLcode Curl_auth_create_ntlm_type3_message(struct Curl_easy *data, free(ntlmv2resp);/* Free the dynamic buffer allocated for NTLMv2 */ -#endif - DEBUG_OUT({ fprintf(stderr, "\n flags=0x%02.2x%02.2x%02.2x%02.2x 0x%08.8x ", LONGQUARTET(ntlm->flags), ntlm->flags); @@ -794,7 +706,7 @@ CURLcode Curl_auth_create_ntlm_type3_message(struct Curl_easy *data, /* 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"); + failf(data, "user + domain + hostname too big"); return CURLE_OUT_OF_MEMORY; } @@ -822,31 +734,25 @@ CURLcode Curl_auth_create_ntlm_type3_message(struct Curl_easy *data, size += hostlen; - /* Convert domain, user, and host to ASCII but leave the rest as-is */ - result = Curl_convert_to_network(data, (char *)&ntlmbuf[domoff], - size - domoff); - if(result) - return CURLE_CONV_FAILED; - - /* Return with binary blob encoded into base64 */ - result = Curl_base64_encode(NULL, (char *)ntlmbuf, size, outptr, outlen); + /* Return the binary blob. */ + result = Curl_bufref_memdup(out, ntlmbuf, size); - Curl_auth_ntlm_cleanup(ntlm); + Curl_auth_cleanup_ntlm(ntlm); return result; } /* -* Curl_auth_ntlm_cleanup() -* -* This is used to clean up the NTLM specific data. -* -* Parameters: -* -* ntlm [in/out] - The NTLM data struct being cleaned up. -* -*/ -void Curl_auth_ntlm_cleanup(struct ntlmdata *ntlm) + * 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); diff --git a/contrib/curl/lib/vauth/ntlm.h b/contrib/curl/lib/vauth/ntlm.h index f906a3c7a7..31ce921cd1 100644 --- a/contrib/curl/lib/vauth/ntlm.h +++ b/contrib/curl/lib/vauth/ntlm.h @@ -1,5 +1,5 @@ -#ifndef HEADER_CURL_NTLM_H -#define HEADER_CURL_NTLM_H +#ifndef HEADER_VAUTH_NTLM_H +#define HEADER_VAUTH_NTLM_H /*************************************************************************** * _ _ ____ _ * Project ___| | | | _ \| | @@ -7,11 +7,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2016, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -20,6 +20,8 @@ * 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" @@ -32,7 +34,8 @@ /* Stuff only required for curl_ntlm_msgs.c */ #ifdef BUILDING_CURL_NTLM_MSGS_C -/* Flag bits definitions based on https://davenport.sourceforge.io/ntlm.html */ +/* 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 @@ -61,9 +64,6 @@ /* Indicates that the LAN Manager session key should be used for signing and sealing authenticated communications. */ -#define NTLMFLAG_NEGOTIATE_NETWARE (1<<8) -/* unknown purpose */ - #define NTLMFLAG_NEGOTIATE_NTLM_KEY (1<<9) /* Indicates that NTLM authentication is being used. */ @@ -140,4 +140,4 @@ #endif /* USE_NTLM */ -#endif /* HEADER_CURL_NTLM_H */ +#endif /* HEADER_VAUTH_NTLM_H */ diff --git a/contrib/curl/lib/vauth/ntlm_sspi.c b/contrib/curl/lib/vauth/ntlm_sspi.c index c3305176d8..55ec8201d8 100644 --- a/contrib/curl/lib/vauth/ntlm_sspi.c +++ b/contrib/curl/lib/vauth/ntlm_sspi.c @@ -5,11 +5,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2016, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -18,6 +18,8 @@ * 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" @@ -28,10 +30,11 @@ #include "vauth/vauth.h" #include "urldata.h" -#include "curl_base64.h" +#include "curl_ntlm_core.h" #include "warnless.h" #include "curl_multibyte.h" #include "sendf.h" +#include "strdup.h" /* The last #include files should be: */ #include "curl_memory.h" @@ -52,9 +55,14 @@ bool Curl_auth_is_ntlm_supported(void) SECURITY_STATUS status; /* Query the security package for NTLM */ - status = s_pSecFn->QuerySecurityPackageInfo((TCHAR *) TEXT(SP_NAME_NTLM), + status = Curl_pSecFn->QuerySecurityPackageInfo((TCHAR *) TEXT(SP_NAME_NTLM), &SecurityPackage); + /* Release the package buffer as it is not required anymore */ + if(status == SEC_E_OK) { + Curl_pSecFn->FreeContextBuffer(SecurityPackage); + } + return (status == SEC_E_OK ? TRUE : FALSE); } @@ -66,19 +74,23 @@ bool Curl_auth_is_ntlm_supported(void) * * Parameters: * - * userp [in] - The user name in the format User or Domain\User. - * passdwp [in] - The user's password. + * data [in] - The session handle. + * userp [in] - The username 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 hostname. * ntlm [in/out] - The NTLM 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. + * out [out] - The result storage. * * Returns CURLE_OK on success. */ -CURLcode Curl_auth_create_ntlm_type1_message(const char *userp, +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, - char **outptr, size_t *outlen) + struct bufref *out) { PSecPkgInfo SecurityPackage; SecBuffer type_1_buf; @@ -88,18 +100,20 @@ CURLcode Curl_auth_create_ntlm_type1_message(const char *userp, TimeStamp expiry; /* For Windows 9x compatibility of SSPI calls */ /* Clean up any former leftovers and initialise to defaults */ - Curl_auth_ntlm_cleanup(ntlm); + Curl_auth_cleanup_ntlm(ntlm); /* Query the security package for NTLM */ - status = s_pSecFn->QuerySecurityPackageInfo((TCHAR *) TEXT(SP_NAME_NTLM), + status = Curl_pSecFn->QuerySecurityPackageInfo((TCHAR *) TEXT(SP_NAME_NTLM), &SecurityPackage); - if(status != SEC_E_OK) - return CURLE_NOT_BUILT_IN; + if(status != SEC_E_OK) { + failf(data, "SSPI: could not 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); + Curl_pSecFn->FreeContextBuffer(SecurityPackage); /* Allocate our output buffer */ ntlm->output_token = malloc(ntlm->token_max); @@ -122,14 +136,12 @@ CURLcode Curl_auth_create_ntlm_type1_message(const char *userp, ntlm->p_identity = NULL; /* Allocate our credentials handle */ - ntlm->credentials = malloc(sizeof(CredHandle)); + ntlm->credentials = calloc(1, sizeof(CredHandle)); if(!ntlm->credentials) return CURLE_OUT_OF_MEMORY; - memset(ntlm->credentials, 0, sizeof(CredHandle)); - /* Acquire our credentials handle */ - status = s_pSecFn->AcquireCredentialsHandle(NULL, + status = Curl_pSecFn->AcquireCredentialsHandle(NULL, (TCHAR *) TEXT(SP_NAME_NTLM), SECPKG_CRED_OUTBOUND, NULL, ntlm->p_identity, NULL, NULL, @@ -138,11 +150,13 @@ CURLcode Curl_auth_create_ntlm_type1_message(const char *userp, return CURLE_LOGIN_DENIED; /* Allocate our new context handle */ - ntlm->context = malloc(sizeof(CtxtHandle)); + ntlm->context = calloc(1, sizeof(CtxtHandle)); if(!ntlm->context) return CURLE_OUT_OF_MEMORY; - memset(ntlm->context, 0, sizeof(CtxtHandle)); + 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; @@ -153,21 +167,23 @@ CURLcode Curl_auth_create_ntlm_type1_message(const char *userp, type_1_buf.cbBuffer = curlx_uztoul(ntlm->token_max); /* Generate our type-1 message */ - status = s_pSecFn->InitializeSecurityContext(ntlm->credentials, NULL, - (TCHAR *) TEXT(""), + status = Curl_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); + Curl_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_RECV_ERROR; + return CURLE_AUTH_ERROR; - /* Base64 encode the response */ - return Curl_base64_encode(NULL, (char *) ntlm->output_token, - type_1_buf.cbBuffer, outptr, outlen); + /* Return the response. */ + Curl_bufref_set(out, ntlm->output_token, type_1_buf.cbBuffer, NULL); + return CURLE_OK; } /* @@ -178,42 +194,33 @@ CURLcode Curl_auth_create_ntlm_type1_message(const char *userp, * Parameters: * * data [in] - The session handle. - * type2msg [in] - The base64 encoded type-2 message. + * 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 char *type2msg, + const struct bufref *type2, struct ntlmdata *ntlm) { - CURLcode result = CURLE_OK; - unsigned char *type2 = NULL; - size_t type2_len = 0; - #if defined(CURL_DISABLE_VERBOSE_STRINGS) (void) data; #endif - /* Decode the base-64 encoded type-2 message */ - if(strlen(type2msg) && *type2msg != '=') { - result = Curl_base64_decode(type2msg, &type2, &type2_len); - if(result) - return result; - } - /* Ensure we have a valid type-2 message */ - if(!type2) { - infof(data, "NTLM handshake failure (empty type-2 message)\n"); - + if(!Curl_bufref_len(type2)) { + infof(data, "NTLM handshake failure (empty type-2 message)"); return CURLE_BAD_CONTENT_ENCODING; } - /* Simply store the challenge for use later */ - ntlm->input_token = type2; - ntlm->input_token_len = type2_len; + /* Store the challenge for later use */ + ntlm->input_token = Curl_memdup0((const char *)Curl_bufref_ptr(type2), + Curl_bufref_len(type2)); + if(!ntlm->input_token) + return CURLE_OUT_OF_MEMORY; + ntlm->input_token_len = Curl_bufref_len(type2); - return result; + return CURLE_OK; } /* @@ -226,12 +233,10 @@ CURLcode Curl_auth_decode_ntlm_type2_message(struct Curl_easy *data, * Parameters: * * data [in] - The session handle. - * userp [in] - The user name in the format User or Domain\User. - * passdwp [in] - The user's password. + * userp [in] - The username in the format User or Domain\User. + * passwdp [in] - The user's password. * ntlm [in/out] - The NTLM 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. + * out [out] - The result storage. * * Returns CURLE_OK on success. */ @@ -239,10 +244,10 @@ CURLcode Curl_auth_create_ntlm_type3_message(struct Curl_easy *data, const char *userp, const char *passwdp, struct ntlmdata *ntlm, - char **outptr, size_t *outlen) + struct bufref *out) { CURLcode result = CURLE_OK; - SecBuffer type_2_buf; + SecBuffer type_2_bufs[2]; SecBuffer type_3_buf; SecBufferDesc type_2_desc; SecBufferDesc type_3_desc; @@ -250,16 +255,46 @@ CURLcode Curl_auth_create_ntlm_type3_message(struct Curl_easy *data, 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_buf; - type_2_buf.BufferType = SECBUFFER_TOKEN; - type_2_buf.pvBuffer = ntlm->input_token; - type_2_buf.cbBuffer = curlx_uztoul(ntlm->input_token_len); + 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 = Curl_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; @@ -270,32 +305,32 @@ CURLcode Curl_auth_create_ntlm_type3_message(struct Curl_easy *data, type_3_buf.cbBuffer = curlx_uztoul(ntlm->token_max); /* Generate our type-3 message */ - status = s_pSecFn->InitializeSecurityContext(ntlm->credentials, + status = Curl_pSecFn->InitializeSecurityContext(ntlm->credentials, ntlm->context, - (TCHAR *) TEXT(""), + 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\n", + infof(data, "NTLM handshake failure (type-3 message): Status=%lx", status); - return CURLE_RECV_ERROR; - } + if(status == SEC_E_INSUFFICIENT_MEMORY) + return CURLE_OUT_OF_MEMORY; - /* Base64 encode the response */ - result = Curl_base64_encode(data, (char *) ntlm->output_token, - type_3_buf.cbBuffer, outptr, outlen); - - Curl_auth_ntlm_cleanup(ntlm); + 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_ntlm_cleanup() + * Curl_auth_cleanup_ntlm() * * This is used to clean up the NTLM specific data. * @@ -304,18 +339,18 @@ CURLcode Curl_auth_create_ntlm_type3_message(struct Curl_easy *data, * ntlm [in/out] - The NTLM data struct being cleaned up. * */ -void Curl_auth_ntlm_cleanup(struct ntlmdata *ntlm) +void Curl_auth_cleanup_ntlm(struct ntlmdata *ntlm) { /* Free our security context */ if(ntlm->context) { - s_pSecFn->DeleteSecurityContext(ntlm->context); + Curl_pSecFn->DeleteSecurityContext(ntlm->context); free(ntlm->context); ntlm->context = NULL; } /* Free our credentials handle */ if(ntlm->credentials) { - s_pSecFn->FreeCredentialsHandle(ntlm->credentials); + Curl_pSecFn->FreeCredentialsHandle(ntlm->credentials); free(ntlm->credentials); ntlm->credentials = NULL; } @@ -330,6 +365,8 @@ void Curl_auth_ntlm_cleanup(struct ntlmdata *ntlm) /* Reset any variables */ ntlm->token_max = 0; + + Curl_safefree(ntlm->spn); } #endif /* USE_WINDOWS_SSPI && USE_NTLM */ diff --git a/contrib/curl/lib/vauth/oauth2.c b/contrib/curl/lib/vauth/oauth2.c index 6288f89a38..dc94afa365 100644 --- a/contrib/curl/lib/vauth/oauth2.c +++ b/contrib/curl/lib/vauth/oauth2.c @@ -5,11 +5,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2016, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -18,17 +18,22 @@ * 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 #include "urldata.h" #include "vauth/vauth.h" -#include "curl_base64.h" #include "warnless.h" #include "curl_printf.h" @@ -39,48 +44,65 @@ /* * Curl_auth_create_oauth_bearer_message() * - * This is used to generate an already encoded OAuth 2.0 message ready for - * sending to the recipient. + * This is used to generate an OAuth 2.0 message ready for sending to the + * recipient. * * Parameters: * - * data[in] - The session handle. - * user[in] - The user name. - * host[in] - The host name(for OAUTHBEARER). - * port[in] - The port(for OAUTHBEARER when not Port 80). + * user[in] - The username. + * host[in] - The hostname. + * port[in] - The port(when not Port 80). * bearer[in] - The bearer token. - * 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. + * out[out] - The result storage. * * Returns CURLE_OK on success. */ -CURLcode Curl_auth_create_oauth_bearer_message(struct Curl_easy *data, - const char *user, +CURLcode Curl_auth_create_oauth_bearer_message(const char *user, const char *host, const long port, const char *bearer, - char **outptr, size_t *outlen) + struct bufref *out) { - CURLcode result = CURLE_OK; - char *oauth = NULL; + char *oauth; /* Generate the message */ - if(host == NULL && (port == 0 || port == 80)) - oauth = aprintf("user=%s\1auth=Bearer %s\1\1", user, bearer); - else if(port == 0 || port == 80) - oauth = aprintf("user=%s\1host=%s\1auth=Bearer %s\1\1", user, host, + if(port == 0 || port == 80) + oauth = aprintf("n,a=%s,\1host=%s\1auth=Bearer %s\1\1", user, host, bearer); else - oauth = aprintf("user=%s\1host=%s\1port=%ld\1auth=Bearer %s\1\1", user, + 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; - /* Base64 encode the reply */ - result = Curl_base64_encode(data, oauth, strlen(oauth), outptr, outlen); + Curl_bufref_set(out, oauth, strlen(oauth), curl_free); + return CURLE_OK; +} - free(oauth); +/* + * 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 username. + * 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; - return result; + Curl_bufref_set(out, xoauth, strlen(xoauth), curl_free); + return CURLE_OK; } +#endif /* disabled, no users */ diff --git a/contrib/curl/lib/vauth/spnego_gssapi.c b/contrib/curl/lib/vauth/spnego_gssapi.c index 8840db8fda..f48d9b7000 100644 --- a/contrib/curl/lib/vauth/spnego_gssapi.c +++ b/contrib/curl/lib/vauth/spnego_gssapi.c @@ -5,11 +5,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2016, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -18,6 +18,8 @@ * 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 * ***************************************************************************/ @@ -63,10 +65,10 @@ bool Curl_auth_is_spnego_supported(void) * Parameters: * * data [in] - The session handle. - * userp [in] - The user name in the format User or Domain\User. - * passdwp [in] - The user's password. + * userp [in] - The username 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. + * host [in] - The hostname. * chlg64 [in] - The optional base64 encoded challenge message. * nego [in/out] - The Negotiate data struct being used and modified. * @@ -89,15 +91,17 @@ CURLcode Curl_auth_decode_spnego_message(struct Curl_easy *data, 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; + gss_channel_bindings_t chan_bindings = GSS_C_NO_CHANNEL_BINDINGS; + struct gss_channel_bindings_struct chan; (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_spnego_cleanup(nego); + * rejected it (since we are again here). Exit with an error since we + * cannot invent anything better */ + Curl_auth_cleanup_spnego(nego); return CURLE_LOGIN_DENIED; } @@ -121,7 +125,7 @@ CURLcode Curl_auth_decode_spnego_message(struct Curl_easy *data, free(spn); - return CURLE_OUT_OF_MEMORY; + return CURLE_AUTH_ERROR; } free(spn); @@ -137,8 +141,7 @@ CURLcode Curl_auth_decode_spnego_message(struct Curl_easy *data, /* Ensure we have a valid challenge message */ if(!chlg) { - infof(data, "SPNEGO handshake failure (empty challenge message)\n"); - + infof(data, "SPNEGO handshake failure (empty challenge message)"); return CURLE_BAD_CONTENT_ENCODING; } @@ -147,13 +150,21 @@ CURLcode Curl_auth_decode_spnego_message(struct Curl_easy *data, input_token.length = chlglen; } + /* Set channel binding data if available */ + if(nego->channel_binding_data.leng > 0) { + memset(&chan, 0, sizeof(struct gss_channel_bindings_struct)); + chan.application_data.length = nego->channel_binding_data.leng; + chan.application_data.value = nego->channel_binding_data.bufr; + chan_bindings = &chan; + } + /* 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, + chan_bindings, &input_token, &output_token, TRUE, @@ -170,16 +181,20 @@ CURLcode Curl_auth_decode_spnego_message(struct Curl_easy *data, Curl_gss_log_error(data, "gss_init_sec_context() failed: ", major_status, minor_status); - return CURLE_OUT_OF_MEMORY; + return CURLE_AUTH_ERROR; } if(!output_token.value || !output_token.length) { if(output_token.value) gss_release_buffer(&unused_status, &output_token); - return CURLE_OUT_OF_MEMORY; + 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; @@ -201,16 +216,14 @@ CURLcode Curl_auth_decode_spnego_message(struct Curl_easy *data, * * Returns CURLE_OK on success. */ -CURLcode Curl_auth_create_spnego_message(struct Curl_easy *data, - struct negotiatedata *nego, +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(data, - nego->output_token.value, + result = Curl_base64_encode(nego->output_token.value, nego->output_token.length, outptr, outlen); @@ -234,7 +247,7 @@ CURLcode Curl_auth_create_spnego_message(struct Curl_easy *data, } /* - * Curl_auth_spnego_cleanup() + * Curl_auth_cleanup_spnego() * * This is used to clean up the SPNEGO (Negotiate) specific data. * @@ -243,7 +256,7 @@ CURLcode Curl_auth_create_spnego_message(struct Curl_easy *data, * nego [in/out] - The Negotiate data struct being cleaned up. * */ -void Curl_auth_spnego_cleanup(struct negotiatedata *nego) +void Curl_auth_cleanup_spnego(struct negotiatedata *nego) { OM_uint32 minor_status; @@ -269,6 +282,10 @@ void Curl_auth_spnego_cleanup(struct negotiatedata *nego) /* 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/contrib/curl/lib/vauth/spnego_sspi.c b/contrib/curl/lib/vauth/spnego_sspi.c index 5fa95e2ebd..38b26ab90c 100644 --- a/contrib/curl/lib/vauth/spnego_sspi.c +++ b/contrib/curl/lib/vauth/spnego_sspi.c @@ -5,11 +5,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2016, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -18,6 +18,8 @@ * 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 * ***************************************************************************/ @@ -34,6 +36,7 @@ #include "warnless.h" #include "curl_multibyte.h" #include "sendf.h" +#include "strerror.h" /* The last #include files should be: */ #include "curl_memory.h" @@ -54,10 +57,16 @@ bool Curl_auth_is_spnego_supported(void) SECURITY_STATUS status; /* Query the security package for Negotiate */ - status = s_pSecFn->QuerySecurityPackageInfo((TCHAR *) + status = Curl_pSecFn->QuerySecurityPackageInfo((TCHAR *) TEXT(SP_NAME_NEGOTIATE), &SecurityPackage); + /* Release the package buffer as it is not required anymore */ + if(status == SEC_E_OK) { + Curl_pSecFn->FreeContextBuffer(SecurityPackage); + } + + return (status == SEC_E_OK ? TRUE : FALSE); } @@ -70,10 +79,10 @@ bool Curl_auth_is_spnego_supported(void) * Parameters: * * data [in] - The session handle. - * userp [in] - The user name in the format User or Domain\User. - * passdwp [in] - The user's password. + * user [in] - The username 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. + * host [in] - The hostname. * chlg64 [in] - The optional base64 encoded challenge message. * nego [in/out] - The Negotiate data struct being used and modified. * @@ -91,7 +100,7 @@ CURLcode Curl_auth_decode_spnego_message(struct Curl_easy *data, size_t chlglen = 0; unsigned char *chlg = NULL; PSecPkgInfo SecurityPackage; - SecBuffer chlg_buf; + SecBuffer chlg_buf[2]; SecBuffer resp_buf; SecBufferDesc chlg_desc; SecBufferDesc resp_desc; @@ -104,9 +113,9 @@ CURLcode Curl_auth_decode_spnego_message(struct Curl_easy *data, 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_spnego_cleanup(nego); + * rejected it (since we are again here). Exit with an error since we + * cannot invent anything better */ + Curl_auth_cleanup_spnego(nego); return CURLE_LOGIN_DENIED; } @@ -119,16 +128,18 @@ CURLcode Curl_auth_decode_spnego_message(struct Curl_easy *data, 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) - return CURLE_NOT_BUILT_IN; + nego->status = (DWORD)Curl_pSecFn->QuerySecurityPackageInfo((TCHAR *) + TEXT(SP_NAME_NEGOTIATE), + &SecurityPackage); + if(nego->status != SEC_E_OK) { + failf(data, "SSPI: could not 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); + Curl_pSecFn->FreeContextBuffer(SecurityPackage); /* Allocate our output buffer */ nego->output_token = malloc(nego->token_max); @@ -137,7 +148,7 @@ CURLcode Curl_auth_decode_spnego_message(struct Curl_easy *data, } if(!nego->credentials) { - /* Do we have credientials to use or are we using single sign-on? */ + /* 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); @@ -152,28 +163,24 @@ CURLcode Curl_auth_decode_spnego_message(struct Curl_easy *data, nego->p_identity = NULL; /* Allocate our credentials handle */ - nego->credentials = malloc(sizeof(CredHandle)); + nego->credentials = calloc(1, sizeof(CredHandle)); if(!nego->credentials) return CURLE_OUT_OF_MEMORY; - memset(nego->credentials, 0, sizeof(CredHandle)); - /* Acquire our credentials handle */ - nego->status = - s_pSecFn->AcquireCredentialsHandle(NULL, + nego->status = (DWORD) + Curl_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_LOGIN_DENIED; + return CURLE_AUTH_ERROR; /* Allocate our new context handle */ - nego->context = malloc(sizeof(CtxtHandle)); + nego->context = calloc(1, sizeof(CtxtHandle)); if(!nego->context) return CURLE_OUT_OF_MEMORY; - - memset(nego->context, 0, sizeof(CtxtHandle)); } if(chlg64 && *chlg64) { @@ -186,18 +193,44 @@ CURLcode Curl_auth_decode_spnego_message(struct Curl_easy *data, /* Ensure we have a valid challenge message */ if(!chlg) { - infof(data, "SPNEGO handshake failure (empty challenge message)\n"); - + 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; - chlg_buf.BufferType = SECBUFFER_TOKEN; - chlg_buf.pvBuffer = chlg; - chlg_buf.cbBuffer = curlx_uztoul(chlglen); + 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 = (DWORD)Curl_pSecFn->QueryContextAttributes( + nego->sslContext, + SECPKG_ATTR_ENDPOINT_BINDINGS, + &pkgBindings + ); + if(nego->status == SEC_E_OK) { + chlg_desc.cBuffers++; + chlg_buf[1].BufferType = SECBUFFER_CHANNEL_BINDINGS; + chlg_buf[1].cbBuffer = pkgBindings.BindingsLength; + chlg_buf[1].pvBuffer = pkgBindings.Bindings; + } + } +#endif } /* Setup the response "output" security buffer */ @@ -209,29 +242,44 @@ CURLcode Curl_auth_decode_spnego_message(struct Curl_easy *data, 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); + nego->status = + (DWORD)Curl_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)) { - return CURLE_OUT_OF_MEMORY; + char buffer[STRERROR_LEN]; + failf(data, "InitializeSecurityContext failed: %s", + Curl_sspi_strerror((int)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); + nego->status = (DWORD)Curl_pSecFn->CompleteAuthToken(nego->context, + &resp_desc); if(GSS_ERROR(nego->status)) { - return CURLE_RECV_ERROR; + char buffer[STRERROR_LEN]; + failf(data, "CompleteAuthToken failed: %s", + Curl_sspi_strerror((int)nego->status, buffer, sizeof(buffer))); + + if(nego->status == (DWORD)SEC_E_INSUFFICIENT_MEMORY) + return CURLE_OUT_OF_MEMORY; + + return CURLE_AUTH_ERROR; } } @@ -256,31 +304,23 @@ CURLcode Curl_auth_decode_spnego_message(struct Curl_easy *data, * * Returns CURLE_OK on success. */ -CURLcode Curl_auth_create_spnego_message(struct Curl_easy *data, - struct negotiatedata *nego, +CURLcode Curl_auth_create_spnego_message(struct negotiatedata *nego, char **outptr, size_t *outlen) { - CURLcode result; - /* Base64 encode the already generated response */ - result = Curl_base64_encode(data, - (const char *) nego->output_token, - nego->output_token_length, - outptr, outlen); - - if(result) - return result; - - if(!*outptr || !*outlen) { + CURLcode result = Curl_base64_encode((const char *) nego->output_token, + nego->output_token_length, outptr, + outlen); + if(!result && (!*outptr || !*outlen)) { free(*outptr); - return CURLE_REMOTE_ACCESS_DENIED; + result = CURLE_REMOTE_ACCESS_DENIED; } - return CURLE_OK; + return result; } /* - * Curl_auth_spnego_cleanup() + * Curl_auth_cleanup_spnego() * * This is used to clean up the SPNEGO (Negotiate) specific data. * @@ -289,18 +329,18 @@ CURLcode Curl_auth_create_spnego_message(struct Curl_easy *data, * nego [in/out] - The Negotiate data struct being cleaned up. * */ -void Curl_auth_spnego_cleanup(struct negotiatedata *nego) +void Curl_auth_cleanup_spnego(struct negotiatedata *nego) { /* Free our security context */ if(nego->context) { - s_pSecFn->DeleteSecurityContext(nego->context); + Curl_pSecFn->DeleteSecurityContext(nego->context); free(nego->context); nego->context = NULL; } /* Free our credentials handle */ if(nego->credentials) { - s_pSecFn->FreeCredentialsHandle(nego->credentials); + Curl_pSecFn->FreeCredentialsHandle(nego->credentials); free(nego->credentials); nego->credentials = NULL; } @@ -316,6 +356,10 @@ void Curl_auth_spnego_cleanup(struct negotiatedata *nego) /* 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/contrib/curl/lib/vauth/vauth.c b/contrib/curl/lib/vauth/vauth.c index b995f34e27..ace43c47d1 100644 --- a/contrib/curl/lib/vauth/vauth.c +++ b/contrib/curl/lib/vauth/vauth.c @@ -5,11 +5,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 2014 - 2016, Steve Holme, . + * Copyright (C) Steve Holme, . * * This software is licensed 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. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -18,6 +18,8 @@ * 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" @@ -25,6 +27,8 @@ #include #include "vauth.h" +#include "urldata.h" +#include "strcase.h" #include "curl_multibyte.h" #include "curl_printf.h" @@ -44,7 +48,7 @@ * Parameters: * * service [in] - The service type such as http, smtp, pop or imap. - * host [in] - The host name. + * host [in] - The hostname. * realm [in] - The realm. * * Returns a pointer to the newly allocated SPN. @@ -72,6 +76,7 @@ TCHAR *Curl_auth_build_spn(const char *service, const char *host, { char *utf8_spn = NULL; TCHAR *tchar_spn = NULL; + TCHAR *dupe_tchar_spn = NULL; (void) realm; @@ -84,47 +89,43 @@ TCHAR *Curl_auth_build_spn(const char *service, const char *host, /* Generate our UTF8 based SPN */ utf8_spn = aprintf("%s/%s", service, host); - if(!utf8_spn) { + if(!utf8_spn) return NULL; - } - - /* Allocate our TCHAR based SPN */ - tchar_spn = Curl_convert_UTF8_to_tchar(utf8_spn); - if(!tchar_spn) { - free(utf8_spn); + /* Allocate and return a TCHAR based SPN. Since curlx_convert_UTF8_to_tchar + must be freed by curlx_unicodefree we will 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; - } - - /* Release the UTF8 variant when operating with Unicode */ - Curl_unicodefree(utf8_spn); - - /* Return our newly allocated SPN */ - return tchar_spn; + 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: -* -* User\Domain (Down-level Logon Name) -* User/Domain (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 credientals cache when -* using GSS-API or via the currently logged in user's credientals when using -* Windows SSPI. -* -* Parameters: -* -* user [in] - The user name. -* -* Returns TRUE on success; otherwise FALSE. -*/ + * 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 username 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 username. + * + * Returns TRUE on success; otherwise FALSE. + */ bool Curl_auth_user_contains_domain(const char *user) { bool valid = FALSE; @@ -138,10 +139,25 @@ bool Curl_auth_user_contains_domain(const char *user) } #if defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI) else - /* User and domain are obtained from the GSS-API credientials cache or the + /* 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/contrib/curl/lib/vauth/vauth.h b/contrib/curl/lib/vauth/vauth.h index 9d61228c3b..7e823484f6 100644 --- a/contrib/curl/lib/vauth/vauth.h +++ b/contrib/curl/lib/vauth/vauth.h @@ -7,11 +7,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 2014 - 2016, Steve Holme, . + * Copyright (C) Steve Holme, . * * This software is licensed 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. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -20,13 +20,17 @@ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY * KIND, either express or implied. * + * SPDX-License-Identifier: curl + * ***************************************************************************/ #include +#include "bufref.h" + struct Curl_easy; -#if !defined(CURL_DISABLE_CRYPTO_AUTH) +#if !defined(CURL_DISABLE_DIGEST_AUTH) struct digestdata; #endif @@ -42,10 +46,20 @@ struct kerberos5data; struct negotiatedata; #endif +#if defined(USE_GSASL) +struct gsasldata; +#endif + #if defined(USE_WINDOWS_SSPI) -#define GSS_ERROR(status) (status & 0x80000000) +#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, @@ -58,50 +72,41 @@ TCHAR *Curl_auth_build_spn(const char *service, const char *host, /* 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 base64 encoded PLAIN cleartext message */ -CURLcode Curl_auth_create_plain_message(struct Curl_easy *data, - const char *userp, - const char *passwdp, - char **outptr, size_t *outlen); - -/* This is used to generate a base64 encoded LOGIN cleartext message */ -CURLcode Curl_auth_create_login_message(struct Curl_easy *data, - const char *valuep, char **outptr, - size_t *outlen); +/* 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 base64 encoded EXTERNAL cleartext message */ -CURLcode Curl_auth_create_external_message(struct Curl_easy *data, - const char *user, char **outptr, - size_t *outlen); +/* This is used to generate a LOGIN cleartext message */ +void Curl_auth_create_login_message(const char *value, struct bufref *out); -#if !defined(CURL_DISABLE_CRYPTO_AUTH) -/* This is used to decode a CRAM-MD5 challenge message */ -CURLcode Curl_auth_decode_cram_md5_message(const char *chlg64, char **outptr, - size_t *outlen); +/* This is used to generate an EXTERNAL cleartext message */ +void Curl_auth_create_external_message(const char *user, struct bufref *out); +#ifndef CURL_DISABLE_DIGEST_AUTH /* This is used to generate a CRAM-MD5 response message */ -CURLcode Curl_auth_create_cram_md5_message(struct Curl_easy *data, - const char *chlg, +CURLcode Curl_auth_create_cram_md5_message(const struct bufref *chlg, const char *userp, const char *passwdp, - char **outptr, size_t *outlen); + 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 char *chlg64, + const struct bufref *chlg, const char *userp, const char *passwdp, const char *service, - char **outptr, size_t *outlen); + struct bufref *out); -/* This is used to decode a HTTP DIGEST challenge message */ +/* 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 a HTTP DIGEST response message */ +/* 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, @@ -112,22 +117,45 @@ CURLcode Curl_auth_create_digest_http_message(struct Curl_easy *data, /* This is used to clean up the digest specific data */ void Curl_auth_digest_cleanup(struct digestdata *digest); -#endif /* !CURL_DISABLE_CRYPTO_AUTH */ +#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(const char *userp, +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, - char **outptr, - size_t *outlen); + 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 char *type2msg, + const struct bufref *type2, struct ntlmdata *ntlm); /* This is used to generate a base64 encoded NTLM type-3 message */ @@ -135,19 +163,24 @@ CURLcode Curl_auth_create_ntlm_type3_message(struct Curl_easy *data, const char *userp, const char *passwdp, struct ntlmdata *ntlm, - char **outptr, size_t *outlen); + struct bufref *out); /* This is used to clean up the NTLM specific data */ -void Curl_auth_ntlm_cleanup(struct ntlmdata *ntlm); +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(struct Curl_easy *data, - const char *user, +CURLcode Curl_auth_create_oauth_bearer_message(const char *user, const char *host, const long port, const char *bearer, - char **outptr, size_t *outlen); + 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); @@ -160,20 +193,20 @@ CURLcode Curl_auth_create_gssapi_user_message(struct Curl_easy *data, const char *service, const char *host, const bool mutual, - const char *chlg64, + const struct bufref *chlg, struct kerberos5data *krb5, - char **outptr, size_t *outlen); + 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 *input, + const char *authzid, + const struct bufref *chlg, struct kerberos5data *krb5, - char **outptr, - size_t *outlen); + struct bufref *out); /* This is used to clean up the GSSAPI specific data */ -void Curl_auth_gssapi_cleanup(struct kerberos5data *krb5); +void Curl_auth_cleanup_gssapi(struct kerberos5data *krb5); #endif /* USE_KERBEROS5 */ #if defined(USE_SPNEGO) @@ -184,7 +217,7 @@ bool Curl_auth_is_spnego_supported(void); message */ CURLcode Curl_auth_decode_spnego_message(struct Curl_easy *data, const char *user, - const char *passwood, + const char *password, const char *service, const char *host, const char *chlg64, @@ -192,12 +225,11 @@ CURLcode Curl_auth_decode_spnego_message(struct Curl_easy *data, /* This is used to generate a base64 encoded SPNEGO (Negotiate) response message */ -CURLcode Curl_auth_create_spnego_message(struct Curl_easy *data, - struct negotiatedata *nego, +CURLcode Curl_auth_create_spnego_message(struct negotiatedata *nego, char **outptr, size_t *outlen); -/* This is used to clean up the SPNEGO specifiec data */ -void Curl_auth_spnego_cleanup(struct negotiatedata *nego); +/* This is used to clean up the SPNEGO specific data */ +void Curl_auth_cleanup_spnego(struct negotiatedata *nego); #endif /* USE_SPNEGO */ diff --git a/contrib/curl/lib/version.c b/contrib/curl/lib/version.c index 3d17768131..3f0f0ae503 100644 --- a/contrib/curl/lib/version.c +++ b/contrib/curl/lib/version.c @@ -5,11 +5,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2016, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -18,19 +18,28 @@ * 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 +#endif + #include #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) || defined(_WIN32) || defined(__SYMBIAN32__)) +# if defined(CURL_STATICLIB) && !defined(CARES_STATICLIB) && \ + defined(_WIN32) # define CARES_STATICLIB # endif # include @@ -44,138 +53,246 @@ #include #endif -#if defined(HAVE_ICONV) && defined(CURL_DOES_CONVERSIONS) -#include -#endif - #ifdef USE_LIBRTMP #include +#include "curl_rtmp.h" #endif -#ifdef USE_LIBSSH2 -#include +#ifdef HAVE_LIBZ +#include #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 +#ifdef HAVE_BROTLI +#if defined(__GNUC__) +/* Ignore -Wvla warnings in brotli headers */ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wvla" +#endif +#include +#if defined(__GNUC__) +#pragma GCC diagnostic pop +#endif #endif -void Curl_version_init(void); +#ifdef HAVE_ZSTD +#include +#endif -/* For thread safety purposes this function is called by global_init so that - the static data in both version functions is initialized. */ -void Curl_version_init(void) +#ifdef USE_GSASL +#include +#endif + +#ifdef USE_OPENLDAP +#include +#endif + +#ifdef HAVE_BROTLI +static void brotli_version(char *buf, size_t bufsz) { - curl_version(); - curl_version_info(CURLVERSION_NOW); + 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 bool initialized; - static char version[200]; - char *ptr = version; - size_t len; - size_t left = sizeof(version); - - if(initialized) - return version; - - strcpy(ptr, LIBCURL_NAME "/" LIBCURL_VERSION); - len = strlen(ptr); - left -= len; - ptr += len; - - if(left > 1) { - len = Curl_ssl_version(ptr + 1, left - 1); - - if(len > 0) { - *ptr = ' '; - left -= ++len; - ptr += len; - } + 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 USE_HTTP3 + 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) { + msnprintf(out, sizeof(out), "%s", debugversion); + 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 - len = snprintf(ptr, left, " zlib/%s", zlibVersion()); - left -= len; - ptr += len; + 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 - /* this function is only present in c-ares, not in the original ares */ - len = snprintf(ptr, left, " c-ares/%s", ares_version(NULL)); - left -= len; - ptr += len; + msnprintf(cares_version, sizeof(cares_version), + "c-ares/%s", ares_version(NULL)); + src[i++] = cares_version; #endif #ifdef USE_LIBIDN2 - if(idn2_check_version(IDN2_VERSION)) { - len = snprintf(ptr, left, " libidn2/%s", idn2_check_version(NULL)); - left -= len; - ptr += len; - } + 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"; +#elif defined(USE_APPLE_IDN) + src[i++] = (char *)"AppleIDN"; #endif + #ifdef USE_LIBPSL - len = snprintf(ptr, left, " libpsl/%s", psl_get_version()); - left -= len; - ptr += len; -#endif -#ifdef USE_WIN32_IDN - len = snprintf(ptr, left, " WinIDN"); - left -= len; - ptr += len; -#endif -#if defined(HAVE_ICONV) && defined(CURL_DOES_CONVERSIONS) -#ifdef _LIBICONV_VERSION - len = snprintf(ptr, left, " iconv/%d.%d", - _LIBICONV_VERSION >> 8, _LIBICONV_VERSION & 255); + { +#if defined(PSL_VERSION_MAJOR) && (PSL_VERSION_MAJOR > 0 || \ + PSL_VERSION_MINOR >= 11) + int num = psl_check_version_number(0); + msnprintf(psl_version, sizeof(psl_version), "libpsl/%d.%d.%d", + num >> 16, (num >> 8) & 0xff, num & 0xff); #else - /* version unknown */ - len = snprintf(ptr, left, " iconv"); -#endif /* _LIBICONV_VERSION */ - left -= len; - ptr += len; + msnprintf(psl_version, sizeof(psl_version), "libpsl/%s", + psl_get_version()); #endif -#ifdef USE_LIBSSH2 - len = snprintf(ptr, left, " libssh2/%s", CURL_LIBSSH2_VERSION); - left -= len; - ptr += len; + src[i++] = psl_version; + } +#endif + +#ifdef USE_SSH + Curl_ssh_version(ssh_version, sizeof(ssh_version)); + src[i++] = ssh_version; #endif #ifdef USE_NGHTTP2 - len = Curl_http2_ver(ptr, left); - left -= len; - ptr += len; + Curl_http2_ver(h2_version, sizeof(h2_version)); + src[i++] = h2_version; +#endif +#ifdef USE_HTTP3 + Curl_quic_ver(h3_version, sizeof(h3_version)); + src[i++] = h3_version; #endif #ifdef USE_LIBRTMP + Curl_rtmp_version(rtmp_version, sizeof(rtmp_version)); + src[i++] = rtmp_version; +#endif +#ifdef USE_HYPER + msnprintf(hyper_buf, sizeof(hyper_buf), "Hyper/%s", hyper_version()); + 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 { - char suff[2]; - if(RTMP_LIB_VERSION & 0xff) { - suff[0] = (RTMP_LIB_VERSION & 0xff) + 'a' - 1; - suff[1] = '\0'; + 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 = (unsigned int)(api.ldapai_vendor_version % 100); + unsigned int major = (unsigned int)(api.ldapai_vendor_version / 10000); + unsigned int minor = + (((unsigned int)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); } - else - suff[0] = '\0'; - - snprintf(ptr, left, " librtmp/%d.%d%s", - RTMP_LIB_VERSION >> 16, (RTMP_LIB_VERSION >> 8) & 0xff, - suff); -/* - If another lib version is added below this one, this code would - also have to do: + } +#endif - len = what snprintf() returned + DEBUGASSERT(i <= VERSION_PARTS); - left -= len; - ptr += len; -*/ + 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; } -#endif + *outp = 0; - initialized = true; - return version; + return out; } /* data for curl_version_info @@ -184,7 +301,7 @@ char *curl_version(void) protocol line has its own #if line to make things easier on the eye. */ -static const char * const protocols[] = { +static const char * const supported_protocols[] = { #ifndef CURL_DISABLE_DICT "dict", #endif @@ -200,6 +317,9 @@ static const char * const protocols[] = { #ifndef CURL_DISABLE_GOPHER "gopher", #endif +#if defined(USE_SSL) && !defined(CURL_DISABLE_GOPHER) + "gophers", +#endif #ifndef CURL_DISABLE_HTTP "http", #endif @@ -220,6 +340,9 @@ static const char * const protocols[] = { "ldaps", #endif #endif +#ifndef CURL_DISABLE_MQTT + "mqtt", +#endif #ifndef CURL_DISABLE_POP3 "pop3", #endif @@ -228,19 +351,22 @@ static const char * const protocols[] = { #endif #ifdef USE_LIBRTMP "rtmp", + "rtmpe", + "rtmps", + "rtmpt", + "rtmpte", + "rtmpts", #endif #ifndef CURL_DISABLE_RTSP "rtsp", #endif -#ifdef USE_LIBSSH2 +#if defined(USE_SSH) && !defined(USE_WOLFSSH) "scp", #endif -#ifdef USE_LIBSSH2 +#ifdef USE_SSH "sftp", #endif -#if !defined(CURL_DISABLE_SMB) && defined(USE_NTLM) && \ - (CURL_SIZEOF_CURL_OFF_T > 4) && \ - (!defined(USE_WINDOWS_SSPI) || defined(USE_WIN32_CRYPTO)) +#if !defined(CURL_DISABLE_SMB) && defined(USE_CURL_NTLM_CORE) "smb", # ifdef USE_SSL "smbs", @@ -257,100 +383,224 @@ static const char * const protocols[] = { #endif #ifndef CURL_DISABLE_TFTP "tftp", +#endif +#ifndef CURL_DISABLE_HTTP + /* WebSocket support relies on HTTP */ +#ifndef CURL_DISABLE_WEBSOCKETS + "ws", +#endif +#if defined(USE_SSL) && !defined(CURL_DISABLE_WEBSOCKETS) + "wss", +#endif #endif 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 is 0 by default */ -#ifdef ENABLE_IPV6 - | CURL_VERSION_IPV6 +/* + * Feature presence runtime 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 -#ifdef USE_SSL - | CURL_VERSION_SSL + +#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 -#ifdef USE_NTLM - | CURL_VERSION_NTLM + +#if defined(USE_SSL) && defined(USE_ECH) +static int ech_present(curl_version_info_data *info) +{ + (void) info; + return Curl_ssl_supports(NULL, SSLSUPP_ECH); +} #endif -#if !defined(CURL_DISABLE_HTTP) && defined(USE_NTLM) && \ - defined(NTLM_WB_ENABLED) - | CURL_VERSION_NTLM_WB + +/* + * 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 USE_SPNEGO - | CURL_VERSION_SPNEGO +#ifdef CURLRES_ASYNCH + FEATURE("AsynchDNS", NULL, CURL_VERSION_ASYNCHDNS), #endif -#ifdef USE_KERBEROS5 - | CURL_VERSION_KERBEROS5 +#ifdef HAVE_BROTLI + FEATURE("brotli", NULL, CURL_VERSION_BROTLI), +#endif +#ifdef DEBUGBUILD + FEATURE("Debug", NULL, CURL_VERSION_DEBUG), +#endif +#if defined(USE_SSL) && defined(USE_ECH) + FEATURE("ECH", ech_present, 0), +#endif +#ifdef USE_GSASL + FEATURE("gsasl", NULL, CURL_VERSION_GSASL), #endif #ifdef HAVE_GSSAPI - | CURL_VERSION_GSSAPI + FEATURE("GSS-API", NULL, CURL_VERSION_GSSAPI), #endif -#ifdef USE_WINDOWS_SSPI - | CURL_VERSION_SSPI +#ifndef CURL_DISABLE_HSTS + FEATURE("HSTS", NULL, CURL_VERSION_HSTS), #endif -#ifdef HAVE_LIBZ - | CURL_VERSION_LIBZ +#if defined(USE_NGHTTP2) + FEATURE("HTTP2", NULL, CURL_VERSION_HTTP2), #endif -#ifdef DEBUGBUILD - | CURL_VERSION_DEBUG +#if defined(USE_HTTP3) + FEATURE("HTTP3", NULL, CURL_VERSION_HTTP3), #endif -#ifdef CURLDEBUG - | CURL_VERSION_CURLDEBUG +#if defined(USE_SSL) && !defined(CURL_DISABLE_PROXY) && \ + !defined(CURL_DISABLE_HTTP) + FEATURE("HTTPS-proxy", https_proxy_present, CURL_VERSION_HTTPS_PROXY), #endif -#ifdef CURLRES_ASYNCH - | CURL_VERSION_ASYNCHDNS +#if defined(USE_LIBIDN2) || defined(USE_WIN32_IDN) || defined(USE_APPLE_IDN) + FEATURE("IDN", idn_present, CURL_VERSION_IDN), #endif -#if (CURL_SIZEOF_CURL_OFF_T > 4) && \ - ( (SIZEOF_OFF_T > 4) || defined(USE_WIN32_LARGE_FILES) ) - | CURL_VERSION_LARGEFILE +#ifdef USE_IPV6 + FEATURE("IPv6", NULL, CURL_VERSION_IPV6), #endif -#if defined(CURL_DOES_CONVERSIONS) - | CURL_VERSION_CONV +#ifdef USE_KERBEROS5 + FEATURE("Kerberos", NULL, CURL_VERSION_KERBEROS5), #endif -#if defined(USE_TLS_SRP) - | CURL_VERSION_TLSAUTH_SRP +#if (SIZEOF_CURL_OFF_T > 4) && \ + ( (SIZEOF_OFF_T > 4) || defined(USE_WIN32_LARGE_FILES) ) + FEATURE("Largefile", NULL, CURL_VERSION_LARGEFILE), #endif -#if defined(USE_NGHTTP2) - | CURL_VERSION_HTTP2 +#ifdef HAVE_LIBZ + FEATURE("libz", NULL, CURL_VERSION_LIBZ), #endif -#if defined(USE_UNIX_SOCKETS) - | CURL_VERSION_UNIX_SOCKETS +#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(USE_LIBPSL) - | CURL_VERSION_PSL + 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 -#if defined(HTTPS_PROXY_SUPPORT) - | CURL_VERSION_HTTPS_PROXY +#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 runtime */ NULL, /* ssl_version */ 0, /* ssl_version_num, this is kept at zero */ NULL, /* zlib_version */ - protocols, + 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, + NULL /* rtmp version */ }; curl_version_info_data *curl_version_info(CURLversion stamp) { - static bool initialized; -#ifdef USE_LIBSSH2 - static char ssh_buffer[80]; + size_t n; + const struct feat *p; + int features = 0; + +#if defined(USE_SSH) + static char ssh_buf[80]; /* 'ssh_buffer' clashes with libssh/libssh.h */ #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 - if(initialized) - return &version_info; + (void)stamp; /* avoid compiler warnings, we do not use this */ #ifdef USE_SSL Curl_ssl_version(ssl_buffer, sizeof(ssl_buffer)); @@ -372,28 +622,73 @@ curl_version_info_data *curl_version_info(CURLversion stamp) /* 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); - if(version_info.libidn) - version_info.features |= CURL_VERSION_IDN; -#elif defined(USE_WIN32_IDN) - version_info.features |= CURL_VERSION_IDN; #endif -#if defined(HAVE_ICONV) && defined(CURL_DOES_CONVERSIONS) -#ifdef _LIBICONV_VERSION - version_info.iconv_ver_num = _LIBICONV_VERSION; -#else - /* version unknown */ - version_info.iconv_ver_num = -1; -#endif /* _LIBICONV_VERSION */ +#if defined(USE_SSH) + Curl_ssh_version(ssh_buf, sizeof(ssh_buf)); + version_info.libssh_version = ssh_buf; +#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_LIBSSH2 - snprintf(ssh_buffer, sizeof(ssh_buffer), "libssh2/%s", LIBSSH2_VERSION); - version_info.libssh_version = ssh_buffer; +#ifdef USE_NGHTTP2 + { + nghttp2_info *h2 = nghttp2_version(0); + version_info.nghttp2_ver_num = (unsigned int)h2->version_num; + version_info.nghttp2_version = h2->version_str; + } +#endif + +#ifdef USE_HTTP3 + { + 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 - (void)stamp; /* avoid compiler warnings, we don't use this */ +#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; + +#ifdef USE_LIBRTMP + { + static char rtmp_version[30]; + Curl_rtmp_version(rtmp_version, sizeof(rtmp_version)); + version_info.rtmp_version = rtmp_version; + } +#endif - initialized = true; return &version_info; } diff --git a/contrib/curl/lib/version_win32.c b/contrib/curl/lib/version_win32.c new file mode 100644 index 0000000000..05c3a1cce5 --- /dev/null +++ b/contrib/curl/lib/version_win32.c @@ -0,0 +1,321 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Steve Holme, . + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the 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 +#include "version_win32.h" +#include "warnless.h" + +/* The last 2 #include files should be in this order */ +#ifdef BUILDING_LIBCURL +#include "curl_memory.h" +#endif +#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 than, 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_UWP) + /* We have no way to determine the Windows version from Windows apps, + so let's assume we are running on the target Windows version. */ + const WORD fullVersion = MAKEWORD(minorVersion, majorVersion); + const WORD targetVersion = (WORD)_WIN32_WINNT; + + (void)buildVersion; + + 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 are 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/contrib/curl/lib/version_win32.h b/contrib/curl/lib/version_win32.h new file mode 100644 index 0000000000..95a9e7f215 --- /dev/null +++ b/contrib/curl/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, . + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the 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/contrib/curl/lib/vquic/.checksrc b/contrib/curl/lib/vquic/.checksrc new file mode 100644 index 0000000000..9066946c89 --- /dev/null +++ b/contrib/curl/lib/vquic/.checksrc @@ -0,0 +1,2 @@ +enable STRERROR +enable STRNCPY diff --git a/contrib/curl/lib/vquic/curl_msh3.c b/contrib/curl/lib/vquic/curl_msh3.c new file mode 100644 index 0000000000..e57a234ad0 --- /dev/null +++ b/contrib/curl/lib/vquic/curl_msh3.c @@ -0,0 +1,1120 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#ifdef USE_MSH3 + +#include "urldata.h" +#include "hash.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 +#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 */ + struct Curl_hash streams; /* hash `data->mid` to `stream_ctx` */ + /* Flags written by msh3/msquic thread */ + bool handshake_complete; + bool handshake_succeeded; + bool connected; + BIT(initialized); + /* Flags written by curl thread */ + BIT(verbose); + BIT(active); +}; + +static void h3_stream_hash_free(void *stream); + +static void cf_msh3_ctx_init(struct cf_msh3_ctx *ctx, + const struct Curl_addrinfo *ai) +{ + DEBUGASSERT(!ctx->initialized); + Curl_hash_offt_init(&ctx->streams, 63, h3_stream_hash_free); + Curl_sock_assign_addr(&ctx->addr, ai, TRNSPRT_QUIC); + ctx->sock[SP_LOCAL] = CURL_SOCKET_BAD; + ctx->sock[SP_REMOTE] = CURL_SOCKET_BAD; + ctx->initialized = TRUE; +} + +static void cf_msh3_ctx_free(struct cf_msh3_ctx *ctx) +{ + if(ctx && ctx->initialized) { + Curl_hash_destroy(&ctx->streams); + } + free(ctx); +} + +static struct cf_msh3_ctx *h3_get_msh3_ctx(struct Curl_easy *data); + +/* How to access `call_data` from a cf_msh3 filter */ +#undef CF_CTX_CALL_DATA +#define CF_CTX_CALL_DATA(cf) \ + ((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(ctx,data) ((struct stream_ctx *)((data && ctx)? \ + Curl_hash_offt_get(&(ctx)->streams, (data)->mid) : NULL)) + +static void h3_stream_ctx_free(struct stream_ctx *stream) +{ + Curl_bufq_free(&stream->recvbuf); + free(stream); +} + +static void h3_stream_hash_free(void *stream) +{ + DEBUGASSERT(stream); + h3_stream_ctx_free((struct stream_ctx *)stream); +} + +static CURLcode h3_data_setup(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct cf_msh3_ctx *ctx = cf->ctx; + struct stream_ctx *stream = H3_STREAM_CTX(ctx, data); + + if(stream) + return CURLE_OK; + + stream = calloc(1, sizeof(*stream)); + if(!stream) + return CURLE_OUT_OF_MEMORY; + + stream->req = ZERO_NULL; + msh3_lock_initialize(&stream->recv_lock); + Curl_bufq_init2(&stream->recvbuf, H3_STREAM_CHUNK_SIZE, + H3_STREAM_RECV_CHUNKS, BUFQ_OPT_SOFT_LIMIT); + CURL_TRC_CF(data, cf, "data setup"); + + if(!Curl_hash_offt_set(&ctx->streams, data->mid, stream)) { + h3_stream_ctx_free(stream); + return CURLE_OUT_OF_MEMORY; + } + + return CURLE_OK; +} + +static void h3_data_done(struct Curl_cfilter *cf, struct Curl_easy *data) +{ + struct cf_msh3_ctx *ctx = cf->ctx; + struct stream_ctx *stream = H3_STREAM_CTX(ctx, data); + + (void)cf; + if(stream) { + CURL_TRC_CF(data, cf, "easy handle is done"); + Curl_hash_offt_remove(&ctx->streams, data->mid); + } +} + +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.select_bits != bits) { + data->state.select_bits = bits; + /* cannot expire from other thread */ + } +} + +static void h3_drain_stream(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct cf_msh3_ctx *ctx = cf->ctx; + struct stream_ctx *stream = H3_STREAM_CTX(ctx, data); + unsigned char bits; + + (void)cf; + bits = CURL_CSELECT_IN; + if(stream && !stream->upload_done) + bits |= CURL_CSELECT_OUT; + if(data->state.select_bits != bits) { + data->state.select_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 cf_msh3_ctx *ctx = h3_get_msh3_ctx(data); + struct stream_ctx *stream = H3_STREAM_CTX(ctx, data); + CURLcode result = CURLE_OK; + ssize_t nwritten; + + 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 cf_msh3_ctx *ctx = h3_get_msh3_ctx(data); + struct stream_ctx *stream = H3_STREAM_CTX(ctx, data); + CURLcode result; + (void)Request; + + DEBUGF(infof(data, "[MSH3] header received, stream=%d", !!stream)); + if(!stream || stream->recv_header_complete) { + return; + } + + 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 cf_msh3_ctx *ctx = h3_get_msh3_ctx(data); + struct stream_ctx *stream = H3_STREAM_CTX(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 cf_msh3_ctx *ctx = h3_get_msh3_ctx(data); + struct stream_ctx *stream = H3_STREAM_CTX(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 cf_msh3_ctx *ctx = h3_get_msh3_ctx(data); + struct stream_ctx *stream = H3_STREAM_CTX(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 cf_msh3_ctx *ctx = h3_get_msh3_ctx(data); + struct stream_ctx *stream = H3_STREAM_CTX(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 cf_msh3_ctx *ctx = cf->ctx; + struct stream_ctx *stream = H3_STREAM_CTX(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 cf_msh3_ctx *ctx = cf->ctx; + struct stream_ctx *stream = H3_STREAM_CTX(ctx, data); + + /* we have no indication from msh3 when it would be a good time + * to juggle the connection again. So, we compromise by calling + * 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 cf_msh3_ctx *ctx = cf->ctx; + struct stream_ctx *stream = H3_STREAM_CTX(ctx, data); + ssize_t nread = -1; + struct cf_call_data save; + + CURL_TRC_CF(data, cf, "cf_recv(len=%zu), stream=%d", len, !!stream); + if(!stream) { + *err = CURLE_RECV_ERROR; + return -1; + } + CF_DATA_SAVE(save, cf, data); + + 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) + h3_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, bool eos, + CURLcode *err) +{ + struct cf_msh3_ctx *ctx = cf->ctx; + struct stream_ctx *stream = H3_STREAM_CTX(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; + + 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; + } + + 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 does not 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(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]); + h3_drain_stream(cf, data); + } + else if(stream->req) { + Curl_pollset_add_out(data, ps, ctx->sock[SP_LOCAL]); + h3_drain_stream(cf, data); + } + } +} + +static bool cf_msh3_data_pending(struct Curl_cfilter *cf, + const struct Curl_easy *data) +{ + struct cf_msh3_ctx *ctx = cf->ctx; + struct stream_ctx *stream = H3_STREAM_CTX(ctx, data); + struct cf_call_data save; + bool pending = FALSE; + + 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) + h3_drain_stream(cf, (struct Curl_easy *)data); + } + + CF_DATA_RESTORE(cf, save); + return pending; +} + +static CURLcode h3_data_pause(struct Curl_cfilter *cf, + struct Curl_easy *data, + bool pause) +{ + if(!pause) { + h3_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 cf_msh3_ctx *ctx = cf->ctx; + struct stream_ctx *stream = H3_STREAM_CTX(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; + 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; + + DEBUGASSERT(ctx->initialized); + conn_config = Curl_ssl_cf_get_primary_config(cf); + if(!conn_config) + return CURLE_FAILED_INIT; + verify = !!conn_config->verifypeer; + + memcpy(&addr, &ctx->addr.curl_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, "cannot 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, "cannot 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], FALSE) < 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->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); + if(cf->ctx) { + cf_msh3_ctx_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_shutdown, + 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, +}; + +static struct cf_msh3_ctx *h3_get_msh3_ctx(struct Curl_easy *data) +{ + if(data && data->conn) { + struct Curl_cfilter *cf = data->conn->cfilter[FIRSTSOCKET]; + while(cf) { + if(cf->cft == &Curl_cft_http3) + return cf->ctx; + cf = cf->next; + } + } + DEBUGF(infof(data, "no filter context found")); + return NULL; +} + +CURLcode Curl_cf_msh3_create(struct Curl_cfilter **pcf, + struct Curl_easy *data, + struct connectdata *conn, + 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; + } + cf_msh3_ctx_init(ctx, ai); + + result = Curl_cf_create(&cf, &Curl_cft_http3, ctx); + +out: + *pcf = (!result) ? cf : NULL; + if(result) { + Curl_safefree(cf); + cf_msh3_ctx_free(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/contrib/curl/lib/vquic/curl_msh3.h b/contrib/curl/lib/vquic/curl_msh3.h new file mode 100644 index 0000000000..33931f59bb --- /dev/null +++ b/contrib/curl/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, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the 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 + +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/contrib/curl/lib/vquic/curl_ngtcp2.c b/contrib/curl/lib/vquic/curl_ngtcp2.c new file mode 100644 index 0000000000..ef93d98af3 --- /dev/null +++ b/contrib/curl/lib/vquic/curl_ngtcp2.c @@ -0,0 +1,2563 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#if defined(USE_NGTCP2) && defined(USE_NGHTTP3) +#include +#include + +#ifdef USE_OPENSSL +#include +#if defined(OPENSSL_IS_BORINGSSL) || defined(OPENSSL_IS_AWSLC) +#include +#else +#include +#endif +#include "vtls/openssl.h" +#elif defined(USE_GNUTLS) +#include +#include "vtls/gtls.h" +#elif defined(USE_WOLFSSL) +#include +#endif + +#include "urldata.h" +#include "hash.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 "transfer.h" +#include "vquic.h" +#include "vquic_int.h" +#include "vquic-tls.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 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. 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 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; + struct curl_tls_ctx tls; + 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; + 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 bufc_pool stream_bufcp; /* chunk pool for streams */ + struct dynbuf scratch; /* temp buffer for header construction */ + struct Curl_hash streams; /* hash `data->mid` to `h3_stream_ctx` */ + size_t max_stream_window; /* max flow window for one stream */ + uint64_t max_idle_ms; /* max idle time for QUIC connection */ + uint64_t used_bidi_streams; /* bidi streams we have opened */ + uint64_t max_bidi_streams; /* max bidi streams we can open */ + int qlogfd; + BIT(initialized); + BIT(shutdown_started); /* queued shutdown packets */ +}; + +/* 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 + +static void h3_stream_hash_free(void *stream); + +static void cf_ngtcp2_ctx_init(struct cf_ngtcp2_ctx *ctx) +{ + DEBUGASSERT(!ctx->initialized); + ctx->qlogfd = -1; + 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); + Curl_dyn_init(&ctx->scratch, CURL_MAX_HTTP_HEADER); + Curl_hash_offt_init(&ctx->streams, 63, h3_stream_hash_free); + ctx->initialized = TRUE; +} + +static void cf_ngtcp2_ctx_free(struct cf_ngtcp2_ctx *ctx) +{ + if(ctx && ctx->initialized) { + Curl_bufcp_free(&ctx->stream_bufcp); + Curl_dyn_free(&ctx->scratch); + Curl_hash_clean(&ctx->streams); + Curl_hash_destroy(&ctx->streams); + Curl_ssl_peer_cleanup(&ctx->peer); + } + free(ctx); +} + +struct pkt_io_ctx; +static CURLcode cf_progress_ingress(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct pkt_io_ctx *pktx); +static CURLcode cf_progress_egress(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct pkt_io_ctx *pktx); + +/** + * All about the H3 internals of a stream + */ +struct h3_stream_ctx { + curl_int64_t id; /* HTTP/3 protocol identifier */ + struct bufq sendbuf; /* h3 request body */ + struct h1_req_parser h1; /* h1 request parsing */ + size_t sendbuf_len_in_flight; /* sendbuf amount "in flight" */ + curl_uint64_t error3; /* HTTP/3 stream error code */ + curl_off_t upload_left; /* number of request bytes left to upload */ + int status_code; /* HTTP status code */ + CURLcode xfer_result; /* result from xfer_resp_write(_hd) */ + bool resp_hds_complete; /* we have a complete, final response */ + bool closed; /* TRUE on stream close */ + bool reset; /* TRUE on stream reset */ + bool send_closed; /* stream is local closed */ + BIT(quic_flow_blocked); /* stream is blocked by QUIC flow control */ +}; + +#define H3_STREAM_CTX(ctx,data) ((struct h3_stream_ctx *)(\ + data? Curl_hash_offt_get(&(ctx)->streams, (data)->mid) : NULL)) +#define H3_STREAM_CTX_ID(ctx,id) ((struct h3_stream_ctx *)(\ + Curl_hash_offt_get(&(ctx)->streams, (id)))) + +static void h3_stream_ctx_free(struct h3_stream_ctx *stream) +{ + Curl_bufq_free(&stream->sendbuf); + Curl_h1_req_parse_free(&stream->h1); + free(stream); +} + +static void h3_stream_hash_free(void *stream) +{ + DEBUGASSERT(stream); + h3_stream_ctx_free((struct h3_stream_ctx *)stream); +} + +static CURLcode h3_data_setup(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct cf_ngtcp2_ctx *ctx = cf->ctx; + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); + + if(!data) + 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; + Curl_h1_req_parse_init(&stream->h1, H1_PARSE_DEFAULT_MAX_LINE_LEN); + + if(!Curl_hash_offt_set(&ctx->streams, data->mid, stream)) { + h3_stream_ctx_free(stream); + return CURLE_OUT_OF_MEMORY; + } + + return CURLE_OK; +} + +static void cf_ngtcp2_stream_close(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct h3_stream_ctx *stream) +{ + struct cf_ngtcp2_ctx *ctx = cf->ctx; + DEBUGASSERT(data); + DEBUGASSERT(stream); + if(!stream->closed && ctx->qconn && ctx->h3conn) { + CURLcode result; + + nghttp3_conn_set_stream_user_data(ctx->h3conn, stream->id, NULL); + ngtcp2_conn_set_stream_user_data(ctx->qconn, stream->id, NULL); + stream->closed = TRUE; + (void)ngtcp2_conn_shutdown_stream(ctx->qconn, 0, stream->id, + NGHTTP3_H3_REQUEST_CANCELLED); + result = cf_progress_egress(cf, data, NULL); + if(result) + CURL_TRC_CF(data, cf, "[%" FMT_PRId64 "] cancel stream -> %d", + stream->id, result); + } +} + +static void h3_data_done(struct Curl_cfilter *cf, struct Curl_easy *data) +{ + struct cf_ngtcp2_ctx *ctx = cf->ctx; + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); + (void)cf; + if(stream) { + CURL_TRC_CF(data, cf, "[%" FMT_PRId64 "] easy handle is done", + stream->id); + cf_ngtcp2_stream_close(cf, data, stream); + Curl_hash_offt_remove(&ctx->streams, data->mid); + } +} + +static struct Curl_easy *get_stream_easy(struct Curl_cfilter *cf, + struct Curl_easy *data, + int64_t stream_id, + struct h3_stream_ctx **pstream) +{ + struct cf_ngtcp2_ctx *ctx = cf->ctx; + struct h3_stream_ctx *stream; + + (void)cf; + stream = H3_STREAM_CTX(ctx, data); + if(stream && stream->id == stream_id) { + *pstream = stream; + return data; + } + else { + struct Curl_llist_node *e; + DEBUGASSERT(data->multi); + for(e = Curl_llist_head(&data->multi->process); e; e = Curl_node_next(e)) { + struct Curl_easy *sdata = Curl_node_elem(e); + if(sdata->conn != data->conn) + continue; + stream = H3_STREAM_CTX(ctx, sdata); + if(stream && stream->id == stream_id) { + *pstream = stream; + return sdata; + } + } + } + *pstream = NULL; + return NULL; +} + +static void h3_drain_stream(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct cf_ngtcp2_ctx *ctx = cf->ctx; + struct h3_stream_ctx *stream = H3_STREAM_CTX(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.select_bits != bits) { + data->state.select_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 = (ngtcp2_tstamp)ctx->q.last_op.tv_sec * NGTCP2_SECONDS + + (ngtcp2_tstamp)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 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; + } +} + +static CURLcode init_ngh3_conn(struct Curl_cfilter *cf); + +static int cb_handshake_completed(ngtcp2_conn *tconn, void *user_data) +{ + (void)user_data; + (void)tconn; + return 0; +} + +static void cf_ngtcp2_conn_close(struct Curl_cfilter *cf, + struct Curl_easy *data); + +static bool cf_ngtcp2_err_is_fatal(int code) +{ + return (NGTCP2_ERR_FATAL >= code) || + (NGTCP2_ERR_DROP_CONN == code) || + (NGTCP2_ERR_IDLE_CLOSE == code); +} + +static void cf_ngtcp2_err_set(struct Curl_cfilter *cf, + struct Curl_easy *data, int code) +{ + struct cf_ngtcp2_ctx *ctx = cf->ctx; + if(!ctx->last_error.error_code) { + if(NGTCP2_ERR_CRYPTO == code) { + ngtcp2_ccerr_set_tls_alert(&ctx->last_error, + ngtcp2_conn_get_tls_alert(ctx->qconn), + NULL, 0); + } + else { + ngtcp2_ccerr_set_liberr(&ctx->last_error, code, NULL, 0); + } + } + if(cf_ngtcp2_err_is_fatal(code)) + cf_ngtcp2_conn_close(cf, data); +} + +static bool cf_ngtcp2_h3_err_is_fatal(int code) +{ + return (NGHTTP3_ERR_FATAL >= code) || + (NGHTTP3_ERR_H3_CLOSED_CRITICAL_STREAM == code); +} + +static void cf_ngtcp2_h3_err_set(struct Curl_cfilter *cf, + struct Curl_easy *data, int code) +{ + struct cf_ngtcp2_ctx *ctx = cf->ctx; + if(!ctx->last_error.error_code) { + ngtcp2_ccerr_set_application_error(&ctx->last_error, + nghttp3_err_infer_quic_app_error_code(code), NULL, 0); + } + if(cf_ngtcp2_h3_err_is_fatal(code)) + cf_ngtcp2_conn_close(cf, data); +} + +static int cb_recv_stream_data(ngtcp2_conn *tconn, uint32_t flags, + int64_t sid, uint64_t offset, + const uint8_t *buf, size_t buflen, + void *user_data, void *stream_user_data) +{ + struct Curl_cfilter *cf = user_data; + struct cf_ngtcp2_ctx *ctx = cf->ctx; + curl_int64_t stream_id = (curl_int64_t)sid; + nghttp3_ssize nconsumed; + int fin = (flags & NGTCP2_STREAM_DATA_FLAG_FIN) ? 1 : 0; + struct Curl_easy *data = stream_user_data; + (void)offset; + (void)data; + + nconsumed = + nghttp3_conn_read_stream(ctx->h3conn, stream_id, buf, buflen, fin); + if(!data) + data = CF_DATA_CURRENT(cf); + if(data) + CURL_TRC_CF(data, cf, "[%" FMT_PRId64 "] read_stream(len=%zu) -> %zd", + stream_id, buflen, nconsumed); + if(nconsumed < 0) { + struct h3_stream_ctx *stream = H3_STREAM_CTX_ID(ctx, stream_id); + if(data && stream) { + CURL_TRC_CF(data, cf, "[%" FMT_PRId64 "] error on known stream, " + "reset=%d, closed=%d", + stream_id, stream->reset, stream->closed); + } + 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, (uint64_t)nconsumed); + ngtcp2_conn_extend_max_offset(tconn, (uint64_t)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 sid, uint64_t app_error_code, + void *user_data, void *stream_user_data) +{ + struct Curl_cfilter *cf = user_data; + struct cf_ngtcp2_ctx *ctx = cf->ctx; + struct Curl_easy *data = stream_user_data; + curl_int64_t stream_id = (curl_int64_t)sid; + int rv; + + (void)tconn; + /* stream is closed... */ + if(!data) + data = CF_DATA_CURRENT(cf); + if(!data) + return NGTCP2_ERR_CALLBACK_FAILURE; + + if(!(flags & NGTCP2_STREAM_CLOSE_FLAG_APP_ERROR_CODE_SET)) { + app_error_code = NGHTTP3_H3_NO_ERROR; + } + + rv = nghttp3_conn_close_stream(ctx->h3conn, stream_id, app_error_code); + CURL_TRC_CF(data, cf, "[%" FMT_PRId64 "] quic close(app_error=%" + FMT_PRIu64 ") -> %d", stream_id, (curl_uint64_t)app_error_code, + rv); + if(rv && rv != NGHTTP3_ERR_STREAM_NOT_FOUND) { + cf_ngtcp2_h3_err_set(cf, data, rv); + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + return 0; +} + +static int cb_stream_reset(ngtcp2_conn *tconn, int64_t sid, + uint64_t final_size, uint64_t app_error_code, + void *user_data, void *stream_user_data) +{ + struct Curl_cfilter *cf = user_data; + struct cf_ngtcp2_ctx *ctx = cf->ctx; + curl_int64_t stream_id = (curl_int64_t)sid; + struct Curl_easy *data = stream_user_data; + int rv; + (void)tconn; + (void)final_size; + (void)app_error_code; + (void)data; + + rv = nghttp3_conn_shutdown_stream_read(ctx->h3conn, stream_id); + CURL_TRC_CF(data, cf, "[%" FMT_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) +{ + struct Curl_cfilter *cf = user_data; + struct cf_ngtcp2_ctx *ctx = cf->ctx; + struct Curl_easy *data = CF_DATA_CURRENT(cf); + + (void)tconn; + ctx->max_bidi_streams = max_streams; + if(data) + CURL_TRC_CF(data, cf, "max bidi streams now %" FMT_PRIu64 + ", used %" FMT_PRIu64, (curl_uint64_t)ctx->max_bidi_streams, + (curl_uint64_t)ctx->used_bidi_streams); + return 0; +} + +static int cb_extend_max_stream_data(ngtcp2_conn *tconn, int64_t sid, + uint64_t max_data, void *user_data, + void *stream_user_data) +{ + struct Curl_cfilter *cf = user_data; + struct cf_ngtcp2_ctx *ctx = cf->ctx; + curl_int64_t stream_id = (curl_int64_t)sid; + struct Curl_easy *data = CF_DATA_CURRENT(cf); + struct Curl_easy *s_data; + struct h3_stream_ctx *stream; + 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); + if(s_data && stream && stream->quic_flow_blocked) { + CURL_TRC_CF(s_data, cf, "[%" FMT_PRId64 "] unblock quic flow", stream_id); + stream->quic_flow_blocked = FALSE; + h3_drain_stream(cf, s_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; +} + +#if defined(_MSC_VER) && defined(_DLL) +# pragma warning(push) +# pragma warning(disable:4232) /* MSVC extension, dllimport identity */ +#endif + +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 */ +}; + +#if defined(_MSC_VER) && defined(_DLL) +# pragma warning(pop) +#endif + +/** + * 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)); + cf_ngtcp2_err_set(cf, data, rv); + 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, (timediff_t)(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, want_send; + + if(!ctx->qconn) + return; + + Curl_pollset_check(data, ps, ctx->q.sockfd, &want_recv, &want_send); + if(!want_send && !Curl_bufq_is_empty(&ctx->q.sendbuf)) + want_send = TRUE; + + if(want_recv || want_send) { + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); + struct cf_call_data save; + bool c_exhaust, s_exhaust; + + CF_DATA_SAVE(save, cf, data); + c_exhaust = want_send && (!ngtcp2_conn_get_cwnd_left(ctx->qconn) || + !ngtcp2_conn_get_max_data_left(ctx->qconn)); + s_exhaust = want_send && stream && stream->id >= 0 && + stream->quic_flow_blocked; + 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 sid, + uint64_t app_error_code, void *user_data, + void *stream_user_data) +{ + struct Curl_cfilter *cf = user_data; + struct cf_ngtcp2_ctx *ctx = cf->ctx; + struct Curl_easy *data = stream_user_data; + curl_int64_t stream_id = (curl_int64_t)sid; + struct h3_stream_ctx *stream = H3_STREAM_CTX(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 = (curl_uint64_t)app_error_code; + if(stream->error3 != NGHTTP3_H3_NO_ERROR) { + stream->reset = TRUE; + stream->send_closed = TRUE; + CURL_TRC_CF(data, cf, "[%" FMT_PRId64 "] RESET: error %" FMT_PRIu64, + stream->id, stream->error3); + } + else { + CURL_TRC_CF(data, cf, "[%" FMT_PRId64 "] CLOSED", stream->id); + } + h3_drain_stream(cf, data); + return 0; +} + +static void h3_xfer_write_resp_hd(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct h3_stream_ctx *stream, + const char *buf, size_t blen, bool eos) +{ + + /* If we already encountered an error, skip further writes */ + if(!stream->xfer_result) { + stream->xfer_result = Curl_xfer_write_resp_hd(data, buf, blen, eos); + if(stream->xfer_result) + CURL_TRC_CF(data, cf, "[%"FMT_PRId64"] error %d writing %zu " + "bytes of headers", stream->id, stream->xfer_result, blen); + } +} + +static void h3_xfer_write_resp(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct h3_stream_ctx *stream, + const char *buf, size_t blen, bool eos) +{ + + /* If we already encountered an error, skip further writes */ + if(!stream->xfer_result) { + stream->xfer_result = Curl_xfer_write_resp(data, buf, blen, eos); + /* If the transfer write is errored, we do not want any more data */ + if(stream->xfer_result) { + CURL_TRC_CF(data, cf, "[%"FMT_PRId64"] error %d writing %zu bytes " + "of data", stream->id, stream->xfer_result, blen); + } + } +} + +static int cb_h3_recv_data(nghttp3_conn *conn, int64_t stream3_id, + const uint8_t *buf, size_t blen, + void *user_data, void *stream_user_data) +{ + struct Curl_cfilter *cf = user_data; + struct cf_ngtcp2_ctx *ctx = cf->ctx; + struct Curl_easy *data = stream_user_data; + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); + + (void)conn; + (void)stream3_id; + + if(!stream) + return NGHTTP3_ERR_CALLBACK_FAILURE; + + h3_xfer_write_resp(cf, data, stream, (char *)buf, blen, FALSE); + if(blen) { + CURL_TRC_CF(data, cf, "[%" FMT_PRId64 "] ACK %zu bytes of DATA", + stream->id, blen); + ngtcp2_conn_extend_max_stream_offset(ctx->qconn, stream->id, blen); + ngtcp2_conn_extend_max_offset(ctx->qconn, blen); + } + CURL_TRC_CF(data, cf, "[%" FMT_PRId64 "] DATA len=%zu", stream->id, blen); + 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 sid, + int fin, void *user_data, void *stream_user_data) +{ + struct Curl_cfilter *cf = user_data; + struct cf_ngtcp2_ctx *ctx = cf->ctx; + struct Curl_easy *data = stream_user_data; + curl_int64_t stream_id = (curl_int64_t)sid; + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); + (void)conn; + (void)stream_id; + (void)fin; + (void)cf; + + if(!stream) + return 0; + /* add a CRLF only if we have received some headers */ + h3_xfer_write_resp_hd(cf, data, stream, STRCONST("\r\n"), stream->closed); + + CURL_TRC_CF(data, cf, "[%" FMT_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 sid, + int32_t token, nghttp3_rcbuf *name, + nghttp3_rcbuf *value, uint8_t flags, + void *user_data, void *stream_user_data) +{ + struct Curl_cfilter *cf = user_data; + struct cf_ngtcp2_ctx *ctx = cf->ctx; + curl_int64_t stream_id = (curl_int64_t)sid; + nghttp3_vec h3name = nghttp3_rcbuf_get_buf(name); + nghttp3_vec h3val = nghttp3_rcbuf_get_buf(value); + struct Curl_easy *data = stream_user_data; + struct h3_stream_ctx *stream = H3_STREAM_CTX(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) { + + result = Curl_http_decode_status(&stream->status_code, + (const char *)h3val.base, h3val.len); + if(result) + return -1; + Curl_dyn_reset(&ctx->scratch); + result = Curl_dyn_addn(&ctx->scratch, STRCONST("HTTP/3 ")); + if(!result) + result = Curl_dyn_addn(&ctx->scratch, + (const char *)h3val.base, h3val.len); + if(!result) + result = Curl_dyn_addn(&ctx->scratch, STRCONST(" \r\n")); + if(!result) + h3_xfer_write_resp_hd(cf, data, stream, Curl_dyn_ptr(&ctx->scratch), + Curl_dyn_len(&ctx->scratch), FALSE); + CURL_TRC_CF(data, cf, "[%" FMT_PRId64 "] status: %s", + stream_id, Curl_dyn_ptr(&ctx->scratch)); + if(result) { + return -1; + } + } + else { + /* store as an HTTP1-style header */ + CURL_TRC_CF(data, cf, "[%" FMT_PRId64 "] header: %.*s: %.*s", + stream_id, (int)h3name.len, h3name.base, + (int)h3val.len, h3val.base); + Curl_dyn_reset(&ctx->scratch); + result = Curl_dyn_addn(&ctx->scratch, + (const char *)h3name.base, h3name.len); + if(!result) + result = Curl_dyn_addn(&ctx->scratch, STRCONST(": ")); + if(!result) + result = Curl_dyn_addn(&ctx->scratch, + (const char *)h3val.base, h3val.len); + if(!result) + result = Curl_dyn_addn(&ctx->scratch, STRCONST("\r\n")); + if(!result) + h3_xfer_write_resp_hd(cf, data, stream, Curl_dyn_ptr(&ctx->scratch), + Curl_dyn_len(&ctx->scratch), FALSE); + } + return 0; +} + +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 NGHTTP3_ERR_CALLBACK_FAILURE; + } + + return 0; +} + +static int cb_h3_reset_stream(nghttp3_conn *conn, int64_t sid, + uint64_t app_error_code, void *user_data, + void *stream_user_data) { + struct Curl_cfilter *cf = user_data; + struct cf_ngtcp2_ctx *ctx = cf->ctx; + curl_int64_t stream_id = (curl_int64_t)sid; + struct Curl_easy *data = stream_user_data; + int rv; + (void)conn; + (void)data; + + rv = ngtcp2_conn_shutdown_stream_write(ctx->qconn, 0, stream_id, + app_error_code); + CURL_TRC_CF(data, cf, "[%" FMT_PRId64 "] reset -> %d", stream_id, rv); + if(rv && rv != NGTCP2_ERR_STREAM_NOT_FOUND) { + return NGHTTP3_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 CURLcode 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 %" FMT_PRId64 " reset by server", stream->id); + *err = data->req.bytecount ? CURLE_PARTIAL_FILE : CURLE_HTTP3; + goto out; + } + else if(!stream->resp_hds_complete) { + failf(data, + "HTTP/3 stream %" FMT_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 blen, CURLcode *err) +{ + struct cf_ngtcp2_ctx *ctx = cf->ctx; + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); + ssize_t nread = -1; + struct cf_call_data save; + struct pkt_io_ctx pktx; + + (void)ctx; + (void)buf; + + 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 || ctx->shutdown_started) { + *err = CURLE_RECV_ERROR; + goto out; + } + + if(cf_progress_ingress(cf, data, &pktx)) { + *err = CURLE_RECV_ERROR; + nread = -1; + goto out; + } + + if(stream->xfer_result) { + CURL_TRC_CF(data, cf, "[%" FMT_PRId64 "] xfer write failed", stream->id); + cf_ngtcp2_stream_close(cf, data, stream); + *err = stream->xfer_result; + nread = -1; + goto out; + } + else if(stream->closed) { + nread = recv_closed_stream(cf, data, stream, err); + goto out; + } + *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, "[%" FMT_PRId64 "] cf_recv(blen=%zu) -> %zd, %d", + stream ? stream->id : -1, blen, 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 cf_ngtcp2_ctx *ctx = cf->ctx; + struct Curl_easy *data = stream_user_data; + struct h3_stream_ctx *stream = H3_STREAM_CTX(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; + + /* Resume upload processing if we have more data to send */ + if(stream->sendbuf_len_in_flight < Curl_bufq_len(&stream->sendbuf)) { + int rv = nghttp3_conn_resume_stream(conn, stream_id); + if(rv && rv != NGHTTP3_ERR_STREAM_NOT_FOUND) { + return NGHTTP3_ERR_CALLBACK_FAILURE; + } + } + 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 cf_ngtcp2_ctx *ctx = cf->ctx; + struct Curl_easy *data = stream_user_data; + struct h3_stream_ctx *stream = H3_STREAM_CTX(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, "[%" FMT_PRId64 "] read req body -> AGAIN", + stream->id); + return NGHTTP3_ERR_WOULDBLOCK; + } + + CURL_TRC_CF(data, cf, "[%" FMT_PRId64 "] read req body -> " + "%d vecs%s with %zu (buffered=%zu, left=%" FMT_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; + int64_t sid; + 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(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, &sid, data); + if(rc) { + failf(data, "can get bidi streams"); + *err = CURLE_SEND_ERROR; + nwritten = -1; + goto out; + } + stream->id = (curl_int64_t)sid; + ++ctx->used_bidi_streams; + + switch(data->state.httpreq) { + case HTTPREQ_POST: + 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[%" FMT_PRId64 "] failed to send, " + "connection is closing", stream->id); + break; + default: + CURL_TRC_CF(data, cf, "h3sid[%" FMT_PRId64 "] failed to send -> " + "%d (%s)", stream->id, rc, nghttp3_strerror(rc)); + break; + } + *err = CURLE_SEND_ERROR; + nwritten = -1; + goto out; + } + + if(Curl_trc_is_verbose(data)) { + infof(data, "[HTTP/3] [%" FMT_PRId64 "] OPENED stream for %s", + stream->id, data->state.url); + for(i = 0; i < nheader; ++i) { + infof(data, "[HTTP/3] [%" FMT_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, bool eos, + CURLcode *err) +{ + struct cf_ngtcp2_ctx *ctx = cf->ctx; + struct h3_stream_ctx *stream = H3_STREAM_CTX(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; + + (void)eos; /* TODO: use for stream EOF and block handling */ + result = cf_progress_ingress(cf, data, &pktx); + if(result) { + *err = result; + sent = -1; + } + + if(!stream || stream->id < 0) { + if(ctx->shutdown_started) { + CURL_TRC_CF(data, cf, "cannot open stream on closed connection"); + *err = CURLE_SEND_ERROR; + sent = -1; + goto out; + } + sent = h3_stream_open(cf, data, buf, len, err); + if(sent < 0) { + CURL_TRC_CF(data, cf, "failed to open stream -> %d", *err); + goto out; + } + stream = H3_STREAM_CTX(ctx, data); + } + else if(stream->xfer_result) { + CURL_TRC_CF(data, cf, "[%" FMT_PRId64 "] xfer write failed", stream->id); + cf_ngtcp2_stream_close(cf, data, stream); + *err = stream->xfer_result; + sent = -1; + goto out; + } + 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, "[%" FMT_PRId64 "] discarding data" + "on closed stream with response", stream->id); + *err = CURLE_OK; + sent = (ssize_t)len; + goto out; + } + CURL_TRC_CF(data, cf, "[%" FMT_PRId64 "] send_body(len=%zu) " + "-> stream closed", stream->id, len); + *err = CURLE_HTTP3; + sent = -1; + goto out; + } + else if(ctx->shutdown_started) { + CURL_TRC_CF(data, cf, "cannot send on closed connection"); + *err = CURLE_SEND_ERROR; + sent = -1; + goto out; + } + else { + sent = Curl_bufq_write(&stream->sendbuf, buf, len, err); + CURL_TRC_CF(data, cf, "[%" FMT_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; + } + +out: + result = check_and_set_expiry(cf, data, &pktx); + if(result) { + *err = result; + sent = -1; + } + CURL_TRC_CF(data, cf, "[%" FMT_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; + + cf->conn->bits.multiplex = TRUE; /* at least potentially multiplexed */ + cf->conn->httpversion = 30; + + return Curl_vquic_tls_verify_peer(&ctx->tls, cf, data, &ctx->peer); +} + +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, + (socklen_t)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); + cf_ngtcp2_err_set(pktx->cf, pktx->data, rv); + + 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; + CURLcode result = CURLE_OK; + + if(!pktx) { + pktx_init(&local_pktx, cf, data); + pktx = &local_pktx; + } + else { + pktx_update_time(pktx, cf); + } + + result = Curl_vquic_tls_before_recv(&ctx->tls, cf, data); + if(result) + return result; + + for(i = 0; i < 4; ++i) { + if(i) + pktx_update_time(pktx, cf); + pktx->pkt_count = 0; + result = vquic_recv_packets(cf, data, &ctx->q, pkts_chunk, + recv_pkt, pktx); + if(result || !pktx->pkt_count) /* error or got nothing */ + 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)); + cf_ngtcp2_h3_err_set(x->cf, x->data, (int)veccnt); + *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(ctx, x->data); + DEBUGASSERT(ndatalen == -1); + nghttp3_conn_block_stream(ctx->h3conn, stream_id); + CURL_TRC_CF(x->data, x->cf, "[%" FMT_PRId64 "] block quic flow", + (curl_int64_t)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)); + cf_ngtcp2_err_set(x->cf, x->data, (int)n); + *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 "guaranteed" 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 will 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) +{ + (void)cf; + (void)data; + return FALSE; +} + +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(ctx, data); + if(stream && !stream->send_closed) { + stream->send_closed = TRUE; + stream->upload_left = Curl_bufq_len(&stream->sendbuf) - + stream->sendbuf_len_in_flight; + (void)nghttp3_conn_resume_stream(ctx->h3conn, stream->id); + } + break; + } + case CF_CTRL_DATA_IDLE: { + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); + CURL_TRC_CF(data, cf, "data idle"); + if(stream && !stream->closed) { + result = check_and_set_expiry(cf, data, NULL); + 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_close(struct cf_ngtcp2_ctx *ctx) +{ + struct cf_call_data save = ctx->call_data; + + if(!ctx->initialized) + return; + if(ctx->qlogfd != -1) { + close(ctx->qlogfd); + } + ctx->qlogfd = -1; + Curl_vquic_tls_cleanup(&ctx->tls); + vquic_ctx_free(&ctx->q); + if(ctx->h3conn) + nghttp3_conn_del(ctx->h3conn); + if(ctx->qconn) + ngtcp2_conn_del(ctx->qconn); + ctx->call_data = save; +} + +static CURLcode cf_ngtcp2_shutdown(struct Curl_cfilter *cf, + struct Curl_easy *data, bool *done) +{ + struct cf_ngtcp2_ctx *ctx = cf->ctx; + struct cf_call_data save; + struct pkt_io_ctx pktx; + CURLcode result = CURLE_OK; + + if(cf->shutdown || !ctx->qconn) { + *done = TRUE; + return CURLE_OK; + } + + CF_DATA_SAVE(save, cf, data); + *done = FALSE; + pktx_init(&pktx, cf, data); + + if(!ctx->shutdown_started) { + char buffer[NGTCP2_MAX_UDP_PAYLOAD_SIZE]; + ngtcp2_ssize nwritten; + + if(!Curl_bufq_is_empty(&ctx->q.sendbuf)) { + CURL_TRC_CF(data, cf, "shutdown, flushing sendbuf"); + result = cf_progress_egress(cf, data, &pktx); + if(!Curl_bufq_is_empty(&ctx->q.sendbuf)) { + CURL_TRC_CF(data, cf, "sending shutdown packets blocked"); + result = CURLE_OK; + goto out; + } + else if(result) { + CURL_TRC_CF(data, cf, "shutdown, error %d flushing sendbuf", result); + *done = TRUE; + goto out; + } + } + + ctx->shutdown_started = TRUE; + nwritten = ngtcp2_conn_write_connection_close( + ctx->qconn, NULL, /* path */ + NULL, /* pkt_info */ + (uint8_t *)buffer, sizeof(buffer), + &ctx->last_error, pktx.ts); + CURL_TRC_CF(data, cf, "start shutdown(err_type=%d, err_code=%" + FMT_PRIu64 ") -> %d", ctx->last_error.type, + (curl_uint64_t)ctx->last_error.error_code, (int)nwritten); + if(nwritten > 0) { + Curl_bufq_write(&ctx->q.sendbuf, (const unsigned char *)buffer, + (size_t)nwritten, &result); + if(result) { + CURL_TRC_CF(data, cf, "error %d adding shutdown packets to sendbuf, " + "aborting shutdown", result); + goto out; + } + ctx->q.no_gso = TRUE; + ctx->q.gsolen = (size_t)nwritten; + ctx->q.split_len = 0; + } + } + + if(!Curl_bufq_is_empty(&ctx->q.sendbuf)) { + CURL_TRC_CF(data, cf, "shutdown, flushing egress"); + result = vquic_flush(cf, data, &ctx->q); + if(result == CURLE_AGAIN) { + CURL_TRC_CF(data, cf, "sending shutdown packets blocked"); + result = CURLE_OK; + goto out; + } + else if(result) { + CURL_TRC_CF(data, cf, "shutdown, error %d flushing sendbuf", result); + *done = TRUE; + goto out; + } + } + + if(Curl_bufq_is_empty(&ctx->q.sendbuf)) { + /* Sent everything off. ngtcp2 seems to have no support for graceful + * shutdowns. So, we are done. */ + CURL_TRC_CF(data, cf, "shutdown completely sent off, done"); + *done = TRUE; + result = CURLE_OK; + } +out: + CF_DATA_RESTORE(cf, save); + return result; +} + +static void cf_ngtcp2_conn_close(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + bool done; + cf_ngtcp2_shutdown(cf, data, &done); +} + +static void cf_ngtcp2_close(struct Curl_cfilter *cf, struct Curl_easy *data) +{ + struct cf_ngtcp2_ctx *ctx = cf->ctx; + struct cf_call_data save; + + CF_DATA_SAVE(save, cf, data); + if(ctx && ctx->qconn) { + cf_ngtcp2_conn_close(cf, data); + cf_ngtcp2_ctx_close(ctx); + CURL_TRC_CF(data, cf, "close"); + } + cf->connected = FALSE; + CF_DATA_RESTORE(cf, save); +} + +static void cf_ngtcp2_destroy(struct Curl_cfilter *cf, struct Curl_easy *data) +{ + CURL_TRC_CF(data, cf, "destroy"); + if(cf->ctx) { + cf_ngtcp2_ctx_free(cf->ctx); + cf->ctx = NULL; + } +} + +#ifdef USE_OPENSSL +/* The "new session" callback must return zero if the session can be removed + * or non-zero if the session has been put into the session cache. + */ +static int quic_ossl_new_session_cb(SSL *ssl, SSL_SESSION *ssl_sessionid) +{ + struct Curl_cfilter *cf; + struct cf_ngtcp2_ctx *ctx; + struct Curl_easy *data; + ngtcp2_crypto_conn_ref *cref; + + cref = (ngtcp2_crypto_conn_ref *)SSL_get_app_data(ssl); + cf = cref ? cref->user_data : NULL; + ctx = cf ? cf->ctx : NULL; + data = cf ? CF_DATA_CURRENT(cf) : NULL; + if(cf && data && ctx) { + Curl_ossl_add_session(cf, data, &ctx->peer, ssl_sessionid); + return 1; + } + return 0; +} +#endif /* USE_OPENSSL */ + +static CURLcode tls_ctx_setup(struct Curl_cfilter *cf, + struct Curl_easy *data, + void *user_data) +{ + struct curl_tls_ctx *ctx = user_data; + (void)cf; +#ifdef USE_OPENSSL +#if defined(OPENSSL_IS_BORINGSSL) || defined(OPENSSL_IS_AWSLC) + if(ngtcp2_crypto_boringssl_configure_client_context(ctx->ossl.ssl_ctx) + != 0) { + failf(data, "ngtcp2_crypto_boringssl_configure_client_context failed"); + return CURLE_FAILED_INIT; + } +#else + if(ngtcp2_crypto_quictls_configure_client_context(ctx->ossl.ssl_ctx) != 0) { + failf(data, "ngtcp2_crypto_quictls_configure_client_context failed"); + return CURLE_FAILED_INIT; + } +#endif /* !OPENSSL_IS_BORINGSSL && !OPENSSL_IS_AWSLC */ + /* Enable the session cache because it is a prerequisite for the + * "new session" callback. Use the "external storage" mode to prevent + * OpenSSL from creating an internal session cache. + */ + SSL_CTX_set_session_cache_mode(ctx->ossl.ssl_ctx, + SSL_SESS_CACHE_CLIENT | + SSL_SESS_CACHE_NO_INTERNAL); + SSL_CTX_sess_set_new_cb(ctx->ossl.ssl_ctx, quic_ossl_new_session_cb); + +#elif defined(USE_GNUTLS) + if(ngtcp2_crypto_gnutls_configure_client_session(ctx->gtls.session) != 0) { + failf(data, "ngtcp2_crypto_gnutls_configure_client_session failed"); + return CURLE_FAILED_INIT; + } +#elif defined(USE_WOLFSSL) + if(ngtcp2_crypto_wolfssl_configure_client_context(ctx->wssl.ctx) != 0) { + failf(data, "ngtcp2_crypto_wolfssl_configure_client_context failed"); + return CURLE_FAILED_INIT; + } +#endif + return CURLE_OK; +} + +/* + * 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; + + DEBUGASSERT(ctx->initialized); + result = Curl_ssl_peer_init(&ctx->peer, cf, TRNSPRT_QUIC); + if(result) + return result; + +#define H3_ALPN "\x2h3\x5h3-29" + result = Curl_vquic_tls_init(&ctx->tls, cf, data, &ctx->peer, + H3_ALPN, sizeof(H3_ALPN) - 1, + tls_ctx_setup, &ctx->tls, &ctx->conn_ref); + if(result) + return result; + +#ifdef USE_OPENSSL + SSL_set_quic_use_legacy_codepoint(ctx->tls.ossl.ssl, 0); +#endif + + ctx->dcid.datalen = NGTCP2_MAX_CIDLEN; + result = Curl_rand(data, ctx->dcid.data, NGTCP2_MAX_CIDLEN); + if(result) + 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); + 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->curl_sa_addr, (socklen_t)sockaddr->addrlen); + + rc = ngtcp2_conn_client_new(&ctx->qconn, &ctx->dcid, &ctx->scid, + &ctx->connected_path, + NGTCP2_PROTO_VER_V1, &ng_callbacks, + &ctx->settings, &ctx->transport_params, + NULL, cf); + if(rc) + return CURLE_QUIC_CONNECT_ERROR; + +#ifdef USE_OPENSSL + ngtcp2_conn_set_tls_native_handle(ctx->qconn, ctx->tls.ossl.ssl); +#elif defined(USE_GNUTLS) + ngtcp2_conn_set_tls_native_handle(ctx->qconn, ctx->tls.gtls.session); +#else + ngtcp2_conn_set_tls_native_handle(ctx->qconn, ctx->tls.wssl.handle); +#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->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) { + struct ip_quadruple ip; + + Curl_cf_socket_peek(cf->next, data, NULL, NULL, &ip); + infof(data, "QUIC connect to %s port %u failed: %s", + ip.remote_ip, ip.remote_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: { + DEBUGASSERT(pres1); + CF_DATA_SAVE(save, cf, data); + /* Set after transport params arrived and continually updated + * by callback. QUIC counts the number over the lifetime of the + * connection, ever increasing. + * We count the *open* transfers plus the budget for new ones. */ + if(!ctx->qconn || ctx->shutdown_started) { + *pres1 = 0; + } + else if(ctx->max_bidi_streams) { + uint64_t avail_bidi_streams = 0; + uint64_t max_streams = CONN_INUSE(cf->conn); + if(ctx->max_bidi_streams > ctx->used_bidi_streams) + avail_bidi_streams = ctx->max_bidi_streams - ctx->used_bidi_streams; + max_streams += avail_bidi_streams; + *pres1 = (max_streams > INT_MAX) ? INT_MAX : (int)max_streams; + } + else /* transport params not arrived yet? take our default. */ + *pres1 = (int)Curl_multi_max_concurrent_streams(data->multi); + CURL_TRC_CF(data, cf, "query conn[%" FMT_OFF_T "]: " + "MAX_CONCURRENT -> %d (%zu in use)", + cf->conn->connection_id, *pres1, CONN_INUSE(cf->conn)); + CF_DATA_RESTORE(cf, save); + return CURLE_OK; + } + case CF_QUERY_CONNECT_REPLY_MS: + if(ctx->q.got_first_byte) { + timediff_t ms = Curl_timediff(ctx->q.first_byte_at, ctx->started_at); + *pres1 = (ms < INT_MAX) ? (int)ms : INT_MAX; + } + else + *pres1 = -1; + return CURLE_OK; + case CF_QUERY_TIMER_CONNECT: { + struct curltime *when = pres2; + if(ctx->q.got_first_byte) + *when = ctx->q.first_byte_at; + return CURLE_OK; + } + case CF_QUERY_TIMER_APPCONNECT: { + struct curltime *when = pres2; + if(cf->connected) + *when = ctx->handshake_at; + return CURLE_OK; + } + 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 || ctx->shutdown_started) + 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 have sent off a request and the connection is + not in use by any other transfer, there should not 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, + cf_ngtcp2_shutdown, + 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; + } + cf_ngtcp2_ctx_init(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); + cf_ngtcp2_ctx_free(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/contrib/curl/lib/vquic/curl_ngtcp2.h b/contrib/curl/lib/vquic/curl_ngtcp2.h new file mode 100644 index 0000000000..db3e611bd0 --- /dev/null +++ b/contrib/curl/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, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the 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 +#endif + +#include +#include +#ifdef USE_OPENSSL +#include +#elif defined(USE_WOLFSSL) +#include +#include +#include +#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/contrib/curl/lib/vquic/curl_osslq.c b/contrib/curl/lib/vquic/curl_osslq.c new file mode 100644 index 0000000000..722cddc3f6 --- /dev/null +++ b/contrib/curl/lib/vquic/curl_osslq.c @@ -0,0 +1,2389 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#if defined(USE_OPENSSL_QUIC) && defined(USE_NGHTTP3) + +#include +#include +#include +#include + +#include "urldata.h" +#include "hash.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 "vquic-tls.h" +#include "vtls/keylog.h" +#include "vtls/vtls.h" +#include "vtls/openssl.h" +#include "curl_osslq.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" + +/* 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 window + * seems good. More does not seem to improve performance. + * The benefit of the pool is that stream buffer to not keep + * spares. 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) + +#ifndef ARRAYSIZE +#define ARRAYSIZE(A) (sizeof(A)/sizeof((A)[0])) +#endif + +#if defined(OPENSSL_IS_BORINGSSL) || defined(OPENSSL_IS_AWSLC) +typedef uint32_t sslerr_t; +#else +typedef unsigned long sslerr_t; +#endif + + +/* How to access `call_data` from a cf_osslq filter */ +#undef CF_CTX_CALL_DATA +#define CF_CTX_CALL_DATA(cf) \ + ((struct cf_osslq_ctx *)(cf)->ctx)->call_data + +static CURLcode cf_progress_ingress(struct Curl_cfilter *cf, + struct Curl_easy *data); + +static const char *osslq_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"; + } +} + +/* Return error string for last OpenSSL error */ +static char *osslq_strerror(unsigned long error, char *buf, size_t size) +{ + DEBUGASSERT(size); + *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) { + const char *msg = error ? "Unknown error" : "No error"; + if(strlen(msg) < size) + strcpy(buf, msg); + } + + return buf; +} + +static CURLcode make_bio_addr(BIO_ADDR **pbio_addr, + const struct Curl_sockaddr_ex *addr) +{ + BIO_ADDR *ba; + CURLcode result = CURLE_FAILED_INIT; + + ba = BIO_ADDR_new(); + if(!ba) { + result = CURLE_OUT_OF_MEMORY; + goto out; + } + + switch(addr->family) { + case AF_INET: { + struct sockaddr_in * const sin = + (struct sockaddr_in * const)(void *)&addr->curl_sa_addr; + if(!BIO_ADDR_rawmake(ba, AF_INET, &sin->sin_addr, + sizeof(sin->sin_addr), sin->sin_port)) { + goto out; + } + result = CURLE_OK; + break; + } +#ifdef USE_IPV6 + case AF_INET6: { + struct sockaddr_in6 * const sin = + (struct sockaddr_in6 * const)(void *)&addr->curl_sa_addr; + if(!BIO_ADDR_rawmake(ba, AF_INET6, &sin->sin6_addr, + sizeof(sin->sin6_addr), sin->sin6_port)) { + } + result = CURLE_OK; + break; + } +#endif /* USE_IPV6 */ + default: + /* sunsupported */ + DEBUGASSERT(0); + break; + } + +out: + if(result && ba) { + BIO_ADDR_free(ba); + ba = NULL; + } + *pbio_addr = ba; + return result; +} + +/* QUIC stream (not necessarily H3) */ +struct cf_osslq_stream { + curl_int64_t id; + SSL *ssl; + struct bufq recvbuf; /* QUIC war data recv buffer */ + BIT(recvd_eos); + BIT(closed); + BIT(reset); + BIT(send_blocked); +}; + +static CURLcode cf_osslq_stream_open(struct cf_osslq_stream *s, + SSL *conn, + uint64_t flags, + struct bufc_pool *bufcp, + void *user_data) +{ + DEBUGASSERT(!s->ssl); + Curl_bufq_initp(&s->recvbuf, bufcp, 1, BUFQ_OPT_NONE); + s->ssl = SSL_new_stream(conn, flags); + if(!s->ssl) { + return CURLE_FAILED_INIT; + } + s->id = (curl_int64_t)SSL_get_stream_id(s->ssl); + SSL_set_app_data(s->ssl, user_data); + return CURLE_OK; +} + +static void cf_osslq_stream_cleanup(struct cf_osslq_stream *s) +{ + if(s->ssl) { + SSL_set_app_data(s->ssl, NULL); + SSL_free(s->ssl); + } + Curl_bufq_free(&s->recvbuf); + memset(s, 0, sizeof(*s)); +} + +static void cf_osslq_stream_close(struct cf_osslq_stream *s) +{ + if(s->ssl) { + SSL_free(s->ssl); + s->ssl = NULL; + } +} + +struct cf_osslq_h3conn { + nghttp3_conn *conn; + nghttp3_settings settings; + struct cf_osslq_stream s_ctrl; + struct cf_osslq_stream s_qpack_enc; + struct cf_osslq_stream s_qpack_dec; + struct cf_osslq_stream remote_ctrl[3]; /* uni streams opened by the peer */ + size_t remote_ctrl_n; /* number of peer streams opened */ +}; + +static void cf_osslq_h3conn_cleanup(struct cf_osslq_h3conn *h3) +{ + size_t i; + + if(h3->conn) + nghttp3_conn_del(h3->conn); + cf_osslq_stream_cleanup(&h3->s_ctrl); + cf_osslq_stream_cleanup(&h3->s_qpack_enc); + cf_osslq_stream_cleanup(&h3->s_qpack_dec); + for(i = 0; i < h3->remote_ctrl_n; ++i) { + cf_osslq_stream_cleanup(&h3->remote_ctrl[i]); + } +} + +struct cf_osslq_ctx { + struct cf_quic_ctx q; + struct ssl_peer peer; + struct curl_tls_ctx tls; + struct cf_call_data call_data; + struct cf_osslq_h3conn h3; + struct curltime started_at; /* time the current attempt started */ + struct curltime handshake_at; /* time connect handshake finished */ + struct curltime first_byte_at; /* when first byte was recvd */ + struct bufc_pool stream_bufcp; /* chunk pool for streams */ + struct Curl_hash streams; /* hash `data->mid` to `h3_stream_ctx` */ + size_t max_stream_window; /* max flow window for one stream */ + uint64_t max_idle_ms; /* max idle time for QUIC connection */ + BIT(initialized); + BIT(got_first_byte); /* if first byte was received */ + BIT(x509_store_setup); /* if x509 store has been set up */ + BIT(protocol_shutdown); /* QUIC connection is shut down */ + BIT(need_recv); /* QUIC connection needs to receive */ + BIT(need_send); /* QUIC connection needs to send */ +}; + +static void h3_stream_hash_free(void *stream); + +static void cf_osslq_ctx_init(struct cf_osslq_ctx *ctx) +{ + DEBUGASSERT(!ctx->initialized); + Curl_bufcp_init(&ctx->stream_bufcp, H3_STREAM_CHUNK_SIZE, + H3_STREAM_POOL_SPARES); + Curl_hash_offt_init(&ctx->streams, 63, h3_stream_hash_free); + ctx->initialized = TRUE; +} + +static void cf_osslq_ctx_free(struct cf_osslq_ctx *ctx) +{ + if(ctx && ctx->initialized) { + Curl_bufcp_free(&ctx->stream_bufcp); + Curl_hash_clean(&ctx->streams); + Curl_hash_destroy(&ctx->streams); + Curl_ssl_peer_cleanup(&ctx->peer); + } + free(ctx); +} + +static void cf_osslq_ctx_close(struct cf_osslq_ctx *ctx) +{ + struct cf_call_data save = ctx->call_data; + + cf_osslq_h3conn_cleanup(&ctx->h3); + Curl_vquic_tls_cleanup(&ctx->tls); + vquic_ctx_free(&ctx->q); + ctx->call_data = save; +} + +static CURLcode cf_osslq_shutdown(struct Curl_cfilter *cf, + struct Curl_easy *data, bool *done) +{ + struct cf_osslq_ctx *ctx = cf->ctx; + struct cf_call_data save; + CURLcode result = CURLE_OK; + int rc; + + CF_DATA_SAVE(save, cf, data); + + if(cf->shutdown || ctx->protocol_shutdown) { + *done = TRUE; + return CURLE_OK; + } + + CF_DATA_SAVE(save, cf, data); + *done = FALSE; + ctx->need_send = FALSE; + ctx->need_recv = FALSE; + + rc = SSL_shutdown_ex(ctx->tls.ossl.ssl, + SSL_SHUTDOWN_FLAG_NO_BLOCK, NULL, 0); + if(rc == 0) { /* ongoing */ + CURL_TRC_CF(data, cf, "shutdown ongoing"); + ctx->need_recv = TRUE; + goto out; + } + else if(rc == 1) { /* done */ + CURL_TRC_CF(data, cf, "shutdown finished"); + *done = TRUE; + goto out; + } + else { + long sslerr; + char err_buffer[256]; + int err = SSL_get_error(ctx->tls.ossl.ssl, rc); + + switch(err) { + case SSL_ERROR_NONE: + case SSL_ERROR_ZERO_RETURN: + CURL_TRC_CF(data, cf, "shutdown not received, but closed"); + *done = TRUE; + goto out; + 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, "shutdown sent, want receive"); + ctx->need_recv = TRUE; + goto out; + case SSL_ERROR_WANT_WRITE: + CURL_TRC_CF(data, cf, "shutdown send blocked"); + ctx->need_send = TRUE; + goto out; + default: + /* We give up on this. */ + sslerr = ERR_get_error(); + CURL_TRC_CF(data, cf, "shutdown, ignore recv error: '%s', errno %d", + (sslerr ? + osslq_strerror(sslerr, err_buffer, sizeof(err_buffer)) : + osslq_SSL_ERROR_to_str(err)), + SOCKERRNO); + *done = TRUE; + result = CURLE_OK; + goto out; + } + } +out: + CF_DATA_RESTORE(cf, save); + return result; +} + +static void cf_osslq_close(struct Curl_cfilter *cf, struct Curl_easy *data) +{ + struct cf_osslq_ctx *ctx = cf->ctx; + struct cf_call_data save; + + CF_DATA_SAVE(save, cf, data); + if(ctx && ctx->tls.ossl.ssl) { + CURL_TRC_CF(data, cf, "cf_osslq_close()"); + if(!cf->shutdown && !ctx->protocol_shutdown) { + /* last best effort, which OpenSSL calls a "rapid" shutdown. */ + SSL_shutdown_ex(ctx->tls.ossl.ssl, + (SSL_SHUTDOWN_FLAG_NO_BLOCK | SSL_SHUTDOWN_FLAG_RAPID), + NULL, 0); + } + cf_osslq_ctx_close(ctx); + } + + cf->connected = FALSE; + CF_DATA_RESTORE(cf, save); +} + +static void cf_osslq_destroy(struct Curl_cfilter *cf, struct Curl_easy *data) +{ + struct cf_osslq_ctx *ctx = cf->ctx; + struct cf_call_data save; + + CF_DATA_SAVE(save, cf, data); + CURL_TRC_CF(data, cf, "destroy"); + if(ctx) { + CURL_TRC_CF(data, cf, "cf_osslq_destroy()"); + if(ctx->tls.ossl.ssl) + cf_osslq_ctx_close(ctx); + cf_osslq_ctx_free(ctx); + } + cf->ctx = NULL; + /* No CF_DATA_RESTORE(cf, save) possible */ + (void)save; +} + +static CURLcode cf_osslq_h3conn_add_stream(struct cf_osslq_h3conn *h3, + SSL *stream_ssl, + struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct cf_osslq_ctx *ctx = cf->ctx; + curl_int64_t stream_id = (curl_int64_t)SSL_get_stream_id(stream_ssl); + + if(h3->remote_ctrl_n >= ARRAYSIZE(h3->remote_ctrl)) { + /* rejected, we are full */ + CURL_TRC_CF(data, cf, "[%" FMT_PRId64 "] rejecting remote stream", + stream_id); + SSL_free(stream_ssl); + return CURLE_FAILED_INIT; + } + switch(SSL_get_stream_type(stream_ssl)) { + case SSL_STREAM_TYPE_READ: { + struct cf_osslq_stream *nstream = &h3->remote_ctrl[h3->remote_ctrl_n++]; + nstream->id = stream_id; + nstream->ssl = stream_ssl; + Curl_bufq_initp(&nstream->recvbuf, &ctx->stream_bufcp, 1, BUFQ_OPT_NONE); + CURL_TRC_CF(data, cf, "[%" FMT_PRId64 "] accepted remote uni stream", + stream_id); + break; + } + default: + CURL_TRC_CF(data, cf, "[%" FMT_PRId64 "] reject remote non-uni-read" + " stream", stream_id); + SSL_free(stream_ssl); + return CURLE_FAILED_INIT; + } + return CURLE_OK; + +} + +static CURLcode cf_osslq_ssl_err(struct Curl_cfilter *cf, + struct Curl_easy *data, + int detail, CURLcode def_result) +{ + struct cf_osslq_ctx *ctx = cf->ctx; + CURLcode result = def_result; + sslerr_t errdetail; + char ebuf[256] = "unknown"; + const char *err_descr = ebuf; + long lerr; + int lib; + int reason; + struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data); + + errdetail = ERR_get_error(); + 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(ctx->tls.ossl.ssl); + if(lerr != X509_V_OK) { + ssl_config->certverifyresult = lerr; + msnprintf(ebuf, sizeof(ebuf), + "SSL certificate problem: %s", + X509_verify_cert_error_string(lerr)); + } + else + err_descr = "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; + osslq_strerror(errdetail, ebuf, sizeof(ebuf)); + } +#endif + else if((lib == ERR_LIB_SSL) && (reason == SSL_R_PROTOCOL_IS_SHUTDOWN)) { + ctx->protocol_shutdown = TRUE; + err_descr = "QUIC connection has been shut down"; + result = def_result; + } + else { + result = def_result; + osslq_strerror(errdetail, ebuf, sizeof(ebuf)); + } + + /* detail is already set to the SSL error above */ + + /* If we e.g. use SSLv2 request-method and the server does not 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; + struct ip_quadruple ip; + + Curl_cf_socket_peek(cf->next, data, NULL, NULL, &ip); + if(sockerr && detail == SSL_ERROR_SYSCALL) + Curl_strerror(sockerr, extramsg, sizeof(extramsg)); + failf(data, "QUIC connect: %s in connection to %s:%d (%s)", + extramsg[0] ? extramsg : osslq_SSL_ERROR_to_str(detail), + ctx->peer.dispname, ip.remote_port, ip.remote_ip); + } + else { + /* Could be a CERT problem */ + failf(data, "%s", err_descr); + } + return result; +} + +static CURLcode cf_osslq_verify_peer(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct cf_osslq_ctx *ctx = cf->ctx; + + cf->conn->bits.multiplex = TRUE; /* at least potentially multiplexed */ + cf->conn->httpversion = 30; + + return Curl_vquic_tls_verify_peer(&ctx->tls, cf, data, &ctx->peer); +} + +/** + * All about the H3 internals of a stream + */ +struct h3_stream_ctx { + struct cf_osslq_stream s; + 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 recv_buf_nonflow; /* buffered bytes, not counting for flow control */ + curl_uint64_t error3; /* HTTP/3 stream error code */ + curl_off_t upload_left; /* number of request bytes left to upload */ + curl_off_t download_recvd; /* number of response DATA bytes received */ + int status_code; /* HTTP status code */ + 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(ctx,data) ((struct h3_stream_ctx *)(\ + data? Curl_hash_offt_get(&(ctx)->streams, (data)->mid) : NULL)) + +static void h3_stream_ctx_free(struct h3_stream_ctx *stream) +{ + cf_osslq_stream_cleanup(&stream->s); + Curl_bufq_free(&stream->sendbuf); + Curl_bufq_free(&stream->recvbuf); + Curl_h1_req_parse_free(&stream->h1); + free(stream); +} + +static void h3_stream_hash_free(void *stream) +{ + DEBUGASSERT(stream); + h3_stream_ctx_free((struct h3_stream_ctx *)stream); +} + +static CURLcode h3_data_setup(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct cf_osslq_ctx *ctx = cf->ctx; + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); + + if(!data) + return CURLE_FAILED_INIT; + + if(stream) + return CURLE_OK; + + stream = calloc(1, sizeof(*stream)); + if(!stream) + return CURLE_OUT_OF_MEMORY; + + stream->s.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); + + if(!Curl_hash_offt_set(&ctx->streams, data->mid, stream)) { + h3_stream_ctx_free(stream); + return CURLE_OUT_OF_MEMORY; + } + + return CURLE_OK; +} + +static void h3_data_done(struct Curl_cfilter *cf, struct Curl_easy *data) +{ + struct cf_osslq_ctx *ctx = cf->ctx; + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); + + (void)cf; + if(stream) { + CURL_TRC_CF(data, cf, "[%"FMT_PRId64"] easy handle is done", + stream->s.id); + if(ctx->h3.conn && !stream->closed) { + nghttp3_conn_shutdown_stream_read(ctx->h3.conn, stream->s.id); + nghttp3_conn_close_stream(ctx->h3.conn, stream->s.id, + NGHTTP3_H3_REQUEST_CANCELLED); + nghttp3_conn_set_stream_user_data(ctx->h3.conn, stream->s.id, NULL); + stream->closed = TRUE; + } + + Curl_hash_offt_remove(&ctx->streams, data->mid); + } +} + +static struct cf_osslq_stream *cf_osslq_get_qstream(struct Curl_cfilter *cf, + struct Curl_easy *data, + int64_t stream_id) +{ + struct cf_osslq_ctx *ctx = cf->ctx; + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); + + if(stream && stream->s.id == stream_id) { + return &stream->s; + } + else if(ctx->h3.s_ctrl.id == stream_id) { + return &ctx->h3.s_ctrl; + } + else if(ctx->h3.s_qpack_enc.id == stream_id) { + return &ctx->h3.s_qpack_enc; + } + else if(ctx->h3.s_qpack_dec.id == stream_id) { + return &ctx->h3.s_qpack_dec; + } + else { + struct Curl_llist_node *e; + DEBUGASSERT(data->multi); + for(e = Curl_llist_head(&data->multi->process); e; e = Curl_node_next(e)) { + struct Curl_easy *sdata = Curl_node_elem(e); + if(sdata->conn != data->conn) + continue; + stream = H3_STREAM_CTX(ctx, sdata); + if(stream && stream->s.id == stream_id) { + return &stream->s; + } + } + } + return NULL; +} + +static void h3_drain_stream(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct cf_osslq_ctx *ctx = cf->ctx; + struct h3_stream_ctx *stream = H3_STREAM_CTX(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.select_bits != bits) { + data->state.select_bits = bits; + Curl_expire(data, 0, EXPIRE_RUN_NOW); + } +} + +static CURLcode h3_data_pause(struct Curl_cfilter *cf, + struct Curl_easy *data, + bool pause) +{ + if(!pause) { + /* unpaused. make it run again right away */ + h3_drain_stream(cf, data); + Curl_expire(data, 0, EXPIRE_RUN_NOW); + } + return CURLE_OK; +} + +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 cf_osslq_ctx *ctx = cf->ctx; + struct Curl_easy *data = stream_user_data; + struct h3_stream_ctx *stream = H3_STREAM_CTX(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, "[%" FMT_PRId64 "] RESET: error %" FMT_PRIu64, + stream->s.id, stream->error3); + } + else { + CURL_TRC_CF(data, cf, "[%" FMT_PRId64 "] CLOSED", stream->s.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 cf_osslq_ctx *ctx = cf->ctx; + struct h3_stream_ctx *stream = H3_STREAM_CTX(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 cf_osslq_ctx *ctx = cf->ctx; + struct Curl_easy *data = stream_user_data; + struct h3_stream_ctx *stream = H3_STREAM_CTX(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, "[%" FMT_PRId64 "] DATA len=%zu, ERROR %d", + stream->s.id, buflen, result); + return NGHTTP3_ERR_CALLBACK_FAILURE; + } + stream->download_recvd += (curl_off_t)buflen; + CURL_TRC_CF(data, cf, "[%" FMT_PRId64 "] DATA len=%zu, total=%zd", + stream->s.id, buflen, stream->download_recvd); + h3_drain_stream(cf, data); + return 0; +} + +static int cb_h3_deferred_consume(nghttp3_conn *conn, int64_t stream_id, + size_t consumed, void *user_data, + void *stream_user_data) +{ + struct Curl_cfilter *cf = user_data; + struct cf_osslq_ctx *ctx = cf->ctx; + struct Curl_easy *data = stream_user_data; + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); + + (void)conn; + (void)stream_id; + if(stream) + CURL_TRC_CF(data, cf, "[%" FMT_PRId64 "] deferred consume %zu bytes", + stream->s.id, consumed); + return 0; +} + +static int cb_h3_recv_header(nghttp3_conn *conn, int64_t sid, + int32_t token, nghttp3_rcbuf *name, + nghttp3_rcbuf *value, uint8_t flags, + void *user_data, void *stream_user_data) +{ + struct Curl_cfilter *cf = user_data; + curl_int64_t stream_id = sid; + struct cf_osslq_ctx *ctx = cf->ctx; + nghttp3_vec h3name = nghttp3_rcbuf_get_buf(name); + nghttp3_vec h3val = nghttp3_rcbuf_get_buf(value); + struct Curl_easy *data = stream_user_data; + struct h3_stream_ctx *stream = H3_STREAM_CTX(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, "[%" FMT_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, "[%" FMT_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_end_headers(nghttp3_conn *conn, int64_t sid, + int fin, void *user_data, void *stream_user_data) +{ + struct Curl_cfilter *cf = user_data; + struct cf_osslq_ctx *ctx = cf->ctx; + struct Curl_easy *data = stream_user_data; + curl_int64_t stream_id = sid; + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); + CURLcode result = CURLE_OK; + (void)conn; + (void)stream_id; + (void)fin; + (void)cf; + + if(!stream) + return 0; + /* add a CRLF only if we have received some headers */ + result = write_resp_raw(cf, data, "\r\n", 2, FALSE); + if(result) { + return -1; + } + + CURL_TRC_CF(data, cf, "[%" FMT_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_stop_sending(nghttp3_conn *conn, int64_t sid, + uint64_t app_error_code, void *user_data, + void *stream_user_data) +{ + struct Curl_cfilter *cf = user_data; + struct cf_osslq_ctx *ctx = cf->ctx; + struct Curl_easy *data = stream_user_data; + curl_int64_t stream_id = sid; + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); + (void)conn; + (void)app_error_code; + + if(!stream || !stream->s.ssl) + return 0; + + CURL_TRC_CF(data, cf, "[%" FMT_PRId64 "] stop_sending", stream_id); + cf_osslq_stream_close(&stream->s); + return 0; +} + +static int cb_h3_reset_stream(nghttp3_conn *conn, int64_t sid, + uint64_t app_error_code, void *user_data, + void *stream_user_data) { + struct Curl_cfilter *cf = user_data; + struct cf_osslq_ctx *ctx = cf->ctx; + struct Curl_easy *data = stream_user_data; + curl_int64_t stream_id = sid; + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); + int rv; + (void)conn; + + if(stream && stream->s.ssl) { + SSL_STREAM_RESET_ARGS args = {0}; + args.quic_error_code = app_error_code; + rv = !SSL_stream_reset(stream->s.ssl, &args, sizeof(args)); + CURL_TRC_CF(data, cf, "[%" FMT_PRId64 "] reset -> %d", stream_id, rv); + if(!rv) { + return NGHTTP3_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 cf_osslq_ctx *ctx = cf->ctx; + struct Curl_easy *data = stream_user_data; + struct h3_stream_ctx *stream = H3_STREAM_CTX(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, "[%" FMT_PRId64 "] read req body -> AGAIN", + stream->s.id); + return NGHTTP3_ERR_WOULDBLOCK; + } + + CURL_TRC_CF(data, cf, "[%" FMT_PRId64 "] read req body -> " + "%d vecs%s with %zu (buffered=%zu, left=%" FMT_OFF_T ")", + stream->s.id, (int)nvecs, + *pflags == NGHTTP3_DATA_FLAG_EOF ? " EOF" : "", + nwritten, Curl_bufq_len(&stream->sendbuf), + stream->upload_left); + return (nghttp3_ssize)nvecs; +} + +static int cb_h3_acked_stream_data(nghttp3_conn *conn, int64_t stream_id, + uint64_t datalen, void *user_data, + void *stream_user_data) +{ + struct Curl_cfilter *cf = user_data; + struct cf_osslq_ctx *ctx = cf->ctx; + struct Curl_easy *data = stream_user_data; + struct h3_stream_ctx *stream = H3_STREAM_CTX(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; + + /* Resume upload processing if we have more data to send */ + if(stream->sendbuf_len_in_flight < Curl_bufq_len(&stream->sendbuf)) { + int rv = nghttp3_conn_resume_stream(conn, stream_id); + if(rv && rv != NGHTTP3_ERR_STREAM_NOT_FOUND) { + return NGHTTP3_ERR_CALLBACK_FAILURE; + } + } + return 0; +} + +static nghttp3_callbacks ngh3_callbacks = { + cb_h3_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 CURLcode cf_osslq_h3conn_init(struct cf_osslq_ctx *ctx, SSL *conn, + void *user_data) +{ + struct cf_osslq_h3conn *h3 = &ctx->h3; + CURLcode result; + int rc; + + nghttp3_settings_default(&h3->settings); + rc = nghttp3_conn_client_new(&h3->conn, + &ngh3_callbacks, + &h3->settings, + nghttp3_mem_default(), + user_data); + if(rc) { + result = CURLE_OUT_OF_MEMORY; + goto out; + } + + result = cf_osslq_stream_open(&h3->s_ctrl, conn, + SSL_STREAM_FLAG_ADVANCE|SSL_STREAM_FLAG_UNI, + &ctx->stream_bufcp, NULL); + if(result) { + result = CURLE_QUIC_CONNECT_ERROR; + goto out; + } + result = cf_osslq_stream_open(&h3->s_qpack_enc, conn, + SSL_STREAM_FLAG_ADVANCE|SSL_STREAM_FLAG_UNI, + &ctx->stream_bufcp, NULL); + if(result) { + result = CURLE_QUIC_CONNECT_ERROR; + goto out; + } + result = cf_osslq_stream_open(&h3->s_qpack_dec, conn, + SSL_STREAM_FLAG_ADVANCE|SSL_STREAM_FLAG_UNI, + &ctx->stream_bufcp, NULL); + if(result) { + result = CURLE_QUIC_CONNECT_ERROR; + goto out; + } + + rc = nghttp3_conn_bind_control_stream(h3->conn, h3->s_ctrl.id); + if(rc) { + result = CURLE_QUIC_CONNECT_ERROR; + goto out; + } + rc = nghttp3_conn_bind_qpack_streams(h3->conn, h3->s_qpack_enc.id, + h3->s_qpack_dec.id); + if(rc) { + result = CURLE_QUIC_CONNECT_ERROR; + goto out; + } + + result = CURLE_OK; +out: + return result; +} + +static CURLcode cf_osslq_ctx_start(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct cf_osslq_ctx *ctx = cf->ctx; + CURLcode result; + int rv; + const struct Curl_sockaddr_ex *peer_addr = NULL; + BIO *bio = NULL; + BIO_ADDR *baddr = NULL; + + DEBUGASSERT(ctx->initialized); + result = Curl_ssl_peer_init(&ctx->peer, cf, TRNSPRT_QUIC); + if(result) + goto out; + +#define H3_ALPN "\x2h3" + result = Curl_vquic_tls_init(&ctx->tls, cf, data, &ctx->peer, + H3_ALPN, sizeof(H3_ALPN) - 1, + NULL, NULL, NULL); + if(result) + goto out; + + result = vquic_ctx_init(&ctx->q); + if(result) + goto out; + + result = CURLE_QUIC_CONNECT_ERROR; + Curl_cf_socket_peek(cf->next, data, &ctx->q.sockfd, &peer_addr, NULL); + if(!peer_addr) + goto out; + + 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) + goto out; + + result = make_bio_addr(&baddr, peer_addr); + if(result) { + failf(data, "error creating BIO_ADDR from sockaddr"); + goto out; + } + + /* Type conversions, see #12861: OpenSSL wants an `int`, but on 64-bit + * Win32 systems, Microsoft defines SOCKET as `unsigned long long`. + */ +#if defined(_WIN32) && !defined(__LWIP_OPT_H__) && !defined(LWIP_HDR_OPT_H) + if(ctx->q.sockfd > INT_MAX) { + failf(data, "Windows socket identifier larger than MAX_INT, " + "unable to set in OpenSSL dgram API."); + result = CURLE_QUIC_CONNECT_ERROR; + goto out; + } + bio = BIO_new_dgram((int)ctx->q.sockfd, BIO_NOCLOSE); +#else + bio = BIO_new_dgram(ctx->q.sockfd, BIO_NOCLOSE); +#endif + if(!bio) { + result = CURLE_OUT_OF_MEMORY; + goto out; + } + + if(!SSL_set1_initial_peer_addr(ctx->tls.ossl.ssl, baddr)) { + failf(data, "failed to set the initial peer address"); + result = CURLE_FAILED_INIT; + goto out; + } + if(!SSL_set_blocking_mode(ctx->tls.ossl.ssl, 0)) { + failf(data, "failed to turn off blocking mode"); + result = CURLE_FAILED_INIT; + goto out; + } + +#ifdef SSL_VALUE_QUIC_IDLE_TIMEOUT + /* Added in OpenSSL v3.3.x */ + if(!SSL_set_feature_request_uint(ctx->tls.ossl.ssl, + SSL_VALUE_QUIC_IDLE_TIMEOUT, + CURL_QUIC_MAX_IDLE_MS)) { + CURL_TRC_CF(data, cf, "error setting idle timeout, "); + result = CURLE_FAILED_INIT; + goto out; + } +#endif + + SSL_set_bio(ctx->tls.ossl.ssl, bio, bio); + bio = NULL; + SSL_set_connect_state(ctx->tls.ossl.ssl); + SSL_set_incoming_stream_policy(ctx->tls.ossl.ssl, + SSL_INCOMING_STREAM_POLICY_ACCEPT, 0); + /* setup the H3 things on top of the QUIC connection */ + result = cf_osslq_h3conn_init(ctx, ctx->tls.ossl.ssl, cf); + +out: + if(bio) + BIO_free(bio); + if(baddr) + BIO_ADDR_free(baddr); + CURL_TRC_CF(data, cf, "QUIC tls init -> %d", result); + return result; +} + +struct h3_quic_recv_ctx { + struct Curl_cfilter *cf; + struct Curl_easy *data; + struct cf_osslq_stream *s; +}; + +static ssize_t h3_quic_recv(void *reader_ctx, + unsigned char *buf, size_t len, + CURLcode *err) +{ + struct h3_quic_recv_ctx *x = reader_ctx; + size_t nread; + int rv; + + *err = CURLE_OK; + rv = SSL_read_ex(x->s->ssl, buf, len, &nread); + if(rv <= 0) { + int detail = SSL_get_error(x->s->ssl, rv); + if(detail == SSL_ERROR_WANT_READ || detail == SSL_ERROR_WANT_WRITE) { + *err = CURLE_AGAIN; + return -1; + } + else if(detail == SSL_ERROR_ZERO_RETURN) { + CURL_TRC_CF(x->data, x->cf, "[%" FMT_PRId64 "] h3_quic_recv -> EOS", + x->s->id); + x->s->recvd_eos = TRUE; + return 0; + } + else if(SSL_get_stream_read_state(x->s->ssl) == + SSL_STREAM_STATE_RESET_REMOTE) { + uint64_t app_error_code = NGHTTP3_H3_NO_ERROR; + SSL_get_stream_read_error_code(x->s->ssl, &app_error_code); + CURL_TRC_CF(x->data, x->cf, "[%" FMT_PRId64 "] h3_quic_recv -> RESET, " + "rv=%d, app_err=%" FMT_PRIu64, + x->s->id, rv, (curl_uint64_t)app_error_code); + if(app_error_code != NGHTTP3_H3_NO_ERROR) { + x->s->reset = TRUE; + } + x->s->recvd_eos = TRUE; + return 0; + } + else { + *err = cf_osslq_ssl_err(x->cf, x->data, detail, CURLE_RECV_ERROR); + return -1; + } + } + return (ssize_t)nread; +} + +static CURLcode cf_osslq_stream_recv(struct cf_osslq_stream *s, + struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct cf_osslq_ctx *ctx = cf->ctx; + CURLcode result = CURLE_OK; + ssize_t nread; + struct h3_quic_recv_ctx x; + int rv, eagain = FALSE; + size_t total_recv_len = 0; + + DEBUGASSERT(s); + if(s->closed) + return CURLE_OK; + + x.cf = cf; + x.data = data; + x.s = s; + while(s->ssl && !s->closed && !eagain && + (total_recv_len < H3_STREAM_CHUNK_SIZE)) { + if(Curl_bufq_is_empty(&s->recvbuf) && !s->recvd_eos) { + while(!eagain && !s->recvd_eos && !Curl_bufq_is_full(&s->recvbuf)) { + nread = Curl_bufq_sipn(&s->recvbuf, 0, h3_quic_recv, &x, &result); + if(nread < 0) { + if(result != CURLE_AGAIN) + goto out; + result = CURLE_OK; + eagain = TRUE; + } + } + } + + /* Forward what we have to nghttp3 */ + if(!Curl_bufq_is_empty(&s->recvbuf)) { + const unsigned char *buf; + size_t blen; + + while(Curl_bufq_peek(&s->recvbuf, &buf, &blen)) { + nread = nghttp3_conn_read_stream(ctx->h3.conn, s->id, + buf, blen, 0); + CURL_TRC_CF(data, cf, "[%" FMT_PRId64 "] forward %zu bytes " + "to nghttp3 -> %zd", s->id, blen, nread); + if(nread < 0) { + failf(data, "nghttp3_conn_read_stream(len=%zu) error: %s", + blen, nghttp3_strerror((int)nread)); + result = CURLE_RECV_ERROR; + goto out; + } + /* success, `nread` is the flow for QUIC to count as "consumed", + * not sure how that will work with OpenSSL. Anyways, without error, + * all data that we passed is not owned by nghttp3. */ + Curl_bufq_skip(&s->recvbuf, blen); + total_recv_len += blen; + } + } + + /* When we forwarded everything, handle RESET/EOS */ + if(Curl_bufq_is_empty(&s->recvbuf) && !s->closed) { + result = CURLE_OK; + if(s->reset) { + uint64_t app_error; + if(!SSL_get_stream_read_error_code(s->ssl, &app_error)) { + failf(data, "SSL_get_stream_read_error_code returned error"); + result = CURLE_RECV_ERROR; + goto out; + } + rv = nghttp3_conn_close_stream(ctx->h3.conn, s->id, app_error); + s->closed = TRUE; + if(rv < 0 && rv != NGHTTP3_ERR_STREAM_NOT_FOUND) { + failf(data, "nghttp3_conn_close_stream returned error: %s", + nghttp3_strerror(rv)); + result = CURLE_RECV_ERROR; + goto out; + } + } + else if(s->recvd_eos) { + rv = nghttp3_conn_close_stream(ctx->h3.conn, s->id, + NGHTTP3_H3_NO_ERROR); + s->closed = TRUE; + CURL_TRC_CF(data, cf, "[%" FMT_PRId64 "] close nghttp3 stream -> %d", + s->id, rv); + if(rv < 0 && rv != NGHTTP3_ERR_STREAM_NOT_FOUND) { + failf(data, "nghttp3_conn_close_stream returned error: %s", + nghttp3_strerror(rv)); + result = CURLE_RECV_ERROR; + goto out; + } + } + } + } +out: + if(result) + CURL_TRC_CF(data, cf, "[%" FMT_PRId64 "] cf_osslq_stream_recv -> %d", + s->id, result); + return result; +} + +static CURLcode cf_progress_ingress(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct cf_osslq_ctx *ctx = cf->ctx; + CURLcode result = CURLE_OK; + + if(!ctx->tls.ossl.ssl) + goto out; + + ERR_clear_error(); + + /* 1. Check for new incoming streams */ + while(1) { + SSL *snew = SSL_accept_stream(ctx->tls.ossl.ssl, + SSL_ACCEPT_STREAM_NO_BLOCK); + if(!snew) + break; + + (void)cf_osslq_h3conn_add_stream(&ctx->h3, snew, cf, data); + } + + if(!SSL_handle_events(ctx->tls.ossl.ssl)) { + int detail = SSL_get_error(ctx->tls.ossl.ssl, 0); + result = cf_osslq_ssl_err(cf, data, detail, CURLE_RECV_ERROR); + } + + if(ctx->h3.conn) { + size_t i; + for(i = 0; i < ctx->h3.remote_ctrl_n; ++i) { + result = cf_osslq_stream_recv(&ctx->h3.remote_ctrl[i], cf, data); + if(result) + goto out; + } + } + + if(ctx->h3.conn) { + struct Curl_llist_node *e; + struct h3_stream_ctx *stream; + /* PULL all open streams */ + DEBUGASSERT(data->multi); + for(e = Curl_llist_head(&data->multi->process); e; e = Curl_node_next(e)) { + struct Curl_easy *sdata = Curl_node_elem(e); + if(sdata->conn == data->conn && CURL_WANT_RECV(sdata)) { + stream = H3_STREAM_CTX(ctx, sdata); + if(stream && !stream->closed && + !Curl_bufq_is_full(&stream->recvbuf)) { + result = cf_osslq_stream_recv(&stream->s, cf, sdata); + if(result) + goto out; + } + } + } + } + +out: + CURL_TRC_CF(data, cf, "progress_ingress -> %d", result); + return result; +} + +/* Iterate over all streams and check if blocked can be unblocked */ +static CURLcode cf_osslq_check_and_unblock(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct cf_osslq_ctx *ctx = cf->ctx; + struct h3_stream_ctx *stream; + + if(ctx->h3.conn) { + struct Curl_llist_node *e; + for(e = Curl_llist_head(&data->multi->process); e; e = Curl_node_next(e)) { + struct Curl_easy *sdata = Curl_node_elem(e); + if(sdata->conn == data->conn) { + stream = H3_STREAM_CTX(ctx, sdata); + if(stream && stream->s.ssl && stream->s.send_blocked && + !SSL_want_write(stream->s.ssl)) { + nghttp3_conn_unblock_stream(ctx->h3.conn, stream->s.id); + stream->s.send_blocked = FALSE; + h3_drain_stream(cf, sdata); + CURL_TRC_CF(sdata, cf, "unblocked"); + } + } + } + } + return CURLE_OK; +} + +static CURLcode h3_send_streams(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct cf_osslq_ctx *ctx = cf->ctx; + CURLcode result = CURLE_OK; + + if(!ctx->tls.ossl.ssl || !ctx->h3.conn) + goto out; + + for(;;) { + struct cf_osslq_stream *s = NULL; + nghttp3_vec vec[16]; + nghttp3_ssize n, i; + int64_t stream_id; + size_t written; + int eos, ok, rv; + size_t total_len, acked_len = 0; + bool blocked = FALSE, eos_written = FALSE; + + n = nghttp3_conn_writev_stream(ctx->h3.conn, &stream_id, &eos, + vec, ARRAYSIZE(vec)); + if(n < 0) { + failf(data, "nghttp3_conn_writev_stream returned error: %s", + nghttp3_strerror((int)n)); + result = CURLE_SEND_ERROR; + goto out; + } + if(stream_id < 0) { + result = CURLE_OK; + goto out; + } + + /* Get the stream for this data */ + s = cf_osslq_get_qstream(cf, data, stream_id); + if(!s) { + failf(data, "nghttp3_conn_writev_stream gave unknown stream %" + FMT_PRId64, (curl_int64_t)stream_id); + result = CURLE_SEND_ERROR; + goto out; + } + /* Now write the data to the stream's SSL*, it may not all fit! */ + DEBUGASSERT(s->id == stream_id); + for(i = 0, total_len = 0; i < n; ++i) { + total_len += vec[i].len; + } + for(i = 0; (i < n) && !blocked; ++i) { + /* Without stream->s.ssl, we closed that already, so + * pretend the write did succeed. */ + uint64_t flags = (eos && ((i + 1) == n)) ? SSL_WRITE_FLAG_CONCLUDE : 0; + written = vec[i].len; + ok = !s->ssl || SSL_write_ex2(s->ssl, vec[i].base, vec[i].len, flags, + &written); + if(ok && flags & SSL_WRITE_FLAG_CONCLUDE) + eos_written = TRUE; + if(ok) { + /* As OpenSSL buffers the data, we count this as acknowledged + * from nghttp3's point of view */ + CURL_TRC_CF(data, cf, "[%" FMT_PRId64 "] send %zu bytes to QUIC ok", + s->id, vec[i].len); + acked_len += vec[i].len; + } + else { + int detail = SSL_get_error(s->ssl, 0); + switch(detail) { + case SSL_ERROR_WANT_WRITE: + case SSL_ERROR_WANT_READ: + /* QUIC blocked us from writing more */ + CURL_TRC_CF(data, cf, "[%"FMT_PRId64 "] send %zu bytes to " + "QUIC blocked", s->id, vec[i].len); + written = 0; + nghttp3_conn_block_stream(ctx->h3.conn, s->id); + s->send_blocked = blocked = TRUE; + break; + default: + failf(data, "[%"FMT_PRId64 "] send %zu bytes to QUIC, SSL error %d", + s->id, vec[i].len, detail); + result = cf_osslq_ssl_err(cf, data, detail, CURLE_HTTP3); + goto out; + } + } + } + + if(acked_len > 0 || (eos && !s->send_blocked)) { + /* Since QUIC buffers the data written internally, we can tell + * nghttp3 that it can move forward on it */ + ctx->q.last_io = Curl_now(); + rv = nghttp3_conn_add_write_offset(ctx->h3.conn, s->id, acked_len); + if(rv && rv != NGHTTP3_ERR_STREAM_NOT_FOUND) { + failf(data, "nghttp3_conn_add_write_offset returned error: %s\n", + nghttp3_strerror(rv)); + result = CURLE_SEND_ERROR; + goto out; + } + rv = nghttp3_conn_add_ack_offset(ctx->h3.conn, s->id, acked_len); + if(rv && rv != NGHTTP3_ERR_STREAM_NOT_FOUND) { + failf(data, "nghttp3_conn_add_ack_offset returned error: %s\n", + nghttp3_strerror(rv)); + result = CURLE_SEND_ERROR; + goto out; + } + CURL_TRC_CF(data, cf, "[%" FMT_PRId64 "] forwarded %zu/%zu h3 bytes " + "to QUIC, eos=%d", s->id, acked_len, total_len, eos); + } + + if(eos && !s->send_blocked && !eos_written) { + /* wrote everything and H3 indicates end of stream */ + CURL_TRC_CF(data, cf, "[%" FMT_PRId64 "] closing QUIC stream", s->id); + SSL_stream_conclude(s->ssl, 0); + } + } + +out: + CURL_TRC_CF(data, cf, "h3_send_streams -> %d", result); + return result; +} + +static CURLcode cf_progress_egress(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct cf_osslq_ctx *ctx = cf->ctx; + CURLcode result = CURLE_OK; + + if(!ctx->tls.ossl.ssl) + goto out; + + ERR_clear_error(); + result = h3_send_streams(cf, data); + if(result) + goto out; + + if(!SSL_handle_events(ctx->tls.ossl.ssl)) { + int detail = SSL_get_error(ctx->tls.ossl.ssl, 0); + result = cf_osslq_ssl_err(cf, data, detail, CURLE_SEND_ERROR); + } + + result = cf_osslq_check_and_unblock(cf, data); + +out: + CURL_TRC_CF(data, cf, "progress_egress -> %d", result); + return result; +} + +static CURLcode check_and_set_expiry(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct cf_osslq_ctx *ctx = cf->ctx; + CURLcode result = CURLE_OK; + struct timeval tv; + timediff_t timeoutms; + int is_infinite = TRUE; + + if(ctx->tls.ossl.ssl && + SSL_get_event_timeout(ctx->tls.ossl.ssl, &tv, &is_infinite) && + !is_infinite) { + timeoutms = curlx_tvtoms(&tv); + /* QUIC want to be called again latest at the returned timeout */ + if(timeoutms <= 0) { + result = cf_progress_ingress(cf, data); + if(result) + goto out; + result = cf_progress_egress(cf, data); + if(result) + goto out; + if(SSL_get_event_timeout(ctx->tls.ossl.ssl, &tv, &is_infinite)) { + timeoutms = curlx_tvtoms(&tv); + } + } + if(!is_infinite) { + Curl_expire(data, timeoutms, EXPIRE_QUIC); + CURL_TRC_CF(data, cf, "QUIC expiry in %ldms", (long)timeoutms); + } + } +out: + return result; +} + +static CURLcode cf_osslq_connect(struct Curl_cfilter *cf, + struct Curl_easy *data, + bool blocking, bool *done) +{ + struct cf_osslq_ctx *ctx = cf->ctx; + CURLcode result = CURLE_OK; + struct cf_call_data save; + struct curltime now; + int err; + + 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(); + CF_DATA_SAVE(save, cf, data); + + if(!ctx->tls.ossl.ssl) { + ctx->started_at = now; + result = cf_osslq_ctx_start(cf, data); + if(result) + goto out; + } + + if(!ctx->got_first_byte) { + int readable = SOCKET_READABLE(ctx->q.sockfd, 0); + if(readable > 0 && (readable & CURL_CSELECT_IN)) { + ctx->got_first_byte = TRUE; + ctx->first_byte_at = Curl_now(); + } + } + + ERR_clear_error(); + err = SSL_do_handshake(ctx->tls.ossl.ssl); + + if(err == 1) { + /* connected */ + ctx->handshake_at = now; + ctx->q.last_io = now; + CURL_TRC_CF(data, cf, "handshake complete after %dms", + (int)Curl_timediff(now, ctx->started_at)); + result = cf_osslq_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 { + int detail = SSL_get_error(ctx->tls.ossl.ssl, err); + switch(detail) { + case SSL_ERROR_WANT_READ: + ctx->q.last_io = now; + CURL_TRC_CF(data, cf, "QUIC SSL_connect() -> WANT_RECV"); + result = Curl_vquic_tls_before_recv(&ctx->tls, cf, data); + goto out; + case SSL_ERROR_WANT_WRITE: + ctx->q.last_io = now; + CURL_TRC_CF(data, cf, "QUIC SSL_connect() -> WANT_SEND"); + result = CURLE_OK; + goto out; +#ifdef SSL_ERROR_WANT_ASYNC + case SSL_ERROR_WANT_ASYNC: + ctx->q.last_io = now; + CURL_TRC_CF(data, cf, "QUIC SSL_connect() -> WANT_ASYNC"); + result = CURLE_OK; + goto out; +#endif +#ifdef SSL_ERROR_WANT_RETRY_VERIFY + case SSL_ERROR_WANT_RETRY_VERIFY: + result = CURLE_OK; + goto out; +#endif + default: + result = cf_osslq_ssl_err(cf, data, detail, CURLE_COULDNT_CONNECT); + goto out; + } + } + +out: + if(result == CURLE_RECV_ERROR && ctx->tls.ossl.ssl && + ctx->protocol_shutdown) { + /* When a QUIC server instance is shutting down, it may send us a + * CONNECTION_CLOSE right away. Our connection then enters the DRAINING + * state. The CONNECT may work in the near future again. Indicate + * that as a "weird" reply. */ + result = CURLE_WEIRD_SERVER_REPLY; + } + +#ifndef CURL_DISABLE_VERBOSE_STRINGS + if(result) { + struct ip_quadruple ip; + + Curl_cf_socket_peek(cf->next, data, NULL, NULL, &ip); + infof(data, "QUIC connect to %s port %u failed: %s", + ip.remote_ip, ip.remote_port, curl_easy_strerror(result)); + } +#endif + if(!result) + result = check_and_set_expiry(cf, data); + if(result || *done) + CURL_TRC_CF(data, cf, "connect -> %d, done=%d", result, *done); + CF_DATA_RESTORE(cf, save); + return result; +} + +static ssize_t h3_stream_open(struct Curl_cfilter *cf, + struct Curl_easy *data, + const void *buf, size_t len, + CURLcode *err) +{ + struct cf_osslq_ctx *ctx = cf->ctx; + struct h3_stream_ctx *stream = 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(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; + } + + DEBUGASSERT(stream->s.id == -1); + *err = cf_osslq_stream_open(&stream->s, ctx->tls.ossl.ssl, 0, + &ctx->stream_bufcp, data); + if(*err) { + failf(data, "cannot 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->h3.conn, stream->s.id, + nva, nheader, preader, data); + if(rc) { + switch(rc) { + case NGHTTP3_ERR_CONN_CLOSING: + CURL_TRC_CF(data, cf, "h3sid[%"FMT_PRId64"] failed to send, " + "connection is closing", stream->s.id); + break; + default: + CURL_TRC_CF(data, cf, "h3sid[%"FMT_PRId64 "] failed to send -> %d (%s)", + stream->s.id, rc, nghttp3_strerror(rc)); + break; + } + *err = CURLE_SEND_ERROR; + nwritten = -1; + goto out; + } + + if(Curl_trc_is_verbose(data)) { + infof(data, "[HTTP/3] [%" FMT_PRId64 "] OPENED stream for %s", + stream->s.id, data->state.url); + for(i = 0; i < nheader; ++i) { + infof(data, "[HTTP/3] [%" FMT_PRId64 "] [%.*s: %.*s]", + stream->s.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_osslq_send(struct Curl_cfilter *cf, struct Curl_easy *data, + const void *buf, size_t len, bool eos, + CURLcode *err) +{ + struct cf_osslq_ctx *ctx = cf->ctx; + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); + struct cf_call_data save; + ssize_t nwritten; + CURLcode result; + + (void)eos; /* TODO: use to end stream */ + CF_DATA_SAVE(save, cf, data); + DEBUGASSERT(cf->connected); + DEBUGASSERT(ctx->tls.ossl.ssl); + DEBUGASSERT(ctx->h3.conn); + *err = CURLE_OK; + + result = cf_progress_ingress(cf, data); + if(result) { + *err = result; + nwritten = -1; + goto out; + } + + result = cf_progress_egress(cf, data); + if(result) { + *err = result; + nwritten = -1; + goto out; + } + + if(!stream || stream->s.id < 0) { + nwritten = h3_stream_open(cf, data, buf, len, err); + if(nwritten < 0) { + CURL_TRC_CF(data, cf, "failed to open stream -> %d", *err); + goto out; + } + stream = H3_STREAM_CTX(ctx, data); + } + 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, "[%" FMT_PRId64 "] discarding data" + "on closed stream with response", stream->s.id); + *err = CURLE_OK; + nwritten = (ssize_t)len; + goto out; + } + CURL_TRC_CF(data, cf, "[%" FMT_PRId64 "] send_body(len=%zu) " + "-> stream closed", stream->s.id, len); + *err = CURLE_HTTP3; + nwritten = -1; + goto out; + } + else { + nwritten = Curl_bufq_write(&stream->sendbuf, buf, len, err); + CURL_TRC_CF(data, cf, "[%" FMT_PRId64 "] cf_send, add to " + "sendbuf(len=%zu) -> %zd, %d", + stream->s.id, len, nwritten, *err); + if(nwritten < 0) { + goto out; + } + + (void)nghttp3_conn_resume_stream(ctx->h3.conn, stream->s.id); + } + + result = cf_progress_egress(cf, data); + if(result) { + *err = result; + nwritten = -1; + } + +out: + result = check_and_set_expiry(cf, data); + CURL_TRC_CF(data, cf, "[%" FMT_PRId64 "] cf_send(len=%zu) -> %zd, %d", + stream ? stream->s.id : -1, len, nwritten, *err); + CF_DATA_RESTORE(cf, save); + return nwritten; +} + +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 %" FMT_PRId64 " reset by server", + stream->s.id); + *err = data->req.bytecount ? CURLE_PARTIAL_FILE : CURLE_HTTP3; + goto out; + } + else if(!stream->resp_hds_complete) { + failf(data, + "HTTP/3 stream %" FMT_PRId64 + " was closed cleanly, but before getting" + " all response header fields, treated as error", + stream->s.id); + *err = CURLE_HTTP3; + goto out; + } + *err = CURLE_OK; + nread = 0; + +out: + return nread; +} + +static ssize_t cf_osslq_recv(struct Curl_cfilter *cf, struct Curl_easy *data, + char *buf, size_t len, CURLcode *err) +{ + struct cf_osslq_ctx *ctx = cf->ctx; + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); + ssize_t nread = -1; + struct cf_call_data save; + CURLcode result; + + (void)ctx; + CF_DATA_SAVE(save, cf, data); + DEBUGASSERT(cf->connected); + DEBUGASSERT(ctx); + DEBUGASSERT(ctx->tls.ossl.ssl); + DEBUGASSERT(ctx->h3.conn); + *err = CURLE_OK; + + 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, "[%" FMT_PRId64 "] read recvbuf(len=%zu) " + "-> %zd, %d", stream->s.id, len, nread, *err); + goto out; + } + } + + result = cf_progress_ingress(cf, data); + if(result) { + *err = result; + 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, "[%" FMT_PRId64 "] read recvbuf(len=%zu) " + "-> %zd, %d", stream->s.id, len, nread, *err); + goto out; + } + } + + 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)) { + *err = CURLE_SEND_ERROR; + nread = -1; + } + else { + CURLcode result2 = check_and_set_expiry(cf, data); + if(result2) { + *err = result2; + nread = -1; + } + } + CURL_TRC_CF(data, cf, "[%" FMT_PRId64 "] cf_recv(len=%zu) -> %zd, %d", + stream ? stream->s.id : -1, len, nread, *err); + CF_DATA_RESTORE(cf, save); + return nread; +} + +/* + * Called from transfer.c:data_pending to know if we should keep looping + * to receive more data from the connection. + */ +static bool cf_osslq_data_pending(struct Curl_cfilter *cf, + const struct Curl_easy *data) +{ + struct cf_osslq_ctx *ctx = cf->ctx; + const struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); + (void)cf; + return stream && !Curl_bufq_is_empty(&stream->recvbuf); +} + +static CURLcode cf_osslq_data_event(struct Curl_cfilter *cf, + struct Curl_easy *data, + int event, int arg1, void *arg2) +{ + struct cf_osslq_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(ctx, data); + if(stream && !stream->send_closed) { + stream->send_closed = TRUE; + stream->upload_left = Curl_bufq_len(&stream->sendbuf) - + stream->sendbuf_len_in_flight; + (void)nghttp3_conn_resume_stream(ctx->h3.conn, stream->s.id); + } + break; + } + case CF_CTRL_DATA_IDLE: { + struct h3_stream_ctx *stream = H3_STREAM_CTX(ctx, data); + CURL_TRC_CF(data, cf, "data idle"); + if(stream && !stream->closed) { + result = check_and_set_expiry(cf, data); + } + break; + } + default: + break; + } + CF_DATA_RESTORE(cf, save); + return result; +} + +static bool cf_osslq_conn_is_alive(struct Curl_cfilter *cf, + struct Curl_easy *data, + bool *input_pending) +{ + struct cf_osslq_ctx *ctx = cf->ctx; + bool alive = FALSE; + struct cf_call_data save; + + CF_DATA_SAVE(save, cf, data); + *input_pending = FALSE; + if(!ctx->tls.ossl.ssl) + goto out; + +#ifdef SSL_VALUE_QUIC_IDLE_TIMEOUT + /* Added in OpenSSL v3.3.x */ + { + timediff_t idletime; + uint64_t idle_ms = ctx->max_idle_ms; + if(!SSL_get_value_uint(ctx->tls.ossl.ssl, + SSL_VALUE_CLASS_FEATURE_NEGOTIATED, + SSL_VALUE_QUIC_IDLE_TIMEOUT, &idle_ms)) { + CURL_TRC_CF(data, cf, "error getting negotiated idle timeout, " + "assume connection is dead."); + goto out; + } + CURL_TRC_CF(data, cf, "negotiated idle timeout: %zums", (size_t)idle_ms); + idletime = Curl_timediff(Curl_now(), ctx->q.last_io); + if(idletime > 0 && (uint64_t)idletime > idle_ms) + goto out; + } + +#endif + + 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 have sent off a request and the connection is + not in use by any other transfer, there should not be any data here, + only "protocol frames" */ + *input_pending = FALSE; + result = cf_progress_ingress(cf, data); + CURL_TRC_CF(data, cf, "is_alive, progress ingress -> %d", result); + alive = result ? FALSE : TRUE; + } + +out: + CF_DATA_RESTORE(cf, save); + return alive; +} + +static void cf_osslq_adjust_pollset(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct easy_pollset *ps) +{ + struct cf_osslq_ctx *ctx = cf->ctx; + + if(!ctx->tls.ossl.ssl) { + /* NOP */ + } + else if(!cf->connected) { + /* during handshake, transfer has not started yet. we always + * add our socket for polling if SSL wants to send/recv */ + Curl_pollset_set(data, ps, ctx->q.sockfd, + SSL_net_read_desired(ctx->tls.ossl.ssl), + SSL_net_write_desired(ctx->tls.ossl.ssl)); + } + else { + /* once connected, we only modify the socket if it is present. + * this avoids adding it for paused transfers. */ + bool want_recv, want_send; + Curl_pollset_check(data, ps, ctx->q.sockfd, &want_recv, &want_send); + if(want_recv || want_send) { + Curl_pollset_set(data, ps, ctx->q.sockfd, + SSL_net_read_desired(ctx->tls.ossl.ssl), + SSL_net_write_desired(ctx->tls.ossl.ssl)); + } + else if(ctx->need_recv || ctx->need_send) { + Curl_pollset_set(data, ps, ctx->q.sockfd, + ctx->need_recv, ctx->need_send); + } + } +} + +static CURLcode cf_osslq_query(struct Curl_cfilter *cf, + struct Curl_easy *data, + int query, int *pres1, void *pres2) +{ + struct cf_osslq_ctx *ctx = cf->ctx; + + switch(query) { + case CF_QUERY_MAX_CONCURRENT: { +#ifdef SSL_VALUE_QUIC_STREAM_BIDI_LOCAL_AVAIL + /* Added in OpenSSL v3.3.x */ + uint64_t v; + if(!SSL_get_value_uint(ctx->tls.ossl.ssl, SSL_VALUE_CLASS_GENERIC, + SSL_VALUE_QUIC_STREAM_BIDI_LOCAL_AVAIL, &v)) { + CURL_TRC_CF(data, cf, "error getting available local bidi streams"); + return CURLE_HTTP3; + } + /* we report avail + in_use */ + v += CONN_INUSE(cf->conn); + *pres1 = (v > INT_MAX) ? INT_MAX : (int)v; +#else + *pres1 = 100; +#endif + CURL_TRC_CF(data, cf, "query max_conncurrent -> %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; +} + +struct Curl_cftype Curl_cft_http3 = { + "HTTP/3", + CF_TYPE_IP_CONNECT | CF_TYPE_SSL | CF_TYPE_MULTIPLEX, + 0, + cf_osslq_destroy, + cf_osslq_connect, + cf_osslq_close, + cf_osslq_shutdown, + Curl_cf_def_get_host, + cf_osslq_adjust_pollset, + cf_osslq_data_pending, + cf_osslq_send, + cf_osslq_recv, + cf_osslq_data_event, + cf_osslq_conn_is_alive, + Curl_cf_def_conn_keep_alive, + cf_osslq_query, +}; + +CURLcode Curl_cf_osslq_create(struct Curl_cfilter **pcf, + struct Curl_easy *data, + struct connectdata *conn, + const struct Curl_addrinfo *ai) +{ + struct cf_osslq_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; + } + cf_osslq_ctx_init(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); + cf_osslq_ctx_free(ctx); + } + return result; +} + +bool Curl_conn_is_osslq(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; +} + +/* + * Store ngtcp2 version info in this buffer. + */ +void Curl_osslq_ver(char *p, size_t len) +{ + const nghttp3_info *ht3 = nghttp3_version(0); + (void)msnprintf(p, len, "nghttp3/%s", ht3->version_str); +} + +#endif /* USE_OPENSSL_QUIC && USE_NGHTTP3 */ diff --git a/contrib/curl/lib/vquic/curl_osslq.h b/contrib/curl/lib/vquic/curl_osslq.h new file mode 100644 index 0000000000..0e12d7023e --- /dev/null +++ b/contrib/curl/lib/vquic/curl_osslq.h @@ -0,0 +1,51 @@ +#ifndef HEADER_CURL_VQUIC_CURL_OSSLQ_H +#define HEADER_CURL_VQUIC_CURL_OSSLQ_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#if defined(USE_OPENSSL_QUIC) && defined(USE_NGHTTP3) + +#ifdef HAVE_NETINET_UDP_H +#include +#endif + +struct Curl_cfilter; + +#include "urldata.h" + +void Curl_osslq_ver(char *p, size_t len); + +CURLcode Curl_cf_osslq_create(struct Curl_cfilter **pcf, + struct Curl_easy *data, + struct connectdata *conn, + const struct Curl_addrinfo *ai); + +bool Curl_conn_is_osslq(const struct Curl_easy *data, + const struct connectdata *conn, + int sockindex); +#endif + +#endif /* HEADER_CURL_VQUIC_CURL_OSSLQ_H */ diff --git a/contrib/curl/lib/vquic/curl_quiche.c b/contrib/curl/lib/vquic/curl_quiche.c new file mode 100644 index 0000000000..f7a8b0eafe --- /dev/null +++ b/contrib/curl/lib/vquic/curl_quiche.c @@ -0,0 +1,1698 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#ifdef USE_QUICHE +#include +#include +#include +#include "bufq.h" +#include "hash.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 "vquic-tls.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. 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()); +} + +struct cf_quiche_ctx { + struct cf_quic_ctx q; + struct ssl_peer peer; + struct curl_tls_ctx tls; + quiche_conn *qconn; + quiche_config *cfg; + quiche_h3_conn *h3c; + quiche_h3_config *h3config; + uint8_t scid[QUICHE_MAX_CONN_ID_LEN]; + struct curltime started_at; /* time the current attempt started */ + struct curltime handshake_at; /* time connect handshake finished */ + struct bufc_pool stream_bufcp; /* chunk pool for streams */ + struct Curl_hash streams; /* hash `data->mid` to `stream_ctx` */ + curl_off_t data_recvd; + BIT(initialized); + BIT(goaway); /* got GOAWAY from server */ + BIT(x509_store_setup); /* if x509 store has been set up */ + BIT(shutdown_started); /* queued shutdown packets */ +}; + +#ifdef DEBUG_QUICHE +/* initialize debug log callback only once */ +static int debug_log_init = 0; +static void quiche_debug_log(const char *line, void *argp) +{ + (void)argp; + fprintf(stderr, "%s\n", line); +} +#endif + +static void h3_stream_hash_free(void *stream); + +static void cf_quiche_ctx_init(struct cf_quiche_ctx *ctx) +{ + DEBUGASSERT(!ctx->initialized); +#ifdef DEBUG_QUICHE + if(!debug_log_init) { + quiche_enable_debug_logging(quiche_debug_log, NULL); + debug_log_init = 1; + } +#endif + Curl_bufcp_init(&ctx->stream_bufcp, H3_STREAM_CHUNK_SIZE, + H3_STREAM_POOL_SPARES); + Curl_hash_offt_init(&ctx->streams, 63, h3_stream_hash_free); + ctx->data_recvd = 0; + ctx->initialized = TRUE; +} + +static void cf_quiche_ctx_free(struct cf_quiche_ctx *ctx) +{ + if(ctx && ctx->initialized) { + /* quiche just freed it */ + ctx->tls.ossl.ssl = NULL; + Curl_vquic_tls_cleanup(&ctx->tls); + Curl_ssl_peer_cleanup(&ctx->peer); + vquic_ctx_free(&ctx->q); + Curl_bufcp_free(&ctx->stream_bufcp); + Curl_hash_clean(&ctx->streams); + Curl_hash_destroy(&ctx->streams); + } + free(ctx); +} + +static void cf_quiche_ctx_close(struct cf_quiche_ctx *ctx) +{ + if(ctx->h3c) + quiche_h3_conn_free(ctx->h3c); + if(ctx->h3config) + quiche_h3_config_free(ctx->h3config); + if(ctx->qconn) + quiche_conn_free(ctx->qconn); + if(ctx->cfg) + quiche_config_free(ctx->cfg); +} + +static CURLcode cf_flush_egress(struct Curl_cfilter *cf, + struct Curl_easy *data); + +/** + * All about the H3 internals of a stream + */ +struct stream_ctx { + curl_uint64_t id; /* HTTP/3 protocol stream identifier */ + struct bufq recvbuf; /* h3 response */ + struct h1_req_parser h1; /* h1 request parsing */ + curl_uint64_t error3; /* HTTP/3 stream error code */ + BIT(opened); /* TRUE after stream has been opened */ + BIT(closed); /* TRUE on stream close */ + BIT(reset); /* TRUE on stream reset */ + BIT(send_closed); /* stream is locally closed */ + BIT(resp_hds_complete); /* final response has been received */ + BIT(resp_got_header); /* TRUE when h3 stream has recvd some HEADER */ + BIT(quic_flow_blocked); /* stream is blocked by QUIC flow control */ +}; + +#define H3_STREAM_CTX(ctx,data) ((struct stream_ctx *)(\ + data? Curl_hash_offt_get(&(ctx)->streams, (data)->mid) : NULL)) + +static void h3_stream_ctx_free(struct stream_ctx *stream) +{ + Curl_bufq_free(&stream->recvbuf); + Curl_h1_req_parse_free(&stream->h1); + free(stream); +} + +static void h3_stream_hash_free(void *stream) +{ + DEBUGASSERT(stream); + h3_stream_ctx_free((struct stream_ctx *)stream); +} + +static void check_resumes(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct cf_quiche_ctx *ctx = cf->ctx; + struct Curl_llist_node *e; + + DEBUGASSERT(data->multi); + for(e = Curl_llist_head(&data->multi->process); e; e = Curl_node_next(e)) { + struct Curl_easy *sdata = Curl_node_elem(e); + if(sdata->conn == data->conn) { + struct stream_ctx *stream = H3_STREAM_CTX(ctx, sdata); + if(stream && stream->quic_flow_blocked) { + stream->quic_flow_blocked = FALSE; + Curl_expire(data, 0, EXPIRE_RUN_NOW); + CURL_TRC_CF(data, cf, "[%"FMT_PRIu64"] 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(ctx, data); + + if(stream) + return CURLE_OK; + + stream = calloc(1, sizeof(*stream)); + if(!stream) + return CURLE_OUT_OF_MEMORY; + + stream->id = -1; + Curl_bufq_initp(&stream->recvbuf, &ctx->stream_bufcp, + H3_STREAM_RECV_CHUNKS, BUFQ_OPT_SOFT_LIMIT); + Curl_h1_req_parse_init(&stream->h1, H1_PARSE_DEFAULT_MAX_LINE_LEN); + + if(!Curl_hash_offt_set(&ctx->streams, data->mid, stream)) { + h3_stream_ctx_free(stream); + return CURLE_OUT_OF_MEMORY; + } + + return CURLE_OK; +} + +static void h3_data_done(struct Curl_cfilter *cf, struct Curl_easy *data) +{ + struct cf_quiche_ctx *ctx = cf->ctx; + struct stream_ctx *stream = H3_STREAM_CTX(ctx, data); + CURLcode result; + + (void)cf; + if(stream) { + CURL_TRC_CF(data, cf, "[%"FMT_PRIu64"] easy handle is done", stream->id); + if(ctx->qconn && !stream->closed) { + quiche_conn_stream_shutdown(ctx->qconn, stream->id, + QUICHE_SHUTDOWN_READ, CURL_H3_NO_ERROR); + 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; + result = cf_flush_egress(cf, data); + if(result) + CURL_TRC_CF(data, cf, "data_done, flush egress -> %d", result); + } + Curl_hash_offt_remove(&ctx->streams, data->mid); + } +} + +static void h3_drain_stream(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct cf_quiche_ctx *ctx = cf->ctx; + struct stream_ctx *stream = H3_STREAM_CTX(ctx, data); + unsigned char bits; + + (void)cf; + bits = CURL_CSELECT_IN; + if(stream && !stream->send_closed) + bits |= CURL_CSELECT_OUT; + if(data->state.select_bits != bits) { + data->state.select_bits = bits; + Curl_expire(data, 0, EXPIRE_RUN_NOW); + } +} + +static struct Curl_easy *get_stream_easy(struct Curl_cfilter *cf, + struct Curl_easy *data, + curl_uint64_t stream_id, + struct stream_ctx **pstream) +{ + struct cf_quiche_ctx *ctx = cf->ctx; + struct stream_ctx *stream; + + (void)cf; + stream = H3_STREAM_CTX(ctx, data); + if(stream && stream->id == stream_id) { + *pstream = stream; + return data; + } + else { + struct Curl_llist_node *e; + DEBUGASSERT(data->multi); + for(e = Curl_llist_head(&data->multi->process); e; e = Curl_node_next(e)) { + struct Curl_easy *sdata = Curl_node_elem(e); + if(sdata->conn != data->conn) + continue; + stream = H3_STREAM_CTX(ctx, sdata); + if(stream && stream->id == stream_id) { + *pstream = stream; + return sdata; + } + } + } + *pstream = NULL; + return NULL; +} + +static void cf_quiche_expire_conn_closed(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct Curl_llist_node *e; + + DEBUGASSERT(data->multi); + CURL_TRC_CF(data, cf, "conn closed, expire all transfers"); + for(e = Curl_llist_head(&data->multi->process); e; e = Curl_node_next(e)) { + struct Curl_easy *sdata = Curl_node_elem(e); + if(sdata == data || sdata->conn != data->conn) + continue; + CURL_TRC_CF(sdata, cf, "conn closed, expire transfer"); + Curl_expire(sdata, 0, EXPIRE_RUN_NOW); + } +} + +/* + * write_resp_raw() copies response data in raw format to the `data`'s + * receive buffer. If not enough space is available, it appends to the + * `data`'s overflow buffer. + */ +static CURLcode write_resp_raw(struct Curl_cfilter *cf, + struct Curl_easy *data, + const void *mem, size_t memlen) +{ + struct cf_quiche_ctx *ctx = cf->ctx; + struct stream_ctx *stream = H3_STREAM_CTX(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 cf_quiche_ctx *ctx = x->cf->ctx; + struct stream_ctx *stream = H3_STREAM_CTX(ctx, x->data); + CURLcode result; + + if(!stream) + return CURLE_OK; + + if((name_len == 7) && !strncmp(HTTP_PSEUDO_STATUS, (char *)name, 7)) { + CURL_TRC_CF(x->data, x->cf, "[%" FMT_PRIu64 "] status: %.*s", + stream->id, (int)value_len, value); + result = write_resp_raw(x->cf, x->data, "HTTP/3 ", sizeof("HTTP/3 ") - 1); + if(!result) + 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, "[%" FMT_PRIu64 "] header: %.*s: %.*s", + stream->id, (int)name_len, name, + (int)value_len, value); + result = write_resp_raw(x->cf, x->data, name, name_len); + 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, "[%"FMT_PRIu64"] 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(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 cf_quiche_ctx *ctx = cf->ctx; + struct stream_ctx *stream = H3_STREAM_CTX(ctx, data); + ssize_t nwritten; + struct cb_ctx cb_ctx; + CURLcode result = CURLE_OK; + + 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, "[%"FMT_PRIu64"] recv_body error %zd", + stream->id, nwritten); + failf(data, "Error %d in HTTP/3 response body for stream[%"FMT_PRIu64"]", + 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, + struct stream_ctx *stream, + quiche_h3_event *ev) +{ + struct cb_ctx cb_ctx; + CURLcode result = CURLE_OK; + int rc; + + if(!stream) + return CURLE_OK; + 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[%" + FMT_PRIu64"]", rc, stream->id); + return CURLE_RECV_ERROR; + } + CURL_TRC_CF(data, cf, "[%"FMT_PRIu64"] <- [HEADERS]", stream->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, "[%"FMT_PRIu64"] RESET", stream->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, "[%"FMT_PRIu64"] CLOSED", stream->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, "[%"FMT_PRIu64"] <- [GOAWAY]", stream->id); + break; + + default: + CURL_TRC_CF(data, cf, "[%"FMT_PRIu64"] recv, unhandled event %d", + stream->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 = NULL; + struct Curl_easy *sdata; + quiche_h3_event *ev; + CURLcode result; + + /* Take in the events and distribute them to the transfers. */ + while(ctx->h3c) { + curl_int64_t stream3_id = quiche_h3_conn_poll(ctx->h3c, ctx->qconn, &ev); + if(stream3_id == QUICHE_H3_ERR_DONE) { + break; + } + else if(stream3_id < 0) { + CURL_TRC_CF(data, cf, "error poll: %"FMT_PRId64, stream3_id); + return CURLE_HTTP3; + } + + sdata = get_stream_easy(cf, data, stream3_id, &stream); + if(!sdata || !stream) { + CURL_TRC_CF(data, cf, "discard event %s for unknown [%"FMT_PRId64"]", + cf_ev_name(ev), stream3_id); + } + else { + result = h3_process_event(cf, sdata, stream, ev); + h3_drain_stream(cf, sdata); + if(result) { + CURL_TRC_CF(data, cf, "error processing event %s " + "for [%"FMT_PRIu64"] -> %d", 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) { + if(quiche_conn_is_draining(ctx->qconn)) { + CURL_TRC_CF(r->data, r->cf, "ingress, connection is draining"); + return CURLE_RECV_ERROR; + } + if(quiche_conn_is_closed(ctx->qconn)) { + CURL_TRC_CF(r->data, r->cf, "ingress, connection is closed"); + return CURLE_RECV_ERROR; + } + CURL_TRC_CF(r->data, r->cf, "ingress, quiche is DONE"); + return CURLE_OK; + } + else if(QUICHE_ERR_TLS_FAIL == nread) { + long verify_ok = SSL_get_verify_result(ctx->tls.ossl.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 = Curl_vquic_tls_before_recv(&ctx->tls, 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; + curl_int64_t expiry_ns; + curl_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)) { + if(quiche_conn_is_timed_out(ctx->qconn)) + failf(data, "connection closed by idle timeout"); + else + failf(data, "connection closed by server"); + /* Connection timed out, expire all transfers belonging to it + * as will not get any more POLL events here. */ + cf_quiche_expire_conn_closed(cf, data); + return CURLE_SEND_ERROR; + } + } + + 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 cf_quiche_ctx *ctx = cf->ctx; + struct stream_ctx *stream = H3_STREAM_CTX(ctx, data); + ssize_t nread = -1; + + DEBUGASSERT(stream); + if(stream->reset) { + failf(data, + "HTTP/3 stream %" FMT_PRIu64 " reset by server", stream->id); + *err = data->req.bytecount ? CURLE_PARTIAL_FILE : CURLE_HTTP3; + CURL_TRC_CF(data, cf, "[%" FMT_PRIu64 "] cf_recv, was reset -> %d", + stream->id, *err); + } + else if(!stream->resp_got_header) { + failf(data, + "HTTP/3 stream %" FMT_PRIu64 " was closed cleanly, but before " + "getting all response header fields, treated as error", + stream->id); + /* *err = CURLE_PARTIAL_FILE; */ + *err = CURLE_HTTP3; + CURL_TRC_CF(data, cf, "[%" FMT_PRIu64 "] 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(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, "[%" FMT_PRIu64 "] 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, "[%" FMT_PRIu64 "] read recvbuf(len=%zu) " + "-> %zd, %d", stream->id, len, nread, *err); + if(nread < 0) + goto out; + } + + if(nread > 0) { + if(stream->closed) + h3_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, "[%"FMT_PRIu64"] cf_recv(total=%" + FMT_OFF_T ") -> %zd, %d", + stream->id, ctx->data_recvd, nread, *err); + return nread; +} + +static ssize_t cf_quiche_send_body(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct stream_ctx *stream, + const void *buf, size_t len, bool eos, + CURLcode *err) +{ + struct cf_quiche_ctx *ctx = cf->ctx; + ssize_t nwritten; + + nwritten = quiche_h3_send_body(ctx->h3c, ctx->qconn, stream->id, + (uint8_t *)buf, len, eos); + 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, "[%" FMT_PRIu64 "] send_body(len=%zu) " + "-> window exhausted", stream->id, len); + stream->quic_flow_blocked = TRUE; + } + *err = CURLE_AGAIN; + return -1; + } + else if(nwritten == QUICHE_H3_TRANSPORT_ERR_INVALID_STREAM_STATE) { + CURL_TRC_CF(data, cf, "[%" FMT_PRIu64 "] send_body(len=%zu) " + "-> invalid stream state", stream->id, len); + *err = CURLE_HTTP3; + return -1; + } + else if(nwritten == QUICHE_H3_TRANSPORT_ERR_FINAL_SIZE) { + CURL_TRC_CF(data, cf, "[%" FMT_PRIu64 "] send_body(len=%zu) " + "-> exceeds size", stream->id, len); + *err = CURLE_SEND_ERROR; + return -1; + } + else if(nwritten < 0) { + CURL_TRC_CF(data, cf, "[%" FMT_PRIu64 "] send_body(len=%zu) " + "-> quiche err %zd", stream->id, len, nwritten); + *err = CURLE_SEND_ERROR; + return -1; + } + else { + if(eos && (len == (size_t)nwritten)) + stream->send_closed = TRUE; + CURL_TRC_CF(data, cf, "[%" FMT_PRIu64 "] send body(len=%zu, " + "eos=%d) -> %zd", + stream->id, len, stream->send_closed, nwritten); + *err = CURLE_OK; + return nwritten; + } +} + +/* 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 char *buf, size_t len, bool eos, + CURLcode *err) +{ + struct cf_quiche_ctx *ctx = cf->ctx; + struct stream_ctx *stream = H3_STREAM_CTX(ctx, data); + size_t nheader, i; + curl_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(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; + } + + if(eos && ((size_t)nwritten == len)) + 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, "[%"FMT_PRIu64"] blocked", stream->id); + stream->quic_flow_blocked = TRUE; + *err = CURLE_AGAIN; + nwritten = -1; + goto out; + } + else { + CURL_TRC_CF(data, cf, "send_request(%s) -> %" FMT_PRIu64, + data->state.url, stream3_id); + } + *err = CURLE_SEND_ERROR; + nwritten = -1; + goto out; + } + + DEBUGASSERT(!stream->opened); + *err = CURLE_OK; + stream->id = stream3_id; + stream->opened = TRUE; + stream->closed = FALSE; + stream->reset = FALSE; + + if(Curl_trc_is_verbose(data)) { + infof(data, "[HTTP/3] [%" FMT_PRIu64 "] OPENED stream for %s", + stream->id, data->state.url); + for(i = 0; i < nheader; ++i) { + infof(data, "[HTTP/3] [%" FMT_PRIu64 "] [%.*s: %.*s]", stream->id, + (int)nva[i].name_len, nva[i].name, + (int)nva[i].value_len, nva[i].value); + } + } + + if(nwritten > 0 && ((size_t)nwritten < len)) { + /* after the headers, there was request BODY data */ + size_t hds_len = (size_t)nwritten; + ssize_t bwritten; + + bwritten = cf_quiche_send_body(cf, data, stream, + buf + hds_len, len - hds_len, eos, err); + if((bwritten < 0) && (CURLE_AGAIN != *err)) { + /* real error, fail */ + nwritten = -1; + } + else if(bwritten > 0) { + nwritten += bwritten; + } + } + +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, bool eos, + CURLcode *err) +{ + struct cf_quiche_ctx *ctx = cf->ctx; + struct stream_ctx *stream = H3_STREAM_CTX(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->opened) { + nwritten = h3_open_stream(cf, data, buf, len, eos, err); + if(nwritten < 0) + goto out; + stream = H3_STREAM_CTX(ctx, data); + } + else if(stream->closed) { + if(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, "[%" FMT_PRIu64 "] discarding data" + "on closed stream with response", stream->id); + *err = CURLE_OK; + nwritten = (ssize_t)len; + goto out; + } + CURL_TRC_CF(data, cf, "[%" FMT_PRIu64 "] send_body(len=%zu) " + "-> stream closed", stream->id, len); + *err = CURLE_HTTP3; + nwritten = -1; + goto out; + } + else { + nwritten = cf_quiche_send_body(cf, data, stream, buf, len, eos, err); + } + +out: + result = cf_flush_egress(cf, data); + if(result) { + *err = result; + nwritten = -1; + } + CURL_TRC_CF(data, cf, "[%" FMT_PRIu64 "] cf_send(len=%zu) -> %zd, %d", + stream ? stream->id : (curl_uint64_t)~0, 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(ctx, data); + + return stream && (quiche_conn_stream_writable( + ctx->qconn, (curl_uint64_t)stream->id, 1) > 0); +} + +static void cf_quiche_adjust_pollset(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct easy_pollset *ps) +{ + struct cf_quiche_ctx *ctx = cf->ctx; + bool want_recv, want_send; + + if(!ctx->qconn) + return; + + Curl_pollset_check(data, ps, ctx->q.sockfd, &want_recv, &want_send); + if(want_recv || want_send) { + struct stream_ctx *stream = H3_STREAM_CTX(ctx, data); + bool c_exhaust, s_exhaust; + + c_exhaust = FALSE; /* Have not found any call in quiche that tells + us if the connection itself is blocked */ + s_exhaust = want_send && stream && stream->opened && + (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) +{ + struct cf_quiche_ctx *ctx = cf->ctx; + const struct stream_ctx *stream = H3_STREAM_CTX(ctx, data); + (void)cf; + return stream && !Curl_bufq_is_empty(&stream->recvbuf); +} + +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) { + h3_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) +{ + struct cf_quiche_ctx *ctx = cf->ctx; + 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(ctx, data); + if(stream && !stream->send_closed) { + unsigned char body[1]; + ssize_t sent; + + stream->send_closed = TRUE; + body[0] = 'X'; + sent = cf_quiche_send(cf, data, body, 0, TRUE, &result); + CURL_TRC_CF(data, cf, "[%"FMT_PRIu64"] DONE_SEND -> %zd, %d", + stream->id, sent, result); + } + break; + } + case CF_CTRL_DATA_IDLE: { + struct stream_ctx *stream = H3_STREAM_CTX(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_quiche_ctx_open(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); + DEBUGASSERT(ctx->initialized); + + result = vquic_ctx_init(&ctx->q); + if(result) + return result; + + result = Curl_ssl_peer_init(&ctx->peer, cf, TRNSPRT_QUIC); + if(result) + return result; + + ctx->cfg = quiche_config_new(QUICHE_PROTOCOL_VERSION); + if(!ctx->cfg) { + failf(data, "cannot create quiche config"); + return CURLE_FAILED_INIT; + } + quiche_config_enable_pacing(ctx->cfg, false); + quiche_config_set_max_idle_timeout(ctx->cfg, CURL_QUIC_MAX_IDLE_MS); + quiche_config_set_initial_max_data(ctx->cfg, (1 * 1024 * 1024) + /* (QUIC_MAX_STREAMS/2) * H3_STREAM_WINDOW_SIZE */); + quiche_config_set_initial_max_streams_bidi(ctx->cfg, QUIC_MAX_STREAMS); + 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); + + result = Curl_vquic_tls_init(&ctx->tls, cf, data, &ctx->peer, + QUICHE_H3_APPLICATION_PROTOCOL, + sizeof(QUICHE_H3_APPLICATION_PROTOCOL) - 1, + NULL, NULL, cf); + 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); + 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->curl_sa_addr, + sockaddr->addrlen, + ctx->cfg, ctx->tls.ossl.ssl, false); + if(!ctx->qconn) { + failf(data, "cannot 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_verify_peer(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct cf_quiche_ctx *ctx = cf->ctx; + + cf->conn->bits.multiplex = TRUE; /* at least potentially multiplexed */ + cf->conn->httpversion = 30; + + return Curl_vquic_tls_verify_peer(&ctx->tls, cf, data, &ctx->peer); +} + +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->qconn) { + result = cf_quiche_ctx_open(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_quiche_verify_peer(cf, data); + if(!result) { + CURL_TRC_CF(data, cf, "peer verified"); + 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; + } + 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) { + struct ip_quadruple ip; + + Curl_cf_socket_peek(cf->next, data, NULL, NULL, &ip); + infof(data, "connect to %s port %u failed: %s", + ip.remote_ip, ip.remote_port, curl_easy_strerror(result)); + } +#endif + return result; +} + +static CURLcode cf_quiche_shutdown(struct Curl_cfilter *cf, + struct Curl_easy *data, bool *done) +{ + struct cf_quiche_ctx *ctx = cf->ctx; + CURLcode result = CURLE_OK; + + if(cf->shutdown || !ctx || !ctx->qconn) { + *done = TRUE; + return CURLE_OK; + } + + *done = FALSE; + if(!ctx->shutdown_started) { + int err; + + ctx->shutdown_started = TRUE; + vquic_ctx_update_time(&ctx->q); + err = quiche_conn_close(ctx->qconn, TRUE, 0, NULL, 0); + if(err) { + CURL_TRC_CF(data, cf, "error %d adding shutdown packet, " + "aborting shutdown", err); + result = CURLE_SEND_ERROR; + goto out; + } + } + + if(!Curl_bufq_is_empty(&ctx->q.sendbuf)) { + CURL_TRC_CF(data, cf, "shutdown, flushing sendbuf"); + result = cf_flush_egress(cf, data); + if(result) + goto out; + } + + if(Curl_bufq_is_empty(&ctx->q.sendbuf)) { + /* sent everything, quiche does not seem to support a graceful + * shutdown waiting for a reply, so ware done. */ + CURL_TRC_CF(data, cf, "shutdown completely sent off, done"); + *done = TRUE; + } + else { + CURL_TRC_CF(data, cf, "shutdown sending blocked"); + } + +out: + return result; +} + +static void cf_quiche_close(struct Curl_cfilter *cf, struct Curl_easy *data) +{ + if(cf->ctx) { + bool done; + (void)cf_quiche_shutdown(cf, data, &done); + cf_quiche_ctx_close(cf->ctx); + } +} + +static void cf_quiche_destroy(struct Curl_cfilter *cf, struct Curl_easy *data) +{ + (void)data; + if(cf->ctx) { + cf_quiche_ctx_free(cf->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: { + curl_uint64_t max_streams = CONN_INUSE(cf->conn); + if(!ctx->goaway) { + max_streams += quiche_conn_peer_streams_left_bidi(ctx->qconn); + } + *pres1 = (max_streams > INT_MAX) ? INT_MAX : (int)max_streams; + CURL_TRC_CF(data, cf, "query conn[%" FMT_OFF_T "]: " + "MAX_CONCURRENT -> %d (%zu in use)", + cf->conn->connection_id, *pres1, CONN_INUSE(cf->conn)); + return CURLE_OK; + } + case CF_QUERY_CONNECT_REPLY_MS: + if(ctx->q.got_first_byte) { + timediff_t ms = Curl_timediff(ctx->q.first_byte_at, ctx->started_at); + *pres1 = (ms < INT_MAX) ? (int)ms : INT_MAX; + } + else + *pres1 = -1; + return CURLE_OK; + case CF_QUERY_TIMER_CONNECT: { + struct curltime *when = pres2; + if(ctx->q.got_first_byte) + *when = ctx->q.first_byte_at; + return CURLE_OK; + } + case CF_QUERY_TIMER_APPCONNECT: { + struct curltime *when = pres2; + if(cf->connected) + *when = ctx->handshake_at; + return CURLE_OK; + } + 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; + + if(quiche_conn_is_closed(ctx->qconn)) { + if(quiche_conn_is_timed_out(ctx->qconn)) + CURL_TRC_CF(data, cf, "connection was closed due to idle timeout"); + else + CURL_TRC_CF(data, cf, "connection is closed"); + return FALSE; + } + + if(!cf->next || !cf->next->cft->is_alive(cf->next, data, input_pending)) + return FALSE; + + if(*input_pending) { + /* This happens before we have sent off a request and the connection is + not in use by any other transfer, there should not 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, + cf_quiche_shutdown, + 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; + } + cf_quiche_ctx_init(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; + + 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); + cf_quiche_ctx_free(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/contrib/curl/lib/vquic/curl_quiche.h b/contrib/curl/lib/vquic/curl_quiche.h new file mode 100644 index 0000000000..bce781c1bc --- /dev/null +++ b/contrib/curl/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, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the 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 +#include + +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/contrib/curl/lib/vquic/vquic-tls.c b/contrib/curl/lib/vquic/vquic-tls.c new file mode 100644 index 0000000000..cc8c22d77d --- /dev/null +++ b/contrib/curl/lib/vquic/vquic-tls.c @@ -0,0 +1,347 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#if defined(USE_HTTP3) && \ + (defined(USE_OPENSSL) || defined(USE_GNUTLS) || defined(USE_WOLFSSL)) + +#ifdef USE_OPENSSL +#include +#include "vtls/openssl.h" +#elif defined(USE_GNUTLS) +#include +#include +#include +#include +#include +#include "vtls/gtls.h" +#elif defined(USE_WOLFSSL) +#include +#include +#include +#include "vtls/wolfssl.h" +#endif + +#include "urldata.h" +#include "curl_trc.h" +#include "cfilters.h" +#include "multiif.h" +#include "vtls/keylog.h" +#include "vtls/vtls.h" +#include "vquic-tls.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 + +#if defined(USE_WOLFSSL) + +#define QUIC_CIPHERS \ + "TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_" \ + "POLY1305_SHA256:TLS_AES_128_CCM_SHA256" +#define QUIC_GROUPS "P-256:P-384:P-521" + +#if defined(HAVE_SECRET_CALLBACK) +static void keylog_callback(const WOLFSSL *ssl, const char *line) +{ + (void)ssl; + Curl_tls_keylog_write_line(line); +} +#endif + +static CURLcode Curl_wssl_init_ctx(struct curl_tls_ctx *ctx, + struct Curl_cfilter *cf, + struct Curl_easy *data, + Curl_vquic_tls_ctx_setup *cb_setup, + void *cb_user_data) +{ + struct ssl_primary_config *conn_config; + CURLcode result = CURLE_FAILED_INIT; + + conn_config = Curl_ssl_cf_get_primary_config(cf); + if(!conn_config) { + result = CURLE_FAILED_INIT; + goto out; + } + + ctx->wssl.ctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method()); + if(!ctx->wssl.ctx) { + result = CURLE_OUT_OF_MEMORY; + goto out; + } + + if(cb_setup) { + result = cb_setup(cf, data, cb_user_data); + if(result) + goto out; + } + + wolfSSL_CTX_set_default_verify_paths(ctx->wssl.ctx); + + if(wolfSSL_CTX_set_cipher_list(ctx->wssl.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); + result = CURLE_BAD_FUNCTION_ARGUMENT; + goto out; + } + + if(wolfSSL_CTX_set1_groups_list(ctx->wssl.ctx, conn_config->curves ? + conn_config->curves : + (char *)QUIC_GROUPS) != 1) { + failf(data, "wolfSSL failed to set curves"); + result = CURLE_BAD_FUNCTION_ARGUMENT; + 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(ctx->wssl.ctx, keylog_callback); +#else + failf(data, "wolfSSL was built without keylog callback"); + result = CURLE_NOT_BUILT_IN; + 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(ctx->wssl.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(ctx->wssl.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"); + result = CURLE_SSL_CACERT_BADFILE; + 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 will not work so + use wolfSSL's built-in default as fallback */ + wolfSSL_CTX_set_default_verify_paths(ctx->wssl.ctx); + } +#endif + } + else { + wolfSSL_CTX_set_verify(ctx->wssl.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, ctx->wssl.ctx, + data->set.ssl.fsslctxp); + Curl_set_in_callback(data, false); + if(result) { + failf(data, "error signaled by ssl ctx callback"); + goto out; + } + } + result = CURLE_OK; + +out: + if(result && ctx->wssl.ctx) { + SSL_CTX_free(ctx->wssl.ctx); + ctx->wssl.ctx = NULL; + } + return result; +} + +/** SSL callbacks ***/ + +static CURLcode Curl_wssl_init_ssl(struct curl_tls_ctx *ctx, + struct Curl_easy *data, + struct ssl_peer *peer, + const char *alpn, size_t alpn_len, + void *user_data) +{ + (void)data; + DEBUGASSERT(!ctx->wssl.handle); + DEBUGASSERT(ctx->wssl.ctx); + ctx->wssl.handle = wolfSSL_new(ctx->wssl.ctx); + + wolfSSL_set_app_data(ctx->wssl.handle, user_data); + wolfSSL_set_connect_state(ctx->wssl.handle); + wolfSSL_set_quic_use_legacy_codepoint(ctx->wssl.handle, 0); + + if(alpn) + wolfSSL_set_alpn_protos(ctx->wssl.handle, (const unsigned char *)alpn, + (unsigned int)alpn_len); + + if(peer->sni) { + wolfSSL_UseSNI(ctx->wssl.handle, WOLFSSL_SNI_HOST_NAME, + peer->sni, (unsigned short)strlen(peer->sni)); + } + + return CURLE_OK; +} +#endif /* defined(USE_WOLFSSL) */ + +CURLcode Curl_vquic_tls_init(struct curl_tls_ctx *ctx, + struct Curl_cfilter *cf, + struct Curl_easy *data, + struct ssl_peer *peer, + const char *alpn, size_t alpn_len, + Curl_vquic_tls_ctx_setup *cb_setup, + void *cb_user_data, void *ssl_user_data) +{ + CURLcode result; + +#ifdef USE_OPENSSL + (void)result; + return Curl_ossl_ctx_init(&ctx->ossl, cf, data, peer, TRNSPRT_QUIC, + (const unsigned char *)alpn, alpn_len, + cb_setup, cb_user_data, NULL, ssl_user_data); +#elif defined(USE_GNUTLS) + (void)result; + return Curl_gtls_ctx_init(&ctx->gtls, cf, data, peer, + (const unsigned char *)alpn, alpn_len, + cb_setup, cb_user_data, ssl_user_data); +#elif defined(USE_WOLFSSL) + result = Curl_wssl_init_ctx(ctx, cf, data, cb_setup, cb_user_data); + if(result) + return result; + + return Curl_wssl_init_ssl(ctx, data, peer, alpn, alpn_len, ssl_user_data); +#else +#error "no TLS lib in used, should not happen" + return CURLE_FAILED_INIT; +#endif +} + +void Curl_vquic_tls_cleanup(struct curl_tls_ctx *ctx) +{ +#ifdef USE_OPENSSL + if(ctx->ossl.ssl) + SSL_free(ctx->ossl.ssl); + if(ctx->ossl.ssl_ctx) + SSL_CTX_free(ctx->ossl.ssl_ctx); +#elif defined(USE_GNUTLS) + if(ctx->gtls.session) + gnutls_deinit(ctx->gtls.session); + Curl_gtls_shared_creds_free(&ctx->gtls.shared_creds); +#elif defined(USE_WOLFSSL) + if(ctx->wssl.handle) + wolfSSL_free(ctx->wssl.handle); + if(ctx->wssl.ctx) + wolfSSL_CTX_free(ctx->wssl.ctx); +#endif + memset(ctx, 0, sizeof(*ctx)); +} + +CURLcode Curl_vquic_tls_before_recv(struct curl_tls_ctx *ctx, + struct Curl_cfilter *cf, + struct Curl_easy *data) +{ +#ifdef USE_OPENSSL + if(!ctx->ossl.x509_store_setup) { + CURLcode result = Curl_ssl_setup_x509_store(cf, data, ctx->ossl.ssl_ctx); + if(result) + return result; + ctx->ossl.x509_store_setup = TRUE; + } +#elif defined(USE_WOLFSSL) + if(!ctx->wssl.x509_store_setup) { + CURLcode result = Curl_wssl_setup_x509_store(cf, data, &ctx->wssl); + if(result) + return result; + } +#elif defined(USE_GNUTLS) + if(!ctx->gtls.shared_creds->trust_setup) { + CURLcode result = Curl_gtls_client_trust_setup(cf, data, &ctx->gtls); + if(result) + return result; + } +#else + (void)ctx; (void)cf; (void)data; +#endif + return CURLE_OK; +} + +CURLcode Curl_vquic_tls_verify_peer(struct curl_tls_ctx *ctx, + struct Curl_cfilter *cf, + struct Curl_easy *data, + struct ssl_peer *peer) +{ + 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; + +#ifdef USE_OPENSSL + (void)conn_config; + result = Curl_oss_check_peer_cert(cf, data, &ctx->ossl, peer); +#elif defined(USE_GNUTLS) + if(conn_config->verifyhost) { + result = Curl_gtls_verifyserver(data, ctx->gtls.session, + conn_config, &data->set.ssl, peer, + data->set.str[STRING_SSL_PINNEDPUBLICKEY]); + if(result) + return result; + } +#elif defined(USE_WOLFSSL) + (void)data; + if(conn_config->verifyhost) { + if(peer->sni) { + WOLFSSL_X509* cert = wolfSSL_get_peer_certificate(ctx->wssl.handle); + if(wolfSSL_X509_check_host(cert, peer->sni, strlen(peer->sni), 0, NULL) + == WOLFSSL_FAILURE) { + result = CURLE_PEER_FAILED_VERIFICATION; + } + wolfSSL_X509_free(cert); + } + + } +#endif + return result; +} + + +#endif /* !USE_HTTP3 && (USE_OPENSSL || USE_GNUTLS || USE_WOLFSSL) */ diff --git a/contrib/curl/lib/vquic/vquic-tls.h b/contrib/curl/lib/vquic/vquic-tls.h new file mode 100644 index 0000000000..0ec74bfbae --- /dev/null +++ b/contrib/curl/lib/vquic/vquic-tls.h @@ -0,0 +1,100 @@ +#ifndef HEADER_CURL_VQUIC_TLS_H +#define HEADER_CURL_VQUIC_TLS_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" +#include "bufq.h" +#include "vtls/openssl.h" + +#if defined(USE_HTTP3) && \ + (defined(USE_OPENSSL) || defined(USE_GNUTLS) || defined(USE_WOLFSSL)) + +#include "vtls/wolfssl.h" + +struct curl_tls_ctx { +#ifdef USE_OPENSSL + struct ossl_ctx ossl; +#elif defined(USE_GNUTLS) + struct gtls_ctx gtls; +#elif defined(USE_WOLFSSL) + struct wolfssl_ctx wssl; +#endif +}; + +/** + * Callback passed to `Curl_vquic_tls_init()` that can + * do early initializations on the not otherwise configured TLS + * instances created. This varies by TLS backend: + * - openssl/wolfssl: SSL_CTX* has just been created + * - gnutls: gtls_client_init() has run + */ +typedef CURLcode Curl_vquic_tls_ctx_setup(struct Curl_cfilter *cf, + struct Curl_easy *data, + void *cb_user_data); + +/** + * Initialize the QUIC TLS instances based of the SSL configurations + * for the connection filter, transfer and peer. + * @param ctx the TLS context to initialize + * @param cf the connection filter involved + * @param data the transfer involved + * @param peer the peer that will be connected to + * @param alpn the ALPN string in protocol format ((len+bytes+)+), + * may be NULL + * @param alpn_len the overall number of bytes in `alpn` + * @param cb_setup optional callback for very early TLS config + Ā± @param cb_user_data user_data param for callback + * @param ssl_user_data optional pointer to set in TLS application context + */ +CURLcode Curl_vquic_tls_init(struct curl_tls_ctx *ctx, + struct Curl_cfilter *cf, + struct Curl_easy *data, + struct ssl_peer *peer, + const char *alpn, size_t alpn_len, + Curl_vquic_tls_ctx_setup *cb_setup, + void *cb_user_data, + void *ssl_user_data); + +/** + * Cleanup all data that has been initialized. + */ +void Curl_vquic_tls_cleanup(struct curl_tls_ctx *ctx); + +CURLcode Curl_vquic_tls_before_recv(struct curl_tls_ctx *ctx, + struct Curl_cfilter *cf, + struct Curl_easy *data); + +/** + * After the QUIC basic handshake has been, verify that the peer + * (and its certificate) fulfill our requirements. + */ +CURLcode Curl_vquic_tls_verify_peer(struct curl_tls_ctx *ctx, + struct Curl_cfilter *cf, + struct Curl_easy *data, + struct ssl_peer *peer); + +#endif /* !USE_HTTP3 && (USE_OPENSSL || USE_GNUTLS || USE_WOLFSSL) */ + +#endif /* HEADER_CURL_VQUIC_TLS_H */ diff --git a/contrib/curl/lib/vquic/vquic.c b/contrib/curl/lib/vquic/vquic.c new file mode 100644 index 0000000000..715328bc9e --- /dev/null +++ b/contrib/curl/lib/vquic/vquic.c @@ -0,0 +1,743 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#ifdef HAVE_NETINET_UDP_H +#include +#endif +#ifdef HAVE_FCNTL_H +#include +#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_osslq.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 USE_HTTP3 + +#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_OPENSSL_QUIC) && defined(USE_NGHTTP3) + Curl_osslq_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); +} + +#if defined(HAVE_SENDMMSG) || defined(HAVE_SENDMSG) +static size_t msghdr_get_udp_gro(struct msghdr *msg) +{ + int gso_size = 0; +#if defined(__linux__) && defined(UDP_GRO) + struct cmsghdr *cmsg; + + /* Workaround musl CMSG_NXTHDR issue */ +#ifndef __GLIBC__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wsign-compare" +#pragma clang diagnostic ignored "-Wcast-align" +#endif + for(cmsg = CMSG_FIRSTHDR(msg); cmsg; cmsg = CMSG_NXTHDR(msg, cmsg)) { +#ifndef __GLIBC__ +#pragma clang diagnostic pop +#endif + if(cmsg->cmsg_level == SOL_UDP && cmsg->cmsg_type == UDP_GRO) { + memcpy(&gso_size, CMSG_DATA(cmsg), sizeof(gso_size)); + + break; + } + } +#endif + (void)msg; + + return (size_t)gso_size; +} +#endif + +#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 msg_ctrl[MMSG_NUM * CMSG_SPACE(sizeof(uint16_t))]; + 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; + size_t gso_size; + size_t pktlen; + size_t offset, to; + + 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]); + mmsg[i].msg_hdr.msg_control = &msg_ctrl[i]; + mmsg[i].msg_hdr.msg_controllen = CMSG_SPACE(sizeof(uint16_t)); + } + + 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) { + struct ip_quadruple ip; + Curl_cf_socket_peek(cf->next, data, NULL, NULL, &ip); + failf(data, "QUIC: connection to %s port %u refused", + ip.remote_ip, ip.remote_port); + result = CURLE_COULDNT_CONNECT; + goto out; + } + 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); + for(i = 0; i < mcount; ++i) { + total_nread += mmsg[i].msg_len; + + gso_size = msghdr_get_udp_gro(&mmsg[i].msg_hdr); + if(gso_size == 0) { + gso_size = mmsg[i].msg_len; + } + + for(offset = 0; offset < mmsg[i].msg_len; offset = to) { + ++pkts; + + to = offset + gso_size; + if(to > mmsg[i].msg_len) { + pktlen = mmsg[i].msg_len - offset; + } + else { + pktlen = gso_size; + } + + result = recv_cb(bufs[i] + offset, pktlen, 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; + uint8_t msg_ctrl[CMSG_SPACE(sizeof(uint16_t))]; + size_t gso_size; + size_t pktlen; + size_t offset, to; + + 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; + msg.msg_control = msg_ctrl; + + DEBUGASSERT(max_pkts > 0); + for(pkts = 0, total_nread = 0; pkts < max_pkts;) { + msg.msg_name = &remote_addr; + msg.msg_namelen = sizeof(remote_addr); + msg.msg_controllen = sizeof(msg_ctrl); + 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) { + struct ip_quadruple ip; + Curl_cf_socket_peek(cf->next, data, NULL, NULL, &ip); + failf(data, "QUIC: connection to %s port %u refused", + ip.remote_ip, ip.remote_port); + result = CURLE_COULDNT_CONNECT; + goto out; + } + 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; + } + + total_nread += (size_t)nread; + + gso_size = msghdr_get_udp_gro(&msg); + if(gso_size == 0) { + gso_size = (size_t)nread; + } + + for(offset = 0; offset < (size_t)nread; offset = to) { + ++pkts; + + to = offset + gso_size; + if(to > (size_t)nread) { + pktlen = (size_t)nread - offset; + } + else { + pktlen = gso_size; + } + + result = + recv_cb(buf + offset, pktlen, 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) { + struct ip_quadruple ip; + Curl_cf_socket_peek(cf->next, data, NULL, NULL, &ip); + failf(data, "QUIC: connection to %s port %u refused", + ip.remote_ip, ip.remote_port); + result = CURLE_COULDNT_CONNECT; + goto out; + } + 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) { + if(!qctx->got_first_byte) { + qctx->got_first_byte = TRUE; + qctx->first_byte_at = qctx->last_op; + } + 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_OPENSSL_QUIC) && defined(USE_NGHTTP3) + return Curl_cf_osslq_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_OPENSSL_QUIC) && defined(USE_NGHTTP3) + return Curl_conn_is_osslq(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 an HTTP proxy"); + return CURLE_URL_MALFORMAT; + } +#endif + + return CURLE_OK; +} + +#else /* USE_HTTP3 */ + +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 /* !USE_HTTP3 */ diff --git a/contrib/curl/lib/vquic/vquic.h b/contrib/curl/lib/vquic/vquic.h new file mode 100644 index 0000000000..c1ca1df6aa --- /dev/null +++ b/contrib/curl/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, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the 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_HTTP3 +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 /* USE_HTTP3 */ + +#define Curl_conn_is_http3(a,b,c) FALSE + +#endif /* !USE_HTTP3 */ + +CURLcode Curl_conn_may_http3(struct Curl_easy *data, + const struct connectdata *conn); + +#endif /* HEADER_CURL_VQUIC_QUIC_H */ diff --git a/contrib/curl/lib/vquic/vquic_int.h b/contrib/curl/lib/vquic/vquic_int.h new file mode 100644 index 0000000000..754e1f5910 --- /dev/null +++ b/contrib/curl/lib/vquic/vquic_int.h @@ -0,0 +1,93 @@ +#ifndef HEADER_CURL_VQUIC_QUIC_INT_H +#define HEADER_CURL_VQUIC_QUIC_INT_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" +#include "bufq.h" + +#ifdef USE_HTTP3 + +#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 first_byte_at; /* when first byte was recvd */ + 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 + BIT(got_first_byte); /* if first byte was received */ + BIT(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 /* !USE_HTTP3 */ + +#endif /* HEADER_CURL_VQUIC_QUIC_INT_H */ diff --git a/contrib/curl/lib/vssh/.checksrc b/contrib/curl/lib/vssh/.checksrc new file mode 100644 index 0000000000..9066946c89 --- /dev/null +++ b/contrib/curl/lib/vssh/.checksrc @@ -0,0 +1,2 @@ +enable STRERROR +enable STRNCPY diff --git a/contrib/curl/lib/vssh/libssh.c b/contrib/curl/lib/vssh/libssh.c new file mode 100644 index 0000000000..4faa61e9d4 --- /dev/null +++ b/contrib/curl/lib/vssh/libssh.c @@ -0,0 +1,2953 @@ +/*************************************************************************** + * _ _ ____ _ + * 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 + +#ifdef HAVE_NETINET_IN_H +#include +#endif +#ifdef HAVE_ARPA_INET_H +#include +#endif +#ifdef HAVE_NETDB_H +#include +#endif +#ifdef __VMS +#include +#include +#endif + +#include +#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 +#endif +#ifdef HAVE_UNISTD_H +#include +#endif +#ifdef HAVE_FCNTL_H +#include +#endif + +/* The last 3 #include files should be in this order */ +#include "curl_printf.h" +#include "curl_memory.h" +#include "memdebug.h" + +/* 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 void myssh_block2waitfor(struct connectdata *conn, bool block); + +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, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ + 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, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ + 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.str[STRING_SSH_KNOWNHOSTS]) { + +#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: + case SSH_KNOWN_HOSTS_NOT_FOUND: + case SSH_KNOWN_HOSTS_UNKNOWN: + 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: + 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 are 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); + + myssh_block2waitfor(conn, (rc == SSH_AGAIN) ? TRUE : FALSE); + if(rc == SSH_AGAIN) { + DEBUGF(infof(data, "ssh_connect -> EAGAIN")); + 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 = + (unsigned int)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 CURL_LIBSSH_VFS_SIZE_MASK "I64u" + #else + #define CURL_LIBSSH_VFS_SIZE_MASK PRIu64 + #endif + char *tmp = aprintf("statvfs:\n" + "f_bsize: %" CURL_LIBSSH_VFS_SIZE_MASK "\n" + "f_frsize: %" CURL_LIBSSH_VFS_SIZE_MASK "\n" + "f_blocks: %" CURL_LIBSSH_VFS_SIZE_MASK "\n" + "f_bfree: %" CURL_LIBSSH_VFS_SIZE_MASK "\n" + "f_bavail: %" CURL_LIBSSH_VFS_SIZE_MASK "\n" + "f_files: %" CURL_LIBSSH_VFS_SIZE_MASK "\n" + "f_ffree: %" CURL_LIBSSH_VFS_SIZE_MASK "\n" + "f_favail: %" CURL_LIBSSH_VFS_SIZE_MASK "\n" + "f_fsid: %" CURL_LIBSSH_VFS_SIZE_MASK "\n" + "f_flag: %" CURL_LIBSSH_VFS_SIZE_MASK "\n" + "f_namemax: %" CURL_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 (%" FMT_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(data->set.seek_func) { + Curl_set_in_callback(data, true); + seekerr = data->set.seek_func(data->set.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 (cannot seek to offset) */ + do { + char scratch[4*1024]; + size_t readthisamountnow = + (data->state.resume_from - passed > + (curl_off_t)sizeof(scratch)) ? + sizeof(scratch) : curlx_sotouz(data->state.resume_from - passed); + + size_t actuallyread = + data->state.fread_func(scratch, 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_xfer_setup1(data, CURL_XFER_SEND, -1, FALSE); + + /* not set by Curl_xfer_setup to preserve keepon bits */ + conn->sockfd = conn->writesockfd; + + /* store this original bitmask setup to use later on if we cannot + 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 */ + data->state.select_bits = CURL_CSELECT_OUT; + + /* since we do not 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 was not 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_xfer_setup_nop(data); + 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 did not return an error, so maybe the server + * just does not support stat() + * OR the server does not 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 (%" FMT_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 (%" FMT_OFF_T ") was beyond file size (%" + FMT_OFF_T ")", from, size); + return CURLE_BAD_DOWNLOAD_RESUME; + } + if(from > to) { + from = to; + size = 0; + } + else { + if((to - from) == CURL_OFF_T_MAX) + return CURLE_RANGE_ERROR; + 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 are supposed to download the last abs(from) bytes */ + if((curl_off_t)size < -data->state.resume_from) { + failf(data, "Offset (%" FMT_OFF_T ") was beyond file size (%" + FMT_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 (%" FMT_OFF_T + ") was beyond file size (%" FMT_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_xfer_setup_nop(data); + infof(data, "File already completely downloaded"); + state(data, SSH_STOP); + break; + } + Curl_xfer_setup1(data, CURL_XFER_RECV, data->req.size, FALSE); + + /* not set by Curl_xfer_setup 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 */ + data->state.select_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_xfer_setup1(data, CURL_XFER_SEND, -1, FALSE); + + /* not set by Curl_xfer_setup to preserve keepon bits */ + conn->sockfd = conn->writesockfd; + + /* store this original bitmask setup to use later on if we cannot + 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 */ + data->state.select_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_xfer_setup1(data, CURL_XFER_RECV, bytecount, FALSE); + + /* not set by Curl_xfer_setup 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 */ + data->state.select_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 have 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: + 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); + + DEBUGF(infof(data, "ssh_getsock -> %x", bitmap)); + return bitmap; +} + +static void myssh_block2waitfor(struct connectdata *conn, bool block) +{ + struct ssh_conn *sshc = &conn->proto.sshc; + + /* If it did not 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); + conn->waitfor = 0; + /* translate the libssh define bits into our own bit defines */ + if(dir & SSH_READ_PENDING) + conn->waitfor |= KEEP_RECV; + 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 is 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, bool eos, CURLcode *err) +{ + int rc; + struct connectdata *conn = data->conn; + (void) sockindex; /* we only support SCP on the fixed known primary socket */ + (void) err; + (void)eos; + + 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 is 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, bool eos, + CURLcode *err) +{ + ssize_t nwrite; + struct connectdata *conn = data->conn; + (void)sockindex; + (void)eos; + + /* 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, + (uint32_t)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 do not 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)); +} + +#endif /* USE_LIBSSH */ diff --git a/contrib/curl/lib/vssh/libssh2.c b/contrib/curl/lib/vssh/libssh2.c new file mode 100644 index 0000000000..1ea356f9e5 --- /dev/null +++ b/contrib/curl/lib/vssh/libssh2.c @@ -0,0 +1,3837 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +/* #define CURL_LIBSSH2_DEBUG */ + +#include "curl_setup.h" + +#ifdef USE_LIBSSH2 + +#include + +#ifdef HAVE_FCNTL_H +#include +#endif + +#ifdef HAVE_NETINET_IN_H +#include +#endif +#ifdef HAVE_ARPA_INET_H +#include +#endif +#ifdef HAVE_NETDB_H +#include +#endif +#ifdef __VMS +#include +#include +#endif + +#include +#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 /* for base64 encoding/decoding */ +#include + + +/* 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, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ + 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, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ + 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 = + responses[0].text == NULL ? 0 : 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; +} + +/* These functions are made to use the libcurl memory functions - NOT the + debugmem functions, as that leads us to trigger on libssh2 memory leaks + that are not ours to care for */ + +static LIBSSH2_ALLOC_FUNC(my_libssh2_malloc) +{ + (void)abstract; /* arg not used */ + return Curl_cmalloc(count); +} + +static LIBSSH2_REALLOC_FUNC(my_libssh2_realloc) +{ + (void)abstract; /* arg not used */ + return Curl_crealloc(ptr, count); +} + +static LIBSSH2_FREE_FUNC(my_libssh2_free) +{ + (void)abstract; /* arg not used */ + if(ptr) /* ssh2 agent sometimes call free with null ptr */ + Curl_cfree(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 did not have the ability to seek to 64-bit + * positions with 32-bit 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 did not do SCP properly beyond 32-bit sizes on + * 32-bit 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_int64_t)d, 0, 0) +#endif + +/* + * libssh2 1.2.8 fixed the problem with 32-bit 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 enum curl_khtype convert_ssh2_keytype(int sshkeytype) +{ + enum curl_khtype 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 are 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 hostname we need to pass in here. + * What hostname 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, cannot 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 : ""); + + /* 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 */ + 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 does not match */ + if(host) + libssh2_knownhost_del(sshc->kh, host); + FALLTHROUGH(); + case CURLKHSTAT_FINE: + case CURLKHSTAT_FINE_ADD_TO_FILE: + /* proceed */ + if(keycheck != LIBSSH2_KNOWNHOST_CHECK_MATCH) { + /* the found host+key did not 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 (!), do not 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 (!), do not 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) { + enum curl_khtype keytype = convert_ssh2_keytype(sshkeytype); + Curl_set_in_callback(data, true); + rc = data->set.ssh_hostkeyfunc(data->set.ssh_hostkeyfunc_userp, + (int)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 are 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 will not 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"); + struct_stat sbuf; + + /* 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(stat(sshc->rsa, &sbuf)) { + Curl_safefree(sshc->rsa); + sshc->rsa = aprintf("%s/.ssh/id_dsa", home); + if(!sshc->rsa) + out_of_memory = TRUE; + else if(stat(sshc->rsa, &sbuf)) { + 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 && stat(sshc->rsa, &sbuf)) { + Curl_safefree(sshc->rsa); + sshc->rsa = strdup("id_dsa"); + if(sshc->rsa && stat(sshc->rsa, &sbuf)) { + 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 was not 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 do not 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' cannot old >32-bit, 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)), + (long)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 CURL_LIBSSH2_VFS_SIZE_MASK "I64u" + #else + #define CURL_LIBSSH2_VFS_SIZE_MASK "llu" + #endif + char *tmp = aprintf("statvfs:\n" + "f_bsize: %" CURL_LIBSSH2_VFS_SIZE_MASK "\n" + "f_frsize: %" CURL_LIBSSH2_VFS_SIZE_MASK "\n" + "f_blocks: %" CURL_LIBSSH2_VFS_SIZE_MASK "\n" + "f_bfree: %" CURL_LIBSSH2_VFS_SIZE_MASK "\n" + "f_bavail: %" CURL_LIBSSH2_VFS_SIZE_MASK "\n" + "f_files: %" CURL_LIBSSH2_VFS_SIZE_MASK "\n" + "f_ffree: %" CURL_LIBSSH2_VFS_SIZE_MASK "\n" + "f_favail: %" CURL_LIBSSH2_VFS_SIZE_MASK "\n" + "f_fsid: %" CURL_LIBSSH2_VFS_SIZE_MASK "\n" + "f_flag: %" CURL_LIBSSH2_VFS_SIZE_MASK "\n" + "f_namemax: %" CURL_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 = (time_t)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 (%" FMT_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, (long)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(data->set.seek_func) { + Curl_set_in_callback(data, true); + seekerr = data->set.seek_func(data->set.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 (cannot seek to offset) */ + do { + char scratch[4*1024]; + size_t readthisamountnow = + (data->state.resume_from - passed > + (curl_off_t)sizeof(scratch)) ? + sizeof(scratch) : curlx_sotouz(data->state.resume_from - passed); + + size_t actuallyread; + Curl_set_in_callback(data, true); + actuallyread = data->state.fread_func(scratch, 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_xfer_setup1(data, CURL_XFER_SEND, -1, FALSE); + + /* not set by Curl_xfer_setup 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 cannot + 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 */ + data->state.select_bits = CURL_CSELECT_OUT; + + /* since we do not 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)), + (long)data->set.new_directory_perms); + if(rc == LIBSSH2_ERROR_EAGAIN) { + break; + } + *sshc->slash_pos = '/'; + ++sshc->slash_pos; + if(rc < 0) { + /* + * Abort if failure was not 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), + (unsigned 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_xfer_setup_nop(data); + 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, (long)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() did not return an error, so maybe the server + * just does not support stat() + * OR the server does not 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 (%" FMT_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 (%" FMT_OFF_T ") was beyond file size (%" + FMT_OFF_T ")", from, (curl_off_t)attrs.filesize); + return CURLE_BAD_DOWNLOAD_RESUME; + } + if(from > to) { + from = to; + size = 0; + } + else { + if((to - from) == CURL_OFF_T_MAX) + return CURLE_RANGE_ERROR; + 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 are supposed to download the last abs(from) bytes */ + if((curl_off_t)attrs.filesize < -data->state.resume_from) { + failf(data, "Offset (%" FMT_OFF_T ") was beyond file size (%" + FMT_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 (%" FMT_OFF_T + ") was beyond file size (%" FMT_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_xfer_setup_nop(data); + infof(data, "File already completely downloaded"); + state(data, SSH_STOP); + break; + } + Curl_xfer_setup1(data, CURL_XFER_RECV, data->req.size, FALSE); + + /* not set by Curl_xfer_setup 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 */ + data->state.select_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_xfer_setup1(data, CURL_XFER_SEND, -1, FALSE); + + /* not set by Curl_xfer_setup 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 cannot + 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 */ + data->state.select_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_xfer_setup1(data, CURL_XFER_RECV, bytecount, FALSE); + + /* not set by Curl_xfer_setup 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 */ + data->state.select_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 have 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: + 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 _does not_ 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 did not block or libssh2 did not 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 is no error, it is not done and it did not 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; + int socknum = Curl_conn_sockindex(data, sock); + (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_conn_recv(data, socknum, 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; + size_t nwrite; + CURLcode result; + struct connectdata *conn = data->conn; + Curl_send *backup = conn->send[0]; + struct ssh_conn *ssh = &conn->proto.sshc; + int socknum = Curl_conn_sockindex(data, sock); + (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_conn_send(data, socknum, buffer, length, FALSE, &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, nwrite); + return (ssize_t)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 + function's memory allocations can be properly 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; + } + + /* 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, + (long)(data->set.server_response_timeout / 1000)); + } +#endif + +#ifndef CURL_DISABLE_PROXY + if(conn->http_proxy.proxytype == CURLPROXY_HTTPS) { + /* + 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); + + */ +#if LIBSSH2_VERSION_NUM >= 0x010b01 + infof(data, "Uses HTTPS proxy"); + libssh2_session_callback_set2(sshc->ssh_session, + LIBSSH2_CALLBACK_RECV, + (libssh2_cb_generic *)ssh_tls_recv); + libssh2_session_callback_set2(sshc->ssh_session, + LIBSSH2_CALLBACK_SEND, + (libssh2_cb_generic *)ssh_tls_send); +#else + /* + * 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"); + libssh2_session_callback_set(sshc->ssh_session, + LIBSSH2_CALLBACK_RECV, sshrecv.recvp); + libssh2_session_callback_set(sshc->ssh_session, + LIBSSH2_CALLBACK_SEND, sshsend.sendp); +#endif + + /* 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 is 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, bool eos, 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 */ + (void)eos; + + /* 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 is 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, bool eos, CURLcode *err) +{ + ssize_t nwrite; + struct connectdata *conn = data->conn; + struct ssh_conn *sshc = &conn->proto.sshc; + (void)sockindex; + (void)eos; + + 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/contrib/curl/lib/ssh.h b/contrib/curl/lib/vssh/ssh.h similarity index 73% rename from contrib/curl/lib/ssh.h rename to contrib/curl/lib/vssh/ssh.h index b350dcf3a5..2ed78649b9 100644 --- a/contrib/curl/lib/ssh.h +++ b/contrib/curl/lib/vssh/ssh.h @@ -7,11 +7,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2015, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -20,14 +20,24 @@ * 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_LIBSSH2_H +#if defined(USE_LIBSSH2) #include #include -#endif /* HAVE_LIBSSH2_H */ +#elif defined(USE_LIBSSH) +/* in 0.10.0 or later, ignore deprecated warnings */ +#define SSH_SUPPRESS_DEPRECATED +#include +#include +#elif defined(USE_WOLFSSH) +#include +#include +#endif /**************************************************************************** * SSH unique setup @@ -51,6 +61,7 @@ typedef enum { 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 */ @@ -86,6 +97,7 @@ typedef enum { 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, @@ -103,47 +115,83 @@ typedef enum { 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 */ -#ifdef USE_LIBSSH2 + + /* common */ const char *passphrase; /* pass-phrase to use */ - char *rsa_pub; /* path name */ - char *rsa; /* path name */ + 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; - LIBSSH2_SFTP_ATTRIBUTES quote_attrs; /* used by the SFTP_QUOTE state */ - bool acceptfail; /* used by the SFTP_QUOTE (continue if - quote command fails) */ + char *homedir; /* when doing SFTP we figure out home dir in the connect phase */ - - /* Here's a set of struct members used by the SFTP_READDIR state */ - LIBSSH2_SFTP_ATTRIBUTES readdir_attrs; - char *readdir_filename; - char *readdir_longentry; - int readdir_len, readdir_totalLen, readdir_currLen; - char *readdir_line; - char *readdir_linkPath; /* 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; + unsigned 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; - int orig_waitfor; /* default READ/WRITE bits wait for */ + +#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 */ @@ -156,10 +204,16 @@ struct ssh_conn { #ifdef HAVE_LIBSSH2_KNOWNHOST_API LIBSSH2_KNOWNHOSTS *kh; #endif -#endif /* USE_LIBSSH2 */ +#elif defined(USE_WOLFSSH) + WOLFSSH *ssh_session; + WOLFSSH_CTX *ctx; + word32 handleSz; + byte handle[WOLFSSH_MAX_HANDLE]; + curl_off_t offset; +#endif /* USE_LIBSSH */ }; -#ifdef USE_LIBSSH2 +#if defined(USE_LIBSSH2) /* Feature detection based on version numbers to better work with non-configure platforms */ @@ -190,9 +244,32 @@ struct ssh_conn { #define HAVE_LIBSSH2_SESSION_HANDSHAKE 1 #endif +#ifdef HAVE_LIBSSH2_VERSION +/* get it runtime if possible */ +#define CURL_LIBSSH2_VERSION libssh2_version(0) +#else +/* use build-time if runtime 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; -#endif /* USE_LIBSSH2 */ +/* 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/contrib/curl/lib/vssh/wolfssh.c b/contrib/curl/lib/vssh/wolfssh.c new file mode 100644 index 0000000000..fabb74fbd2 --- /dev/null +++ b/contrib/curl/lib/vssh/wolfssh.c @@ -0,0 +1,1170 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#ifdef USE_WOLFSSH + +#include + +#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" +#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 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, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ + 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, /* write_resp */ + ZERO_NULL, /* write_resp_hd */ + 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, bool eos, + 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)eos; + (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, bool eos, CURLcode *err) +{ + struct connectdata *conn = data->conn; + struct ssh_conn *sshc = &conn->proto.sshc; + word32 offset[2]; + int rc; + (void)sockindex; + (void)eos; + + 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 %" FMT_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 username"); + 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 are 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 = Curl_memdup0(name->fName, name->fSz); + if(!sshc->homedir) + sshc->actualcode = CURLE_OUT_OF_MEMORY; + 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 (%" FMT_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(data->set.seek_func) { + Curl_set_in_callback(data, true); + seekerr = data->set.seek_func(data->set.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 (cannot seek to offset) */ + do { + char scratch[4*1024]; + size_t readthisamountnow = + (data->state.resume_from - passed > + (curl_off_t)sizeof(scratch)) ? + sizeof(scratch) : curlx_sotouz(data->state.resume_from - passed); + + size_t actuallyread; + Curl_set_in_callback(data, true); + actuallyread = data->state.fread_func(scratch, 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_xfer_setup1(data, CURL_XFER_SEND, -1, FALSE); + + /* not set by Curl_xfer_setup 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 cannot + 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 */ + data->state.select_bits = CURL_CSELECT_OUT; + + /* since we do not 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 %" FMT_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_xfer_setup_nop(data); + infof(data, "File already completely downloaded"); + state(data, SSH_STOP); + break; + } + Curl_xfer_setup1(data, CURL_XFER_RECV, data->req.size, FALSE); + + /* not set by Curl_xfer_setup 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 */ + data->state.select_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 is no error, it is not done and it did not 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 is 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/contrib/curl/lib/vtls/.checksrc b/contrib/curl/lib/vtls/.checksrc new file mode 100644 index 0000000000..9066946c89 --- /dev/null +++ b/contrib/curl/lib/vtls/.checksrc @@ -0,0 +1,2 @@ +enable STRERROR +enable STRNCPY diff --git a/contrib/curl/lib/vtls/axtls.c b/contrib/curl/lib/vtls/axtls.c deleted file mode 100644 index 1de758b0c2..0000000000 --- a/contrib/curl/lib/vtls/axtls.c +++ /dev/null @@ -1,700 +0,0 @@ -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) 2010, DirecTV, Contact: Eric Hu, . - * Copyright (C) 2010 - 2017, Daniel Stenberg, , et al. - * - * This software is licensed as described in the file COPYING, which - * you should have received as part of this distribution. The terms - * are also available at https://curl.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. - * - ***************************************************************************/ - -/* - * Source file for all axTLS-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_AXTLS -#include -#include -#include "axtls.h" - -#include "sendf.h" -#include "inet_pton.h" -#include "vtls.h" -#include "parsedate.h" -#include "connect.h" /* for the connect timeout */ -#include "select.h" -#include "curl_printf.h" -#include "hostcheck.h" -#include - -/* The last #include files should be: */ -#include "curl_memory.h" -#include "memdebug.h" - - -/* Global axTLS init, called from Curl_ssl_init() */ -int Curl_axtls_init(void) -{ -/* axTLS has no global init. Everything is done through SSL and SSL_CTX - * structs stored in connectdata structure. Perhaps can move to axtls.h. - */ - return 1; -} - -int Curl_axtls_cleanup(void) -{ - /* axTLS has no global cleanup. Perhaps can move this to axtls.h. */ - return 1; -} - -static CURLcode map_error_to_curl(int axtls_err) -{ - switch(axtls_err) { - case SSL_ERROR_NOT_SUPPORTED: - case SSL_ERROR_INVALID_VERSION: - case -70: /* protocol version alert from server */ - return CURLE_UNSUPPORTED_PROTOCOL; - break; - case SSL_ERROR_NO_CIPHER: - return CURLE_SSL_CIPHER; - break; - case SSL_ERROR_BAD_CERTIFICATE: /* this may be bad server cert too */ - case SSL_ERROR_NO_CERT_DEFINED: - case -42: /* bad certificate alert from server */ - case -43: /* unsupported cert alert from server */ - case -44: /* cert revoked alert from server */ - case -45: /* cert expired alert from server */ - case -46: /* cert unknown alert from server */ - return CURLE_SSL_CERTPROBLEM; - break; - case SSL_X509_ERROR(X509_NOT_OK): - case SSL_X509_ERROR(X509_VFY_ERROR_NO_TRUSTED_CERT): - case SSL_X509_ERROR(X509_VFY_ERROR_BAD_SIGNATURE): - case SSL_X509_ERROR(X509_VFY_ERROR_NOT_YET_VALID): - case SSL_X509_ERROR(X509_VFY_ERROR_EXPIRED): - case SSL_X509_ERROR(X509_VFY_ERROR_SELF_SIGNED): - case SSL_X509_ERROR(X509_VFY_ERROR_INVALID_CHAIN): - case SSL_X509_ERROR(X509_VFY_ERROR_UNSUPPORTED_DIGEST): - case SSL_X509_ERROR(X509_INVALID_PRIV_KEY): - return CURLE_PEER_FAILED_VERIFICATION; - break; - case -48: /* unknown ca alert from server */ - return CURLE_SSL_CACERT; - break; - case -49: /* access denied alert from server */ - return CURLE_REMOTE_ACCESS_DENIED; - break; - case SSL_ERROR_CONN_LOST: - case SSL_ERROR_SOCK_SETUP_FAILURE: - case SSL_ERROR_INVALID_HANDSHAKE: - case SSL_ERROR_INVALID_PROT_MSG: - case SSL_ERROR_INVALID_HMAC: - case SSL_ERROR_INVALID_SESSION: - case SSL_ERROR_INVALID_KEY: /* it's too bad this doesn't map better */ - case SSL_ERROR_FINISHED_INVALID: - case SSL_ERROR_NO_CLIENT_RENOG: - default: - return CURLE_SSL_CONNECT_ERROR; - break; - } -} - -static Curl_recv axtls_recv; -static Curl_send axtls_send; - -static void free_ssl_structs(struct ssl_connect_data *connssl) -{ - if(connssl->ssl) { - ssl_free(connssl->ssl); - connssl->ssl = NULL; - } - if(connssl->ssl_ctx) { - ssl_ctx_free(connssl->ssl_ctx); - connssl->ssl_ctx = NULL; - } -} - -/* - * For both blocking and non-blocking connects, this function sets up the - * ssl context and state. This function is called after the TCP connect - * has completed. - */ -static CURLcode connect_prep(struct connectdata *conn, int sockindex) -{ - struct Curl_easy *data = conn->data; - SSL_CTX *ssl_ctx; - SSL *ssl = NULL; - int cert_types[] = {SSL_OBJ_X509_CERT, SSL_OBJ_PKCS12, 0}; - int key_types[] = {SSL_OBJ_RSA_KEY, SSL_OBJ_PKCS8, SSL_OBJ_PKCS12, 0}; - int i, ssl_fcn_return; - - /* Assuming users will not compile in custom key/cert to axTLS. - * Also, even for blocking connects, use axTLS non-blocking feature. - */ - uint32_t client_option = SSL_NO_DEFAULT_KEY | - SSL_SERVER_VERIFY_LATER | - SSL_CONNECT_IN_PARTS; - - if(conn->ssl[sockindex].state == ssl_connection_complete) - /* to make us tolerant against being called more than once for the - same connection */ - return CURLE_OK; - - /* axTLS only supports TLSv1 */ - /* check to see if we've been told to use an explicit SSL/TLS version */ - switch(SSL_CONN_CONFIG(version)) { - case CURL_SSLVERSION_DEFAULT: - case CURL_SSLVERSION_TLSv1: - break; - default: - failf(data, "axTLS only supports TLS 1.0 and 1.1, " - "and it cannot be specified which one to use"); - return CURLE_SSL_CONNECT_ERROR; - } - -#ifdef AXTLSDEBUG - client_option |= SSL_DISPLAY_STATES | SSL_DISPLAY_RSA | SSL_DISPLAY_CERTS; -#endif /* AXTLSDEBUG */ - - /* Allocate an SSL_CTX struct */ - ssl_ctx = ssl_ctx_new(client_option, SSL_DEFAULT_CLNT_SESS); - if(ssl_ctx == NULL) { - failf(data, "unable to create client SSL context"); - return CURLE_SSL_CONNECT_ERROR; - } - - conn->ssl[sockindex].ssl_ctx = ssl_ctx; - conn->ssl[sockindex].ssl = NULL; - - /* Load the trusted CA cert bundle file */ - if(SSL_CONN_CONFIG(CAfile)) { - if(ssl_obj_load(ssl_ctx, SSL_OBJ_X509_CACERT, - SSL_CONN_CONFIG(CAfile), NULL) != SSL_OK) { - infof(data, "error reading ca cert file %s \n", - SSL_CONN_CONFIG(CAfile)); - if(SSL_CONN_CONFIG(verifypeer)) { - return CURLE_SSL_CACERT_BADFILE; - } - } - else - infof(data, "found certificates in %s\n", SSL_CONN_CONFIG(CAfile)); - } - - /* gtls.c tasks we're skipping for now: - * 1) certificate revocation list checking - * 2) dns name assignment to host - * 3) set protocol priority. axTLS is TLSv1 only, so can probably ignore - * 4) set certificate priority. axTLS ignores type and sends certs in - * order added. can probably ignore this. - */ - - /* Load client certificate */ - if(SSL_SET_OPTION(cert)) { - i=0; - /* Instead of trying to analyze cert type here, let axTLS try them all. */ - while(cert_types[i] != 0) { - ssl_fcn_return = ssl_obj_load(ssl_ctx, cert_types[i], - SSL_SET_OPTION(cert), NULL); - if(ssl_fcn_return == SSL_OK) { - infof(data, "successfully read cert file %s \n", - SSL_SET_OPTION(cert)); - break; - } - i++; - } - /* Tried all cert types, none worked. */ - if(cert_types[i] == 0) { - failf(data, "%s is not x509 or pkcs12 format", - SSL_SET_OPTION(cert)); - return CURLE_SSL_CERTPROBLEM; - } - } - - /* Load client key. - If a pkcs12 file successfully loaded a cert, then there's nothing to do - because the key has already been loaded. */ - if(SSL_SET_OPTION(key) && cert_types[i] != SSL_OBJ_PKCS12) { - i=0; - /* Instead of trying to analyze key type here, let axTLS try them all. */ - while(key_types[i] != 0) { - ssl_fcn_return = ssl_obj_load(ssl_ctx, key_types[i], - SSL_SET_OPTION(key), NULL); - if(ssl_fcn_return == SSL_OK) { - infof(data, "successfully read key file %s \n", - SSL_SET_OPTION(key)); - break; - } - i++; - } - /* Tried all key types, none worked. */ - if(key_types[i] == 0) { - failf(data, "Failure: %s is not a supported key file", - SSL_SET_OPTION(key)); - return CURLE_SSL_CONNECT_ERROR; - } - } - - /* gtls.c does more here that is being left out for now - * 1) set session credentials. can probably ignore since axtls puts this - * info in the ssl_ctx struct - * 2) setting up callbacks. these seem gnutls specific - */ - - if(data->set.general_ssl.sessionid) { - const uint8_t *ssl_sessionid; - size_t ssl_idsize; - - /* In axTLS, handshaking happens inside ssl_client_new. */ - Curl_ssl_sessionid_lock(conn); - if(!Curl_ssl_getsessionid(conn, (void **) &ssl_sessionid, &ssl_idsize, - sockindex)) { - /* we got a session id, use it! */ - infof(data, "SSL re-using session ID\n"); - ssl = ssl_client_new(ssl_ctx, conn->sock[sockindex], - ssl_sessionid, (uint8_t)ssl_idsize, NULL); - } - Curl_ssl_sessionid_unlock(conn); - } - - if(!ssl) - ssl = ssl_client_new(ssl_ctx, conn->sock[sockindex], NULL, 0, NULL); - - conn->ssl[sockindex].ssl = ssl; - return CURLE_OK; -} - -/* - * For both blocking and non-blocking connects, this function finalizes the - * SSL connection. - */ -static CURLcode connect_finish(struct connectdata *conn, int sockindex) -{ - struct Curl_easy *data = conn->data; - SSL *ssl = conn->ssl[sockindex].ssl; - const char *peer_CN; - uint32_t dns_altname_index; - const char *dns_altname; - int8_t found_subject_alt_names = 0; - int8_t found_subject_alt_name_matching_conn = 0; - const char * const hostname = SSL_IS_PROXY() ? conn->http_proxy.host.name : - conn->host.name; - const char * const dispname = SSL_IS_PROXY() ? - conn->http_proxy.host.dispname : conn->host.dispname; - - /* Here, gtls.c gets the peer certificates and fails out depending on - * settings in "data." axTLS api doesn't have get cert chain fcn, so omit? - */ - - /* Verify server's certificate */ - if(SSL_CONN_CONFIG(verifypeer)) { - if(ssl_verify_cert(ssl) != SSL_OK) { - Curl_axtls_close(conn, sockindex); - failf(data, "server cert verify failed"); - return CURLE_PEER_FAILED_VERIFICATION; - } - } - else - infof(data, "\t server certificate verification SKIPPED\n"); - - /* Here, gtls.c does issuer verification. axTLS has no straightforward - * equivalent, so omitting for now.*/ - - /* Here, gtls.c does the following - * 1) x509 hostname checking per RFC2818. axTLS doesn't support this, but - * it seems useful. This is now implemented, by Oscar Koeroo - * 2) checks cert validity based on time. axTLS does this in ssl_verify_cert - * 3) displays a bunch of cert information. axTLS doesn't support most of - * this, but a couple fields are available. - */ - - /* There is no (DNS) Altnames count in the version 1.4.8 API. There is a - risk of an inifite loop */ - for(dns_altname_index = 0; ; dns_altname_index++) { - dns_altname = ssl_get_cert_subject_alt_dnsname(ssl, dns_altname_index); - if(dns_altname == NULL) { - break; - } - found_subject_alt_names = 1; - - infof(data, "\tComparing subject alt name DNS with hostname: %s <-> %s\n", - dns_altname, hostname); - if(Curl_cert_hostcheck(dns_altname, hostname)) { - found_subject_alt_name_matching_conn = 1; - break; - } - } - - /* RFC2818 checks */ - if(found_subject_alt_names && !found_subject_alt_name_matching_conn) { - if(SSL_CONN_CONFIG(verifyhost)) { - /* Break connection ! */ - Curl_axtls_close(conn, sockindex); - failf(data, "\tsubjectAltName(s) do not match %s\n", dispname); - return CURLE_PEER_FAILED_VERIFICATION; - } - else - infof(data, "\tsubjectAltName(s) do not match %s\n", dispname); - } - else if(found_subject_alt_names == 0) { - /* Per RFC2818, when no Subject Alt Names were available, examine the peer - CN as a legacy fallback */ - peer_CN = ssl_get_cert_dn(ssl, SSL_X509_CERT_COMMON_NAME); - if(peer_CN == NULL) { - if(SSL_CONN_CONFIG(verifyhost)) { - Curl_axtls_close(conn, sockindex); - failf(data, "unable to obtain common name from peer certificate"); - return CURLE_PEER_FAILED_VERIFICATION; - } - else - infof(data, "unable to obtain common name from peer certificate"); - } - else { - if(!Curl_cert_hostcheck((const char *)peer_CN, hostname)) { - if(SSL_CONN_CONFIG(verifyhost)) { - /* Break connection ! */ - Curl_axtls_close(conn, sockindex); - failf(data, "\tcommon name \"%s\" does not match \"%s\"\n", - peer_CN, dispname); - return CURLE_PEER_FAILED_VERIFICATION; - } - else - infof(data, "\tcommon name \"%s\" does not match \"%s\"\n", - peer_CN, dispname); - } - } - } - - /* General housekeeping */ - conn->ssl[sockindex].state = ssl_connection_complete; - conn->recv[sockindex] = axtls_recv; - conn->send[sockindex] = axtls_send; - - /* Put our freshly minted SSL session in cache */ - if(data->set.general_ssl.sessionid) { - const uint8_t *ssl_sessionid = ssl_get_session_id(ssl); - size_t ssl_idsize = ssl_get_session_id_size(ssl); - Curl_ssl_sessionid_lock(conn); - if(Curl_ssl_addsessionid(conn, (void *) ssl_sessionid, ssl_idsize, - sockindex) != CURLE_OK) - infof(data, "failed to add session to cache\n"); - Curl_ssl_sessionid_unlock(conn); - } - - return CURLE_OK; -} - -/* - * Use axTLS's non-blocking connection feature to open an SSL connection. - * This is called after a TCP connection is already established. - */ -CURLcode Curl_axtls_connect_nonblocking( - struct connectdata *conn, - int sockindex, - bool *done) -{ - CURLcode conn_step; - int ssl_fcn_return; - int i; - - *done = FALSE; - /* connectdata is calloc'd and connecting_state is only changed in this - function, so this is safe, as the state is effectively initialized. */ - if(conn->ssl[sockindex].connecting_state == ssl_connect_1) { - conn_step = connect_prep(conn, sockindex); - if(conn_step != CURLE_OK) { - Curl_axtls_close(conn, sockindex); - return conn_step; - } - conn->ssl[sockindex].connecting_state = ssl_connect_2; - } - - if(conn->ssl[sockindex].connecting_state == ssl_connect_2) { - /* Check to make sure handshake was ok. */ - if(ssl_handshake_status(conn->ssl[sockindex].ssl) != SSL_OK) { - /* Loop to perform more work in between sleeps. This is work around the - fact that axtls does not expose any knowledge about when work needs - to be performed. This can save ~25% of time on SSL handshakes. */ - for(i=0; i<5; i++) { - ssl_fcn_return = ssl_read(conn->ssl[sockindex].ssl, NULL); - if(ssl_fcn_return < 0) { - Curl_axtls_close(conn, sockindex); - ssl_display_error(ssl_fcn_return); /* goes to stdout. */ - return map_error_to_curl(ssl_fcn_return); - } - return CURLE_OK; - } - } - infof(conn->data, "handshake completed successfully\n"); - conn->ssl[sockindex].connecting_state = ssl_connect_3; - } - - if(conn->ssl[sockindex].connecting_state == ssl_connect_3) { - conn_step = connect_finish(conn, sockindex); - if(conn_step != CURLE_OK) { - Curl_axtls_close(conn, sockindex); - return conn_step; - } - - /* Reset connect state */ - conn->ssl[sockindex].connecting_state = ssl_connect_1; - - *done = TRUE; - return CURLE_OK; - } - - /* Unrecognized state. Things are very bad. */ - conn->ssl[sockindex].state = ssl_connection_none; - conn->ssl[sockindex].connecting_state = ssl_connect_1; - /* Return value perhaps not strictly correct, but distinguishes the issue.*/ - return CURLE_BAD_FUNCTION_ARGUMENT; -} - - -/* - * This function is called after the TCP connect has completed. Setup the TLS - * layer and do all necessary magic for a blocking connect. - */ -CURLcode -Curl_axtls_connect(struct connectdata *conn, - int sockindex) - -{ - struct Curl_easy *data = conn->data; - CURLcode conn_step = connect_prep(conn, sockindex); - int ssl_fcn_return; - SSL *ssl = conn->ssl[sockindex].ssl; - long timeout_ms; - - if(conn_step != CURLE_OK) { - Curl_axtls_close(conn, sockindex); - return conn_step; - } - - /* Check to make sure handshake was ok. */ - while(ssl_handshake_status(ssl) != SSL_OK) { - /* 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; - } - - ssl_fcn_return = ssl_read(ssl, NULL); - if(ssl_fcn_return < 0) { - Curl_axtls_close(conn, sockindex); - ssl_display_error(ssl_fcn_return); /* goes to stdout. */ - return map_error_to_curl(ssl_fcn_return); - } - /* TODO: avoid polling */ - Curl_wait_ms(10); - } - infof(conn->data, "handshake completed successfully\n"); - - conn_step = connect_finish(conn, sockindex); - if(conn_step != CURLE_OK) { - Curl_axtls_close(conn, sockindex); - return conn_step; - } - - return CURLE_OK; -} - -/* return number of sent (non-SSL) bytes */ -static ssize_t axtls_send(struct connectdata *conn, - int sockindex, - const void *mem, - size_t len, - CURLcode *err) -{ - /* ssl_write() returns 'int' while write() and send() returns 'size_t' */ - int rc = ssl_write(conn->ssl[sockindex].ssl, mem, (int)len); - - infof(conn->data, " axtls_send\n"); - - if(rc < 0) { - *err = map_error_to_curl(rc); - rc = -1; /* generic error code for send failure */ - } - - *err = CURLE_OK; - return rc; -} - -void Curl_axtls_close(struct connectdata *conn, int sockindex) -{ - struct ssl_connect_data *connssl = &conn->ssl[sockindex]; - - infof(conn->data, " Curl_axtls_close\n"); - - /* line from openssl.c: (void)SSL_shutdown(connssl->ssl); - axTLS compat layer does nothing for SSL_shutdown */ - - /* The following line is from openssl.c. There seems to be no axTLS - equivalent. ssl_free and ssl_ctx_free close things. - SSL_set_connect_state(connssl->handle); */ - - free_ssl_structs(connssl); -} - -/* - * This function is called to shut down the SSL layer but keep the - * socket open (CCC - Clear Command Channel) - */ -int Curl_axtls_shutdown(struct connectdata *conn, int sockindex) -{ - /* Outline taken from openssl.c since functions are in axTLS compat layer. - axTLS's error set is much smaller, so a lot of error-handling was removed. - */ - int retval = 0; - struct ssl_connect_data *connssl = &conn->ssl[sockindex]; - struct Curl_easy *data = conn->data; - uint8_t *buf; - ssize_t nread; - - infof(conn->data, " Curl_axtls_shutdown\n"); - - /* 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... */ - - /* axTLS compat layer does nothing for SSL_shutdown, so we do nothing too - if(data->set.ftp_ccc == CURLFTPSSL_CCC_ACTIVE) - (void)SSL_shutdown(connssl->ssl); - */ - - if(connssl->ssl) { - int what = SOCKET_READABLE(conn->sock[sockindex], 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. buf is managed internally by - axTLS and will be released upon calling ssl_free via - free_ssl_structs. */ - nread = (ssize_t)ssl_read(connssl->ssl, &buf); - - if(nread < SSL_OK) { - failf(data, "close notify alert not received during shutdown"); - retval = -1; - } - } - else if(0 == what) { - /* timeout */ - failf(data, "SSL shutdown timeout"); - } - else { - /* anything that gets here is fatally bad */ - failf(data, "select/poll on SSL socket, errno: %d", SOCKERRNO); - retval = -1; - } - - free_ssl_structs(connssl); - } - return retval; -} - -static ssize_t axtls_recv(struct connectdata *conn, /* connection data */ - int num, /* socketindex */ - char *buf, /* store read data here */ - size_t buffersize, /* max amount to read */ - CURLcode *err) -{ - struct ssl_connect_data *connssl = &conn->ssl[num]; - ssize_t ret = 0; - uint8_t *read_buf; - - infof(conn->data, " axtls_recv\n"); - - *err = CURLE_OK; - if(connssl) { - ret = ssl_read(connssl->ssl, &read_buf); - if(ret > SSL_OK) { - /* ssl_read returns SSL_OK if there is more data to read, so if it is - larger, then all data has been read already. */ - memcpy(buf, read_buf, - (size_t)ret > buffersize ? buffersize : (size_t)ret); - } - else if(ret == SSL_OK) { - /* more data to be read, signal caller to call again */ - *err = CURLE_AGAIN; - ret = -1; - } - else if(ret == -3) { - /* With patched axTLS, SSL_CLOSE_NOTIFY=-3. Hard-coding until axTLS - team approves proposed fix. */ - Curl_axtls_close(conn, num); - } - else { - failf(conn->data, "axTLS recv error (%d)", ret); - *err = map_error_to_curl((int) ret); - ret = -1; - } - } - - return ret; -} - -/* - * Return codes: - * 1 means the connection is still in place - * 0 means the connection has been closed - * -1 means the connection status is unknown - */ -int Curl_axtls_check_cxn(struct connectdata *conn) -{ - /* openssl.c line: rc = SSL_peek(conn->ssl[FIRSTSOCKET].ssl, (void*)&buf, 1); - axTLS compat layer always returns the last argument, so connection is - always alive? */ - - infof(conn->data, " Curl_axtls_check_cxn\n"); - return 1; /* connection still in place */ -} - -void Curl_axtls_session_free(void *ptr) -{ - (void)ptr; - /* free the ID */ - /* both openssl.c and gtls.c do something here, but axTLS's OpenSSL - compatibility layer does nothing, so we do nothing too. */ -} - -size_t Curl_axtls_version(char *buffer, size_t size) -{ - return snprintf(buffer, size, "axTLS/%s", ssl_version()); -} - -CURLcode Curl_axtls_random(struct Curl_easy *data, - unsigned char *entropy, - size_t length) -{ - static bool ssl_seeded = FALSE; - (void)data; - if(!ssl_seeded) { - ssl_seeded = TRUE; - /* Initialize the seed if not already done. This call is not exactly thread - * safe (and neither is the ssl_seeded check), but the worst effect of a - * race condition is that some global resources will leak. */ - RNG_initialize(); - } - get_random((int)length, entropy); - return CURLE_OK; -} - -#endif /* USE_AXTLS */ diff --git a/contrib/curl/lib/vtls/axtls.h b/contrib/curl/lib/vtls/axtls.h deleted file mode 100644 index 53797eadbb..0000000000 --- a/contrib/curl/lib/vtls/axtls.h +++ /dev/null @@ -1,71 +0,0 @@ -#ifndef HEADER_CURL_AXTLS_H -#define HEADER_CURL_AXTLS_H -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) 2010, DirecTV, Contact: Eric Hu - * Copyright (C) 2010 - 2017, Daniel Stenberg, , et al. - * - * This software is licensed as described in the file COPYING, which - * you should have received as part of this distribution. The terms - * are also available at https://curl.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. - * - ***************************************************************************/ - -#ifdef USE_AXTLS -#include "curl/curl.h" -#include "urldata.h" - -int Curl_axtls_init(void); -int Curl_axtls_cleanup(void); -CURLcode Curl_axtls_connect(struct connectdata *conn, int sockindex); -CURLcode Curl_axtls_connect_nonblocking( - struct connectdata *conn, - int sockindex, - bool *done); - - /* close a SSL connection */ -void Curl_axtls_close(struct connectdata *conn, int sockindex); - -void Curl_axtls_session_free(void *ptr); -size_t Curl_axtls_version(char *buffer, size_t size); -int Curl_axtls_shutdown(struct connectdata *conn, int sockindex); -int Curl_axtls_check_cxn(struct connectdata *conn); -CURLcode Curl_axtls_random(struct Curl_easy *data, - unsigned char *entropy, - size_t length); - -/* Set the API backend definition to axTLS */ -#define CURL_SSL_BACKEND CURLSSLBACKEND_AXTLS - -/* API setup for axTLS */ -#define curlssl_init Curl_axtls_init -#define curlssl_cleanup Curl_axtls_cleanup -#define curlssl_connect Curl_axtls_connect -#define curlssl_connect_nonblocking Curl_axtls_connect_nonblocking -#define curlssl_session_free(x) Curl_axtls_session_free(x) -#define curlssl_close_all(x) ((void)x) -#define curlssl_close Curl_axtls_close -#define curlssl_shutdown(x,y) Curl_axtls_shutdown(x,y) -#define curlssl_set_engine(x,y) ((void)x, (void)y, CURLE_NOT_BUILT_IN) -#define curlssl_set_engine_default(x) ((void)x, CURLE_NOT_BUILT_IN) -#define curlssl_engines_list(x) ((void)x, (struct curl_slist *)NULL) -#define curlssl_version Curl_axtls_version -#define curlssl_check_cxn(x) Curl_axtls_check_cxn(x) -#define curlssl_data_pending(x,y) ((void)x, (void)y, 0) -#define curlssl_random(x,y,z) Curl_axtls_random(x,y,z) - -#endif /* USE_AXTLS */ -#endif /* HEADER_CURL_AXTLS_H */ - diff --git a/contrib/curl/lib/vtls/bearssl.c b/contrib/curl/lib/vtls/bearssl.c new file mode 100644 index 0000000000..08edbeb991 --- /dev/null +++ b/contrib/curl/lib/vtls/bearssl.c @@ -0,0 +1,1175 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Michael Forney, + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the 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 + +#include "bearssl.h" +#include "cipher_suite.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" + +/* 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; + BIT(sent_shutdown); +}; + +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 = NULL; + const char *name; + size_t n = 0, 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 (unsigned)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 +}; + +static CURLcode +bearssl_set_ssl_version_min_max(struct Curl_easy *data, + br_ssl_engine_context *ssl_eng, + struct ssl_primary_config *conn_config) +{ + unsigned version_min, version_max; + + switch(conn_config->version) { + case CURL_SSLVERSION_DEFAULT: + case CURL_SSLVERSION_TLSv1: + case CURL_SSLVERSION_TLSv1_0: + version_min = BR_TLS10; + break; + case CURL_SSLVERSION_TLSv1_1: + version_min = BR_TLS11; + break; + case CURL_SSLVERSION_TLSv1_2: + version_min = BR_TLS12; + break; + case CURL_SSLVERSION_TLSv1_3: + failf(data, "BearSSL: does not support TLS 1.3"); + return CURLE_SSL_CONNECT_ERROR; + default: + failf(data, "BearSSL: unsupported minimum TLS version value"); + return CURLE_SSL_CONNECT_ERROR; + } + + switch(conn_config->version_max) { + case CURL_SSLVERSION_MAX_DEFAULT: + case CURL_SSLVERSION_MAX_NONE: + case CURL_SSLVERSION_MAX_TLSv1_3: + case CURL_SSLVERSION_MAX_TLSv1_2: + version_max = BR_TLS12; + break; + case CURL_SSLVERSION_MAX_TLSv1_1: + version_max = BR_TLS11; + break; + case CURL_SSLVERSION_MAX_TLSv1_0: + version_max = BR_TLS10; + break; + default: + failf(data, "BearSSL: unsupported maximum TLS version value"); + return CURLE_SSL_CONNECT_ERROR; + } + + br_ssl_engine_set_versions(ssl_eng, version_min, version_max); + + return CURLE_OK; +} + +static const uint16_t ciphertable[] = { + /* RFC 2246 TLS 1.0 */ + BR_TLS_RSA_WITH_3DES_EDE_CBC_SHA, /* 0x000A */ + + /* RFC 3268 TLS 1.0 AES */ + BR_TLS_RSA_WITH_AES_128_CBC_SHA, /* 0x002F */ + BR_TLS_RSA_WITH_AES_256_CBC_SHA, /* 0x0035 */ + + /* RFC 5246 TLS 1.2 */ + BR_TLS_RSA_WITH_AES_128_CBC_SHA256, /* 0x003C */ + BR_TLS_RSA_WITH_AES_256_CBC_SHA256, /* 0x003D */ + + /* RFC 5288 TLS 1.2 AES GCM */ + BR_TLS_RSA_WITH_AES_128_GCM_SHA256, /* 0x009C */ + BR_TLS_RSA_WITH_AES_256_GCM_SHA384, /* 0x009D */ + + /* RFC 4492 TLS 1.0 ECC */ + BR_TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA, /* 0xC003 */ + BR_TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA, /* 0xC004 */ + BR_TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA, /* 0xC005 */ + BR_TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA, /* 0xC008 */ + BR_TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, /* 0xC009 */ + BR_TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, /* 0xC00A */ + BR_TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA, /* 0xC00D */ + BR_TLS_ECDH_RSA_WITH_AES_128_CBC_SHA, /* 0xC00E */ + BR_TLS_ECDH_RSA_WITH_AES_256_CBC_SHA, /* 0xC00F */ + BR_TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA, /* 0xC012 */ + BR_TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, /* 0xC013 */ + BR_TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, /* 0xC014 */ + + /* RFC 5289 TLS 1.2 ECC HMAC SHA256/384 */ + BR_TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256, /* 0xC023 */ + BR_TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384, /* 0xC024 */ + BR_TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256, /* 0xC025 */ + BR_TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384, /* 0xC026 */ + BR_TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256, /* 0xC027 */ + BR_TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384, /* 0xC028 */ + BR_TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256, /* 0xC029 */ + BR_TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384, /* 0xC02A */ + + /* RFC 5289 TLS 1.2 GCM */ + BR_TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, /* 0xC02B */ + BR_TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, /* 0xC02C */ + BR_TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256, /* 0xC02D */ + BR_TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384, /* 0xC02E */ + BR_TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, /* 0xC02F */ + BR_TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, /* 0xC030 */ + BR_TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256, /* 0xC031 */ + BR_TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384, /* 0xC032 */ + +#ifdef BR_TLS_RSA_WITH_AES_128_CCM + /* RFC 6655 TLS 1.2 CCM + Supported since BearSSL 0.6 */ + BR_TLS_RSA_WITH_AES_128_CCM, /* 0xC09C */ + BR_TLS_RSA_WITH_AES_256_CCM, /* 0xC09D */ + BR_TLS_RSA_WITH_AES_128_CCM_8, /* 0xC0A0 */ + BR_TLS_RSA_WITH_AES_256_CCM_8, /* 0xC0A1 */ + + /* RFC 7251 TLS 1.2 ECC CCM + Supported since BearSSL 0.6 */ + BR_TLS_ECDHE_ECDSA_WITH_AES_128_CCM, /* 0xC0AC */ + BR_TLS_ECDHE_ECDSA_WITH_AES_256_CCM, /* 0xC0AD */ + BR_TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8, /* 0xC0AE */ + BR_TLS_ECDHE_ECDSA_WITH_AES_256_CCM_8, /* 0xC0AF */ +#endif + + /* RFC 7905 TLS 1.2 ChaCha20-Poly1305 + Supported since BearSSL 0.2 */ + BR_TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, /* 0xCCA8 */ + BR_TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, /* 0xCCA9 */ +}; + +#define NUM_OF_CIPHERS (sizeof(ciphertable) / sizeof(ciphertable[0])) + +static CURLcode bearssl_set_selected_ciphers(struct Curl_easy *data, + br_ssl_engine_context *ssl_eng, + const char *ciphers) +{ + uint16_t selected[NUM_OF_CIPHERS]; + size_t count = 0, i; + const char *ptr, *end; + + for(ptr = ciphers; ptr[0] != '\0' && count < NUM_OF_CIPHERS; ptr = end) { + uint16_t id = Curl_cipher_suite_walk_str(&ptr, &end); + + /* Check if cipher is supported */ + if(id) { + for(i = 0; i < NUM_OF_CIPHERS && ciphertable[i] != id; i++); + if(i == NUM_OF_CIPHERS) + id = 0; + } + if(!id) { + if(ptr[0] != '\0') + infof(data, "BearSSL: unknown cipher in list: \"%.*s\"", + (int) (end - ptr), ptr); + continue; + } + + /* No duplicates allowed */ + for(i = 0; i < count && selected[i] != id; i++); + if(i < count) { + infof(data, "BearSSL: duplicate cipher in list: \"%.*s\"", + (int) (end - ptr), ptr); + continue; + } + + selected[count++] = id; + } + + if(count == 0) { + failf(data, "BearSSL: no supported cipher in list"); + return CURLE_SSL_CIPHER; + } + + br_ssl_engine_set_suites(ssl_eng, 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; + int session_set = 0; + + DEBUGASSERT(backend); + CURL_TRC_CF(data, cf, "connect_step1"); + + 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); + + ret = bearssl_set_ssl_version_min_max(data, &backend->ctx.eng, conn_config); + if(ret != CURLE_OK) + return ret; + + 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.cache_session) { + void *session; + + CURL_TRC_CF(data, cf, "connect_step1, check session cache"); + Curl_ssl_sessionid_lock(data); + if(!Curl_ssl_getsessionid(cf, data, &connssl->peer, &session, NULL)) { + br_ssl_engine_set_session_parameters(&backend->ctx.eng, session); + session_set = 1; + infof(data, "BearSSL: reusing session ID"); + } + 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.type != CURL_SSL_PEER_DNS) { + 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 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); + + connssl->io_need = CURL_SSL_IO_NEED_NONE; + 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; + default:; + } + failf(data, "BearSSL: connection error 0x%04x", err); + /* 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, FALSE, + &result); + CURL_TRC_CF(data, cf, "ssl_send(len=%zu) -> %zd, %d", len, ret, result); + if(ret <= 0) { + if(result == CURLE_AGAIN) + connssl->io_need |= CURL_SSL_IO_NEED_SEND; + 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_RECV_ERROR; + } + if(ret <= 0) { + if(result == CURLE_AGAIN) + connssl->io_need |= CURL_SSL_IO_NEED_RECV; + 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; + br_ssl_session_parameters session; + char cipher_str[64]; + char ver_str[16]; + 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 == BR_TLS12) + strcpy(ver_str, "TLSv1.2"); + else if(tver == BR_TLS11) + strcpy(ver_str, "TLSv1.1"); + else if(tver == BR_TLS10) + strcpy(ver_str, "TLSv1.0"); + else { + msnprintf(ver_str, sizeof(ver_str), "TLS 0x%04x", tver); + } + br_ssl_engine_get_session_parameters(&backend->ctx.eng, &session); + Curl_cipher_suite_get_str(session.cipher_suite, cipher_str, + sizeof(cipher_str), true); + infof(data, "BearSSL: %s connection using %s", ver_str, cipher_str); + } + return ret; +} + +static void bearssl_session_free(void *sessionid, size_t idsize) +{ + (void)idsize; + free(sessionid); +} + +static CURLcode bearssl_connect_step3(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + 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.cache_session) { + 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); + ret = Curl_ssl_set_sessionid(cf, data, &connssl->peer, session, 0, + bearssl_session_free); + Curl_ssl_sessionid_unlock(data); + if(ret) + return ret; + } + + 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) { + /* 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 is available. */ + if(connssl->io_need) { + curl_socket_t writefd = (connssl->io_need & CURL_SSL_IO_NEED_SEND) ? + sockfd : CURL_SOCKET_BAD; + curl_socket_t readfd = (connssl->io_need & CURL_SSL_IO_NEED_RECV) ? + 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. + */ + connssl->io_need = CURL_SSL_IO_NEED_NONE; + ret = bearssl_connect_step2(cf, data); + if(ret || (nonblocking && (ssl_connect_2 == 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 CURLcode bearssl_shutdown(struct Curl_cfilter *cf, + struct Curl_easy *data, + bool send_shutdown, bool *done) +{ + struct ssl_connect_data *connssl = cf->ctx; + struct bearssl_ssl_backend_data *backend = + (struct bearssl_ssl_backend_data *)connssl->backend; + CURLcode result; + + DEBUGASSERT(backend); + if(!backend->active || cf->shutdown) { + *done = TRUE; + return CURLE_OK; + } + + *done = FALSE; + if(!backend->sent_shutdown) { + (void)send_shutdown; /* unknown how to suppress our close notify */ + br_ssl_engine_close(&backend->ctx.eng); + backend->sent_shutdown = TRUE; + } + + result = bearssl_run_until(cf, data, BR_SSL_CLOSED); + if(result == CURLE_OK) { + *done = TRUE; + } + else if(result == CURLE_AGAIN) { + CURL_TRC_CF(data, cf, "shutdown EAGAIN, io_need=%x", connssl->io_need); + result = CURLE_OK; + } + else + CURL_TRC_CF(data, cf, "shutdown error: %d", result); + + cf->shutdown = (result || *done); + return result; +} + +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; + + (void)data; + DEBUGASSERT(backend); + + backend->active = FALSE; + if(backend->anchors) { + for(i = 0; i < backend->anchors_len; ++i) + free(backend->anchors[i].dn.data); + Curl_safefree(backend->anchors); + } +} + +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 | + SSLSUPP_CIPHER_LIST, + + sizeof(struct bearssl_ssl_backend_data), + + Curl_none_init, /* init */ + Curl_none_cleanup, /* cleanup */ + bearssl_version, /* version */ + Curl_none_check_cxn, /* check_cxn */ + bearssl_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 */ + Curl_ssl_adjust_pollset, /* adjust_pollset */ + bearssl_get_internals, /* get_internals */ + bearssl_close, /* close_one */ + Curl_none_close_all, /* close_all */ + 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 */ + bearssl_recv, /* recv decrypted data */ + bearssl_send, /* send data to encrypt */ + NULL, /* get_channel_binding */ +}; + +#endif /* USE_BEARSSL */ diff --git a/contrib/curl/lib/vtls/bearssl.h b/contrib/curl/lib/vtls/bearssl.h new file mode 100644 index 0000000000..b3651b092c --- /dev/null +++ b/contrib/curl/lib/vtls/bearssl.h @@ -0,0 +1,34 @@ +#ifndef HEADER_CURL_BEARSSL_H +#define HEADER_CURL_BEARSSL_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Michael Forney, + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the 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/contrib/curl/lib/vtls/cipher_suite.c b/contrib/curl/lib/vtls/cipher_suite.c new file mode 100644 index 0000000000..c025b53e95 --- /dev/null +++ b/contrib/curl/lib/vtls/cipher_suite.c @@ -0,0 +1,891 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Jan Venekamp, + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the 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_SECTRANSP) || defined(USE_MBEDTLS) || \ + defined(USE_BEARSSL) || defined(USE_RUSTLS) +#include "cipher_suite.h" +#include "curl_printf.h" +#include "strcase.h" +#include + +/* + * To support the CURLOPT_SSL_CIPHER_LIST option on SSL backends + * that do not support it natively, but do support setting a list of + * IANA ids, we need a list of all supported cipher suite names + * (OpenSSL and IANA) to be able to look up the IANA ids. + * + * To keep the binary size of this list down we compress each entry + * down to 2 + 6 bytes using the C preprocessor. + */ + +/* + * mbedTLS NOTE: mbedTLS has mbedtls_ssl_get_ciphersuite_id() to + * convert a string representation to an IANA id, we do not use that + * because it does not support "standard" OpenSSL cipher suite + * names, nor IANA names. + */ + +/* NOTE: also see tests/unit/unit3205.c */ + +/* Text for cipher suite parts (max 64 entries), + keep indexes below in sync with this! */ +static const char *cs_txt = + "\0" + "TLS" "\0" + "WITH" "\0" + "128" "\0" + "256" "\0" + "3DES" "\0" + "8" "\0" + "AES" "\0" + "AES128" "\0" + "AES256" "\0" + "CBC" "\0" + "CBC3" "\0" + "CCM" "\0" + "CCM8" "\0" + "CHACHA20" "\0" + "DES" "\0" + "DHE" "\0" + "ECDH" "\0" + "ECDHE" "\0" + "ECDSA" "\0" + "EDE" "\0" + "GCM" "\0" + "MD5" "\0" + "NULL" "\0" + "POLY1305" "\0" + "PSK" "\0" + "RSA" "\0" + "SHA" "\0" + "SHA256" "\0" + "SHA384" "\0" +#if defined(USE_MBEDTLS) + "ARIA" "\0" + "ARIA128" "\0" + "ARIA256" "\0" + "CAMELLIA" "\0" + "CAMELLIA128" "\0" + "CAMELLIA256" "\0" +#endif +#if defined(USE_SECTRANSP) + "40" "\0" + "ADH" "\0" + "AECDH" "\0" + "anon" "\0" + "DES40" "\0" + "DH" "\0" + "DSS" "\0" + "EDH" "\0" + "EXP" "\0" + "EXPORT" "\0" + "IDEA" "\0" + "RC2" "\0" + "RC4" "\0" +#endif +; +/* Indexes of above cs_txt */ +enum { + CS_TXT_IDX_, + CS_TXT_IDX_TLS, + CS_TXT_IDX_WITH, + CS_TXT_IDX_128, + CS_TXT_IDX_256, + CS_TXT_IDX_3DES, + CS_TXT_IDX_8, + CS_TXT_IDX_AES, + CS_TXT_IDX_AES128, + CS_TXT_IDX_AES256, + CS_TXT_IDX_CBC, + CS_TXT_IDX_CBC3, + CS_TXT_IDX_CCM, + CS_TXT_IDX_CCM8, + CS_TXT_IDX_CHACHA20, + CS_TXT_IDX_DES, + CS_TXT_IDX_DHE, + CS_TXT_IDX_ECDH, + CS_TXT_IDX_ECDHE, + CS_TXT_IDX_ECDSA, + CS_TXT_IDX_EDE, + CS_TXT_IDX_GCM, + CS_TXT_IDX_MD5, + CS_TXT_IDX_NULL, + CS_TXT_IDX_POLY1305, + CS_TXT_IDX_PSK, + CS_TXT_IDX_RSA, + CS_TXT_IDX_SHA, + CS_TXT_IDX_SHA256, + CS_TXT_IDX_SHA384, +#if defined(USE_MBEDTLS) + CS_TXT_IDX_ARIA, + CS_TXT_IDX_ARIA128, + CS_TXT_IDX_ARIA256, + CS_TXT_IDX_CAMELLIA, + CS_TXT_IDX_CAMELLIA128, + CS_TXT_IDX_CAMELLIA256, +#endif +#if defined(USE_SECTRANSP) + CS_TXT_IDX_40, + CS_TXT_IDX_ADH, + CS_TXT_IDX_AECDH, + CS_TXT_IDX_anon, + CS_TXT_IDX_DES40, + CS_TXT_IDX_DH, + CS_TXT_IDX_DSS, + CS_TXT_IDX_EDH, + CS_TXT_IDX_EXP, + CS_TXT_IDX_EXPORT, + CS_TXT_IDX_IDEA, + CS_TXT_IDX_RC2, + CS_TXT_IDX_RC4, +#endif + CS_TXT_LEN, +}; + +#define CS_ZIP_IDX(a, b, c, d, e, f, g, h) \ +{ \ + (uint8_t) ((((a) << 2) & 0xFF) | ((b) & 0x3F) >> 4), \ + (uint8_t) ((((b) << 4) & 0xFF) | ((c) & 0x3F) >> 2), \ + (uint8_t) ((((c) << 6) & 0xFF) | ((d) & 0x3F)), \ + (uint8_t) ((((e) << 2) & 0xFF) | ((f) & 0x3F) >> 4), \ + (uint8_t) ((((f) << 4) & 0xFF) | ((g) & 0x3F) >> 2), \ + (uint8_t) ((((g) << 6) & 0xFF) | ((h) & 0x3F)) \ +} +#define CS_ENTRY(id, a, b, c, d, e, f, g, h) \ +{ \ + id, \ + CS_ZIP_IDX( \ + CS_TXT_IDX_ ## a, CS_TXT_IDX_ ## b, \ + CS_TXT_IDX_ ## c, CS_TXT_IDX_ ## d, \ + CS_TXT_IDX_ ## e, CS_TXT_IDX_ ## f, \ + CS_TXT_IDX_ ## g, CS_TXT_IDX_ ## h \ + ) \ +} + +struct cs_entry { + uint16_t id; + uint8_t zip[6]; +}; + +/* !checksrc! disable COMMANOSPACE all */ +static const struct cs_entry cs_list [] = { + /* TLS 1.3 ciphers */ +#if defined(USE_SECTRANSP) || defined(USE_MBEDTLS) || defined(USE_RUSTLS) + CS_ENTRY(0x1301, TLS,AES,128,GCM,SHA256,,,), + CS_ENTRY(0x1302, TLS,AES,256,GCM,SHA384,,,), + CS_ENTRY(0x1303, TLS,CHACHA20,POLY1305,SHA256,,,,), + CS_ENTRY(0x1304, TLS,AES,128,CCM,SHA256,,,), + CS_ENTRY(0x1305, TLS,AES,128,CCM,8,SHA256,,), +#endif + /* TLS 1.2 ciphers */ + CS_ENTRY(0xC02B, TLS,ECDHE,ECDSA,WITH,AES,128,GCM,SHA256), + CS_ENTRY(0xC02B, ECDHE,ECDSA,AES128,GCM,SHA256,,,), + CS_ENTRY(0xC02C, TLS,ECDHE,ECDSA,WITH,AES,256,GCM,SHA384), + CS_ENTRY(0xC02C, ECDHE,ECDSA,AES256,GCM,SHA384,,,), + CS_ENTRY(0xC02F, TLS,ECDHE,RSA,WITH,AES,128,GCM,SHA256), + CS_ENTRY(0xC02F, ECDHE,RSA,AES128,GCM,SHA256,,,), + CS_ENTRY(0xC030, TLS,ECDHE,RSA,WITH,AES,256,GCM,SHA384), + CS_ENTRY(0xC030, ECDHE,RSA,AES256,GCM,SHA384,,,), + CS_ENTRY(0xCCA8, TLS,ECDHE,RSA,WITH,CHACHA20,POLY1305,SHA256,), + CS_ENTRY(0xCCA8, ECDHE,RSA,CHACHA20,POLY1305,,,,), + CS_ENTRY(0xCCA9, TLS,ECDHE,ECDSA,WITH,CHACHA20,POLY1305,SHA256,), + CS_ENTRY(0xCCA9, ECDHE,ECDSA,CHACHA20,POLY1305,,,,), +#if defined(USE_SECTRANSP) || defined(USE_MBEDTLS) || defined(USE_BEARSSL) + CS_ENTRY(0x002F, TLS,RSA,WITH,AES,128,CBC,SHA,), + CS_ENTRY(0x002F, AES128,SHA,,,,,,), + CS_ENTRY(0x0035, TLS,RSA,WITH,AES,256,CBC,SHA,), + CS_ENTRY(0x0035, AES256,SHA,,,,,,), + CS_ENTRY(0x003C, TLS,RSA,WITH,AES,128,CBC,SHA256,), + CS_ENTRY(0x003C, AES128,SHA256,,,,,,), + CS_ENTRY(0x003D, TLS,RSA,WITH,AES,256,CBC,SHA256,), + CS_ENTRY(0x003D, AES256,SHA256,,,,,,), + CS_ENTRY(0x009C, TLS,RSA,WITH,AES,128,GCM,SHA256,), + CS_ENTRY(0x009C, AES128,GCM,SHA256,,,,,), + CS_ENTRY(0x009D, TLS,RSA,WITH,AES,256,GCM,SHA384,), + CS_ENTRY(0x009D, AES256,GCM,SHA384,,,,,), + CS_ENTRY(0xC004, TLS,ECDH,ECDSA,WITH,AES,128,CBC,SHA), + CS_ENTRY(0xC004, ECDH,ECDSA,AES128,SHA,,,,), + CS_ENTRY(0xC005, TLS,ECDH,ECDSA,WITH,AES,256,CBC,SHA), + CS_ENTRY(0xC005, ECDH,ECDSA,AES256,SHA,,,,), + CS_ENTRY(0xC009, TLS,ECDHE,ECDSA,WITH,AES,128,CBC,SHA), + CS_ENTRY(0xC009, ECDHE,ECDSA,AES128,SHA,,,,), + CS_ENTRY(0xC00A, TLS,ECDHE,ECDSA,WITH,AES,256,CBC,SHA), + CS_ENTRY(0xC00A, ECDHE,ECDSA,AES256,SHA,,,,), + CS_ENTRY(0xC00E, TLS,ECDH,RSA,WITH,AES,128,CBC,SHA), + CS_ENTRY(0xC00E, ECDH,RSA,AES128,SHA,,,,), + CS_ENTRY(0xC00F, TLS,ECDH,RSA,WITH,AES,256,CBC,SHA), + CS_ENTRY(0xC00F, ECDH,RSA,AES256,SHA,,,,), + CS_ENTRY(0xC013, TLS,ECDHE,RSA,WITH,AES,128,CBC,SHA), + CS_ENTRY(0xC013, ECDHE,RSA,AES128,SHA,,,,), + CS_ENTRY(0xC014, TLS,ECDHE,RSA,WITH,AES,256,CBC,SHA), + CS_ENTRY(0xC014, ECDHE,RSA,AES256,SHA,,,,), + CS_ENTRY(0xC023, TLS,ECDHE,ECDSA,WITH,AES,128,CBC,SHA256), + CS_ENTRY(0xC023, ECDHE,ECDSA,AES128,SHA256,,,,), + CS_ENTRY(0xC024, TLS,ECDHE,ECDSA,WITH,AES,256,CBC,SHA384), + CS_ENTRY(0xC024, ECDHE,ECDSA,AES256,SHA384,,,,), + CS_ENTRY(0xC025, TLS,ECDH,ECDSA,WITH,AES,128,CBC,SHA256), + CS_ENTRY(0xC025, ECDH,ECDSA,AES128,SHA256,,,,), + CS_ENTRY(0xC026, TLS,ECDH,ECDSA,WITH,AES,256,CBC,SHA384), + CS_ENTRY(0xC026, ECDH,ECDSA,AES256,SHA384,,,,), + CS_ENTRY(0xC027, TLS,ECDHE,RSA,WITH,AES,128,CBC,SHA256), + CS_ENTRY(0xC027, ECDHE,RSA,AES128,SHA256,,,,), + CS_ENTRY(0xC028, TLS,ECDHE,RSA,WITH,AES,256,CBC,SHA384), + CS_ENTRY(0xC028, ECDHE,RSA,AES256,SHA384,,,,), + CS_ENTRY(0xC029, TLS,ECDH,RSA,WITH,AES,128,CBC,SHA256), + CS_ENTRY(0xC029, ECDH,RSA,AES128,SHA256,,,,), + CS_ENTRY(0xC02A, TLS,ECDH,RSA,WITH,AES,256,CBC,SHA384), + CS_ENTRY(0xC02A, ECDH,RSA,AES256,SHA384,,,,), + CS_ENTRY(0xC02D, TLS,ECDH,ECDSA,WITH,AES,128,GCM,SHA256), + CS_ENTRY(0xC02D, ECDH,ECDSA,AES128,GCM,SHA256,,,), + CS_ENTRY(0xC02E, TLS,ECDH,ECDSA,WITH,AES,256,GCM,SHA384), + CS_ENTRY(0xC02E, ECDH,ECDSA,AES256,GCM,SHA384,,,), + CS_ENTRY(0xC031, TLS,ECDH,RSA,WITH,AES,128,GCM,SHA256), + CS_ENTRY(0xC031, ECDH,RSA,AES128,GCM,SHA256,,,), + CS_ENTRY(0xC032, TLS,ECDH,RSA,WITH,AES,256,GCM,SHA384), + CS_ENTRY(0xC032, ECDH,RSA,AES256,GCM,SHA384,,,), +#endif +#if defined(USE_SECTRANSP) || defined(USE_MBEDTLS) + CS_ENTRY(0x0001, TLS,RSA,WITH,NULL,MD5,,,), + CS_ENTRY(0x0001, NULL,MD5,,,,,,), + CS_ENTRY(0x0002, TLS,RSA,WITH,NULL,SHA,,,), + CS_ENTRY(0x0002, NULL,SHA,,,,,,), + CS_ENTRY(0x002C, TLS,PSK,WITH,NULL,SHA,,,), + CS_ENTRY(0x002C, PSK,NULL,SHA,,,,,), + CS_ENTRY(0x002D, TLS,DHE,PSK,WITH,NULL,SHA,,), + CS_ENTRY(0x002D, DHE,PSK,NULL,SHA,,,,), + CS_ENTRY(0x002E, TLS,RSA,PSK,WITH,NULL,SHA,,), + CS_ENTRY(0x002E, RSA,PSK,NULL,SHA,,,,), + CS_ENTRY(0x0033, TLS,DHE,RSA,WITH,AES,128,CBC,SHA), + CS_ENTRY(0x0033, DHE,RSA,AES128,SHA,,,,), + CS_ENTRY(0x0039, TLS,DHE,RSA,WITH,AES,256,CBC,SHA), + CS_ENTRY(0x0039, DHE,RSA,AES256,SHA,,,,), + CS_ENTRY(0x003B, TLS,RSA,WITH,NULL,SHA256,,,), + CS_ENTRY(0x003B, NULL,SHA256,,,,,,), + CS_ENTRY(0x0067, TLS,DHE,RSA,WITH,AES,128,CBC,SHA256), + CS_ENTRY(0x0067, DHE,RSA,AES128,SHA256,,,,), + CS_ENTRY(0x006B, TLS,DHE,RSA,WITH,AES,256,CBC,SHA256), + CS_ENTRY(0x006B, DHE,RSA,AES256,SHA256,,,,), + CS_ENTRY(0x008C, TLS,PSK,WITH,AES,128,CBC,SHA,), + CS_ENTRY(0x008C, PSK,AES128,CBC,SHA,,,,), + CS_ENTRY(0x008D, TLS,PSK,WITH,AES,256,CBC,SHA,), + CS_ENTRY(0x008D, PSK,AES256,CBC,SHA,,,,), + CS_ENTRY(0x0090, TLS,DHE,PSK,WITH,AES,128,CBC,SHA), + CS_ENTRY(0x0090, DHE,PSK,AES128,CBC,SHA,,,), + CS_ENTRY(0x0091, TLS,DHE,PSK,WITH,AES,256,CBC,SHA), + CS_ENTRY(0x0091, DHE,PSK,AES256,CBC,SHA,,,), + CS_ENTRY(0x0094, TLS,RSA,PSK,WITH,AES,128,CBC,SHA), + CS_ENTRY(0x0094, RSA,PSK,AES128,CBC,SHA,,,), + CS_ENTRY(0x0095, TLS,RSA,PSK,WITH,AES,256,CBC,SHA), + CS_ENTRY(0x0095, RSA,PSK,AES256,CBC,SHA,,,), + CS_ENTRY(0x009E, TLS,DHE,RSA,WITH,AES,128,GCM,SHA256), + CS_ENTRY(0x009E, DHE,RSA,AES128,GCM,SHA256,,,), + CS_ENTRY(0x009F, TLS,DHE,RSA,WITH,AES,256,GCM,SHA384), + CS_ENTRY(0x009F, DHE,RSA,AES256,GCM,SHA384,,,), + CS_ENTRY(0x00A8, TLS,PSK,WITH,AES,128,GCM,SHA256,), + CS_ENTRY(0x00A8, PSK,AES128,GCM,SHA256,,,,), + CS_ENTRY(0x00A9, TLS,PSK,WITH,AES,256,GCM,SHA384,), + CS_ENTRY(0x00A9, PSK,AES256,GCM,SHA384,,,,), + CS_ENTRY(0x00AA, TLS,DHE,PSK,WITH,AES,128,GCM,SHA256), + CS_ENTRY(0x00AA, DHE,PSK,AES128,GCM,SHA256,,,), + CS_ENTRY(0x00AB, TLS,DHE,PSK,WITH,AES,256,GCM,SHA384), + CS_ENTRY(0x00AB, DHE,PSK,AES256,GCM,SHA384,,,), + CS_ENTRY(0x00AC, TLS,RSA,PSK,WITH,AES,128,GCM,SHA256), + CS_ENTRY(0x00AC, RSA,PSK,AES128,GCM,SHA256,,,), + CS_ENTRY(0x00AD, TLS,RSA,PSK,WITH,AES,256,GCM,SHA384), + CS_ENTRY(0x00AD, RSA,PSK,AES256,GCM,SHA384,,,), + CS_ENTRY(0x00AE, TLS,PSK,WITH,AES,128,CBC,SHA256,), + CS_ENTRY(0x00AE, PSK,AES128,CBC,SHA256,,,,), + CS_ENTRY(0x00AF, TLS,PSK,WITH,AES,256,CBC,SHA384,), + CS_ENTRY(0x00AF, PSK,AES256,CBC,SHA384,,,,), + CS_ENTRY(0x00B0, TLS,PSK,WITH,NULL,SHA256,,,), + CS_ENTRY(0x00B0, PSK,NULL,SHA256,,,,,), + CS_ENTRY(0x00B1, TLS,PSK,WITH,NULL,SHA384,,,), + CS_ENTRY(0x00B1, PSK,NULL,SHA384,,,,,), + CS_ENTRY(0x00B2, TLS,DHE,PSK,WITH,AES,128,CBC,SHA256), + CS_ENTRY(0x00B2, DHE,PSK,AES128,CBC,SHA256,,,), + CS_ENTRY(0x00B3, TLS,DHE,PSK,WITH,AES,256,CBC,SHA384), + CS_ENTRY(0x00B3, DHE,PSK,AES256,CBC,SHA384,,,), + CS_ENTRY(0x00B4, TLS,DHE,PSK,WITH,NULL,SHA256,,), + CS_ENTRY(0x00B4, DHE,PSK,NULL,SHA256,,,,), + CS_ENTRY(0x00B5, TLS,DHE,PSK,WITH,NULL,SHA384,,), + CS_ENTRY(0x00B5, DHE,PSK,NULL,SHA384,,,,), + CS_ENTRY(0x00B6, TLS,RSA,PSK,WITH,AES,128,CBC,SHA256), + CS_ENTRY(0x00B6, RSA,PSK,AES128,CBC,SHA256,,,), + CS_ENTRY(0x00B7, TLS,RSA,PSK,WITH,AES,256,CBC,SHA384), + CS_ENTRY(0x00B7, RSA,PSK,AES256,CBC,SHA384,,,), + CS_ENTRY(0x00B8, TLS,RSA,PSK,WITH,NULL,SHA256,,), + CS_ENTRY(0x00B8, RSA,PSK,NULL,SHA256,,,,), + CS_ENTRY(0x00B9, TLS,RSA,PSK,WITH,NULL,SHA384,,), + CS_ENTRY(0x00B9, RSA,PSK,NULL,SHA384,,,,), + CS_ENTRY(0xC001, TLS,ECDH,ECDSA,WITH,NULL,SHA,,), + CS_ENTRY(0xC001, ECDH,ECDSA,NULL,SHA,,,,), + CS_ENTRY(0xC006, TLS,ECDHE,ECDSA,WITH,NULL,SHA,,), + CS_ENTRY(0xC006, ECDHE,ECDSA,NULL,SHA,,,,), + CS_ENTRY(0xC00B, TLS,ECDH,RSA,WITH,NULL,SHA,,), + CS_ENTRY(0xC00B, ECDH,RSA,NULL,SHA,,,,), + CS_ENTRY(0xC010, TLS,ECDHE,RSA,WITH,NULL,SHA,,), + CS_ENTRY(0xC010, ECDHE,RSA,NULL,SHA,,,,), + CS_ENTRY(0xC035, TLS,ECDHE,PSK,WITH,AES,128,CBC,SHA), + CS_ENTRY(0xC035, ECDHE,PSK,AES128,CBC,SHA,,,), + CS_ENTRY(0xC036, TLS,ECDHE,PSK,WITH,AES,256,CBC,SHA), + CS_ENTRY(0xC036, ECDHE,PSK,AES256,CBC,SHA,,,), + CS_ENTRY(0xCCAB, TLS,PSK,WITH,CHACHA20,POLY1305,SHA256,,), + CS_ENTRY(0xCCAB, PSK,CHACHA20,POLY1305,,,,,), +#endif +#if defined(USE_SECTRANSP) || defined(USE_BEARSSL) + CS_ENTRY(0x000A, TLS,RSA,WITH,3DES,EDE,CBC,SHA,), + CS_ENTRY(0x000A, DES,CBC3,SHA,,,,,), + CS_ENTRY(0xC003, TLS,ECDH,ECDSA,WITH,3DES,EDE,CBC,SHA), + CS_ENTRY(0xC003, ECDH,ECDSA,DES,CBC3,SHA,,,), + CS_ENTRY(0xC008, TLS,ECDHE,ECDSA,WITH,3DES,EDE,CBC,SHA), + CS_ENTRY(0xC008, ECDHE,ECDSA,DES,CBC3,SHA,,,), + CS_ENTRY(0xC00D, TLS,ECDH,RSA,WITH,3DES,EDE,CBC,SHA), + CS_ENTRY(0xC00D, ECDH,RSA,DES,CBC3,SHA,,,), + CS_ENTRY(0xC012, TLS,ECDHE,RSA,WITH,3DES,EDE,CBC,SHA), + CS_ENTRY(0xC012, ECDHE,RSA,DES,CBC3,SHA,,,), +#endif +#if defined(USE_MBEDTLS) || defined(USE_BEARSSL) + CS_ENTRY(0xC09C, TLS,RSA,WITH,AES,128,CCM,,), + CS_ENTRY(0xC09C, AES128,CCM,,,,,,), + CS_ENTRY(0xC09D, TLS,RSA,WITH,AES,256,CCM,,), + CS_ENTRY(0xC09D, AES256,CCM,,,,,,), + CS_ENTRY(0xC0A0, TLS,RSA,WITH,AES,128,CCM,8,), + CS_ENTRY(0xC0A0, AES128,CCM8,,,,,,), + CS_ENTRY(0xC0A1, TLS,RSA,WITH,AES,256,CCM,8,), + CS_ENTRY(0xC0A1, AES256,CCM8,,,,,,), + CS_ENTRY(0xC0AC, TLS,ECDHE,ECDSA,WITH,AES,128,CCM,), + CS_ENTRY(0xC0AC, ECDHE,ECDSA,AES128,CCM,,,,), + CS_ENTRY(0xC0AD, TLS,ECDHE,ECDSA,WITH,AES,256,CCM,), + CS_ENTRY(0xC0AD, ECDHE,ECDSA,AES256,CCM,,,,), + CS_ENTRY(0xC0AE, TLS,ECDHE,ECDSA,WITH,AES,128,CCM,8), + CS_ENTRY(0xC0AE, ECDHE,ECDSA,AES128,CCM8,,,,), + CS_ENTRY(0xC0AF, TLS,ECDHE,ECDSA,WITH,AES,256,CCM,8), + CS_ENTRY(0xC0AF, ECDHE,ECDSA,AES256,CCM8,,,,), +#endif +#if defined(USE_SECTRANSP) + /* entries marked bc are backward compatible aliases for old OpenSSL names */ + CS_ENTRY(0x0003, TLS,RSA,EXPORT,WITH,RC4,40,MD5,), + CS_ENTRY(0x0003, EXP,RC4,MD5,,,,,), + CS_ENTRY(0x0004, TLS,RSA,WITH,RC4,128,MD5,,), + CS_ENTRY(0x0004, RC4,MD5,,,,,,), + CS_ENTRY(0x0005, TLS,RSA,WITH,RC4,128,SHA,,), + CS_ENTRY(0x0005, RC4,SHA,,,,,,), + CS_ENTRY(0x0006, TLS,RSA,EXPORT,WITH,RC2,CBC,40,MD5), + CS_ENTRY(0x0006, EXP,RC2,CBC,MD5,,,,), + CS_ENTRY(0x0007, TLS,RSA,WITH,IDEA,CBC,SHA,,), + CS_ENTRY(0x0007, IDEA,CBC,SHA,,,,,), + CS_ENTRY(0x0008, TLS,RSA,EXPORT,WITH,DES40,CBC,SHA,), + CS_ENTRY(0x0008, EXP,DES,CBC,SHA,,,,), + CS_ENTRY(0x0009, TLS,RSA,WITH,DES,CBC,SHA,,), + CS_ENTRY(0x0009, DES,CBC,SHA,,,,,), + CS_ENTRY(0x000B, TLS,DH,DSS,EXPORT,WITH,DES40,CBC,SHA), + CS_ENTRY(0x000B, EXP,DH,DSS,DES,CBC,SHA,,), + CS_ENTRY(0x000C, TLS,DH,DSS,WITH,DES,CBC,SHA,), + CS_ENTRY(0x000C, DH,DSS,DES,CBC,SHA,,,), + CS_ENTRY(0x000D, TLS,DH,DSS,WITH,3DES,EDE,CBC,SHA), + CS_ENTRY(0x000D, DH,DSS,DES,CBC3,SHA,,,), + CS_ENTRY(0x000E, TLS,DH,RSA,EXPORT,WITH,DES40,CBC,SHA), + CS_ENTRY(0x000E, EXP,DH,RSA,DES,CBC,SHA,,), + CS_ENTRY(0x000F, TLS,DH,RSA,WITH,DES,CBC,SHA,), + CS_ENTRY(0x000F, DH,RSA,DES,CBC,SHA,,,), + CS_ENTRY(0x0010, TLS,DH,RSA,WITH,3DES,EDE,CBC,SHA), + CS_ENTRY(0x0010, DH,RSA,DES,CBC3,SHA,,,), + CS_ENTRY(0x0011, TLS,DHE,DSS,EXPORT,WITH,DES40,CBC,SHA), + CS_ENTRY(0x0011, EXP,DHE,DSS,DES,CBC,SHA,,), + CS_ENTRY(0x0011, EXP,EDH,DSS,DES,CBC,SHA,,), /* bc */ + CS_ENTRY(0x0012, TLS,DHE,DSS,WITH,DES,CBC,SHA,), + CS_ENTRY(0x0012, DHE,DSS,DES,CBC,SHA,,,), + CS_ENTRY(0x0012, EDH,DSS,DES,CBC,SHA,,,), /* bc */ + CS_ENTRY(0x0013, TLS,DHE,DSS,WITH,3DES,EDE,CBC,SHA), + CS_ENTRY(0x0013, DHE,DSS,DES,CBC3,SHA,,,), + CS_ENTRY(0x0013, EDH,DSS,DES,CBC3,SHA,,,), /* bc */ + CS_ENTRY(0x0014, TLS,DHE,RSA,EXPORT,WITH,DES40,CBC,SHA), + CS_ENTRY(0x0014, EXP,DHE,RSA,DES,CBC,SHA,,), + CS_ENTRY(0x0014, EXP,EDH,RSA,DES,CBC,SHA,,), /* bc */ + CS_ENTRY(0x0015, TLS,DHE,RSA,WITH,DES,CBC,SHA,), + CS_ENTRY(0x0015, DHE,RSA,DES,CBC,SHA,,,), + CS_ENTRY(0x0015, EDH,RSA,DES,CBC,SHA,,,), /* bc */ + CS_ENTRY(0x0016, TLS,DHE,RSA,WITH,3DES,EDE,CBC,SHA), + CS_ENTRY(0x0016, DHE,RSA,DES,CBC3,SHA,,,), + CS_ENTRY(0x0016, EDH,RSA,DES,CBC3,SHA,,,), /* bc */ + CS_ENTRY(0x0017, TLS,DH,anon,EXPORT,WITH,RC4,40,MD5), + CS_ENTRY(0x0017, EXP,ADH,RC4,MD5,,,,), + CS_ENTRY(0x0018, TLS,DH,anon,WITH,RC4,128,MD5,), + CS_ENTRY(0x0018, ADH,RC4,MD5,,,,,), + CS_ENTRY(0x0019, TLS,DH,anon,EXPORT,WITH,DES40,CBC,SHA), + CS_ENTRY(0x0019, EXP,ADH,DES,CBC,SHA,,,), + CS_ENTRY(0x001A, TLS,DH,anon,WITH,DES,CBC,SHA,), + CS_ENTRY(0x001A, ADH,DES,CBC,SHA,,,,), + CS_ENTRY(0x001B, TLS,DH,anon,WITH,3DES,EDE,CBC,SHA), + CS_ENTRY(0x001B, ADH,DES,CBC3,SHA,,,,), + CS_ENTRY(0x0030, TLS,DH,DSS,WITH,AES,128,CBC,SHA), + CS_ENTRY(0x0030, DH,DSS,AES128,SHA,,,,), + CS_ENTRY(0x0031, TLS,DH,RSA,WITH,AES,128,CBC,SHA), + CS_ENTRY(0x0031, DH,RSA,AES128,SHA,,,,), + CS_ENTRY(0x0032, TLS,DHE,DSS,WITH,AES,128,CBC,SHA), + CS_ENTRY(0x0032, DHE,DSS,AES128,SHA,,,,), + CS_ENTRY(0x0034, TLS,DH,anon,WITH,AES,128,CBC,SHA), + CS_ENTRY(0x0034, ADH,AES128,SHA,,,,,), + CS_ENTRY(0x0036, TLS,DH,DSS,WITH,AES,256,CBC,SHA), + CS_ENTRY(0x0036, DH,DSS,AES256,SHA,,,,), + CS_ENTRY(0x0037, TLS,DH,RSA,WITH,AES,256,CBC,SHA), + CS_ENTRY(0x0037, DH,RSA,AES256,SHA,,,,), + CS_ENTRY(0x0038, TLS,DHE,DSS,WITH,AES,256,CBC,SHA), + CS_ENTRY(0x0038, DHE,DSS,AES256,SHA,,,,), + CS_ENTRY(0x003A, TLS,DH,anon,WITH,AES,256,CBC,SHA), + CS_ENTRY(0x003A, ADH,AES256,SHA,,,,,), + CS_ENTRY(0x003E, TLS,DH,DSS,WITH,AES,128,CBC,SHA256), + CS_ENTRY(0x003E, DH,DSS,AES128,SHA256,,,,), + CS_ENTRY(0x003F, TLS,DH,RSA,WITH,AES,128,CBC,SHA256), + CS_ENTRY(0x003F, DH,RSA,AES128,SHA256,,,,), + CS_ENTRY(0x0040, TLS,DHE,DSS,WITH,AES,128,CBC,SHA256), + CS_ENTRY(0x0040, DHE,DSS,AES128,SHA256,,,,), + CS_ENTRY(0x0068, TLS,DH,DSS,WITH,AES,256,CBC,SHA256), + CS_ENTRY(0x0068, DH,DSS,AES256,SHA256,,,,), + CS_ENTRY(0x0069, TLS,DH,RSA,WITH,AES,256,CBC,SHA256), + CS_ENTRY(0x0069, DH,RSA,AES256,SHA256,,,,), + CS_ENTRY(0x006A, TLS,DHE,DSS,WITH,AES,256,CBC,SHA256), + CS_ENTRY(0x006A, DHE,DSS,AES256,SHA256,,,,), + CS_ENTRY(0x006C, TLS,DH,anon,WITH,AES,128,CBC,SHA256), + CS_ENTRY(0x006C, ADH,AES128,SHA256,,,,,), + CS_ENTRY(0x006D, TLS,DH,anon,WITH,AES,256,CBC,SHA256), + CS_ENTRY(0x006D, ADH,AES256,SHA256,,,,,), + CS_ENTRY(0x008A, TLS,PSK,WITH,RC4,128,SHA,,), + CS_ENTRY(0x008A, PSK,RC4,SHA,,,,,), + CS_ENTRY(0x008B, TLS,PSK,WITH,3DES,EDE,CBC,SHA,), + CS_ENTRY(0x008B, PSK,3DES,EDE,CBC,SHA,,,), + CS_ENTRY(0x008E, TLS,DHE,PSK,WITH,RC4,128,SHA,), + CS_ENTRY(0x008E, DHE,PSK,RC4,SHA,,,,), + CS_ENTRY(0x008F, TLS,DHE,PSK,WITH,3DES,EDE,CBC,SHA), + CS_ENTRY(0x008F, DHE,PSK,3DES,EDE,CBC,SHA,,), + CS_ENTRY(0x0092, TLS,RSA,PSK,WITH,RC4,128,SHA,), + CS_ENTRY(0x0092, RSA,PSK,RC4,SHA,,,,), + CS_ENTRY(0x0093, TLS,RSA,PSK,WITH,3DES,EDE,CBC,SHA), + CS_ENTRY(0x0093, RSA,PSK,3DES,EDE,CBC,SHA,,), + CS_ENTRY(0x00A0, TLS,DH,RSA,WITH,AES,128,GCM,SHA256), + CS_ENTRY(0x00A0, DH,RSA,AES128,GCM,SHA256,,,), + CS_ENTRY(0x00A1, TLS,DH,RSA,WITH,AES,256,GCM,SHA384), + CS_ENTRY(0x00A1, DH,RSA,AES256,GCM,SHA384,,,), + CS_ENTRY(0x00A2, TLS,DHE,DSS,WITH,AES,128,GCM,SHA256), + CS_ENTRY(0x00A2, DHE,DSS,AES128,GCM,SHA256,,,), + CS_ENTRY(0x00A3, TLS,DHE,DSS,WITH,AES,256,GCM,SHA384), + CS_ENTRY(0x00A3, DHE,DSS,AES256,GCM,SHA384,,,), + CS_ENTRY(0x00A4, TLS,DH,DSS,WITH,AES,128,GCM,SHA256), + CS_ENTRY(0x00A4, DH,DSS,AES128,GCM,SHA256,,,), + CS_ENTRY(0x00A5, TLS,DH,DSS,WITH,AES,256,GCM,SHA384), + CS_ENTRY(0x00A5, DH,DSS,AES256,GCM,SHA384,,,), + CS_ENTRY(0x00A6, TLS,DH,anon,WITH,AES,128,GCM,SHA256), + CS_ENTRY(0x00A6, ADH,AES128,GCM,SHA256,,,,), + CS_ENTRY(0x00A7, TLS,DH,anon,WITH,AES,256,GCM,SHA384), + CS_ENTRY(0x00A7, ADH,AES256,GCM,SHA384,,,,), + CS_ENTRY(0xC002, TLS,ECDH,ECDSA,WITH,RC4,128,SHA,), + CS_ENTRY(0xC002, ECDH,ECDSA,RC4,SHA,,,,), + CS_ENTRY(0xC007, TLS,ECDHE,ECDSA,WITH,RC4,128,SHA,), + CS_ENTRY(0xC007, ECDHE,ECDSA,RC4,SHA,,,,), + CS_ENTRY(0xC00C, TLS,ECDH,RSA,WITH,RC4,128,SHA,), + CS_ENTRY(0xC00C, ECDH,RSA,RC4,SHA,,,,), + CS_ENTRY(0xC011, TLS,ECDHE,RSA,WITH,RC4,128,SHA,), + CS_ENTRY(0xC011, ECDHE,RSA,RC4,SHA,,,,), + CS_ENTRY(0xC015, TLS,ECDH,anon,WITH,NULL,SHA,,), + CS_ENTRY(0xC015, AECDH,NULL,SHA,,,,,), + CS_ENTRY(0xC016, TLS,ECDH,anon,WITH,RC4,128,SHA,), + CS_ENTRY(0xC016, AECDH,RC4,SHA,,,,,), + CS_ENTRY(0xC017, TLS,ECDH,anon,WITH,3DES,EDE,CBC,SHA), + CS_ENTRY(0xC017, AECDH,DES,CBC3,SHA,,,,), + CS_ENTRY(0xC018, TLS,ECDH,anon,WITH,AES,128,CBC,SHA), + CS_ENTRY(0xC018, AECDH,AES128,SHA,,,,,), + CS_ENTRY(0xC019, TLS,ECDH,anon,WITH,AES,256,CBC,SHA), + CS_ENTRY(0xC019, AECDH,AES256,SHA,,,,,), +#endif +#if defined(USE_MBEDTLS) + /* entries marked ns are "non-standard", they are not in OpenSSL */ + CS_ENTRY(0x0041, TLS,RSA,WITH,CAMELLIA,128,CBC,SHA,), + CS_ENTRY(0x0041, CAMELLIA128,SHA,,,,,,), + CS_ENTRY(0x0045, TLS,DHE,RSA,WITH,CAMELLIA,128,CBC,SHA), + CS_ENTRY(0x0045, DHE,RSA,CAMELLIA128,SHA,,,,), + CS_ENTRY(0x0084, TLS,RSA,WITH,CAMELLIA,256,CBC,SHA,), + CS_ENTRY(0x0084, CAMELLIA256,SHA,,,,,,), + CS_ENTRY(0x0088, TLS,DHE,RSA,WITH,CAMELLIA,256,CBC,SHA), + CS_ENTRY(0x0088, DHE,RSA,CAMELLIA256,SHA,,,,), + CS_ENTRY(0x00BA, TLS,RSA,WITH,CAMELLIA,128,CBC,SHA256,), + CS_ENTRY(0x00BA, CAMELLIA128,SHA256,,,,,,), + CS_ENTRY(0x00BE, TLS,DHE,RSA,WITH,CAMELLIA,128,CBC,SHA256), + CS_ENTRY(0x00BE, DHE,RSA,CAMELLIA128,SHA256,,,,), + CS_ENTRY(0x00C0, TLS,RSA,WITH,CAMELLIA,256,CBC,SHA256,), + CS_ENTRY(0x00C0, CAMELLIA256,SHA256,,,,,,), + CS_ENTRY(0x00C4, TLS,DHE,RSA,WITH,CAMELLIA,256,CBC,SHA256), + CS_ENTRY(0x00C4, DHE,RSA,CAMELLIA256,SHA256,,,,), + CS_ENTRY(0xC037, TLS,ECDHE,PSK,WITH,AES,128,CBC,SHA256), + CS_ENTRY(0xC037, ECDHE,PSK,AES128,CBC,SHA256,,,), + CS_ENTRY(0xC038, TLS,ECDHE,PSK,WITH,AES,256,CBC,SHA384), + CS_ENTRY(0xC038, ECDHE,PSK,AES256,CBC,SHA384,,,), + CS_ENTRY(0xC039, TLS,ECDHE,PSK,WITH,NULL,SHA,,), + CS_ENTRY(0xC039, ECDHE,PSK,NULL,SHA,,,,), + CS_ENTRY(0xC03A, TLS,ECDHE,PSK,WITH,NULL,SHA256,,), + CS_ENTRY(0xC03A, ECDHE,PSK,NULL,SHA256,,,,), + CS_ENTRY(0xC03B, TLS,ECDHE,PSK,WITH,NULL,SHA384,,), + CS_ENTRY(0xC03B, ECDHE,PSK,NULL,SHA384,,,,), + CS_ENTRY(0xC03C, TLS,RSA,WITH,ARIA,128,CBC,SHA256,), + CS_ENTRY(0xC03C, ARIA128,SHA256,,,,,,), /* ns */ + CS_ENTRY(0xC03D, TLS,RSA,WITH,ARIA,256,CBC,SHA384,), + CS_ENTRY(0xC03D, ARIA256,SHA384,,,,,,), /* ns */ + CS_ENTRY(0xC044, TLS,DHE,RSA,WITH,ARIA,128,CBC,SHA256), + CS_ENTRY(0xC044, DHE,RSA,ARIA128,SHA256,,,,), /* ns */ + CS_ENTRY(0xC045, TLS,DHE,RSA,WITH,ARIA,256,CBC,SHA384), + CS_ENTRY(0xC045, DHE,RSA,ARIA256,SHA384,,,,), /* ns */ + CS_ENTRY(0xC048, TLS,ECDHE,ECDSA,WITH,ARIA,128,CBC,SHA256), + CS_ENTRY(0xC048, ECDHE,ECDSA,ARIA128,SHA256,,,,), /* ns */ + CS_ENTRY(0xC049, TLS,ECDHE,ECDSA,WITH,ARIA,256,CBC,SHA384), + CS_ENTRY(0xC049, ECDHE,ECDSA,ARIA256,SHA384,,,,), /* ns */ + CS_ENTRY(0xC04A, TLS,ECDH,ECDSA,WITH,ARIA,128,CBC,SHA256), + CS_ENTRY(0xC04A, ECDH,ECDSA,ARIA128,SHA256,,,,), /* ns */ + CS_ENTRY(0xC04B, TLS,ECDH,ECDSA,WITH,ARIA,256,CBC,SHA384), + CS_ENTRY(0xC04B, ECDH,ECDSA,ARIA256,SHA384,,,,), /* ns */ + CS_ENTRY(0xC04C, TLS,ECDHE,RSA,WITH,ARIA,128,CBC,SHA256), + CS_ENTRY(0xC04C, ECDHE,ARIA128,SHA256,,,,,), /* ns */ + CS_ENTRY(0xC04D, TLS,ECDHE,RSA,WITH,ARIA,256,CBC,SHA384), + CS_ENTRY(0xC04D, ECDHE,ARIA256,SHA384,,,,,), /* ns */ + CS_ENTRY(0xC04E, TLS,ECDH,RSA,WITH,ARIA,128,CBC,SHA256), + CS_ENTRY(0xC04E, ECDH,ARIA128,SHA256,,,,,), /* ns */ + CS_ENTRY(0xC04F, TLS,ECDH,RSA,WITH,ARIA,256,CBC,SHA384), + CS_ENTRY(0xC04F, ECDH,ARIA256,SHA384,,,,,), /* ns */ + CS_ENTRY(0xC050, TLS,RSA,WITH,ARIA,128,GCM,SHA256,), + CS_ENTRY(0xC050, ARIA128,GCM,SHA256,,,,,), + CS_ENTRY(0xC051, TLS,RSA,WITH,ARIA,256,GCM,SHA384,), + CS_ENTRY(0xC051, ARIA256,GCM,SHA384,,,,,), + CS_ENTRY(0xC052, TLS,DHE,RSA,WITH,ARIA,128,GCM,SHA256), + CS_ENTRY(0xC052, DHE,RSA,ARIA128,GCM,SHA256,,,), + CS_ENTRY(0xC053, TLS,DHE,RSA,WITH,ARIA,256,GCM,SHA384), + CS_ENTRY(0xC053, DHE,RSA,ARIA256,GCM,SHA384,,,), + CS_ENTRY(0xC05C, TLS,ECDHE,ECDSA,WITH,ARIA,128,GCM,SHA256), + CS_ENTRY(0xC05C, ECDHE,ECDSA,ARIA128,GCM,SHA256,,,), + CS_ENTRY(0xC05D, TLS,ECDHE,ECDSA,WITH,ARIA,256,GCM,SHA384), + CS_ENTRY(0xC05D, ECDHE,ECDSA,ARIA256,GCM,SHA384,,,), + CS_ENTRY(0xC05E, TLS,ECDH,ECDSA,WITH,ARIA,128,GCM,SHA256), + CS_ENTRY(0xC05E, ECDH,ECDSA,ARIA128,GCM,SHA256,,,), /* ns */ + CS_ENTRY(0xC05F, TLS,ECDH,ECDSA,WITH,ARIA,256,GCM,SHA384), + CS_ENTRY(0xC05F, ECDH,ECDSA,ARIA256,GCM,SHA384,,,), /* ns */ + CS_ENTRY(0xC060, TLS,ECDHE,RSA,WITH,ARIA,128,GCM,SHA256), + CS_ENTRY(0xC060, ECDHE,ARIA128,GCM,SHA256,,,,), + CS_ENTRY(0xC061, TLS,ECDHE,RSA,WITH,ARIA,256,GCM,SHA384), + CS_ENTRY(0xC061, ECDHE,ARIA256,GCM,SHA384,,,,), + CS_ENTRY(0xC062, TLS,ECDH,RSA,WITH,ARIA,128,GCM,SHA256), + CS_ENTRY(0xC062, ECDH,ARIA128,GCM,SHA256,,,,), /* ns */ + CS_ENTRY(0xC063, TLS,ECDH,RSA,WITH,ARIA,256,GCM,SHA384), + CS_ENTRY(0xC063, ECDH,ARIA256,GCM,SHA384,,,,), /* ns */ + CS_ENTRY(0xC064, TLS,PSK,WITH,ARIA,128,CBC,SHA256,), + CS_ENTRY(0xC064, PSK,ARIA128,SHA256,,,,,), /* ns */ + CS_ENTRY(0xC065, TLS,PSK,WITH,ARIA,256,CBC,SHA384,), + CS_ENTRY(0xC065, PSK,ARIA256,SHA384,,,,,), /* ns */ + CS_ENTRY(0xC066, TLS,DHE,PSK,WITH,ARIA,128,CBC,SHA256), + CS_ENTRY(0xC066, DHE,PSK,ARIA128,SHA256,,,,), /* ns */ + CS_ENTRY(0xC067, TLS,DHE,PSK,WITH,ARIA,256,CBC,SHA384), + CS_ENTRY(0xC067, DHE,PSK,ARIA256,SHA384,,,,), /* ns */ + CS_ENTRY(0xC068, TLS,RSA,PSK,WITH,ARIA,128,CBC,SHA256), + CS_ENTRY(0xC068, RSA,PSK,ARIA128,SHA256,,,,), /* ns */ + CS_ENTRY(0xC069, TLS,RSA,PSK,WITH,ARIA,256,CBC,SHA384), + CS_ENTRY(0xC069, RSA,PSK,ARIA256,SHA384,,,,), /* ns */ + CS_ENTRY(0xC06A, TLS,PSK,WITH,ARIA,128,GCM,SHA256,), + CS_ENTRY(0xC06A, PSK,ARIA128,GCM,SHA256,,,,), + CS_ENTRY(0xC06B, TLS,PSK,WITH,ARIA,256,GCM,SHA384,), + CS_ENTRY(0xC06B, PSK,ARIA256,GCM,SHA384,,,,), + CS_ENTRY(0xC06C, TLS,DHE,PSK,WITH,ARIA,128,GCM,SHA256), + CS_ENTRY(0xC06C, DHE,PSK,ARIA128,GCM,SHA256,,,), + CS_ENTRY(0xC06D, TLS,DHE,PSK,WITH,ARIA,256,GCM,SHA384), + CS_ENTRY(0xC06D, DHE,PSK,ARIA256,GCM,SHA384,,,), + CS_ENTRY(0xC06E, TLS,RSA,PSK,WITH,ARIA,128,GCM,SHA256), + CS_ENTRY(0xC06E, RSA,PSK,ARIA128,GCM,SHA256,,,), + CS_ENTRY(0xC06F, TLS,RSA,PSK,WITH,ARIA,256,GCM,SHA384), + CS_ENTRY(0xC06F, RSA,PSK,ARIA256,GCM,SHA384,,,), + CS_ENTRY(0xC070, TLS,ECDHE,PSK,WITH,ARIA,128,CBC,SHA256), + CS_ENTRY(0xC070, ECDHE,PSK,ARIA128,SHA256,,,,), /* ns */ + CS_ENTRY(0xC071, TLS,ECDHE,PSK,WITH,ARIA,256,CBC,SHA384), + CS_ENTRY(0xC071, ECDHE,PSK,ARIA256,SHA384,,,,), /* ns */ + CS_ENTRY(0xC072, TLS,ECDHE,ECDSA,WITH,CAMELLIA,128,CBC,SHA256), + CS_ENTRY(0xC072, ECDHE,ECDSA,CAMELLIA128,SHA256,,,,), + CS_ENTRY(0xC073, TLS,ECDHE,ECDSA,WITH,CAMELLIA,256,CBC,SHA384), + CS_ENTRY(0xC073, ECDHE,ECDSA,CAMELLIA256,SHA384,,,,), + CS_ENTRY(0xC074, TLS,ECDH,ECDSA,WITH,CAMELLIA,128,CBC,SHA256), + CS_ENTRY(0xC074, ECDH,ECDSA,CAMELLIA128,SHA256,,,,), /* ns */ + CS_ENTRY(0xC075, TLS,ECDH,ECDSA,WITH,CAMELLIA,256,CBC,SHA384), + CS_ENTRY(0xC075, ECDH,ECDSA,CAMELLIA256,SHA384,,,,), /* ns */ + CS_ENTRY(0xC076, TLS,ECDHE,RSA,WITH,CAMELLIA,128,CBC,SHA256), + CS_ENTRY(0xC076, ECDHE,RSA,CAMELLIA128,SHA256,,,,), + CS_ENTRY(0xC077, TLS,ECDHE,RSA,WITH,CAMELLIA,256,CBC,SHA384), + CS_ENTRY(0xC077, ECDHE,RSA,CAMELLIA256,SHA384,,,,), + CS_ENTRY(0xC078, TLS,ECDH,RSA,WITH,CAMELLIA,128,CBC,SHA256), + CS_ENTRY(0xC078, ECDH,CAMELLIA128,SHA256,,,,,), /* ns */ + CS_ENTRY(0xC079, TLS,ECDH,RSA,WITH,CAMELLIA,256,CBC,SHA384), + CS_ENTRY(0xC079, ECDH,CAMELLIA256,SHA384,,,,,), /* ns */ + CS_ENTRY(0xC07A, TLS,RSA,WITH,CAMELLIA,128,GCM,SHA256,), + CS_ENTRY(0xC07A, CAMELLIA128,GCM,SHA256,,,,,), /* ns */ + CS_ENTRY(0xC07B, TLS,RSA,WITH,CAMELLIA,256,GCM,SHA384,), + CS_ENTRY(0xC07B, CAMELLIA256,GCM,SHA384,,,,,), /* ns */ + CS_ENTRY(0xC07C, TLS,DHE,RSA,WITH,CAMELLIA,128,GCM,SHA256), + CS_ENTRY(0xC07C, DHE,RSA,CAMELLIA128,GCM,SHA256,,,), /* ns */ + CS_ENTRY(0xC07D, TLS,DHE,RSA,WITH,CAMELLIA,256,GCM,SHA384), + CS_ENTRY(0xC07D, DHE,RSA,CAMELLIA256,GCM,SHA384,,,), /* ns */ + CS_ENTRY(0xC086, TLS,ECDHE,ECDSA,WITH,CAMELLIA,128,GCM,SHA256), + CS_ENTRY(0xC086, ECDHE,ECDSA,CAMELLIA128,GCM,SHA256,,,), /* ns */ + CS_ENTRY(0xC087, TLS,ECDHE,ECDSA,WITH,CAMELLIA,256,GCM,SHA384), + CS_ENTRY(0xC087, ECDHE,ECDSA,CAMELLIA256,GCM,SHA384,,,), /* ns */ + CS_ENTRY(0xC088, TLS,ECDH,ECDSA,WITH,CAMELLIA,128,GCM,SHA256), + CS_ENTRY(0xC088, ECDH,ECDSA,CAMELLIA128,GCM,SHA256,,,), /* ns */ + CS_ENTRY(0xC089, TLS,ECDH,ECDSA,WITH,CAMELLIA,256,GCM,SHA384), + CS_ENTRY(0xC089, ECDH,ECDSA,CAMELLIA256,GCM,SHA384,,,), /* ns */ + CS_ENTRY(0xC08A, TLS,ECDHE,RSA,WITH,CAMELLIA,128,GCM,SHA256), + CS_ENTRY(0xC08A, ECDHE,CAMELLIA128,GCM,SHA256,,,,), /* ns */ + CS_ENTRY(0xC08B, TLS,ECDHE,RSA,WITH,CAMELLIA,256,GCM,SHA384), + CS_ENTRY(0xC08B, ECDHE,CAMELLIA256,GCM,SHA384,,,,), /* ns */ + CS_ENTRY(0xC08C, TLS,ECDH,RSA,WITH,CAMELLIA,128,GCM,SHA256), + CS_ENTRY(0xC08C, ECDH,CAMELLIA128,GCM,SHA256,,,,), /* ns */ + CS_ENTRY(0xC08D, TLS,ECDH,RSA,WITH,CAMELLIA,256,GCM,SHA384), + CS_ENTRY(0xC08D, ECDH,CAMELLIA256,GCM,SHA384,,,,), /* ns */ + CS_ENTRY(0xC08E, TLS,PSK,WITH,CAMELLIA,128,GCM,SHA256,), + CS_ENTRY(0xC08E, PSK,CAMELLIA128,GCM,SHA256,,,,), /* ns */ + CS_ENTRY(0xC08F, TLS,PSK,WITH,CAMELLIA,256,GCM,SHA384,), + CS_ENTRY(0xC08F, PSK,CAMELLIA256,GCM,SHA384,,,,), /* ns */ + CS_ENTRY(0xC090, TLS,DHE,PSK,WITH,CAMELLIA,128,GCM,SHA256), + CS_ENTRY(0xC090, DHE,PSK,CAMELLIA128,GCM,SHA256,,,), /* ns */ + CS_ENTRY(0xC091, TLS,DHE,PSK,WITH,CAMELLIA,256,GCM,SHA384), + CS_ENTRY(0xC091, DHE,PSK,CAMELLIA256,GCM,SHA384,,,), /* ns */ + CS_ENTRY(0xC092, TLS,RSA,PSK,WITH,CAMELLIA,128,GCM,SHA256), + CS_ENTRY(0xC092, RSA,PSK,CAMELLIA128,GCM,SHA256,,,), /* ns */ + CS_ENTRY(0xC093, TLS,RSA,PSK,WITH,CAMELLIA,256,GCM,SHA384), + CS_ENTRY(0xC093, RSA,PSK,CAMELLIA256,GCM,SHA384,,,), /* ns */ + CS_ENTRY(0xC094, TLS,PSK,WITH,CAMELLIA,128,CBC,SHA256,), + CS_ENTRY(0xC094, PSK,CAMELLIA128,SHA256,,,,,), + CS_ENTRY(0xC095, TLS,PSK,WITH,CAMELLIA,256,CBC,SHA384,), + CS_ENTRY(0xC095, PSK,CAMELLIA256,SHA384,,,,,), + CS_ENTRY(0xC096, TLS,DHE,PSK,WITH,CAMELLIA,128,CBC,SHA256), + CS_ENTRY(0xC096, DHE,PSK,CAMELLIA128,SHA256,,,,), + CS_ENTRY(0xC097, TLS,DHE,PSK,WITH,CAMELLIA,256,CBC,SHA384), + CS_ENTRY(0xC097, DHE,PSK,CAMELLIA256,SHA384,,,,), + CS_ENTRY(0xC098, TLS,RSA,PSK,WITH,CAMELLIA,128,CBC,SHA256), + CS_ENTRY(0xC098, RSA,PSK,CAMELLIA128,SHA256,,,,), + CS_ENTRY(0xC099, TLS,RSA,PSK,WITH,CAMELLIA,256,CBC,SHA384), + CS_ENTRY(0xC099, RSA,PSK,CAMELLIA256,SHA384,,,,), + CS_ENTRY(0xC09A, TLS,ECDHE,PSK,WITH,CAMELLIA,128,CBC,SHA256), + CS_ENTRY(0xC09A, ECDHE,PSK,CAMELLIA128,SHA256,,,,), + CS_ENTRY(0xC09B, TLS,ECDHE,PSK,WITH,CAMELLIA,256,CBC,SHA384), + CS_ENTRY(0xC09B, ECDHE,PSK,CAMELLIA256,SHA384,,,,), + CS_ENTRY(0xC09E, TLS,DHE,RSA,WITH,AES,128,CCM,), + CS_ENTRY(0xC09E, DHE,RSA,AES128,CCM,,,,), + CS_ENTRY(0xC09F, TLS,DHE,RSA,WITH,AES,256,CCM,), + CS_ENTRY(0xC09F, DHE,RSA,AES256,CCM,,,,), + CS_ENTRY(0xC0A2, TLS,DHE,RSA,WITH,AES,128,CCM,8), + CS_ENTRY(0xC0A2, DHE,RSA,AES128,CCM8,,,,), + CS_ENTRY(0xC0A3, TLS,DHE,RSA,WITH,AES,256,CCM,8), + CS_ENTRY(0xC0A3, DHE,RSA,AES256,CCM8,,,,), + CS_ENTRY(0xC0A4, TLS,PSK,WITH,AES,128,CCM,,), + CS_ENTRY(0xC0A4, PSK,AES128,CCM,,,,,), + CS_ENTRY(0xC0A5, TLS,PSK,WITH,AES,256,CCM,,), + CS_ENTRY(0xC0A5, PSK,AES256,CCM,,,,,), + CS_ENTRY(0xC0A6, TLS,DHE,PSK,WITH,AES,128,CCM,), + CS_ENTRY(0xC0A6, DHE,PSK,AES128,CCM,,,,), + CS_ENTRY(0xC0A7, TLS,DHE,PSK,WITH,AES,256,CCM,), + CS_ENTRY(0xC0A7, DHE,PSK,AES256,CCM,,,,), + CS_ENTRY(0xC0A8, TLS,PSK,WITH,AES,128,CCM,8,), + CS_ENTRY(0xC0A8, PSK,AES128,CCM8,,,,,), + CS_ENTRY(0xC0A9, TLS,PSK,WITH,AES,256,CCM,8,), + CS_ENTRY(0xC0A9, PSK,AES256,CCM8,,,,,), + CS_ENTRY(0xC0AA, TLS,PSK,DHE,WITH,AES,128,CCM,8), + CS_ENTRY(0xC0AA, DHE,PSK,AES128,CCM8,,,,), + CS_ENTRY(0xC0AB, TLS,PSK,DHE,WITH,AES,256,CCM,8), + CS_ENTRY(0xC0AB, DHE,PSK,AES256,CCM8,,,,), + CS_ENTRY(0xCCAA, TLS,DHE,RSA,WITH,CHACHA20,POLY1305,SHA256,), + CS_ENTRY(0xCCAA, DHE,RSA,CHACHA20,POLY1305,,,,), + CS_ENTRY(0xCCAC, TLS,ECDHE,PSK,WITH,CHACHA20,POLY1305,SHA256,), + CS_ENTRY(0xCCAC, ECDHE,PSK,CHACHA20,POLY1305,,,,), + CS_ENTRY(0xCCAD, TLS,DHE,PSK,WITH,CHACHA20,POLY1305,SHA256,), + CS_ENTRY(0xCCAD, DHE,PSK,CHACHA20,POLY1305,,,,), + CS_ENTRY(0xCCAE, TLS,RSA,PSK,WITH,CHACHA20,POLY1305,SHA256,), + CS_ENTRY(0xCCAE, RSA,PSK,CHACHA20,POLY1305,,,,), +#endif +}; +#define CS_LIST_LEN (sizeof(cs_list) / sizeof(cs_list[0])) + +static int cs_str_to_zip(const char *cs_str, size_t cs_len, + uint8_t zip[6]) +{ + uint8_t indexes[8] = {0}; + const char *entry, *cur; + const char *nxt = cs_str; + const char *end = cs_str + cs_len; + char separator = '-'; + int idx, i = 0; + size_t len; + + /* split the cipher string by '-' or '_' */ + if(strncasecompare(cs_str, "TLS", 3)) + separator = '_'; + + do { + if(i == 8) + return -1; + + /* determine the length of the part */ + cur = nxt; + for(; nxt < end && *nxt != '\0' && *nxt != separator; nxt++); + len = nxt - cur; + + /* lookup index for the part (skip empty string at 0) */ + for(idx = 1, entry = cs_txt + 1; idx < CS_TXT_LEN; idx++) { + size_t elen = strlen(entry); + if(elen == len && strncasecompare(entry, cur, len)) + break; + entry += elen + 1; + } + if(idx == CS_TXT_LEN) + return -1; + + indexes[i++] = (uint8_t) idx; + } while(nxt < end && *(nxt++) != '\0'); + + /* zip the 8 indexes into 48 bits */ + zip[0] = (uint8_t) (indexes[0] << 2 | (indexes[1] & 0x3F) >> 4); + zip[1] = (uint8_t) (indexes[1] << 4 | (indexes[2] & 0x3F) >> 2); + zip[2] = (uint8_t) (indexes[2] << 6 | (indexes[3] & 0x3F)); + zip[3] = (uint8_t) (indexes[4] << 2 | (indexes[5] & 0x3F) >> 4); + zip[4] = (uint8_t) (indexes[5] << 4 | (indexes[6] & 0x3F) >> 2); + zip[5] = (uint8_t) (indexes[6] << 6 | (indexes[7] & 0x3F)); + + return 0; +} + +static int cs_zip_to_str(const uint8_t zip[6], + char *buf, size_t buf_size) +{ + uint8_t indexes[8] = {0}; + const char *entry; + char separator = '-'; + int idx, i, r; + size_t len = 0; + + /* unzip the 8 indexes */ + indexes[0] = zip[0] >> 2; + indexes[1] = ((zip[0] << 4) & 0x3F) | zip[1] >> 4; + indexes[2] = ((zip[1] << 2) & 0x3F) | zip[2] >> 6; + indexes[3] = ((zip[2] << 0) & 0x3F); + indexes[4] = zip[3] >> 2; + indexes[5] = ((zip[3] << 4) & 0x3F) | zip[4] >> 4; + indexes[6] = ((zip[4] << 2) & 0x3F) | zip[5] >> 6; + indexes[7] = ((zip[5] << 0) & 0x3F); + + if(indexes[0] == CS_TXT_IDX_TLS) + separator = '_'; + + for(i = 0; i < 8 && indexes[i] != 0 && len < buf_size; i++) { + if(indexes[i] >= CS_TXT_LEN) + return -1; + + /* lookup the part string for the index (skip empty string at 0) */ + for(idx = 1, entry = cs_txt + 1; idx < indexes[i]; idx++) { + size_t elen = strlen(entry); + entry += elen + 1; + } + + /* append the part string to the buffer */ + if(i > 0) + r = msnprintf(&buf[len], buf_size - len, "%c%s", separator, entry); + else + r = msnprintf(&buf[len], buf_size - len, "%s", entry); + + if(r < 0) + return -1; + len += r; + } + + return 0; +} + +uint16_t Curl_cipher_suite_lookup_id(const char *cs_str, size_t cs_len) +{ + size_t i; + uint8_t zip[6]; + + if(cs_len > 0 && cs_str_to_zip(cs_str, cs_len, zip) == 0) { + for(i = 0; i < CS_LIST_LEN; i++) { + if(memcmp(cs_list[i].zip, zip, sizeof(zip)) == 0) + return cs_list[i].id; + } + } + + return 0; +} + +static bool cs_is_separator(char c) +{ + switch(c) { + case ' ': + case '\t': + case ':': + case ',': + case ';': + return true; + default:; + } + return false; +} + +uint16_t Curl_cipher_suite_walk_str(const char **str, const char **end) +{ + /* move string pointer to first non-separator or end of string */ + for(; cs_is_separator(*str[0]); (*str)++); + + /* move end pointer to next separator or end of string */ + for(*end = *str; *end[0] != '\0' && !cs_is_separator(*end[0]); (*end)++); + + return Curl_cipher_suite_lookup_id(*str, *end - *str); +} + +int Curl_cipher_suite_get_str(uint16_t id, char *buf, size_t buf_size, + bool prefer_rfc) +{ + size_t i, j = CS_LIST_LEN; + int r = -1; + + for(i = 0; i < CS_LIST_LEN; i++) { + if(cs_list[i].id != id) + continue; + if((cs_list[i].zip[0] >> 2 != CS_TXT_IDX_TLS) == !prefer_rfc) { + j = i; + break; + } + if(j == CS_LIST_LEN) + j = i; + } + + if(j < CS_LIST_LEN) + r = cs_zip_to_str(cs_list[j].zip, buf, buf_size); + + if(r < 0) + msnprintf(buf, buf_size, "TLS_UNKNOWN_0x%04x", id); + + return r; +} + +#endif /* defined(USE_SECTRANSP) || defined(USE_MBEDTLS) || \ + defined(USE_BEARSSL) || defined(USE_RUSTLS) */ diff --git a/contrib/curl/lib/vtls/cipher_suite.h b/contrib/curl/lib/vtls/cipher_suite.h new file mode 100644 index 0000000000..6d980103a5 --- /dev/null +++ b/contrib/curl/lib/vtls/cipher_suite.h @@ -0,0 +1,48 @@ +#ifndef HEADER_CURL_CIPHER_SUITE_H +#define HEADER_CURL_CIPHER_SUITE_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Jan Venekamp, + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the 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_SECTRANSP) || defined(USE_MBEDTLS) || \ + defined(USE_BEARSSL) || defined(USE_RUSTLS) +#include + +/* Lookup IANA id for cipher suite string, returns 0 if not recognized */ +uint16_t Curl_cipher_suite_lookup_id(const char *cs_str, size_t cs_len); + +/* Walk over cipher suite string, update str and end pointers to next + cipher suite in string, returns IANA id of that suite if recognized */ +uint16_t Curl_cipher_suite_walk_str(const char **str, const char **end); + +/* Copy openssl or RFC name for cipher suite in supplied buffer. + Caller is responsible to supply sufficiently large buffer (size + of 64 should suffice), excess bytes are silently truncated. */ +int Curl_cipher_suite_get_str(uint16_t id, char *buf, size_t buf_size, + bool prefer_rfc); + +#endif /* defined(USE_SECTRANSP) || defined(USE_MBEDTLS) || \ + defined(USE_BEARSSL) || defined(USE_RUSTLS) */ +#endif /* HEADER_CURL_CIPHER_SUITE_H */ diff --git a/contrib/curl/lib/vtls/cyassl.c b/contrib/curl/lib/vtls/cyassl.c deleted file mode 100644 index 5e5947ce0d..0000000000 --- a/contrib/curl/lib/vtls/cyassl.c +++ /dev/null @@ -1,947 +0,0 @@ -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) 1998 - 2017, Daniel Stenberg, , et al. - * - * This software is licensed as described in the file COPYING, which - * you should have received as part of this distribution. The terms - * are also available at https://curl.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. - * - ***************************************************************************/ - -/* - * Source file for all CyaSSL-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_CYASSL - -#define WOLFSSL_OPTIONS_IGNORE_SYS -/* CyaSSL's version.h, which should contain only the version, should come -before all other CyaSSL includes and be immediately followed by build config -aka options.h. https://curl.haxx.se/mail/lib-2015-04/0069.html */ -#include -#if defined(HAVE_CYASSL_OPTIONS_H) && (LIBCYASSL_VERSION_HEX > 0x03004008) -#if defined(CYASSL_API) || defined(WOLFSSL_API) -/* Safety measure. If either is defined some API include was already included -and that's a problem since options.h hasn't been included yet. */ -#error "CyaSSL API was included before the CyaSSL build options." -#endif -#include -#endif - -#ifdef HAVE_LIMITS_H -#include -#endif - -#include "urldata.h" -#include "sendf.h" -#include "inet_pton.h" -#include "vtls.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 -#ifdef HAVE_CYASSL_ERROR_SSL_H -#include -#else -#include -#endif -#include -#include - -#include "cyassl.h" - -/* The last #include files should be: */ -#include "curl_memory.h" -#include "memdebug.h" - -#if LIBCYASSL_VERSION_HEX < 0x02007002 /* < 2.7.2 */ -#define CYASSL_MAX_ERROR_SZ 80 -#endif - -/* To determine what functions are available we rely on one or both of: - - the user's options.h generated by CyaSSL/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 - -/* WOLFSSL_ALLOW_SSLV3 is wolfSSL's build time symbol for enabling SSLv3 in - options.h, but is only seen in >= 3.6.6 since that's when they started - disabling SSLv3 by default. */ -#ifndef WOLFSSL_ALLOW_SSLV3 -#if (LIBCYASSL_VERSION_HEX < 0x03006006) || \ - defined(HAVE_WOLFSSLV3_CLIENT_METHOD) -#define WOLFSSL_ALLOW_SSLV3 -#endif -#endif - -/* HAVE_SUPPORTED_CURVES is wolfSSL's build time symbol for enabling the ECC - supported curve extension in options.h. Note ECC is enabled separately. */ -#ifndef HAVE_SUPPORTED_CURVES -#if defined(HAVE_CYASSL_CTX_USESUPPORTEDCURVE) || \ - defined(HAVE_WOLFSSL_CTX_USESUPPORTEDCURVE) -#define HAVE_SUPPORTED_CURVES -#endif -#endif - -static Curl_recv cyassl_recv; -static Curl_send cyassl_send; - - -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; -} - -/* - * This function loads all the client/CA certificates and CRLs. Setup the TLS - * layer and do all necessary magic. - */ -static CURLcode -cyassl_connect_step1(struct connectdata *conn, - int sockindex) -{ - char error_buffer[CYASSL_MAX_ERROR_SZ]; - char *ciphers; - struct Curl_easy *data = conn->data; - struct ssl_connect_data* conssl = &conn->ssl[sockindex]; - SSL_METHOD* req_method = NULL; - curl_socket_t sockfd = conn->sock[sockindex]; -#ifdef HAVE_SNI - bool sni = FALSE; -#define use_sni(x) sni = (x) -#else -#define use_sni(x) Curl_nop_stmt -#endif - - if(conssl->state == ssl_connection_complete) - return CURLE_OK; - - /* check to see if we've been told to use an explicit SSL/TLS version */ - switch(SSL_CONN_CONFIG(version)) { - case CURL_SSLVERSION_DEFAULT: - case CURL_SSLVERSION_TLSv1: -#if LIBCYASSL_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, "CyaSSL <3.3.0 cannot be configured to use TLS 1.0-1.2, " - "TLS 1.0 is used exclusively\n"); - req_method = TLSv1_client_method(); -#endif - use_sni(TRUE); - break; - case CURL_SSLVERSION_TLSv1_0: - req_method = TLSv1_client_method(); - use_sni(TRUE); - break; - case CURL_SSLVERSION_TLSv1_1: - req_method = TLSv1_1_client_method(); - use_sni(TRUE); - break; - case CURL_SSLVERSION_TLSv1_2: - req_method = TLSv1_2_client_method(); - use_sni(TRUE); - break; - case CURL_SSLVERSION_TLSv1_3: - failf(data, "CyaSSL: TLS 1.3 is not yet supported"); - return CURLE_SSL_CONNECT_ERROR; - case CURL_SSLVERSION_SSLv3: -#ifdef WOLFSSL_ALLOW_SSLV3 - req_method = SSLv3_client_method(); - use_sni(FALSE); -#else - failf(data, "CyaSSL does not support SSLv3"); - return CURLE_NOT_BUILT_IN; -#endif - break; - case CURL_SSLVERSION_SSLv2: - failf(data, "CyaSSL does not support SSLv2"); - return CURLE_SSL_CONNECT_ERROR; - 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(conssl->ctx) - SSL_CTX_free(conssl->ctx); - conssl->ctx = SSL_CTX_new(req_method); - - if(!conssl->ctx) { - failf(data, "SSL: couldn't create a context!"); - return CURLE_OUT_OF_MEMORY; - } - - switch(SSL_CONN_CONFIG(version)) { - case CURL_SSLVERSION_DEFAULT: - case CURL_SSLVERSION_TLSv1: -#if LIBCYASSL_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. We use wolfSSL_CTX_SetMinVersion and not CyaSSL_SetMinVersion - because only the former will work before the user's CTX callback is called. - */ - if((wolfSSL_CTX_SetMinVersion(conssl->ctx, WOLFSSL_TLSV1) != 1) && - (wolfSSL_CTX_SetMinVersion(conssl->ctx, WOLFSSL_TLSV1_1) != 1) && - (wolfSSL_CTX_SetMinVersion(conssl->ctx, WOLFSSL_TLSV1_2) != 1)) { - failf(data, "SSL: couldn't set the minimum protocol version"); - return CURLE_SSL_CONNECT_ERROR; - } -#endif - break; - } - - ciphers = SSL_CONN_CONFIG(cipher_list); - if(ciphers) { - if(!SSL_CTX_set_cipher_list(conssl->ctx, ciphers)) { - failf(data, "failed setting cipher list: %s", ciphers); - return CURLE_SSL_CIPHER; - } - infof(data, "Cipher selection: %s\n", ciphers); - } - -#ifndef NO_FILESYSTEM - /* load trusted cacert */ - if(SSL_CONN_CONFIG(CAfile)) { - if(1 != SSL_CTX_load_verify_locations(conssl->ctx, - SSL_CONN_CONFIG(CAfile), - SSL_CONN_CONFIG(CApath))) { - if(SSL_CONN_CONFIG(verifypeer)) { - /* Fail if we insist on successfully verifying the server. */ - failf(data, "error setting certificate verify locations:\n" - " CAfile: %s\n CApath: %s", - SSL_CONN_CONFIG(CAfile)? - SSL_CONN_CONFIG(CAfile): "none", - SSL_CONN_CONFIG(CApath)? - SSL_CONN_CONFIG(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:\n"); - } - } - else { - /* Everything is fine. */ - infof(data, "successfully set certificate verify locations:\n"); - } - infof(data, - " CAfile: %s\n" - " CApath: %s\n", - SSL_CONN_CONFIG(CAfile) ? SSL_CONN_CONFIG(CAfile): - "none", - SSL_CONN_CONFIG(CApath) ? SSL_CONN_CONFIG(CApath): - "none"); - } - - /* Load the client certificate, and private key */ - if(SSL_SET_OPTION(cert) && SSL_SET_OPTION(key)) { - int file_type = do_file_type(SSL_SET_OPTION(cert_type)); - - if(SSL_CTX_use_certificate_file(conssl->ctx, SSL_SET_OPTION(cert), - 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_SET_OPTION(key_type)); - if(SSL_CTX_use_PrivateKey_file(conssl->ctx, SSL_SET_OPTION(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. */ - SSL_CTX_set_verify(conssl->ctx, - SSL_CONN_CONFIG(verifypeer)?SSL_VERIFY_PEER: - SSL_VERIFY_NONE, - NULL); - -#ifdef HAVE_SNI - if(sni) { - struct in_addr addr4; -#ifdef ENABLE_IPV6 - struct in6_addr addr6; -#endif - const char * const hostname = SSL_IS_PROXY() ? conn->http_proxy.host.name : - conn->host.name; - size_t hostname_len = strlen(hostname); - if((hostname_len < USHRT_MAX) && - (0 == Curl_inet_pton(AF_INET, hostname, &addr4)) && -#ifdef ENABLE_IPV6 - (0 == Curl_inet_pton(AF_INET6, hostname, &addr6)) && -#endif - (CyaSSL_CTX_UseSNI(conssl->ctx, CYASSL_SNI_HOST_NAME, hostname, - (unsigned short)hostname_len) != 1)) { - infof(data, "WARNING: failed to configure server name indication (SNI) " - "TLS extension\n"); - } - } -#endif - -#ifdef HAVE_SUPPORTED_CURVES - /* CyaSSL/wolfSSL does not send the supported ECC curves ext automatically: - https://github.com/wolfSSL/wolfssl/issues/366 - The supported curves below are those also supported by OpenSSL 1.0.2 and - in the same order. */ - CyaSSL_CTX_UseSupportedCurve(conssl->ctx, 0x17); /* secp256r1 */ - CyaSSL_CTX_UseSupportedCurve(conssl->ctx, 0x19); /* secp521r1 */ - CyaSSL_CTX_UseSupportedCurve(conssl->ctx, 0x18); /* secp384r1 */ -#endif - - /* give application a chance to interfere with SSL set up. */ - if(data->set.ssl.fsslctx) { - CURLcode result = CURLE_OK; - result = (*data->set.ssl.fsslctx)(data, conssl->ctx, - data->set.ssl.fsslctxp); - if(result) { - failf(data, "error signaled by ssl ctx callback"); - return result; - } - } -#ifdef NO_FILESYSTEM - else if(SSL_CONN_CONFIG(verifypeer)) { - failf(data, "SSL: Certificates couldn't be loaded because CyaSSL 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(conssl->handle) - SSL_free(conssl->handle); - conssl->handle = SSL_new(conssl->ctx); - if(!conssl->handle) { - failf(data, "SSL: couldn't create a context (handle)!"); - return CURLE_OUT_OF_MEMORY; - } - -#ifdef HAVE_ALPN - if(conn->bits.tls_enable_alpn) { - char protocols[128]; - *protocols = '\0'; - - /* wolfSSL's ALPN protocol name list format is a comma separated string of - protocols in descending order of preference, eg: "h2,http/1.1" */ - -#ifdef USE_NGHTTP2 - if(data->set.httpversion >= CURL_HTTP_VERSION_2) { - strcpy(protocols + strlen(protocols), NGHTTP2_PROTO_VERSION_ID ","); - infof(data, "ALPN, offering %s\n", NGHTTP2_PROTO_VERSION_ID); - } -#endif - - strcpy(protocols + strlen(protocols), ALPN_HTTP_1_1); - infof(data, "ALPN, offering %s\n", ALPN_HTTP_1_1); - - if(wolfSSL_UseALPN(conssl->handle, protocols, - (unsigned)strlen(protocols), - WOLFSSL_ALPN_CONTINUE_ON_MISMATCH) != SSL_SUCCESS) { - failf(data, "SSL: failed setting ALPN protocols"); - return CURLE_SSL_CONNECT_ERROR; - } - } -#endif /* HAVE_ALPN */ - - /* Check if there's a cached ID we can/should use here! */ - if(data->set.general_ssl.sessionid) { - void *ssl_sessionid = NULL; - - Curl_ssl_sessionid_lock(conn); - if(!Curl_ssl_getsessionid(conn, &ssl_sessionid, NULL, sockindex)) { - /* we got a session id, use it! */ - if(!SSL_set_session(conssl->handle, ssl_sessionid)) { - Curl_ssl_sessionid_unlock(conn); - failf(data, "SSL: SSL_set_session failed: %s", - ERR_error_string(SSL_get_error(conssl->handle, 0), - error_buffer)); - return CURLE_SSL_CONNECT_ERROR; - } - /* Informational message */ - infof(data, "SSL re-using session ID\n"); - } - Curl_ssl_sessionid_unlock(conn); - } - - /* pass the raw socket into the SSL layer */ - if(!SSL_set_fd(conssl->handle, (int)sockfd)) { - failf(data, "SSL: SSL_set_fd failed"); - return CURLE_SSL_CONNECT_ERROR; - } - - conssl->connecting_state = ssl_connect_2; - return CURLE_OK; -} - - -static CURLcode -cyassl_connect_step2(struct connectdata *conn, - int sockindex) -{ - int ret = -1; - struct Curl_easy *data = conn->data; - struct ssl_connect_data* conssl = &conn->ssl[sockindex]; - const char * const hostname = SSL_IS_PROXY() ? conn->http_proxy.host.name : - conn->host.name; - const char * const dispname = SSL_IS_PROXY() ? - conn->http_proxy.host.dispname : conn->host.dispname; - const char * const pinnedpubkey = SSL_IS_PROXY() ? - data->set.str[STRING_SSL_PINNEDPUBLICKEY_PROXY] : - data->set.str[STRING_SSL_PINNEDPUBLICKEY_ORIG]; - - conn->recv[sockindex] = cyassl_recv; - conn->send[sockindex] = cyassl_send; - - /* Enable RFC2818 checks */ - if(SSL_CONN_CONFIG(verifyhost)) { - ret = CyaSSL_check_domain_name(conssl->handle, hostname); - if(ret == SSL_FAILURE) - return CURLE_OUT_OF_MEMORY; - } - - ret = SSL_connect(conssl->handle); - if(ret != 1) { - char error_buffer[CYASSL_MAX_ERROR_SZ]; - int detail = SSL_get_error(conssl->handle, ret); - - if(SSL_ERROR_WANT_READ == detail) { - conssl->connecting_state = ssl_connect_2_reading; - return CURLE_OK; - } - else if(SSL_ERROR_WANT_WRITE == detail) { - conssl->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, "\tsubject alt name(s) or common name do not match \"%s\"\n", - dispname); - return CURLE_PEER_FAILED_VERIFICATION; -#else - /* When the CyaSSL_check_domain_name() is used and you desire to continue - * on a DOMAIN_NAME_MISMATCH, i.e. 'conn->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 CyaSSL_check_domain_name() - * in and out based on the 'conn->ssl_config.verifyhost' value. */ - if(SSL_CONN_CONFIG(verifyhost)) { - failf(data, - "\tsubject alt name(s) or common name do not match \"%s\"\n", - dispname); - return CURLE_PEER_FAILED_VERIFICATION; - } - else { - infof(data, - "\tsubject alt name(s) and/or common name do not match \"%s\"\n", - dispname); - return CURLE_OK; - } -#endif - } -#if LIBCYASSL_VERSION_HEX >= 0x02007000 /* 2.7.0 */ - else if(ASN_NO_SIGNER_E == detail) { - if(SSL_CONN_CONFIG(verifypeer)) { - failf(data, "\tCA signer not available for verification\n"); - 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\n"); - } - } -#endif - else { - failf(data, "SSL_connect failed with error %d: %s", detail, - 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; - curl_X509certificate x509_parsed; - curl_asn1Element *pubkey; - CURLcode result; - - x509 = SSL_get_peer_certificate(conssl->handle); - if(!x509) { - failf(data, "SSL: failed retrieving server certificate"); - return CURLE_SSL_PINNEDPUBKEYNOTMATCH; - } - - x509_der = (const char *)CyaSSL_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(conn->bits.tls_enable_alpn) { - int rc; - char *protocol = NULL; - unsigned short protocol_len = 0; - - rc = wolfSSL_ALPN_GetProtocol(conssl->handle, &protocol, &protocol_len); - - if(rc == SSL_SUCCESS) { - infof(data, "ALPN, server accepted to use %.*s\n", protocol_len, - protocol); - - if(protocol_len == ALPN_HTTP_1_1_LENGTH && - !memcmp(protocol, ALPN_HTTP_1_1, ALPN_HTTP_1_1_LENGTH)) - conn->negnpn = CURL_HTTP_VERSION_1_1; -#ifdef USE_NGHTTP2 - else if(data->set.httpversion >= CURL_HTTP_VERSION_2 && - protocol_len == NGHTTP2_PROTO_VERSION_ID_LEN && - !memcmp(protocol, NGHTTP2_PROTO_VERSION_ID, - NGHTTP2_PROTO_VERSION_ID_LEN)) - conn->negnpn = CURL_HTTP_VERSION_2; -#endif - else - infof(data, "ALPN, unrecognized protocol %.*s\n", protocol_len, - protocol); - } - else if(rc == SSL_ALPN_NOT_FOUND) - infof(data, "ALPN, server did not agree to a protocol\n"); - else { - failf(data, "ALPN, failure getting protocol, error %d", rc); - return CURLE_SSL_CONNECT_ERROR; - } - } -#endif /* HAVE_ALPN */ - - conssl->connecting_state = ssl_connect_3; -#if (LIBCYASSL_VERSION_HEX >= 0x03009010) - infof(data, "SSL connection using %s / %s\n", - wolfSSL_get_version(conssl->handle), - wolfSSL_get_cipher_name(conssl->handle)); -#else - infof(data, "SSL connected\n"); -#endif - - return CURLE_OK; -} - - -static CURLcode -cyassl_connect_step3(struct connectdata *conn, - int sockindex) -{ - CURLcode result = CURLE_OK; - struct Curl_easy *data = conn->data; - struct ssl_connect_data *connssl = &conn->ssl[sockindex]; - - DEBUGASSERT(ssl_connect_3 == connssl->connecting_state); - - if(data->set.general_ssl.sessionid) { - bool incache; - SSL_SESSION *our_ssl_sessionid; - void *old_ssl_sessionid = NULL; - - our_ssl_sessionid = SSL_get_session(connssl->handle); - - Curl_ssl_sessionid_lock(conn); - incache = !(Curl_ssl_getsessionid(conn, &old_ssl_sessionid, NULL, - sockindex)); - if(incache) { - if(old_ssl_sessionid != our_ssl_sessionid) { - infof(data, "old SSL session ID is stale, removing\n"); - Curl_ssl_delsessionid(conn, old_ssl_sessionid); - incache = FALSE; - } - } - - if(!incache) { - result = Curl_ssl_addsessionid(conn, our_ssl_sessionid, - 0 /* unknown size */, sockindex); - if(result) { - Curl_ssl_sessionid_unlock(conn); - failf(data, "failed to store ssl session"); - return result; - } - } - Curl_ssl_sessionid_unlock(conn); - } - - connssl->connecting_state = ssl_connect_done; - - return result; -} - - -static ssize_t cyassl_send(struct connectdata *conn, - int sockindex, - const void *mem, - size_t len, - CURLcode *curlcode) -{ - char error_buffer[CYASSL_MAX_ERROR_SZ]; - int memlen = (len > (size_t)INT_MAX) ? INT_MAX : (int)len; - int rc = SSL_write(conn->ssl[sockindex].handle, mem, memlen); - - if(rc < 0) { - int err = SSL_get_error(conn->ssl[sockindex].handle, rc); - - switch(err) { - case SSL_ERROR_WANT_READ: - case SSL_ERROR_WANT_WRITE: - /* there's data pending, re-invoke SSL_write() */ - *curlcode = CURLE_AGAIN; - return -1; - default: - failf(conn->data, "SSL write: %s, errno %d", - ERR_error_string(err, error_buffer), - SOCKERRNO); - *curlcode = CURLE_SEND_ERROR; - return -1; - } - } - return rc; -} - -void Curl_cyassl_close(struct connectdata *conn, int sockindex) -{ - struct ssl_connect_data *conssl = &conn->ssl[sockindex]; - - if(conssl->handle) { - (void)SSL_shutdown(conssl->handle); - SSL_free(conssl->handle); - conssl->handle = NULL; - } - if(conssl->ctx) { - SSL_CTX_free(conssl->ctx); - conssl->ctx = NULL; - } -} - -static ssize_t cyassl_recv(struct connectdata *conn, - int num, - char *buf, - size_t buffersize, - CURLcode *curlcode) -{ - char error_buffer[CYASSL_MAX_ERROR_SZ]; - int buffsize = (buffersize > (size_t)INT_MAX) ? INT_MAX : (int)buffersize; - int nread = SSL_read(conn->ssl[num].handle, buf, buffsize); - - if(nread < 0) { - int err = SSL_get_error(conn->ssl[num].handle, nread); - - switch(err) { - case SSL_ERROR_ZERO_RETURN: /* no more data */ - break; - case SSL_ERROR_WANT_READ: - case SSL_ERROR_WANT_WRITE: - /* there's data pending, re-invoke SSL_read() */ - *curlcode = CURLE_AGAIN; - return -1; - default: - failf(conn->data, "SSL read: %s, errno %d", - ERR_error_string(err, error_buffer), - SOCKERRNO); - *curlcode = CURLE_RECV_ERROR; - return -1; - } - } - return nread; -} - - -void Curl_cyassl_session_free(void *ptr) -{ - (void)ptr; - /* CyaSSL reuses sessions on own, no free */ -} - - -size_t Curl_cyassl_version(char *buffer, size_t size) -{ -#ifdef WOLFSSL_VERSION - return snprintf(buffer, size, "wolfSSL/%s", WOLFSSL_VERSION); -#elif defined(CYASSL_VERSION) - return snprintf(buffer, size, "CyaSSL/%s", CYASSL_VERSION); -#else - return snprintf(buffer, size, "CyaSSL/%s", "<1.8.8"); -#endif -} - - -int Curl_cyassl_init(void) -{ - return (CyaSSL_Init() == SSL_SUCCESS); -} - - -bool Curl_cyassl_data_pending(const struct connectdata* conn, int connindex) -{ - if(conn->ssl[connindex].handle) /* SSL is in use */ - return (0 != SSL_pending(conn->ssl[connindex].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) - */ -int Curl_cyassl_shutdown(struct connectdata *conn, int sockindex) -{ - int retval = 0; - struct ssl_connect_data *connssl = &conn->ssl[sockindex]; - - if(connssl->handle) { - SSL_free(connssl->handle); - connssl->handle = NULL; - } - return retval; -} - - -static CURLcode -cyassl_connect_common(struct connectdata *conn, - int sockindex, - bool nonblocking, - bool *done) -{ - CURLcode result; - struct Curl_easy *data = conn->data; - struct ssl_connect_data *connssl = &conn->ssl[sockindex]; - curl_socket_t sockfd = conn->sock[sockindex]; - time_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; - } - - result = cyassl_connect_step1(conn, sockindex); - 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 */ - 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 = cyassl_connect_step2(conn, sockindex); - 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 = cyassl_connect_step3(conn, sockindex); - if(result) - return result; - } - - if(ssl_connect_done == connssl->connecting_state) { - connssl->state = ssl_connection_complete; - conn->recv[sockindex] = cyassl_recv; - conn->send[sockindex] = cyassl_send; - *done = TRUE; - } - else - *done = FALSE; - - /* Reset our connect state machine */ - connssl->connecting_state = ssl_connect_1; - - return CURLE_OK; -} - - -CURLcode -Curl_cyassl_connect_nonblocking(struct connectdata *conn, - int sockindex, - bool *done) -{ - return cyassl_connect_common(conn, sockindex, TRUE, done); -} - - -CURLcode -Curl_cyassl_connect(struct connectdata *conn, - int sockindex) -{ - CURLcode result; - bool done = FALSE; - - result = cyassl_connect_common(conn, sockindex, FALSE, &done); - if(result) - return result; - - DEBUGASSERT(done); - - return CURLE_OK; -} - -CURLcode Curl_cyassl_random(struct Curl_easy *data, - unsigned char *entropy, - size_t length) -{ - RNG rng; - (void)data; - if(InitRng(&rng)) - return CURLE_FAILED_INIT; - if(length > UINT_MAX) - return CURLE_FAILED_INIT; - if(RNG_GenerateBlock(&rng, entropy, (unsigned)length)) - return CURLE_FAILED_INIT; - return CURLE_OK; -} - -void Curl_cyassl_sha256sum(const unsigned char *tmp, /* input */ - size_t tmplen, - unsigned char *sha256sum /* output */, - size_t unused) -{ - Sha256 SHA256pw; - (void)unused; - InitSha256(&SHA256pw); - Sha256Update(&SHA256pw, tmp, (word32)tmplen); - Sha256Final(&SHA256pw, sha256sum); -} - -#endif diff --git a/contrib/curl/lib/vtls/cyassl.h b/contrib/curl/lib/vtls/cyassl.h deleted file mode 100644 index f47719e4e7..0000000000 --- a/contrib/curl/lib/vtls/cyassl.h +++ /dev/null @@ -1,92 +0,0 @@ -#ifndef HEADER_CURL_CYASSL_H -#define HEADER_CURL_CYASSL_H -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) 1998 - 2017, Daniel Stenberg, , et al. - * - * This software is licensed as described in the file COPYING, which - * you should have received as part of this distribution. The terms - * are also available at https://curl.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. - * - ***************************************************************************/ -#include "curl_setup.h" - -#ifdef USE_CYASSL - -/* 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_CYASSL_GET_PEER_CERTIFICATE) || \ - defined(HAVE_WOLFSSL_GET_PEER_CERTIFICATE) || \ - (defined(OPENSSL_EXTRA) && !defined(NO_CERTS)) -#define KEEP_PEER_CERT -#endif -#endif - -CURLcode Curl_cyassl_connect(struct connectdata *conn, int sockindex); -bool Curl_cyassl_data_pending(const struct connectdata* conn, int connindex); -int Curl_cyassl_shutdown(struct connectdata* conn, int sockindex); - - /* close a SSL connection */ -void Curl_cyassl_close(struct connectdata *conn, int sockindex); - -void Curl_cyassl_session_free(void *ptr); -size_t Curl_cyassl_version(char *buffer, size_t size); -int Curl_cyassl_shutdown(struct connectdata *conn, int sockindex); -int Curl_cyassl_init(void); -CURLcode Curl_cyassl_connect_nonblocking(struct connectdata *conn, - int sockindex, - bool *done); -CURLcode Curl_cyassl_random(struct Curl_easy *data, - unsigned char *entropy, - size_t length); -void Curl_cyassl_sha256sum(const unsigned char *tmp, /* input */ - size_t tmplen, - unsigned char *sha256sum, /* output */ - size_t unused); - -/* Set the API backend definition to CyaSSL */ -#define CURL_SSL_BACKEND CURLSSLBACKEND_CYASSL - -/* this backend supports CURLOPT_SSL_CTX_* */ -#define have_curlssl_ssl_ctx 1 - -#ifdef KEEP_PEER_CERT -/* this backend supports CURLOPT_PINNEDPUBLICKEY */ -#define have_curlssl_pinnedpubkey 1 -#endif - -/* API setup for CyaSSL */ -#define curlssl_init Curl_cyassl_init -#define curlssl_cleanup() Curl_nop_stmt -#define curlssl_connect Curl_cyassl_connect -#define curlssl_connect_nonblocking Curl_cyassl_connect_nonblocking -#define curlssl_session_free(x) Curl_cyassl_session_free(x) -#define curlssl_close_all(x) ((void)x) -#define curlssl_close Curl_cyassl_close -#define curlssl_shutdown(x,y) Curl_cyassl_shutdown(x,y) -#define curlssl_set_engine(x,y) ((void)x, (void)y, CURLE_NOT_BUILT_IN) -#define curlssl_set_engine_default(x) ((void)x, CURLE_NOT_BUILT_IN) -#define curlssl_engines_list(x) ((void)x, (struct curl_slist *)NULL) -#define curlssl_version Curl_cyassl_version -#define curlssl_check_cxn(x) ((void)x, -1) -#define curlssl_data_pending(x,y) Curl_cyassl_data_pending(x,y) -#define curlssl_random(x,y,z) Curl_cyassl_random(x,y,z) -#define curlssl_sha256sum(a,b,c,d) Curl_cyassl_sha256sum(a,b,c,d) - -#endif /* USE_CYASSL */ -#endif /* HEADER_CURL_CYASSL_H */ diff --git a/contrib/curl/lib/vtls/darwinssl.c b/contrib/curl/lib/vtls/darwinssl.c deleted file mode 100644 index 050bf960b5..0000000000 --- a/contrib/curl/lib/vtls/darwinssl.c +++ /dev/null @@ -1,2593 +0,0 @@ -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) 2012 - 2014, Nick Zitzmann, . - * Copyright (C) 2012 - 2017, Daniel Stenberg, , et al. - * - * This software is licensed as described in the file COPYING, which - * you should have received as part of this distribution. The terms - * are also available at https://curl.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. - * - ***************************************************************************/ - -/* - * Source file for all iOS and Mac OS X 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" - -#ifdef USE_DARWINSSL - -#ifdef HAVE_LIMITS_H -#include -#endif - -#include -#include -#include -#include - -/* The Security framework has changed greatly between iOS and different OS X - 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. - - IMPORTANT: If TLS 1.1 and 1.2 support are important for you on OS X, then - you must build this project against the 10.8 SDK or later. */ -#if (TARGET_OS_MAC && !(TARGET_OS_EMBEDDED || TARGET_OS_IPHONE)) - -#if MAC_OS_X_VERSION_MAX_ALLOWED < 1050 -#error "The darwinssl 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_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 -/* 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 by setting the MACOSX_DEPLOYMENT_TARGET - environmental variable.) */ -#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_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_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 darwinssl back-end requires iOS or OS X." -#endif /* (TARGET_OS_MAC && !(TARGET_OS_EMBEDDED || TARGET_OS_IPHONE)) */ - -#if CURL_BUILD_MAC -#include -#endif /* CURL_BUILD_MAC */ - -#include "urldata.h" -#include "sendf.h" -#include "inet_pton.h" -#include "connect.h" -#include "select.h" -#include "vtls.h" -#include "darwinssl.h" -#include "curl_printf.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 - -/* The following two functions were ripped from Apple sample code, - * with some modifications: */ -static OSStatus SocketRead(SSLConnectionRef connection, - void *data, /* owned by - * caller, data - * RETURNED */ - size_t *dataLength) /* IN/OUT */ -{ - size_t bytesToGo = *dataLength; - size_t initLen = bytesToGo; - UInt8 *currData = (UInt8 *)data; - /*int sock = *(int *)connection;*/ - struct ssl_connect_data *connssl = (struct ssl_connect_data *)connection; - int sock = connssl->ssl_sockfd; - OSStatus rtn = noErr; - size_t bytesRead; - ssize_t rrtn; - int theErr; - - *dataLength = 0; - - for(;;) { - bytesRead = 0; - rrtn = read(sock, currData, bytesToGo); - if(rrtn <= 0) { - /* this is guesswork... */ - theErr = errno; - if(rrtn == 0) { /* EOF = server hung up */ - /* the framework will turn this into errSSLClosedNoNotify */ - rtn = errSSLClosedGraceful; - } - else /* do the switch */ - switch(theErr) { - case ENOENT: - /* connection closed */ - rtn = errSSLClosedGraceful; - break; - case ECONNRESET: - rtn = errSSLClosedAbort; - break; - case EAGAIN: - rtn = errSSLWouldBlock; - connssl->ssl_direction = false; - break; - default: - rtn = ioErr; - break; - } - break; - } - else { - bytesRead = rrtn; - } - bytesToGo -= bytesRead; - currData += bytesRead; - - if(bytesToGo == 0) { - /* filled buffer with incoming data, done */ - break; - } - } - *dataLength = initLen - bytesToGo; - - return rtn; -} - -static OSStatus SocketWrite(SSLConnectionRef connection, - const void *data, - size_t *dataLength) /* IN/OUT */ -{ - size_t bytesSent = 0; - /*int sock = *(int *)connection;*/ - struct ssl_connect_data *connssl = (struct ssl_connect_data *)connection; - int sock = connssl->ssl_sockfd; - ssize_t length; - size_t dataLen = *dataLength; - const UInt8 *dataPtr = (UInt8 *)data; - OSStatus ortn; - int theErr; - - *dataLength = 0; - - do { - length = write(sock, - (char *)dataPtr + bytesSent, - dataLen - bytesSent); - } while((length > 0) && - ( (bytesSent += length) < dataLen) ); - - if(length <= 0) { - theErr = errno; - if(theErr == EAGAIN) { - ortn = errSSLWouldBlock; - connssl->ssl_direction = true; - } - else { - ortn = ioErr; - } - } - else { - ortn = noErr; - } - *dataLength = bytesSent; - return ortn; -} - -#ifndef CURL_DISABLE_VERBOSE_STRINGS -CF_INLINE const char *SSLCipherNameForNumber(SSLCipherSuite cipher) -{ - switch(cipher) { - /* SSL version 3.0 */ - case SSL_RSA_WITH_NULL_MD5: - return "SSL_RSA_WITH_NULL_MD5"; - break; - case SSL_RSA_WITH_NULL_SHA: - return "SSL_RSA_WITH_NULL_SHA"; - break; - case SSL_RSA_EXPORT_WITH_RC4_40_MD5: - return "SSL_RSA_EXPORT_WITH_RC4_40_MD5"; - break; - case SSL_RSA_WITH_RC4_128_MD5: - return "SSL_RSA_WITH_RC4_128_MD5"; - break; - case SSL_RSA_WITH_RC4_128_SHA: - return "SSL_RSA_WITH_RC4_128_SHA"; - break; - case SSL_RSA_EXPORT_WITH_RC2_CBC_40_MD5: - return "SSL_RSA_EXPORT_WITH_RC2_CBC_40_MD5"; - break; - case SSL_RSA_WITH_IDEA_CBC_SHA: - return "SSL_RSA_WITH_IDEA_CBC_SHA"; - break; - case SSL_RSA_EXPORT_WITH_DES40_CBC_SHA: - return "SSL_RSA_EXPORT_WITH_DES40_CBC_SHA"; - break; - case SSL_RSA_WITH_DES_CBC_SHA: - return "SSL_RSA_WITH_DES_CBC_SHA"; - break; - case SSL_RSA_WITH_3DES_EDE_CBC_SHA: - return "SSL_RSA_WITH_3DES_EDE_CBC_SHA"; - break; - case SSL_DH_DSS_EXPORT_WITH_DES40_CBC_SHA: - return "SSL_DH_DSS_EXPORT_WITH_DES40_CBC_SHA"; - break; - case SSL_DH_DSS_WITH_DES_CBC_SHA: - return "SSL_DH_DSS_WITH_DES_CBC_SHA"; - break; - case SSL_DH_DSS_WITH_3DES_EDE_CBC_SHA: - return "SSL_DH_DSS_WITH_3DES_EDE_CBC_SHA"; - break; - case SSL_DH_RSA_EXPORT_WITH_DES40_CBC_SHA: - return "SSL_DH_RSA_EXPORT_WITH_DES40_CBC_SHA"; - break; - case SSL_DH_RSA_WITH_DES_CBC_SHA: - return "SSL_DH_RSA_WITH_DES_CBC_SHA"; - break; - case SSL_DH_RSA_WITH_3DES_EDE_CBC_SHA: - return "SSL_DH_RSA_WITH_3DES_EDE_CBC_SHA"; - break; - case SSL_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA: - return "SSL_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA"; - break; - case SSL_DHE_DSS_WITH_DES_CBC_SHA: - return "SSL_DHE_DSS_WITH_DES_CBC_SHA"; - break; - case SSL_DHE_DSS_WITH_3DES_EDE_CBC_SHA: - return "SSL_DHE_DSS_WITH_3DES_EDE_CBC_SHA"; - break; - case SSL_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA: - return "SSL_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA"; - break; - case SSL_DHE_RSA_WITH_DES_CBC_SHA: - return "SSL_DHE_RSA_WITH_DES_CBC_SHA"; - break; - case SSL_DHE_RSA_WITH_3DES_EDE_CBC_SHA: - return "SSL_DHE_RSA_WITH_3DES_EDE_CBC_SHA"; - break; - case SSL_DH_anon_EXPORT_WITH_RC4_40_MD5: - return "SSL_DH_anon_EXPORT_WITH_RC4_40_MD5"; - break; - case SSL_DH_anon_WITH_RC4_128_MD5: - return "SSL_DH_anon_WITH_RC4_128_MD5"; - break; - case SSL_DH_anon_EXPORT_WITH_DES40_CBC_SHA: - return "SSL_DH_anon_EXPORT_WITH_DES40_CBC_SHA"; - break; - case SSL_DH_anon_WITH_DES_CBC_SHA: - return "SSL_DH_anon_WITH_DES_CBC_SHA"; - break; - case SSL_DH_anon_WITH_3DES_EDE_CBC_SHA: - return "SSL_DH_anon_WITH_3DES_EDE_CBC_SHA"; - break; - case SSL_FORTEZZA_DMS_WITH_NULL_SHA: - return "SSL_FORTEZZA_DMS_WITH_NULL_SHA"; - break; - case SSL_FORTEZZA_DMS_WITH_FORTEZZA_CBC_SHA: - return "SSL_FORTEZZA_DMS_WITH_FORTEZZA_CBC_SHA"; - break; - /* TLS 1.0 with AES (RFC 3268) - (Apparently these are used in SSLv3 implementations as well.) */ - case TLS_RSA_WITH_AES_128_CBC_SHA: - return "TLS_RSA_WITH_AES_128_CBC_SHA"; - break; - case TLS_DH_DSS_WITH_AES_128_CBC_SHA: - return "TLS_DH_DSS_WITH_AES_128_CBC_SHA"; - break; - case TLS_DH_RSA_WITH_AES_128_CBC_SHA: - return "TLS_DH_RSA_WITH_AES_128_CBC_SHA"; - break; - case TLS_DHE_DSS_WITH_AES_128_CBC_SHA: - return "TLS_DHE_DSS_WITH_AES_128_CBC_SHA"; - break; - case TLS_DHE_RSA_WITH_AES_128_CBC_SHA: - return "TLS_DHE_RSA_WITH_AES_128_CBC_SHA"; - break; - case TLS_DH_anon_WITH_AES_128_CBC_SHA: - return "TLS_DH_anon_WITH_AES_128_CBC_SHA"; - break; - case TLS_RSA_WITH_AES_256_CBC_SHA: - return "TLS_RSA_WITH_AES_256_CBC_SHA"; - break; - case TLS_DH_DSS_WITH_AES_256_CBC_SHA: - return "TLS_DH_DSS_WITH_AES_256_CBC_SHA"; - break; - case TLS_DH_RSA_WITH_AES_256_CBC_SHA: - return "TLS_DH_RSA_WITH_AES_256_CBC_SHA"; - break; - case TLS_DHE_DSS_WITH_AES_256_CBC_SHA: - return "TLS_DHE_DSS_WITH_AES_256_CBC_SHA"; - break; - case TLS_DHE_RSA_WITH_AES_256_CBC_SHA: - return "TLS_DHE_RSA_WITH_AES_256_CBC_SHA"; - break; - case TLS_DH_anon_WITH_AES_256_CBC_SHA: - return "TLS_DH_anon_WITH_AES_256_CBC_SHA"; - break; - /* SSL version 2.0 */ - case SSL_RSA_WITH_RC2_CBC_MD5: - return "SSL_RSA_WITH_RC2_CBC_MD5"; - break; - case SSL_RSA_WITH_IDEA_CBC_MD5: - return "SSL_RSA_WITH_IDEA_CBC_MD5"; - break; - case SSL_RSA_WITH_DES_CBC_MD5: - return "SSL_RSA_WITH_DES_CBC_MD5"; - break; - case SSL_RSA_WITH_3DES_EDE_CBC_MD5: - return "SSL_RSA_WITH_3DES_EDE_CBC_MD5"; - break; - } - return "SSL_NULL_WITH_NULL_NULL"; -} - -CF_INLINE const char *TLSCipherNameForNumber(SSLCipherSuite cipher) -{ - switch(cipher) { - /* TLS 1.0 with AES (RFC 3268) */ - case TLS_RSA_WITH_AES_128_CBC_SHA: - return "TLS_RSA_WITH_AES_128_CBC_SHA"; - break; - case TLS_DH_DSS_WITH_AES_128_CBC_SHA: - return "TLS_DH_DSS_WITH_AES_128_CBC_SHA"; - break; - case TLS_DH_RSA_WITH_AES_128_CBC_SHA: - return "TLS_DH_RSA_WITH_AES_128_CBC_SHA"; - break; - case TLS_DHE_DSS_WITH_AES_128_CBC_SHA: - return "TLS_DHE_DSS_WITH_AES_128_CBC_SHA"; - break; - case TLS_DHE_RSA_WITH_AES_128_CBC_SHA: - return "TLS_DHE_RSA_WITH_AES_128_CBC_SHA"; - break; - case TLS_DH_anon_WITH_AES_128_CBC_SHA: - return "TLS_DH_anon_WITH_AES_128_CBC_SHA"; - break; - case TLS_RSA_WITH_AES_256_CBC_SHA: - return "TLS_RSA_WITH_AES_256_CBC_SHA"; - break; - case TLS_DH_DSS_WITH_AES_256_CBC_SHA: - return "TLS_DH_DSS_WITH_AES_256_CBC_SHA"; - break; - case TLS_DH_RSA_WITH_AES_256_CBC_SHA: - return "TLS_DH_RSA_WITH_AES_256_CBC_SHA"; - break; - case TLS_DHE_DSS_WITH_AES_256_CBC_SHA: - return "TLS_DHE_DSS_WITH_AES_256_CBC_SHA"; - break; - case TLS_DHE_RSA_WITH_AES_256_CBC_SHA: - return "TLS_DHE_RSA_WITH_AES_256_CBC_SHA"; - break; - case TLS_DH_anon_WITH_AES_256_CBC_SHA: - return "TLS_DH_anon_WITH_AES_256_CBC_SHA"; - break; -#if CURL_BUILD_MAC_10_6 || CURL_BUILD_IOS - /* TLS 1.0 with ECDSA (RFC 4492) */ - case TLS_ECDH_ECDSA_WITH_NULL_SHA: - return "TLS_ECDH_ECDSA_WITH_NULL_SHA"; - break; - case TLS_ECDH_ECDSA_WITH_RC4_128_SHA: - return "TLS_ECDH_ECDSA_WITH_RC4_128_SHA"; - break; - case TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA: - return "TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA"; - break; - case TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA: - return "TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA"; - break; - case TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA: - return "TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA"; - break; - case TLS_ECDHE_ECDSA_WITH_NULL_SHA: - return "TLS_ECDHE_ECDSA_WITH_NULL_SHA"; - break; - case TLS_ECDHE_ECDSA_WITH_RC4_128_SHA: - return "TLS_ECDHE_ECDSA_WITH_RC4_128_SHA"; - break; - case TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA: - return "TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA"; - break; - case TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA: - return "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA"; - break; - case TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA: - return "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA"; - break; - case TLS_ECDH_RSA_WITH_NULL_SHA: - return "TLS_ECDH_RSA_WITH_NULL_SHA"; - break; - case TLS_ECDH_RSA_WITH_RC4_128_SHA: - return "TLS_ECDH_RSA_WITH_RC4_128_SHA"; - break; - case TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA: - return "TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA"; - break; - case TLS_ECDH_RSA_WITH_AES_128_CBC_SHA: - return "TLS_ECDH_RSA_WITH_AES_128_CBC_SHA"; - break; - case TLS_ECDH_RSA_WITH_AES_256_CBC_SHA: - return "TLS_ECDH_RSA_WITH_AES_256_CBC_SHA"; - break; - case TLS_ECDHE_RSA_WITH_NULL_SHA: - return "TLS_ECDHE_RSA_WITH_NULL_SHA"; - break; - case TLS_ECDHE_RSA_WITH_RC4_128_SHA: - return "TLS_ECDHE_RSA_WITH_RC4_128_SHA"; - break; - case TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA: - return "TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA"; - break; - case TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA: - return "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA"; - break; - case TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA: - return "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA"; - break; - case TLS_ECDH_anon_WITH_NULL_SHA: - return "TLS_ECDH_anon_WITH_NULL_SHA"; - break; - case TLS_ECDH_anon_WITH_RC4_128_SHA: - return "TLS_ECDH_anon_WITH_RC4_128_SHA"; - break; - case TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA: - return "TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA"; - break; - case TLS_ECDH_anon_WITH_AES_128_CBC_SHA: - return "TLS_ECDH_anon_WITH_AES_128_CBC_SHA"; - break; - case TLS_ECDH_anon_WITH_AES_256_CBC_SHA: - return "TLS_ECDH_anon_WITH_AES_256_CBC_SHA"; - break; -#endif /* CURL_BUILD_MAC_10_6 || CURL_BUILD_IOS */ -#if CURL_BUILD_MAC_10_8 || CURL_BUILD_IOS - /* TLS 1.2 (RFC 5246) */ - case TLS_RSA_WITH_NULL_MD5: - return "TLS_RSA_WITH_NULL_MD5"; - break; - case TLS_RSA_WITH_NULL_SHA: - return "TLS_RSA_WITH_NULL_SHA"; - break; - case TLS_RSA_WITH_RC4_128_MD5: - return "TLS_RSA_WITH_RC4_128_MD5"; - break; - case TLS_RSA_WITH_RC4_128_SHA: - return "TLS_RSA_WITH_RC4_128_SHA"; - break; - case TLS_RSA_WITH_3DES_EDE_CBC_SHA: - return "TLS_RSA_WITH_3DES_EDE_CBC_SHA"; - break; - case TLS_RSA_WITH_NULL_SHA256: - return "TLS_RSA_WITH_NULL_SHA256"; - break; - case TLS_RSA_WITH_AES_128_CBC_SHA256: - return "TLS_RSA_WITH_AES_128_CBC_SHA256"; - break; - case TLS_RSA_WITH_AES_256_CBC_SHA256: - return "TLS_RSA_WITH_AES_256_CBC_SHA256"; - break; - case TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA: - return "TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA"; - break; - case TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA: - return "TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA"; - break; - case TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA: - return "TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA"; - break; - case TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA: - return "TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA"; - break; - case TLS_DH_DSS_WITH_AES_128_CBC_SHA256: - return "TLS_DH_DSS_WITH_AES_128_CBC_SHA256"; - break; - case TLS_DH_RSA_WITH_AES_128_CBC_SHA256: - return "TLS_DH_RSA_WITH_AES_128_CBC_SHA256"; - break; - case TLS_DHE_DSS_WITH_AES_128_CBC_SHA256: - return "TLS_DHE_DSS_WITH_AES_128_CBC_SHA256"; - break; - case TLS_DHE_RSA_WITH_AES_128_CBC_SHA256: - return "TLS_DHE_RSA_WITH_AES_128_CBC_SHA256"; - break; - case TLS_DH_DSS_WITH_AES_256_CBC_SHA256: - return "TLS_DH_DSS_WITH_AES_256_CBC_SHA256"; - break; - case TLS_DH_RSA_WITH_AES_256_CBC_SHA256: - return "TLS_DH_RSA_WITH_AES_256_CBC_SHA256"; - break; - case TLS_DHE_DSS_WITH_AES_256_CBC_SHA256: - return "TLS_DHE_DSS_WITH_AES_256_CBC_SHA256"; - break; - case TLS_DHE_RSA_WITH_AES_256_CBC_SHA256: - return "TLS_DHE_RSA_WITH_AES_256_CBC_SHA256"; - break; - case TLS_DH_anon_WITH_RC4_128_MD5: - return "TLS_DH_anon_WITH_RC4_128_MD5"; - break; - case TLS_DH_anon_WITH_3DES_EDE_CBC_SHA: - return "TLS_DH_anon_WITH_3DES_EDE_CBC_SHA"; - break; - case TLS_DH_anon_WITH_AES_128_CBC_SHA256: - return "TLS_DH_anon_WITH_AES_128_CBC_SHA256"; - break; - case TLS_DH_anon_WITH_AES_256_CBC_SHA256: - return "TLS_DH_anon_WITH_AES_256_CBC_SHA256"; - break; - /* TLS 1.2 with AES GCM (RFC 5288) */ - case TLS_RSA_WITH_AES_128_GCM_SHA256: - return "TLS_RSA_WITH_AES_128_GCM_SHA256"; - break; - case TLS_RSA_WITH_AES_256_GCM_SHA384: - return "TLS_RSA_WITH_AES_256_GCM_SHA384"; - break; - case TLS_DHE_RSA_WITH_AES_128_GCM_SHA256: - return "TLS_DHE_RSA_WITH_AES_128_GCM_SHA256"; - break; - case TLS_DHE_RSA_WITH_AES_256_GCM_SHA384: - return "TLS_DHE_RSA_WITH_AES_256_GCM_SHA384"; - break; - case TLS_DH_RSA_WITH_AES_128_GCM_SHA256: - return "TLS_DH_RSA_WITH_AES_128_GCM_SHA256"; - break; - case TLS_DH_RSA_WITH_AES_256_GCM_SHA384: - return "TLS_DH_RSA_WITH_AES_256_GCM_SHA384"; - break; - case TLS_DHE_DSS_WITH_AES_128_GCM_SHA256: - return "TLS_DHE_DSS_WITH_AES_128_GCM_SHA256"; - break; - case TLS_DHE_DSS_WITH_AES_256_GCM_SHA384: - return "TLS_DHE_DSS_WITH_AES_256_GCM_SHA384"; - break; - case TLS_DH_DSS_WITH_AES_128_GCM_SHA256: - return "TLS_DH_DSS_WITH_AES_128_GCM_SHA256"; - break; - case TLS_DH_DSS_WITH_AES_256_GCM_SHA384: - return "TLS_DH_DSS_WITH_AES_256_GCM_SHA384"; - break; - case TLS_DH_anon_WITH_AES_128_GCM_SHA256: - return "TLS_DH_anon_WITH_AES_128_GCM_SHA256"; - break; - case TLS_DH_anon_WITH_AES_256_GCM_SHA384: - return "TLS_DH_anon_WITH_AES_256_GCM_SHA384"; - break; - /* TLS 1.2 with elliptic curve ciphers (RFC 5289) */ - case TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256: - return "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256"; - break; - case TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384: - return "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384"; - break; - case TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256: - return "TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256"; - break; - case TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384: - return "TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384"; - break; - case TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256: - return "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256"; - break; - case TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384: - return "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384"; - break; - case TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256: - return "TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256"; - break; - case TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384: - return "TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384"; - break; - case TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256: - return "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256"; - break; - case TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384: - return "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384"; - break; - case TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256: - return "TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256"; - break; - case TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384: - return "TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384"; - break; - case TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256: - return "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256"; - break; - case TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384: - return "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384"; - break; - case TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256: - return "TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256"; - break; - case TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384: - return "TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384"; - break; - case TLS_EMPTY_RENEGOTIATION_INFO_SCSV: - return "TLS_EMPTY_RENEGOTIATION_INFO_SCSV"; - break; -#else - case SSL_RSA_WITH_NULL_MD5: - return "TLS_RSA_WITH_NULL_MD5"; - break; - case SSL_RSA_WITH_NULL_SHA: - return "TLS_RSA_WITH_NULL_SHA"; - break; - case SSL_RSA_WITH_RC4_128_MD5: - return "TLS_RSA_WITH_RC4_128_MD5"; - break; - case SSL_RSA_WITH_RC4_128_SHA: - return "TLS_RSA_WITH_RC4_128_SHA"; - break; - case SSL_RSA_WITH_3DES_EDE_CBC_SHA: - return "TLS_RSA_WITH_3DES_EDE_CBC_SHA"; - break; - case SSL_DH_anon_WITH_RC4_128_MD5: - return "TLS_DH_anon_WITH_RC4_128_MD5"; - break; - case SSL_DH_anon_WITH_3DES_EDE_CBC_SHA: - return "TLS_DH_anon_WITH_3DES_EDE_CBC_SHA"; - break; -#endif /* CURL_BUILD_MAC_10_8 || CURL_BUILD_IOS */ -#if CURL_BUILD_MAC_10_9 || CURL_BUILD_IOS_7 - /* TLS PSK (RFC 4279): */ - case TLS_PSK_WITH_RC4_128_SHA: - return "TLS_PSK_WITH_RC4_128_SHA"; - break; - case TLS_PSK_WITH_3DES_EDE_CBC_SHA: - return "TLS_PSK_WITH_3DES_EDE_CBC_SHA"; - break; - case TLS_PSK_WITH_AES_128_CBC_SHA: - return "TLS_PSK_WITH_AES_128_CBC_SHA"; - break; - case TLS_PSK_WITH_AES_256_CBC_SHA: - return "TLS_PSK_WITH_AES_256_CBC_SHA"; - break; - case TLS_DHE_PSK_WITH_RC4_128_SHA: - return "TLS_DHE_PSK_WITH_RC4_128_SHA"; - break; - case TLS_DHE_PSK_WITH_3DES_EDE_CBC_SHA: - return "TLS_DHE_PSK_WITH_3DES_EDE_CBC_SHA"; - break; - case TLS_DHE_PSK_WITH_AES_128_CBC_SHA: - return "TLS_DHE_PSK_WITH_AES_128_CBC_SHA"; - break; - case TLS_DHE_PSK_WITH_AES_256_CBC_SHA: - return "TLS_DHE_PSK_WITH_AES_256_CBC_SHA"; - break; - case TLS_RSA_PSK_WITH_RC4_128_SHA: - return "TLS_RSA_PSK_WITH_RC4_128_SHA"; - break; - case TLS_RSA_PSK_WITH_3DES_EDE_CBC_SHA: - return "TLS_RSA_PSK_WITH_3DES_EDE_CBC_SHA"; - break; - case TLS_RSA_PSK_WITH_AES_128_CBC_SHA: - return "TLS_RSA_PSK_WITH_AES_128_CBC_SHA"; - break; - case TLS_RSA_PSK_WITH_AES_256_CBC_SHA: - return "TLS_RSA_PSK_WITH_AES_256_CBC_SHA"; - break; - /* More TLS PSK (RFC 4785): */ - case TLS_PSK_WITH_NULL_SHA: - return "TLS_PSK_WITH_NULL_SHA"; - break; - case TLS_DHE_PSK_WITH_NULL_SHA: - return "TLS_DHE_PSK_WITH_NULL_SHA"; - break; - case TLS_RSA_PSK_WITH_NULL_SHA: - return "TLS_RSA_PSK_WITH_NULL_SHA"; - break; - /* Even more TLS PSK (RFC 5487): */ - case TLS_PSK_WITH_AES_128_GCM_SHA256: - return "TLS_PSK_WITH_AES_128_GCM_SHA256"; - break; - case TLS_PSK_WITH_AES_256_GCM_SHA384: - return "TLS_PSK_WITH_AES_256_GCM_SHA384"; - break; - case TLS_DHE_PSK_WITH_AES_128_GCM_SHA256: - return "TLS_DHE_PSK_WITH_AES_128_GCM_SHA256"; - break; - case TLS_DHE_PSK_WITH_AES_256_GCM_SHA384: - return "TLS_DHE_PSK_WITH_AES_256_GCM_SHA384"; - break; - case TLS_RSA_PSK_WITH_AES_128_GCM_SHA256: - return "TLS_RSA_PSK_WITH_AES_128_GCM_SHA256"; - break; - case TLS_RSA_PSK_WITH_AES_256_GCM_SHA384: - return "TLS_PSK_WITH_AES_256_GCM_SHA384"; - break; - case TLS_PSK_WITH_AES_128_CBC_SHA256: - return "TLS_PSK_WITH_AES_128_CBC_SHA256"; - break; - case TLS_PSK_WITH_AES_256_CBC_SHA384: - return "TLS_PSK_WITH_AES_256_CBC_SHA384"; - break; - case TLS_PSK_WITH_NULL_SHA256: - return "TLS_PSK_WITH_NULL_SHA256"; - break; - case TLS_PSK_WITH_NULL_SHA384: - return "TLS_PSK_WITH_NULL_SHA384"; - break; - case TLS_DHE_PSK_WITH_AES_128_CBC_SHA256: - return "TLS_DHE_PSK_WITH_AES_128_CBC_SHA256"; - break; - case TLS_DHE_PSK_WITH_AES_256_CBC_SHA384: - return "TLS_DHE_PSK_WITH_AES_256_CBC_SHA384"; - break; - case TLS_DHE_PSK_WITH_NULL_SHA256: - return "TLS_DHE_PSK_WITH_NULL_SHA256"; - break; - case TLS_DHE_PSK_WITH_NULL_SHA384: - return "TLS_RSA_PSK_WITH_NULL_SHA384"; - break; - case TLS_RSA_PSK_WITH_AES_128_CBC_SHA256: - return "TLS_RSA_PSK_WITH_AES_128_CBC_SHA256"; - break; - case TLS_RSA_PSK_WITH_AES_256_CBC_SHA384: - return "TLS_RSA_PSK_WITH_AES_256_CBC_SHA384"; - break; - case TLS_RSA_PSK_WITH_NULL_SHA256: - return "TLS_RSA_PSK_WITH_NULL_SHA256"; - break; - case TLS_RSA_PSK_WITH_NULL_SHA384: - return "TLS_RSA_PSK_WITH_NULL_SHA384"; - break; -#endif /* CURL_BUILD_MAC_10_9 || CURL_BUILD_IOS_7 */ - } - return "TLS_NULL_WITH_NULL_NULL"; -} -#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 CopyCertSubject(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 != NULL) - 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 != NULL) - 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; -} - -#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; - CFStringRef common_name; - - /* 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 != NULL && kSecClassIdentity != NULL) { - 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; idata; - curl_socket_t sockfd = conn->sock[sockindex]; - struct ssl_connect_data *connssl = &conn->ssl[sockindex]; - const char * const ssl_cafile = SSL_CONN_CONFIG(CAfile); - const bool verifypeer = SSL_CONN_CONFIG(verifypeer); - char * const ssl_cert = SSL_SET_OPTION(cert); - const char * const hostname = SSL_IS_PROXY() ? conn->http_proxy.host.name : - conn->host.name; - const long int port = SSL_IS_PROXY() ? conn->port : conn->remote_port; -#ifdef ENABLE_IPV6 - struct in6_addr addr; -#else - struct in_addr addr; -#endif /* ENABLE_IPV6 */ - 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 */ - -#if CURL_BUILD_MAC_10_8 || CURL_BUILD_IOS - if(SSLCreateContext != NULL) { /* use the newer API if avaialble */ - if(connssl->ssl_ctx) - CFRelease(connssl->ssl_ctx); - connssl->ssl_ctx = SSLCreateContext(NULL, kSSLClientSide, kSSLStreamType); - if(!connssl->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(connssl->ssl_ctx) - (void)SSLDisposeContext(connssl->ssl_ctx); - err = SSLNewContext(false, &(connssl->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(connssl->ssl_ctx) - (void)SSLDisposeContext(connssl->ssl_ctx); - err = SSLNewContext(false, &(connssl->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 */ - connssl->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 != NULL) { - switch(conn->ssl_config.version) { - case CURL_SSLVERSION_DEFAULT: - case CURL_SSLVERSION_TLSv1: - (void)SSLSetProtocolVersionMin(connssl->ssl_ctx, kTLSProtocol1); - (void)SSLSetProtocolVersionMax(connssl->ssl_ctx, kTLSProtocol12); - break; - case CURL_SSLVERSION_TLSv1_0: - (void)SSLSetProtocolVersionMin(connssl->ssl_ctx, kTLSProtocol1); - (void)SSLSetProtocolVersionMax(connssl->ssl_ctx, kTLSProtocol1); - break; - case CURL_SSLVERSION_TLSv1_1: - (void)SSLSetProtocolVersionMin(connssl->ssl_ctx, kTLSProtocol11); - (void)SSLSetProtocolVersionMax(connssl->ssl_ctx, kTLSProtocol11); - break; - case CURL_SSLVERSION_TLSv1_2: - (void)SSLSetProtocolVersionMin(connssl->ssl_ctx, kTLSProtocol12); - (void)SSLSetProtocolVersionMax(connssl->ssl_ctx, kTLSProtocol12); - break; - case CURL_SSLVERSION_TLSv1_3: - failf(data, "DarwinSSL: TLS 1.3 is not yet supported"); - return CURLE_SSL_CONNECT_ERROR; - case CURL_SSLVERSION_SSLv3: - err = SSLSetProtocolVersionMin(connssl->ssl_ctx, kSSLProtocol3); - if(err != noErr) { - failf(data, "Your version of the OS does not support SSLv3"); - return CURLE_SSL_CONNECT_ERROR; - } - (void)SSLSetProtocolVersionMax(connssl->ssl_ctx, kSSLProtocol3); - break; - case CURL_SSLVERSION_SSLv2: - err = SSLSetProtocolVersionMin(connssl->ssl_ctx, kSSLProtocol2); - if(err != noErr) { - failf(data, "Your version of the OS does not support SSLv2"); - return CURLE_SSL_CONNECT_ERROR; - } - (void)SSLSetProtocolVersionMax(connssl->ssl_ctx, kSSLProtocol2); - break; - default: - failf(data, "Unrecognized parameter passed via CURLOPT_SSLVERSION"); - return CURLE_SSL_CONNECT_ERROR; - } - } - else { -#if CURL_SUPPORT_MAC_10_8 - (void)SSLSetProtocolVersionEnabled(connssl->ssl_ctx, - kSSLProtocolAll, - false); - switch(conn->ssl_config.version) { - case CURL_SSLVERSION_DEFAULT: - case CURL_SSLVERSION_TLSv1: - (void)SSLSetProtocolVersionEnabled(connssl->ssl_ctx, - kTLSProtocol1, - true); - (void)SSLSetProtocolVersionEnabled(connssl->ssl_ctx, - kTLSProtocol11, - true); - (void)SSLSetProtocolVersionEnabled(connssl->ssl_ctx, - kTLSProtocol12, - true); - break; - case CURL_SSLVERSION_TLSv1_0: - (void)SSLSetProtocolVersionEnabled(connssl->ssl_ctx, - kTLSProtocol1, - true); - break; - case CURL_SSLVERSION_TLSv1_1: - (void)SSLSetProtocolVersionEnabled(connssl->ssl_ctx, - kTLSProtocol11, - true); - break; - case CURL_SSLVERSION_TLSv1_2: - (void)SSLSetProtocolVersionEnabled(connssl->ssl_ctx, - kTLSProtocol12, - true); - break; - case CURL_SSLVERSION_TLSv1_3: - failf(data, "DarwinSSL: TLS 1.3 is not yet supported"); - return CURLE_SSL_CONNECT_ERROR; - case CURL_SSLVERSION_SSLv3: - err = SSLSetProtocolVersionEnabled(connssl->ssl_ctx, - kSSLProtocol3, - true); - if(err != noErr) { - failf(data, "Your version of the OS does not support SSLv3"); - return CURLE_SSL_CONNECT_ERROR; - } - break; - case CURL_SSLVERSION_SSLv2: - err = SSLSetProtocolVersionEnabled(connssl->ssl_ctx, - kSSLProtocol2, - true); - if(err != noErr) { - failf(data, "Your version of the OS does not support SSLv2"); - return CURLE_SSL_CONNECT_ERROR; - } - break; - default: - failf(data, "Unrecognized parameter passed via CURLOPT_SSLVERSION"); - return CURLE_SSL_CONNECT_ERROR; - } -#endif /* CURL_SUPPORT_MAC_10_8 */ - } -#else - (void)SSLSetProtocolVersionEnabled(connssl->ssl_ctx, kSSLProtocolAll, false); - switch(conn->ssl_config.version) { - case CURL_SSLVERSION_DEFAULT: - case CURL_SSLVERSION_TLSv1: - case CURL_SSLVERSION_TLSv1_0: - (void)SSLSetProtocolVersionEnabled(connssl->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: - err = SSLSetProtocolVersionEnabled(connssl->ssl_ctx, - kSSLProtocol2, - true); - if(err != noErr) { - failf(data, "Your version of the OS does not support SSLv2"); - return CURLE_SSL_CONNECT_ERROR; - } - break; - case CURL_SSLVERSION_SSLv3: - err = SSLSetProtocolVersionEnabled(connssl->ssl_ctx, - kSSLProtocol3, - true); - if(err != noErr) { - failf(data, "Your version of the OS does not support SSLv3"); - return CURLE_SSL_CONNECT_ERROR; - } - break; - default: - failf(data, "Unrecognized parameter passed via CURLOPT_SSLVERSION"); - return CURLE_SSL_CONNECT_ERROR; - } -#endif /* CURL_BUILD_MAC_10_8 || CURL_BUILD_IOS */ - - if(SSL_SET_OPTION(key)) { - infof(data, "WARNING: SSL: CURLOPT_SSLKEY is ignored by Secure " - "Transport. The private key must be in the Keychain.\n"); - } - - if(ssl_cert) { - SecIdentityRef cert_and_key = NULL; - bool is_cert_file = is_file(ssl_cert); - - /* User wants to authenticate with a client cert. Look for it: - If we detect that this is a file on disk, then let's load it. - Otherwise, assume that the user wants to use an identity loaded - from the Keychain. */ - if(is_cert_file) { - if(!SSL_SET_OPTION(cert_type)) - infof(data, "WARNING: SSL: Certificate type not set, assuming " - "PKCS#12 format.\n"); - else if(strncmp(SSL_SET_OPTION(cert_type), "P12", - strlen(SSL_SET_OPTION(cert_type))) != 0) - infof(data, "WARNING: SSL: The Security framework only supports " - "loading identities that are in PKCS#12 format.\n"); - - err = CopyIdentityFromPKCS12File(ssl_cert, - SSL_SET_OPTION(key_passwd), &cert_and_key); - } - else - err = CopyIdentityWithLabel(ssl_cert, &cert_and_key); - - if(err == noErr) { - 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) { - CFStringRef cert_summary = CopyCertSubject(cert); - char cert_summary_c[128]; - - if(cert_summary) { - memset(cert_summary_c, 0, 128); - if(CFStringGetCString(cert_summary, - cert_summary_c, - 128, - kCFStringEncodingUTF8)) { - infof(data, "Client certificate: %s\n", cert_summary_c); - } - CFRelease(cert_summary); - CFRelease(cert); - } - } - certs_c[0] = cert_and_key; - certs = CFArrayCreate(NULL, (const void **)certs_c, 1L, - &kCFTypeArrayCallBacks); - err = SSLSetCertificate(connssl->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 { - switch(err) { - case errSecAuthFailed: case -25264: /* errSecPkcs12VerifyFailure */ - failf(data, "SSL: Incorrect password for the certificate \"%s\" " - "and its private key.", ssl_cert); - 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.", - ssl_cert); - break; - case -25260: /* errSecPassphraseRequired */ - failf(data, "SSL The certificate \"%s\" requires a password.", - ssl_cert); - break; - case errSecItemNotFound: - failf(data, "SSL: Can't find the certificate \"%s\" and its private " - "key in the Keychain.", ssl_cert); - break; - default: - failf(data, "SSL: Can't load the certificate \"%s\" and its private " - "key: OSStatus %d", ssl_cert, 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 != NULL && darwinver_maj >= 13) { -#else - if(SSLSetSessionOption != NULL) { -#endif /* CURL_BUILD_MAC */ - bool break_on_auth = !conn->ssl_config.verifypeer || ssl_cafile; - err = SSLSetSessionOption(connssl->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(connssl->ssl_ctx, - conn->ssl_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(connssl->ssl_ctx, - conn->ssl_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 && verifypeer) { - bool is_cert_file = is_file(ssl_cafile); - - if(!is_cert_file) { - failf(data, "SSL: can't load CA certificate file %s", ssl_cafile); - 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->ssl_config.verifyhost) { - err = SSLSetPeerDomainName(connssl->ssl_ctx, hostname, - strlen(hostname)); - - if(err != noErr) { - infof(data, "WARNING: SSL: SSLSetPeerDomainName() failed: OSStatus %d\n", - err); - } - - if((Curl_inet_pton(AF_INET, hostname, &addr)) - #ifdef ENABLE_IPV6 - || (Curl_inet_pton(AF_INET6, hostname, &addr)) - #endif - ) { - infof(data, "WARNING: using IP address, SNI is being disabled by " - "the OS.\n"); - } - } - - /* 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.) */ - (void)SSLGetNumberSupportedCiphers(connssl->ssl_ctx, &all_ciphers_count); - all_ciphers = malloc(all_ciphers_count*sizeof(SSLCipherSuite)); - allowed_ciphers = malloc(all_ciphers_count*sizeof(SSLCipherSuite)); - if(all_ciphers && allowed_ciphers && - SSLGetSupportedCiphers(connssl->ssl_ctx, all_ciphers, - &all_ciphers_count) == noErr) { - 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 */ - switch(all_ciphers[i]) { - /* Disable NULL ciphersuites: */ - case SSL_NULL_WITH_NULL_NULL: - case SSL_RSA_WITH_NULL_MD5: - case SSL_RSA_WITH_NULL_SHA: - case 0x003B: /* TLS_RSA_WITH_NULL_SHA256 */ - case SSL_FORTEZZA_DMS_WITH_NULL_SHA: - case 0xC001: /* TLS_ECDH_ECDSA_WITH_NULL_SHA */ - case 0xC006: /* TLS_ECDHE_ECDSA_WITH_NULL_SHA */ - case 0xC00B: /* TLS_ECDH_RSA_WITH_NULL_SHA */ - case 0xC010: /* TLS_ECDHE_RSA_WITH_NULL_SHA */ - case 0x002C: /* TLS_PSK_WITH_NULL_SHA */ - case 0x002D: /* TLS_DHE_PSK_WITH_NULL_SHA */ - case 0x002E: /* TLS_RSA_PSK_WITH_NULL_SHA */ - case 0x00B0: /* TLS_PSK_WITH_NULL_SHA256 */ - case 0x00B1: /* TLS_PSK_WITH_NULL_SHA384 */ - case 0x00B4: /* TLS_DHE_PSK_WITH_NULL_SHA256 */ - case 0x00B5: /* TLS_DHE_PSK_WITH_NULL_SHA384 */ - case 0x00B8: /* TLS_RSA_PSK_WITH_NULL_SHA256 */ - case 0x00B9: /* TLS_RSA_PSK_WITH_NULL_SHA384 */ - /* Disable anonymous ciphersuites: */ - case SSL_DH_anon_EXPORT_WITH_RC4_40_MD5: - case SSL_DH_anon_WITH_RC4_128_MD5: - case SSL_DH_anon_EXPORT_WITH_DES40_CBC_SHA: - case SSL_DH_anon_WITH_DES_CBC_SHA: - case SSL_DH_anon_WITH_3DES_EDE_CBC_SHA: - case TLS_DH_anon_WITH_AES_128_CBC_SHA: - case TLS_DH_anon_WITH_AES_256_CBC_SHA: - case 0xC015: /* TLS_ECDH_anon_WITH_NULL_SHA */ - case 0xC016: /* TLS_ECDH_anon_WITH_RC4_128_SHA */ - case 0xC017: /* TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA */ - case 0xC018: /* TLS_ECDH_anon_WITH_AES_128_CBC_SHA */ - case 0xC019: /* TLS_ECDH_anon_WITH_AES_256_CBC_SHA */ - case 0x006C: /* TLS_DH_anon_WITH_AES_128_CBC_SHA256 */ - case 0x006D: /* TLS_DH_anon_WITH_AES_256_CBC_SHA256 */ - case 0x00A6: /* TLS_DH_anon_WITH_AES_128_GCM_SHA256 */ - case 0x00A7: /* TLS_DH_anon_WITH_AES_256_GCM_SHA384 */ - /* Disable weak key ciphersuites: */ - case SSL_RSA_EXPORT_WITH_RC4_40_MD5: - case SSL_RSA_EXPORT_WITH_RC2_CBC_40_MD5: - case SSL_RSA_EXPORT_WITH_DES40_CBC_SHA: - case SSL_DH_DSS_EXPORT_WITH_DES40_CBC_SHA: - case SSL_DH_RSA_EXPORT_WITH_DES40_CBC_SHA: - case SSL_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA: - case SSL_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA: - case SSL_RSA_WITH_DES_CBC_SHA: - case SSL_DH_DSS_WITH_DES_CBC_SHA: - case SSL_DH_RSA_WITH_DES_CBC_SHA: - case SSL_DHE_DSS_WITH_DES_CBC_SHA: - case SSL_DHE_RSA_WITH_DES_CBC_SHA: - /* Disable IDEA: */ - case SSL_RSA_WITH_IDEA_CBC_SHA: - case SSL_RSA_WITH_IDEA_CBC_MD5: - /* Disable RC4: */ - case SSL_RSA_WITH_RC4_128_MD5: - case SSL_RSA_WITH_RC4_128_SHA: - case 0xC002: /* TLS_ECDH_ECDSA_WITH_RC4_128_SHA */ - case 0xC007: /* TLS_ECDHE_ECDSA_WITH_RC4_128_SHA*/ - case 0xC00C: /* TLS_ECDH_RSA_WITH_RC4_128_SHA */ - case 0xC011: /* TLS_ECDHE_RSA_WITH_RC4_128_SHA */ - case 0x008A: /* TLS_PSK_WITH_RC4_128_SHA */ - case 0x008E: /* TLS_DHE_PSK_WITH_RC4_128_SHA */ - case 0x0092: /* TLS_RSA_PSK_WITH_RC4_128_SHA */ - break; - default: /* enable everything else */ - allowed_ciphers[allowed_ciphers_count++] = all_ciphers[i]; - break; - } - } - err = SSLSetEnabledCiphers(connssl->ssl_ctx, allowed_ciphers, - allowed_ciphers_count); - if(err != noErr) { - failf(data, "SSL: SSLSetEnabledCiphers() failed: OSStatus %d", err); - return CURLE_SSL_CONNECT_ERROR; - } - } - else { - Curl_safefree(all_ciphers); - Curl_safefree(allowed_ciphers); - failf(data, "SSL: Failed to allocate memory for allowed ciphers"); - return CURLE_OUT_OF_MEMORY; - } - Curl_safefree(all_ciphers); - Curl_safefree(allowed_ciphers); - -#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 != NULL) { - /* TODO s/data->set.ssl.enable_beast/SSL_SET_OPTION(enable_beast)/g */ - SSLSetSessionOption(connssl->ssl_ctx, kSSLSessionOptionSendOneByteRecord, - !data->set.ssl.enable_beast); - SSLSetSessionOption(connssl->ssl_ctx, kSSLSessionOptionFalseStart, - data->set.ssl.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(data->set.general_ssl.sessionid) { - char *ssl_sessionid; - size_t ssl_sessionid_len; - - Curl_ssl_sessionid_lock(conn); - if(!Curl_ssl_getsessionid(conn, (void **)&ssl_sessionid, - &ssl_sessionid_len, sockindex)) { - /* we got a session id, use it! */ - err = SSLSetPeerID(connssl->ssl_ctx, ssl_sessionid, ssl_sessionid_len); - Curl_ssl_sessionid_unlock(conn); - if(err != noErr) { - failf(data, "SSL: SSLSetPeerID() failed: OSStatus %d", err); - return CURLE_SSL_CONNECT_ERROR; - } - /* Informational message */ - infof(data, "SSL re-using session ID\n"); - } - /* 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:%hu", ssl_cafile, - verifypeer, SSL_CONN_CONFIG(verifyhost), hostname, port); - ssl_sessionid_len = strlen(ssl_sessionid); - - err = SSLSetPeerID(connssl->ssl_ctx, ssl_sessionid, ssl_sessionid_len); - if(err != noErr) { - Curl_ssl_sessionid_unlock(conn); - failf(data, "SSL: SSLSetPeerID() failed: OSStatus %d", err); - return CURLE_SSL_CONNECT_ERROR; - } - - result = Curl_ssl_addsessionid(conn, ssl_sessionid, ssl_sessionid_len, - sockindex); - Curl_ssl_sessionid_unlock(conn); - if(result) { - failf(data, "failed to store ssl session"); - return result; - } - } - } - - err = SSLSetIOFuncs(connssl->ssl_ctx, SocketRead, SocketWrite); - if(err != noErr) { - failf(data, "SSL: SSLSetIOFuncs() failed: OSStatus %d", err); - return CURLE_SSL_CONNECT_ERROR; - } - - /* pass the raw socket into the SSL layers */ - /* We need to store the FD in a constant memory address, because - * SSLSetConnection() will not copy that address. I've found that - * conn->sock[sockindex] may change on its own. */ - connssl->ssl_sockfd = sockfd; - err = SSLSetConnection(connssl->ssl_ctx, connssl); - 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 == NULL) - return 0; - cert_start = strstr(sep_start + 1, "-----"); - if(cert_start == NULL) - return -1; - - cert_start += 5; - - /* Find separator after the end of the certificate. */ - cert_end = strstr(cert_start, "-----"); - if(cert_end == NULL) - return -1; - - sep_end = strstr(cert_end + 1, "-----"); - if(sep_end == NULL) - 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; -} - -static int read_cert(const char *file, unsigned char **out, size_t *outlen) -{ - int fd; - ssize_t n, len = 0, cap = 512; - unsigned char buf[cap], *data; - - fd = open(file, 0); - if(fd < 0) - return -1; - - data = malloc(cap); - if(!data) { - close(fd); - return -1; - } - - for(;;) { - n = read(fd, buf, sizeof(buf)); - if(n < 0) { - close(fd); - free(data); - return -1; - } - else if(n == 0) { - close(fd); - break; - } - - if(len + n >= cap) { - cap *= 2; - data = realloc(data, cap); - if(!data) { - close(fd); - return -1; - } - } - - memcpy(data + len, buf, n); - len += n; - } - data[len] = '\0'; - - *out = data; - *outlen = len; - - return 0; -} - -static int sslerr_to_curlerr(struct Curl_easy *data, int err) -{ - switch(err) { - case errSSLXCertChainInvalid: - failf(data, "SSL certificate problem: Invalid certificate chain"); - return CURLE_SSL_CACERT; - case errSSLUnknownRootCert: - failf(data, "SSL certificate problem: Untrusted root certificate"); - return CURLE_SSL_CACERT; - case errSSLNoRootCert: - failf(data, "SSL certificate problem: No root certificate"); - return CURLE_SSL_CACERT; - case errSSLCertExpired: - failf(data, "SSL certificate problem: Certificate chain had an " - "expired certificate"); - return CURLE_SSL_CACERT; - case errSSLBadCert: - failf(data, "SSL certificate problem: Couldn't understand the server " - "certificate format"); - return CURLE_SSL_CONNECT_ERROR; - case errSSLHostNameMismatch: - failf(data, "SSL certificate peer hostname mismatch"); - return CURLE_PEER_FAILED_VERIFICATION; - default: - failf(data, "SSL unexpected certificate error %d", err); - return CURLE_SSL_CACERT; - } -} - -static int append_cert_to_array(struct Curl_easy *data, - unsigned char *buf, size_t buflen, - CFMutableArrayRef array) -{ - CFDataRef certdata = CFDataCreate(kCFAllocatorDefault, buf, buflen); - if(!certdata) { - failf(data, "SSL: failed to allocate array for CA certificate"); - return CURLE_OUT_OF_MEMORY; - } - - SecCertificateRef cacert = - SecCertificateCreateWithData(kCFAllocatorDefault, certdata); - CFRelease(certdata); - if(!cacert) { - failf(data, "SSL: failed to create SecCertificate from CA certificate"); - return CURLE_SSL_CACERT; - } - - /* Check if cacert is valid. */ - CFStringRef subject = CopyCertSubject(cacert); - if(subject) { - char subject_cbuf[128]; - memset(subject_cbuf, 0, 128); - if(!CFStringGetCString(subject, - subject_cbuf, - 128, - kCFStringEncodingUTF8)) { - CFRelease(cacert); - failf(data, "SSL: invalid CA certificate subject"); - return CURLE_SSL_CACERT; - } - CFRelease(subject); - } - else { - CFRelease(cacert); - failf(data, "SSL: invalid CA certificate"); - return CURLE_SSL_CACERT; - } - - CFArrayAppendValue(array, cacert); - CFRelease(cacert); - - return CURLE_OK; -} - -static int verify_cert(const char *cafile, struct Curl_easy *data, - SSLContextRef ctx) -{ - int n = 0, rc; - long res; - unsigned char *certbuf, *der; - size_t buflen, derlen, offset = 0; - - if(read_cert(cafile, &certbuf, &buflen) < 0) { - failf(data, "SSL: failed to read or invalid CA certificate"); - return CURLE_SSL_CACERT; - } - - /* - * 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. - */ - CFMutableArrayRef array = CFArrayCreateMutable(kCFAllocatorDefault, 0, - &kCFTypeArrayCallBacks); - if(array == NULL) { - free(certbuf); - failf(data, "SSL: out of memory creating CA certificate array"); - return CURLE_OUT_OF_MEMORY; - } - - 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) { - free(certbuf); - CFRelease(array); - failf(data, "SSL: invalid CA certificate #%d (offset %d) in bundle", - n, offset); - return CURLE_SSL_CACERT; - } - 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); - free(certbuf); - if(rc != CURLE_OK) { - CFRelease(array); - return rc; - } - break; - } - else if(res == 0) { - /* No more certificates in the bundle. */ - free(certbuf); - break; - } - - rc = append_cert_to_array(data, der, derlen, array); - free(der); - if(rc != CURLE_OK) { - free(certbuf); - CFRelease(array); - return rc; - } - } - - SecTrustRef trust; - OSStatus ret = SSLCopyPeerTrust(ctx, &trust); - if(trust == NULL) { - failf(data, "SSL: error getting certificate chain"); - CFRelease(array); - return CURLE_OUT_OF_MEMORY; - } - else if(ret != noErr) { - CFRelease(array); - return sslerr_to_curlerr(data, ret); - } - - ret = SecTrustSetAnchorCertificates(trust, array); - if(ret != noErr) { - CFRelease(trust); - return sslerr_to_curlerr(data, ret); - } - ret = SecTrustSetAnchorCertificatesOnly(trust, true); - if(ret != noErr) { - CFRelease(trust); - return sslerr_to_curlerr(data, ret); - } - - SecTrustResultType trust_eval = 0; - ret = SecTrustEvaluate(trust, &trust_eval); - CFRelease(array); - CFRelease(trust); - if(ret != noErr) { - return sslerr_to_curlerr(data, ret); - } - - switch(trust_eval) { - case kSecTrustResultUnspecified: - case kSecTrustResultProceed: - return CURLE_OK; - - case kSecTrustResultRecoverableTrustFailure: - case kSecTrustResultDeny: - default: - failf(data, "SSL: certificate verification failed (result: %d)", - trust_eval); - return CURLE_PEER_FAILED_VERIFICATION; - } -} - -static CURLcode -darwinssl_connect_step2(struct connectdata *conn, int sockindex) -{ - struct Curl_easy *data = conn->data; - struct ssl_connect_data *connssl = &conn->ssl[sockindex]; - OSStatus err; - SSLCipherSuite cipher; - SSLProtocol protocol = 0; - const char * const hostname = SSL_IS_PROXY() ? conn->http_proxy.host.name : - conn->host.name; - - DEBUGASSERT(ssl_connect_2 == connssl->connecting_state - || ssl_connect_2_reading == connssl->connecting_state - || ssl_connect_2_writing == connssl->connecting_state); - - /* Here goes nothing: */ - err = SSLHandshake(connssl->ssl_ctx); - - if(err != noErr) { - switch(err) { - case errSSLWouldBlock: /* they're not done with us yet */ - connssl->connecting_state = connssl->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(SSL_CONN_CONFIG(CAfile) && SSL_CONN_CONFIG(verifypeer)) { - int res = verify_cert(SSL_CONN_CONFIG(CAfile), data, - connssl->ssl_ctx); - if(res != CURLE_OK) - return res; - } - /* the documentation says we need to call SSLHandshake() again */ - return darwinssl_connect_step2(conn, sockindex); - - /* These are all certificate problems with the server: */ - case errSSLXCertChainInvalid: - failf(data, "SSL certificate problem: Invalid certificate chain"); - return CURLE_SSL_CACERT; - case errSSLUnknownRootCert: - failf(data, "SSL certificate problem: Untrusted root certificate"); - return CURLE_SSL_CACERT; - case errSSLNoRootCert: - failf(data, "SSL certificate problem: No root certificate"); - return CURLE_SSL_CACERT; - case errSSLCertExpired: - failf(data, "SSL certificate problem: Certificate chain had an " - "expired certificate"); - return CURLE_SSL_CACERT; - case errSSLBadCert: - failf(data, "SSL certificate problem: Couldn't understand the server " - "certificate format"); - return CURLE_SSL_CONNECT_ERROR; - - /* These are all certificate problems with the client: */ - case errSecAuthFailed: - failf(data, "SSL authentication failed"); - return CURLE_SSL_CONNECT_ERROR; - case errSSLPeerHandshakeFail: - failf(data, "SSL peer handshake failed, the server most likely " - "requires a client certificate to connect"); - return CURLE_SSL_CONNECT_ERROR; - case errSSLPeerUnknownCA: - failf(data, "SSL server rejected the client certificate due to " - "the certificate being signed by an unknown certificate " - "authority"); - return CURLE_SSL_CONNECT_ERROR; - - /* 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", conn->host.dispname); - return CURLE_PEER_FAILED_VERIFICATION; - - /* Generic handshake errors: */ - case errSSLConnectionRefused: - failf(data, "Server dropped the connection during the SSL handshake"); - return CURLE_SSL_CONNECT_ERROR; - case errSSLClosedAbort: - failf(data, "Server aborted the SSL handshake"); - return CURLE_SSL_CONNECT_ERROR; - case errSSLNegotiation: - failf(data, "Could not negotiate an SSL cipher suite with the server"); - return CURLE_SSL_CONNECT_ERROR; - /* Sometimes paramErr happens with buggy ciphers: */ - case paramErr: case errSSLInternal: - failf(data, "Internal SSL engine error encountered during the " - "SSL handshake"); - return CURLE_SSL_CONNECT_ERROR; - case errSSLFatalAlert: - failf(data, "Fatal SSL engine error encountered during the SSL " - "handshake"); - return CURLE_SSL_CONNECT_ERROR; - default: - failf(data, "Unknown SSL protocol error in connection to %s:%d", - hostname, err); - return CURLE_SSL_CONNECT_ERROR; - } - } - else { - /* we have been connected fine, we're not waiting for anything else. */ - connssl->connecting_state = ssl_connect_3; - - /* Informational message */ - (void)SSLGetNegotiatedCipher(connssl->ssl_ctx, &cipher); - (void)SSLGetNegotiatedProtocolVersion(connssl->ssl_ctx, &protocol); - switch(protocol) { - case kSSLProtocol2: - infof(data, "SSL 2.0 connection using %s\n", - SSLCipherNameForNumber(cipher)); - break; - case kSSLProtocol3: - infof(data, "SSL 3.0 connection using %s\n", - SSLCipherNameForNumber(cipher)); - break; - case kTLSProtocol1: - infof(data, "TLS 1.0 connection using %s\n", - TLSCipherNameForNumber(cipher)); - break; -#if CURL_BUILD_MAC_10_8 || CURL_BUILD_IOS - case kTLSProtocol11: - infof(data, "TLS 1.1 connection using %s\n", - TLSCipherNameForNumber(cipher)); - break; - case kTLSProtocol12: - infof(data, "TLS 1.2 connection using %s\n", - TLSCipherNameForNumber(cipher)); - break; -#endif - default: - infof(data, "Unknown protocol connection\n"); - break; - } - - return CURLE_OK; - } -} - -#ifndef CURL_DISABLE_VERBOSE_STRINGS -/* This should be called during step3 of the connection at the earliest */ -static void -show_verbose_server_cert(struct connectdata *conn, - int sockindex) -{ - struct Curl_easy *data = conn->data; - struct ssl_connect_data *connssl = &conn->ssl[sockindex]; - CFStringRef server_cert_summary; - char server_cert_summary_c[128]; - CFArrayRef server_certs = NULL; - SecCertificateRef server_cert; - OSStatus err; - CFIndex i, count; - SecTrustRef trust = NULL; - - if(!connssl->ssl_ctx) - return; - -#if CURL_BUILD_MAC_10_7 || CURL_BUILD_IOS -#if CURL_BUILD_IOS -#pragma unused(server_certs) - err = SSLCopyPeerTrust(connssl->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); - for(i = 0L ; i < count ; i++) { - server_cert = SecTrustGetCertificateAtIndex(trust, i); - server_cert_summary = CopyCertSubject(server_cert); - memset(server_cert_summary_c, 0, 128); - if(CFStringGetCString(server_cert_summary, - server_cert_summary_c, - 128, - kCFStringEncodingUTF8)) { - infof(data, "Server certificate: %s\n", server_cert_summary_c); - } - CFRelease(server_cert_summary); - } - 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(SecTrustEvaluateAsync != NULL) { -#pragma unused(server_certs) - err = SSLCopyPeerTrust(connssl->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); - for(i = 0L ; i < count ; i++) { - server_cert = SecTrustGetCertificateAtIndex(trust, i); - server_cert_summary = CopyCertSubject(server_cert); - memset(server_cert_summary_c, 0, 128); - if(CFStringGetCString(server_cert_summary, - server_cert_summary_c, - 128, - kCFStringEncodingUTF8)) { - infof(data, "Server certificate: %s\n", server_cert_summary_c); - } - CFRelease(server_cert_summary); - } - CFRelease(trust); - } - } - else { -#if CURL_SUPPORT_MAC_10_8 - err = SSLCopyPeerCertificates(connssl->ssl_ctx, &server_certs); - /* Just in case SSLCopyPeerCertificates() returns null too... */ - if(err == noErr && server_certs) { - count = CFArrayGetCount(server_certs); - for(i = 0L ; i < count ; i++) { - server_cert = (SecCertificateRef)CFArrayGetValueAtIndex(server_certs, - i); - - server_cert_summary = CopyCertSubject(server_cert); - memset(server_cert_summary_c, 0, 128); - if(CFStringGetCString(server_cert_summary, - server_cert_summary_c, - 128, - kCFStringEncodingUTF8)) { - infof(data, "Server certificate: %s\n", server_cert_summary_c); - } - CFRelease(server_cert_summary); - } - CFRelease(server_certs); - } -#endif /* CURL_SUPPORT_MAC_10_8 */ - } -#endif /* CURL_BUILD_IOS */ -#else -#pragma unused(trust) - err = SSLCopyPeerCertificates(connssl->ssl_ctx, &server_certs); - if(err == noErr) { - count = CFArrayGetCount(server_certs); - for(i = 0L ; i < count ; i++) { - server_cert = (SecCertificateRef)CFArrayGetValueAtIndex(server_certs, i); - server_cert_summary = CopyCertSubject(server_cert); - memset(server_cert_summary_c, 0, 128); - if(CFStringGetCString(server_cert_summary, - server_cert_summary_c, - 128, - kCFStringEncodingUTF8)) { - infof(data, "Server certificate: %s\n", server_cert_summary_c); - } - CFRelease(server_cert_summary); - } - CFRelease(server_certs); - } -#endif /* CURL_BUILD_MAC_10_7 || CURL_BUILD_IOS */ -} -#endif /* !CURL_DISABLE_VERBOSE_STRINGS */ - -static CURLcode -darwinssl_connect_step3(struct connectdata *conn, - int sockindex) -{ - struct Curl_easy *data = conn->data; - struct ssl_connect_data *connssl = &conn->ssl[sockindex]; - - /* There is no step 3! - * Well, okay, if verbose mode is on, let's print the details of the - * server certificates. */ -#ifndef CURL_DISABLE_VERBOSE_STRINGS - if(data->set.verbose) - show_verbose_server_cert(conn, sockindex); -#endif - - connssl->connecting_state = ssl_connect_done; - return CURLE_OK; -} - -static Curl_recv darwinssl_recv; -static Curl_send darwinssl_send; - -static CURLcode -darwinssl_connect_common(struct connectdata *conn, - int sockindex, - bool nonblocking, - bool *done) -{ - CURLcode result; - struct Curl_easy *data = conn->data; - struct ssl_connect_data *connssl = &conn->ssl[sockindex]; - curl_socket_t sockfd = conn->sock[sockindex]; - long 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; - } - - result = darwinssl_connect_step1(conn, sockindex); - 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 */ - 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 = darwinssl_connect_step2(conn, sockindex); - 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 = darwinssl_connect_step3(conn, sockindex); - if(result) - return result; - } - - if(ssl_connect_done == connssl->connecting_state) { - connssl->state = ssl_connection_complete; - conn->recv[sockindex] = darwinssl_recv; - conn->send[sockindex] = darwinssl_send; - *done = TRUE; - } - else - *done = FALSE; - - /* Reset our connect state machine */ - connssl->connecting_state = ssl_connect_1; - - return CURLE_OK; -} - -CURLcode -Curl_darwinssl_connect_nonblocking(struct connectdata *conn, - int sockindex, - bool *done) -{ - return darwinssl_connect_common(conn, sockindex, TRUE, done); -} - -CURLcode -Curl_darwinssl_connect(struct connectdata *conn, - int sockindex) -{ - CURLcode result; - bool done = FALSE; - - result = darwinssl_connect_common(conn, sockindex, FALSE, &done); - - if(result) - return result; - - DEBUGASSERT(done); - - return CURLE_OK; -} - -void Curl_darwinssl_close(struct connectdata *conn, int sockindex) -{ - struct ssl_connect_data *connssl = &conn->ssl[sockindex]; - - if(connssl->ssl_ctx) { - (void)SSLClose(connssl->ssl_ctx); -#if CURL_BUILD_MAC_10_8 || CURL_BUILD_IOS - if(SSLCreateContext != NULL) - CFRelease(connssl->ssl_ctx); -#if CURL_SUPPORT_MAC_10_8 - else - (void)SSLDisposeContext(connssl->ssl_ctx); -#endif /* CURL_SUPPORT_MAC_10_8 */ -#else - (void)SSLDisposeContext(connssl->ssl_ctx); -#endif /* CURL_BUILD_MAC_10_8 || CURL_BUILD_IOS */ - connssl->ssl_ctx = NULL; - } - connssl->ssl_sockfd = 0; -} - -int Curl_darwinssl_shutdown(struct connectdata *conn, int sockindex) -{ - struct ssl_connect_data *connssl = &conn->ssl[sockindex]; - struct Curl_easy *data = conn->data; - ssize_t nread; - int what; - int rc; - char buf[120]; - - if(!connssl->ssl_ctx) - return 0; - - if(data->set.ftp_ccc != CURLFTPSSL_CCC_ACTIVE) - return 0; - - Curl_darwinssl_close(conn, sockindex); - - rc = 0; - - what = SOCKET_READABLE(conn->sock[sockindex], SSL_SHUTDOWN_TIMEOUT); - - for(;;) { - 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 = read(conn->sock[sockindex], buf, sizeof(buf)); - - if(nread < 0) { - failf(data, "read: %s", strerror(errno)); - rc = -1; - } - - if(nread <= 0) - break; - - what = SOCKET_READABLE(conn->sock[sockindex], 0); - } - - return rc; -} - -void Curl_darwinssl_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 darwinssl_connect_step1... */ - Curl_safefree(ptr); -} - -size_t Curl_darwinssl_version(char *buffer, size_t size) -{ - return snprintf(buffer, size, "SecureTransport"); -} - -/* - * This function uses SSLGetSessionState 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 - */ -int Curl_darwinssl_check_cxn(struct connectdata *conn) -{ - struct ssl_connect_data *connssl = &conn->ssl[FIRSTSOCKET]; - OSStatus err; - SSLSessionState state; - - if(connssl->ssl_ctx) { - err = SSLGetSessionState(connssl->ssl_ctx, &state); - if(err == noErr) - return state == kSSLConnected || state == kSSLHandshake; - return -1; - } - return 0; -} - -bool Curl_darwinssl_data_pending(const struct connectdata *conn, - int connindex) -{ - const struct ssl_connect_data *connssl = &conn->ssl[connindex]; - OSStatus err; - size_t buffer; - - if(connssl->ssl_ctx) { /* SSL is in use */ - err = SSLGetBufferedReadSize(connssl->ssl_ctx, &buffer); - if(err == noErr) - return buffer > 0UL; - return false; - } - else - return false; -} - -CURLcode Curl_darwinssl_random(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; - - 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; -} - -void Curl_darwinssl_md5sum(unsigned char *tmp, /* input */ - size_t tmplen, - unsigned char *md5sum, /* output */ - size_t md5len) -{ - (void)md5len; - (void)CC_MD5(tmp, (CC_LONG)tmplen, md5sum); -} - -bool Curl_darwinssl_false_start(void) -{ -#if CURL_BUILD_MAC_10_9 || CURL_BUILD_IOS_7 - if(SSLSetSessionOption != NULL) - return TRUE; -#endif - return FALSE; -} - -static ssize_t darwinssl_send(struct connectdata *conn, - int sockindex, - const void *mem, - size_t len, - CURLcode *curlcode) -{ - /*struct Curl_easy *data = conn->data;*/ - struct ssl_connect_data *connssl = &conn->ssl[sockindex]; - size_t processed = 0UL; - OSStatus err; - - /* 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(connssl->ssl_write_buffered_length) { - /* Write the buffered data: */ - err = SSLWrite(connssl->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 = connssl->ssl_write_buffered_length; - connssl->ssl_write_buffered_length = 0UL; - break; - case errSSLWouldBlock: /* argh, try again */ - *curlcode = CURLE_AGAIN; - return -1L; - default: - failf(conn->data, "SSLWrite() returned error %d", err); - *curlcode = CURLE_SEND_ERROR; - return -1L; - } - } - else { - /* We've got new data to write: */ - err = SSLWrite(connssl->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 */ - connssl->ssl_write_buffered_length = len; - *curlcode = CURLE_AGAIN; - return -1L; - default: - failf(conn->data, "SSLWrite() returned error %d", err); - *curlcode = CURLE_SEND_ERROR; - return -1L; - } - } - } - return (ssize_t)processed; -} - -static ssize_t darwinssl_recv(struct connectdata *conn, - int num, - char *buf, - size_t buffersize, - CURLcode *curlcode) -{ - /*struct Curl_easy *data = conn->data;*/ - struct ssl_connect_data *connssl = &conn->ssl[num]; - size_t processed = 0UL; - OSStatus err = SSLRead(connssl->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; - break; - - /* 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 -1L; - break; - - default: - failf(conn->data, "SSLRead() return error %d", err); - *curlcode = CURLE_RECV_ERROR; - return -1L; - break; - } - } - return (ssize_t)processed; -} - -#endif /* USE_DARWINSSL */ diff --git a/contrib/curl/lib/vtls/darwinssl.h b/contrib/curl/lib/vtls/darwinssl.h deleted file mode 100644 index 4bd41ca470..0000000000 --- a/contrib/curl/lib/vtls/darwinssl.h +++ /dev/null @@ -1,76 +0,0 @@ -#ifndef HEADER_CURL_DARWINSSL_H -#define HEADER_CURL_DARWINSSL_H -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) 2012 - 2014, Nick Zitzmann, . - * Copyright (C) 2012 - 2017, Daniel Stenberg, , et al. - * - * This software is licensed as described in the file COPYING, which - * you should have received as part of this distribution. The terms - * are also available at https://curl.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. - * - ***************************************************************************/ -#include "curl_setup.h" - -#ifdef USE_DARWINSSL - -CURLcode Curl_darwinssl_connect(struct connectdata *conn, int sockindex); - -CURLcode Curl_darwinssl_connect_nonblocking(struct connectdata *conn, - int sockindex, - bool *done); - -/* close a SSL connection */ -void Curl_darwinssl_close(struct connectdata *conn, int sockindex); - -void Curl_darwinssl_session_free(void *ptr); -size_t Curl_darwinssl_version(char *buffer, size_t size); -int Curl_darwinssl_shutdown(struct connectdata *conn, int sockindex); -int Curl_darwinssl_check_cxn(struct connectdata *conn); -bool Curl_darwinssl_data_pending(const struct connectdata *conn, - int connindex); - -CURLcode Curl_darwinssl_random(unsigned char *entropy, - size_t length); -void Curl_darwinssl_md5sum(unsigned char *tmp, /* input */ - size_t tmplen, - unsigned char *md5sum, /* output */ - size_t md5len); -bool Curl_darwinssl_false_start(void); - -/* Set the API backend definition to SecureTransport */ -#define CURL_SSL_BACKEND CURLSSLBACKEND_DARWINSSL - -/* API setup for SecureTransport */ -#define curlssl_init() (1) -#define curlssl_cleanup() Curl_nop_stmt -#define curlssl_connect Curl_darwinssl_connect -#define curlssl_connect_nonblocking Curl_darwinssl_connect_nonblocking -#define curlssl_session_free(x) Curl_darwinssl_session_free(x) -#define curlssl_close_all(x) ((void)x) -#define curlssl_close Curl_darwinssl_close -#define curlssl_shutdown(x,y) 0 -#define curlssl_set_engine(x,y) ((void)x, (void)y, CURLE_NOT_BUILT_IN) -#define curlssl_set_engine_default(x) ((void)x, CURLE_NOT_BUILT_IN) -#define curlssl_engines_list(x) ((void)x, (struct curl_slist *)NULL) -#define curlssl_version Curl_darwinssl_version -#define curlssl_check_cxn Curl_darwinssl_check_cxn -#define curlssl_data_pending(x,y) Curl_darwinssl_data_pending(x, y) -#define curlssl_random(x,y,z) ((void)x, Curl_darwinssl_random(y,z)) -#define curlssl_md5sum(a,b,c,d) Curl_darwinssl_md5sum(a,b,c,d) -#define curlssl_false_start() Curl_darwinssl_false_start() - -#endif /* USE_DARWINSSL */ -#endif /* HEADER_CURL_DARWINSSL_H */ diff --git a/contrib/curl/lib/vtls/gskit.c b/contrib/curl/lib/vtls/gskit.c deleted file mode 100644 index a0d462b70a..0000000000 --- a/contrib/curl/lib/vtls/gskit.c +++ /dev/null @@ -1,1307 +0,0 @@ -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) 1998 - 2016, Daniel Stenberg, , et al. - * - * This software is licensed as described in the file COPYING, which - * you should have received as part of this distribution. The terms - * are also available at https://curl.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. - * - ***************************************************************************/ - -#include "curl_setup.h" - -#ifdef USE_GSKIT - -#include -#include - -/* Some symbols are undefined/unsupported on OS400 versions < V7R1. */ -#ifndef GSK_SSL_EXTN_SERVERNAME_REQUEST -#define GSK_SSL_EXTN_SERVERNAME_REQUEST 230 -#endif - -#ifndef GSK_TLSV10_CIPHER_SPECS -#define GSK_TLSV10_CIPHER_SPECS 236 -#endif - -#ifndef GSK_TLSV11_CIPHER_SPECS -#define GSK_TLSV11_CIPHER_SPECS 237 -#endif - -#ifndef GSK_TLSV12_CIPHER_SPECS -#define GSK_TLSV12_CIPHER_SPECS 238 -#endif - -#ifndef GSK_PROTOCOL_TLSV11 -#define GSK_PROTOCOL_TLSV11 437 -#endif - -#ifndef GSK_PROTOCOL_TLSV12 -#define GSK_PROTOCOL_TLSV12 438 -#endif - -#ifndef GSK_FALSE -#define GSK_FALSE 0 -#endif - -#ifndef GSK_TRUE -#define GSK_TRUE 1 -#endif - - -#ifdef HAVE_LIMITS_H -# include -#endif - -#include -#include "urldata.h" -#include "sendf.h" -#include "gskit.h" -#include "vtls.h" -#include "connect.h" /* for the connect timeout */ -#include "select.h" -#include "strcase.h" -#include "x509asn1.h" -#include "curl_printf.h" - -#include "curl_memory.h" -/* The last #include file should be: */ -#include "memdebug.h" - - -/* Directions. */ -#define SOS_READ 0x01 -#define SOS_WRITE 0x02 - -/* SSL version flags. */ -#define CURL_GSKPROTO_SSLV2 0 -#define CURL_GSKPROTO_SSLV2_MASK (1 << CURL_GSKPROTO_SSLV2) -#define CURL_GSKPROTO_SSLV3 1 -#define CURL_GSKPROTO_SSLV3_MASK (1 << CURL_GSKPROTO_SSLV3) -#define CURL_GSKPROTO_TLSV10 2 -#define CURL_GSKPROTO_TLSV10_MASK (1 << CURL_GSKPROTO_TLSV10) -#define CURL_GSKPROTO_TLSV11 3 -#define CURL_GSKPROTO_TLSV11_MASK (1 << CURL_GSKPROTO_TLSV11) -#define CURL_GSKPROTO_TLSV12 4 -#define CURL_GSKPROTO_TLSV12_MASK (1 << CURL_GSKPROTO_TLSV12) -#define CURL_GSKPROTO_LAST 5 - - -/* Supported ciphers. */ -typedef struct { - const char *name; /* Cipher name. */ - const char *gsktoken; /* Corresponding token for GSKit String. */ - unsigned int versions; /* SSL version flags. */ -} gskit_cipher; - -static const gskit_cipher ciphertable[] = { - { "null-md5", "01", - CURL_GSKPROTO_SSLV3_MASK | CURL_GSKPROTO_TLSV10_MASK | - CURL_GSKPROTO_TLSV11_MASK | CURL_GSKPROTO_TLSV12_MASK }, - { "null-sha", "02", - CURL_GSKPROTO_SSLV3_MASK | CURL_GSKPROTO_TLSV10_MASK | - CURL_GSKPROTO_TLSV11_MASK | CURL_GSKPROTO_TLSV12_MASK }, - { "exp-rc4-md5", "03", - CURL_GSKPROTO_SSLV3_MASK | CURL_GSKPROTO_TLSV10_MASK }, - { "rc4-md5", "04", - CURL_GSKPROTO_SSLV3_MASK | CURL_GSKPROTO_TLSV10_MASK | - CURL_GSKPROTO_TLSV11_MASK | CURL_GSKPROTO_TLSV12_MASK }, - { "rc4-sha", "05", - CURL_GSKPROTO_SSLV3_MASK | CURL_GSKPROTO_TLSV10_MASK | - CURL_GSKPROTO_TLSV11_MASK | CURL_GSKPROTO_TLSV12_MASK }, - { "exp-rc2-cbc-md5", "06", - CURL_GSKPROTO_SSLV3_MASK | CURL_GSKPROTO_TLSV10_MASK }, - { "exp-des-cbc-sha", "09", - CURL_GSKPROTO_SSLV3_MASK | CURL_GSKPROTO_TLSV10_MASK | - CURL_GSKPROTO_TLSV11_MASK }, - { "des-cbc3-sha", "0A", - CURL_GSKPROTO_SSLV3_MASK | CURL_GSKPROTO_TLSV10_MASK | - CURL_GSKPROTO_TLSV11_MASK | CURL_GSKPROTO_TLSV12_MASK }, - { "aes128-sha", "2F", - CURL_GSKPROTO_TLSV10_MASK | CURL_GSKPROTO_TLSV11_MASK | - CURL_GSKPROTO_TLSV12_MASK }, - { "aes256-sha", "35", - CURL_GSKPROTO_TLSV10_MASK | CURL_GSKPROTO_TLSV11_MASK | - CURL_GSKPROTO_TLSV12_MASK }, - { "null-sha256", "3B", CURL_GSKPROTO_TLSV12_MASK }, - { "aes128-sha256", "3C", CURL_GSKPROTO_TLSV12_MASK }, - { "aes256-sha256", "3D", CURL_GSKPROTO_TLSV12_MASK }, - { "aes128-gcm-sha256", - "9C", CURL_GSKPROTO_TLSV12_MASK }, - { "aes256-gcm-sha384", - "9D", CURL_GSKPROTO_TLSV12_MASK }, - { "rc4-md5", "1", CURL_GSKPROTO_SSLV2_MASK }, - { "exp-rc4-md5", "2", CURL_GSKPROTO_SSLV2_MASK }, - { "rc2-md5", "3", CURL_GSKPROTO_SSLV2_MASK }, - { "exp-rc2-md5", "4", CURL_GSKPROTO_SSLV2_MASK }, - { "des-cbc-md5", "6", CURL_GSKPROTO_SSLV2_MASK }, - { "des-cbc3-md5", "7", CURL_GSKPROTO_SSLV2_MASK }, - { (const char *) NULL, (const char *) NULL, 0 } -}; - - -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 gskit_status(struct Curl_easy *data, int rc, - const char *procname, CURLcode defcode) -{ - /* Process GSKit status and map it to a CURLcode. */ - switch(rc) { - case GSK_OK: - case GSK_OS400_ASYNCHRONOUS_SOC_INIT: - return CURLE_OK; - case GSK_KEYRING_OPEN_ERROR: - case GSK_OS400_ERROR_NO_ACCESS: - return CURLE_SSL_CACERT_BADFILE; - case GSK_INSUFFICIENT_STORAGE: - return CURLE_OUT_OF_MEMORY; - case GSK_ERROR_BAD_V2_CIPHER: - case GSK_ERROR_BAD_V3_CIPHER: - case GSK_ERROR_NO_CIPHERS: - return CURLE_SSL_CIPHER; - case GSK_OS400_ERROR_NOT_TRUSTED_ROOT: - case GSK_ERROR_CERT_VALIDATION: - return CURLE_PEER_FAILED_VERIFICATION; - case GSK_OS400_ERROR_TIMED_OUT: - return CURLE_OPERATION_TIMEDOUT; - case GSK_WOULD_BLOCK: - return CURLE_AGAIN; - case GSK_OS400_ERROR_NOT_REGISTERED: - break; - case GSK_ERROR_IO: - switch(errno) { - case ENOMEM: - return CURLE_OUT_OF_MEMORY; - default: - failf(data, "%s I/O error: %s", procname, strerror(errno)); - break; - } - break; - default: - failf(data, "%s: %s", procname, gsk_strerror(rc)); - break; - } - return defcode; -} - - -static CURLcode set_enum(struct Curl_easy *data, gsk_handle h, - GSK_ENUM_ID id, GSK_ENUM_VALUE value, bool unsupported_ok) -{ - int rc = gsk_attribute_set_enum(h, id, value); - - switch(rc) { - case GSK_OK: - return CURLE_OK; - case GSK_ERROR_IO: - failf(data, "gsk_attribute_set_enum() I/O error: %s", strerror(errno)); - break; - case GSK_ATTRIBUTE_INVALID_ID: - if(unsupported_ok) - return CURLE_UNSUPPORTED_PROTOCOL; - default: - failf(data, "gsk_attribute_set_enum(): %s", gsk_strerror(rc)); - break; - } - return CURLE_SSL_CONNECT_ERROR; -} - - -static CURLcode set_buffer(struct Curl_easy *data, gsk_handle h, - GSK_BUF_ID id, const char *buffer, bool unsupported_ok) -{ - int rc = gsk_attribute_set_buffer(h, id, buffer, 0); - - switch(rc) { - case GSK_OK: - return CURLE_OK; - case GSK_ERROR_IO: - failf(data, "gsk_attribute_set_buffer() I/O error: %s", strerror(errno)); - break; - case GSK_ATTRIBUTE_INVALID_ID: - if(unsupported_ok) - return CURLE_UNSUPPORTED_PROTOCOL; - default: - failf(data, "gsk_attribute_set_buffer(): %s", gsk_strerror(rc)); - break; - } - return CURLE_SSL_CONNECT_ERROR; -} - - -static CURLcode set_numeric(struct Curl_easy *data, - gsk_handle h, GSK_NUM_ID id, int value) -{ - int rc = gsk_attribute_set_numeric_value(h, id, value); - - switch(rc) { - case GSK_OK: - return CURLE_OK; - case GSK_ERROR_IO: - failf(data, "gsk_attribute_set_numeric_value() I/O error: %s", - strerror(errno)); - break; - default: - failf(data, "gsk_attribute_set_numeric_value(): %s", gsk_strerror(rc)); - break; - } - return CURLE_SSL_CONNECT_ERROR; -} - - -static CURLcode set_callback(struct Curl_easy *data, - gsk_handle h, GSK_CALLBACK_ID id, void *info) -{ - int rc = gsk_attribute_set_callback(h, id, info); - - switch(rc) { - case GSK_OK: - return CURLE_OK; - case GSK_ERROR_IO: - failf(data, "gsk_attribute_set_callback() I/O error: %s", strerror(errno)); - break; - default: - failf(data, "gsk_attribute_set_callback(): %s", gsk_strerror(rc)); - break; - } - return CURLE_SSL_CONNECT_ERROR; -} - - -static CURLcode set_ciphers(struct connectdata *conn, - gsk_handle h, unsigned int *protoflags) -{ - struct Curl_easy *data = conn->data; - const char *cipherlist = SSL_CONN_CONFIG(cipher_list); - const char *clp; - const gskit_cipher *ctp; - int i; - int l; - bool unsupported; - CURLcode result; - struct { - char *buf; - char *ptr; - } ciphers[CURL_GSKPROTO_LAST]; - - /* Compile cipher list into GSKit-compatible cipher lists. */ - - if(!cipherlist) - return CURLE_OK; - while(is_separator(*cipherlist)) /* Skip initial separators. */ - cipherlist++; - if(!*cipherlist) - return CURLE_OK; - - /* We allocate GSKit buffers of the same size as the input string: since - GSKit tokens are always shorter than their cipher names, allocated buffers - will always be large enough to accomodate the result. */ - l = strlen(cipherlist) + 1; - memset((char *) ciphers, 0, sizeof ciphers); - for(i = 0; i < CURL_GSKPROTO_LAST; i++) { - ciphers[i].buf = malloc(l); - if(!ciphers[i].buf) { - while(i--) - free(ciphers[i].buf); - return CURLE_OUT_OF_MEMORY; - } - ciphers[i].ptr = ciphers[i].buf; - *ciphers[i].ptr = '\0'; - } - - /* Process each cipher in input string. */ - unsupported = FALSE; - result = CURLE_OK; - for(;;) { - for(clp = cipherlist; *cipherlist && !is_separator(*cipherlist);) - cipherlist++; - l = cipherlist - clp; - if(!l) - break; - /* Search the cipher in our table. */ - for(ctp = ciphertable; ctp->name; ctp++) - if(strncasecompare(ctp->name, clp, l) && !ctp->name[l]) - break; - if(!ctp->name) { - failf(data, "Unknown cipher %.*s", l, clp); - result = CURLE_SSL_CIPHER; - } - else { - unsupported |= !(ctp->versions & (CURL_GSKPROTO_SSLV2_MASK | - CURL_GSKPROTO_SSLV3_MASK | CURL_GSKPROTO_TLSV10_MASK)); - for(i = 0; i < CURL_GSKPROTO_LAST; i++) { - if(ctp->versions & (1 << i)) { - strcpy(ciphers[i].ptr, ctp->gsktoken); - ciphers[i].ptr += strlen(ctp->gsktoken); - } - } - } - - /* Advance to next cipher name or end of string. */ - while(is_separator(*cipherlist)) - cipherlist++; - } - - /* Disable protocols with empty cipher lists. */ - for(i = 0; i < CURL_GSKPROTO_LAST; i++) { - if(!(*protoflags & (1 << i)) || !ciphers[i].buf[0]) { - *protoflags &= ~(1 << i); - ciphers[i].buf[0] = '\0'; - } - } - - /* Try to set-up TLSv1.1 and TLSv2.1 ciphers. */ - if(*protoflags & CURL_GSKPROTO_TLSV11_MASK) { - result = set_buffer(data, h, GSK_TLSV11_CIPHER_SPECS, - ciphers[CURL_GSKPROTO_TLSV11].buf, TRUE); - if(result == CURLE_UNSUPPORTED_PROTOCOL) { - result = CURLE_OK; - if(unsupported) { - failf(data, "TLSv1.1-only ciphers are not yet supported"); - result = CURLE_SSL_CIPHER; - } - } - } - if(!result && (*protoflags & CURL_GSKPROTO_TLSV12_MASK)) { - result = set_buffer(data, h, GSK_TLSV12_CIPHER_SPECS, - ciphers[CURL_GSKPROTO_TLSV12].buf, TRUE); - if(result == CURLE_UNSUPPORTED_PROTOCOL) { - result = CURLE_OK; - if(unsupported) { - failf(data, "TLSv1.2-only ciphers are not yet supported"); - result = CURLE_SSL_CIPHER; - } - } - } - - /* Try to set-up TLSv1.0 ciphers. If not successful, concatenate them to - the SSLv3 ciphers. OS/400 prior to version 7.1 will understand it. */ - if(!result && (*protoflags & CURL_GSKPROTO_TLSV10_MASK)) { - result = set_buffer(data, h, GSK_TLSV10_CIPHER_SPECS, - ciphers[CURL_GSKPROTO_TLSV10].buf, TRUE); - if(result == CURLE_UNSUPPORTED_PROTOCOL) { - result = CURLE_OK; - strcpy(ciphers[CURL_GSKPROTO_SSLV3].ptr, - ciphers[CURL_GSKPROTO_TLSV10].ptr); - } - } - - /* Set-up other ciphers. */ - if(!result && (*protoflags & CURL_GSKPROTO_SSLV3_MASK)) - result = set_buffer(data, h, GSK_V3_CIPHER_SPECS, - ciphers[CURL_GSKPROTO_SSLV3].buf, FALSE); - if(!result && (*protoflags & CURL_GSKPROTO_SSLV2_MASK)) - result = set_buffer(data, h, GSK_V2_CIPHER_SPECS, - ciphers[CURL_GSKPROTO_SSLV2].buf, FALSE); - - /* Clean-up. */ - for(i = 0; i < CURL_GSKPROTO_LAST; i++) - free(ciphers[i].buf); - - return result; -} - - -int Curl_gskit_init(void) -{ - /* No initialisation needed. */ - - return 1; -} - - -void Curl_gskit_cleanup(void) -{ - /* Nothing to do. */ -} - - -static CURLcode init_environment(struct Curl_easy *data, - gsk_handle *envir, const char *appid, - const char *file, const char *label, - const char *password) -{ - int rc; - CURLcode result; - gsk_handle h; - - /* Creates the GSKit environment. */ - - rc = gsk_environment_open(&h); - switch(rc) { - case GSK_OK: - break; - case GSK_INSUFFICIENT_STORAGE: - return CURLE_OUT_OF_MEMORY; - default: - failf(data, "gsk_environment_open(): %s", gsk_strerror(rc)); - return CURLE_SSL_CONNECT_ERROR; - } - - result = set_enum(data, h, GSK_SESSION_TYPE, GSK_CLIENT_SESSION, FALSE); - if(!result && appid) - result = set_buffer(data, h, GSK_OS400_APPLICATION_ID, appid, FALSE); - if(!result && file) - result = set_buffer(data, h, GSK_KEYRING_FILE, file, FALSE); - if(!result && label) - result = set_buffer(data, h, GSK_KEYRING_LABEL, label, FALSE); - if(!result && password) - result = set_buffer(data, h, GSK_KEYRING_PW, password, FALSE); - - if(!result) { - /* Locate CAs, Client certificate and key according to our settings. - Note: this call may be blocking for some tenths of seconds. */ - result = gskit_status(data, gsk_environment_init(h), - "gsk_environment_init()", CURLE_SSL_CERTPROBLEM); - if(!result) { - *envir = h; - return result; - } - } - /* Error: rollback. */ - gsk_environment_close(&h); - return result; -} - - -static void cancel_async_handshake(struct connectdata *conn, int sockindex) -{ - struct ssl_connect_data *connssl = &conn->ssl[sockindex]; - Qso_OverlappedIO_t cstat; - - if(QsoCancelOperation(conn->sock[sockindex], 0) > 0) - QsoWaitForIOCompletion(connssl->iocport, &cstat, (struct timeval *) NULL); -} - - -static void close_async_handshake(struct ssl_connect_data *connssl) -{ - QsoDestroyIOCompletionPort(connssl->iocport); - connssl->iocport = -1; -} - -/* SSL over SSL - * Problems: - * 1) GSKit can only perform SSL on an AF_INET or AF_INET6 stream socket. To - * pipe an SSL stream into another, it is therefore needed to have a pair - * of such communicating sockets and handle the pipelining explicitly. - * 2) OS/400 socketpair() is only implemented for domain AF_UNIX, thus cannot - * be used to produce the pipeline. - * The solution is to simulate socketpair() for AF_INET with low-level API - * listen(), bind() and connect(). - */ - -static int -inetsocketpair(int sv[2]) -{ - int lfd; /* Listening socket. */ - int sfd; /* Server socket. */ - int cfd; /* Client socket. */ - int len; - struct sockaddr_in addr1; - struct sockaddr_in addr2; - - /* Create listening socket on a local dynamic port. */ - lfd = socket(AF_INET, SOCK_STREAM, 0); - if(lfd < 0) - return -1; - memset((char *) &addr1, 0, sizeof addr1); - addr1.sin_family = AF_INET; - addr1.sin_addr.s_addr = htonl(INADDR_LOOPBACK); - addr1.sin_port = 0; - if(bind(lfd, (struct sockaddr *) &addr1, sizeof addr1) || - listen(lfd, 2) < 0) { - close(lfd); - return -1; - } - - /* Get the allocated port. */ - len = sizeof addr1; - if(getsockname(lfd, (struct sockaddr *) &addr1, &len) < 0) { - close(lfd); - return -1; - } - - /* Create the client socket. */ - cfd = socket(AF_INET, SOCK_STREAM, 0); - if(cfd < 0) { - close(lfd); - return -1; - } - - /* Request unblocking connection to the listening socket. */ - curlx_nonblock(cfd, TRUE); - if(connect(cfd, (struct sockaddr *) &addr1, sizeof addr1) < 0 && - errno != EINPROGRESS) { - close(lfd); - close(cfd); - return -1; - } - - /* Get the client dynamic port for intrusion check below. */ - len = sizeof addr2; - if(getsockname(cfd, (struct sockaddr *) &addr2, &len) < 0) { - close(lfd); - close(cfd); - return -1; - } - - /* Accept the incoming connection and get the server socket. */ - curlx_nonblock(lfd, TRUE); - for(;;) { - len = sizeof addr1; - sfd = accept(lfd, (struct sockaddr *) &addr1, &len); - if(sfd < 0) { - close(lfd); - close(cfd); - return -1; - } - - /* Check for possible intrusion from an external process. */ - if(addr1.sin_addr.s_addr == addr2.sin_addr.s_addr && - addr1.sin_port == addr2.sin_port) - break; - - /* Intrusion: reject incoming connection. */ - close(sfd); - } - - /* Done, return sockets and succeed. */ - close(lfd); - curlx_nonblock(cfd, FALSE); - sv[0] = cfd; - sv[1] = sfd; - return 0; -} - -static int pipe_ssloverssl(struct connectdata *conn, int sockindex, - int directions) -{ - struct ssl_connect_data *connssl = &conn->ssl[sockindex]; - struct ssl_connect_data *connproxyssl = &conn->proxy_ssl[sockindex]; - fd_set fds_read; - fd_set fds_write; - int n; - int m; - int i; - int ret = 0; - struct timeval tv = {0, 0}; - char buf[CURL_MAX_WRITE_SIZE]; - - if(!connssl->use || !connproxyssl->use) - return 0; /* No SSL over SSL: OK. */ - - FD_ZERO(&fds_read); - FD_ZERO(&fds_write); - n = -1; - if(directions & SOS_READ) { - FD_SET(connssl->remotefd, &fds_write); - n = connssl->remotefd; - } - if(directions & SOS_WRITE) { - FD_SET(connssl->remotefd, &fds_read); - n = connssl->remotefd; - FD_SET(conn->sock[sockindex], &fds_write); - if(n < conn->sock[sockindex]) - n = conn->sock[sockindex]; - } - i = select(n + 1, &fds_read, &fds_write, NULL, &tv); - if(i < 0) - return -1; /* Select error. */ - - if(FD_ISSET(connssl->remotefd, &fds_write)) { - /* Try getting data from HTTPS proxy and pipe it upstream. */ - n = 0; - i = gsk_secure_soc_read(connproxyssl->handle, buf, sizeof buf, &n); - switch(i) { - case GSK_OK: - if(n) { - i = write(connssl->remotefd, buf, n); - if(i < 0) - return -1; - ret = 1; - } - break; - case GSK_OS400_ERROR_TIMED_OUT: - case GSK_WOULD_BLOCK: - break; - default: - return -1; - } - } - - if(FD_ISSET(connssl->remotefd, &fds_read) && - FD_ISSET(conn->sock[sockindex], &fds_write)) { - /* Pipe data to HTTPS proxy. */ - n = read(connssl->remotefd, buf, sizeof buf); - if(n < 0) - return -1; - if(n) { - i = gsk_secure_soc_write(connproxyssl->handle, buf, n, &m); - if(i != GSK_OK || n != m) - return -1; - ret = 1; - } - } - - return ret; /* OK */ -} - - -static void close_one(struct ssl_connect_data *connssl, - struct connectdata *conn, int sockindex) -{ - if(connssl->handle) { - gskit_status(conn->data, gsk_secure_soc_close(&connssl->handle), - "gsk_secure_soc_close()", 0); - /* Last chance to drain output. */ - while(pipe_ssloverssl(conn, sockindex, SOS_WRITE) > 0) - ; - connssl->handle = (gsk_handle) NULL; - if(connssl->localfd >= 0) { - close(connssl->localfd); - connssl->localfd = -1; - } - if(connssl->remotefd >= 0) { - close(connssl->remotefd); - connssl->remotefd = -1; - } - } - if(connssl->iocport >= 0) - close_async_handshake(connssl); -} - - -static ssize_t gskit_send(struct connectdata *conn, int sockindex, - const void *mem, size_t len, CURLcode *curlcode) -{ - struct Curl_easy *data = conn->data; - CURLcode cc = CURLE_SEND_ERROR; - int written; - - if(pipe_ssloverssl(conn, sockindex, SOS_WRITE) >= 0) { - cc = gskit_status(data, - gsk_secure_soc_write(conn->ssl[sockindex].handle, - (char *) mem, (int) len, &written), - "gsk_secure_soc_write()", CURLE_SEND_ERROR); - if(cc == CURLE_OK) - if(pipe_ssloverssl(conn, sockindex, SOS_WRITE) < 0) - cc = CURLE_SEND_ERROR; - } - if(cc != CURLE_OK) { - *curlcode = cc; - written = -1; - } - return (ssize_t) written; /* number of bytes */ -} - - -static ssize_t gskit_recv(struct connectdata *conn, int num, char *buf, - size_t buffersize, CURLcode *curlcode) -{ - struct Curl_easy *data = conn->data; - int buffsize; - int nread; - CURLcode cc = CURLE_RECV_ERROR; - - if(pipe_ssloverssl(conn, num, SOS_READ) >= 0) { - buffsize = buffersize > (size_t) INT_MAX? INT_MAX: (int) buffersize; - cc = gskit_status(data, gsk_secure_soc_read(conn->ssl[num].handle, - buf, buffsize, &nread), - "gsk_secure_soc_read()", CURLE_RECV_ERROR); - } - switch(cc) { - case CURLE_OK: - break; - case CURLE_OPERATION_TIMEDOUT: - cc = CURLE_AGAIN; - default: - *curlcode = cc; - nread = -1; - break; - } - return (ssize_t) nread; -} - - -static CURLcode gskit_connect_step1(struct connectdata *conn, int sockindex) -{ - struct Curl_easy *data = conn->data; - struct ssl_connect_data *connssl = &conn->ssl[sockindex]; - gsk_handle envir; - CURLcode result; - int rc; - const char * const keyringfile = SSL_CONN_CONFIG(CAfile); - const char * const keyringpwd = SSL_SET_OPTION(key_passwd); - const char * const keyringlabel = SSL_SET_OPTION(cert); - const long int ssl_version = SSL_CONN_CONFIG(version); - const bool verifypeer = SSL_CONN_CONFIG(verifypeer); - const char * const hostname = SSL_IS_PROXY()? conn->http_proxy.host.name: - conn->host.name; - const char *sni; - unsigned int protoflags; - long timeout; - Qso_OverlappedIO_t commarea; - int sockpair[2]; - static const int sobufsize = CURL_MAX_WRITE_SIZE; - - /* Create SSL environment, start (preferably asynchronous) handshake. */ - - connssl->handle = (gsk_handle) NULL; - connssl->iocport = -1; - connssl->localfd = -1; - connssl->remotefd = -1; - - /* GSKit supports two ways of specifying an SSL context: either by - * application identifier (that should have been defined at the system - * level) or by keyring file, password and certificate label. - * Local certificate name (CURLOPT_SSLCERT) is used to hold either the - * application identifier of the certificate label. - * Key password (CURLOPT_KEYPASSWD) holds the keyring password. - * It is not possible to have different keyrings for the CAs and the - * local certificate. We thus use the CA file (CURLOPT_CAINFO) to identify - * the keyring file. - * If no key password is given and the keyring is the system keyring, - * application identifier mode is tried first, as recommended in IBM doc. - */ - - envir = (gsk_handle) NULL; - - if(keyringlabel && *keyringlabel && !keyringpwd && - !strcmp(keyringfile, CURL_CA_BUNDLE)) { - /* Try application identifier mode. */ - init_environment(data, &envir, keyringlabel, (const char *) NULL, - (const char *) NULL, (const char *) NULL); - } - - if(!envir) { - /* Use keyring mode. */ - result = init_environment(data, &envir, (const char *) NULL, - keyringfile, keyringlabel, keyringpwd); - if(result) - return result; - } - - /* Create secure session. */ - result = gskit_status(data, gsk_secure_soc_open(envir, &connssl->handle), - "gsk_secure_soc_open()", CURLE_SSL_CONNECT_ERROR); - gsk_environment_close(&envir); - if(result) - return result; - - /* Establish a pipelining socket pair for SSL over SSL. */ - if(conn->proxy_ssl[sockindex].use) { - if(inetsocketpair(sockpair)) - return CURLE_SSL_CONNECT_ERROR; - connssl->localfd = sockpair[0]; - connssl->remotefd = sockpair[1]; - setsockopt(connssl->localfd, SOL_SOCKET, SO_RCVBUF, - (void *) sobufsize, sizeof sobufsize); - setsockopt(connssl->remotefd, SOL_SOCKET, SO_RCVBUF, - (void *) sobufsize, sizeof sobufsize); - setsockopt(connssl->localfd, SOL_SOCKET, SO_SNDBUF, - (void *) sobufsize, sizeof sobufsize); - setsockopt(connssl->remotefd, SOL_SOCKET, SO_SNDBUF, - (void *) sobufsize, sizeof sobufsize); - curlx_nonblock(connssl->localfd, TRUE); - curlx_nonblock(connssl->remotefd, TRUE); - } - - /* Determine which SSL/TLS version should be enabled. */ - sni = hostname; - switch(ssl_version) { - case CURL_SSLVERSION_SSLv2: - protoflags = CURL_GSKPROTO_SSLV2_MASK; - sni = NULL; - break; - case CURL_SSLVERSION_SSLv3: - protoflags = CURL_GSKPROTO_SSLV3_MASK; - sni = NULL; - break; - case CURL_SSLVERSION_DEFAULT: - case CURL_SSLVERSION_TLSv1: - protoflags = CURL_GSKPROTO_TLSV10_MASK | - CURL_GSKPROTO_TLSV11_MASK | CURL_GSKPROTO_TLSV12_MASK; - break; - case CURL_SSLVERSION_TLSv1_0: - protoflags = CURL_GSKPROTO_TLSV10_MASK; - break; - case CURL_SSLVERSION_TLSv1_1: - protoflags = CURL_GSKPROTO_TLSV11_MASK; - break; - case CURL_SSLVERSION_TLSv1_2: - protoflags = CURL_GSKPROTO_TLSV12_MASK; - break; - case CURL_SSLVERSION_TLSv1_3: - failf(data, "GSKit: TLS 1.3 is not yet supported"); - return CURLE_SSL_CONNECT_ERROR; - default: - failf(data, "Unrecognized parameter passed via CURLOPT_SSLVERSION"); - return CURLE_SSL_CONNECT_ERROR; - } - - /* Process SNI. Ignore if not supported (on OS400 < V7R1). */ - if(sni) { - result = set_buffer(data, connssl->handle, - GSK_SSL_EXTN_SERVERNAME_REQUEST, sni, TRUE); - if(result == CURLE_UNSUPPORTED_PROTOCOL) - result = CURLE_OK; - } - - /* Set session parameters. */ - if(!result) { - /* Compute the handshake timeout. Since GSKit granularity is 1 second, - we round up the required value. */ - timeout = Curl_timeleft(data, NULL, TRUE); - if(timeout < 0) - result = CURLE_OPERATION_TIMEDOUT; - else - result = set_numeric(data, connssl->handle, GSK_HANDSHAKE_TIMEOUT, - (timeout + 999) / 1000); - } - if(!result) - result = set_numeric(data, connssl->handle, GSK_OS400_READ_TIMEOUT, 1); - if(!result) - result = set_numeric(data, connssl->handle, GSK_FD, connssl->localfd >= 0? - connssl->localfd: conn->sock[sockindex]); - if(!result) - result = set_ciphers(conn, connssl->handle, &protoflags); - if(!protoflags) { - failf(data, "No SSL protocol/cipher combination enabled"); - result = CURLE_SSL_CIPHER; - } - if(!result) - result = set_enum(data, connssl->handle, GSK_PROTOCOL_SSLV2, - (protoflags & CURL_GSKPROTO_SSLV2_MASK)? - GSK_PROTOCOL_SSLV2_ON: GSK_PROTOCOL_SSLV2_OFF, FALSE); - if(!result) - result = set_enum(data, connssl->handle, GSK_PROTOCOL_SSLV3, - (protoflags & CURL_GSKPROTO_SSLV3_MASK)? - GSK_PROTOCOL_SSLV3_ON: GSK_PROTOCOL_SSLV3_OFF, FALSE); - if(!result) - result = set_enum(data, connssl->handle, GSK_PROTOCOL_TLSV1, - (protoflags & CURL_GSKPROTO_TLSV10_MASK)? - GSK_PROTOCOL_TLSV1_ON: GSK_PROTOCOL_TLSV1_OFF, FALSE); - if(!result) { - result = set_enum(data, connssl->handle, GSK_PROTOCOL_TLSV11, - (protoflags & CURL_GSKPROTO_TLSV11_MASK)? - GSK_TRUE: GSK_FALSE, TRUE); - if(result == CURLE_UNSUPPORTED_PROTOCOL) { - result = CURLE_OK; - if(protoflags == CURL_GSKPROTO_TLSV11_MASK) { - failf(data, "TLS 1.1 not yet supported"); - result = CURLE_SSL_CIPHER; - } - } - } - if(!result) { - result = set_enum(data, connssl->handle, GSK_PROTOCOL_TLSV12, - (protoflags & CURL_GSKPROTO_TLSV12_MASK)? - GSK_TRUE: GSK_FALSE, TRUE); - if(result == CURLE_UNSUPPORTED_PROTOCOL) { - result = CURLE_OK; - if(protoflags == CURL_GSKPROTO_TLSV12_MASK) { - failf(data, "TLS 1.2 not yet supported"); - result = CURLE_SSL_CIPHER; - } - } - } - if(!result) - result = set_enum(data, connssl->handle, GSK_SERVER_AUTH_TYPE, - verifypeer? GSK_SERVER_AUTH_FULL: - GSK_SERVER_AUTH_PASSTHRU, FALSE); - - if(!result) { - /* Start handshake. Try asynchronous first. */ - memset(&commarea, 0, sizeof commarea); - connssl->iocport = QsoCreateIOCompletionPort(); - if(connssl->iocport != -1) { - result = gskit_status(data, - gsk_secure_soc_startInit(connssl->handle, - connssl->iocport, - &commarea), - "gsk_secure_soc_startInit()", - CURLE_SSL_CONNECT_ERROR); - if(!result) { - connssl->connecting_state = ssl_connect_2; - return CURLE_OK; - } - else - close_async_handshake(connssl); - } - else if(errno != ENOBUFS) - result = gskit_status(data, GSK_ERROR_IO, - "QsoCreateIOCompletionPort()", 0); - else if(conn->proxy_ssl[sockindex].use) { - /* Cannot pipeline while handshaking synchronously. */ - result = CURLE_SSL_CONNECT_ERROR; - } - else { - /* No more completion port available. Use synchronous IO. */ - result = gskit_status(data, gsk_secure_soc_init(connssl->handle), - "gsk_secure_soc_init()", CURLE_SSL_CONNECT_ERROR); - if(!result) { - connssl->connecting_state = ssl_connect_3; - return CURLE_OK; - } - } - } - - /* Error: rollback. */ - close_one(connssl, conn, sockindex); - return result; -} - - -static CURLcode gskit_connect_step2(struct connectdata *conn, int sockindex, - bool nonblocking) -{ - struct Curl_easy *data = conn->data; - struct ssl_connect_data *connssl = &conn->ssl[sockindex]; - Qso_OverlappedIO_t cstat; - long timeout_ms; - struct timeval stmv; - CURLcode result; - - /* Poll or wait for end of SSL asynchronous handshake. */ - - for(;;) { - timeout_ms = nonblocking? 0: Curl_timeleft(data, NULL, TRUE); - if(timeout_ms < 0) - timeout_ms = 0; - stmv.tv_sec = timeout_ms / 1000; - stmv.tv_usec = (timeout_ms - stmv.tv_sec * 1000) * 1000; - switch(QsoWaitForIOCompletion(connssl->iocport, &cstat, &stmv)) { - case 1: /* Operation complete. */ - break; - case -1: /* An error occurred: handshake still in progress. */ - if(errno == EINTR) { - if(nonblocking) - return CURLE_OK; - continue; /* Retry. */ - } - if(errno != ETIME) { - failf(data, "QsoWaitForIOCompletion() I/O error: %s", strerror(errno)); - cancel_async_handshake(conn, sockindex); - close_async_handshake(connssl); - return CURLE_SSL_CONNECT_ERROR; - } - /* FALL INTO... */ - case 0: /* Handshake in progress, timeout occurred. */ - if(nonblocking) - return CURLE_OK; - cancel_async_handshake(conn, sockindex); - close_async_handshake(connssl); - return CURLE_OPERATION_TIMEDOUT; - } - break; - } - result = gskit_status(data, cstat.returnValue, "SSL handshake", - CURLE_SSL_CONNECT_ERROR); - if(!result) - connssl->connecting_state = ssl_connect_3; - close_async_handshake(connssl); - return result; -} - - -static CURLcode gskit_connect_step3(struct connectdata *conn, int sockindex) -{ - struct Curl_easy *data = conn->data; - struct ssl_connect_data *connssl = &conn->ssl[sockindex]; - const gsk_cert_data_elem *cdev; - int cdec; - const gsk_cert_data_elem *p; - const char *cert = (const char *) NULL; - const char *certend; - const char *ptr; - int i; - CURLcode result; - - /* SSL handshake done: gather certificate info and verify host. */ - - if(gskit_status(data, gsk_attribute_get_cert_info(connssl->handle, - GSK_PARTNER_CERT_INFO, - &cdev, &cdec), - "gsk_attribute_get_cert_info()", CURLE_SSL_CONNECT_ERROR) == - CURLE_OK) { - infof(data, "Server certificate:\n"); - p = cdev; - for(i = 0; i++ < cdec; p++) - switch(p->cert_data_id) { - case CERT_BODY_DER: - cert = p->cert_data_p; - certend = cert + cdev->cert_data_l; - break; - case CERT_DN_PRINTABLE: - infof(data, "\t subject: %.*s\n", p->cert_data_l, p->cert_data_p); - break; - case CERT_ISSUER_DN_PRINTABLE: - infof(data, "\t issuer: %.*s\n", p->cert_data_l, p->cert_data_p); - break; - case CERT_VALID_FROM: - infof(data, "\t start date: %.*s\n", p->cert_data_l, p->cert_data_p); - break; - case CERT_VALID_TO: - infof(data, "\t expire date: %.*s\n", p->cert_data_l, p->cert_data_p); - break; - } - } - - /* Verify host. */ - result = Curl_verifyhost(conn, cert, certend); - if(result) - return result; - - /* The only place GSKit can get the whole CA chain is a validation - callback where no user data pointer is available. Therefore it's not - possible to copy this chain into our structures for CAINFO. - However the server certificate may be available, thus we can return - info about it. */ - if(data->set.ssl.certinfo) { - result = Curl_ssl_init_certinfo(data, 1); - if(result) - return result; - - if(cert) { - result = Curl_extract_certinfo(conn, 0, cert, certend); - if(result) - return result; - } - } - - /* Check pinned public key. */ - ptr = SSL_IS_PROXY() ? data->set.str[STRING_SSL_PINNEDPUBLICKEY_PROXY] : - data->set.str[STRING_SSL_PINNEDPUBLICKEY_ORIG]; - if(!result && ptr) { - curl_X509certificate x509; - curl_asn1Element *p; - - if(Curl_parseX509(&x509, cert, certend)) - return CURLE_SSL_PINNEDPUBKEYNOTMATCH; - p = &x509.subjectPublicKeyInfo; - result = Curl_pin_peer_pubkey(data, ptr, p->header, p->end - p->header); - if(result) { - failf(data, "SSL: public key does not match pinned public key!"); - return result; - } - } - - connssl->connecting_state = ssl_connect_done; - return CURLE_OK; -} - - -static CURLcode gskit_connect_common(struct connectdata *conn, int sockindex, - bool nonblocking, bool *done) -{ - struct Curl_easy *data = conn->data; - struct ssl_connect_data *connssl = &conn->ssl[sockindex]; - long timeout_ms; - Qso_OverlappedIO_t cstat; - CURLcode result = CURLE_OK; - - *done = connssl->state == ssl_connection_complete; - if(*done) - return CURLE_OK; - - /* Step 1: create session, start handshake. */ - if(connssl->connecting_state == ssl_connect_1) { - /* 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"); - result = CURLE_OPERATION_TIMEDOUT; - } - else - result = gskit_connect_step1(conn, sockindex); - } - - /* Handle handshake pipelining. */ - if(!result) - if(pipe_ssloverssl(conn, sockindex, SOS_READ | SOS_WRITE) < 0) - result = CURLE_SSL_CONNECT_ERROR; - - /* Step 2: check if handshake is over. */ - if(!result && connssl->connecting_state == ssl_connect_2) { - /* 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"); - result = CURLE_OPERATION_TIMEDOUT; - } - else - result = gskit_connect_step2(conn, sockindex, nonblocking); - } - - /* Handle handshake pipelining. */ - if(!result) - if(pipe_ssloverssl(conn, sockindex, SOS_READ | SOS_WRITE) < 0) - result = CURLE_SSL_CONNECT_ERROR; - - /* Step 3: gather certificate info, verify host. */ - if(!result && connssl->connecting_state == ssl_connect_3) - result = gskit_connect_step3(conn, sockindex); - - if(result) - close_one(connssl, conn, sockindex); - else if(connssl->connecting_state == ssl_connect_done) { - connssl->state = ssl_connection_complete; - connssl->connecting_state = ssl_connect_1; - conn->recv[sockindex] = gskit_recv; - conn->send[sockindex] = gskit_send; - *done = TRUE; - } - - return result; -} - - -CURLcode Curl_gskit_connect_nonblocking(struct connectdata *conn, - int sockindex, - bool *done) -{ - CURLcode result; - - result = gskit_connect_common(conn, sockindex, TRUE, done); - if(*done || result) - conn->ssl[sockindex].connecting_state = ssl_connect_1; - return result; -} - - -CURLcode Curl_gskit_connect(struct connectdata *conn, int sockindex) -{ - CURLcode result; - bool done; - - conn->ssl[sockindex].connecting_state = ssl_connect_1; - result = gskit_connect_common(conn, sockindex, FALSE, &done); - if(result) - return result; - - DEBUGASSERT(done); - - return CURLE_OK; -} - - -void Curl_gskit_close(struct connectdata *conn, int sockindex) -{ - close_one(&conn->ssl[sockindex], conn, sockindex); - close_one(&conn->proxy_ssl[sockindex], conn, sockindex); -} - - -int Curl_gskit_shutdown(struct connectdata *conn, int sockindex) -{ - struct ssl_connect_data *connssl = &conn->ssl[sockindex]; - struct Curl_easy *data = conn->data; - ssize_t nread; - int what; - int rc; - char buf[120]; - - if(!connssl->handle) - return 0; - - if(data->set.ftp_ccc != CURLFTPSSL_CCC_ACTIVE) - return 0; - - close_one(connssl, conn, sockindex); - rc = 0; - what = SOCKET_READABLE(conn->sock[sockindex], - SSL_SHUTDOWN_TIMEOUT); - - for(;;) { - 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 gsk_secure_soc_read() now, so - use read(). */ - - nread = read(conn->sock[sockindex], buf, sizeof(buf)); - - if(nread < 0) { - failf(data, "read: %s", strerror(errno)); - rc = -1; - } - - if(nread <= 0) - break; - - what = SOCKET_READABLE(conn->sock[sockindex], 0); - } - - return rc; -} - - -size_t Curl_gskit_version(char *buffer, size_t size) -{ - strncpy(buffer, "GSKit", size); - return strlen(buffer); -} - - -int Curl_gskit_check_cxn(struct connectdata *cxn) -{ - int err; - int errlen; - - /* The only thing that can be tested here is at the socket level. */ - - if(!cxn->ssl[FIRSTSOCKET].handle) - return 0; /* connection has been closed */ - - err = 0; - errlen = sizeof err; - - if(getsockopt(cxn->sock[FIRSTSOCKET], SOL_SOCKET, SO_ERROR, - (unsigned char *) &err, &errlen) || - errlen != sizeof err || err) - return 0; /* connection has been closed */ - - return -1; /* connection status unknown */ -} - -#endif /* USE_GSKIT */ diff --git a/contrib/curl/lib/vtls/gskit.h b/contrib/curl/lib/vtls/gskit.h deleted file mode 100644 index 229759217a..0000000000 --- a/contrib/curl/lib/vtls/gskit.h +++ /dev/null @@ -1,74 +0,0 @@ -#ifndef HEADER_CURL_GSKIT_H -#define HEADER_CURL_GSKIT_H -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) 1998 - 2016, Daniel Stenberg, , et al. - * - * This software is licensed as described in the file COPYING, which - * you should have received as part of this distribution. The terms - * are also available at https://curl.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. - * - ***************************************************************************/ -#include "curl_setup.h" - -/* - * This header should only be needed to get included by vtls.c and gskit.c - */ - -#include "urldata.h" - -#ifdef USE_GSKIT -int Curl_gskit_init(void); -void Curl_gskit_cleanup(void); -CURLcode Curl_gskit_connect(struct connectdata *conn, int sockindex); -CURLcode Curl_gskit_connect_nonblocking(struct connectdata *conn, - int sockindex, bool *done); -void Curl_gskit_close(struct connectdata *conn, int sockindex); -int Curl_gskit_shutdown(struct connectdata *conn, int sockindex); - -size_t Curl_gskit_version(char *buffer, size_t size); -int Curl_gskit_check_cxn(struct connectdata *cxn); - -/* Support HTTPS-proxy */ -/* TODO: add '#define HTTPS_PROXY_SUPPORT 1' and fix test #1014 (if need) */ - -/* Set the API backend definition to GSKit */ -#define CURL_SSL_BACKEND CURLSSLBACKEND_GSKIT - -/* this backend supports CURLOPT_CERTINFO */ -#define have_curlssl_certinfo 1 - -/* API setup for GSKit */ -#define curlssl_init Curl_gskit_init -#define curlssl_cleanup Curl_gskit_cleanup -#define curlssl_connect Curl_gskit_connect -#define curlssl_connect_nonblocking Curl_gskit_connect_nonblocking - -/* No session handling for GSKit */ -#define curlssl_session_free(x) Curl_nop_stmt -#define curlssl_close_all(x) ((void)x) -#define curlssl_close Curl_gskit_close -#define curlssl_shutdown(x,y) Curl_gskit_shutdown(x,y) -#define curlssl_set_engine(x,y) CURLE_NOT_BUILT_IN -#define curlssl_set_engine_default(x) CURLE_NOT_BUILT_IN -#define curlssl_engines_list(x) NULL -#define curlssl_version Curl_gskit_version -#define curlssl_check_cxn(x) Curl_gskit_check_cxn(x) -#define curlssl_data_pending(x,y) 0 -#define curlssl_random(x,y,z) (x=x, y=y, z=z, CURLE_NOT_BUILT_IN) - -#endif /* USE_GSKIT */ - -#endif /* HEADER_CURL_GSKIT_H */ diff --git a/contrib/curl/lib/vtls/gtls.c b/contrib/curl/lib/vtls/gtls.c index faa70aca2a..562c5a3b2c 100644 --- a/contrib/curl/lib/vtls/gtls.c +++ b/contrib/curl/lib/vtls/gtls.c @@ -5,11 +5,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2017, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -18,13 +18,15 @@ * 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, + * Note: do not use the GnuTLS' *_t variable type names in this source code, * since they were not present in 1.0.X. */ @@ -35,42 +37,39 @@ #include #include #include - -#ifdef USE_GNUTLS_NETTLE #include -#include #include -#else -#include -#endif #include "urldata.h" #include "sendf.h" #include "inet_pton.h" +#include "keylog.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" -/* - Some hackish cast macros based on: - https://developer.gnome.org/glib/unstable/glib-Type-Conversion-Macros.html -*/ -#ifndef GNUTLS_POINTER_TO_INT_CAST -#define GNUTLS_POINTER_TO_INT_CAST(p) ((int) (long) (p)) -#endif -#ifndef GNUTLS_INT_TO_POINTER_CAST -#define GNUTLS_INT_TO_POINTER_CAST(i) ((void *) (long) (i)) +#ifndef ARRAYSIZE +#define ARRAYSIZE(A) (sizeof(A)/sizeof((A)[0])) #endif +#define QUIC_PRIORITY \ + "NORMAL:-VERS-ALL:+VERS-TLS1.3:-CIPHER-ALL:+AES-128-GCM:+AES-256-GCM:" \ + "+CHACHA20-POLY1305:+AES-128-CCM:-GROUP-ALL:+GROUP-SECP256R1:" \ + "+GROUP-X25519:+GROUP-SECP384R1:+GROUP-SECP521R1:" \ + "%DISABLE_TLS13_COMPAT_MODE" + /* Enable GnuTLS debugging by defining GTLSDEBUG */ /*#define GTLSDEBUG */ @@ -82,117 +81,85 @@ static void tls_log_func(int level, const char *str) #endif static bool gtls_inited = FALSE; -#if defined(GNUTLS_VERSION_NUMBER) -# if (GNUTLS_VERSION_NUMBER >= 0x020c00) -# undef gnutls_transport_set_lowat -# define gnutls_transport_set_lowat(A,B) Curl_nop_stmt -# define USE_GNUTLS_PRIORITY_SET_DIRECT 1 -# endif -# if (GNUTLS_VERSION_NUMBER >= 0x020c03) -# define GNUTLS_MAPS_WINSOCK_ERRORS 1 -# endif - -# if HAVE_GNUTLS_ALPN_SET_PROTOCOLS -# define HAS_ALPN -# endif - -# if HAVE_GNUTLS_OCSP_REQ_INIT -# define HAS_OCSP -# endif - -# if (GNUTLS_VERSION_NUMBER >= 0x030306) -# define HAS_CAPATH -# endif +#if !defined(GNUTLS_VERSION_NUMBER) || (GNUTLS_VERSION_NUMBER < 0x03010a) +#error "too old GnuTLS version" #endif -#ifdef HAS_OCSP # include -#endif -/* - * Custom push and pull callback functions used by GNU TLS to read and write - * to the socket. These functions are simple wrappers to send() and recv() - * (although here using the sread/swrite macros as defined by - * curl_setup_once.h). - * We use custom functions rather than the GNU TLS defaults because it allows - * us to get specific about the fourth "flags" argument, and to use arbitrary - * private data with gnutls_transport_set_ptr if we wish. - * - * When these custom push and pull callbacks fail, GNU TLS checks its own - * session-specific error variable, and when not set also its own global - * errno variable, in order to take appropriate action. GNU TLS does not - * require that the transport is actually a socket. This implies that for - * Windows builds these callbacks should ideally set the session-specific - * error variable using function gnutls_transport_set_errno or as a last - * resort global errno variable using gnutls_transport_set_global_errno, - * with a transport agnostic error value. This implies that some winsock - * error translation must take place in these callbacks. - * - * Paragraph above applies to GNU TLS versions older than 2.12.3, since - * this version GNU TLS does its own internal winsock error translation - * using system_errno() function. - */ +struct gtls_ssl_backend_data { + struct gtls_ctx gtls; +}; -#if defined(USE_WINSOCK) && !defined(GNUTLS_MAPS_WINSOCK_ERRORS) -# define gtls_EINTR 4 -# define gtls_EIO 5 -# define gtls_EAGAIN 11 -static int gtls_mapped_sockerrno(void) +static ssize_t gtls_push(void *s, const void *buf, size_t blen) { - switch(SOCKERRNO) { - case WSAEWOULDBLOCK: - return gtls_EAGAIN; - case WSAEINTR: - return gtls_EINTR; - default: - break; - } - return gtls_EIO; -} -#endif + struct Curl_cfilter *cf = s; + struct ssl_connect_data *connssl = cf->ctx; + struct gtls_ssl_backend_data *backend = + (struct gtls_ssl_backend_data *)connssl->backend; + struct Curl_easy *data = CF_DATA_CURRENT(cf); + ssize_t nwritten; + CURLcode result; -static ssize_t Curl_gtls_push(void *s, const void *buf, size_t len) -{ - ssize_t ret = swrite(GNUTLS_POINTER_TO_INT_CAST(s), buf, len); -#if defined(USE_WINSOCK) && !defined(GNUTLS_MAPS_WINSOCK_ERRORS) - if(ret < 0) - gnutls_transport_set_global_errno(gtls_mapped_sockerrno()); -#endif - return ret; + DEBUGASSERT(data); + nwritten = Curl_conn_cf_send(cf->next, data, buf, blen, FALSE, &result); + CURL_TRC_CF(data, cf, "gtls_push(len=%zu) -> %zd, err=%d", + blen, nwritten, result); + backend->gtls.io_result = result; + if(nwritten < 0) { + gnutls_transport_set_errno(backend->gtls.session, + (CURLE_AGAIN == result) ? EAGAIN : EINVAL); + nwritten = -1; + } + return nwritten; } -static ssize_t Curl_gtls_pull(void *s, void *buf, size_t len) +static ssize_t gtls_pull(void *s, void *buf, size_t blen) { - ssize_t ret = sread(GNUTLS_POINTER_TO_INT_CAST(s), buf, len); -#if defined(USE_WINSOCK) && !defined(GNUTLS_MAPS_WINSOCK_ERRORS) - if(ret < 0) - gnutls_transport_set_global_errno(gtls_mapped_sockerrno()); -#endif - return ret; -} + struct Curl_cfilter *cf = s; + struct ssl_connect_data *connssl = cf->ctx; + struct gtls_ssl_backend_data *backend = + (struct gtls_ssl_backend_data *)connssl->backend; + struct Curl_easy *data = CF_DATA_CURRENT(cf); + ssize_t nread; + CURLcode result; -static ssize_t Curl_gtls_push_ssl(void *s, const void *buf, size_t len) -{ - return gnutls_record_send((gnutls_session_t) s, buf, len); -} + DEBUGASSERT(data); + if(!backend->gtls.shared_creds->trust_setup) { + result = Curl_gtls_client_trust_setup(cf, data, &backend->gtls); + if(result) { + gnutls_transport_set_errno(backend->gtls.session, EINVAL); + backend->gtls.io_result = result; + return -1; + } + } -static ssize_t Curl_gtls_pull_ssl(void *s, void *buf, size_t len) -{ - return gnutls_record_recv((gnutls_session_t) s, buf, len); + nread = Curl_conn_cf_recv(cf->next, data, buf, blen, &result); + CURL_TRC_CF(data, cf, "glts_pull(len=%zu) -> %zd, err=%d", + blen, nread, result); + backend->gtls.io_result = result; + if(nread < 0) { + gnutls_transport_set_errno(backend->gtls.session, + (CURLE_AGAIN == result) ? EAGAIN : EINVAL); + nread = -1; + } + else if(nread == 0) + connssl->peer_closed = TRUE; + return nread; } -/* Curl_gtls_init() +/* 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! */ -int Curl_gtls_init(void) +static int gtls_init(void) { int ret = 1; if(!gtls_inited) { - ret = gnutls_global_init()?0:1; + ret = gnutls_global_init() ? 0 : 1; #ifdef GTLSDEBUG gnutls_global_set_log_function(tls_log_func); gnutls_global_set_log_level(2); @@ -202,38 +169,40 @@ int Curl_gtls_init(void) return ret; } -int Curl_gtls_cleanup(void) +static void gtls_cleanup(void) { if(gtls_inited) { gnutls_global_deinit(); gtls_inited = FALSE; } - return 1; } +#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; - snprintf(data->state.buffer, - BUFSIZE, - "\t %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\n", data->state.buffer); + 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) { @@ -269,20 +238,25 @@ static void unload_file(gnutls_datum_t data) /* this function does a SSL/TLS (re-)handshake */ -static CURLcode handshake(struct connectdata *conn, - int sockindex, +static CURLcode handshake(struct Curl_cfilter *cf, + struct Curl_easy *data, bool duringconnect, bool nonblocking) { - struct Curl_easy *data = conn->data; - struct ssl_connect_data *connssl = &conn->ssl[sockindex]; - gnutls_session_t session = conn->ssl[sockindex].session; - curl_socket_t sockfd = conn->sock[sockindex]; - long timeout_ms; - int rc; - int what; + 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; + connssl->connecting_state = ssl_connect_2; for(;;) { + timediff_t timeout_ms; + int rc; + /* check allowed time left */ timeout_ms = Curl_timeleft(data, NULL, duringconnect); @@ -292,18 +266,17 @@ static CURLcode handshake(struct connectdata *conn, 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; + /* if ssl is expecting something, check if it is available. */ + if(connssl->io_need) { + int what; + curl_socket_t writefd = (connssl->io_need & CURL_SSL_IO_NEED_SEND) ? + sockfd : CURL_SOCKET_BAD; + curl_socket_t readfd = (connssl->io_need & CURL_SSL_IO_NEED_RECV) ? + sockfd : CURL_SOCKET_BAD; what = Curl_socket_check(readfd, CURL_SOCKET_BAD, writefd, - nonblocking?0: - timeout_ms?timeout_ms:1000); + nonblocking ? 0 : + timeout_ms ? timeout_ms : 1000); if(what < 0) { /* fatal error */ failf(data, "select/poll on SSL socket, errno: %d", SOCKERRNO); @@ -314,47 +287,60 @@ static CURLcode handshake(struct connectdata *conn, return CURLE_OK; else if(timeout_ms) { /* timeout */ - failf(data, "SSL connection timeout at %ld", timeout_ms); + failf(data, "SSL connection timeout at %ld", (long)timeout_ms); return CURLE_OPERATION_TIMEDOUT; } } /* socket is readable or writable */ } + connssl->io_need = CURL_SSL_IO_NEED_NONE; + backend->gtls.io_result = CURLE_OK; rc = gnutls_handshake(session); + if(!backend->gtls.shared_creds->trust_setup) { + /* After having send off the ClientHello, we prepare the trust + * store to verify the coming certificate from the server */ + CURLcode result = Curl_gtls_client_trust_setup(cf, data, &backend->gtls); + if(result) + return result; + } + if((rc == GNUTLS_E_AGAIN) || (rc == GNUTLS_E_INTERRUPTED)) { - connssl->connecting_state = - gnutls_record_get_direction(session)? - ssl_connect_2_writing:ssl_connect_2_reading; + connssl->io_need = + gnutls_record_get_direction(session) ? + CURL_SSL_IO_NEED_SEND : CURL_SSL_IO_NEED_RECV; 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); + gnutls_alert_description_t alert = gnutls_alert_get(session); strerr = gnutls_alert_get_name(alert); } - if(strerr == NULL) + if(!strerr) strerr = gnutls_strerror(rc); - infof(data, "gnutls_handshake() warning: %s\n", strerr); + infof(data, "gnutls_handshake() warning: %s", strerr); continue; } + else if((rc < 0) && backend->gtls.io_result) { + return backend->gtls.io_result; + } else if(rc < 0) { const char *strerr = NULL; if(rc == GNUTLS_E_FATAL_ALERT_RECEIVED) { - int alert = gnutls_alert_get(session); + gnutls_alert_description_t alert = gnutls_alert_get(session); strerr = gnutls_alert_get_name(alert); } - if(strerr == NULL) + if(!strerr) strerr = gnutls_strerror(rc); - failf(data, "gnutls_handshake() failed: %s", strerr); + failf(data, "GnuTLS, handshake failed: %s", strerr); return CURLE_SSL_CONNECT_ERROR; } @@ -364,7 +350,7 @@ static CURLcode handshake(struct connectdata *conn, } } -static gnutls_x509_crt_fmt_t do_file_type(const char *type) +static gnutls_x509_crt_fmt_t gnutls_do_file_type(const char *type) { if(!type || !type[0]) return GNUTLS_X509_FMT_PEM; @@ -372,343 +358,608 @@ static gnutls_x509_crt_fmt_t do_file_type(const char *type) return GNUTLS_X509_FMT_PEM; if(strcasecompare(type, "DER")) return GNUTLS_X509_FMT_DER; - return -1; + return GNUTLS_X509_FMT_PEM; /* default to PEM */ } -static CURLcode -gtls_connect_step1(struct connectdata *conn, - int sockindex) -{ - struct Curl_easy *data = conn->data; - unsigned int init_flags; - gnutls_session_t session; - int rc; - bool sni = TRUE; /* default is SNI enabled */ - void *transport_ptr = NULL; - gnutls_push_func gnutls_transport_push = NULL; - gnutls_pull_func gnutls_transport_pull = NULL; -#ifdef ENABLE_IPV6 - struct in6_addr addr; -#else - struct in_addr addr; -#endif -#ifndef USE_GNUTLS_PRIORITY_SET_DIRECT - static const int cipher_priority[] = { - /* These two ciphers were added to GnuTLS as late as ver. 3.0.1, - but this code path is only ever used for ver. < 2.12.0. - GNUTLS_CIPHER_AES_128_GCM, - GNUTLS_CIPHER_AES_256_GCM, - */ - GNUTLS_CIPHER_AES_128_CBC, - GNUTLS_CIPHER_AES_256_CBC, - GNUTLS_CIPHER_CAMELLIA_128_CBC, - GNUTLS_CIPHER_CAMELLIA_256_CBC, - GNUTLS_CIPHER_3DES_CBC, - }; - static const int cert_type_priority[] = { GNUTLS_CRT_X509, 0 }; - static int protocol_priority[] = { 0, 0, 0, 0 }; -#else #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" - const char *prioritylist; - const char *err = NULL; -#endif - - const char * const hostname = SSL_IS_PROXY() ? conn->http_proxy.host.name : - conn->host.name; - if(conn->ssl[sockindex].state == ssl_connection_complete) - /* to make us tolerant against being called more than once for the - same connection */ +static CURLcode +gnutls_set_ssl_version_min_max(struct Curl_easy *data, + struct ssl_peer *peer, + struct ssl_primary_config *conn_config, + const char **prioritylist, + const char *tls13support) +{ + 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(peer->transport == TRNSPRT_QUIC) { + if((ssl_version_max != CURL_SSLVERSION_MAX_DEFAULT) && + (ssl_version_max < CURL_SSLVERSION_MAX_TLSv1_3)) { + failf(data, "QUIC needs at least TLS version 1.3"); + return CURLE_SSL_CONNECT_ERROR; + } + *prioritylist = QUIC_PRIORITY; return CURLE_OK; + } - if(!gtls_inited) - Curl_gtls_init(); + if(!tls13support) { + /* If the running GnuTLS does not 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; + } - if(SSL_CONN_CONFIG(version) == CURL_SSLVERSION_SSLv2) { - failf(data, "GnuTLS does not support SSLv2"); - return CURLE_SSL_CONNECT_ERROR; + 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; } - else if(SSL_CONN_CONFIG(version) == CURL_SSLVERSION_SSLv3) - sni = FALSE; /* SSLv3 has no SNI */ - /* allocate a cred struct */ - rc = gnutls_certificate_allocate_credentials(&conn->ssl[sockindex].cred); + failf(data, "GnuTLS: cannot set ssl protocol"); + return CURLE_SSL_CONNECT_ERROR; +} + +CURLcode Curl_gtls_shared_creds_create(struct Curl_easy *data, + struct gtls_shared_creds **pcreds) +{ + struct gtls_shared_creds *shared; + int rc; + + *pcreds = NULL; + shared = calloc(1, sizeof(*shared)); + if(!shared) + return CURLE_OUT_OF_MEMORY; + + rc = gnutls_certificate_allocate_credentials(&shared->creds); if(rc != GNUTLS_E_SUCCESS) { failf(data, "gnutls_cert_all_cred() failed: %s", gnutls_strerror(rc)); + free(shared); return CURLE_SSL_CONNECT_ERROR; } -#ifdef USE_TLS_SRP - if(SSL_SET_OPTION(authtype) == CURL_TLSAUTH_SRP) { - infof(data, "Using TLS-SRP username: %s\n", SSL_SET_OPTION(username)); + shared->refcount = 1; + shared->time = Curl_now(); + *pcreds = shared; + return CURLE_OK; +} - rc = gnutls_srp_allocate_client_credentials( - &conn->ssl[sockindex].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; - } +CURLcode Curl_gtls_shared_creds_up_ref(struct gtls_shared_creds *creds) +{ + DEBUGASSERT(creds); + if(creds->refcount < SIZE_T_MAX) { + ++creds->refcount; + return CURLE_OK; + } + return CURLE_BAD_FUNCTION_ARGUMENT; +} - rc = gnutls_srp_set_client_credentials(conn->ssl[sockindex]. - srp_client_cred, - SSL_SET_OPTION(username), - SSL_SET_OPTION(password)); - if(rc != GNUTLS_E_SUCCESS) { - failf(data, "gnutls_srp_set_client_cred() failed: %s", - gnutls_strerror(rc)); - return CURLE_BAD_FUNCTION_ARGUMENT; +void Curl_gtls_shared_creds_free(struct gtls_shared_creds **pcreds) +{ + struct gtls_shared_creds *shared = *pcreds; + *pcreds = NULL; + if(shared) { + --shared->refcount; + if(!shared->refcount) { + gnutls_certificate_free_credentials(shared->creds); + free(shared->CAfile); + free(shared); } } -#endif +} - if(SSL_CONN_CONFIG(CAfile)) { - /* set the trusted CA cert bundle file */ - gnutls_certificate_set_verify_flags(conn->ssl[sockindex].cred, - GNUTLS_VERIFY_ALLOW_X509_V1_CA_CRT); +static CURLcode gtls_populate_creds(struct Curl_cfilter *cf, + struct Curl_easy *data, + gnutls_certificate_credentials_t creds) +{ + struct ssl_primary_config *config = Curl_ssl_cf_get_primary_config(cf); + struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data); + int rc; - rc = gnutls_certificate_set_x509_trust_file(conn->ssl[sockindex].cred, - SSL_CONN_CONFIG(CAfile), - GNUTLS_X509_FMT_PEM); - if(rc < 0) { - infof(data, "error reading ca cert file %s (%s)\n", - SSL_CONN_CONFIG(CAfile), gnutls_strerror(rc)); - if(SSL_CONN_CONFIG(verifypeer)) - return CURLE_SSL_CACERT_BADFILE; + if(config->verifypeer) { + bool imported_native_ca = false; + + if(ssl_config->native_ca_store) { + rc = gnutls_certificate_set_x509_system_trust(creds); + 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; + } } - else - infof(data, "found %d certificates in %s\n", rc, - SSL_CONN_CONFIG(CAfile)); - } -#ifdef HAS_CAPATH - if(SSL_CONN_CONFIG(CApath)) { - /* set the trusted CA cert directory */ - rc = gnutls_certificate_set_x509_trust_dir(conn->ssl[sockindex].cred, - SSL_CONN_CONFIG(CApath), - GNUTLS_X509_FMT_PEM); - if(rc < 0) { - infof(data, "error reading ca cert file %s (%s)\n", - SSL_CONN_CONFIG(CApath), gnutls_strerror(rc)); - if(SSL_CONN_CONFIG(verifypeer)) - return CURLE_SSL_CACERT_BADFILE; + if(config->CAfile) { + /* set the trusted CA cert bundle file */ + gnutls_certificate_set_verify_flags(creds, + GNUTLS_VERIFY_ALLOW_X509_V1_CA_CRT); + + rc = gnutls_certificate_set_x509_trust_file(creds, + 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) { + ssl_config->certverifyresult = rc; + return CURLE_SSL_CACERT_BADFILE; + } + } + else + infof(data, "found %d certificates in %s", rc, config->CAfile); } - else - infof(data, "found %d certificates in %s\n", - rc, SSL_CONN_CONFIG(CApath)); - } -#endif -#ifdef CURL_CA_FALLBACK - /* use system ca certificate store as fallback */ - if(SSL_CONN_CONFIG(verifypeer) && - !(SSL_CONN_CONFIG(CAfile) || SSL_CONN_CONFIG(CApath))) { - gnutls_certificate_set_x509_system_trust(conn->ssl[sockindex].cred); + if(config->CApath) { + /* set the trusted CA cert directory */ + rc = gnutls_certificate_set_x509_trust_dir(creds, 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) { + ssl_config->certverifyresult = rc; + return CURLE_SSL_CACERT_BADFILE; + } + } + else + infof(data, "found %d certificates in %s", rc, config->CApath); + } } -#endif - if(SSL_SET_OPTION(CRLfile)) { + if(config->CRLfile) { /* set the CRL list file */ - rc = gnutls_certificate_set_x509_crl_file(conn->ssl[sockindex].cred, - SSL_SET_OPTION(CRLfile), + rc = gnutls_certificate_set_x509_crl_file(creds, config->CRLfile, GNUTLS_X509_FMT_PEM); if(rc < 0) { failf(data, "error reading crl file %s (%s)", - SSL_SET_OPTION(CRLfile), gnutls_strerror(rc)); + config->CRLfile, gnutls_strerror(rc)); return CURLE_SSL_CRL_BADFILE; } else - infof(data, "found %d CRL in %s\n", - rc, SSL_SET_OPTION(CRLfile)); + infof(data, "found %d CRL in %s", rc, config->CRLfile); + } + + return CURLE_OK; +} + +/* key to use at `multi->proto_hash` */ +#define MPROTO_GTLS_X509_KEY "tls:gtls:x509:share" + +static bool gtls_shared_creds_expired(const struct Curl_easy *data, + const struct gtls_shared_creds *sc) +{ + const struct ssl_general_config *cfg = &data->set.general_ssl; + struct curltime now = Curl_now(); + timediff_t elapsed_ms = Curl_timediff(now, sc->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 gtls_shared_creds_different(struct Curl_cfilter *cf, + const struct gtls_shared_creds *sc) +{ + struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); + if(!sc->CAfile || !conn_config->CAfile) + return sc->CAfile != conn_config->CAfile; + + return strcmp(sc->CAfile, conn_config->CAfile); +} + +static struct gtls_shared_creds* +gtls_get_cached_creds(struct Curl_cfilter *cf, struct Curl_easy *data) +{ + struct gtls_shared_creds *shared_creds; + + if(data->multi) { + shared_creds = Curl_hash_pick(&data->multi->proto_hash, + (void *)MPROTO_GTLS_X509_KEY, + sizeof(MPROTO_GTLS_X509_KEY)-1); + if(shared_creds && shared_creds->creds && + !gtls_shared_creds_expired(data, shared_creds) && + !gtls_shared_creds_different(cf, shared_creds)) { + return shared_creds; + } } + return NULL; +} + +static void gtls_shared_creds_hash_free(void *key, size_t key_len, void *p) +{ + struct gtls_shared_creds *sc = p; + DEBUGASSERT(key_len == (sizeof(MPROTO_GTLS_X509_KEY)-1)); + DEBUGASSERT(!memcmp(MPROTO_GTLS_X509_KEY, key, key_len)); + (void)key; + (void)key_len; + Curl_gtls_shared_creds_free(&sc); /* down reference */ +} + +static void gtls_set_cached_creds(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct gtls_shared_creds *sc) +{ + struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); + + DEBUGASSERT(sc); + DEBUGASSERT(sc->creds); + DEBUGASSERT(!sc->CAfile); + DEBUGASSERT(sc->refcount == 1); + if(!data->multi) + return; + + if(conn_config->CAfile) { + sc->CAfile = strdup(conn_config->CAfile); + if(!sc->CAfile) + return; + } + + if(Curl_gtls_shared_creds_up_ref(sc)) + return; + + if(!Curl_hash_add2(&data->multi->proto_hash, + (void *)MPROTO_GTLS_X509_KEY, + sizeof(MPROTO_GTLS_X509_KEY)-1, + sc, gtls_shared_creds_hash_free)) { + Curl_gtls_shared_creds_free(&sc); /* down reference again */ + return; + } +} + +CURLcode Curl_gtls_client_trust_setup(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct gtls_ctx *gtls) +{ + 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 gtls_shared_creds *cached_creds = NULL; + bool cache_criteria_met; + CURLcode result; + int rc; + + + /* 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 && + !conn_config->clientcert; /* GnuTLS adds client cert to its credentials! */ + + if(cache_criteria_met) + cached_creds = gtls_get_cached_creds(cf, data); + + if(cached_creds && !Curl_gtls_shared_creds_up_ref(cached_creds)) { + CURL_TRC_CF(data, cf, "using shared trust anchors and CRLs"); + Curl_gtls_shared_creds_free(>ls->shared_creds); + gtls->shared_creds = cached_creds; + rc = gnutls_credentials_set(gtls->session, GNUTLS_CRD_CERTIFICATE, + gtls->shared_creds->creds); + if(rc != GNUTLS_E_SUCCESS) { + failf(data, "gnutls_credentials_set() failed: %s", gnutls_strerror(rc)); + return CURLE_SSL_CONNECT_ERROR; + } + } + else { + CURL_TRC_CF(data, cf, "loading trust anchors and CRLs"); + result = gtls_populate_creds(cf, data, gtls->shared_creds->creds); + if(result) + return result; + gtls->shared_creds->trust_setup = TRUE; + if(cache_criteria_met) + gtls_set_cached_creds(cf, data, gtls->shared_creds); + } + return CURLE_OK; +} + +static void gtls_sessionid_free(void *sessionid, size_t idsize) +{ + (void)idsize; + free(sessionid); +} + +static CURLcode gtls_update_session_id(struct Curl_cfilter *cf, + struct Curl_easy *data, + gnutls_session_t session) +{ + struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data); + struct ssl_connect_data *connssl = cf->ctx; + CURLcode result = CURLE_OK; + + if(ssl_config->primary.cache_session) { + /* we always unconditionally get the session id here, as even if we + already got it from the cache and asked to use it in the connection, it + might've been rejected and then a new one is in use now and we need to + detect that. */ + void *connect_sessionid; + size_t connect_idsize = 0; + + /* get the session ID data size */ + gnutls_session_get_data(session, NULL, &connect_idsize); + connect_sessionid = malloc(connect_idsize); /* get a buffer for it */ + if(!connect_sessionid) { + return CURLE_OUT_OF_MEMORY; + } + else { + /* extract session ID to the allocated buffer */ + gnutls_session_get_data(session, connect_sessionid, &connect_idsize); + + CURL_TRC_CF(data, cf, "get session id (len=%zu) and store in cache", + connect_idsize); + Curl_ssl_sessionid_lock(data); + /* store this session id, takes ownership */ + result = Curl_ssl_set_sessionid(cf, data, &connssl->peer, + connect_sessionid, connect_idsize, + gtls_sessionid_free); + Curl_ssl_sessionid_unlock(data); + } + } + return result; +} + +static int gtls_handshake_cb(gnutls_session_t session, unsigned int htype, + unsigned when, unsigned int incoming, + const gnutls_datum_t *msg) +{ + struct Curl_cfilter *cf = gnutls_session_get_ptr(session); + + (void)msg; + (void)incoming; + if(when) { /* after message has been processed */ + struct Curl_easy *data = CF_DATA_CURRENT(cf); + if(data) { + CURL_TRC_CF(data, cf, "handshake: %s message type %d", + incoming ? "incoming" : "outgoing", htype); + switch(htype) { + case GNUTLS_HANDSHAKE_NEW_SESSION_TICKET: { + gtls_update_session_id(cf, data, session); + break; + } + default: + break; + } + } + } + return 0; +} + +static CURLcode gtls_client_init(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct ssl_peer *peer, + struct gtls_ctx *gtls) +{ + struct ssl_primary_config *config = Curl_ssl_cf_get_primary_config(cf); + struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data); + unsigned int init_flags; + int rc; + bool sni = TRUE; /* default is SNI enabled */ + const char *prioritylist; + const char *err = NULL; + const char *tls13support; + CURLcode result; + + if(!gtls_inited) + gtls_init(); + + if(config->version == CURL_SSLVERSION_SSLv2) { + failf(data, "GnuTLS does not support SSLv2"); + return CURLE_SSL_CONNECT_ERROR; + } + else if(config->version == CURL_SSLVERSION_SSLv3) + sni = FALSE; /* SSLv3 has no SNI */ + + /* allocate a shared creds struct */ + result = Curl_gtls_shared_creds_create(data, >ls->shared_creds); + if(result) + return result; + +#ifdef USE_GNUTLS_SRP + if(config->username && Curl_auth_allowed_to_host(data)) { + infof(data, "Using TLS-SRP username: %s", config->username); + + rc = gnutls_srp_allocate_client_credentials(>ls->srp_client_cred); + if(rc != GNUTLS_E_SUCCESS) { + failf(data, "gnutls_srp_allocate_client_cred() failed: %s", + gnutls_strerror(rc)); + return CURLE_OUT_OF_MEMORY; + } + + rc = gnutls_srp_set_client_credentials(gtls->srp_client_cred, + config->username, + config->password); + if(rc != GNUTLS_E_SUCCESS) { + failf(data, "gnutls_srp_set_client_cred() failed: %s", + gnutls_strerror(rc)); + return CURLE_BAD_FUNCTION_ARGUMENT; + } + } +#endif + + ssl_config->certverifyresult = 0; /* Initialize TLS session as a client */ init_flags = GNUTLS_CLIENT; +#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(&conn->ssl[sockindex].session, init_flags); +#if defined(GNUTLS_NO_STATUS_REQUEST) + if(!config->verifystatus) + /* Disable the "status_request" TLS extension, enabled by default since + GnuTLS 3.8.0. */ + init_flags |= GNUTLS_NO_STATUS_REQUEST; +#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; } - /* convenient assign */ - session = conn->ssl[sockindex].session; - - if((0 == Curl_inet_pton(AF_INET, hostname, &addr)) && -#ifdef ENABLE_IPV6 - (0 == Curl_inet_pton(AF_INET6, hostname, &addr)) && -#endif - sni && - (gnutls_server_name_set(session, GNUTLS_NAME_DNS, hostname, - strlen(hostname)) < 0)) - infof(data, "WARNING: failed to configure server name indication (SNI) " - "TLS extension\n"); + 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(session); + rc = gnutls_set_default_priority(gtls->session); if(rc != GNUTLS_E_SUCCESS) return CURLE_SSL_CONNECT_ERROR; -#ifndef USE_GNUTLS_PRIORITY_SET_DIRECT - rc = gnutls_cipher_set_priority(session, cipher_priority); - 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"); - /* Sets the priority on the certificate types supported by gnutls. Priority - is higher for types specified before others. After specifying the types - you want, you must append a 0. */ - rc = gnutls_certificate_type_set_priority(session, cert_type_priority); - if(rc != GNUTLS_E_SUCCESS) - return CURLE_SSL_CONNECT_ERROR; + /* Ensure +SRP comes at the *end* of all relevant strings so that it can be + * removed if a runtime error indicates that SRP is not supported by this + * GnuTLS version */ - if(SSL_CONN_CONFIG(cipher_list) != NULL) { - failf(data, "can't pass a custom cipher list to older GnuTLS" - " versions"); + 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; } - switch(SSL_CONN_CONFIG(version) { - case CURL_SSLVERSION_SSLv3: - protocol_priority[0] = GNUTLS_SSL3; - break; - case CURL_SSLVERSION_DEFAULT: - case CURL_SSLVERSION_TLSv1: - protocol_priority[0] = GNUTLS_TLS1_0; - protocol_priority[1] = GNUTLS_TLS1_1; - protocol_priority[2] = GNUTLS_TLS1_2; - break; - case CURL_SSLVERSION_TLSv1_0: - protocol_priority[0] = GNUTLS_TLS1_0; - break; - case CURL_SSLVERSION_TLSv1_1: - protocol_priority[0] = GNUTLS_TLS1_1; - break; - case CURL_SSLVERSION_TLSv1_2: - protocol_priority[0] = GNUTLS_TLS1_2; - break; - case CURL_SSLVERSION_TLSv1_3: - failf(data, "GnuTLS: TLS 1.3 is not yet supported"); - return CURLE_SSL_CONNECT_ERROR; - case CURL_SSLVERSION_SSLv2: - failf(data, "GnuTLS does not support SSLv2"); + 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; - default: - failf(data, "Unrecognized parameter passed via CURLOPT_SSLVERSION"); - return CURLE_SSL_CONNECT_ERROR; - } - rc = gnutls_protocol_set_priority(session, protocol_priority); - if(rc != GNUTLS_E_SUCCESS) { - failf(data, "Did you pass a valid GnuTLS cipher list?"); - return CURLE_SSL_CONNECT_ERROR; + } } -#else - /* 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 */ - switch(SSL_CONN_CONFIG(version)) { - case CURL_SSLVERSION_SSLv3: - prioritylist = GNUTLS_CIPHERS ":-VERS-TLS-ALL:+VERS-SSL3.0"; - sni = false; - break; - case CURL_SSLVERSION_DEFAULT: - case CURL_SSLVERSION_TLSv1: - prioritylist = GNUTLS_CIPHERS ":-VERS-SSL3.0:" GNUTLS_SRP; - break; - case CURL_SSLVERSION_TLSv1_0: - prioritylist = GNUTLS_CIPHERS ":-VERS-SSL3.0:-VERS-TLS-ALL:" - "+VERS-TLS1.0:" GNUTLS_SRP; - break; - case CURL_SSLVERSION_TLSv1_1: - prioritylist = GNUTLS_CIPHERS ":-VERS-SSL3.0:-VERS-TLS-ALL:" - "+VERS-TLS1.1:" GNUTLS_SRP; - break; - case CURL_SSLVERSION_TLSv1_2: - prioritylist = GNUTLS_CIPHERS ":-VERS-SSL3.0:-VERS-TLS-ALL:" - "+VERS-TLS1.2:" GNUTLS_SRP; - break; - case CURL_SSLVERSION_TLSv1_3: - failf(data, "GnuTLS: TLS 1.3 is not yet supported"); - return CURLE_SSL_CONNECT_ERROR; - case CURL_SSLVERSION_SSLv2: - failf(data, "GnuTLS does not support SSLv2"); - return CURLE_SSL_CONNECT_ERROR; - default: - failf(data, "Unrecognized parameter passed via CURLOPT_SSLVERSION"); - return CURLE_SSL_CONNECT_ERROR; - } - rc = gnutls_priority_set_direct(session, prioritylist, &err); - if((rc == GNUTLS_E_INVALID_REQUEST) && err) { - if(!strcmp(err, GNUTLS_SRP)) { - /* This GnuTLS was probably compiled without support for SRP. - * Note that fact and try again without it. */ - int validprioritylen = curlx_uztosi(err - prioritylist); - char *prioritycopy = strdup(prioritylist); - if(!prioritycopy) - return CURLE_OUT_OF_MEMORY; - - infof(data, "This GnuTLS does not support SRP\n"); - if(validprioritylen) - /* Remove the :+SRP */ - prioritycopy[validprioritylen - 1] = 0; - rc = gnutls_priority_set_direct(session, prioritycopy, &err); - free(prioritycopy); + /* At this point we know we have a supported TLS version, so set it */ + result = gnutls_set_ssl_version_min_max(data, peer, + 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) { + char *prioritysrp = aprintf("%s:" GNUTLS_SRP, prioritylist); + if(!prioritysrp) + return CURLE_OUT_OF_MEMORY; + 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; } -#endif -#ifdef HAS_ALPN - if(conn->bits.tls_enable_alpn) { - int cur = 0; - gnutls_datum_t protocols[2]; - -#ifdef USE_NGHTTP2 - if(data->set.httpversion >= CURL_HTTP_VERSION_2) { - protocols[cur].data = (unsigned char *)NGHTTP2_PROTO_VERSION_ID; - protocols[cur].size = NGHTTP2_PROTO_VERSION_ID_LEN; - cur++; - infof(data, "ALPN, offering %s\n", NGHTTP2_PROTO_VERSION_ID); + if(config->clientcert) { + if(!gtls->shared_creds->trust_setup) { + result = Curl_gtls_client_trust_setup(cf, data, gtls); + if(result) + return result; + } + if(ssl_config->cert_type && strcasecompare(ssl_config->cert_type, "P12")) { + rc = gnutls_certificate_set_x509_simple_pkcs12_file( + gtls->shared_creds->creds, config->clientcert, GNUTLS_X509_FMT_DER, + ssl_config->key_passwd ? ssl_config->key_passwd : ""); + if(rc != GNUTLS_E_SUCCESS) { + failf(data, + "error reading X.509 potentially-encrypted key or certificate " + "file: %s", + gnutls_strerror(rc)); + return CURLE_SSL_CONNECT_ERROR; + } } -#endif - - protocols[cur].data = (unsigned char *)ALPN_HTTP_1_1; - protocols[cur].size = ALPN_HTTP_1_1_LENGTH; - cur++; - infof(data, "ALPN, offering %s\n", ALPN_HTTP_1_1); - - gnutls_alpn_set_protocols(session, protocols, cur, 0); - } -#endif - - if(SSL_SET_OPTION(cert)) { - if(SSL_SET_OPTION(key_passwd)) { -#if HAVE_GNUTLS_CERTIFICATE_SET_X509_KEY_FILE2 + else 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( - conn->ssl[sockindex].cred, - SSL_SET_OPTION(cert), - SSL_SET_OPTION(key) ? - SSL_SET_OPTION(key) : SSL_SET_OPTION(cert), - do_file_type(SSL_SET_OPTION(cert_type)), - SSL_SET_OPTION(key_passwd), + gtls->shared_creds->creds, + config->clientcert, + ssl_config->key ? ssl_config->key : config->clientcert, + gnutls_do_file_type(ssl_config->cert_type), + ssl_config->key_passwd, supported_key_encryption_algorithms); if(rc != GNUTLS_E_SUCCESS) { failf(data, @@ -716,18 +967,13 @@ gtls_connect_step1(struct connectdata *conn, gnutls_strerror(rc)); return CURLE_SSL_CONNECT_ERROR; } -#else - failf(data, "gnutls lacks support for encrypted key files"); - return CURLE_SSL_CONNECT_ERROR; -#endif } else { if(gnutls_certificate_set_x509_key_file( - conn->ssl[sockindex].cred, - SSL_SET_OPTION(cert), - SSL_SET_OPTION(key) ? - SSL_SET_OPTION(key) : SSL_SET_OPTION(cert), - do_file_type(SSL_SET_OPTION(cert_type)) ) != + gtls->shared_creds->creds, + config->clientcert, + ssl_config->key ? ssl_config->key : config->clientcert, + gnutls_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; @@ -735,11 +981,11 @@ gtls_connect_step1(struct connectdata *conn, } } -#ifdef USE_TLS_SRP +#ifdef USE_GNUTLS_SRP /* put the credentials to the current session */ - if(SSL_SET_OPTION(authtype) == CURL_TLSAUTH_SRP) { - rc = gnutls_credentials_set(session, GNUTLS_CRD_SRP, - conn->ssl[sockindex].srp_client_cred); + 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; @@ -748,63 +994,162 @@ gtls_connect_step1(struct connectdata *conn, else #endif { - rc = gnutls_credentials_set(session, GNUTLS_CRD_CERTIFICATE, - conn->ssl[sockindex].cred); + rc = gnutls_credentials_set(gtls->session, GNUTLS_CRD_CERTIFICATE, + gtls->shared_creds->creds); if(rc != GNUTLS_E_SUCCESS) { failf(data, "gnutls_credentials_set() failed: %s", gnutls_strerror(rc)); return CURLE_SSL_CONNECT_ERROR; } } - if(conn->proxy_ssl[sockindex].use) { - transport_ptr = conn->proxy_ssl[sockindex].session; - gnutls_transport_push = Curl_gtls_push_ssl; - gnutls_transport_pull = Curl_gtls_pull_ssl; + 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; + } } - else { - /* file descriptor for the socket */ - transport_ptr = GNUTLS_INT_TO_POINTER_CAST(conn->sock[sockindex]); - gnutls_transport_push = Curl_gtls_push; - gnutls_transport_pull = Curl_gtls_pull; + + return CURLE_OK; +} + +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; } - /* set the connection handle */ - gnutls_transport_set_ptr(session, transport_ptr); + Curl_tls_keylog_write(label, crandom.data, secret->data, secret->size); + return 0; +} + +CURLcode Curl_gtls_ctx_init(struct gtls_ctx *gctx, + struct Curl_cfilter *cf, + struct Curl_easy *data, + struct ssl_peer *peer, + const unsigned char *alpn, size_t alpn_len, + Curl_gtls_ctx_setup_cb *cb_setup, + void *cb_user_data, + void *ssl_user_data) +{ + struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); + CURLcode result; - /* register callback functions to send and receive data. */ - gnutls_transport_set_push_function(session, gnutls_transport_push); - gnutls_transport_set_pull_function(session, gnutls_transport_pull); + DEBUGASSERT(gctx); + + result = gtls_client_init(cf, data, peer, gctx); + if(result) + return result; - /* lowat must be set to zero when using custom push and pull functions. */ - gnutls_transport_set_lowat(session, 0); + gnutls_session_set_ptr(gctx->session, ssl_user_data); -#ifdef HAS_OCSP - if(SSL_CONN_CONFIG(verifystatus)) { - rc = gnutls_ocsp_status_request_enable_client(session, NULL, 0, NULL); - if(rc != GNUTLS_E_SUCCESS) { - failf(data, "gnutls_ocsp_status_request_enable_client() failed: %d", rc); + if(cb_setup) { + result = cb_setup(cf, data, cb_user_data); + if(result) + return result; + } + + /* Open the file if a TLS or QUIC backend has not done this before. */ + Curl_tls_keylog_open(); + if(Curl_tls_keylog_enabled()) { + gnutls_session_set_keylog_function(gctx->session, keylog_callback); + } + + /* convert the ALPN string from our arguments to a list of strings + * that gnutls wants and will convert internally back to this very + * string for sending to the server. nice. */ + if(alpn && alpn_len) { + gnutls_datum_t alpns[5]; + size_t i, alen = alpn_len; + unsigned char *s = (unsigned char *)alpn; + unsigned char slen; + for(i = 0; (i < ARRAYSIZE(alpns)) && alen; ++i) { + slen = s[0]; + if(slen >= alen) + return CURLE_FAILED_INIT; + alpns[i].data = s + 1; + alpns[i].size = slen; + s += slen + 1; + alen -= (size_t)slen + 1; + } + if(alen) /* not all alpn chars used, wrong format or too many */ + return CURLE_FAILED_INIT; + if(i && gnutls_alpn_set_protocols(gctx->session, + alpns, (unsigned int)i, + GNUTLS_ALPN_MANDATORY)) { + failf(data, "failed setting ALPN"); return CURLE_SSL_CONNECT_ERROR; } } -#endif /* This might be a reconnect, so we check for a session ID in the cache to speed up things */ - if(data->set.general_ssl.sessionid) { + if(conn_config->cache_session) { void *ssl_sessionid; size_t ssl_idsize; - Curl_ssl_sessionid_lock(conn); - if(!Curl_ssl_getsessionid(conn, &ssl_sessionid, &ssl_idsize, sockindex)) { + Curl_ssl_sessionid_lock(data); + if(!Curl_ssl_getsessionid(cf, data, peer, &ssl_sessionid, &ssl_idsize)) { /* we got a session id, use it! */ - gnutls_session_set_data(session, ssl_sessionid, ssl_idsize); + int rc; + + rc = gnutls_session_set_data(gctx->session, ssl_sessionid, ssl_idsize); + if(rc < 0) + infof(data, "SSL failed to set session ID"); + else + infof(data, "SSL reusing session ID (size=%zu)", ssl_idsize); + } + Curl_ssl_sessionid_unlock(data); + } + return CURLE_OK; +} + +static CURLcode +gtls_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data) +{ + struct ssl_connect_data *connssl = cf->ctx; + struct gtls_ssl_backend_data *backend = + (struct gtls_ssl_backend_data *)connssl->backend; + struct alpn_proto_buf proto; + CURLcode result; + + DEBUGASSERT(backend); + DEBUGASSERT(ssl_connect_1 == connssl->connecting_state); + + if(connssl->state == ssl_connection_complete) + /* to make us tolerant against being called more than once for the + same connection */ + return CURLE_OK; - /* Informational message */ - infof(data, "SSL re-using session ID\n"); + memset(&proto, 0, sizeof(proto)); + if(connssl->alpn) { + result = Curl_alpn_to_proto_buf(&proto, connssl->alpn); + if(result) { + failf(data, "Error determining ALPN"); + return CURLE_SSL_CONNECT_ERROR; } - Curl_ssl_sessionid_unlock(conn); } + result = Curl_gtls_ctx_init(&backend->gtls, cf, data, &connssl->peer, + proto.data, proto.len, NULL, NULL, cf); + if(result) + return result; + + gnutls_handshake_set_hook_function(backend->gtls.session, + GNUTLS_HANDSHAKE_ANY, GNUTLS_HOOK_POST, + gtls_handshake_cb); + + /* register callback functions and handle to send and receive data. */ + gnutls_transport_set_ptr(backend->gtls.session, cf); + gnutls_transport_set_push_function(backend->gtls.session, gtls_push); + gnutls_transport_set_pull_function(backend->gtls.session, gtls_pull); + return CURLE_OK; } @@ -819,17 +1164,18 @@ static CURLcode pkp_pin_peer_pubkey(struct Curl_easy *data, gnutls_pubkey_t key = NULL; /* Result is returned to caller */ - int ret = 0; CURLcode result = CURLE_SSL_PINNEDPUBKEYNOTMATCH; - /* if a path wasn't specified, don't pin */ - if(NULL == pinnedpubkey) + /* if a path was not specified, do not pin */ + if(!pinnedpubkey) return CURLE_OK; - if(NULL == cert) + if(!cert) return result; do { + int ret; + /* Begin Gyrations to get the public key */ gnutls_pubkey_init(&key); @@ -842,7 +1188,7 @@ static CURLcode pkp_pin_peer_pubkey(struct Curl_easy *data, break; /* failed */ buff1 = malloc(len1); - if(NULL == buff1) + if(!buff1) break; /* failed */ len2 = len1; @@ -857,7 +1203,7 @@ static CURLcode pkp_pin_peer_pubkey(struct Curl_easy *data, result = Curl_pin_peer_pubkey(data, pinnedpubkey, buff1, len1); } while(0); - if(NULL != key) + if(key) gnutls_pubkey_deinit(key); Curl_safefree(buff1); @@ -865,42 +1211,42 @@ static CURLcode pkp_pin_peer_pubkey(struct Curl_easy *data, return result; } -static Curl_recv gtls_recv; -static Curl_send gtls_send; - -static CURLcode -gtls_connect_step3(struct connectdata *conn, - int sockindex) +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; - char certbuf[256] = ""; /* big enough? */ + gnutls_datum_t certfields; + char certname[65] = ""; /* limited to 64 chars by ASN.1 */ size_t size; - unsigned int algo; - unsigned int bits; time_t certclock; - const char *ptr; - struct Curl_easy *data = conn->data; - gnutls_session_t session = conn->ssl[sockindex].session; int rc; -#ifdef HAS_ALPN - gnutls_datum_t proto; -#endif CURLcode result = CURLE_OK; +#ifndef CURL_DISABLE_VERBOSE_STRINGS + const char *ptr; + int algo; + unsigned int bits; gnutls_protocol_t version = gnutls_protocol_get_version(session); - const char * const hostname = SSL_IS_PROXY() ? conn->http_proxy.host.name : - conn->host.name; +#endif + long * const certverifyresult = &ssl_config->certverifyresult; +#ifndef CURL_DISABLE_VERBOSE_STRINGS /* 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\n", + infof(data, "SSL connection using %s / %s", gnutls_protocol_get_name(version), ptr); +#endif /* This function will return the peer's raw certificate (chain) as sent by the peer. These certificates are in raw format (DER encoded for @@ -910,32 +1256,31 @@ gtls_connect_step3(struct connectdata *conn, chainp = gnutls_certificate_get_peers(session, &cert_list_size); if(!chainp) { - if(SSL_CONN_CONFIG(verifypeer) || - SSL_CONN_CONFIG(verifyhost) || - SSL_SET_OPTION(issuercert)) { -#ifdef USE_TLS_SRP - if(SSL_SET_OPTION(authtype) == CURL_TLSAUTH_SRP - && SSL_SET_OPTION(username) != NULL - && !SSL_CONN_CONFIG(verifypeer) - && gnutls_cipher_get(session)) { + 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_TLS_SRP +#ifdef USE_GNUTLS_SRP } #endif } - infof(data, "\t common name: WARNING couldn't obtain\n"); + infof(data, " common name: WARNING could not obtain"); } if(data->set.ssl.certinfo && chainp) { unsigned int i; - result = Curl_ssl_init_certinfo(data, cert_list_size); + result = Curl_ssl_init_certinfo(data, (int)cert_list_size); if(result) return result; @@ -943,13 +1288,13 @@ gtls_connect_step3(struct connectdata *conn, const char *beg = (const char *) chainp[i].data; const char *end = beg + chainp[i].size; - result = Curl_extract_certinfo(conn, i, beg, end); + result = Curl_extract_certinfo(data, (int)i, beg, end); if(result) return result; } } - if(SSL_CONN_CONFIG(verifypeer)) { + 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 @@ -960,131 +1305,135 @@ gtls_connect_step3(struct connectdata *conn, 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(SSL_CONN_CONFIG(verifypeer)) { - failf(data, "server certificate verification failed. CAfile: %s " - "CRLfile: %s", SSL_CONN_CONFIG(CAfile) ? SSL_CONN_CONFIG(CAfile): - "none", - SSL_SET_OPTION(CRLfile)?SSL_SET_OPTION(CRLfile):"none"); - return CURLE_SSL_CACERT; + if(config->verifypeer) { + const char *cause = "certificate error, no details available"; + if(verify_status & GNUTLS_CERT_EXPIRED) + cause = "certificate has expired"; + else if(verify_status & GNUTLS_CERT_SIGNER_NOT_FOUND) + cause = "certificate signer not trusted"; + else if(verify_status & GNUTLS_CERT_INSECURE_ALGORITHM) + cause = "certificate uses insecure algorithm"; + else if(verify_status & GNUTLS_CERT_INVALID_OCSP_STATUS) + cause = "attached OCSP status response is invalid"; + failf(data, "server verification failed: %s. (CAfile: %s " + "CRLfile: %s)", cause, + config->CAfile ? config->CAfile : "none", + ssl_config->primary.CRLfile ? + ssl_config->primary.CRLfile : "none"); + return CURLE_PEER_FAILED_VERIFICATION; } else - infof(data, "\t server certificate verification FAILED\n"); + infof(data, " server certificate verification FAILED"); } else - infof(data, "\t server certificate verification OK\n"); + infof(data, " server certificate verification OK"); } else - infof(data, "\t server certificate verification SKIPPED\n"); + infof(data, " server certificate verification SKIPPED"); -#ifdef HAS_OCSP - if(SSL_CONN_CONFIG(verifystatus)) { - if(gnutls_ocsp_status_request_is_checked(session, 0) == 0) { - gnutls_datum_t status_request; - gnutls_ocsp_resp_t ocsp_resp; + if(config->verifystatus) { + gnutls_datum_t status_request; + gnutls_ocsp_resp_t ocsp_resp; + gnutls_ocsp_cert_status_t status; + gnutls_x509_crl_reason_t reason; - gnutls_ocsp_cert_status_t status; - gnutls_x509_crl_reason_t reason; + rc = gnutls_ocsp_status_request_get(session, &status_request); - rc = gnutls_ocsp_status_request_get(session, &status_request); + if(rc == GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE) { + failf(data, "No OCSP response received"); + return CURLE_SSL_INVALIDCERTSTATUS; + } - infof(data, "\t server certificate status verification FAILED\n"); + if(rc < 0) { + failf(data, "Invalid OCSP response received"); + return CURLE_SSL_INVALIDCERTSTATUS; + } - if(rc == GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE) { - failf(data, "No OCSP response received"); - return CURLE_SSL_INVALIDCERTSTATUS; - } + gnutls_ocsp_resp_init(&ocsp_resp); - if(rc < 0) { - failf(data, "Invalid OCSP response received"); - return CURLE_SSL_INVALIDCERTSTATUS; - } + rc = gnutls_ocsp_resp_import(ocsp_resp, &status_request); + if(rc < 0) { + failf(data, "Invalid OCSP response received"); + return CURLE_SSL_INVALIDCERTSTATUS; + } - gnutls_ocsp_resp_init(&ocsp_resp); + (void)gnutls_ocsp_resp_get_single(ocsp_resp, 0, NULL, NULL, NULL, NULL, + &status, NULL, NULL, NULL, &reason); - rc = gnutls_ocsp_resp_import(ocsp_resp, &status_request); - if(rc < 0) { - failf(data, "Invalid OCSP response received"); - return CURLE_SSL_INVALIDCERTSTATUS; - } + switch(status) { + case GNUTLS_OCSP_CERT_GOOD: + break; - rc = gnutls_ocsp_resp_get_single(ocsp_resp, 0, NULL, NULL, NULL, NULL, - &status, NULL, NULL, NULL, &reason); + case GNUTLS_OCSP_CERT_REVOKED: { + const char *crl_reason; - switch(status) { - case GNUTLS_OCSP_CERT_GOOD: + switch(reason) { + default: + case GNUTLS_X509_CRLREASON_UNSPECIFIED: + crl_reason = "unspecified reason"; 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_KEYCOMPROMISE: + crl_reason = "private key compromised"; + break; - case GNUTLS_X509_CRLREASON_SUPERSEDED: - crl_reason = "certificate superseded"; - break; + case GNUTLS_X509_CRLREASON_CACOMPROMISE: + crl_reason = "CA compromised"; + break; - case GNUTLS_X509_CRLREASON_CESSATIONOFOPERATION: - crl_reason = "operation has ceased"; - break; + case GNUTLS_X509_CRLREASON_AFFILIATIONCHANGED: + crl_reason = "affiliation has changed"; + break; - case GNUTLS_X509_CRLREASON_CERTIFICATEHOLD: - crl_reason = "certificate is on hold"; - break; + case GNUTLS_X509_CRLREASON_SUPERSEDED: + crl_reason = "certificate superseded"; + break; - case GNUTLS_X509_CRLREASON_REMOVEFROMCRL: - crl_reason = "will be removed from delta CRL"; - break; + case GNUTLS_X509_CRLREASON_CESSATIONOFOPERATION: + crl_reason = "operation has ceased"; + break; - case GNUTLS_X509_CRLREASON_PRIVILEGEWITHDRAWN: - crl_reason = "privilege withdrawn"; - break; + case GNUTLS_X509_CRLREASON_CERTIFICATEHOLD: + crl_reason = "certificate is on hold"; + break; - case GNUTLS_X509_CRLREASON_AACOMPROMISE: - crl_reason = "AA compromised"; - break; - } + case GNUTLS_X509_CRLREASON_REMOVEFROMCRL: + crl_reason = "will be removed from delta CRL"; + break; - failf(data, "Server certificate was revoked: %s", crl_reason); + case GNUTLS_X509_CRLREASON_PRIVILEGEWITHDRAWN: + crl_reason = "privilege withdrawn"; break; - } - default: - case GNUTLS_OCSP_CERT_UNKNOWN: - failf(data, "Server certificate status is unknown"); + case GNUTLS_X509_CRLREASON_AACOMPROMISE: + crl_reason = "AA compromised"; break; } - gnutls_ocsp_resp_deinit(ocsp_resp); + 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); + if(status != GNUTLS_OCSP_CERT_GOOD) return CURLE_SSL_INVALIDCERTSTATUS; - } - else - infof(data, "\t server certificate status verification OK\n"); } else - infof(data, "\t server certificate status verification SKIPPED\n"); -#endif + infof(data, " server certificate status verification SKIPPED"); /* initialize an X.509 certificate structure. */ gnutls_x509_crt_init(&x509_cert); @@ -1094,31 +1443,31 @@ gtls_connect_step3(struct connectdata *conn, gnutls_x509_crt_t format */ gnutls_x509_crt_import(x509_cert, chainp, GNUTLS_X509_FMT_DER); - if(SSL_SET_OPTION(issuercert)) { + if(config->issuercert) { gnutls_x509_crt_init(&x509_issuer); - issuerp = load_file(SSL_SET_OPTION(issuercert)); + 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); + rc = (int)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)", - SSL_SET_OPTION(issuercert)?SSL_SET_OPTION(issuercert):"none"); + config->issuercert ? config->issuercert : "none"); gnutls_x509_crt_deinit(x509_cert); return CURLE_SSL_ISSUER_ERROR; } - infof(data, "\t server certificate issuer check OK (Issuer Cert: %s)\n", - SSL_SET_OPTION(issuercert)?SSL_SET_OPTION(issuercert):"none"); + infof(data, " server certificate issuer check OK (Issuer Cert: %s)", + config->issuercert ? config->issuercert : "none"); } - size=sizeof(certbuf); + 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, - certbuf, + certname, &size); if(rc) { - infof(data, "error fetching CN from cert:%s\n", + infof(data, "error fetching CN from cert:%s", gnutls_strerror(rc)); } @@ -1127,35 +1476,41 @@ gtls_connect_step3(struct connectdata *conn, 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, hostname); + + /* This function does not handle trailing dots, so if we have an SNI name + use that and fallback to the hostname only if there is no SNI (like for + IP addresses) */ + rc = (int)gnutls_x509_crt_check_hostname(x509_cert, + peer->sni ? peer->sni : + peer->hostname); #if GNUTLS_VERSION_NUMBER < 0x030306 - /* Before 3.3.6, gnutls_x509_crt_check_hostname() didn't check IP + /* Before 3.3.6, gnutls_x509_crt_check_hostname() did not check IP addresses. */ if(!rc) { -#ifdef ENABLE_IPV6 +#ifdef USE_IPV6 #define use_addr in6_addr #else #define use_addr in_addr #endif unsigned char addrbuf[sizeof(struct use_addr)]; - unsigned char certaddr[sizeof(struct use_addr)]; - size_t addrlen = 0, certaddrlen; - int i; - int ret = 0; + size_t addrlen = 0; - if(Curl_inet_pton(AF_INET, hostname, addrbuf) > 0) + if(Curl_inet_pton(AF_INET, peer->hostname, addrbuf) > 0) addrlen = 4; -#ifdef ENABLE_IPV6 - else if(Curl_inet_pton(AF_INET6, hostname, addrbuf) > 0) +#ifdef USE_IPV6 + else if(Curl_inet_pton(AF_INET6, peer->hostname, addrbuf) > 0) addrlen = 16; #endif if(addrlen) { - for(i=0; ; i++) { - certaddrlen = sizeof(certaddr); - ret = gnutls_x509_crt_get_subject_alt_name(x509_cert, i, certaddr, - &certaddrlen, NULL); - /* If this happens, it wasn't an IP address. */ + 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 was not an IP address. */ if(ret == GNUTLS_E_SHORT_MEMORY_BUFFER) continue; if(ret < 0) @@ -1171,79 +1526,78 @@ gtls_connect_step3(struct connectdata *conn, } #endif if(!rc) { - const char * const dispname = SSL_IS_PROXY() ? - conn->http_proxy.host.dispname : conn->host.dispname; - - if(SSL_CONN_CONFIG(verifyhost)) { + if(config->verifyhost) { failf(data, "SSL: certificate subject name (%s) does not match " - "target host name '%s'", certbuf, dispname); + "target hostname '%s'", certname, peer->dispname); gnutls_x509_crt_deinit(x509_cert); return CURLE_PEER_FAILED_VERIFICATION; } else - infof(data, "\t common name: %s (does not match '%s')\n", - certbuf, dispname); + infof(data, " common name: %s (does not match '%s')", + certname, peer->dispname); } else - infof(data, "\t common name: %s (matched)\n", certbuf); + 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(SSL_CONN_CONFIG(verifypeer)) { + 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, "\t server certificate expiration date verify FAILED\n"); + infof(data, " server certificate expiration date verify FAILED"); } else { if(certclock < time(NULL)) { - if(SSL_CONN_CONFIG(verifypeer)) { + 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, "\t server certificate expiration date FAILED\n"); + infof(data, " server certificate expiration date FAILED"); } else - infof(data, "\t server certificate expiration date OK\n"); + infof(data, " server certificate expiration date OK"); } certclock = gnutls_x509_crt_get_activation_time(x509_cert); if(certclock == (time_t)-1) { - if(SSL_CONN_CONFIG(verifypeer)) { + 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, "\t server certificate activation date verify FAILED\n"); + infof(data, " server certificate activation date verify FAILED"); } else { if(certclock > time(NULL)) { - if(SSL_CONN_CONFIG(verifypeer)) { + 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, "\t server certificate activation date FAILED\n"); + infof(data, " server certificate activation date FAILED"); } else - infof(data, "\t server certificate activation date OK\n"); + infof(data, " server certificate activation date OK"); } - ptr = SSL_IS_PROXY() ? data->set.str[STRING_SSL_PINNEDPUBLICKEY_PROXY] : - data->set.str[STRING_SSL_PINNEDPUBLICKEY_ORIG]; - if(ptr) { - result = pkp_pin_peer_pubkey(data, x509_cert, ptr); + 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!"); + failf(data, "SSL: public key does not match pinned public key"); gnutls_x509_crt_deinit(x509_cert); return result; } @@ -1259,169 +1613,158 @@ gtls_connect_step3(struct connectdata *conn, */ +#ifndef CURL_DISABLE_VERBOSE_STRINGS /* public key algorithm's parameters */ algo = gnutls_x509_crt_get_pk_algorithm(x509_cert, &bits); - infof(data, "\t certificate public key: %s\n", - gnutls_pk_algorithm_get_name(algo)); + infof(data, " certificate public key: %s", + gnutls_pk_algorithm_get_name((gnutls_pk_algorithm_t)algo)); /* version of the X.509 certificate. */ - infof(data, "\t certificate version: #%d\n", + infof(data, " certificate version: #%d", gnutls_x509_crt_get_version(x509_cert)); - size = sizeof(certbuf); - gnutls_x509_crt_get_dn(x509_cert, certbuf, &size); - infof(data, "\t subject: %s\n", certbuf); - - certclock = gnutls_x509_crt_get_activation_time(x509_cert); - showtime(data, "start date", certclock); + 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_expiration_time(x509_cert); - showtime(data, "expire date", certclock); + certclock = gnutls_x509_crt_get_activation_time(x509_cert); + showtime(data, "start date", certclock); - size = sizeof(certbuf); - gnutls_x509_crt_get_issuer_dn(x509_cert, certbuf, &size); - infof(data, "\t issuer: %s\n", certbuf); + certclock = gnutls_x509_crt_get_expiration_time(x509_cert); + showtime(data, "expire date", certclock); - gnutls_x509_crt_deinit(x509_cert); + gnutls_free(certfields.data); + } - /* compression algorithm (if any) */ - ptr = gnutls_compression_get_name(gnutls_compression_get(session)); - /* the *_get_name() says "NULL" if GNUTLS_COMP_NULL is returned */ - infof(data, "\t compression: %s\n", ptr); + 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); -#ifdef HAS_ALPN - if(conn->bits.tls_enable_alpn) { - rc = gnutls_alpn_get_selected_protocol(session, &proto); - if(rc == 0) { - infof(data, "ALPN, server accepted to use %.*s\n", proto.size, - proto.data); - -#ifdef USE_NGHTTP2 - if(proto.size == NGHTTP2_PROTO_VERSION_ID_LEN && - !memcmp(NGHTTP2_PROTO_VERSION_ID, proto.data, - NGHTTP2_PROTO_VERSION_ID_LEN)) { - conn->negnpn = CURL_HTTP_VERSION_2; - } - else -#endif - if(proto.size == ALPN_HTTP_1_1_LENGTH && - !memcmp(ALPN_HTTP_1_1, proto.data, ALPN_HTTP_1_1_LENGTH)) { - conn->negnpn = CURL_HTTP_VERSION_1_1; - } - } - else - infof(data, "ALPN, server did not agree to a protocol\n"); + gnutls_free(certfields.data); } #endif - conn->ssl[sockindex].state = ssl_connection_complete; - conn->recv[sockindex] = gtls_recv; - conn->send[sockindex] = gtls_send; + gnutls_x509_crt_deinit(x509_cert); - if(data->set.general_ssl.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. */ - bool incache; - void *ssl_sessionid; - void *connect_sessionid; - size_t connect_idsize = 0; + return result; +} - /* get the session ID data size */ - gnutls_session_get_data(session, NULL, &connect_idsize); - connect_sessionid = malloc(connect_idsize); /* get a buffer for it */ +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); +#ifndef CURL_DISABLE_PROXY + const char *pinned_key = Curl_ssl_cf_is_proxy(cf) ? + data->set.str[STRING_SSL_PINNEDPUBLICKEY_PROXY] : + data->set.str[STRING_SSL_PINNEDPUBLICKEY]; +#else + const char *pinned_key = data->set.str[STRING_SSL_PINNEDPUBLICKEY]; +#endif + CURLcode result; - if(connect_sessionid) { - /* extract session ID to the allocated buffer */ - gnutls_session_get_data(session, connect_sessionid, &connect_idsize); + result = Curl_gtls_verifyserver(data, session, conn_config, ssl_config, + &connssl->peer, pinned_key); + if(result) + goto out; - Curl_ssl_sessionid_lock(conn); - incache = !(Curl_ssl_getsessionid(conn, &ssl_sessionid, NULL, - sockindex)); - 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(conn, ssl_sessionid); - } + if(connssl->alpn) { + gnutls_datum_t proto; + int rc; - /* store this session id */ - result = Curl_ssl_addsessionid(conn, connect_sessionid, connect_idsize, - sockindex); - Curl_ssl_sessionid_unlock(conn); - if(result) { - free(connect_sessionid); - result = CURLE_OUT_OF_MEMORY; - } - } + rc = gnutls_alpn_get_selected_protocol(session, &proto); + if(rc == 0) + Curl_alpn_set_negotiated(cf, data, proto.data, proto.size); else - result = CURLE_OUT_OF_MEMORY; + Curl_alpn_set_negotiated(cf, data, NULL, 0); } + /* Only on TLSv1.2 or lower do we have the session id now. For + * TLSv1.3 we get it via a SESSION_TICKET message that arrives later. */ + if(gnutls_protocol_get_version(session) < GNUTLS_TLS1_3) + result = gtls_update_session_id(cf, data, session); + +out: return result; } - /* * 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). + 'ssl_connect_2' (doing handshake with the server), and + 'ssl_connect_3' (verifying and getting stats). */ static CURLcode -gtls_connect_common(struct connectdata *conn, - int sockindex, +gtls_connect_common(struct Curl_cfilter *cf, + struct Curl_easy *data, bool nonblocking, bool *done) { - int rc; - struct ssl_connect_data *connssl = &conn->ssl[sockindex]; + struct ssl_connect_data *connssl = cf->ctx; + CURLcode rc; + CURLcode result = CURLE_OK; /* Initiate the connection, if not already done */ - if(ssl_connect_1==connssl->connecting_state) { - rc = gtls_connect_step1(conn, sockindex); - if(rc) - return rc; + if(ssl_connect_1 == connssl->connecting_state) { + rc = gtls_connect_step1(cf, data); + if(rc) { + result = rc; + goto out; + } } - rc = handshake(conn, sockindex, TRUE, nonblocking); - if(rc) + rc = handshake(cf, data, TRUE, nonblocking); + if(rc) { /* handshake() sets its own error message with failf() */ - return rc; + result = rc; + goto out; + } /* Finish connecting once the handshake is done */ - if(ssl_connect_1==connssl->connecting_state) { - rc = gtls_connect_step3(conn, sockindex); - if(rc) - return rc; + 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; } - *done = ssl_connect_1==connssl->connecting_state; +out: + *done = ssl_connect_1 == connssl->connecting_state; - return CURLE_OK; + return result; } -CURLcode -Curl_gtls_connect_nonblocking(struct connectdata *conn, - int sockindex, - bool *done) +static CURLcode gtls_connect_nonblocking(struct Curl_cfilter *cf, + struct Curl_easy *data, + bool *done) { - return gtls_connect_common(conn, sockindex, TRUE, done); + return gtls_connect_common(cf, data, TRUE, done); } -CURLcode -Curl_gtls_connect(struct connectdata *conn, - int sockindex) - +static CURLcode gtls_connect(struct Curl_cfilter *cf, + struct Curl_easy *data) { CURLcode result; bool done = FALSE; - result = gtls_connect_common(conn, sockindex, FALSE, &done); + result = gtls_connect_common(cf, data, FALSE, &done); if(result) return result; @@ -1430,271 +1773,290 @@ Curl_gtls_connect(struct connectdata *conn, return CURLE_OK; } -bool Curl_gtls_data_pending(const struct connectdata *conn, int connindex) +static bool gtls_data_pending(struct Curl_cfilter *cf, + const struct Curl_easy *data) { - bool res = FALSE; - if(conn->ssl[connindex].session && - 0 != gnutls_record_check_pending(conn->ssl[connindex].session)) - res = TRUE; - - if(conn->proxy_ssl[connindex].session && - 0 != gnutls_record_check_pending(conn->proxy_ssl[connindex].session)) - res = TRUE; + struct ssl_connect_data *ctx = cf->ctx; + struct gtls_ssl_backend_data *backend; - return res; + (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 connectdata *conn, - int sockindex, - const void *mem, - size_t len, +static ssize_t gtls_send(struct Curl_cfilter *cf, + struct Curl_easy *data, + const void *buf, + size_t blen, CURLcode *curlcode) { - ssize_t rc = gnutls_record_send(conn->ssl[sockindex].session, mem, len); + struct ssl_connect_data *connssl = cf->ctx; + struct gtls_ssl_backend_data *backend = + (struct gtls_ssl_backend_data *)connssl->backend; + ssize_t rc; + size_t nwritten, total_written = 0; - if(rc < 0) { - *curlcode = (rc == GNUTLS_E_AGAIN) - ? CURLE_AGAIN - : CURLE_SEND_ERROR; - - rc = -1; - } + (void)data; + DEBUGASSERT(backend); + while(blen) { + backend->gtls.io_result = CURLE_OK; + rc = gnutls_record_send(backend->gtls.session, buf, blen); - return rc; -} + if(rc < 0) { + if(total_written && (rc == GNUTLS_E_AGAIN)) { + *curlcode = CURLE_OK; + rc = (ssize_t)total_written; + goto out; + } + *curlcode = (rc == GNUTLS_E_AGAIN) ? + CURLE_AGAIN : + (backend->gtls.io_result ? backend->gtls.io_result : CURLE_SEND_ERROR); -static void close_one(struct ssl_connect_data *ssl) -{ - if(ssl->session) { - gnutls_bye(ssl->session, GNUTLS_SHUT_RDWR); - gnutls_deinit(ssl->session); - ssl->session = NULL; - } - if(ssl->cred) { - gnutls_certificate_free_credentials(ssl->cred); - ssl->cred = NULL; - } -#ifdef USE_TLS_SRP - if(ssl->srp_client_cred) { - gnutls_srp_free_client_credentials(ssl->srp_client_cred); - ssl->srp_client_cred = NULL; + rc = -1; + goto out; + } + nwritten = (size_t)rc; + total_written += nwritten; + DEBUGASSERT(nwritten <= blen); + buf = (char *)buf + nwritten; + blen -= nwritten; } -#endif -} + rc = total_written; -void Curl_gtls_close(struct connectdata *conn, int sockindex) -{ - close_one(&conn->ssl[sockindex]); - close_one(&conn->proxy_ssl[sockindex]); +out: + return rc; } /* * This function is called to shut down the SSL layer but keep the * socket open (CCC - Clear Command Channel) */ -int Curl_gtls_shutdown(struct connectdata *conn, int sockindex) +static CURLcode gtls_shutdown(struct Curl_cfilter *cf, + struct Curl_easy *data, + bool send_shutdown, bool *done) { - ssize_t result; - int retval = 0; - struct Curl_easy *data = conn->data; - int done = 0; - char buf[120]; - - /* 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(conn->ssl[sockindex].session, GNUTLS_SHUT_WR); - - if(conn->ssl[sockindex].session) { - while(!done) { - int what = SOCKET_READABLE(conn->sock[sockindex], - 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(conn->ssl[sockindex].session, - buf, sizeof(buf)); - switch(result) { - case 0: - /* This is the expected response. There was no data but only - the close notify alert */ - done = 1; - break; - case GNUTLS_E_AGAIN: - case GNUTLS_E_INTERRUPTED: - infof(data, "GNUTLS_E_AGAIN || GNUTLS_E_INTERRUPTED\n"); - break; - default: - retval = -1; - done = 1; - break; - } - } - else if(0 == what) { - /* timeout */ - failf(data, "SSL shutdown timeout"); - done = 1; - break; + struct ssl_connect_data *connssl = cf->ctx; + struct gtls_ssl_backend_data *backend = + (struct gtls_ssl_backend_data *)connssl->backend; + char buf[1024]; + CURLcode result = CURLE_OK; + ssize_t nread; + size_t i; + + DEBUGASSERT(backend); + if(!backend->gtls.session || cf->shutdown) { + *done = TRUE; + goto out; + } + + connssl->io_need = CURL_SSL_IO_NEED_NONE; + *done = FALSE; + + if(!backend->gtls.sent_shutdown) { + /* do this only once */ + backend->gtls.sent_shutdown = TRUE; + if(send_shutdown) { + int ret = gnutls_bye(backend->gtls.session, GNUTLS_SHUT_RDWR); + if((ret == GNUTLS_E_AGAIN) || (ret == GNUTLS_E_INTERRUPTED)) { + CURL_TRC_CF(data, cf, "SSL shutdown, gnutls_bye EAGAIN"); + connssl->io_need = gnutls_record_get_direction(backend->gtls.session) ? + CURL_SSL_IO_NEED_SEND : CURL_SSL_IO_NEED_RECV; + backend->gtls.sent_shutdown = FALSE; + result = CURLE_OK; + goto out; } - else { - /* anything that gets here is fatally bad */ - failf(data, "select/poll on SSL socket, errno: %d", SOCKERRNO); - retval = -1; - done = 1; + if(ret != GNUTLS_E_SUCCESS) { + CURL_TRC_CF(data, cf, "SSL shutdown, gnutls_bye error: '%s'(%d)", + gnutls_strerror((int)ret), (int)ret); + result = CURLE_RECV_ERROR; + goto out; } } - gnutls_deinit(conn->ssl[sockindex].session); } - gnutls_certificate_free_credentials(conn->ssl[sockindex].cred); -#ifdef USE_TLS_SRP - if(SSL_SET_OPTION(authtype) == CURL_TLSAUTH_SRP - && SSL_SET_OPTION(username) != NULL) - gnutls_srp_free_client_credentials(conn->ssl[sockindex].srp_client_cred); -#endif + /* SSL should now have started the shutdown from our side. Since it + * was not complete, we are lacking the close notify from the server. */ + for(i = 0; i < 10; ++i) { + nread = gnutls_record_recv(backend->gtls.session, buf, sizeof(buf)); + if(nread <= 0) + break; + } + if(nread > 0) { + /* still data coming in? */ + } + else if(nread == 0) { + /* We got the close notify alert and are done. */ + *done = TRUE; + } + else if((nread == GNUTLS_E_AGAIN) || (nread == GNUTLS_E_INTERRUPTED)) { + connssl->io_need = gnutls_record_get_direction(backend->gtls.session) ? + CURL_SSL_IO_NEED_SEND : CURL_SSL_IO_NEED_RECV; + } + else { + CURL_TRC_CF(data, cf, "SSL shutdown, error: '%s'(%d)", + gnutls_strerror((int)nread), (int)nread); + result = CURLE_RECV_ERROR; + } - conn->ssl[sockindex].cred = NULL; - conn->ssl[sockindex].session = NULL; +out: + cf->shutdown = (result || *done); + return result; +} - return retval; +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); + CURL_TRC_CF(data, cf, "close"); + if(backend->gtls.session) { + gnutls_deinit(backend->gtls.session); + backend->gtls.session = NULL; + } + if(backend->gtls.shared_creds) { + Curl_gtls_shared_creds_free(&backend->gtls.shared_creds); + } +#ifdef USE_GNUTLS_SRP + if(backend->gtls.srp_client_cred) { + gnutls_srp_free_client_credentials(backend->gtls.srp_client_cred); + backend->gtls.srp_client_cred = NULL; + } +#endif } -static ssize_t gtls_recv(struct connectdata *conn, /* connection data */ - int num, /* socketindex */ - char *buf, /* store read data here */ - size_t buffersize, /* max amount to read */ +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; - ret = gnutls_record_recv(conn->ssl[num].session, buf, buffersize); + (void)data; + DEBUGASSERT(backend); + + backend->gtls.io_result = CURLE_OK; + ret = gnutls_record_recv(backend->gtls.session, buf, buffersize); if((ret == GNUTLS_E_AGAIN) || (ret == GNUTLS_E_INTERRUPTED)) { *curlcode = CURLE_AGAIN; - return -1; + 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(conn, num, FALSE, FALSE); + 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 */ - return -1; + ret = -1; + goto out; } if(ret < 0) { - failf(conn->data, "GnuTLS recv error (%d): %s", + failf(data, "GnuTLS recv error (%d): %s", (int)ret, gnutls_strerror((int)ret)); - *curlcode = CURLE_RECV_ERROR; - return -1; + *curlcode = backend->gtls.io_result ? + backend->gtls.io_result : CURLE_RECV_ERROR; + ret = -1; + goto out; } +out: return ret; } -void Curl_gtls_session_free(void *ptr) -{ - free(ptr); -} - -size_t Curl_gtls_version(char *buffer, size_t size) +static size_t gtls_version(char *buffer, size_t size) { - return snprintf(buffer, size, "GnuTLS/%s", gnutls_check_version(NULL)); -} - -#ifndef USE_GNUTLS_NETTLE -static int Curl_gtls_seed(struct Curl_easy *data) -{ - /* we have the "SSL is seeded" boolean static to prevent multiple - time-consuming seedings in vain */ - static bool ssl_seeded = FALSE; - - /* Quickly add a bit of entropy */ - gcry_fast_random_poll(); - - if(!ssl_seeded || data->set.str[STRING_SSL_RANDOM_FILE] || - data->set.str[STRING_SSL_EGDSOCKET]) { - - /* TODO: to a good job seeding the RNG - This may involve the gcry_control function and these options: - GCRYCTL_SET_RANDOM_SEED_FILE - GCRYCTL_SET_RNDEGD_SOCKET - */ - ssl_seeded = TRUE; - } - return 0; + return msnprintf(buffer, size, "GnuTLS/%s", gnutls_check_version(NULL)); } -#endif /* data might be NULL! */ -CURLcode Curl_gtls_random(struct Curl_easy *data, - unsigned char *entropy, - size_t length) +static CURLcode gtls_random(struct Curl_easy *data, + unsigned char *entropy, size_t length) { -#if defined(USE_GNUTLS_NETTLE) int rc; (void)data; rc = gnutls_rnd(GNUTLS_RND_RANDOM, entropy, length); - return rc?CURLE_FAILED_INIT:CURLE_OK; -#elif defined(USE_GNUTLS) - if(data) - Curl_gtls_seed(data); /* Initiate the seed if not already done */ - gcry_randomize(entropy, length, GCRY_STRONG_RANDOM); -#endif - return CURLE_OK; -} - -void Curl_gtls_md5sum(unsigned char *tmp, /* input */ - size_t tmplen, - unsigned char *md5sum, /* output */ - size_t md5len) -{ -#if defined(USE_GNUTLS_NETTLE) - struct md5_ctx MD5pw; - md5_init(&MD5pw); - md5_update(&MD5pw, (unsigned int)tmplen, tmp); - md5_digest(&MD5pw, (unsigned int)md5len, md5sum); -#elif defined(USE_GNUTLS) - gcry_md_hd_t MD5pw; - gcry_md_open(&MD5pw, GCRY_MD_MD5, 0); - gcry_md_write(MD5pw, tmp, tmplen); - memcpy(md5sum, gcry_md_read(MD5pw, 0), md5len); - gcry_md_close(MD5pw); -#endif + return rc ? CURLE_FAILED_INIT : CURLE_OK; } -void Curl_gtls_sha256sum(const unsigned char *tmp, /* input */ - size_t tmplen, - unsigned char *sha256sum, /* output */ - size_t sha256len) +static CURLcode gtls_sha256sum(const unsigned char *tmp, /* input */ + size_t tmplen, + unsigned char *sha256sum, /* output */ + size_t sha256len) { -#if defined(USE_GNUTLS_NETTLE) struct sha256_ctx SHA256pw; sha256_init(&SHA256pw); sha256_update(&SHA256pw, (unsigned int)tmplen, tmp); sha256_digest(&SHA256pw, (unsigned int)sha256len, sha256sum); -#elif defined(USE_GNUTLS) - gcry_md_hd_t SHA256pw; - gcry_md_open(&SHA256pw, GCRY_MD_SHA256, 0); - gcry_md_write(SHA256pw, tmp, tmplen); - memcpy(sha256sum, gcry_md_read(SHA256pw, 0), sha256len); - gcry_md_close(SHA256pw); -#endif + return CURLE_OK; } -bool Curl_gtls_cert_status_request(void) +static bool gtls_cert_status_request(void) { -#ifdef HAS_OCSP return TRUE; -#else - return FALSE; -#endif } +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 | + SSLSUPP_CA_CACHE, + + 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 */ + 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 */ + gtls_recv, /* recv decrypted data */ + gtls_send, /* send data to encrypt */ + NULL, /* get_channel_binding */ +}; + #endif /* USE_GNUTLS */ diff --git a/contrib/curl/lib/vtls/gtls.h b/contrib/curl/lib/vtls/gtls.h index 462c04853e..b0ca55bfb7 100644 --- a/contrib/curl/lib/vtls/gtls.h +++ b/contrib/curl/lib/vtls/gtls.h @@ -7,11 +7,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2017, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -20,77 +20,80 @@ * 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 #ifdef USE_GNUTLS -#include "urldata.h" +#include +#include "timeval.h" -int Curl_gtls_init(void); -int Curl_gtls_cleanup(void); -CURLcode Curl_gtls_connect(struct connectdata *conn, int sockindex); -CURLcode Curl_gtls_connect_nonblocking(struct connectdata *conn, - int sockindex, - bool *done); -bool Curl_gtls_data_pending(const struct connectdata *conn, - int connindex); +#ifdef HAVE_GNUTLS_SRP +/* the function exists */ +#ifdef USE_TLS_SRP +/* the functionality is not disabled */ +#define USE_GNUTLS_SRP +#endif +#endif - /* close a SSL connection */ -void Curl_gtls_close(struct connectdata *conn, int sockindex); +struct Curl_easy; +struct Curl_cfilter; +struct ssl_primary_config; +struct ssl_config_data; +struct ssl_peer; -void Curl_gtls_session_free(void *ptr); -size_t Curl_gtls_version(char *buffer, size_t size); -int Curl_gtls_shutdown(struct connectdata *conn, int sockindex); -CURLcode Curl_gtls_random(struct Curl_easy *data, - unsigned char *entropy, - size_t length); -void Curl_gtls_md5sum(unsigned char *tmp, /* input */ - size_t tmplen, - unsigned char *md5sum, /* output */ - size_t md5len); -void Curl_gtls_sha256sum(const unsigned char *tmp, /* input */ - size_t tmplen, - unsigned char *sha256sum, /* output */ - size_t sha256len); +struct gtls_shared_creds { + gnutls_certificate_credentials_t creds; + char *CAfile; /* CAfile path used to generate X509 store */ + struct curltime time; /* when the shared creds was created */ + size_t refcount; + BIT(trust_setup); /* x509 anchors + CRLs have been set up */ +}; -bool Curl_gtls_cert_status_request(void); +CURLcode Curl_gtls_shared_creds_create(struct Curl_easy *data, + struct gtls_shared_creds **pcreds); +CURLcode Curl_gtls_shared_creds_up_ref(struct gtls_shared_creds *creds); +void Curl_gtls_shared_creds_free(struct gtls_shared_creds **pcreds); -/* Support HTTPS-proxy */ -#define HTTPS_PROXY_SUPPORT 1 +struct gtls_ctx { + gnutls_session_t session; + struct gtls_shared_creds *shared_creds; +#ifdef USE_GNUTLS_SRP + gnutls_srp_client_credentials_t srp_client_cred; +#endif + CURLcode io_result; /* result of last IO cfilter operation */ + BIT(sent_shutdown); +}; -/* Set the API backend definition to GnuTLS */ -#define CURL_SSL_BACKEND CURLSSLBACKEND_GNUTLS +typedef CURLcode Curl_gtls_ctx_setup_cb(struct Curl_cfilter *cf, + struct Curl_easy *data, + void *user_data); -/* this backend supports the CAPATH option */ -#define have_curlssl_ca_path 1 +CURLcode Curl_gtls_ctx_init(struct gtls_ctx *gctx, + struct Curl_cfilter *cf, + struct Curl_easy *data, + struct ssl_peer *peer, + const unsigned char *alpn, size_t alpn_len, + Curl_gtls_ctx_setup_cb *cb_setup, + void *cb_user_data, + void *ssl_user_data); -/* this backend supports CURLOPT_CERTINFO */ -#define have_curlssl_certinfo 1 +CURLcode Curl_gtls_client_trust_setup(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct gtls_ctx *gtls); -/* this backend supports CURLOPT_PINNEDPUBLICKEY */ -#define have_curlssl_pinnedpubkey 1 +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); -/* API setup for GnuTLS */ -#define curlssl_init Curl_gtls_init -#define curlssl_cleanup Curl_gtls_cleanup -#define curlssl_connect Curl_gtls_connect -#define curlssl_connect_nonblocking Curl_gtls_connect_nonblocking -#define curlssl_session_free(x) Curl_gtls_session_free(x) -#define curlssl_close_all(x) ((void)x) -#define curlssl_close Curl_gtls_close -#define curlssl_shutdown(x,y) Curl_gtls_shutdown(x,y) -#define curlssl_set_engine(x,y) ((void)x, (void)y, CURLE_NOT_BUILT_IN) -#define curlssl_set_engine_default(x) ((void)x, CURLE_NOT_BUILT_IN) -#define curlssl_engines_list(x) ((void)x, (struct curl_slist *)NULL) -#define curlssl_version Curl_gtls_version -#define curlssl_check_cxn(x) ((void)x, -1) -#define curlssl_data_pending(x,y) Curl_gtls_data_pending(x,y) -#define curlssl_random(x,y,z) Curl_gtls_random(x,y,z) -#define curlssl_md5sum(a,b,c,d) Curl_gtls_md5sum(a,b,c,d) -#define curlssl_sha256sum(a,b,c,d) Curl_gtls_sha256sum(a,b,c,d) -#define curlssl_cert_status_request() Curl_gtls_cert_status_request() +extern const struct Curl_ssl Curl_ssl_gnutls; #endif /* USE_GNUTLS */ #endif /* HEADER_CURL_GTLS_H */ diff --git a/contrib/curl/lib/vtls/hostcheck.c b/contrib/curl/lib/vtls/hostcheck.c new file mode 100644 index 0000000000..e46439a5ec --- /dev/null +++ b/contrib/curl/lib/vtls/hostcheck.c @@ -0,0 +1,135 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#if defined(USE_OPENSSL) \ + || defined(USE_SCHANNEL) +/* these backends use functions from this file */ + +#ifdef HAVE_NETINET_IN_H +#include +#endif +#ifdef HAVE_NETINET_IN6_H +#include +#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 hostnames 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/contrib/curl/lib/hostcheck.h b/contrib/curl/lib/vtls/hostcheck.h similarity index 75% rename from contrib/curl/lib/hostcheck.h rename to contrib/curl/lib/vtls/hostcheck.h index 86e3b96a97..6b4e379644 100644 --- a/contrib/curl/lib/hostcheck.h +++ b/contrib/curl/lib/vtls/hostcheck.h @@ -7,11 +7,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2012, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -20,13 +20,14 @@ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY * KIND, either express or implied. * + * SPDX-License-Identifier: curl + * ***************************************************************************/ #include -#define CURL_HOST_NOMATCH 0 -#define CURL_HOST_MATCH 1 -int Curl_cert_hostcheck(const char *match_pattern, const char *hostname); +/* returns TRUE if there is 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/contrib/curl/lib/vtls/keylog.c b/contrib/curl/lib/vtls/keylog.c new file mode 100644 index 0000000000..ab7baaaeca --- /dev/null +++ b/contrib/curl/lib/vtls/keylog.c @@ -0,0 +1,167 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ +#include "curl_setup.h" + +#if defined(USE_OPENSSL) || \ + defined(USE_GNUTLS) || \ + defined(USE_WOLFSSL) || \ + (defined(USE_NGTCP2) && defined(USE_NGHTTP3)) || \ + defined(USE_QUICHE) + +#include "keylog.h" +#include + +/* 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/contrib/curl/lib/vtls/keylog.h b/contrib/curl/lib/vtls/keylog.h new file mode 100644 index 0000000000..eff5bf38f3 --- /dev/null +++ b/contrib/curl/lib/vtls/keylog.h @@ -0,0 +1,58 @@ +#ifndef HEADER_CURL_KEYLOG_H +#define HEADER_CURL_KEYLOG_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ +#include "curl_setup.h" + +/* + * 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/contrib/curl/lib/vtls/mbedtls.c b/contrib/curl/lib/vtls/mbedtls.c index 07239bf266..9ec2aad359 100644 --- a/contrib/curl/lib/vtls/mbedtls.c +++ b/contrib/curl/lib/vtls/mbedtls.c @@ -5,12 +5,12 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 2010 - 2011, Hoi-Ho Chan, - * Copyright (C) 2012 - 2017, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. + * Copyright (C) Hoi-Ho Chan, * * This software is licensed 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. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -19,6 +19,8 @@ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY * KIND, either express or implied. * + * SPDX-License-Identifier: curl + * ***************************************************************************/ /* @@ -31,6 +33,9 @@ #ifdef USE_MBEDTLS +/* Define this to enable lots of debugging for mbedTLS */ +/* #define MBEDTLS_DEBUG */ + #include #if MBEDTLS_VERSION_NUMBER >= 0x02040000 #include @@ -38,7 +43,6 @@ #include #endif #include -#include #include #include @@ -46,92 +50,182 @@ #include #include +#if MBEDTLS_VERSION_MAJOR >= 2 +# ifdef MBEDTLS_DEBUG +# include +# endif +#endif + +#include "cipher_suite.h" +#include "strcase.h" #include "urldata.h" #include "sendf.h" #include "inet_pton.h" #include "mbedtls.h" #include "vtls.h" +#include "vtls_int.h" +#include "x509asn1.h" #include "parsedate.h" #include "connect.h" /* for the connect timeout */ #include "select.h" -#include "polarssl_threadlock.h" +#include "multiif.h" +#include "mbedtls_threadlock.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" +/* 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 + int *ciphersuites; + BIT(initialized); /* mbedtls_ssl_context is initialized */ + BIT(sent_shutdown); +}; + /* apply threading? */ -#if defined(USE_THREADS_POSIX) || defined(USE_THREADS_WIN32) +#if (defined(USE_THREADS_POSIX) && defined(HAVE_PTHREAD_H)) || \ + defined(_WIN32) #define THREADING_SUPPORT #endif +#ifndef MBEDTLS_ERROR_C +#define mbedtls_strerror(a,b,c) b[0] = 0 +#endif + +#if defined(MBEDTLS_SSL_PROTO_TLS1_3) && MBEDTLS_VERSION_NUMBER >= 0x03060000 +#define TLS13_SUPPORT +#endif + #if defined(THREADING_SUPPORT) -static mbedtls_entropy_context entropy; +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_polarsslthreadlock_lock_function(0); + Curl_mbedtlsthreadlock_lock_function(0); if(entropy_init_initialized == 0) { mbedtls_entropy_init(ctx); entropy_init_initialized = 1; } - Curl_polarsslthreadlock_unlock_function(0); + Curl_mbedtlsthreadlock_unlock_function(0); +} + +static void entropy_cleanup_mutex(mbedtls_entropy_context *ctx) +{ + /* lock 0 = use same lock as init */ + Curl_mbedtlsthreadlock_lock_function(0); + if(entropy_init_initialized == 1) { + mbedtls_entropy_free(ctx); + entropy_init_initialized = 0; + } + 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_polarsslthreadlock_lock_function(1); + Curl_mbedtlsthreadlock_lock_function(1); ret = mbedtls_entropy_func(data, output, len); - Curl_polarsslthreadlock_unlock_function(1); + Curl_mbedtlsthreadlock_unlock_function(1); return ret; } -/* end of entropy_func_mutex() */ #endif /* THREADING_SUPPORT */ -/* Define this to enable lots of debugging for mbedTLS */ -#undef MBEDTLS_DEBUG - #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); + struct Curl_easy *data = (struct Curl_easy *)context; (void) level; + (void) line_nb; + (void) f_name; + + if(data) { + size_t len = strlen(line); + if(len && (line[len - 1] == '\n')) + /* discount any trailing newline */ + len--; + infof(data, "%.*s", (int)len, line); + } } -#else #endif -/* ALPN for http2? */ -#ifdef USE_NGHTTP2 -# undef HAS_ALPN -# ifdef MBEDTLS_SSL_ALPN -# define HAS_ALPN -# endif -#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); + if(!data) + return 0; + + nwritten = Curl_conn_cf_send(cf->next, data, (char *)buf, blen, FALSE, + &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); + if(!data) + return 0; + /* 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 */ -const mbedtls_x509_crt_profile mbedtls_x509_crt_profile_fr = +static const mbedtls_x509_crt_profile mbedtls_x509_crt_profile_fr = { /* Hashes from SHA-1 and above */ MBEDTLS_X509_ID_FLAG(MBEDTLS_MD_SHA1) | @@ -145,8 +239,8 @@ const mbedtls_x509_crt_profile mbedtls_x509_crt_profile_fr = 1024, /* RSA min key len */ }; -/* See https://tls.mbed.org/discussions/generic/ - howto-determine-exact-buffer-len-for-mbedtls_pk_write_pubkey_der +/* See https://web.archive.org/web/20200921194007/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) @@ -154,158 +248,555 @@ const mbedtls_x509_crt_profile mbedtls_x509_crt_profile_fr = #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 Curl_recv mbed_recv; -static Curl_send mbed_send; +static CURLcode +mbed_set_ssl_version_min_max(struct Curl_easy *data, + struct mbed_ssl_backend_data *backend, + struct ssl_primary_config *conn_config) +{ + /* TLS 1.0 and TLS 1.1 were dropped with mbedTLS 3.0.0 (2021). So, since + * then, and before the introduction of TLS 1.3 in 3.6.0 (2024), this + * function basically always sets TLS 1.2 as min/max, unless given + * unsupported option values. */ + +#if MBEDTLS_VERSION_NUMBER < 0x03020000 + int ver_min = MBEDTLS_SSL_MINOR_VERSION_3; /* TLS 1.2 */ + int ver_max = MBEDTLS_SSL_MINOR_VERSION_3; /* TLS 1.2 */ +#else + /* mbedTLS 3.2.0 (2022) introduced new methods for setting TLS version */ + mbedtls_ssl_protocol_version ver_min = MBEDTLS_SSL_VERSION_TLS1_2; + mbedtls_ssl_protocol_version ver_max = MBEDTLS_SSL_VERSION_TLS1_2; +#endif + + switch(conn_config->version) { + case CURL_SSLVERSION_DEFAULT: +#if MBEDTLS_VERSION_NUMBER < 0x03000000 + case CURL_SSLVERSION_TLSv1: + case CURL_SSLVERSION_TLSv1_0: + ver_min = MBEDTLS_SSL_MINOR_VERSION_1; + break; + case CURL_SSLVERSION_TLSv1_1: + ver_min = MBEDTLS_SSL_MINOR_VERSION_2; + break; +#else + case CURL_SSLVERSION_TLSv1: + case CURL_SSLVERSION_TLSv1_0: + case CURL_SSLVERSION_TLSv1_1: +#endif + case CURL_SSLVERSION_TLSv1_2: + /* ver_min = MBEDTLS_SSL_VERSION_TLS1_2; */ + break; + case CURL_SSLVERSION_TLSv1_3: +#ifdef TLS13_SUPPORT + ver_min = MBEDTLS_SSL_VERSION_TLS1_3; + break; +#endif + default: + failf(data, "mbedTLS: unsupported minimum TLS version value"); + return CURLE_SSL_CONNECT_ERROR; + } + + switch(conn_config->version_max) { + case CURL_SSLVERSION_MAX_DEFAULT: + case CURL_SSLVERSION_MAX_NONE: + case CURL_SSLVERSION_MAX_TLSv1_3: +#ifdef TLS13_SUPPORT + ver_max = MBEDTLS_SSL_VERSION_TLS1_3; + break; +#endif + case CURL_SSLVERSION_MAX_TLSv1_2: + /* ver_max = MBEDTLS_SSL_VERSION_TLS1_2; */ + break; +#if MBEDTLS_VERSION_NUMBER < 0x03000000 + case CURL_SSLVERSION_MAX_TLSv1_1: + ver_max = MBEDTLS_SSL_MINOR_VERSION_2; + break; + case CURL_SSLVERSION_MAX_TLSv1_0: + ver_max = MBEDTLS_SSL_MINOR_VERSION_1; + break; +#else + case CURL_SSLVERSION_MAX_TLSv1_1: + case CURL_SSLVERSION_MAX_TLSv1_0: +#endif + default: + failf(data, "mbedTLS: unsupported maximum TLS version value"); + return CURLE_SSL_CONNECT_ERROR; + } + +#if MBEDTLS_VERSION_NUMBER < 0x03020000 + mbedtls_ssl_conf_min_version(&backend->config, MBEDTLS_SSL_MAJOR_VERSION_3, + ver_min); + mbedtls_ssl_conf_max_version(&backend->config, MBEDTLS_SSL_MAJOR_VERSION_3, + ver_max); +#else + mbedtls_ssl_conf_min_tls_version(&backend->config, ver_min); + mbedtls_ssl_conf_max_tls_version(&backend->config, ver_max); +#endif + + return CURLE_OK; +} + +/* TLS_ECJPAKE_WITH_AES_128_CCM_8 (0xC0FF) is marked experimental + in mbedTLS. The number is not reserved by IANA nor is the + cipher suite present in other SSL implementations. Provide + provisional support for specifying the cipher suite here. */ +#ifdef MBEDTLS_TLS_ECJPAKE_WITH_AES_128_CCM_8 +static int +mbed_cipher_suite_get_str(uint16_t id, char *buf, size_t buf_size, + bool prefer_rfc) +{ + if(id == MBEDTLS_TLS_ECJPAKE_WITH_AES_128_CCM_8) + msnprintf(buf, buf_size, "%s", "TLS_ECJPAKE_WITH_AES_128_CCM_8"); + else + return Curl_cipher_suite_get_str(id, buf, buf_size, prefer_rfc); + return 0; +} + +static uint16_t +mbed_cipher_suite_walk_str(const char **str, const char **end) +{ + uint16_t id = Curl_cipher_suite_walk_str(str, end); + size_t len = *end - *str; + + if(!id) { + if(strncasecompare("TLS_ECJPAKE_WITH_AES_128_CCM_8", *str, len)) + id = MBEDTLS_TLS_ECJPAKE_WITH_AES_128_CCM_8; + } + return id; +} +#else +#define mbed_cipher_suite_get_str Curl_cipher_suite_get_str +#define mbed_cipher_suite_walk_str Curl_cipher_suite_walk_str +#endif + +static CURLcode +mbed_set_selected_ciphers(struct Curl_easy *data, + struct mbed_ssl_backend_data *backend, + const char *ciphers12, + const char *ciphers13) +{ + const char *ciphers = ciphers12; + const int *supported; + int *selected; + size_t supported_len, count = 0, default13_count = 0, i, j; + const char *ptr, *end; + + supported = mbedtls_ssl_list_ciphersuites(); + for(i = 0; supported[i] != 0; i++); + supported_len = i; + + selected = malloc(sizeof(int) * (supported_len + 1)); + if(!selected) + return CURLE_OUT_OF_MEMORY; + +#ifndef TLS13_SUPPORT + (void) ciphers13, (void) j; +#else + if(!ciphers13) { + /* Add default TLSv1.3 ciphers to selection */ + for(j = 0; j < supported_len; j++) { + uint16_t id = (uint16_t) supported[j]; + if(strncmp(mbedtls_ssl_get_ciphersuite_name(id), "TLS1-3", 6) != 0) + continue; + + selected[count++] = id; + } + + default13_count = count; + } + else + ciphers = ciphers13; + +add_ciphers: +#endif + for(ptr = ciphers; ptr[0] != '\0' && count < supported_len; ptr = end) { + uint16_t id = mbed_cipher_suite_walk_str(&ptr, &end); + + /* Check if cipher is supported */ + if(id) { + for(i = 0; i < supported_len && supported[i] != id; i++); + if(i == supported_len) + id = 0; + } + if(!id) { + if(ptr[0] != '\0') + infof(data, "mbedTLS: unknown cipher in list: \"%.*s\"", + (int) (end - ptr), ptr); + continue; + } + + /* No duplicates allowed (so selected cannot overflow) */ + for(i = 0; i < count && selected[i] != id; i++); + if(i < count) { + if(i >= default13_count) + infof(data, "mbedTLS: duplicate cipher in list: \"%.*s\"", + (int) (end - ptr), ptr); + continue; + } + + selected[count++] = id; + } + +#ifdef TLS13_SUPPORT + if(ciphers == ciphers13 && ciphers12) { + ciphers = ciphers12; + goto add_ciphers; + } + + if(!ciphers12) { + /* Add default TLSv1.2 ciphers to selection */ + for(j = 0; j < supported_len; j++) { + uint16_t id = (uint16_t) supported[j]; + if(strncmp(mbedtls_ssl_get_ciphersuite_name(id), "TLS1-3", 6) == 0) + continue; + + /* No duplicates allowed (so selected cannot overflow) */ + for(i = 0; i < count && selected[i] != id; i++); + if(i < count) + continue; + + selected[count++] = id; + } + } +#endif + + selected[count] = 0; + + if(count == 0) { + free(selected); + failf(data, "mbedTLS: no supported cipher in list"); + return CURLE_SSL_CIPHER; + } + + /* mbedtls_ssl_conf_ciphersuites(): The ciphersuites array is not copied. + It must remain valid for the lifetime of the SSL configuration */ + backend->ciphersuites = selected; + mbedtls_ssl_conf_ciphersuites(&backend->config, backend->ciphersuites); + return CURLE_OK; +} + +static void +mbed_dump_cert_info(struct Curl_easy *data, const mbedtls_x509_crt *crt) +{ +#if defined(CURL_DISABLE_VERBOSE_STRINGS) || \ + (MBEDTLS_VERSION_NUMBER >= 0x03000000 && defined(MBEDTLS_X509_REMOVE_INFO)) + (void) data, (void) crt; +#else + const size_t bufsize = 16384; + char *p, *buffer = malloc(bufsize); + + if(buffer && mbedtls_x509_crt_info(buffer, bufsize, " ", crt) > 0) { + infof(data, "Server certificate:"); + for(p = buffer; *p; p += *p != '\0') { + size_t s = strcspn(p, "\n"); + infof(data, "%.*s", (int) s, p); + p += s; + } + } + else + infof(data, "Unable to dump certificate information"); + + free(buffer); +#endif +} + +static void +mbed_extract_certinfo(struct Curl_easy *data, const mbedtls_x509_crt *crt) +{ + CURLcode result; + const mbedtls_x509_crt *cur; + int i; + + for(i = 0, cur = crt; cur; ++i, cur = cur->next); + result = Curl_ssl_init_certinfo(data, i); + + for(i = 0, cur = crt; result == CURLE_OK && cur; ++i, cur = cur->next) { + const char *beg = (const char *) cur->raw.p; + const char *end = beg + cur->raw.len; + result = Curl_extract_certinfo(data, i, beg, end); + } +} + +static int mbed_verify_cb(void *ptr, mbedtls_x509_crt *crt, + int depth, uint32_t *flags) +{ + struct Curl_cfilter *cf = (struct Curl_cfilter *) ptr; + struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); + struct Curl_easy *data = CF_DATA_CURRENT(cf); + + if(depth == 0) { + if(data->set.verbose) + mbed_dump_cert_info(data, crt); + if(data->set.ssl.certinfo) + mbed_extract_certinfo(data, crt); + } + + if(!conn_config->verifypeer) + *flags = 0; + else if(!conn_config->verifyhost) + *flags &= ~MBEDTLS_X509_BADCERT_CN_MISMATCH; + + if(*flags) { +#if MBEDTLS_VERSION_NUMBER < 0x03000000 || !defined(MBEDTLS_X509_REMOVE_INFO) + char buf[128]; + mbedtls_x509_crt_verify_info(buf, sizeof(buf), "", *flags); + failf(data, "mbedTLS: %s", buf); +#else + failf(data, "mbedTLS: certificate verification error 0x%08x", *flags); +#endif + } + + return 0; +} static CURLcode -mbed_connect_step1(struct connectdata *conn, - int sockindex) -{ - struct Curl_easy *data = conn->data; - struct ssl_connect_data* connssl = &conn->ssl[sockindex]; - const char * const ssl_cafile = SSL_CONN_CONFIG(CAfile); - const bool verifypeer = SSL_CONN_CONFIG(verifypeer); - const char * const ssl_capath = SSL_CONN_CONFIG(CApath); - char * const ssl_cert = SSL_SET_OPTION(cert); - const char * const ssl_crlfile = SSL_SET_OPTION(CRLfile); - const char * const hostname = SSL_IS_PROXY() ? conn->http_proxy.host.name : - conn->host.name; - const long int port = SSL_IS_PROXY() ? conn->port : conn->remote_port; +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]; - errorbuf[0]=0; - /* mbedTLS only supports SSLv3 and TLSv1 */ - if(SSL_CONN_CONFIG(version) == CURL_SSLVERSION_SSLv2) { - failf(data, "mbedTLS does not support SSLv2"); + DEBUGASSERT(backend); + DEBUGASSERT(!backend->initialized); + + 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 TLS13_SUPPORT + ret = psa_crypto_init(); + if(ret != PSA_SUCCESS) { + mbedtls_strerror(ret, errorbuf, sizeof(errorbuf)); + failf(data, "mbedTLS psa_crypto_init returned (-0x%04X) %s", + -ret, errorbuf); return CURLE_SSL_CONNECT_ERROR; } +#endif /* TLS13_SUPPORT */ #ifdef THREADING_SUPPORT - entropy_init_mutex(&entropy); - mbedtls_ctr_drbg_init(&connssl->ctr_drbg); + mbedtls_ctr_drbg_init(&backend->ctr_drbg); - ret = mbedtls_ctr_drbg_seed(&connssl->ctr_drbg, entropy_func_mutex, - &entropy, NULL, 0); + ret = mbedtls_ctr_drbg_seed(&backend->ctr_drbg, entropy_func_mutex, + &ts_entropy, NULL, 0); if(ret) { -#ifdef MBEDTLS_ERROR_C mbedtls_strerror(ret, errorbuf, sizeof(errorbuf)); -#endif /* MBEDTLS_ERROR_C */ - failf(data, "Failed - mbedTLS: ctr_drbg_init returned (-0x%04X) %s\n", + failf(data, "mbedtls_ctr_drbg_seed returned (-0x%04X) %s", -ret, errorbuf); + return CURLE_FAILED_INIT; } #else - mbedtls_entropy_init(&connssl->entropy); - mbedtls_ctr_drbg_init(&connssl->ctr_drbg); + mbedtls_entropy_init(&backend->entropy); + mbedtls_ctr_drbg_init(&backend->ctr_drbg); - ret = mbedtls_ctr_drbg_seed(&connssl->ctr_drbg, mbedtls_entropy_func, - &connssl->entropy, NULL, 0); + ret = mbedtls_ctr_drbg_seed(&backend->ctr_drbg, mbedtls_entropy_func, + &backend->entropy, NULL, 0); if(ret) { -#ifdef MBEDTLS_ERROR_C mbedtls_strerror(ret, errorbuf, sizeof(errorbuf)); -#endif /* MBEDTLS_ERROR_C */ - failf(data, "Failed - mbedTLS: ctr_drbg_init returned (-0x%04X) %s\n", + 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(&connssl->cacert); + 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 = Curl_memdup0(ca_info_blob->data, + ca_info_blob->len); + if(!newblob) + return CURLE_OUT_OF_MEMORY; + 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) { - ret = mbedtls_x509_crt_parse_file(&connssl->cacert, ssl_cafile); + if(ssl_cafile && verifypeer) { +#ifdef MBEDTLS_FS_IO + ret = mbedtls_x509_crt_parse_file(&backend->cacert, ssl_cafile); - if(ret<0) { -#ifdef MBEDTLS_ERROR_C + if(ret < 0) { mbedtls_strerror(ret, errorbuf, sizeof(errorbuf)); -#endif /* MBEDTLS_ERROR_C */ failf(data, "Error reading ca cert file %s - mbedTLS: (-0x%04X) %s", ssl_cafile, -ret, errorbuf); - - if(verifypeer) - return CURLE_SSL_CACERT_BADFILE; + 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) { - ret = mbedtls_x509_crt_parse_path(&connssl->cacert, ssl_capath); +#ifdef MBEDTLS_FS_IO + ret = mbedtls_x509_crt_parse_path(&backend->cacert, ssl_capath); - if(ret<0) { -#ifdef MBEDTLS_ERROR_C + if(ret < 0) { mbedtls_strerror(ret, errorbuf, sizeof(errorbuf)); -#endif /* MBEDTLS_ERROR_C */ 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(&connssl->clicert); + mbedtls_x509_crt_init(&backend->clicert); if(ssl_cert) { - ret = mbedtls_x509_crt_parse_file(&connssl->clicert, ssl_cert); +#ifdef MBEDTLS_FS_IO + ret = mbedtls_x509_crt_parse_file(&backend->clicert, ssl_cert); if(ret) { -#ifdef MBEDTLS_ERROR_C mbedtls_strerror(ret, errorbuf, sizeof(errorbuf)); -#endif /* MBEDTLS_ERROR_C */ 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 } - /* Load the client private key */ - mbedtls_pk_init(&connssl->pk); - - if(SSL_SET_OPTION(key)) { - ret = mbedtls_pk_parse_keyfile(&connssl->pk, SSL_SET_OPTION(key), - SSL_SET_OPTION(key_passwd)); - if(ret == 0 && !mbedtls_pk_can_do(&connssl->pk, MBEDTLS_PK_RSA)) - ret = MBEDTLS_ERR_PK_TYPE_MISMATCH; + 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 = Curl_memdup0(ssl_cert_blob->data, + ssl_cert_blob->len); + if(!newblob) + return CURLE_OUT_OF_MEMORY; + ret = mbedtls_x509_crt_parse(&backend->clicert, newblob, + ssl_cert_blob->len + 1); + free(newblob); if(ret) { -#ifdef MBEDTLS_ERROR_C mbedtls_strerror(ret, errorbuf, sizeof(errorbuf)); -#endif /* MBEDTLS_ERROR_C */ - failf(data, "Error reading private key %s - mbedTLS: (-0x%04X) %s", - SSL_SET_OPTION(key), -ret, errorbuf); - + failf(data, "Error reading client cert data %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 */ - mbedtls_x509_crl_init(&connssl->crl); +#ifdef MBEDTLS_X509_CRL_PARSE_C + mbedtls_x509_crl_init(&backend->crl); if(ssl_crlfile) { - ret = mbedtls_x509_crl_parse_file(&connssl->crl, ssl_crlfile); +#ifdef MBEDTLS_FS_IO + ret = mbedtls_x509_crl_parse_file(&backend->crl, ssl_crlfile); if(ret) { -#ifdef MBEDTLS_ERROR_C mbedtls_strerror(ret, errorbuf, sizeof(errorbuf)); -#endif /* MBEDTLS_ERROR_C */ 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\n", hostname, port); - - mbedtls_ssl_config_init(&connssl->config); + infof(data, "mbedTLS: Connecting to %s:%d", hostname, connssl->peer.port); - mbedtls_ssl_init(&connssl->ssl); - if(mbedtls_ssl_setup(&connssl->ssl, &connssl->config)) { - failf(data, "mbedTLS: ssl_init failed"); - return CURLE_SSL_CONNECT_ERROR; - } - ret = mbedtls_ssl_config_defaults(&connssl->config, + 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); @@ -314,127 +805,133 @@ mbed_connect_step1(struct connectdata *conn, return CURLE_SSL_CONNECT_ERROR; } + /* Always let mbedTLS verify certificates, if verifypeer or verifyhost are + * disabled we clear the corresponding error flags in the verify callback + * function. That is also where we log verification errors. */ + mbedtls_ssl_conf_verify(&backend->config, mbed_verify_cb, cf); + mbedtls_ssl_conf_authmode(&backend->config, MBEDTLS_SSL_VERIFY_REQUIRED); + + mbedtls_ssl_init(&backend->ssl); + backend->initialized = TRUE; + /* new profile with RSA min key len = 1024 ... */ - mbedtls_ssl_conf_cert_profile(&connssl->config, + mbedtls_ssl_conf_cert_profile(&backend->config, &mbedtls_x509_crt_profile_fr); - switch(SSL_CONN_CONFIG(version)) { - case CURL_SSLVERSION_DEFAULT: - case CURL_SSLVERSION_TLSv1: - mbedtls_ssl_conf_min_version(&connssl->config, MBEDTLS_SSL_MAJOR_VERSION_3, - MBEDTLS_SSL_MINOR_VERSION_1); - infof(data, "mbedTLS: Set min SSL version to TLS 1.0\n"); - break; - case CURL_SSLVERSION_SSLv3: - mbedtls_ssl_conf_min_version(&connssl->config, MBEDTLS_SSL_MAJOR_VERSION_3, - MBEDTLS_SSL_MINOR_VERSION_0); - mbedtls_ssl_conf_max_version(&connssl->config, MBEDTLS_SSL_MAJOR_VERSION_3, - MBEDTLS_SSL_MINOR_VERSION_0); - infof(data, "mbedTLS: Set SSL version to SSLv3\n"); - break; - case CURL_SSLVERSION_TLSv1_0: - mbedtls_ssl_conf_min_version(&connssl->config, MBEDTLS_SSL_MAJOR_VERSION_3, - MBEDTLS_SSL_MINOR_VERSION_1); - mbedtls_ssl_conf_max_version(&connssl->config, MBEDTLS_SSL_MAJOR_VERSION_3, - MBEDTLS_SSL_MINOR_VERSION_1); - infof(data, "mbedTLS: Set SSL version to TLS 1.0\n"); - break; - case CURL_SSLVERSION_TLSv1_1: - mbedtls_ssl_conf_min_version(&connssl->config, MBEDTLS_SSL_MAJOR_VERSION_3, - MBEDTLS_SSL_MINOR_VERSION_2); - mbedtls_ssl_conf_max_version(&connssl->config, MBEDTLS_SSL_MAJOR_VERSION_3, - MBEDTLS_SSL_MINOR_VERSION_2); - infof(data, "mbedTLS: Set SSL version to TLS 1.1\n"); - break; - case CURL_SSLVERSION_TLSv1_2: - mbedtls_ssl_conf_min_version(&connssl->config, MBEDTLS_SSL_MAJOR_VERSION_3, - MBEDTLS_SSL_MINOR_VERSION_3); - mbedtls_ssl_conf_max_version(&connssl->config, MBEDTLS_SSL_MAJOR_VERSION_3, - MBEDTLS_SSL_MINOR_VERSION_3); - infof(data, "mbedTLS: Set SSL version to TLS 1.2\n"); - break; - case CURL_SSLVERSION_TLSv1_3: - failf(data, "mbedTLS: TLS 1.3 is not yet supported"); - return CURLE_SSL_CONNECT_ERROR; - default: - failf(data, "Unrecognized parameter passed via CURLOPT_SSLVERSION"); + ret = mbed_set_ssl_version_min_max(data, backend, conn_config); + if(ret != CURLE_OK) + return ret; + + mbedtls_ssl_conf_rng(&backend->config, mbedtls_ctr_drbg_random, + &backend->ctr_drbg); + + ret = mbedtls_ssl_setup(&backend->ssl, &backend->config); + if(ret) { + mbedtls_strerror(ret, errorbuf, sizeof(errorbuf)); + failf(data, "ssl_setup failed - mbedTLS: (-0x%04X) %s", + -ret, errorbuf); return CURLE_SSL_CONNECT_ERROR; } - mbedtls_ssl_conf_authmode(&connssl->config, MBEDTLS_SSL_VERIFY_OPTIONAL); - - mbedtls_ssl_conf_rng(&connssl->config, mbedtls_ctr_drbg_random, - &connssl->ctr_drbg); - mbedtls_ssl_set_bio(&connssl->ssl, &conn->sock[sockindex], - mbedtls_net_send, - mbedtls_net_recv, + mbedtls_ssl_set_bio(&backend->ssl, cf, + mbedtls_bio_cf_write, + mbedtls_bio_cf_read, NULL /* rev_timeout() */); - mbedtls_ssl_conf_ciphersuites(&connssl->config, - mbedtls_ssl_list_ciphersuites()); +#ifndef TLS13_SUPPORT + if(conn_config->cipher_list) { + CURLcode result = mbed_set_selected_ciphers(data, backend, + conn_config->cipher_list, + NULL); +#else + if(conn_config->cipher_list || conn_config->cipher_list13) { + CURLcode result = mbed_set_selected_ciphers(data, backend, + conn_config->cipher_list, + conn_config->cipher_list13); +#endif + if(result != CURLE_OK) { + failf(data, "mbedTLS: failed to set cipher suites"); + return result; + } + } + else { + 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(&connssl->config, + 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(data->set.general_ssl.sessionid) { + /* Check if there is a cached ID we can/should use here! */ + if(ssl_config->primary.cache_session) { void *old_session = NULL; - Curl_ssl_sessionid_lock(conn); - if(!Curl_ssl_getsessionid(conn, &old_session, NULL, sockindex)) { - ret = mbedtls_ssl_set_session(&connssl->ssl, old_session); + Curl_ssl_sessionid_lock(data); + if(!Curl_ssl_getsessionid(cf, data, &connssl->peer, &old_session, NULL)) { + ret = mbedtls_ssl_set_session(&backend->ssl, old_session); if(ret) { - Curl_ssl_sessionid_unlock(conn); + Curl_ssl_sessionid_unlock(data); failf(data, "mbedtls_ssl_set_session returned -0x%x", -ret); return CURLE_SSL_CONNECT_ERROR; } - infof(data, "mbedTLS re-using session\n"); + infof(data, "mbedTLS reusing session"); } - Curl_ssl_sessionid_unlock(conn); + Curl_ssl_sessionid_unlock(data); } - mbedtls_ssl_conf_ca_chain(&connssl->config, - &connssl->cacert, - &connssl->crl); + mbedtls_ssl_conf_ca_chain(&backend->config, + &backend->cacert, +#ifdef MBEDTLS_X509_CRL_PARSE_C + &backend->crl); +#else + NULL); +#endif - if(SSL_SET_OPTION(key)) { - mbedtls_ssl_conf_own_cert(&connssl->config, - &connssl->clicert, &connssl->pk); + if(ssl_config->key || ssl_config->key_blob) { + mbedtls_ssl_conf_own_cert(&backend->config, + &backend->clicert, &backend->pk); } - if(mbedtls_ssl_set_hostname(&connssl->ssl, hostname)) { - /* mbedtls_ssl_set_hostname() sets the name to use in CN/SAN checks *and* + + if(mbedtls_ssl_set_hostname(&backend->ssl, connssl->peer.sni ? + connssl->peer.sni : connssl->peer.hostname)) { + /* 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, "couldn't set hostname in mbedTLS"); + failf(data, "Failed to set SNI"); return CURLE_SSL_CONNECT_ERROR; } #ifdef HAS_ALPN - if(conn->bits.tls_enable_alpn) { - const char **p = &connssl->protocols[0]; -#ifdef USE_NGHTTP2 - if(data->set.httpversion >= CURL_HTTP_VERSION_2) - *p++ = NGHTTP2_PROTO_VERSION_ID; -#endif - *p++ = ALPN_HTTP_1_1; - *p = NULL; - /* this function doesn't clone the protocols array, which is why we need + 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 does not clone the protocols array, which is why we need to keep it around */ - if(mbedtls_ssl_conf_alpn_protocols(&connssl->config, - &connssl->protocols[0])) { + if(mbedtls_ssl_conf_alpn_protocols(&backend->config, + &backend->protocols[0])) { failf(data, "Failed setting ALPN protocols"); return CURLE_SSL_CONNECT_ERROR; } - for(p = &connssl->protocols[0]; *p; ++p) - infof(data, "ALPN, offering %s\n", *p); + 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(&connssl->config, mbed_debug, data); + mbedtls_ssl_conf_dbg(&backend->config, mbed_debug, data); /* - 0 No debug * - 1 Error * - 2 State change @@ -444,100 +941,93 @@ mbed_connect_step1(struct connectdata *conn, mbedtls_debug_set_threshold(4); #endif + /* give application a chance to interfere with mbedTLS set up. */ + if(data->set.ssl.fsslctx) { + CURLcode result = (*data->set.ssl.fsslctx)(data, &backend->config, + data->set.ssl.fsslctxp); + if(result != CURLE_OK) { + failf(data, "error signaled by ssl ctx callback"); + return result; + } + } + connssl->connecting_state = ssl_connect_2; return CURLE_OK; } static CURLcode -mbed_connect_step2(struct connectdata *conn, - int sockindex) +mbed_connect_step2(struct Curl_cfilter *cf, struct Curl_easy *data) { int ret; - struct Curl_easy *data = conn->data; - struct ssl_connect_data* connssl = &conn->ssl[sockindex]; - const mbedtls_x509_crt *peercert; - const char * const pinnedpubkey = SSL_IS_PROXY() ? - data->set.str[STRING_SSL_PINNEDPUBLICKEY_PROXY] : - data->set.str[STRING_SSL_PINNEDPUBLICKEY_ORIG]; - -#ifdef HAS_ALPN - const char *next_protocol; + struct ssl_connect_data *connssl = cf->ctx; + struct mbed_ssl_backend_data *backend = + (struct mbed_ssl_backend_data *)connssl->backend; +#ifndef CURL_DISABLE_PROXY + const char * const pinnedpubkey = Curl_ssl_cf_is_proxy(cf) ? + data->set.str[STRING_SSL_PINNEDPUBLICKEY_PROXY] : + data->set.str[STRING_SSL_PINNEDPUBLICKEY]; +#else + const char * const pinnedpubkey = data->set.str[STRING_SSL_PINNEDPUBLICKEY]; #endif - char errorbuf[128]; - errorbuf[0] = 0; - - conn->recv[sockindex] = mbed_recv; - conn->send[sockindex] = mbed_send; + DEBUGASSERT(backend); - ret = mbedtls_ssl_handshake(&connssl->ssl); + ret = mbedtls_ssl_handshake(&backend->ssl); if(ret == MBEDTLS_ERR_SSL_WANT_READ) { - connssl->connecting_state = ssl_connect_2_reading; + connssl->io_need = CURL_SSL_IO_NEED_RECV; return CURLE_OK; } else if(ret == MBEDTLS_ERR_SSL_WANT_WRITE) { - connssl->connecting_state = ssl_connect_2_writing; + connssl->io_need = CURL_SSL_IO_NEED_SEND; return CURLE_OK; } + else if(ret == MBEDTLS_ERR_X509_CERT_VERIFY_FAILED) { + failf(data, "peer certificate could not be verified"); + return CURLE_PEER_FAILED_VERIFICATION; + } else if(ret) { -#ifdef MBEDTLS_ERROR_C + char errorbuf[128]; +#if MBEDTLS_VERSION_NUMBER >= 0x03020000 + CURL_TRC_CF(data, cf, "TLS version %04X", + mbedtls_ssl_get_version_number(&backend->ssl)); +#endif mbedtls_strerror(ret, errorbuf, sizeof(errorbuf)); -#endif /* MBEDTLS_ERROR_C */ - failf(data, "ssl_handshake returned - mbedTLS: (-0x%04X) %s", + failf(data, "ssl_handshake returned: (-0x%04X) %s", -ret, errorbuf); return CURLE_SSL_CONNECT_ERROR; } - infof(data, "mbedTLS: Handshake complete, cipher is %s\n", - mbedtls_ssl_get_ciphersuite(&conn->ssl[sockindex].ssl) - ); - - ret = mbedtls_ssl_get_verify_result(&conn->ssl[sockindex].ssl); - - if(ret && SSL_CONN_CONFIG(verifypeer)) { - if(ret & MBEDTLS_X509_BADCERT_EXPIRED) - failf(data, "Cert verify failed: BADCERT_EXPIRED"); - - if(ret & MBEDTLS_X509_BADCERT_REVOKED) { - failf(data, "Cert verify failed: BADCERT_REVOKED"); - return CURLE_SSL_CACERT; - } - - if(ret & MBEDTLS_X509_BADCERT_CN_MISMATCH) - failf(data, "Cert verify failed: BADCERT_CN_MISMATCH"); - - if(ret & MBEDTLS_X509_BADCERT_NOT_TRUSTED) - failf(data, "Cert verify failed: BADCERT_NOT_TRUSTED"); - - return CURLE_PEER_FAILED_VERIFICATION; - } - - peercert = mbedtls_ssl_get_peer_cert(&connssl->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:\n%s\n", buffer); - else - infof(data, "Unable to dump certificate information.\n"); - - free(buffer); +#if MBEDTLS_VERSION_NUMBER >= 0x03020000 + { + char cipher_str[64]; + uint16_t cipher_id; + cipher_id = (uint16_t) + mbedtls_ssl_get_ciphersuite_id_from_ssl(&backend->ssl); + mbed_cipher_suite_get_str(cipher_id, cipher_str, sizeof(cipher_str), true); + infof(data, "mbedTLS: %s Handshake complete, cipher is %s", + mbedtls_ssl_get_version(&backend->ssl), cipher_str); } +#else + infof(data, "mbedTLS: %s Handshake complete", + mbedtls_ssl_get_version(&backend->ssl)); +#endif if(pinnedpubkey) { int size; CURLcode result; - mbedtls_x509_crt *p; - unsigned char pubkey[PUB_DER_MAX_BYTES]; - + const mbedtls_x509_crt *peercert; + mbedtls_x509_crt *p = NULL; + unsigned char *pubkey = NULL; + + peercert = mbedtls_ssl_get_peer_cert(&backend->ssl); +#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; } @@ -547,86 +1037,93 @@ mbed_connect_step2(struct connectdata *conn, 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"); - mbedtls_x509_crt_free(p); - free(p); - return CURLE_SSL_PINNEDPUBKEYNOTMATCH; + 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"); - mbedtls_x509_crt_free(p); - free(p); - return CURLE_SSL_PINNEDPUBKEYNOTMATCH; + 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) { - mbedtls_x509_crt_free(p); - free(p); return result; } - - mbedtls_x509_crt_free(p); - free(p); } #ifdef HAS_ALPN - if(conn->bits.tls_enable_alpn) { - next_protocol = mbedtls_ssl_get_alpn_protocol(&connssl->ssl); - - if(next_protocol) { - infof(data, "ALPN, server accepted to use %s\n", next_protocol); -#ifdef USE_NGHTTP2 - if(!strncmp(next_protocol, NGHTTP2_PROTO_VERSION_ID, - NGHTTP2_PROTO_VERSION_ID_LEN) && - !next_protocol[NGHTTP2_PROTO_VERSION_ID_LEN]) { - conn->negnpn = CURL_HTTP_VERSION_2; - } - else -#endif - if(!strncmp(next_protocol, ALPN_HTTP_1_1, ALPN_HTTP_1_1_LENGTH) && - !next_protocol[ALPN_HTTP_1_1_LENGTH]) { - conn->negnpn = CURL_HTTP_VERSION_1_1; - } - } - else { - infof(data, "ALPN, server did not agree to a protocol\n"); - } + 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\n"); + infof(data, "SSL connected"); return CURLE_OK; } +static void mbedtls_session_free(void *sessionid, size_t idsize) +{ + (void)idsize; + mbedtls_ssl_session_free(sessionid); + free(sessionid); +} + static CURLcode -mbed_connect_step3(struct connectdata *conn, - int sockindex) +mbed_connect_step3(struct Curl_cfilter *cf, struct Curl_easy *data) { CURLcode retcode = CURLE_OK; - struct ssl_connect_data *connssl = &conn->ssl[sockindex]; - struct Curl_easy *data = conn->data; + 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(data->set.general_ssl.sessionid) { + if(ssl_config->primary.cache_session) { int ret; mbedtls_ssl_session *our_ssl_sessionid; - void *old_ssl_sessionid = NULL; our_ssl_sessionid = malloc(sizeof(mbedtls_ssl_session)); if(!our_ssl_sessionid) @@ -634,24 +1131,23 @@ mbed_connect_step3(struct connectdata *conn, mbedtls_ssl_session_init(our_ssl_sessionid); - ret = mbedtls_ssl_get_session(&connssl->ssl, 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(conn); - if(!Curl_ssl_getsessionid(conn, &old_ssl_sessionid, NULL, sockindex)) - Curl_ssl_delsessionid(conn, old_ssl_sessionid); - - retcode = Curl_ssl_addsessionid(conn, our_ssl_sessionid, 0, sockindex); - Curl_ssl_sessionid_unlock(conn); - if(retcode) { - free(our_ssl_sessionid); - failf(data, "failed to store ssl session"); + /* If there is already a matching session in the cache, delete it */ + Curl_ssl_sessionid_lock(data); + retcode = Curl_ssl_set_sessionid(cf, data, &connssl->peer, + our_ssl_sessionid, 0, + mbedtls_session_free); + Curl_ssl_sessionid_unlock(data); + if(retcode) return retcode; - } } connssl->connecting_state = ssl_connect_done; @@ -659,60 +1155,184 @@ mbed_connect_step3(struct connectdata *conn, return CURLE_OK; } -static ssize_t mbed_send(struct connectdata *conn, int sockindex, +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; - ret = mbedtls_ssl_write(&conn->ssl[sockindex].ssl, - (unsigned char *)mem, len); + (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; + CURL_TRC_CF(data, cf, "mbedtls_ssl_write(len=%zu) -> -0x%04X", + len, -ret); + *curlcode = ((ret == MBEDTLS_ERR_SSL_WANT_WRITE) +#ifdef TLS13_SUPPORT + || (ret == MBEDTLS_ERR_SSL_RECEIVED_NEW_SESSION_TICKET) +#endif + ) ? CURLE_AGAIN : CURLE_SEND_ERROR; ret = -1; } return ret; } -void Curl_mbedtls_close_all(struct Curl_easy *data) +static void mbedtls_close_all(struct Curl_easy *data) { (void)data; } -void Curl_mbedtls_close(struct connectdata *conn, int sockindex) +static CURLcode mbedtls_shutdown(struct Curl_cfilter *cf, + struct Curl_easy *data, + bool send_shutdown, bool *done) +{ + struct ssl_connect_data *connssl = cf->ctx; + struct mbed_ssl_backend_data *backend = + (struct mbed_ssl_backend_data *)connssl->backend; + unsigned char buf[1024]; + CURLcode result = CURLE_OK; + int ret; + size_t i; + + DEBUGASSERT(backend); + + if(!backend->initialized || cf->shutdown) { + *done = TRUE; + return CURLE_OK; + } + + connssl->io_need = CURL_SSL_IO_NEED_NONE; + *done = FALSE; + + if(!backend->sent_shutdown) { + /* do this only once */ + backend->sent_shutdown = TRUE; + if(send_shutdown) { + ret = mbedtls_ssl_close_notify(&backend->ssl); + switch(ret) { + case 0: /* we sent it, receive from the server */ + break; + case MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY: /* server also closed */ + *done = TRUE; + goto out; + case MBEDTLS_ERR_SSL_WANT_READ: + connssl->io_need = CURL_SSL_IO_NEED_RECV; + goto out; + case MBEDTLS_ERR_SSL_WANT_WRITE: + connssl->io_need = CURL_SSL_IO_NEED_SEND; + goto out; + default: + CURL_TRC_CF(data, cf, "mbedtls_shutdown error -0x%04X", -ret); + result = CURLE_RECV_ERROR; + goto out; + } + } + } + + /* SSL should now have started the shutdown from our side. Since it + * was not complete, we are lacking the close notify from the server. */ + for(i = 0; i < 10; ++i) { + ret = mbedtls_ssl_read(&backend->ssl, buf, sizeof(buf)); + /* This seems to be a bug in mbedTLS TLSv1.3 where it reports + * WANT_READ, but has not encountered an EAGAIN. */ + if(ret == MBEDTLS_ERR_SSL_WANT_READ) + ret = mbedtls_ssl_read(&backend->ssl, buf, sizeof(buf)); +#ifdef TLS13_SUPPORT + if(ret == MBEDTLS_ERR_SSL_RECEIVED_NEW_SESSION_TICKET) + continue; +#endif + if(ret <= 0) + break; + } + + if(ret > 0) { + /* still data coming in? */ + CURL_TRC_CF(data, cf, "mbedtls_shutdown, still getting data"); + } + else if(ret == 0 || (ret == MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY)) { + /* We got the close notify alert and are done. */ + CURL_TRC_CF(data, cf, "mbedtls_shutdown done"); + *done = TRUE; + } + else if(ret == MBEDTLS_ERR_SSL_WANT_READ) { + CURL_TRC_CF(data, cf, "mbedtls_shutdown, need RECV"); + connssl->io_need = CURL_SSL_IO_NEED_RECV; + } + else if(ret == MBEDTLS_ERR_SSL_WANT_WRITE) { + CURL_TRC_CF(data, cf, "mbedtls_shutdown, need SEND"); + connssl->io_need = CURL_SSL_IO_NEED_SEND; + } + else { + CURL_TRC_CF(data, cf, "mbedtls_shutdown error -0x%04X", -ret); + result = CURLE_RECV_ERROR; + } + +out: + cf->shutdown = (result || *done); + return result; +} + +static void mbedtls_close(struct Curl_cfilter *cf, struct Curl_easy *data) { - mbedtls_pk_free(&conn->ssl[sockindex].pk); - mbedtls_x509_crt_free(&conn->ssl[sockindex].clicert); - mbedtls_x509_crt_free(&conn->ssl[sockindex].cacert); - mbedtls_x509_crl_free(&conn->ssl[sockindex].crl); - mbedtls_ssl_config_free(&conn->ssl[sockindex].config); - mbedtls_ssl_free(&conn->ssl[sockindex].ssl); - mbedtls_ctr_drbg_free(&conn->ssl[sockindex].ctr_drbg); + struct ssl_connect_data *connssl = cf->ctx; + struct mbed_ssl_backend_data *backend = + (struct mbed_ssl_backend_data *)connssl->backend; + + (void)data; + DEBUGASSERT(backend); + if(backend->initialized) { + 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 + Curl_safefree(backend->ciphersuites); + mbedtls_ssl_config_free(&backend->config); + mbedtls_ssl_free(&backend->ssl); + mbedtls_ctr_drbg_free(&backend->ctr_drbg); #ifndef THREADING_SUPPORT - mbedtls_entropy_free(&conn->ssl[sockindex].entropy); + mbedtls_entropy_free(&backend->entropy); #endif /* THREADING_SUPPORT */ + backend->initialized = FALSE; + } } -static ssize_t mbed_recv(struct connectdata *conn, int num, +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; - memset(buf, 0, buffersize); - ret = mbedtls_ssl_read(&conn->ssl[num].ssl, (unsigned char *)buf, - buffersize); + (void)data; + DEBUGASSERT(backend); + ret = mbedtls_ssl_read(&backend->ssl, (unsigned char *)buf, + buffersize); if(ret <= 0) { + CURL_TRC_CF(data, cf, "mbedtls_ssl_read(len=%zu) -> -0x%04X", + buffersize, -ret); if(ret == MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY) return 0; - - *curlcode = (ret == MBEDTLS_ERR_SSL_WANT_READ) ? - CURLE_AGAIN : CURLE_RECV_ERROR; + *curlcode = ((ret == MBEDTLS_ERR_SSL_WANT_READ) +#ifdef TLS13_SUPPORT + || (ret == MBEDTLS_ERR_SSL_RECEIVED_NEW_SESSION_TICKET) +#endif + ) ? CURLE_AGAIN : CURLE_RECV_ERROR; + if(*curlcode != CURLE_AGAIN) { + char errorbuf[128]; + mbedtls_strerror(ret, errorbuf, sizeof(errorbuf)); + failf(data, "ssl_read returned: (-0x%04X) %s", -ret, errorbuf); + } return -1; } @@ -721,21 +1341,20 @@ static ssize_t mbed_recv(struct connectdata *conn, int num, return len; } -void Curl_mbedtls_session_free(void *ptr) -{ - mbedtls_ssl_session_free(ptr); - free(ptr); -} - -size_t Curl_mbedtls_version(char *buffer, size_t size) +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 snprintf(buffer, size, "mbedTLS/%d.%d.%d", version>>24, - (version>>16)&0xff, (version>>8)&0xff); + 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 } -CURLcode Curl_mbedtls_random(struct Curl_easy *data, unsigned char *entropy, - size_t length) +static CURLcode mbedtls_random(struct Curl_easy *data, + unsigned char *entropy, size_t length) { #if defined(MBEDTLS_CTR_DRBG_C) int ret = -1; @@ -744,26 +1363,21 @@ CURLcode Curl_mbedtls_random(struct Curl_easy *data, unsigned char *entropy, mbedtls_ctr_drbg_context ctr_drbg; mbedtls_entropy_init(&ctr_entropy); mbedtls_ctr_drbg_init(&ctr_drbg); - errorbuf[0]=0; ret = mbedtls_ctr_drbg_seed(&ctr_drbg, mbedtls_entropy_func, &ctr_entropy, NULL, 0); if(ret) { -#ifdef MBEDTLS_ERROR_C mbedtls_strerror(ret, errorbuf, sizeof(errorbuf)); -#endif /* MBEDTLS_ERROR_C */ - failf(data, "Failed - mbedTLS: ctr_drbg_seed returned (-0x%04X) %s\n", + failf(data, "mbedtls_ctr_drbg_seed returned (-0x%04X) %s", -ret, errorbuf); } else { ret = mbedtls_ctr_drbg_random(&ctr_drbg, entropy, length); if(ret) { -#ifdef MBEDTLS_ERROR_C mbedtls_strerror(ret, errorbuf, sizeof(errorbuf)); -#endif /* MBEDTLS_ERROR_C */ - failf(data, "mbedTLS: ctr_drbg_init returned (-0x%04X) %s\n", + failf(data, "mbedtls_ctr_drbg_random returned (-0x%04X) %s", -ret, errorbuf); } } @@ -784,16 +1398,14 @@ CURLcode Curl_mbedtls_random(struct Curl_easy *data, unsigned char *entropy, } static CURLcode -mbed_connect_common(struct connectdata *conn, - int sockindex, +mbed_connect_common(struct Curl_cfilter *cf, struct Curl_easy *data, bool nonblocking, bool *done) { CURLcode retcode; - struct Curl_easy *data = conn->data; - struct ssl_connect_data *connssl = &conn->ssl[sockindex]; - curl_socket_t sockfd = conn->sock[sockindex]; - long timeout_ms; + 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 */ @@ -802,8 +1414,8 @@ mbed_connect_common(struct connectdata *conn, return CURLE_OK; } - if(ssl_connect_1==connssl->connecting_state) { - /* Find out how much more time we're allowed */ + if(ssl_connect_1 == connssl->connecting_state) { + /* Find out how much more time we are allowed */ timeout_ms = Curl_timeleft(data, NULL, TRUE); if(timeout_ms < 0) { @@ -811,14 +1423,12 @@ mbed_connect_common(struct connectdata *conn, failf(data, "SSL connection timeout"); return CURLE_OPERATION_TIMEDOUT; } - retcode = mbed_connect_step1(conn, sockindex); + 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) { + while(ssl_connect_2 == connssl->connecting_state) { /* check allowed time left */ timeout_ms = Curl_timeleft(data, NULL, TRUE); @@ -829,14 +1439,12 @@ mbed_connect_common(struct connectdata *conn, 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; + /* if ssl is expecting something, check if it is available. */ + if(connssl->io_need) { + curl_socket_t writefd = (connssl->io_need & CURL_SSL_IO_NEED_SEND) ? + sockfd : CURL_SOCKET_BAD; + curl_socket_t readfd = (connssl->io_need & CURL_SSL_IO_NEED_RECV) ? + sockfd : CURL_SOCKET_BAD; what = Curl_socket_check(readfd, CURL_SOCKET_BAD, writefd, nonblocking ? 0 : timeout_ms); @@ -866,25 +1474,22 @@ mbed_connect_common(struct connectdata *conn, * ensuring that a client using select() or epoll() will always * have a valid fdset to wait on. */ - retcode = mbed_connect_step2(conn, sockindex); - if(retcode || (nonblocking && - (ssl_connect_2 == connssl->connecting_state || - ssl_connect_2_reading == connssl->connecting_state || - ssl_connect_2_writing == connssl->connecting_state))) + connssl->io_need = CURL_SSL_IO_NEED_NONE; + retcode = mbed_connect_step2(cf, data); + if(retcode || + (nonblocking && (ssl_connect_2 == connssl->connecting_state))) return retcode; } /* repeat step2 until all transactions are done. */ - if(ssl_connect_3==connssl->connecting_state) { - retcode = mbed_connect_step3(conn, sockindex); + if(ssl_connect_3 == connssl->connecting_state) { + retcode = mbed_connect_step3(cf, data); if(retcode) return retcode; } - if(ssl_connect_done==connssl->connecting_state) { + if(ssl_connect_done == connssl->connecting_state) { connssl->state = ssl_connection_complete; - conn->recv[sockindex] = mbed_recv; - conn->send[sockindex] = mbed_send; *done = TRUE; } else @@ -896,23 +1501,21 @@ mbed_connect_common(struct connectdata *conn, return CURLE_OK; } -CURLcode -Curl_mbedtls_connect_nonblocking(struct connectdata *conn, - int sockindex, - bool *done) +static CURLcode mbedtls_connect_nonblocking(struct Curl_cfilter *cf, + struct Curl_easy *data, + bool *done) { - return mbed_connect_common(conn, sockindex, TRUE, done); + return mbed_connect_common(cf, data, TRUE, done); } -CURLcode -Curl_mbedtls_connect(struct connectdata *conn, - int sockindex) +static CURLcode mbedtls_connect(struct Curl_cfilter *cf, + struct Curl_easy *data) { CURLcode retcode; bool done = FALSE; - retcode = mbed_connect_common(conn, sockindex, FALSE, &done); + retcode = mbed_connect_common(cf, data, FALSE, &done); if(retcode) return retcode; @@ -925,19 +1528,107 @@ Curl_mbedtls_connect(struct connectdata *conn, * return 0 error initializing SSL * return 1 SSL initialized successfully */ -int Curl_mbedtls_init(void) +static int mbedtls_init(void) { - return Curl_polarsslthreadlock_thread_setup(); + if(!Curl_mbedtlsthreadlock_thread_setup()) + return 0; +#ifdef THREADING_SUPPORT + entropy_init_mutex(&ts_entropy); +#endif + return 1; } -void Curl_mbedtls_cleanup(void) +static void mbedtls_cleanup(void) { - (void)Curl_polarsslthreadlock_thread_cleanup(); +#ifdef THREADING_SUPPORT + entropy_cleanup_mutex(&ts_entropy); +#endif + (void)Curl_mbedtlsthreadlock_thread_cleanup(); } -int Curl_mbedtls_data_pending(const struct connectdata *conn, int sockindex) +static bool mbedtls_data_pending(struct Curl_cfilter *cf, + const struct Curl_easy *data) { - return mbedtls_ssl_get_bytes_avail(&conn->ssl[sockindex].ssl) != 0; + 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_CERTINFO | + SSLSUPP_PINNEDPUBKEY | + SSLSUPP_SSL_CTX | +#ifdef TLS13_SUPPORT + SSLSUPP_TLS13_CIPHERSUITES | +#endif + SSLSUPP_HTTPS_PROXY | + SSLSUPP_CIPHER_LIST, + + sizeof(struct mbed_ssl_backend_data), + + mbedtls_init, /* init */ + mbedtls_cleanup, /* cleanup */ + mbedtls_version, /* version */ + Curl_none_check_cxn, /* check_cxn */ + mbedtls_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 */ + 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 */ + mbed_recv, /* recv decrypted data */ + mbed_send, /* send data to encrypt */ + NULL, /* get_channel_binding */ +}; + #endif /* USE_MBEDTLS */ diff --git a/contrib/curl/lib/vtls/mbedtls.h b/contrib/curl/lib/vtls/mbedtls.h index 5b0bcf6d77..d8a0a06eb6 100644 --- a/contrib/curl/lib/vtls/mbedtls.h +++ b/contrib/curl/lib/vtls/mbedtls.h @@ -7,12 +7,12 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 2012 - 2016, Daniel Stenberg, , et al. - * Copyright (C) 2010, Hoi-Ho Chan, + * Copyright (C) Daniel Stenberg, , et al. + * Copyright (C) Hoi-Ho Chan, * * This software is licensed 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. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -21,59 +21,14 @@ * 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 -#include - -/* Called on first use mbedTLS, setup threading if supported */ -int Curl_mbedtls_init(void); -void Curl_mbedtls_cleanup(void); -int Curl_mbedtls_data_pending(const struct connectdata *conn, int sockindex); - -CURLcode Curl_mbedtls_connect(struct connectdata *conn, int sockindex); - -CURLcode Curl_mbedtls_connect_nonblocking(struct connectdata *conn, - int sockindex, - bool *done); - -/* tell mbedTLS to close down all open information regarding connections (and - thus session ID caching etc) */ -void Curl_mbedtls_close_all(struct Curl_easy *data); - - /* close a SSL connection */ -void Curl_mbedtls_close(struct connectdata *conn, int sockindex); - -void Curl_mbedtls_session_free(void *ptr); -size_t Curl_mbedtls_version(char *buffer, size_t size); -int Curl_mbedtls_shutdown(struct connectdata *conn, int sockindex); - -CURLcode Curl_mbedtls_random(struct Curl_easy *data, unsigned char *entropy, - size_t length); - -/* this backends supports CURLOPT_PINNEDPUBLICKEY */ -#define have_curlssl_pinnedpubkey 1 - -/* API setup for mbedTLS */ -#define curlssl_init() Curl_mbedtls_init() -#define curlssl_cleanup() Curl_mbedtls_cleanup() -#define curlssl_connect Curl_mbedtls_connect -#define curlssl_connect_nonblocking Curl_mbedtls_connect_nonblocking -#define curlssl_session_free(x) Curl_mbedtls_session_free(x) -#define curlssl_close_all Curl_mbedtls_close_all -#define curlssl_close Curl_mbedtls_close -#define curlssl_shutdown(x,y) 0 -#define curlssl_set_engine(x,y) (x=x, y=y, CURLE_NOT_BUILT_IN) -#define curlssl_set_engine_default(x) (x=x, CURLE_NOT_BUILT_IN) -#define curlssl_engines_list(x) (x=x, (struct curl_slist *)NULL) -#define curlssl_version Curl_mbedtls_version -#define curlssl_check_cxn(x) (x=x, -1) -#define curlssl_data_pending(x,y) Curl_mbedtls_data_pending(x, y) -#define CURL_SSL_BACKEND CURLSSLBACKEND_MBEDTLS -#define curlssl_sha256sum(a,b,c,d) mbedtls_sha256(a,b,c,0) -#define curlssl_random(x,y,z) Curl_mbedtls_random(x, y, z) +extern const struct Curl_ssl Curl_ssl_mbedtls; #endif /* USE_MBEDTLS */ #endif /* HEADER_CURL_MBEDTLS_H */ diff --git a/contrib/curl/lib/vtls/mbedtls_threadlock.c b/contrib/curl/lib/vtls/mbedtls_threadlock.c new file mode 100644 index 0000000000..b96a904fcb --- /dev/null +++ b/contrib/curl/lib/vtls/mbedtls_threadlock.c @@ -0,0 +1,134 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * Copyright (C) Hoi-Ho Chan, + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the 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(_WIN32)) + +#if defined(USE_THREADS_POSIX) && defined(HAVE_PTHREAD_H) +# include +# define MBEDTLS_MUTEX_T pthread_mutex_t +#elif defined(_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(_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(_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(_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(_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/contrib/curl/lib/vtls/mbedtls_threadlock.h b/contrib/curl/lib/vtls/mbedtls_threadlock.h new file mode 100644 index 0000000000..484626852f --- /dev/null +++ b/contrib/curl/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, , et al. + * Copyright (C) Hoi-Ho Chan, + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the 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(_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 && HAVE_PTHREAD_H) || _WIN32 */ + +#endif /* USE_MBEDTLS */ + +#endif /* HEADER_CURL_MBEDTLS_THREADLOCK_H */ diff --git a/contrib/curl/lib/vtls/nss.c b/contrib/curl/lib/vtls/nss.c deleted file mode 100644 index d5158aceff..0000000000 --- a/contrib/curl/lib/vtls/nss.c +++ /dev/null @@ -1,2197 +0,0 @@ -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) 1998 - 2017, Daniel Stenberg, , et al. - * - * This software is licensed as described in the file COPYING, which - * you should have received as part of this distribution. The terms - * are also available at https://curl.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. - * - ***************************************************************************/ - -/* - * Source file for all NSS-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_NSS - -#include "urldata.h" -#include "sendf.h" -#include "formdata.h" /* for the boundary function */ -#include "url.h" /* for the ssl config check function */ -#include "connect.h" -#include "strcase.h" -#include "select.h" -#include "vtls.h" -#include "llist.h" -#include "curl_printf.h" -#include "nssg.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include /* for SECKEY_DestroyPublicKey() */ - -#define NSSVERNUM ((NSS_VMAJOR<<16)|(NSS_VMINOR<<8)|NSS_VPATCH) - -#if NSSVERNUM >= 0x030f00 /* 3.15.0 */ -#include -#endif - -#include "strcase.h" -#include "warnless.h" -#include "x509asn1.h" - -/* The last #include files should be: */ -#include "curl_memory.h" -#include "memdebug.h" - -#define SSL_DIR "/etc/pki/nssdb" - -/* enough to fit the string "PEM Token #[0|1]" */ -#define SLOTSIZE 13 - -PRFileDesc *PR_ImportTCPSocket(PRInt32 osfd); -static PRLock *nss_initlock = NULL; -static PRLock *nss_crllock = NULL; -static PRLock *nss_findslot_lock = NULL; -static struct curl_llist *nss_crl_list = NULL; -static NSSInitContext *nss_context = NULL; -static volatile int initialized = 0; - -typedef struct { - const char *name; - int num; -} cipher_s; - -#define PK11_SETATTRS(_attr, _idx, _type, _val, _len) do { \ - CK_ATTRIBUTE *ptr = (_attr) + ((_idx)++); \ - ptr->type = (_type); \ - ptr->pValue = (_val); \ - ptr->ulValueLen = (_len); \ -} WHILE_FALSE - -#define CERT_NewTempCertificate __CERT_NewTempCertificate - -#define NUM_OF_CIPHERS sizeof(cipherlist)/sizeof(cipherlist[0]) -static const cipher_s cipherlist[] = { - /* SSL2 cipher suites */ - {"rc4", SSL_EN_RC4_128_WITH_MD5}, - {"rc4-md5", SSL_EN_RC4_128_WITH_MD5}, - {"rc4export", SSL_EN_RC4_128_EXPORT40_WITH_MD5}, - {"rc2", SSL_EN_RC2_128_CBC_WITH_MD5}, - {"rc2export", SSL_EN_RC2_128_CBC_EXPORT40_WITH_MD5}, - {"des", SSL_EN_DES_64_CBC_WITH_MD5}, - {"desede3", SSL_EN_DES_192_EDE3_CBC_WITH_MD5}, - /* SSL3/TLS cipher suites */ - {"rsa_rc4_128_md5", SSL_RSA_WITH_RC4_128_MD5}, - {"rsa_rc4_128_sha", SSL_RSA_WITH_RC4_128_SHA}, - {"rsa_3des_sha", SSL_RSA_WITH_3DES_EDE_CBC_SHA}, - {"rsa_des_sha", SSL_RSA_WITH_DES_CBC_SHA}, - {"rsa_rc4_40_md5", SSL_RSA_EXPORT_WITH_RC4_40_MD5}, - {"rsa_rc2_40_md5", SSL_RSA_EXPORT_WITH_RC2_CBC_40_MD5}, - {"rsa_null_md5", SSL_RSA_WITH_NULL_MD5}, - {"rsa_null_sha", SSL_RSA_WITH_NULL_SHA}, - {"fips_3des_sha", SSL_RSA_FIPS_WITH_3DES_EDE_CBC_SHA}, - {"fips_des_sha", SSL_RSA_FIPS_WITH_DES_CBC_SHA}, - {"fortezza", SSL_FORTEZZA_DMS_WITH_FORTEZZA_CBC_SHA}, - {"fortezza_rc4_128_sha", SSL_FORTEZZA_DMS_WITH_RC4_128_SHA}, - {"fortezza_null", SSL_FORTEZZA_DMS_WITH_NULL_SHA}, - /* TLS 1.0: Exportable 56-bit Cipher Suites. */ - {"rsa_des_56_sha", TLS_RSA_EXPORT1024_WITH_DES_CBC_SHA}, - {"rsa_rc4_56_sha", TLS_RSA_EXPORT1024_WITH_RC4_56_SHA}, - /* AES ciphers. */ - {"dhe_dss_aes_128_cbc_sha", TLS_DHE_DSS_WITH_AES_128_CBC_SHA}, - {"dhe_dss_aes_256_cbc_sha", TLS_DHE_DSS_WITH_AES_256_CBC_SHA}, - {"dhe_rsa_aes_128_cbc_sha", TLS_DHE_RSA_WITH_AES_128_CBC_SHA}, - {"dhe_rsa_aes_256_cbc_sha", TLS_DHE_RSA_WITH_AES_256_CBC_SHA}, - {"rsa_aes_128_sha", TLS_RSA_WITH_AES_128_CBC_SHA}, - {"rsa_aes_256_sha", TLS_RSA_WITH_AES_256_CBC_SHA}, - /* ECC ciphers. */ - {"ecdh_ecdsa_null_sha", TLS_ECDH_ECDSA_WITH_NULL_SHA}, - {"ecdh_ecdsa_rc4_128_sha", TLS_ECDH_ECDSA_WITH_RC4_128_SHA}, - {"ecdh_ecdsa_3des_sha", TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA}, - {"ecdh_ecdsa_aes_128_sha", TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA}, - {"ecdh_ecdsa_aes_256_sha", TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA}, - {"ecdhe_ecdsa_null_sha", TLS_ECDHE_ECDSA_WITH_NULL_SHA}, - {"ecdhe_ecdsa_rc4_128_sha", TLS_ECDHE_ECDSA_WITH_RC4_128_SHA}, - {"ecdhe_ecdsa_3des_sha", TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA}, - {"ecdhe_ecdsa_aes_128_sha", TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA}, - {"ecdhe_ecdsa_aes_256_sha", TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA}, - {"ecdh_rsa_null_sha", TLS_ECDH_RSA_WITH_NULL_SHA}, - {"ecdh_rsa_128_sha", TLS_ECDH_RSA_WITH_RC4_128_SHA}, - {"ecdh_rsa_3des_sha", TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA}, - {"ecdh_rsa_aes_128_sha", TLS_ECDH_RSA_WITH_AES_128_CBC_SHA}, - {"ecdh_rsa_aes_256_sha", TLS_ECDH_RSA_WITH_AES_256_CBC_SHA}, - {"ecdhe_rsa_null", TLS_ECDHE_RSA_WITH_NULL_SHA}, - {"ecdhe_rsa_rc4_128_sha", TLS_ECDHE_RSA_WITH_RC4_128_SHA}, - {"ecdhe_rsa_3des_sha", TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA}, - {"ecdhe_rsa_aes_128_sha", TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA}, - {"ecdhe_rsa_aes_256_sha", TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA}, - {"ecdh_anon_null_sha", TLS_ECDH_anon_WITH_NULL_SHA}, - {"ecdh_anon_rc4_128sha", TLS_ECDH_anon_WITH_RC4_128_SHA}, - {"ecdh_anon_3des_sha", TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA}, - {"ecdh_anon_aes_128_sha", TLS_ECDH_anon_WITH_AES_128_CBC_SHA}, - {"ecdh_anon_aes_256_sha", TLS_ECDH_anon_WITH_AES_256_CBC_SHA}, -#ifdef TLS_RSA_WITH_NULL_SHA256 - /* new HMAC-SHA256 cipher suites specified in RFC */ - {"rsa_null_sha_256", TLS_RSA_WITH_NULL_SHA256}, - {"rsa_aes_128_cbc_sha_256", TLS_RSA_WITH_AES_128_CBC_SHA256}, - {"rsa_aes_256_cbc_sha_256", TLS_RSA_WITH_AES_256_CBC_SHA256}, - {"dhe_rsa_aes_128_cbc_sha_256", TLS_DHE_RSA_WITH_AES_128_CBC_SHA256}, - {"dhe_rsa_aes_256_cbc_sha_256", TLS_DHE_RSA_WITH_AES_256_CBC_SHA256}, - {"ecdhe_ecdsa_aes_128_cbc_sha_256", TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256}, - {"ecdhe_rsa_aes_128_cbc_sha_256", TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256}, -#endif -#ifdef TLS_RSA_WITH_AES_128_GCM_SHA256 - /* AES GCM cipher suites in RFC 5288 and RFC 5289 */ - {"rsa_aes_128_gcm_sha_256", TLS_RSA_WITH_AES_128_GCM_SHA256}, - {"dhe_rsa_aes_128_gcm_sha_256", TLS_DHE_RSA_WITH_AES_128_GCM_SHA256}, - {"dhe_dss_aes_128_gcm_sha_256", TLS_DHE_DSS_WITH_AES_128_GCM_SHA256}, - {"ecdhe_ecdsa_aes_128_gcm_sha_256", TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256}, - {"ecdh_ecdsa_aes_128_gcm_sha_256", TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256}, - {"ecdhe_rsa_aes_128_gcm_sha_256", TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256}, - {"ecdh_rsa_aes_128_gcm_sha_256", TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256}, -#endif -#ifdef TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 - /* cipher suites using SHA384 */ - {"rsa_aes_256_gcm_sha_384", TLS_RSA_WITH_AES_256_GCM_SHA384}, - {"dhe_rsa_aes_256_gcm_sha_384", TLS_DHE_RSA_WITH_AES_256_GCM_SHA384}, - {"dhe_dss_aes_256_gcm_sha_384", TLS_DHE_DSS_WITH_AES_256_GCM_SHA384}, - {"ecdhe_ecdsa_aes_256_sha_384", TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384}, - {"ecdhe_rsa_aes_256_sha_384", TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384}, - {"ecdhe_ecdsa_aes_256_gcm_sha_384", TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384}, - {"ecdhe_rsa_aes_256_gcm_sha_384", TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384}, -#endif -#ifdef TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256 - /* chacha20-poly1305 cipher suites */ - {"ecdhe_rsa_chacha20_poly1305_sha_256", - TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256}, - {"ecdhe_ecdsa_chacha20_poly1305_sha_256", - TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256}, - {"dhe_rsa_chacha20_poly1305_sha_256", - TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256}, -#endif -}; - -static const char *pem_library = "libnsspem.so"; -static SECMODModule *mod = NULL; - -/* NSPR I/O layer we use to detect blocking direction during SSL handshake */ -static PRDescIdentity nspr_io_identity = PR_INVALID_IO_LAYER; -static PRIOMethods nspr_io_methods; - -static const char *nss_error_to_name(PRErrorCode code) -{ - const char *name = PR_ErrorToName(code); - if(name) - return name; - - return "unknown error"; -} - -static void nss_print_error_message(struct Curl_easy *data, PRUint32 err) -{ - failf(data, "%s", PR_ErrorToString(err, PR_LANGUAGE_I_DEFAULT)); -} - -static SECStatus set_ciphers(struct Curl_easy *data, PRFileDesc * model, - char *cipher_list) -{ - unsigned int i; - PRBool cipher_state[NUM_OF_CIPHERS]; - PRBool found; - char *cipher; - - /* use accessors to avoid dynamic linking issues after an update of NSS */ - const PRUint16 num_implemented_ciphers = SSL_GetNumImplementedCiphers(); - const PRUint16 *implemented_ciphers = SSL_GetImplementedCiphers(); - if(!implemented_ciphers) - return SECFailure; - - /* First disable all ciphers. This uses a different max value in case - * NSS adds more ciphers later we don't want them available by - * accident - */ - for(i = 0; i < num_implemented_ciphers; i++) { - SSL_CipherPrefSet(model, implemented_ciphers[i], PR_FALSE); - } - - /* Set every entry in our list to false */ - for(i = 0; i < NUM_OF_CIPHERS; i++) { - cipher_state[i] = PR_FALSE; - } - - cipher = cipher_list; - - while(cipher_list && (cipher_list[0])) { - while((*cipher) && (ISSPACE(*cipher))) - ++cipher; - - cipher_list = strchr(cipher, ','); - if(cipher_list) { - *cipher_list++ = '\0'; - } - - found = PR_FALSE; - - for(i=0; i. - */ -static PK11SlotInfo* nss_find_slot_by_name(const char *slot_name) -{ - PK11SlotInfo *slot; - PR_Lock(nss_findslot_lock); - slot = PK11_FindSlotByName(slot_name); - PR_Unlock(nss_findslot_lock); - return slot; -} - -/* Call PK11_CreateGenericObject() with the given obj_class and filename. If - * the call succeeds, append the object handle to the list of objects so that - * the object can be destroyed in Curl_nss_close(). */ -static CURLcode nss_create_object(struct ssl_connect_data *ssl, - CK_OBJECT_CLASS obj_class, - const char *filename, bool cacert) -{ - PK11SlotInfo *slot; - PK11GenericObject *obj; - CK_BBOOL cktrue = CK_TRUE; - CK_BBOOL ckfalse = CK_FALSE; - CK_ATTRIBUTE attrs[/* max count of attributes */ 4]; - int attr_cnt = 0; - CURLcode result = (cacert) - ? CURLE_SSL_CACERT_BADFILE - : CURLE_SSL_CERTPROBLEM; - - const int slot_id = (cacert) ? 0 : 1; - char *slot_name = aprintf("PEM Token #%d", slot_id); - if(!slot_name) - return CURLE_OUT_OF_MEMORY; - - slot = nss_find_slot_by_name(slot_name); - free(slot_name); - if(!slot) - return result; - - PK11_SETATTRS(attrs, attr_cnt, CKA_CLASS, &obj_class, sizeof(obj_class)); - PK11_SETATTRS(attrs, attr_cnt, CKA_TOKEN, &cktrue, sizeof(CK_BBOOL)); - PK11_SETATTRS(attrs, attr_cnt, CKA_LABEL, (unsigned char *)filename, - strlen(filename) + 1); - - if(CKO_CERTIFICATE == obj_class) { - CK_BBOOL *pval = (cacert) ? (&cktrue) : (&ckfalse); - PK11_SETATTRS(attrs, attr_cnt, CKA_TRUST, pval, sizeof(*pval)); - } - - obj = PK11_CreateGenericObject(slot, attrs, attr_cnt, PR_FALSE); - PK11_FreeSlot(slot); - if(!obj) - return result; - - if(!Curl_llist_insert_next(ssl->obj_list, ssl->obj_list->tail, obj)) { - PK11_DestroyGenericObject(obj); - return CURLE_OUT_OF_MEMORY; - } - - if(!cacert && CKO_CERTIFICATE == obj_class) - /* store reference to a client certificate */ - ssl->obj_clicert = obj; - - return CURLE_OK; -} - -/* Destroy the NSS object whose handle is given by ptr. This function is - * a callback of Curl_llist_alloc() used by Curl_llist_destroy() to destroy - * NSS objects in Curl_nss_close() */ -static void nss_destroy_object(void *user, void *ptr) -{ - PK11GenericObject *obj = (PK11GenericObject *)ptr; - (void) user; - PK11_DestroyGenericObject(obj); -} - -/* same as nss_destroy_object() but for CRL items */ -static void nss_destroy_crl_item(void *user, void *ptr) -{ - SECItem *crl_der = (SECItem *)ptr; - (void) user; - SECITEM_FreeItem(crl_der, PR_TRUE); -} - -static CURLcode nss_load_cert(struct ssl_connect_data *ssl, - const char *filename, PRBool cacert) -{ - CURLcode result = (cacert) - ? CURLE_SSL_CACERT_BADFILE - : CURLE_SSL_CERTPROBLEM; - - /* libnsspem.so leaks memory if the requested file does not exist. For more - * details, go to . */ - if(is_file(filename)) - result = nss_create_object(ssl, CKO_CERTIFICATE, filename, cacert); - - if(!result && !cacert) { - /* we have successfully loaded a client certificate */ - CERTCertificate *cert; - char *nickname = NULL; - char *n = strrchr(filename, '/'); - if(n) - n++; - - /* The following undocumented magic helps to avoid a SIGSEGV on call - * of PK11_ReadRawAttribute() from SelectClientCert() when using an - * immature version of libnsspem.so. For more details, go to - * . */ - nickname = aprintf("PEM Token #1:%s", n); - if(nickname) { - cert = PK11_FindCertFromNickname(nickname, NULL); - if(cert) - CERT_DestroyCertificate(cert); - - free(nickname); - } - } - - return result; -} - -/* add given CRL to cache if it is not already there */ -static CURLcode nss_cache_crl(SECItem *crl_der) -{ - CERTCertDBHandle *db = CERT_GetDefaultCertDB(); - CERTSignedCrl *crl = SEC_FindCrlByDERCert(db, crl_der, 0); - if(crl) { - /* CRL already cached */ - SEC_DestroyCrl(crl); - SECITEM_FreeItem(crl_der, PR_TRUE); - return CURLE_OK; - } - - /* acquire lock before call of CERT_CacheCRL() and accessing nss_crl_list */ - PR_Lock(nss_crllock); - - /* store the CRL item so that we can free it in Curl_nss_cleanup() */ - if(!Curl_llist_insert_next(nss_crl_list, nss_crl_list->tail, crl_der)) { - SECITEM_FreeItem(crl_der, PR_TRUE); - PR_Unlock(nss_crllock); - return CURLE_OUT_OF_MEMORY; - } - - if(SECSuccess != CERT_CacheCRL(db, crl_der)) { - /* unable to cache CRL */ - PR_Unlock(nss_crllock); - return CURLE_SSL_CRL_BADFILE; - } - - /* we need to clear session cache, so that the CRL could take effect */ - SSL_ClearSessionCache(); - PR_Unlock(nss_crllock); - return CURLE_OK; -} - -static CURLcode nss_load_crl(const char *crlfilename) -{ - PRFileDesc *infile; - PRFileInfo info; - SECItem filedata = { 0, NULL, 0 }; - SECItem *crl_der = NULL; - char *body; - - infile = PR_Open(crlfilename, PR_RDONLY, 0); - if(!infile) - return CURLE_SSL_CRL_BADFILE; - - if(PR_SUCCESS != PR_GetOpenFileInfo(infile, &info)) - goto fail; - - if(!SECITEM_AllocItem(NULL, &filedata, info.size + /* zero ended */ 1)) - goto fail; - - if(info.size != PR_Read(infile, filedata.data, info.size)) - goto fail; - - crl_der = SECITEM_AllocItem(NULL, NULL, 0U); - if(!crl_der) - goto fail; - - /* place a trailing zero right after the visible data */ - body = (char *)filedata.data; - body[--filedata.len] = '\0'; - - body = strstr(body, "-----BEGIN"); - if(body) { - /* assume ASCII */ - char *trailer; - char *begin = PORT_Strchr(body, '\n'); - if(!begin) - begin = PORT_Strchr(body, '\r'); - if(!begin) - goto fail; - - trailer = strstr(++begin, "-----END"); - if(!trailer) - goto fail; - - /* retrieve DER from ASCII */ - *trailer = '\0'; - if(ATOB_ConvertAsciiToItem(crl_der, begin)) - goto fail; - - SECITEM_FreeItem(&filedata, PR_FALSE); - } - else - /* assume DER */ - *crl_der = filedata; - - PR_Close(infile); - return nss_cache_crl(crl_der); - -fail: - PR_Close(infile); - SECITEM_FreeItem(crl_der, PR_TRUE); - SECITEM_FreeItem(&filedata, PR_FALSE); - return CURLE_SSL_CRL_BADFILE; -} - -static CURLcode nss_load_key(struct connectdata *conn, int sockindex, - char *key_file) -{ - PK11SlotInfo *slot; - SECStatus status; - CURLcode result; - struct ssl_connect_data *ssl = conn->ssl; - struct Curl_easy *data = conn->data; - - (void)sockindex; /* unused */ - - result = nss_create_object(ssl, CKO_PRIVATE_KEY, key_file, FALSE); - if(result) { - PR_SetError(SEC_ERROR_BAD_KEY, 0); - return result; - } - - slot = nss_find_slot_by_name("PEM Token #1"); - if(!slot) - return CURLE_SSL_CERTPROBLEM; - - /* This will force the token to be seen as re-inserted */ - SECMOD_WaitForAnyTokenEvent(mod, 0, 0); - PK11_IsPresent(slot); - - status = PK11_Authenticate(slot, PR_TRUE, SSL_SET_OPTION(key_passwd)); - PK11_FreeSlot(slot); - - return (SECSuccess == status) ? CURLE_OK : CURLE_SSL_CERTPROBLEM; -} - -static int display_error(struct connectdata *conn, PRInt32 err, - const char *filename) -{ - switch(err) { - case SEC_ERROR_BAD_PASSWORD: - failf(conn->data, "Unable to load client key: Incorrect password"); - return 1; - case SEC_ERROR_UNKNOWN_CERT: - failf(conn->data, "Unable to load certificate %s", filename); - return 1; - default: - break; - } - return 0; /* The caller will print a generic error */ -} - -static CURLcode cert_stuff(struct connectdata *conn, int sockindex, - char *cert_file, char *key_file) -{ - struct Curl_easy *data = conn->data; - CURLcode result; - - if(cert_file) { - result = nss_load_cert(&conn->ssl[sockindex], cert_file, PR_FALSE); - if(result) { - const PRErrorCode err = PR_GetError(); - if(!display_error(conn, err, cert_file)) { - const char *err_name = nss_error_to_name(err); - failf(data, "unable to load client cert: %d (%s)", err, err_name); - } - - return result; - } - } - - if(key_file || (is_file(cert_file))) { - if(key_file) - result = nss_load_key(conn, sockindex, key_file); - else - /* In case the cert file also has the key */ - result = nss_load_key(conn, sockindex, cert_file); - if(result) { - const PRErrorCode err = PR_GetError(); - if(!display_error(conn, err, key_file)) { - const char *err_name = nss_error_to_name(err); - failf(data, "unable to load client key: %d (%s)", err, err_name); - } - - return result; - } - } - - return CURLE_OK; -} - -static char *nss_get_password(PK11SlotInfo *slot, PRBool retry, void *arg) -{ - (void)slot; /* unused */ - - if(retry || NULL == arg) - return NULL; - else - return (char *)PORT_Strdup((char *)arg); -} - -/* bypass the default SSL_AuthCertificate() hook in case we do not want to - * verify peer */ -static SECStatus nss_auth_cert_hook(void *arg, PRFileDesc *fd, PRBool checksig, - PRBool isServer) -{ - struct connectdata *conn = (struct connectdata *)arg; - -#ifdef SSL_ENABLE_OCSP_STAPLING - if(SSL_CONN_CONFIG(verifystatus)) { - SECStatus cacheResult; - - const SECItemArray *csa = SSL_PeerStapledOCSPResponses(fd); - if(!csa) { - failf(conn->data, "Invalid OCSP response"); - return SECFailure; - } - - if(csa->len == 0) { - failf(conn->data, "No OCSP response received"); - return SECFailure; - } - - cacheResult = CERT_CacheOCSPResponseFromSideChannel( - CERT_GetDefaultCertDB(), SSL_PeerCertificate(fd), - PR_Now(), &csa->items[0], arg - ); - - if(cacheResult != SECSuccess) { - failf(conn->data, "Invalid OCSP response"); - return cacheResult; - } - } -#endif - - if(!SSL_CONN_CONFIG(verifypeer)) { - infof(conn->data, "skipping SSL peer certificate verification\n"); - return SECSuccess; - } - - return SSL_AuthCertificate(CERT_GetDefaultCertDB(), fd, checksig, isServer); -} - -/** - * Inform the application that the handshake is complete. - */ -static void HandshakeCallback(PRFileDesc *sock, void *arg) -{ - struct connectdata *conn = (struct connectdata*) arg; - unsigned int buflenmax = 50; - unsigned char buf[50]; - unsigned int buflen; - SSLNextProtoState state; - - if(!conn->bits.tls_enable_npn && !conn->bits.tls_enable_alpn) { - return; - } - - if(SSL_GetNextProto(sock, &state, buf, &buflen, buflenmax) == SECSuccess) { - - switch(state) { -#if NSSVERNUM >= 0x031a00 /* 3.26.0 */ - /* used by NSS internally to implement 0-RTT */ - case SSL_NEXT_PROTO_EARLY_VALUE: - /* fall through! */ -#endif - case SSL_NEXT_PROTO_NO_SUPPORT: - case SSL_NEXT_PROTO_NO_OVERLAP: - infof(conn->data, "ALPN/NPN, server did not agree to a protocol\n"); - return; -#ifdef SSL_ENABLE_ALPN - case SSL_NEXT_PROTO_SELECTED: - infof(conn->data, "ALPN, server accepted to use %.*s\n", buflen, buf); - break; -#endif - case SSL_NEXT_PROTO_NEGOTIATED: - infof(conn->data, "NPN, server accepted to use %.*s\n", buflen, buf); - break; - } - -#ifdef USE_NGHTTP2 - if(buflen == NGHTTP2_PROTO_VERSION_ID_LEN && - !memcmp(NGHTTP2_PROTO_VERSION_ID, buf, NGHTTP2_PROTO_VERSION_ID_LEN)) { - conn->negnpn = CURL_HTTP_VERSION_2; - } - else -#endif - if(buflen == ALPN_HTTP_1_1_LENGTH && - !memcmp(ALPN_HTTP_1_1, buf, ALPN_HTTP_1_1_LENGTH)) { - conn->negnpn = CURL_HTTP_VERSION_1_1; - } - } -} - -#if NSSVERNUM >= 0x030f04 /* 3.15.4 */ -static SECStatus CanFalseStartCallback(PRFileDesc *sock, void *client_data, - PRBool *canFalseStart) -{ - struct connectdata *conn = client_data; - struct Curl_easy *data = conn->data; - - SSLChannelInfo channelInfo; - SSLCipherSuiteInfo cipherInfo; - - SECStatus rv; - PRBool negotiatedExtension; - - *canFalseStart = PR_FALSE; - - if(SSL_GetChannelInfo(sock, &channelInfo, sizeof(channelInfo)) != SECSuccess) - return SECFailure; - - if(SSL_GetCipherSuiteInfo(channelInfo.cipherSuite, &cipherInfo, - sizeof(cipherInfo)) != SECSuccess) - return SECFailure; - - /* Prevent version downgrade attacks from TLS 1.2, and avoid False Start for - * TLS 1.3 and later. See https://bugzilla.mozilla.org/show_bug.cgi?id=861310 - */ - if(channelInfo.protocolVersion != SSL_LIBRARY_VERSION_TLS_1_2) - goto end; - - /* Only allow ECDHE key exchange algorithm. - * See https://bugzilla.mozilla.org/show_bug.cgi?id=952863 */ - if(cipherInfo.keaType != ssl_kea_ecdh) - goto end; - - /* Prevent downgrade attacks on the symmetric cipher. We do not allow CBC - * mode due to BEAST, POODLE, and other attacks on the MAC-then-Encrypt - * design. See https://bugzilla.mozilla.org/show_bug.cgi?id=1109766 */ - if(cipherInfo.symCipher != ssl_calg_aes_gcm) - goto end; - - /* Enforce ALPN or NPN to do False Start, as an indicator of server - * compatibility. */ - rv = SSL_HandshakeNegotiatedExtension(sock, ssl_app_layer_protocol_xtn, - &negotiatedExtension); - if(rv != SECSuccess || !negotiatedExtension) { - rv = SSL_HandshakeNegotiatedExtension(sock, ssl_next_proto_nego_xtn, - &negotiatedExtension); - } - - if(rv != SECSuccess || !negotiatedExtension) - goto end; - - *canFalseStart = PR_TRUE; - - infof(data, "Trying TLS False Start\n"); - -end: - return SECSuccess; -} -#endif - -static void display_cert_info(struct Curl_easy *data, - CERTCertificate *cert) -{ - char *subject, *issuer, *common_name; - PRExplodedTime printableTime; - char timeString[256]; - PRTime notBefore, notAfter; - - subject = CERT_NameToAscii(&cert->subject); - issuer = CERT_NameToAscii(&cert->issuer); - common_name = CERT_GetCommonName(&cert->subject); - infof(data, "\tsubject: %s\n", subject); - - CERT_GetCertTimes(cert, ¬Before, ¬After); - PR_ExplodeTime(notBefore, PR_GMTParameters, &printableTime); - PR_FormatTime(timeString, 256, "%b %d %H:%M:%S %Y GMT", &printableTime); - infof(data, "\tstart date: %s\n", timeString); - PR_ExplodeTime(notAfter, PR_GMTParameters, &printableTime); - PR_FormatTime(timeString, 256, "%b %d %H:%M:%S %Y GMT", &printableTime); - infof(data, "\texpire date: %s\n", timeString); - infof(data, "\tcommon name: %s\n", common_name); - infof(data, "\tissuer: %s\n", issuer); - - PR_Free(subject); - PR_Free(issuer); - PR_Free(common_name); -} - -static CURLcode display_conn_info(struct connectdata *conn, PRFileDesc *sock) -{ - CURLcode result = CURLE_OK; - SSLChannelInfo channel; - SSLCipherSuiteInfo suite; - CERTCertificate *cert; - CERTCertificate *cert2; - CERTCertificate *cert3; - PRTime now; - int i; - - if(SSL_GetChannelInfo(sock, &channel, sizeof channel) == - SECSuccess && channel.length == sizeof channel && - channel.cipherSuite) { - if(SSL_GetCipherSuiteInfo(channel.cipherSuite, - &suite, sizeof suite) == SECSuccess) { - infof(conn->data, "SSL connection using %s\n", suite.cipherSuiteName); - } - } - - cert = SSL_PeerCertificate(sock); - if(cert) { - infof(conn->data, "Server certificate:\n"); - - if(!conn->data->set.ssl.certinfo) { - display_cert_info(conn->data, cert); - CERT_DestroyCertificate(cert); - } - else { - /* Count certificates in chain. */ - now = PR_Now(); - i = 1; - if(!cert->isRoot) { - cert2 = CERT_FindCertIssuer(cert, now, certUsageSSLCA); - while(cert2) { - i++; - if(cert2->isRoot) { - CERT_DestroyCertificate(cert2); - break; - } - cert3 = CERT_FindCertIssuer(cert2, now, certUsageSSLCA); - CERT_DestroyCertificate(cert2); - cert2 = cert3; - } - } - - result = Curl_ssl_init_certinfo(conn->data, i); - if(!result) { - for(i = 0; cert; cert = cert2) { - result = Curl_extract_certinfo(conn, i++, (char *)cert->derCert.data, - (char *)cert->derCert.data + - cert->derCert.len); - if(result) - break; - - if(cert->isRoot) { - CERT_DestroyCertificate(cert); - break; - } - - cert2 = CERT_FindCertIssuer(cert, now, certUsageSSLCA); - CERT_DestroyCertificate(cert); - } - } - } - } - - return result; -} - -static SECStatus BadCertHandler(void *arg, PRFileDesc *sock) -{ - struct connectdata *conn = (struct connectdata *)arg; - struct Curl_easy *data = conn->data; - PRErrorCode err = PR_GetError(); - CERTCertificate *cert; - - /* remember the cert verification result */ - if(SSL_IS_PROXY()) - data->set.proxy_ssl.certverifyresult = err; - else - data->set.ssl.certverifyresult = err; - - if(err == SSL_ERROR_BAD_CERT_DOMAIN && !SSL_CONN_CONFIG(verifyhost)) - /* we are asked not to verify the host name */ - return SECSuccess; - - /* print only info about the cert, the error is printed off the callback */ - cert = SSL_PeerCertificate(sock); - if(cert) { - infof(data, "Server certificate:\n"); - display_cert_info(data, cert); - CERT_DestroyCertificate(cert); - } - - return SECFailure; -} - -/** - * - * Check that the Peer certificate's issuer certificate matches the one found - * by issuer_nickname. This is not exactly the way OpenSSL and GNU TLS do the - * issuer check, so we provide comments that mimic the OpenSSL - * X509_check_issued function (in x509v3/v3_purp.c) - */ -static SECStatus check_issuer_cert(PRFileDesc *sock, - char *issuer_nickname) -{ - CERTCertificate *cert, *cert_issuer, *issuer; - SECStatus res=SECSuccess; - void *proto_win = NULL; - - cert = SSL_PeerCertificate(sock); - cert_issuer = CERT_FindCertIssuer(cert, PR_Now(), certUsageObjectSigner); - - proto_win = SSL_RevealPinArg(sock); - issuer = PK11_FindCertFromNickname(issuer_nickname, proto_win); - - if((!cert_issuer) || (!issuer)) - res = SECFailure; - else if(SECITEM_CompareItem(&cert_issuer->derCert, - &issuer->derCert)!=SECEqual) - res = SECFailure; - - CERT_DestroyCertificate(cert); - CERT_DestroyCertificate(issuer); - CERT_DestroyCertificate(cert_issuer); - return res; -} - -static CURLcode cmp_peer_pubkey(struct ssl_connect_data *connssl, - const char *pinnedpubkey) -{ - CURLcode result = CURLE_SSL_PINNEDPUBKEYNOTMATCH; - struct Curl_easy *data = connssl->data; - CERTCertificate *cert; - - if(!pinnedpubkey) - /* no pinned public key specified */ - return CURLE_OK; - - /* get peer certificate */ - cert = SSL_PeerCertificate(connssl->handle); - if(cert) { - /* extract public key from peer certificate */ - SECKEYPublicKey *pubkey = CERT_ExtractPublicKey(cert); - if(pubkey) { - /* encode the public key as DER */ - SECItem *cert_der = PK11_DEREncodePublicKey(pubkey); - if(cert_der) { - /* compare the public key with the pinned public key */ - result = Curl_pin_peer_pubkey(data, pinnedpubkey, cert_der->data, - cert_der->len); - SECITEM_FreeItem(cert_der, PR_TRUE); - } - SECKEY_DestroyPublicKey(pubkey); - } - CERT_DestroyCertificate(cert); - } - - /* report the resulting status */ - switch(result) { - case CURLE_OK: - infof(data, "pinned public key verified successfully!\n"); - break; - case CURLE_SSL_PINNEDPUBKEYNOTMATCH: - failf(data, "failed to verify pinned public key"); - break; - default: - /* OOM, etc. */ - break; - } - - return result; -} - -/** - * - * Callback to pick the SSL client certificate. - */ -static SECStatus SelectClientCert(void *arg, PRFileDesc *sock, - struct CERTDistNamesStr *caNames, - struct CERTCertificateStr **pRetCert, - struct SECKEYPrivateKeyStr **pRetKey) -{ - struct ssl_connect_data *connssl = (struct ssl_connect_data *)arg; - struct Curl_easy *data = connssl->data; - const char *nickname = connssl->client_nickname; - static const char pem_slotname[] = "PEM Token #1"; - - if(connssl->obj_clicert) { - /* use the cert/key provided by PEM reader */ - SECItem cert_der = { 0, NULL, 0 }; - void *proto_win = SSL_RevealPinArg(sock); - struct CERTCertificateStr *cert; - struct SECKEYPrivateKeyStr *key; - - PK11SlotInfo *slot = nss_find_slot_by_name(pem_slotname); - if(NULL == slot) { - failf(data, "NSS: PK11 slot not found: %s", pem_slotname); - return SECFailure; - } - - if(PK11_ReadRawAttribute(PK11_TypeGeneric, connssl->obj_clicert, CKA_VALUE, - &cert_der) != SECSuccess) { - failf(data, "NSS: CKA_VALUE not found in PK11 generic object"); - PK11_FreeSlot(slot); - return SECFailure; - } - - cert = PK11_FindCertFromDERCertItem(slot, &cert_der, proto_win); - SECITEM_FreeItem(&cert_der, PR_FALSE); - if(NULL == cert) { - failf(data, "NSS: client certificate from file not found"); - PK11_FreeSlot(slot); - return SECFailure; - } - - key = PK11_FindPrivateKeyFromCert(slot, cert, NULL); - PK11_FreeSlot(slot); - if(NULL == key) { - failf(data, "NSS: private key from file not found"); - CERT_DestroyCertificate(cert); - return SECFailure; - } - - infof(data, "NSS: client certificate from file\n"); - display_cert_info(data, cert); - - *pRetCert = cert; - *pRetKey = key; - return SECSuccess; - } - - /* use the default NSS hook */ - if(SECSuccess != NSS_GetClientAuthData((void *)nickname, sock, caNames, - pRetCert, pRetKey) - || NULL == *pRetCert) { - - if(NULL == nickname) - failf(data, "NSS: client certificate not found (nickname not " - "specified)"); - else - failf(data, "NSS: client certificate not found: %s", nickname); - - return SECFailure; - } - - /* get certificate nickname if any */ - nickname = (*pRetCert)->nickname; - if(NULL == nickname) - nickname = "[unknown]"; - - if(!strncmp(nickname, pem_slotname, sizeof(pem_slotname) - 1U)) { - failf(data, "NSS: refusing previously loaded certificate from file: %s", - nickname); - return SECFailure; - } - - if(NULL == *pRetKey) { - failf(data, "NSS: private key not found for certificate: %s", nickname); - return SECFailure; - } - - infof(data, "NSS: using client certificate: %s\n", nickname); - display_cert_info(data, *pRetCert); - return SECSuccess; -} - -/* update blocking direction in case of PR_WOULD_BLOCK_ERROR */ -static void nss_update_connecting_state(ssl_connect_state state, void *secret) -{ - struct ssl_connect_data *connssl = (struct ssl_connect_data *)secret; - if(PR_GetError() != PR_WOULD_BLOCK_ERROR) - /* an unrelated error is passing by */ - return; - - switch(connssl->connecting_state) { - case ssl_connect_2: - case ssl_connect_2_reading: - case ssl_connect_2_writing: - break; - default: - /* we are not called from an SSL handshake */ - return; - } - - /* update the state accordingly */ - connssl->connecting_state = state; -} - -/* recv() wrapper we use to detect blocking direction during SSL handshake */ -static PRInt32 nspr_io_recv(PRFileDesc *fd, void *buf, PRInt32 amount, - PRIntn flags, PRIntervalTime timeout) -{ - const PRRecvFN recv_fn = fd->lower->methods->recv; - const PRInt32 rv = recv_fn(fd->lower, buf, amount, flags, timeout); - if(rv < 0) - /* check for PR_WOULD_BLOCK_ERROR and update blocking direction */ - nss_update_connecting_state(ssl_connect_2_reading, fd->secret); - return rv; -} - -/* send() wrapper we use to detect blocking direction during SSL handshake */ -static PRInt32 nspr_io_send(PRFileDesc *fd, const void *buf, PRInt32 amount, - PRIntn flags, PRIntervalTime timeout) -{ - const PRSendFN send_fn = fd->lower->methods->send; - const PRInt32 rv = send_fn(fd->lower, buf, amount, flags, timeout); - if(rv < 0) - /* check for PR_WOULD_BLOCK_ERROR and update blocking direction */ - nss_update_connecting_state(ssl_connect_2_writing, fd->secret); - return rv; -} - -/* close() wrapper to avoid assertion failure due to fd->secret != NULL */ -static PRStatus nspr_io_close(PRFileDesc *fd) -{ - const PRCloseFN close_fn = PR_GetDefaultIOMethods()->close; - fd->secret = NULL; - return close_fn(fd); -} - -/* data might be NULL */ -static CURLcode nss_init_core(struct Curl_easy *data, const char *cert_dir) -{ - NSSInitParameters initparams; - - if(nss_context != NULL) - return CURLE_OK; - - memset((void *) &initparams, '\0', sizeof(initparams)); - initparams.length = sizeof(initparams); - - if(cert_dir) { - char *certpath = aprintf("sql:%s", cert_dir); - if(!certpath) - return CURLE_OUT_OF_MEMORY; - - infof(data, "Initializing NSS with certpath: %s\n", certpath); - nss_context = NSS_InitContext(certpath, "", "", "", &initparams, - NSS_INIT_READONLY | NSS_INIT_PK11RELOAD); - free(certpath); - - if(nss_context != NULL) - return CURLE_OK; - - infof(data, "Unable to initialize NSS database\n"); - } - - infof(data, "Initializing NSS with certpath: none\n"); - nss_context = NSS_InitContext("", "", "", "", &initparams, NSS_INIT_READONLY - | NSS_INIT_NOCERTDB | NSS_INIT_NOMODDB | NSS_INIT_FORCEOPEN - | NSS_INIT_NOROOTINIT | NSS_INIT_OPTIMIZESPACE | NSS_INIT_PK11RELOAD); - if(nss_context != NULL) - return CURLE_OK; - - infof(data, "Unable to initialize NSS\n"); - return CURLE_SSL_CACERT_BADFILE; -} - -/* data might be NULL */ -static CURLcode nss_init(struct Curl_easy *data) -{ - char *cert_dir; - struct_stat st; - CURLcode result; - - if(initialized) - return CURLE_OK; - - /* list of all CRL items we need to destroy in Curl_nss_cleanup() */ - nss_crl_list = Curl_llist_alloc(nss_destroy_crl_item); - if(!nss_crl_list) - return CURLE_OUT_OF_MEMORY; - - /* First we check if $SSL_DIR points to a valid dir */ - cert_dir = getenv("SSL_DIR"); - if(cert_dir) { - if((stat(cert_dir, &st) != 0) || - (!S_ISDIR(st.st_mode))) { - cert_dir = NULL; - } - } - - /* Now we check if the default location is a valid dir */ - if(!cert_dir) { - if((stat(SSL_DIR, &st) == 0) && - (S_ISDIR(st.st_mode))) { - cert_dir = (char *)SSL_DIR; - } - } - - if(nspr_io_identity == PR_INVALID_IO_LAYER) { - /* allocate an identity for our own NSPR I/O layer */ - nspr_io_identity = PR_GetUniqueIdentity("libcurl"); - if(nspr_io_identity == PR_INVALID_IO_LAYER) - return CURLE_OUT_OF_MEMORY; - - /* the default methods just call down to the lower I/O layer */ - memcpy(&nspr_io_methods, PR_GetDefaultIOMethods(), sizeof nspr_io_methods); - - /* override certain methods in the table by our wrappers */ - nspr_io_methods.recv = nspr_io_recv; - nspr_io_methods.send = nspr_io_send; - nspr_io_methods.close = nspr_io_close; - } - - result = nss_init_core(data, cert_dir); - if(result) - return result; - - if(!any_cipher_enabled()) - NSS_SetDomesticPolicy(); - - initialized = 1; - - return CURLE_OK; -} - -/** - * Global SSL init - * - * @retval 0 error initializing SSL - * @retval 1 SSL initialized successfully - */ -int Curl_nss_init(void) -{ - /* curl_global_init() is not thread-safe so this test is ok */ - if(nss_initlock == NULL) { - PR_Init(PR_USER_THREAD, PR_PRIORITY_NORMAL, 256); - nss_initlock = PR_NewLock(); - nss_crllock = PR_NewLock(); - nss_findslot_lock = PR_NewLock(); - } - - /* We will actually initialize NSS later */ - - return 1; -} - -/* data might be NULL */ -CURLcode Curl_nss_force_init(struct Curl_easy *data) -{ - CURLcode result; - if(!nss_initlock) { - if(data) - failf(data, "unable to initialize NSS, curl_global_init() should have " - "been called with CURL_GLOBAL_SSL or CURL_GLOBAL_ALL"); - return CURLE_FAILED_INIT; - } - - PR_Lock(nss_initlock); - result = nss_init(data); - PR_Unlock(nss_initlock); - - return result; -} - -/* Global cleanup */ -void Curl_nss_cleanup(void) -{ - /* This function isn't required to be threadsafe and this is only done - * as a safety feature. - */ - PR_Lock(nss_initlock); - if(initialized) { - /* Free references to client certificates held in the SSL session cache. - * Omitting this hampers destruction of the security module owning - * the certificates. */ - SSL_ClearSessionCache(); - - if(mod && SECSuccess == SECMOD_UnloadUserModule(mod)) { - SECMOD_DestroyModule(mod); - mod = NULL; - } - NSS_ShutdownContext(nss_context); - nss_context = NULL; - } - - /* destroy all CRL items */ - Curl_llist_destroy(nss_crl_list, NULL); - nss_crl_list = NULL; - - PR_Unlock(nss_initlock); - - PR_DestroyLock(nss_initlock); - PR_DestroyLock(nss_crllock); - PR_DestroyLock(nss_findslot_lock); - nss_initlock = NULL; - - initialized = 0; -} - -/* - * This function uses SSL_peek 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 - */ -int -Curl_nss_check_cxn(struct connectdata *conn) -{ - int rc; - char buf; - - rc = - PR_Recv(conn->ssl[FIRSTSOCKET].handle, (void *)&buf, 1, PR_MSG_PEEK, - PR_SecondsToInterval(1)); - if(rc > 0) - return 1; /* connection still in place */ - - if(rc == 0) - return 0; /* connection has been closed */ - - return -1; /* connection status unknown */ -} - -static void nss_close(struct ssl_connect_data *connssl) -{ - /* before the cleanup, check whether we are using a client certificate */ - const bool client_cert = (connssl->client_nickname != NULL) - || (connssl->obj_clicert != NULL); - - free(connssl->client_nickname); - connssl->client_nickname = NULL; - - /* destroy all NSS objects in order to avoid failure of NSS shutdown */ - Curl_llist_destroy(connssl->obj_list, NULL); - connssl->obj_list = NULL; - connssl->obj_clicert = NULL; - - if(connssl->handle) { - if(client_cert) - /* A server might require different authentication based on the - * particular path being requested by the client. To support this - * scenario, we must ensure that a connection will never reuse the - * authentication data from a previous connection. */ - SSL_InvalidateSession(connssl->handle); - - PR_Close(connssl->handle); - connssl->handle = NULL; - } -} - -/* - * This function is called when an SSL connection is closed. - */ -void Curl_nss_close(struct connectdata *conn, int sockindex) -{ - struct ssl_connect_data *connssl = &conn->ssl[sockindex]; - struct ssl_connect_data *connssl_proxy = &conn->proxy_ssl[sockindex]; - - if(connssl->handle || connssl_proxy->handle) { - /* NSS closes the socket we previously handed to it, so we must mark it - as closed to avoid double close */ - fake_sclose(conn->sock[sockindex]); - conn->sock[sockindex] = CURL_SOCKET_BAD; - } - - if(connssl->handle) - /* nss_close(connssl) will transitively close also connssl_proxy->handle - if both are used. Clear it to avoid a double close leading to crash. */ - connssl_proxy->handle = NULL; - - nss_close(connssl); - nss_close(connssl_proxy); -} - -/* return true if NSS can provide error code (and possibly msg) for the - error */ -static bool is_nss_error(CURLcode err) -{ - switch(err) { - case CURLE_PEER_FAILED_VERIFICATION: - case CURLE_SSL_CACERT: - case CURLE_SSL_CERTPROBLEM: - case CURLE_SSL_CONNECT_ERROR: - case CURLE_SSL_ISSUER_ERROR: - return true; - - default: - return false; - } -} - -/* return true if the given error code is related to a client certificate */ -static bool is_cc_error(PRInt32 err) -{ - switch(err) { - case SSL_ERROR_BAD_CERT_ALERT: - case SSL_ERROR_EXPIRED_CERT_ALERT: - case SSL_ERROR_REVOKED_CERT_ALERT: - return true; - - default: - return false; - } -} - -static Curl_recv nss_recv; -static Curl_send nss_send; - -static CURLcode nss_load_ca_certificates(struct connectdata *conn, - int sockindex) -{ - struct Curl_easy *data = conn->data; - const char *cafile = SSL_CONN_CONFIG(CAfile); - const char *capath = SSL_CONN_CONFIG(CApath); - - if(cafile) { - CURLcode result = nss_load_cert(&conn->ssl[sockindex], cafile, PR_TRUE); - if(result) - return result; - } - - if(capath) { - struct_stat st; - if(stat(capath, &st) == -1) - return CURLE_SSL_CACERT_BADFILE; - - if(S_ISDIR(st.st_mode)) { - PRDirEntry *entry; - PRDir *dir = PR_OpenDir(capath); - if(!dir) - return CURLE_SSL_CACERT_BADFILE; - - while((entry = PR_ReadDir(dir, PR_SKIP_BOTH | PR_SKIP_HIDDEN))) { - char *fullpath = aprintf("%s/%s", capath, entry->name); - if(!fullpath) { - PR_CloseDir(dir); - return CURLE_OUT_OF_MEMORY; - } - - if(CURLE_OK != nss_load_cert(&conn->ssl[sockindex], fullpath, PR_TRUE)) - /* This is purposefully tolerant of errors so non-PEM files can - * be in the same directory */ - infof(data, "failed to load '%s' from CURLOPT_CAPATH\n", fullpath); - - free(fullpath); - } - - PR_CloseDir(dir); - } - else - infof(data, "warning: CURLOPT_CAPATH not a directory (%s)\n", capath); - } - - infof(data, " CAfile: %s\n CApath: %s\n", - cafile ? cafile : "none", - capath ? capath : "none"); - - return CURLE_OK; -} - -static CURLcode nss_init_sslver(SSLVersionRange *sslver, - struct Curl_easy *data, - struct connectdata *conn) -{ - switch(SSL_CONN_CONFIG(version)) { - case CURL_SSLVERSION_DEFAULT: - /* map CURL_SSLVERSION_DEFAULT to NSS default */ - if(SSL_VersionRangeGetDefault(ssl_variant_stream, sslver) != SECSuccess) - return CURLE_SSL_CONNECT_ERROR; - /* ... but make sure we use at least TLSv1.0 according to libcurl API */ - if(sslver->min < SSL_LIBRARY_VERSION_TLS_1_0) - sslver->min = SSL_LIBRARY_VERSION_TLS_1_0; - return CURLE_OK; - - case CURL_SSLVERSION_TLSv1: - sslver->min = SSL_LIBRARY_VERSION_TLS_1_0; - /* TODO: set sslver->max to SSL_LIBRARY_VERSION_TLS_1_3 once stable */ -#ifdef SSL_LIBRARY_VERSION_TLS_1_2 - sslver->max = SSL_LIBRARY_VERSION_TLS_1_2; -#elif defined SSL_LIBRARY_VERSION_TLS_1_1 - sslver->max = SSL_LIBRARY_VERSION_TLS_1_1; -#else - sslver->max = SSL_LIBRARY_VERSION_TLS_1_0; -#endif - return CURLE_OK; - - case CURL_SSLVERSION_SSLv2: - sslver->min = SSL_LIBRARY_VERSION_2; - sslver->max = SSL_LIBRARY_VERSION_2; - return CURLE_OK; - - case CURL_SSLVERSION_SSLv3: - sslver->min = SSL_LIBRARY_VERSION_3_0; - sslver->max = SSL_LIBRARY_VERSION_3_0; - return CURLE_OK; - - case CURL_SSLVERSION_TLSv1_0: - sslver->min = SSL_LIBRARY_VERSION_TLS_1_0; - sslver->max = SSL_LIBRARY_VERSION_TLS_1_0; - return CURLE_OK; - - case CURL_SSLVERSION_TLSv1_1: -#ifdef SSL_LIBRARY_VERSION_TLS_1_1 - sslver->min = SSL_LIBRARY_VERSION_TLS_1_1; - sslver->max = SSL_LIBRARY_VERSION_TLS_1_1; - return CURLE_OK; -#endif - break; - - case CURL_SSLVERSION_TLSv1_2: -#ifdef SSL_LIBRARY_VERSION_TLS_1_2 - sslver->min = SSL_LIBRARY_VERSION_TLS_1_2; - sslver->max = SSL_LIBRARY_VERSION_TLS_1_2; - return CURLE_OK; -#endif - break; - - case CURL_SSLVERSION_TLSv1_3: -#ifdef SSL_LIBRARY_VERSION_TLS_1_3 - sslver->min = SSL_LIBRARY_VERSION_TLS_1_3; - sslver->max = SSL_LIBRARY_VERSION_TLS_1_3; - return CURLE_OK; -#endif - break; - - default: - failf(data, "Unrecognized parameter passed via CURLOPT_SSLVERSION"); - return CURLE_SSL_CONNECT_ERROR; - } - - failf(data, "TLS minor version cannot be set"); - return CURLE_SSL_CONNECT_ERROR; -} - -static CURLcode nss_fail_connect(struct ssl_connect_data *connssl, - struct Curl_easy *data, - CURLcode curlerr) -{ - PRErrorCode err = 0; - - if(is_nss_error(curlerr)) { - /* read NSPR error code */ - err = PR_GetError(); - if(is_cc_error(err)) - curlerr = CURLE_SSL_CERTPROBLEM; - - /* print the error number and error string */ - infof(data, "NSS error %d (%s)\n", err, nss_error_to_name(err)); - - /* print a human-readable message describing the error if available */ - nss_print_error_message(data, err); - } - - /* cleanup on connection failure */ - Curl_llist_destroy(connssl->obj_list, NULL); - connssl->obj_list = NULL; - - return curlerr; -} - -/* Switch the SSL socket into blocking or non-blocking mode. */ -static CURLcode nss_set_blocking(struct ssl_connect_data *connssl, - struct Curl_easy *data, - bool blocking) -{ - static PRSocketOptionData sock_opt; - sock_opt.option = PR_SockOpt_Nonblocking; - sock_opt.value.non_blocking = !blocking; - - if(PR_SetSocketOption(connssl->handle, &sock_opt) != PR_SUCCESS) - return nss_fail_connect(connssl, data, CURLE_SSL_CONNECT_ERROR); - - return CURLE_OK; -} - -static CURLcode nss_setup_connect(struct connectdata *conn, int sockindex) -{ - PRFileDesc *model = NULL; - PRFileDesc *nspr_io = NULL; - PRFileDesc *nspr_io_stub = NULL; - PRBool ssl_no_cache; - PRBool ssl_cbc_random_iv; - struct Curl_easy *data = conn->data; - curl_socket_t sockfd = conn->sock[sockindex]; - struct ssl_connect_data *connssl = &conn->ssl[sockindex]; - CURLcode result; - bool second_layer = FALSE; - - SSLVersionRange sslver = { - SSL_LIBRARY_VERSION_TLS_1_0, /* min */ - SSL_LIBRARY_VERSION_TLS_1_0 /* max */ - }; - - connssl->data = data; - - /* list of all NSS objects we need to destroy in Curl_nss_close() */ - connssl->obj_list = Curl_llist_alloc(nss_destroy_object); - if(!connssl->obj_list) - return CURLE_OUT_OF_MEMORY; - - /* FIXME. NSS doesn't support multiple databases open at the same time. */ - PR_Lock(nss_initlock); - result = nss_init(conn->data); - if(result) { - PR_Unlock(nss_initlock); - goto error; - } - - result = CURLE_SSL_CONNECT_ERROR; - - if(!mod) { - char *configstring = aprintf("library=%s name=PEM", pem_library); - if(!configstring) { - PR_Unlock(nss_initlock); - goto error; - } - mod = SECMOD_LoadUserModule(configstring, NULL, PR_FALSE); - free(configstring); - - if(!mod || !mod->loaded) { - if(mod) { - SECMOD_DestroyModule(mod); - mod = NULL; - } - infof(data, "WARNING: failed to load NSS PEM library %s. Using " - "OpenSSL PEM certificates will not work.\n", pem_library); - } - } - - PK11_SetPasswordFunc(nss_get_password); - PR_Unlock(nss_initlock); - - model = PR_NewTCPSocket(); - if(!model) - goto error; - model = SSL_ImportFD(NULL, model); - - if(SSL_OptionSet(model, SSL_SECURITY, PR_TRUE) != SECSuccess) - goto error; - if(SSL_OptionSet(model, SSL_HANDSHAKE_AS_SERVER, PR_FALSE) != SECSuccess) - goto error; - if(SSL_OptionSet(model, SSL_HANDSHAKE_AS_CLIENT, PR_TRUE) != SECSuccess) - goto error; - - /* do not use SSL cache if disabled or we are not going to verify peer */ - ssl_no_cache = (data->set.general_ssl.sessionid - && SSL_CONN_CONFIG(verifypeer)) ? PR_FALSE : PR_TRUE; - if(SSL_OptionSet(model, SSL_NO_CACHE, ssl_no_cache) != SECSuccess) - goto error; - - /* enable/disable the requested SSL version(s) */ - if(nss_init_sslver(&sslver, data, conn) != CURLE_OK) - goto error; - if(SSL_VersionRangeSet(model, &sslver) != SECSuccess) - goto error; - - ssl_cbc_random_iv = !SSL_SET_OPTION(enable_beast); -#ifdef SSL_CBC_RANDOM_IV - /* unless the user explicitly asks to allow the protocol vulnerability, we - use the work-around */ - if(SSL_OptionSet(model, SSL_CBC_RANDOM_IV, ssl_cbc_random_iv) != SECSuccess) - infof(data, "warning: failed to set SSL_CBC_RANDOM_IV = %d\n", - ssl_cbc_random_iv); -#else - if(ssl_cbc_random_iv) - infof(data, "warning: support for SSL_CBC_RANDOM_IV not compiled in\n"); -#endif - - if(SSL_CONN_CONFIG(cipher_list)) { - if(set_ciphers(data, model, SSL_CONN_CONFIG(cipher_list)) != SECSuccess) { - result = CURLE_SSL_CIPHER; - goto error; - } - } - - if(!SSL_CONN_CONFIG(verifypeer) && SSL_CONN_CONFIG(verifyhost)) - infof(data, "warning: ignoring value of ssl.verifyhost\n"); - - /* bypass the default SSL_AuthCertificate() hook in case we do not want to - * verify peer */ - if(SSL_AuthCertificateHook(model, nss_auth_cert_hook, conn) != SECSuccess) - goto error; - - /* not checked yet */ - if(SSL_IS_PROXY()) - data->set.proxy_ssl.certverifyresult = 0; - else - data->set.ssl.certverifyresult = 0; - - if(SSL_BadCertHook(model, BadCertHandler, conn) != SECSuccess) - goto error; - - if(SSL_HandshakeCallback(model, HandshakeCallback, conn) != SECSuccess) - goto error; - - if(SSL_CONN_CONFIG(verifypeer)) { - const CURLcode rv = nss_load_ca_certificates(conn, sockindex); - if(rv) { - result = rv; - goto error; - } - } - - if(SSL_SET_OPTION(CRLfile)) { - const CURLcode rv = nss_load_crl(SSL_SET_OPTION(CRLfile)); - if(rv) { - result = rv; - goto error; - } - infof(data, " CRLfile: %s\n", SSL_SET_OPTION(CRLfile)); - } - - if(SSL_SET_OPTION(cert)) { - char *nickname = dup_nickname(data, SSL_SET_OPTION(cert)); - if(nickname) { - /* we are not going to use libnsspem.so to read the client cert */ - connssl->obj_clicert = NULL; - } - else { - CURLcode rv = cert_stuff(conn, sockindex, SSL_SET_OPTION(cert), - SSL_SET_OPTION(key)); - if(rv) { - /* failf() is already done in cert_stuff() */ - result = rv; - goto error; - } - } - - /* store the nickname for SelectClientCert() called during handshake */ - connssl->client_nickname = nickname; - } - else - connssl->client_nickname = NULL; - - if(SSL_GetClientAuthDataHook(model, SelectClientCert, - (void *)connssl) != SECSuccess) { - result = CURLE_SSL_CERTPROBLEM; - goto error; - } - - if(conn->proxy_ssl[sockindex].use) { - DEBUGASSERT(ssl_connection_complete == conn->proxy_ssl[sockindex].state); - DEBUGASSERT(conn->proxy_ssl[sockindex].handle != NULL); - nspr_io = conn->proxy_ssl[sockindex].handle; - second_layer = TRUE; - } - else { - /* wrap OS file descriptor by NSPR's file descriptor abstraction */ - nspr_io = PR_ImportTCPSocket(sockfd); - if(!nspr_io) - goto error; - } - - /* create our own NSPR I/O layer */ - nspr_io_stub = PR_CreateIOLayerStub(nspr_io_identity, &nspr_io_methods); - if(!nspr_io_stub) { - if(!second_layer) - PR_Close(nspr_io); - goto error; - } - - /* make the per-connection data accessible from NSPR I/O callbacks */ - nspr_io_stub->secret = (void *)connssl; - - /* push our new layer to the NSPR I/O stack */ - if(PR_PushIOLayer(nspr_io, PR_TOP_IO_LAYER, nspr_io_stub) != PR_SUCCESS) { - if(!second_layer) - PR_Close(nspr_io); - PR_Close(nspr_io_stub); - goto error; - } - - /* import our model socket onto the current I/O stack */ - connssl->handle = SSL_ImportFD(model, nspr_io); - if(!connssl->handle) { - if(!second_layer) - PR_Close(nspr_io); - goto error; - } - - PR_Close(model); /* We don't need this any more */ - model = NULL; - - /* This is the password associated with the cert that we're using */ - if(SSL_SET_OPTION(key_passwd)) { - SSL_SetPKCS11PinArg(connssl->handle, SSL_SET_OPTION(key_passwd)); - } - -#ifdef SSL_ENABLE_OCSP_STAPLING - if(SSL_CONN_CONFIG(verifystatus)) { - if(SSL_OptionSet(connssl->handle, SSL_ENABLE_OCSP_STAPLING, PR_TRUE) - != SECSuccess) - goto error; - } -#endif - -#ifdef SSL_ENABLE_NPN - if(SSL_OptionSet(connssl->handle, SSL_ENABLE_NPN, conn->bits.tls_enable_npn - ? PR_TRUE : PR_FALSE) != SECSuccess) - goto error; -#endif - -#ifdef SSL_ENABLE_ALPN - if(SSL_OptionSet(connssl->handle, SSL_ENABLE_ALPN, conn->bits.tls_enable_alpn - ? PR_TRUE : PR_FALSE) != SECSuccess) - goto error; -#endif - -#if NSSVERNUM >= 0x030f04 /* 3.15.4 */ - if(data->set.ssl.falsestart) { - if(SSL_OptionSet(connssl->handle, SSL_ENABLE_FALSE_START, PR_TRUE) - != SECSuccess) - goto error; - - if(SSL_SetCanFalseStartCallback(connssl->handle, CanFalseStartCallback, - conn) != SECSuccess) - goto error; - } -#endif - -#if defined(SSL_ENABLE_NPN) || defined(SSL_ENABLE_ALPN) - if(conn->bits.tls_enable_npn || conn->bits.tls_enable_alpn) { - int cur = 0; - unsigned char protocols[128]; - -#ifdef USE_NGHTTP2 - if(data->set.httpversion >= CURL_HTTP_VERSION_2) { - protocols[cur++] = NGHTTP2_PROTO_VERSION_ID_LEN; - memcpy(&protocols[cur], NGHTTP2_PROTO_VERSION_ID, - NGHTTP2_PROTO_VERSION_ID_LEN); - cur += NGHTTP2_PROTO_VERSION_ID_LEN; - } -#endif - protocols[cur++] = ALPN_HTTP_1_1_LENGTH; - memcpy(&protocols[cur], ALPN_HTTP_1_1, ALPN_HTTP_1_1_LENGTH); - cur += ALPN_HTTP_1_1_LENGTH; - - if(SSL_SetNextProtoNego(connssl->handle, protocols, cur) != SECSuccess) - goto error; - } -#endif - - - /* Force handshake on next I/O */ - if(SSL_ResetHandshake(connssl->handle, /* asServer */ PR_FALSE) - != SECSuccess) - goto error; - - /* propagate hostname to the TLS layer */ - if(SSL_SetURL(connssl->handle, SSL_IS_PROXY() ? conn->http_proxy.host.name : - conn->host.name) != SECSuccess) - goto error; - - /* prevent NSS from re-using the session for a different hostname */ - if(SSL_SetSockPeerID(connssl->handle, SSL_IS_PROXY() ? - conn->http_proxy.host.name : conn->host.name) - != SECSuccess) - goto error; - - return CURLE_OK; - -error: - if(model) - PR_Close(model); - - return nss_fail_connect(connssl, data, result); -} - -static CURLcode nss_do_connect(struct connectdata *conn, int sockindex) -{ - struct ssl_connect_data *connssl = &conn->ssl[sockindex]; - struct Curl_easy *data = conn->data; - CURLcode result = CURLE_SSL_CONNECT_ERROR; - PRUint32 timeout; - long * const certverifyresult = SSL_IS_PROXY() ? - &data->set.proxy_ssl.certverifyresult : &data->set.ssl.certverifyresult; - const char * const pinnedpubkey = SSL_IS_PROXY() ? - data->set.str[STRING_SSL_PINNEDPUBLICKEY_PROXY] : - data->set.str[STRING_SSL_PINNEDPUBLICKEY_ORIG]; - - - /* check timeout situation */ - const long time_left = Curl_timeleft(data, NULL, TRUE); - if(time_left < 0L) { - failf(data, "timed out before SSL handshake"); - result = CURLE_OPERATION_TIMEDOUT; - goto error; - } - - /* Force the handshake now */ - timeout = PR_MillisecondsToInterval((PRUint32) time_left); - if(SSL_ForceHandshakeWithTimeout(connssl->handle, timeout) != SECSuccess) { - if(PR_GetError() == PR_WOULD_BLOCK_ERROR) - /* blocking direction is updated by nss_update_connecting_state() */ - return CURLE_AGAIN; - else if(*certverifyresult == SSL_ERROR_BAD_CERT_DOMAIN) - result = CURLE_PEER_FAILED_VERIFICATION; - else if(*certverifyresult != 0) - result = CURLE_SSL_CACERT; - goto error; - } - - result = display_conn_info(conn, connssl->handle); - if(result) - goto error; - - if(SSL_SET_OPTION(issuercert)) { - SECStatus ret = SECFailure; - char *nickname = dup_nickname(data, SSL_SET_OPTION(issuercert)); - if(nickname) { - /* we support only nicknames in case of issuercert for now */ - ret = check_issuer_cert(connssl->handle, nickname); - free(nickname); - } - - if(SECFailure == ret) { - infof(data, "SSL certificate issuer check failed\n"); - result = CURLE_SSL_ISSUER_ERROR; - goto error; - } - else { - infof(data, "SSL certificate issuer check ok\n"); - } - } - - result = cmp_peer_pubkey(connssl, pinnedpubkey); - if(result) - /* status already printed */ - goto error; - - return CURLE_OK; - -error: - return nss_fail_connect(connssl, data, result); -} - -static CURLcode nss_connect_common(struct connectdata *conn, int sockindex, - bool *done) -{ - struct ssl_connect_data *connssl = &conn->ssl[sockindex]; - struct Curl_easy *data = conn->data; - const bool blocking = (done == NULL); - CURLcode result; - - if(connssl->state == ssl_connection_complete) { - if(!blocking) - *done = TRUE; - return CURLE_OK; - } - - if(connssl->connecting_state == ssl_connect_1) { - result = nss_setup_connect(conn, sockindex); - if(result) - /* we do not expect CURLE_AGAIN from nss_setup_connect() */ - return result; - - connssl->connecting_state = ssl_connect_2; - } - - /* enable/disable blocking mode before handshake */ - result = nss_set_blocking(connssl, data, blocking); - if(result) - return result; - - result = nss_do_connect(conn, sockindex); - switch(result) { - case CURLE_OK: - break; - case CURLE_AGAIN: - if(!blocking) - /* CURLE_AGAIN in non-blocking mode is not an error */ - return CURLE_OK; - /* fall through */ - default: - return result; - } - - if(blocking) { - /* in blocking mode, set NSS non-blocking mode _after_ SSL handshake */ - result = nss_set_blocking(connssl, data, /* blocking */ FALSE); - if(result) - return result; - } - else - /* signal completed SSL handshake */ - *done = TRUE; - - connssl->state = ssl_connection_complete; - conn->recv[sockindex] = nss_recv; - conn->send[sockindex] = nss_send; - - /* ssl_connect_done is never used outside, go back to the initial state */ - connssl->connecting_state = ssl_connect_1; - - return CURLE_OK; -} - -CURLcode Curl_nss_connect(struct connectdata *conn, int sockindex) -{ - return nss_connect_common(conn, sockindex, /* blocking */ NULL); -} - -CURLcode Curl_nss_connect_nonblocking(struct connectdata *conn, - int sockindex, bool *done) -{ - return nss_connect_common(conn, sockindex, done); -} - -static ssize_t nss_send(struct connectdata *conn, /* connection data */ - int sockindex, /* socketindex */ - const void *mem, /* send this data */ - size_t len, /* amount to write */ - CURLcode *curlcode) -{ - ssize_t rc = PR_Send(conn->ssl[sockindex].handle, mem, (int)len, 0, - PR_INTERVAL_NO_WAIT); - if(rc < 0) { - PRInt32 err = PR_GetError(); - if(err == PR_WOULD_BLOCK_ERROR) - *curlcode = CURLE_AGAIN; - else { - /* print the error number and error string */ - const char *err_name = nss_error_to_name(err); - infof(conn->data, "SSL write: error %d (%s)\n", err, err_name); - - /* print a human-readable message describing the error if available */ - nss_print_error_message(conn->data, err); - - *curlcode = (is_cc_error(err)) - ? CURLE_SSL_CERTPROBLEM - : CURLE_SEND_ERROR; - } - - return -1; - } - - return rc; /* number of bytes */ -} - -static ssize_t nss_recv(struct connectdata * conn, /* connection data */ - int num, /* socketindex */ - char *buf, /* store read data here */ - size_t buffersize, /* max amount to read */ - CURLcode *curlcode) -{ - ssize_t nread = PR_Recv(conn->ssl[num].handle, buf, (int)buffersize, 0, - PR_INTERVAL_NO_WAIT); - if(nread < 0) { - /* failed SSL read */ - PRInt32 err = PR_GetError(); - - if(err == PR_WOULD_BLOCK_ERROR) - *curlcode = CURLE_AGAIN; - else { - /* print the error number and error string */ - const char *err_name = nss_error_to_name(err); - infof(conn->data, "SSL read: errno %d (%s)\n", err, err_name); - - /* print a human-readable message describing the error if available */ - nss_print_error_message(conn->data, err); - - *curlcode = (is_cc_error(err)) - ? CURLE_SSL_CERTPROBLEM - : CURLE_RECV_ERROR; - } - - return -1; - } - - return nread; -} - -size_t Curl_nss_version(char *buffer, size_t size) -{ - return snprintf(buffer, size, "NSS/%s", NSS_VERSION); -} - -/* data might be NULL */ -int Curl_nss_seed(struct Curl_easy *data) -{ - /* make sure that NSS is initialized */ - return !!Curl_nss_force_init(data); -} - -/* data might be NULL */ -CURLcode Curl_nss_random(struct Curl_easy *data, - unsigned char *entropy, - size_t length) -{ - Curl_nss_seed(data); /* Initiate the seed if not already done */ - - if(SECSuccess != PK11_GenerateRandom(entropy, curlx_uztosi(length))) - /* signal a failure */ - return CURLE_FAILED_INIT; - - return CURLE_OK; -} - -void Curl_nss_md5sum(unsigned char *tmp, /* input */ - size_t tmplen, - unsigned char *md5sum, /* output */ - size_t md5len) -{ - PK11Context *MD5pw = PK11_CreateDigestContext(SEC_OID_MD5); - unsigned int MD5out; - - PK11_DigestOp(MD5pw, tmp, curlx_uztoui(tmplen)); - PK11_DigestFinal(MD5pw, md5sum, &MD5out, curlx_uztoui(md5len)); - PK11_DestroyContext(MD5pw, PR_TRUE); -} - -void Curl_nss_sha256sum(const unsigned char *tmp, /* input */ - size_t tmplen, - unsigned char *sha256sum, /* output */ - size_t sha256len) -{ - PK11Context *SHA256pw = PK11_CreateDigestContext(SEC_OID_SHA256); - unsigned int SHA256out; - - PK11_DigestOp(SHA256pw, tmp, curlx_uztoui(tmplen)); - PK11_DigestFinal(SHA256pw, sha256sum, &SHA256out, curlx_uztoui(sha256len)); - PK11_DestroyContext(SHA256pw, PR_TRUE); -} - -bool Curl_nss_cert_status_request(void) -{ -#ifdef SSL_ENABLE_OCSP_STAPLING - return TRUE; -#else - return FALSE; -#endif -} - -bool Curl_nss_false_start(void) -{ -#if NSSVERNUM >= 0x030f04 /* 3.15.4 */ - return TRUE; -#else - return FALSE; -#endif -} - -#endif /* USE_NSS */ diff --git a/contrib/curl/lib/vtls/nssg.h b/contrib/curl/lib/vtls/nssg.h deleted file mode 100644 index 8c46929ff2..0000000000 --- a/contrib/curl/lib/vtls/nssg.h +++ /dev/null @@ -1,108 +0,0 @@ -#ifndef HEADER_CURL_NSSG_H -#define HEADER_CURL_NSSG_H -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) 1998 - 2017, Daniel Stenberg, , et al. - * - * This software is licensed as described in the file COPYING, which - * you should have received as part of this distribution. The terms - * are also available at https://curl.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. - * - ***************************************************************************/ -#include "curl_setup.h" - -#ifdef USE_NSS -/* - * This header should only be needed to get included by vtls.c and nss.c - */ - -#include "urldata.h" - -CURLcode Curl_nss_connect(struct connectdata *conn, int sockindex); -CURLcode Curl_nss_connect_nonblocking(struct connectdata *conn, - int sockindex, - bool *done); -/* close a SSL connection */ -void Curl_nss_close(struct connectdata *conn, int sockindex); - -int Curl_nss_init(void); -void Curl_nss_cleanup(void); - -size_t Curl_nss_version(char *buffer, size_t size); -int Curl_nss_check_cxn(struct connectdata *cxn); -int Curl_nss_seed(struct Curl_easy *data); - -/* initialize NSS library if not already */ -CURLcode Curl_nss_force_init(struct Curl_easy *data); - -CURLcode Curl_nss_random(struct Curl_easy *data, - unsigned char *entropy, - size_t length); - -void Curl_nss_md5sum(unsigned char *tmp, /* input */ - size_t tmplen, - unsigned char *md5sum, /* output */ - size_t md5len); - -void Curl_nss_sha256sum(const unsigned char *tmp, /* input */ - size_t tmplen, - unsigned char *sha256sum, /* output */ - size_t sha256len); - -bool Curl_nss_cert_status_request(void); - -bool Curl_nss_false_start(void); - -/* Support HTTPS-proxy */ -#define HTTPS_PROXY_SUPPORT 1 - -/* Set the API backend definition to NSS */ -#define CURL_SSL_BACKEND CURLSSLBACKEND_NSS - -/* this backend supports the CAPATH option */ -#define have_curlssl_ca_path 1 - -/* this backend supports CURLOPT_CERTINFO */ -#define have_curlssl_certinfo 1 - -/* this backends supports CURLOPT_PINNEDPUBLICKEY */ -#define have_curlssl_pinnedpubkey 1 - -/* API setup for NSS */ -#define curlssl_init Curl_nss_init -#define curlssl_cleanup Curl_nss_cleanup -#define curlssl_connect Curl_nss_connect -#define curlssl_connect_nonblocking Curl_nss_connect_nonblocking - -/* NSS has its own session ID cache */ -#define curlssl_session_free(x) Curl_nop_stmt -#define curlssl_close_all(x) ((void)x) -#define curlssl_close Curl_nss_close -/* NSS has no shutdown function provided and thus always fail */ -#define curlssl_shutdown(x,y) ((void)x, (void)y, 1) -#define curlssl_set_engine(x,y) ((void)x, (void)y, CURLE_NOT_BUILT_IN) -#define curlssl_set_engine_default(x) ((void)x, CURLE_NOT_BUILT_IN) -#define curlssl_engines_list(x) ((void)x, (struct curl_slist *)NULL) -#define curlssl_version Curl_nss_version -#define curlssl_check_cxn(x) Curl_nss_check_cxn(x) -#define curlssl_data_pending(x,y) ((void)x, (void)y, 0) -#define curlssl_random(x,y,z) Curl_nss_random(x,y,z) -#define curlssl_md5sum(a,b,c,d) Curl_nss_md5sum(a,b,c,d) -#define curlssl_sha256sum(a,b,c,d) Curl_nss_sha256sum(a,b,c,d) -#define curlssl_cert_status_request() Curl_nss_cert_status_request() -#define curlssl_false_start() Curl_nss_false_start() - -#endif /* USE_NSS */ -#endif /* HEADER_CURL_NSSG_H */ diff --git a/contrib/curl/lib/vtls/openssl.c b/contrib/curl/lib/vtls/openssl.c index eb625fe93d..4d5950a696 100644 --- a/contrib/curl/lib/vtls/openssl.c +++ b/contrib/curl/lib/vtls/openssl.c @@ -5,11 +5,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2017, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -18,6 +18,8 @@ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY * KIND, either express or implied. * + * SPDX-License-Identifier: curl + * ***************************************************************************/ /* @@ -25,17 +27,22 @@ * but vtls.c should ever call or use these functions. */ -/* - * The original SSLeay-using code for curl was written by Linas Vepstas and - * Sampo Kellomaki 1998. - */ - #include "curl_setup.h" -#ifdef USE_OPENSSL +#if defined(USE_QUICHE) || defined(USE_OPENSSL) -#ifdef HAVE_LIMITS_H #include + +/* Wincrypt must be included before anything that could include OpenSSL. */ +#if defined(USE_WIN32_CRYPTO) +#include +/* 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" @@ -48,41 +55,76 @@ #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 #include #include +#ifndef OPENSSL_NO_DSA #include +#endif #include #include #include #include #include #include - -#ifdef HAVE_OPENSSL_PKCS12_H +#include +#include #include -#endif +#include +#include + +#ifdef USE_ECH +# ifndef OPENSSL_IS_BORINGSSL +# include +# endif +# include "curl_base64.h" +# define ECH_ENABLED(__data__) \ + (__data__->set.tls_ech && \ + !(__data__->set.tls_ech & CURLECH_DISABLE)\ + ) +#endif /* USE_ECH */ #if (OPENSSL_VERSION_NUMBER >= 0x0090808fL) && !defined(OPENSSL_NO_OCSP) #include #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 +#endif + #include "warnless.h" -#include "non-ascii.h" /* for Curl_convert_from_utf8 prototype */ /* 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 -#if defined(HAVE_OPENSSL_ENGINE_H) +#ifdef USE_OPENSSL_ENGINE #include #endif @@ -96,43 +138,44 @@ #define HAVE_ERR_REMOVE_THREAD_STATE 1 #endif -#if !defined(HAVE_SSLV2_CLIENT_METHOD) || \ - OPENSSL_VERSION_NUMBER >= 0x10100000L /* 1.1.0+ has no SSLv2 */ -#undef OPENSSL_NO_SSL2 /* undef first to avoid compiler warnings */ -#define OPENSSL_NO_SSL2 -#endif - #if (OPENSSL_VERSION_NUMBER >= 0x10100000L) && /* OpenSSL 1.1.0+ */ \ - !defined(LIBRESSL_VERSION_NUMBER) + !(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 CONST_ASN1_BIT_STRING 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 */ -#define CONST_ASN1_BIT_STRING /* nope */ -#ifdef LIBRESSL_VERSION_NUMBER -static unsigned long OpenSSL_version_num(void) -{ - return LIBRESSL_VERSION_NUMBER; -} -#else +#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) + !(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) @@ -144,1683 +187,3412 @@ static unsigned long OpenSSL_version_num(void) #define OPENSSL_load_builtin_modules(x) #endif -#if defined(LIBRESSL_VERSION_NUMBER) -#define OSSL_PACKAGE "LibreSSL" -#elif defined(OPENSSL_IS_BORINGSSL) -#define OSSL_PACKAGE "BoringSSL" +#if (OPENSSL_VERSION_NUMBER >= 0x30000000L) +#define HAVE_EVP_PKEY_GET_PARAMS 1 +#endif + +#ifdef HAVE_EVP_PKEY_GET_PARAMS +#include +#define DECLARE_PKEY_PARAM_BIGNUM(name) BIGNUM *name = NULL +#define FREE_PKEY_PARAM_BIGNUM(name) BN_clear_free(name) #else -#define OSSL_PACKAGE "OpenSSL" +#define DECLARE_PKEY_PARAM_BIGNUM(name) const BIGNUM *name +#define FREE_PKEY_PARAM_BIGNUM(name) #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 - * entopy to properly seed OpenSSL's PRNG. + * 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: not supported. 3.5.0+ has a stub function that does nothing. */ -#define RAND_LOAD_LENGTH 1024 - -static int passwd_callback(char *buf, int num, int encrypting, - void *global_passwd) -{ - DEBUGASSERT(0 == encrypting); +#if (OPENSSL_VERSION_NUMBER >= 0x10101000L && \ + !defined(LIBRESSL_VERSION_NUMBER)) || \ + defined(OPENSSL_IS_BORINGSSL) +#define HAVE_KEYLOG_CALLBACK +#endif - if(!encrypting) { - int klen = curlx_uztosi(strlen((char *)global_passwd)); - if(num > klen) { - memcpy(buf, global_passwd, klen+1); - return klen; - } - } - return 0; -} +/* 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 /* - * rand_enough() returns TRUE if we have seeded the random engine properly. + * Whether SSL_CTX_set1_curves_list is available. + * OpenSSL: supported since 1.0.2, see + * https://docs.openssl.org/master/man3/SSL_CTX_set1_curves/ + * BoringSSL: supported since 5fd1807d95f7 (committed 2016-09-30) + * LibreSSL: since 2.5.3 (April 12, 2017) */ -static bool rand_enough(void) -{ - return (0 != RAND_status()) ? TRUE : FALSE; -} - -static CURLcode Curl_ossl_seed(struct Curl_easy *data) -{ - /* we have the "SSL is seeded" boolean static to prevent multiple - time-consuming seedings in vain */ - static bool ssl_seeded = FALSE; - char *buf = data->state.buffer; /* point to the big buffer */ - int nread=0; - - if(ssl_seeded) - return CURLE_OK; - - if(rand_enough()) { - /* OpenSSL 1.1.0+ will return here */ - ssl_seeded = TRUE; - return CURLE_OK; - } +#if (OPENSSL_VERSION_NUMBER >= 0x10002000L) || \ + defined(OPENSSL_IS_BORINGSSL) +#define HAVE_SSL_CTX_SET_EC_CURVES +#endif -#ifndef RANDOM_FILE - /* if RANDOM_FILE isn't defined, we only perform this if an option tells - us to! */ - if(data->set.str[STRING_SSL_RANDOM_FILE]) -#define RANDOM_FILE "" /* doesn't matter won't be used */ +#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 - { - /* let the option override the define */ - nread += RAND_load_file((data->set.str[STRING_SSL_RANDOM_FILE]? - data->set.str[STRING_SSL_RANDOM_FILE]: - RANDOM_FILE), - RAND_LOAD_LENGTH); - if(rand_enough()) - return nread; - } - -#if defined(HAVE_RAND_EGD) - /* only available in OpenSSL 0.9.5 and later */ - /* EGD_SOCKET is set at configure time or not at all */ -#ifndef EGD_SOCKET - /* If we don't have the define set, we only do this if the egd-option - is set */ - if(data->set.str[STRING_SSL_EGDSOCKET]) -#define EGD_SOCKET "" /* doesn't matter won't be used */ #endif - { - /* If there's an option and a define, the option overrides the - define */ - int ret = RAND_egd(data->set.str[STRING_SSL_EGDSOCKET]? - data->set.str[STRING_SSL_EGDSOCKET]:EGD_SOCKET); - if(-1 != ret) { - nread += ret; - if(rand_enough()) - return nread; - } - } + +#if defined(OPENSSL_IS_BORINGSSL) || defined(OPENSSL_IS_AWSLC) +typedef size_t numcert_t; +#else +typedef int numcert_t; #endif +#define ossl_valsize_t numcert_t - /* If we get here, it means we need to seed the PRNG using a "silly" - approach! */ - do { - unsigned char randb[64]; - int len = sizeof(randb); - if(!RAND_bytes(randb, len)) - break; - RAND_add(randb, len, (len >> 1)); - } while(!rand_enough()); +#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 +/* not the case with old versions of OpenSSL */ +#define DEFAULT_CIPHER_SELECTION \ + "ALL:!EXPORT:!EXPORT40:!EXPORT56:!aNULL:!LOW:!RC4:@STRENGTH" +#endif - /* generates a default path for the random seed file */ - buf[0]=0; /* blank it first */ - RAND_file_name(buf, BUFSIZE); - if(buf[0]) { - /* we got a file name to try */ - nread += RAND_load_file(buf, RAND_LOAD_LENGTH); - if(rand_enough()) - return nread; - } +#ifdef HAVE_OPENSSL_SRP +/* the function exists */ +#ifdef USE_TLS_SRP +/* the functionality is not disabled */ +#define USE_OPENSSL_SRP +#endif +#endif - infof(data, "libcurl is now using a weak random seed!\n"); - return CURLE_SSL_CONNECT_ERROR; /* confusing error code */ -} +#if (OPENSSL_VERSION_NUMBER >= 0x10100000L) +#define HAVE_RANDOM_INIT_BY_DEFAULT 1 +#endif -#ifndef SSL_FILETYPE_ENGINE -#define SSL_FILETYPE_ENGINE 42 +#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 -#ifndef SSL_FILETYPE_PKCS12 -#define SSL_FILETYPE_PKCS12 43 + +#if defined(OPENSSL_IS_BORINGSSL) || defined(OPENSSL_IS_AWSLC) +typedef uint32_t sslerr_t; +#else +typedef unsigned long sslerr_t; #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; -} -#if defined(HAVE_OPENSSL_ENGINE_H) /* - * 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. + * 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. */ -static int ssl_ui_reader(UI *ui, UI_STRING *uis) +#if (OPENSSL_VERSION_NUMBER >= 0x10100000L) /* OpenSSL >= 1.1.0 */ +#define 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 */ + +static CURLcode ossl_certchain(struct Curl_easy *data, SSL *ssl); + +static CURLcode push_certinfo(struct Curl_easy *data, + BIO *mem, const char *label, int num) + WARN_UNUSED_RESULT; + +static CURLcode push_certinfo(struct Curl_easy *data, + BIO *mem, const char *label, int num) { - 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); + char *ptr; + long len = BIO_get_mem_data(mem, &ptr); + CURLcode result = Curl_ssl_push_certinfo_len(data, num, label, ptr, len); + (void)BIO_reset(mem); + return result; } -/* - * Suppress interactive request for a default password if available. - */ -static int ssl_ui_writer(UI *ui, UI_STRING *uis) +static CURLcode pubkey_show(struct Curl_easy *data, + BIO *mem, + int num, + const char *type, + const char *name, + const BIGNUM *bn) { - 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); + char namebuf[32]; + + msnprintf(namebuf, sizeof(namebuf), "%s(%s)", type, name); + + if(bn) + BN_print(mem, bn); + return push_certinfo(data, mem, 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 cert_stuff(struct connectdata *conn, - SSL_CTX* ctx, - char *cert_file, - const char *cert_type, - char *key_file, - const char *key_type, - char *key_passwd) +static int asn1_object_dump(ASN1_OBJECT *a, char *buf, size_t len) { - struct Curl_easy *data = conn->data; - - int file_type = do_file_type(cert_type); + int i, ilen; - if(cert_file || (file_type == SSL_FILETYPE_ENGINE)) { - SSL *ssl; - X509 *x509; - int cert_done = 0; + ilen = (int)len; + if(ilen < 0) + return 1; /* buffer too big */ - 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); - } + i = i2t_ASN1_OBJECT(buf, ilen, a); + if(i >= ilen) + return 1; /* buffer too small */ - switch(file_type) { - case SSL_FILETYPE_PEM: - /* SSL_CTX_use_certificate_chain_file() only works on PEM files */ - if(SSL_CTX_use_certificate_chain_file(ctx, - cert_file) != 1) { - failf(data, - "could not load PEM client certificate, " OSSL_PACKAGE - " error %s, " - "(no key found, wrong pass phrase, or wrong file format?)", - ERR_error_string(ERR_get_error(), NULL) ); - return 0; - } - break; + return 0; +} - 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. */ - if(SSL_CTX_use_certificate_file(ctx, - cert_file, - file_type) != 1) { - failf(data, - "could not load ASN1 client certificate, " OSSL_PACKAGE - " error %s, " - "(no key found, wrong pass phrase, or wrong file format?)", - ERR_error_string(ERR_get_error(), NULL) ); - return 0; - } - break; - case SSL_FILETYPE_ENGINE: -#if defined(HAVE_OPENSSL_ENGINE_H) && defined(ENGINE_CTRL_GET_CMD_FROM_NAME) - { - 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; - } +static CURLcode X509V3_ext(struct Curl_easy *data, + int certnum, + CONST_EXTS STACK_OF(X509_EXTENSION) *exts) +{ + int i; + CURLcode result = CURLE_OK; - /* 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, - ERR_error_string(ERR_get_error(), NULL)); - return 0; - } + if((int)sk_X509_EXTENSION_num(exts) <= 0) + /* no extensions, bail out */ + return result; - if(!params.cert) { - failf(data, "ssl engine didn't initialized the certificate " - "properly."); - return 0; - } + for(i = 0; i < (int)sk_X509_EXTENSION_num(exts); i++) { + ASN1_OBJECT *obj; + X509_EXTENSION *ext = sk_X509_EXTENSION_value(exts, (ossl_valsize_t)i); + BUF_MEM *biomem; + char namebuf[128]; + BIO *bio_out = BIO_new(BIO_s_mem()); - if(SSL_CTX_use_certificate(ctx, params.cert) != 1) { - failf(data, "unable to set client certificate"); - X509_free(params.cert); - 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 + if(!bio_out) + return result; - case SSL_FILETYPE_PKCS12: - { -#ifdef HAVE_OPENSSL_PKCS12_H - FILE *f; - PKCS12 *p12; - EVP_PKEY *pri; - STACK_OF(X509) *ca = NULL; + obj = X509_EXTENSION_get_object(ext); - f = fopen(cert_file, "rb"); - if(!f) { - failf(data, "could not open PKCS12 file '%s'", cert_file); - return 0; - } - p12 = d2i_PKCS12_fp(f, NULL); - fclose(f); + asn1_object_dump(obj, namebuf, sizeof(namebuf)); - if(!p12) { - failf(data, "error reading PKCS12 file '%s'", cert_file); - return 0; - } + if(!X509V3_EXT_print(bio_out, ext, 0, 0)) + ASN1_STRING_print(bio_out, (ASN1_STRING *)X509_EXTENSION_get_data(ext)); - PKCS12_PBE_add(); + BIO_get_mem_ptr(bio_out, &biomem); + result = Curl_ssl_push_certinfo_len(data, certnum, namebuf, biomem->data, + biomem->length); + BIO_free(bio_out); + if(result) + break; + } + return result; +} - if(!PKCS12_parse(p12, key_passwd, &pri, &x509, - &ca)) { - failf(data, - "could not parse PKCS12 file, check password, " OSSL_PACKAGE - " error %s", - ERR_error_string(ERR_get_error(), NULL) ); - PKCS12_free(p12); - return 0; - } +static CURLcode ossl_certchain(struct Curl_easy *data, SSL *ssl) +{ + CURLcode result; + STACK_OF(X509) *sk; + int i; + numcert_t numcerts; + BIO *mem; - PKCS12_free(p12); + DEBUGASSERT(ssl); - if(SSL_CTX_use_certificate(ctx, x509) != 1) { - failf(data, - "could not load PKCS12 client certificate, " OSSL_PACKAGE - " error %s", - ERR_error_string(ERR_get_error(), NULL) ); - goto fail; - } + sk = SSL_get_peer_cert_chain(ssl); + if(!sk) { + return CURLE_OUT_OF_MEMORY; + } - if(SSL_CTX_use_PrivateKey(ctx, pri) != 1) { - failf(data, "unable to use private key from PKCS12 file '%s'", - cert_file); - goto fail; - } + numcerts = sk_X509_num(sk); - 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; + result = Curl_ssl_init_certinfo(data, (int)numcerts); + if(result) + return result; + + mem = BIO_new(BIO_s_mem()); + if(!mem) + result = CURLE_OUT_OF_MEMORY; + + for(i = 0; !result && (i < (int)numcerts); i++) { + ASN1_INTEGER *num; + X509 *x = sk_X509_value(sk, (ossl_valsize_t)i); + EVP_PKEY *pubkey = NULL; + int j; + const ASN1_BIT_STRING *psig = NULL; + + X509_NAME_print_ex(mem, X509_get_subject_name(x), 0, XN_FLAG_ONELINE); + result = push_certinfo(data, mem, "Subject", i); + if(result) + break; + + X509_NAME_print_ex(mem, X509_get_issuer_name(x), 0, XN_FLAG_ONELINE); + result = push_certinfo(data, mem, "Issuer", i); + if(result) + break; + + BIO_printf(mem, "%lx", X509_get_version(x)); + result = push_certinfo(data, mem, "Version", i); + if(result) + break; + + 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]); + result = push_certinfo(data, mem, "Serial Number", i); + if(result) + break; + +#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); + result = push_certinfo(data, mem, "Signature Algorithm", i); + if(result) + break; } - /* 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; - } + + xpubkey = X509_get_X509_PUBKEY(x); + if(xpubkey) { + X509_PUBKEY_get0_param(&pubkeyoid, NULL, NULL, NULL, xpubkey); + if(pubkeyoid) { + i2a_ASN1_OBJECT(mem, pubkeyoid); + result = push_certinfo(data, mem, "Public Key Algorithm", i); + if(result) + break; } } - cert_done = 1; - fail: - EVP_PKEY_free(pri); - X509_free(x509); - sk_X509_pop_free(ca, X509_free); + result = X509V3_ext(data, i, X509_get0_extensions(x)); + if(result) + break; + } +#else + { + /* before OpenSSL 1.0.2 */ + X509_CINF *cinf = x->cert_info; - if(!cert_done) - return 0; /* failure! */ + i2a_ASN1_OBJECT(mem, cinf->signature->algorithm); + result = push_certinfo(data, mem, "Signature Algorithm", i); + + if(!result) { + i2a_ASN1_OBJECT(mem, cinf->key->algor->algorithm); + result = push_certinfo(data, mem, "Public Key Algorithm", i); + } + + if(!result) + result = X509V3_ext(data, i, cinf->extensions); + + if(result) + break; + + psig = x->signature; + } +#endif + + ASN1_TIME_print(mem, X509_get0_notBefore(x)); + result = push_certinfo(data, mem, "Start date", i); + if(result) + break; + + ASN1_TIME_print(mem, X509_get0_notAfter(x)); + result = push_certinfo(data, mem, "Expire date", i); + if(result) break; + + 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 - failf(data, "file type P12 for certificate not supported"); - return 0; + pktype = pubkey->type; #endif - } - default: - failf(data, "not supported file type '%s' for certificate", cert_type); - return 0; - } + 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 */ - file_type = do_file_type(key_type); + { +#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 */ + result = push_certinfo(data, mem, "RSA Public Key", i); + if(result) + break; + print_pubkey_BN(rsa, n, i); + print_pubkey_BN(rsa, e, i); + FREE_PKEY_PARAM_BIGNUM(n); + FREE_PKEY_PARAM_BIGNUM(e); + } - switch(file_type) { - case SSL_FILETYPE_PEM: - if(cert_done) break; - if(!key_file) - /* cert & key can only be in PEM case in the same file */ - key_file=cert_file; - case SSL_FILETYPE_ASN1: - if(SSL_CTX_use_PrivateKey_file(ctx, key_file, file_type) != 1) { - failf(data, "unable to set private key file: '%s' type %s", - key_file, key_type?key_type:"PEM"); - return 0; } - break; - case SSL_FILETYPE_ENGINE: -#ifdef HAVE_OPENSSL_ENGINE_H - { /* XXXX still needs some work */ - EVP_PKEY *priv_key = NULL; - 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); - /* the typecast below was added to please mingw32 */ - priv_key = (EVP_PKEY *) - 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; + 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; } - 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 - 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; + 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; } - 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; + } + EVP_PKEY_free(pubkey); } - 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(!result && psig) { + for(j = 0; j < psig->length; j++) + BIO_printf(mem, "%02x:", psig->data[j]); + result = push_certinfo(data, mem, "Signature", i); } - SSL_free(ssl); - - /* If we are using DSA, we can copy the parameters from - * the private key */ - - - /* 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; + if(!result) { + PEM_write_bio_X509(mem, x); + result = push_certinfo(data, mem, "Cert", i); } } - return 1; -} -/* returns non-zero on failure */ -static int x509_name_oneline(X509_NAME *a, char *buf, size_t size) -{ -#if 0 - return X509_NAME_oneline(a, buf, size); -#else - BIO *bio_out = BIO_new(BIO_s_mem()); - BUF_MEM *biomem; - int rc; + BIO_free(mem); - if(!bio_out) - return 1; /* alloc failed! */ + if(result) + /* cleanup all leftovers */ + Curl_ssl_free_certinfo(data); - rc = X509_NAME_print_ex(bio_out, a, 0, XN_FLAG_SEP_SPLUS_SPC); - BIO_get_mem_ptr(bio_out, &biomem); + return result; +} - if((size_t)biomem->length < size) - size = biomem->length; - else - size--; /* don't overwrite the buffer end */ +#endif /* quiche or OpenSSL */ - memcpy(buf, biomem->data, size); - buf[size]=0; +#ifdef USE_OPENSSL - BIO_free(bio_out); +#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 */ - return !rc; +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; } -/* Return error string for last OpenSSL error - */ -static char *ossl_strerror(unsigned long error, char *buf, size_t size) +static int ossl_bio_cf_destroy(BIO *bio) { - /* OpenSSL 0.9.6 and later has a function named - ERR_error_string_n() that takes the size of the buffer as a - third argument */ - ERR_error_string_n(error, buf, size); - return buf; + if(!bio) + return 0; + return 1; } -/** - * Global SSL init - * - * @retval 0 error initializing SSL - * @retval 1 SSL initialized successfully - */ -int Curl_ossl_init(void) +static long ossl_bio_cf_ctrl(BIO *bio, int cmd, long num, void *ptr) { - OPENSSL_load_builtin_modules(); - -#ifdef HAVE_ENGINE_LOAD_BUILTIN_ENGINES - ENGINE_load_builtin_engines(); + 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; +} - /* OPENSSL_config(NULL); is "strongly recommended" to use but unfortunately - that function makes an exit() call on wrongly formatted config files - which makes it hard to use in some situations. OPENSSL_config() itself - calls CONF_modules_load_file() and we use that instead and we ignore - its return code! */ - - /* CONF_MFLAGS_DEFAULT_SECTION introduced some time between 0.9.8b and - 0.9.8e */ -#ifndef CONF_MFLAGS_DEFAULT_SECTION -#define CONF_MFLAGS_DEFAULT_SECTION 0x0 -#endif +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_ctx *octx = (struct ossl_ctx *)connssl->backend; + struct Curl_easy *data = CF_DATA_CURRENT(cf); + ssize_t nwritten; + CURLcode result = CURLE_SEND_ERROR; + + DEBUGASSERT(data); + if(blen < 0) + return 0; - CONF_modules_load_file(NULL, NULL, - CONF_MFLAGS_DEFAULT_SECTION| - CONF_MFLAGS_IGNORE_MISSING_FILE); + nwritten = Curl_conn_cf_send(cf->next, data, buf, (size_t)blen, FALSE, + &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); + octx->io_result = result; + if(nwritten < 0) { + if(CURLE_AGAIN == result) + BIO_set_retry_write(bio); + } + return (int)nwritten; +} -#if (OPENSSL_VERSION_NUMBER >= 0x10100000L) && \ - !defined(LIBRESSL_VERSION_NUMBER) - /* OpenSSL 1.1.0+ takes care of initialization itself */ -#else - /* Lets get nice error messages */ - SSL_load_error_strings(); +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_ctx *octx = (struct ossl_ctx *)connssl->backend; + struct Curl_easy *data = CF_DATA_CURRENT(cf); + ssize_t nread; + CURLcode result = CURLE_RECV_ERROR; - /* Init the global ciphers and digests */ - if(!SSLeay_add_ssl_algorithms()) + DEBUGASSERT(data); + /* OpenSSL catches this case, so should we. */ + if(!buf) + return 0; + if(blen < 0) return 0; - OpenSSL_add_all_algorithms(); -#endif + nread = Curl_conn_cf_recv(cf->next, data, buf, (size_t)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); + octx->io_result = result; + if(nread < 0) { + if(CURLE_AGAIN == result) + BIO_set_retry_read(bio); + } + else if(nread == 0) { + connssl->peer_closed = TRUE; + } - return 1; + /* Before returning server replies to the SSL instance, we need + * to have setup the x509 store or verification will fail. */ + if(!octx->x509_store_setup) { + result = Curl_ssl_setup_x509_store(cf, data, octx->ssl_ctx); + if(result) { + octx->io_result = result; + return -1; + } + octx->x509_store_setup = TRUE; + } + + return (int)nread; } -/* Global cleanup */ -void Curl_ossl_cleanup(void) +#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) { -#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 */ + return &ossl_bio_cf_meth_1_0; +} + +#define ossl_bio_cf_method_free(m) Curl_nop_stmt + #else - /* Free ciphers and digests lists */ - EVP_cleanup(); -#ifdef HAVE_ENGINE_CLEANUP - /* Free engine list */ - ENGINE_cleanup(); +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 - /* 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); +/* + * 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 - ERR_remove_state(0); -#endif +/* + * ossl_log_tls12_secret is called by libcurl to make the CLIENT_RANDOMs if the + * OpenSSL being used does not 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; - /* Free all memory allocated by all configuration modules */ - CONF_modules_free(); + if(!session || *keylog_done) + return; -#ifdef HAVE_SSL_COMP_FREE_COMPRESSION_METHODS - SSL_COMP_free_compression_methods(); -#endif +#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 */ -/* - * This function is used 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 - */ -int Curl_ossl_check_cxn(struct connectdata *conn) +static const char *SSL_ERROR_to_str(int err) { - /* SSL_peek takes data out of the raw recv buffer without peeking so we use - recv MSG_PEEK instead. Bug #795 */ -#ifdef MSG_PEEK - char buf; - ssize_t nread; - nread = recv((RECV_TYPE_ARG1)conn->sock[FIRSTSOCKET], (RECV_TYPE_ARG2)&buf, - (RECV_TYPE_ARG3)1, (RECV_TYPE_ARG4)MSG_PEEK); - if(nread == 0) - return 0; /* connection has been closed */ - else if(nread == 1) - return 1; /* connection still in place */ - else if(nread == -1) { - int err = SOCKERRNO; - if(err == EINPROGRESS || -#if defined(EAGAIN) && (EAGAIN != EWOULDBLOCK) - err == EAGAIN || + 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 - err == EWOULDBLOCK) - return 1; /* connection still in place */ - if(err == ECONNRESET || -#ifdef ECONNABORTED - err == ECONNABORTED || +#if defined(SSL_ERROR_WANT_ASYNC_JOB) + case SSL_ERROR_WANT_ASYNC_JOB: + return "SSL_ERROR_WANT_ASYNC_JOB"; #endif -#ifdef ENETDOWN - err == ENETDOWN || +#if defined(SSL_ERROR_WANT_EARLY) + case SSL_ERROR_WANT_EARLY: + return "SSL_ERROR_WANT_EARLY"; #endif -#ifdef ENETRESET - err == ENETRESET || + 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 -#ifdef ESHUTDOWN - err == ESHUTDOWN || + + if(!*buf) { + const char *msg = error ? "Unknown error" : "No error"; + if(strlen(msg) < size) + strcpy(buf, msg); + } + + return buf; +} + +static int passwd_callback(char *buf, int num, int encrypting, + void *global_passwd) +{ + DEBUGASSERT(0 == encrypting); + + if(!encrypting && num >= 0) { + 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 + + /* 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 *= (time_t)i + 1; + tv.tv_usec *= (int)i + 2; + tv.tv_sec ^= ((Curl_now().tv_sec + (time_t)Curl_now().tv_usec) * + (time_t)(i + 3)) << 8; + tv.tv_usec ^= (int) ((Curl_now().tv_sec + (time_t)Curl_now().tv_usec) * + (time_t)(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 filename 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 -#ifdef ETIMEDOUT - err == ETIMEDOUT || +} + +#ifndef SSL_FILETYPE_ENGINE +#define SSL_FILETYPE_ENGINE 42 #endif - err == ENOTCONN) - return 0; /* connection has been closed */ - } +#ifndef SSL_FILETYPE_PKCS12 +#define SSL_FILETYPE_PKCS12 43 #endif - return -1; /* connection status unknown */ +static int ossl_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; } -/* Selects an OpenSSL crypto engine +#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. */ -CURLcode Curl_ossl_set_engine(struct Curl_easy *data, const char *engine) +static int ssl_ui_reader(UI *ui, UI_STRING *uis) { -#if defined(USE_OPENSSL) && defined(HAVE_OPENSSL_ENGINE_H) - ENGINE *e; + 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; + } + FALLTHROUGH(); + default: + break; + } + return (UI_method_get_reader(UI_OpenSSL()))(ui, uis); +} -#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; +/* + * 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; + } + FALLTHROUGH(); + 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 - if(!e) { - failf(data, "SSL Engine '%s' not found", engine); - return CURLE_SSL_ENGINE_NOTFOUND; +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 = ossl_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 did not 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 do not need the handle any more... */ + } + else { + failf(data, "crypto engine not set, cannot 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 would 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 = ossl_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 do not need the handle any more... */ + } + else { + failf(data, "crypto engine not set, cannot 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, do not check the private key if its flags indicate + * it does not 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; +} + +/* returns non-zero on failure */ +static CURLcode x509_name_oneline(X509_NAME *a, struct dynbuf *d) +{ + BIO *bio_out = BIO_new(BIO_s_mem()); + BUF_MEM *biomem; + int rc; + CURLcode result = CURLE_OUT_OF_MEMORY; + + if(bio_out) { + Curl_dyn_reset(d); + rc = X509_NAME_print_ex(bio_out, a, 0, XN_FLAG_SEP_SPLUS_SPC); + if(rc != -1) { + BIO_get_mem_ptr(bio_out, &biomem); + result = Curl_dyn_addn(d, biomem->data, biomem->length); + BIO_free(bio_out); + } + } + return result; +} + +/** + * 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) || LIBRESSL_VERSION_NUMBER >= 0x2070000fL) + /* 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 CURLcode ossl_shutdown(struct Curl_cfilter *cf, + struct Curl_easy *data, + bool send_shutdown, bool *done) +{ + struct ssl_connect_data *connssl = cf->ctx; + struct ossl_ctx *octx = (struct ossl_ctx *)connssl->backend; + CURLcode result = CURLE_OK; + char buf[1024]; + int nread = -1, err; + unsigned long sslerr; + size_t i; + + DEBUGASSERT(octx); + if(!octx->ssl || cf->shutdown) { + *done = TRUE; + goto out; + } + + connssl->io_need = CURL_SSL_IO_NEED_NONE; + *done = FALSE; + if(!(SSL_get_shutdown(octx->ssl) & SSL_SENT_SHUTDOWN)) { + /* We have not started the shutdown from our side yet. Check + * if the server already sent us one. */ + ERR_clear_error(); + for(i = 0; i < 10; ++i) { + nread = SSL_read(octx->ssl, buf, (int)sizeof(buf)); + CURL_TRC_CF(data, cf, "SSL shutdown not sent, read -> %d", nread); + if(nread <= 0) + break; + } + err = SSL_get_error(octx->ssl, nread); + if(!nread && err == SSL_ERROR_ZERO_RETURN) { + bool input_pending; + /* Yes, it did. */ + if(!send_shutdown) { + CURL_TRC_CF(data, cf, "SSL shutdown received, not sending"); + *done = TRUE; + goto out; + } + else if(!cf->next->cft->is_alive(cf->next, data, &input_pending)) { + /* Server closed the connection after its closy notify. It + * seems not interested to see our close notify, so do not + * send it. We are done. */ + connssl->peer_closed = TRUE; + CURL_TRC_CF(data, cf, "peer closed connection"); + *done = TRUE; + goto out; + } + } + } + + /* SSL should now have started the shutdown from our side. Since it + * was not complete, we are lacking the close notify from the server. */ + if(send_shutdown) { + ERR_clear_error(); + if(SSL_shutdown(octx->ssl) == 1) { + CURL_TRC_CF(data, cf, "SSL shutdown finished"); + *done = TRUE; + goto out; + } + if(SSL_ERROR_WANT_WRITE == SSL_get_error(octx->ssl, nread)) { + CURL_TRC_CF(data, cf, "SSL shutdown still wants to send"); + connssl->io_need = CURL_SSL_IO_NEED_SEND; + goto out; + } + /* Having sent the close notify, we use SSL_read() to get the + * missing close notify from the server. */ + } + + for(i = 0; i < 10; ++i) { + ERR_clear_error(); + nread = SSL_read(octx->ssl, buf, (int)sizeof(buf)); + CURL_TRC_CF(data, cf, "SSL shutdown read -> %d", nread); + if(nread <= 0) + break; + } + err = SSL_get_error(octx->ssl, nread); + switch(err) { + case SSL_ERROR_ZERO_RETURN: /* no more data */ + CURL_TRC_CF(data, cf, "SSL shutdown not received, but closed"); + *done = TRUE; + break; + case SSL_ERROR_NONE: /* just did not get anything */ + case SSL_ERROR_WANT_READ: + /* SSL has send its notify and now wants to read the reply + * from the server. We are not really interested in that. */ + CURL_TRC_CF(data, cf, "SSL shutdown sent, want receive"); + connssl->io_need = CURL_SSL_IO_NEED_RECV; + break; + case SSL_ERROR_WANT_WRITE: + CURL_TRC_CF(data, cf, "SSL shutdown send blocked"); + connssl->io_need = CURL_SSL_IO_NEED_SEND; + break; + default: + /* Server seems to have closed the connection without sending us + * a close notify. */ + sslerr = ERR_get_error(); + CURL_TRC_CF(data, cf, "SSL shutdown, ignore recv error: '%s', errno %d", + (sslerr ? + ossl_strerror(sslerr, buf, sizeof(buf)) : + SSL_ERROR_to_str(err)), + SOCKERRNO); + *done = TRUE; + result = CURLE_OK; + break; + } + +out: + cf->shutdown = (result || *done); + return result; +} + +static void ossl_close(struct Curl_cfilter *cf, struct Curl_easy *data) +{ + struct ssl_connect_data *connssl = cf->ctx; + struct ossl_ctx *octx = (struct ossl_ctx *)connssl->backend; + + (void)data; + DEBUGASSERT(octx); + + if(octx->ssl) { + SSL_free(octx->ssl); + octx->ssl = NULL; + } + if(octx->ssl_ctx) { + SSL_CTX_free(octx->ssl_ctx); + octx->ssl_ctx = NULL; + octx->x509_store_setup = FALSE; + } + if(octx->bio_method) { + ossl_bio_cf_method_free(octx->bio_method); + octx->bio_method = NULL; + } +} + +static void ossl_session_free(void *sessionid, size_t idsize) +{ + /* free the ID */ + (void)idsize; + free(sessionid); +} + +/* + * 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 hostname. + */ +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. +*/ +static CURLcode ossl_verifyhost(struct Curl_easy *data, + struct connectdata *conn, + struct ssl_peer *peer, X509 *server_cert) +{ + bool matched = FALSE; + int target; /* target type, GEN_DNS or GEN_IPADD */ + size_t addrlen = 0; + STACK_OF(GENERAL_NAME) *altnames; +#ifdef USE_IPV6 + struct in6_addr addr; +#else + struct in_addr addr; +#endif + CURLcode result = CURLE_OK; + bool dNSName = FALSE; /* if a dNSName field exists in the cert */ + bool iPAddress = FALSE; /* if an iPAddress field exists in the cert */ + size_t hostlen; + + (void)conn; + hostlen = strlen(peer->hostname); + switch(peer->type) { + case CURL_SSL_PEER_IPV4: + if(!Curl_inet_pton(AF_INET, peer->hostname, &addr)) + return CURLE_PEER_FAILED_VERIFICATION; + target = GEN_IPADD; + addrlen = sizeof(struct in_addr); + break; +#ifdef USE_IPV6 + case CURL_SSL_PEER_IPV6: + if(!Curl_inet_pton(AF_INET6, peer->hostname, &addr)) + return CURLE_PEER_FAILED_VERIFICATION; + target = GEN_IPADD; + addrlen = sizeof(struct in6_addr); + break; +#endif + case CURL_SSL_PEER_DNS: + target = GEN_DNS; + break; + default: + DEBUGASSERT(0); + failf(data, "unexpected ssl peer type: %d", peer->type); + return CURLE_PEER_FAILED_VERIFICATION; + } + + /* 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 do not 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 manpage 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 is not 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) { + const char *tname = (peer->type == CURL_SSL_PEER_DNS) ? "hostname" : + (peer->type == CURL_SSL_PEER_IPV4) ? + "ipv4 address" : "ipv6 address"; + infof(data, " subjectAltName does not match %s %s", tname, peer->dispname); + failf(data, "SSL: no alternative certificate subject name matches " + "target %s '%s'", tname, 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 *cn = NULL; + int cnlen = 0; + bool free_cn = FALSE; + + /* 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) { + cnlen = ASN1_STRING_length(tmp); + cn = (unsigned char *) ASN1_STRING_get0_data(tmp); + } + else { /* not a UTF8 name */ + cnlen = ASN1_STRING_to_UTF8(&cn, tmp); + free_cn = TRUE; + } + + if((cnlen <= 0) || !cn) + result = CURLE_OUT_OF_MEMORY; + else if((size_t)cnlen != strlen((char *)cn)) { + /* 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(!cn) { + failf(data, + "SSL: unable to obtain common name from peer certificate"); + result = CURLE_PEER_FAILED_VERIFICATION; + } + else if(!Curl_cert_hostcheck((const char *)cn, cnlen, + peer->hostname, hostlen)) { + failf(data, "SSL: certificate subject name '%s' does not match " + "target hostname '%s'", cn, peer->dispname); + result = CURLE_PEER_FAILED_VERIFICATION; + } + else { + infof(data, " common name: %s (matched)", cn); + } + if(free_cn) + OPENSSL_free(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 ossl_ctx *octx) +{ + 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; + X509 *cert; + OCSP_CERTID *id = NULL; + int cert_status, crl_reason; + ASN1_GENERALIZEDTIME *rev, *thisupd, *nextupd; + int ret; + long len; + + (void)cf; + DEBUGASSERT(octx); + + len = (long)SSL_get_tlsext_status_ocsp_resp(octx->ssl, &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(octx->ssl); + if(!ch) { + failf(data, "Could not get peer certificate chain"); + result = CURLE_SSL_INVALIDCERTSTATUS; + goto end; + } + st = SSL_CTX_get_cert_store(octx->ssl_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 is a root cert, + no problem, but if it is 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; } - if(data->state.engine) { - ENGINE_finish(data->state.engine); - ENGINE_free(data->state.engine); - data->state.engine = NULL; + /* Compute the certificate's ID */ + cert = SSL_get1_peer_certificate(octx->ssl); + if(!cert) { + failf(data, "Error getting peer certificate"); + result = CURLE_SSL_INVALIDCERTSTATUS; + goto end; } - if(!ENGINE_init(e)) { - char buf[256]; - ENGINE_free(e); - failf(data, "Failed to initialise SSL Engine '%s':\n%s", - engine, ossl_strerror(ERR_get_error(), buf, sizeof(buf))); - return CURLE_SSL_ENGINE_INITFAILED; + for(i = 0; i < (int)sk_X509_num(ch); i++) { + X509 *issuer = sk_X509_value(ch, (ossl_valsize_t)i); + if(X509_check_issued(issuer, cert) == X509_V_OK) { + id = OCSP_cert_to_id(EVP_sha1(), cert, issuer); + break; + } } - data->state.engine = e; - return CURLE_OK; -#else - (void)engine; - failf(data, "SSL Engine not supported"); - return CURLE_SSL_ENGINE_NOTFOUND; -#endif -} + X509_free(cert); -/* Sets engine as default for all SSL operations - */ -CURLcode Curl_ossl_set_engine_default(struct Curl_easy *data) -{ -#ifdef HAVE_OPENSSL_ENGINE_H - if(data->state.engine) { - if(ENGINE_set_default(data->state.engine, ENGINE_METHOD_ALL) > 0) { - infof(data, "set default crypto engine '%s'\n", - 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; - } + if(!id) { + failf(data, "Error computing OCSP ID"); + result = CURLE_SSL_INVALIDCERTSTATUS; + goto end; } -#else - (void) data; -#endif - return CURLE_OK; -} -/* Return list of OpenSSL crypto engine names. - */ -struct curl_slist *Curl_ossl_engines_list(struct Curl_easy *data) -{ - struct curl_slist *list = NULL; -#if defined(USE_OPENSSL) && defined(HAVE_OPENSSL_ENGINE_H) - struct curl_slist *beg; - ENGINE *e; + /* 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; + } - 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; + /* 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; } -#endif - (void) data; - return list; -} + infof(data, "SSL certificate status: %s (%d)", + OCSP_cert_status_str(cert_status), cert_status); -static void ossl_close(struct ssl_connect_data *connssl) -{ - if(connssl->handle) { - (void)SSL_shutdown(connssl->handle); - SSL_set_connect_state(connssl->handle); + switch(cert_status) { + case V_OCSP_CERTSTATUS_GOOD: + break; - SSL_free(connssl->handle); - connssl->handle = NULL; - } - if(connssl->ctx) { - SSL_CTX_free(connssl->ctx); - connssl->ctx = NULL; + 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; } -} -/* - * This function is called when an SSL connection is closed. - */ -void Curl_ossl_close(struct connectdata *conn, int sockindex) -{ - ossl_close(&conn->ssl[sockindex]); - ossl_close(&conn->proxy_ssl[sockindex]); +end: + if(br) + OCSP_BASICRESP_free(br); + OCSP_RESPONSE_free(rsp); + + return result; } +#endif -/* - * This function is called to shut down the SSL layer but keep the - * socket open (CCC - Clear Command Channel) - */ -int Curl_ossl_shutdown(struct connectdata *conn, int sockindex) -{ - int retval = 0; - struct ssl_connect_data *connssl = &conn->ssl[sockindex]; - struct Curl_easy *data = conn->data; - 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; - ssize_t nread; - int buffsize; - int err; - int done = 0; - - /* 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(connssl->handle); - - if(connssl->handle) { - buffsize = (int)sizeof(buf); - while(!done) { - int what = SOCKET_READABLE(conn->sock[sockindex], - 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 = (ssize_t)SSL_read(conn->ssl[sockindex].handle, buf, - buffsize); - err = SSL_get_error(conn->ssl[sockindex].handle, (int)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 = 1; - break; - case SSL_ERROR_WANT_READ: - /* there's data pending, re-invoke SSL_read() */ - infof(data, "SSL_ERROR_WANT_READ\n"); - break; - case SSL_ERROR_WANT_WRITE: - /* SSL wants a write. Really odd. Let's bail out. */ - infof(data, "SSL_ERROR_WANT_WRITE\n"); - done = 1; - break; - default: - /* openssl/ssl.h says "look at error stack/return value/errno" */ - sslerror = ERR_get_error(); - failf(conn->data, OSSL_PACKAGE " SSL read: %s, errno %d", - ossl_strerror(sslerror, buf, sizeof(buf)), - SOCKERRNO); - done = 1; - break; - } - } - else if(0 == what) { - /* timeout */ - failf(data, "SSL shutdown timeout"); - done = 1; - } - else { - /* anything that gets here is fatally bad */ - failf(data, "select/poll on SSL socket, errno: %d", SOCKERRNO); - retval = -1; - done = 1; - } - } /* while()-loop for the select() */ +#endif /* USE_OPENSSL */ - if(data->set.verbose) { -#ifdef HAVE_SSL_GET_SHUTDOWN - switch(SSL_get_shutdown(connssl->handle)) { - case SSL_SENT_SHUTDOWN: - infof(data, "SSL_get_shutdown() returned SSL_SENT_SHUTDOWN\n"); - break; - case SSL_RECEIVED_SHUTDOWN: - infof(data, "SSL_get_shutdown() returned SSL_RECEIVED_SHUTDOWN\n"); - break; - case SSL_SENT_SHUTDOWN|SSL_RECEIVED_SHUTDOWN: - infof(data, "SSL_get_shutdown() returned SSL_SENT_SHUTDOWN|" - "SSL_RECEIVED__SHUTDOWN\n"); - break; - } +/* The SSL_CTRL_SET_MSG_CALLBACK does not 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 } - - SSL_free(connssl->handle); - connssl->handle = NULL; } - return retval; + return "Unknown"; } -void Curl_ossl_session_free(void *ptr) +static const char *tls_rt_type(int type) { - /* free the ID */ - SSL_SESSION_free(ptr); + 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"; + } } /* - * This function is called when the 'data' struct is going away. Close - * down everything and free all resources! + * Our callback from the SSL/TLS layers. */ -void Curl_ossl_close_all(struct Curl_easy *data) +static void ossl_trace(int direction, int ssl_ver, int content_type, + const void *buf, size_t len, SSL *ssl, + void *userp) { -#ifdef HAVE_OPENSSL_ENGINE_H - if(data->state.engine) { - ENGINE_finish(data->state.engine); - ENGINE_free(data->state.engine); - data->state.engine = NULL; + 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; } -#else - (void)data; + + /* 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 -#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); +#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 */ -/* Quote from RFC2818 section 3.1 "Server Identity" + /* SSLv2 does not 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 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. + 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); + } - 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. + 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); + } + } - 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. + Curl_debug(data, (direction == 1) ? CURLINFO_SSL_DATA_OUT : + CURLINFO_SSL_DATA_IN, (char *)buf, len); + (void) ssl; +} +#endif -*/ -static CURLcode verifyhost(struct connectdata *conn, X509 *server_cert) +#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 + +#if (OPENSSL_VERSION_NUMBER >= 0x10100000L) /* 1.1.0 */ +static CURLcode +ossl_set_ssl_version_min_max(struct Curl_cfilter *cf, SSL_CTX *ctx) { - bool matched = FALSE; - int target = GEN_DNS; /* target type, GEN_DNS or GEN_IPADD */ - size_t addrlen = 0; - struct Curl_easy *data = conn->data; - STACK_OF(GENERAL_NAME) *altnames; -#ifdef ENABLE_IPV6 - struct in6_addr addr; + 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 - struct in_addr addr; + return CURLE_NOT_BUILT_IN; #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 */ - const char * const hostname = SSL_IS_PROXY() ? conn->http_proxy.host.name : - conn->host.name; - const char * const dispname = SSL_IS_PROXY() ? - conn->http_proxy.host.dispname : conn->host.dispname; - -#ifdef ENABLE_IPV6 - if(conn->bits.ipv6_ip && - Curl_inet_pton(AF_INET6, hostname, &addr)) { - target = GEN_IPADD; - addrlen = sizeof(struct in6_addr); } - else -#endif - if(Curl_inet_pton(AF_INET, 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) { - int numalts; - int i; - bool dnsmatched = FALSE; - bool ipmatched = FALSE; + /* CURL_SSLVERSION_DEFAULT means that no option was selected. + We do not 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; + } + } - /* 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); + /* ... then, TLS max version */ + curl_ssl_version_max = (long)conn_config->version_max; - /* 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); + /* 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(check->type == GEN_DNS) - dNSName = TRUE; - else if(check->type == GEN_IPADD) - iPAddress = TRUE; + if(!SSL_CTX_set_max_proto_version(ctx, ossl_ssl_version_max)) { + return CURLE_SSL_CONNECT_ERROR; + } - /* 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); + return CURLE_OK; +} +#endif - 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 and IA5String the data will be ASCII" +#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; +#elif OPENSSL_VERSION_NUMBER >= 0x10100000L && \ + !defined(LIBRESSL_VERSION_NUMBER) +typedef unsigned long ctx_option_t; +#else +typedef long ctx_option_t; +#endif - Gisle researched the OpenSSL sources: - "I checked the 0.9.6 and 0.9.8 sources before my patch and - it always 0-terminates an IA5String." - */ - if((altlen == strlen(altptr)) && - /* if this isn't true, there was an embedded zero in the name - string and we cannot match it. */ - Curl_cert_hostcheck(altptr, hostname)) { - dnsmatched = TRUE; - infof(data, - " subjectAltName: host \"%s\" matched cert's \"%s\"\n", - dispname, altptr); - } - break; +#if (OPENSSL_VERSION_NUMBER < 0x10100000L) /* 1.1.0 */ +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; - 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!\n", - dispname); - } - break; - } - } - } - GENERAL_NAMES_free(altnames); + (void) data; /* In case it is unused. */ - if(dnsmatched || ipmatched) - matched = TRUE; + switch(ssl_version) { + case CURL_SSLVERSION_TLSv1_3: +#ifdef TLS1_3_VERSION + { + struct ssl_connect_data *connssl = cf->ctx; + struct ossl_ctx *octx = (struct ossl_ctx *)connssl->backend; + DEBUGASSERT(octx); + SSL_CTX_set_max_proto_version(octx->ssl_ctx, TLS1_3_VERSION); + *ctx_options |= SSL_OP_NO_TLSv1_2; } - - if(matched) - /* an alternative name matched */ - ; - else if(dNSName || iPAddress) { - infof(data, " subjectAltName does not match %s\n", dispname); - failf(data, "SSL: no alternative certificate subject name matches " - "target host name '%s'", dispname); - result = CURLE_PEER_FAILED_VERIFICATION; +#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; } - else { - /* we have to look to the last occurrence of a commonName in the - distinguished one to get the most significant one. */ - int j, i=-1; - - /* The following is done because of a bug in 0.9.6b */ - - unsigned char *nulstr = (unsigned char *)""; - unsigned char *peer_CN = nulstr; - X509_NAME *name = X509_get_subject_name(server_cert); - if(name) - while((j = X509_NAME_get_index_by_NID(name, NID_commonName, i))>=0) - i=j; + 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 - /* 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. */ +CURLcode Curl_ossl_add_session(struct Curl_cfilter *cf, + struct Curl_easy *data, + const struct ssl_peer *peer, + SSL_SESSION *session) +{ + const struct ssl_config_data *config; + CURLcode result = CURLE_OK; + size_t der_session_size; + unsigned char *der_session_buf; + unsigned char *der_session_ptr; - if(i>=0) { - ASN1_STRING *tmp = - X509_NAME_ENTRY_get_data(X509_NAME_get_entry(name, i)); + if(!cf || !data) + goto out; - /* 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. Work-around - brought by Alexis S. L. Carvalho. */ - if(tmp) { - if(ASN1_STRING_type(tmp) == V_ASN1_UTF8STRING) { - j = ASN1_STRING_length(tmp); - if(j >= 0) { - peer_CN = OPENSSL_malloc(j+1); - if(peer_CN) { - memcpy(peer_CN, ASN1_STRING_get0_data(tmp), j); - peer_CN[j] = '\0'; - } - } - } - else /* not a UTF8 name */ - j = ASN1_STRING_to_UTF8(&peer_CN, tmp); + config = Curl_ssl_cf_get_config(cf, data); + if(config->primary.cache_session) { - if(peer_CN && (curlx_uztosi(strlen((char *)peer_CN)) != j)) { - /* 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; - } - } + der_session_size = i2d_SSL_SESSION(session, NULL); + if(der_session_size == 0) { + result = CURLE_OUT_OF_MEMORY; + goto out; } - if(peer_CN == nulstr) - peer_CN = NULL; - else { - /* convert peer_CN from UTF8 */ - CURLcode rc = Curl_convert_from_utf8(data, peer_CN, strlen(peer_CN)); - /* Curl_convert_from_utf8 calls failf if unsuccessful */ - if(rc) { - OPENSSL_free(peer_CN); - return rc; - } + der_session_buf = der_session_ptr = malloc(der_session_size); + if(!der_session_buf) { + result = CURLE_OUT_OF_MEMORY; + goto out; } - 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; + der_session_size = i2d_SSL_SESSION(session, &der_session_ptr); + if(der_session_size == 0) { + result = CURLE_OUT_OF_MEMORY; + free(der_session_buf); + goto out; } - else if(!Curl_cert_hostcheck((const char *)peer_CN, hostname)) { - failf(data, "SSL: certificate subject name '%s' does not match " - "target host name '%s'", peer_CN, dispname); - result = CURLE_PEER_FAILED_VERIFICATION; - } - else { - infof(data, " common name: %s (matched)\n", peer_CN); - } - if(peer_CN) - OPENSSL_free(peer_CN); + + Curl_ssl_sessionid_lock(data); + result = Curl_ssl_set_sessionid(cf, data, peer, der_session_buf, + der_session_size, ossl_session_free); + Curl_ssl_sessionid_unlock(data); } +out: return result; } -#if (OPENSSL_VERSION_NUMBER >= 0x0090808fL) && !defined(OPENSSL_NO_TLSEXT) && \ - !defined(OPENSSL_NO_OCSP) -static CURLcode verifystatus(struct connectdata *conn, - struct ssl_connect_data *connssl) +/* 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 i, ocsp_status; - const unsigned char *p; - CURLcode result = CURLE_OK; - struct Curl_easy *data = conn->data; + struct Curl_cfilter *cf; + struct Curl_easy *data; + struct ssl_connect_data *connssl; - OCSP_RESPONSE *rsp = NULL; - OCSP_BASICRESP *br = NULL; - X509_STORE *st = NULL; - STACK_OF(X509) *ch = NULL; + cf = (struct Curl_cfilter*) SSL_get_app_data(ssl); + connssl = cf ? cf->ctx : NULL; + data = connssl ? CF_DATA_CURRENT(cf) : NULL; + Curl_ossl_add_session(cf, data, &connssl->peer, ssl_sessionid); + return 0; +} - long len = SSL_get_tlsext_status_ocsp_resp(connssl->handle, &p); +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; - if(!p) { - failf(data, "No OCSP response received"); - result = CURLE_SSL_INVALIDCERTSTATUS; - goto end; - } + /* everything else is just a reference */ + int i, count = 0; + X509_INFO *itmp = NULL; - rsp = d2i_OCSP_RESPONSE(NULL, &p, len); - if(!rsp) { - failf(data, "Invalid OCSP response"); - result = CURLE_SSL_INVALIDCERTSTATUS; - goto end; - } + if(ca_info_blob->len > (size_t)INT_MAX) + return CURLE_SSL_CACERT_BADFILE; - 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; - } + cbio = BIO_new_mem_buf(ca_info_blob->data, (int)ca_info_blob->len); + if(!cbio) + return CURLE_OUT_OF_MEMORY; - br = OCSP_response_get1_basic(rsp); - if(!br) { - failf(data, "Invalid OCSP response"); - result = CURLE_SSL_INVALIDCERTSTATUS; - goto end; + inf = PEM_X509_INFO_read_bio(cbio, NULL, NULL, NULL); + if(!inf) { + BIO_free(cbio); + return CURLE_SSL_CACERT_BADFILE; } - ch = SSL_get_peer_cert_chain(connssl->handle); - st = SSL_CTX_get_cert_store(connssl->ctx); - -#if ((OPENSSL_VERSION_NUMBER <= 0x1000201fL) /* Fixed after 1.0.2a */ || \ - defined(LIBRESSL_VERSION_NUMBER)) - /* 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; - } + /* 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, (ossl_valsize_t)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; } } } -#endif - if(OCSP_basic_verify(br, ch, st, 0) <= 0) { - failf(data, "OCSP response verification failed"); - result = CURLE_SSL_INVALIDCERTSTATUS; - goto end; - } + sk_X509_INFO_pop_free(inf, X509_INFO_free); + BIO_free(cbio); + + /* if we did not end up importing anything, treat that as an error */ + return (count > 0) ? CURLE_OK : CURLE_SSL_CACERT_BADFILE; +} - for(i = 0; i < OCSP_resp_count(br); i++) { - int cert_status, crl_reason; - OCSP_SINGLERESP *single = NULL; +#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; + pContext = CertEnumCertificatesInStore(hStore, pContext); + if(!pContext) + break; - ASN1_GENERALIZEDTIME *rev, *thisupd, *nextupd; +#if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS) + else { + char cert_name[256]; + if(!CertGetNameStringA(pContext, CERT_NAME_SIMPLE_DISPLAY_TYPE, 0, + NULL, cert_name, sizeof(cert_name))) + infof(data, "SSL: unknown cert name"); + else + 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; + } - single = OCSP_resp_get0(br, i); - if(!single) - continue; + enhkey_usage = (CERT_ENHKEY_USAGE *)tmp; + enhkey_usage_size = req_size; + } - cert_status = OCSP_single_get0_status(single, &crl_reason, &rev, - &thisupd, &nextupd); + 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(!OCSP_check_validity(thisupd, nextupd, 300L, -1L)) { - failf(data, "OCSP response has expired"); - result = CURLE_SSL_INVALIDCERTSTATUS; - goto end; + if(!found) + continue; + } + } + else + continue; + } + else + continue; + + x509 = d2i_X509(NULL, &encoded_cert, (long)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"); +#endif + *imported = true; + } + X509_free(x509); } - infof(data, "SSL certificate status: %s (%d)\n", - 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; + free(enhkey_usage); + CertFreeCertificateContext(pContext); + CertCloseStore(hStore, 0); - case V_OCSP_CERTSTATUS_UNKNOWN: - result = CURLE_SSL_INVALIDCERTSTATUS; - goto end; - } + if(result) + return result; } -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) +static CURLcode populate_x509_store(struct Curl_cfilter *cf, + struct Curl_easy *data, + X509_STORE *store) { -#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"; + 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; + + CURL_TRC_CF(data, cf, "populate_x509_store, path=%s, blob=%d", + ssl_cafile ? ssl_cafile : "none", !!ca_info_blob); + 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]); + } } - } - 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"; + 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"); } - } - return "Unknown"; -} -static const char *tls_rt_type(int type) -{ - switch(type) { -#ifdef SSL3_RT_HEADER - case SSL3_RT_HEADER: - return "TLS header"; +#ifdef CURL_CA_FALLBACK + if(!ssl_cafile && !ssl_capath && + !imported_native_ca && !imported_ca_info_blob) { + /* verifying the peer without any CA certificates will not + work so use OpenSSL's built-in default as fallback */ + X509_STORE_set_default_paths(store); + } #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 ssl_tls_trace(int direction, int ssl_ver, int content_type, - const void *buf, size_t len, SSL *ssl, - void *userp) -{ - struct Curl_easy *data; - const char *msg_name, *tls_rt_name; - char ssl_buf[1024]; - char unknown[32]; - int msg_type, txt_len; - const char *verstr = NULL; - struct connectdata *conn = userp; - if(!conn || !conn->data || !conn->data->set.fdebug || - (direction != 0 && direction != 1)) - return; + 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); - data = conn->data; + infof(data, " CRLfile: %s", ssl_crlfile); + } - 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; + 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://web.archive.org/web/20190422050538/ + rt.openssl.org/Ticket/Display.html?id=3621 + */ +#if defined(X509_V_FLAG_TRUSTED_FIRST) + X509_STORE_set_flags(store, X509_V_FLAG_TRUSTED_FIRST); #endif -#ifdef TLS1_3_VERSION - case TLS1_3_VERSION: - verstr = "TLSv1.3"; - break; +#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 - case 0: - break; - default: - snprintf(unknown, sizeof(unknown), "(%x)", ssl_ver); - verstr = unknown; - break; } - if(ssl_ver) { - /* the info given when the version is zero is not that useful for us */ + return result; +} - ssl_ver >>= 8; /* check the upper 8 bits only below */ +#if defined(HAVE_SSL_X509_STORE_SHARE) - /* 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 = ""; +/* key to use at `multi->proto_hash` */ +#define MPROTO_OSSL_X509_KEY "tls:ossl:x509:share" - msg_type = *(char *)buf; - msg_name = ssl_msg_type(ssl_ver, msg_type); +struct ossl_x509_share { + 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 */ +}; - txt_len = snprintf(ssl_buf, sizeof(ssl_buf), "%s (%s), %s, %s (%d):\n", - verstr, direction?"OUT":"IN", - tls_rt_name, msg_name, msg_type); - Curl_debug(data, CURLINFO_TEXT, ssl_buf, (size_t)txt_len, NULL); +static void oss_x509_share_free(void *key, size_t key_len, void *p) +{ + struct ossl_x509_share *share = p; + DEBUGASSERT(key_len == (sizeof(MPROTO_OSSL_X509_KEY)-1)); + DEBUGASSERT(!memcmp(MPROTO_OSSL_X509_KEY, key, key_len)); + (void)key; + (void)key_len; + if(share->store) { + X509_STORE_free(share->store); } - - Curl_debug(data, (direction == 1) ? CURLINFO_SSL_DATA_OUT : - CURLINFO_SSL_DATA_IN, (char *)buf, len, NULL); - (void) ssl; + free(share->CAfile); + free(share); } -#endif - -#ifdef USE_OPENSSL -/* ====================================================== */ -#ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME -# define use_sni(x) sni = (x) -#else -# define use_sni(x) Curl_nop_stmt -#endif - -/* 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 +static bool +cached_x509_store_expired(const struct Curl_easy *data, + const struct ossl_x509_share *mb) +{ + const struct ssl_general_config *cfg = &data->set.general_ssl; + if(cfg->ca_cache_timeout < 0) + return FALSE; + else { + 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; -/* Check for OpenSSL 1.0.1 which has NPN support. */ -#undef HAS_NPN -#if OPENSSL_VERSION_NUMBER >= 0x10001000L \ - && !defined(OPENSSL_NO_TLSEXT) \ - && !defined(OPENSSL_NO_NEXTPROTONEG) -# define HAS_NPN 1 -#endif + return elapsed_ms >= timeout_ms; + } +} -#ifdef HAS_NPN +static bool +cached_x509_store_different(struct Curl_cfilter *cf, + const struct ossl_x509_share *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; -/* - * in is a list of lenght prefixed strings. this function has to select - * the protocol we want to use from the list and write its string into out. - */ + return strcmp(mb->CAfile, conn_config->CAfile); +} -static int -select_next_protocol(unsigned char **out, unsigned char *outlen, - const unsigned char *in, unsigned int inlen, - const char *key, unsigned int keylen) -{ - unsigned int i; - for(i = 0; i + keylen <= inlen; i += in[i] + 1) { - if(memcmp(&in[i + 1], key, keylen) == 0) { - *out = (unsigned char *) &in[i + 1]; - *outlen = in[i]; - return 0; - } +static X509_STORE *get_cached_x509_store(struct Curl_cfilter *cf, + const struct Curl_easy *data) +{ + struct Curl_multi *multi = data->multi; + struct ossl_x509_share *share; + X509_STORE *store = NULL; + + DEBUGASSERT(multi); + share = multi ? Curl_hash_pick(&multi->proto_hash, + (void *)MPROTO_OSSL_X509_KEY, + sizeof(MPROTO_OSSL_X509_KEY)-1) : NULL; + if(share && share->store && + !cached_x509_store_expired(data, share) && + !cached_x509_store_different(cf, share)) { + store = share->store; } - return -1; + + return store; } -static int -select_next_proto_cb(SSL *ssl, - unsigned char **out, unsigned char *outlen, - const unsigned char *in, unsigned int inlen, - void *arg) +static void set_cached_x509_store(struct Curl_cfilter *cf, + const struct Curl_easy *data, + X509_STORE *store) { - struct connectdata *conn = (struct connectdata*) arg; - - (void)ssl; + struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); + struct Curl_multi *multi = data->multi; + struct ossl_x509_share *share; -#ifdef USE_NGHTTP2 - if(conn->data->set.httpversion >= CURL_HTTP_VERSION_2 && - !select_next_protocol(out, outlen, in, inlen, NGHTTP2_PROTO_VERSION_ID, - NGHTTP2_PROTO_VERSION_ID_LEN)) { - infof(conn->data, "NPN, negotiated HTTP2 (%s)\n", - NGHTTP2_PROTO_VERSION_ID); - conn->negnpn = CURL_HTTP_VERSION_2; - return SSL_TLSEXT_ERR_OK; + DEBUGASSERT(multi); + if(!multi) + return; + share = Curl_hash_pick(&multi->proto_hash, + (void *)MPROTO_OSSL_X509_KEY, + sizeof(MPROTO_OSSL_X509_KEY)-1); + + if(!share) { + share = calloc(1, sizeof(*share)); + if(!share) + return; + if(!Curl_hash_add2(&multi->proto_hash, + (void *)MPROTO_OSSL_X509_KEY, + sizeof(MPROTO_OSSL_X509_KEY)-1, + share, oss_x509_share_free)) { + free(share); + return; + } } -#endif - if(!select_next_protocol(out, outlen, in, inlen, ALPN_HTTP_1_1, - ALPN_HTTP_1_1_LENGTH)) { - infof(conn->data, "NPN, negotiated HTTP1.1\n"); - conn->negnpn = CURL_HTTP_VERSION_1_1; - return SSL_TLSEXT_ERR_OK; - } + 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; + } + } - infof(conn->data, "NPN, no overlap, use HTTP1.1\n"); - *out = (unsigned char *)ALPN_HTTP_1_1; - *outlen = ALPN_HTTP_1_1_LENGTH; - conn->negnpn = CURL_HTTP_VERSION_1_1; + if(share->store) { + X509_STORE_free(share->store); + free(share->CAfile); + } - return SSL_TLSEXT_ERR_OK; + share->time = Curl_now(); + share->store = store; + share->CAfile = CAfile; + } } -#endif /* HAS_NPN */ -static const char * -get_ssl_version_txt(SSL *ssl) +CURLcode Curl_ssl_setup_x509_store(struct Curl_cfilter *cf, + struct Curl_easy *data, + SSL_CTX *ssl_ctx) { - if(!ssl) - return ""; + 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); - switch(SSL_version(ssl)) { -#ifdef TLS1_3_VERSION - case TLS1_3_VERSION: - return "TLSv1.3"; -#endif -#if OPENSSL_VERSION_NUMBER >= 0x1000100FL - case TLS1_2_VERSION: - return "TLSv1.2"; - case TLS1_1_VERSION: - return "TLSv1.1"; -#endif - case TLS1_VERSION: - return "TLSv1.0"; - case SSL3_VERSION: - return "SSLv3"; - case SSL2_VERSION: - return "SSLv2"; + result = populate_x509_store(cf, data, store); + if(result == CURLE_OK && cache_criteria_met) { + set_cached_x509_store(cf, data, store); + } } - return "unknown"; + + 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); -static CURLcode ossl_connect_step1(struct connectdata *conn, int sockindex) + return populate_x509_store(cf, data, store); +} +#endif /* HAVE_SSL_X509_STORE_SHARE */ + +CURLcode Curl_ossl_ctx_init(struct ossl_ctx *octx, + struct Curl_cfilter *cf, + struct Curl_easy *data, + struct ssl_peer *peer, + int transport, /* TCP or QUIC */ + const unsigned char *alpn, size_t alpn_len, + Curl_ossl_ctx_setup_cb *cb_setup, + void *cb_user_data, + Curl_ossl_new_session_cb *cb_new_session, + void *ssl_user_data) { CURLcode result = CURLE_OK; - char *ciphers; - struct Curl_easy *data = conn->data; + const char *ciphers; SSL_METHOD_QUAL SSL_METHOD *req_method = NULL; - X509_LOOKUP *lookup = NULL; - curl_socket_t sockfd = conn->sock[sockindex]; - struct ssl_connect_data *connssl = &conn->ssl[sockindex]; - long ctx_options; -#ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME - bool sni; -#ifdef ENABLE_IPV6 - struct in6_addr addr; -#else - struct in_addr addr; -#endif -#endif - long * const certverifyresult = SSL_IS_PROXY() ? - &data->set.proxy_ssl.certverifyresult : &data->set.ssl.certverifyresult; - const long int ssl_version = SSL_CONN_CONFIG(version); -#ifdef USE_TLS_SRP - const enum CURL_TLSAUTH ssl_authtype = SSL_SET_OPTION(authtype); -#endif - char * const ssl_cert = SSL_SET_OPTION(cert); - const char * const ssl_cert_type = SSL_SET_OPTION(cert_type); - const char * const ssl_cafile = SSL_CONN_CONFIG(CAfile); - const char * const ssl_capath = SSL_CONN_CONFIG(CApath); - const bool verifypeer = SSL_CONN_CONFIG(verifypeer); - const char * const ssl_crlfile = SSL_SET_OPTION(CRLfile); - const char * const hostname = SSL_IS_PROXY() ? conn->http_proxy.host.name : - conn->host.name; - - DEBUGASSERT(ssl_connect_1 == connssl->connecting_state); + ctx_option_t ctx_options = 0; + SSL_SESSION *ssl_session = NULL; + const unsigned char *der_sessionid = NULL; + size_t der_sessionid_size = 0; + 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 long int ssl_version_min = 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]; /* Make funny stuff to get random input */ - result = Curl_ossl_seed(data); + result = ossl_seed(data); if(result) return result; - *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) && \ - !defined(LIBRESSL_VERSION_NUMBER) - req_method = TLS_client_method(); -#else - req_method = SSLv23_client_method(); -#endif - use_sni(TRUE); - break; - case CURL_SSLVERSION_SSLv2: -#ifdef OPENSSL_NO_SSL2 - failf(data, OSSL_PACKAGE " was built without SSLv2 support"); - return CURLE_NOT_BUILT_IN; -#else -#ifdef USE_TLS_SRP - if(ssl_authtype == CURL_TLSAUTH_SRP) + ssl_config->certverifyresult = !X509_V_OK; + + switch(transport) { + case TRNSPRT_TCP: + /* check to see if we have been told to use an explicit SSL/TLS version */ + switch(ssl_version_min) { + 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; -#endif - req_method = SSLv2_client_method(); - use_sni(FALSE); + } break; -#endif - case CURL_SSLVERSION_SSLv3: -#ifdef OPENSSL_NO_SSL3_METHOD - failf(data, OSSL_PACKAGE " was built without SSLv3 support"); - return CURLE_NOT_BUILT_IN; -#else -#ifdef USE_TLS_SRP - if(ssl_authtype == CURL_TLSAUTH_SRP) + case TRNSPRT_QUIC: + if(conn_config->version_max && + (conn_config->version_max != CURL_SSLVERSION_MAX_TLSv1_3)) { + failf(data, "QUIC needs at least TLS version 1.3"); return CURLE_SSL_CONNECT_ERROR; + } + +#ifdef USE_OPENSSL_QUIC + req_method = OSSL_QUIC_client_method(); +#elif (OPENSSL_VERSION_NUMBER >= 0x10100000L) + req_method = TLS_method(); +#else + req_method = SSLv23_client_method(); #endif - req_method = SSLv3_client_method(); - use_sni(FALSE); break; -#endif default: - failf(data, "Unrecognized parameter passed via CURLOPT_SSLVERSION"); + failf(data, "unsupported transport %d in SSL init", transport); return CURLE_SSL_CONNECT_ERROR; } - if(connssl->ctx) - SSL_CTX_free(connssl->ctx); - connssl->ctx = SSL_CTX_new(req_method); - if(!connssl->ctx) { - failf(data, "SSL: couldn't create a context: %s", - ERR_error_string(ERR_peek_error(), NULL)); + DEBUGASSERT(!octx->ssl_ctx); + octx->ssl_ctx = SSL_CTX_new(req_method); + + if(!octx->ssl_ctx) { + failf(data, "SSL: could not 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(connssl->ctx, SSL_MODE_RELEASE_BUFFERS); -#endif + if(cb_setup) { + result = cb_setup(cf, data, cb_user_data); + if(result) + return result; + } #ifdef SSL_CTRL_SET_MSG_CALLBACK if(data->set.fdebug && data->set.verbose) { /* the SSL trace callback is only used for verbose logging */ - SSL_CTX_set_msg_callback(connssl->ctx, ssl_tls_trace); - SSL_CTX_set_msg_callback_arg(connssl->ctx, conn); + SSL_CTX_set_msg_callback(octx->ssl_ctx, ossl_trace); + SSL_CTX_set_msg_callback_arg(octx->ssl_ctx, cf); } #endif - /* OpenSSL contains code to work-around lots of bugs and flaws in various + /* 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 + work-arounds. The manpage 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 Openssl0.9.8j. It's a flag to - disable "rfc4507bis session ticket support". rfc4507bis was later turned - into the proper RFC5077 it seems: https://tools.ietf.org/html/rfc5077 + The "-no_ticket" option was introduced in OpenSSL 0.9.8j. it is 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 + 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 + 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 @@ -1832,12 +3604,12 @@ static CURLcode ossl_connect_step1(struct connectdata *conn, int sockindex) 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. + OpenSSL added a work-around for a SSL 3.0/TLS 1.0 CBC vulnerability: + https://web.archive.org/web/20240114184648/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; @@ -1852,837 +3624,773 @@ static CURLcode ossl_connect_step1(struct connectdata *conn, int sockindex) #ifdef SSL_OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG /* mitigate CVE-2010-4180 */ - ctx_options &= ~SSL_OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG; + ctx_options &= ~(ctx_option_t)SSL_OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG; #endif #ifdef SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS - /* unless the user explicitly ask to allow the protocol vulnerability we + /* unless the user explicitly asks to allow the protocol vulnerability we use the work-around */ - if(!SSL_SET_OPTION(enable_beast)) - ctx_options &= ~SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS; + if(!ssl_config->enable_beast) + ctx_options &= ~(ctx_option_t)SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS; #endif - switch(ssl_version) { + switch(ssl_version_min) { + case CURL_SSLVERSION_SSLv2: case CURL_SSLVERSION_SSLv3: -#ifdef USE_TLS_SRP - if(ssl_authtype == CURL_TLSAUTH_SRP) { - infof(data, "Set version TLSv1.x for SRP authorisation\n"); - } -#endif - ctx_options |= SSL_OP_NO_SSLv2; - ctx_options |= SSL_OP_NO_TLSv1; -#if OPENSSL_VERSION_NUMBER >= 0x1000100FL - ctx_options |= SSL_OP_NO_TLSv1_1; - ctx_options |= SSL_OP_NO_TLSv1_2; -#ifdef TLS1_3_VERSION - ctx_options |= SSL_OP_NO_TLSv1_3; -#endif -#endif - break; - - case CURL_SSLVERSION_DEFAULT: - case CURL_SSLVERSION_TLSv1: - ctx_options |= SSL_OP_NO_SSLv2; - ctx_options |= SSL_OP_NO_SSLv3; - break; - - case CURL_SSLVERSION_TLSv1_0: - ctx_options |= SSL_OP_NO_SSLv2; - ctx_options |= SSL_OP_NO_SSLv3; -#if OPENSSL_VERSION_NUMBER >= 0x1000100FL - ctx_options |= SSL_OP_NO_TLSv1_1; - ctx_options |= SSL_OP_NO_TLSv1_2; -#ifdef TLS1_3_VERSION - ctx_options |= SSL_OP_NO_TLSv1_3; -#endif -#endif - break; - - case CURL_SSLVERSION_TLSv1_1: -#if OPENSSL_VERSION_NUMBER >= 0x1000100FL - ctx_options |= SSL_OP_NO_SSLv2; - ctx_options |= SSL_OP_NO_SSLv3; - ctx_options |= SSL_OP_NO_TLSv1; - ctx_options |= SSL_OP_NO_TLSv1_2; -#ifdef TLS1_3_VERSION - ctx_options |= SSL_OP_NO_TLSv1_3; -#endif - break; -#else - failf(data, OSSL_PACKAGE " was built without TLS 1.1 support"); return CURLE_NOT_BUILT_IN; -#endif - case CURL_SSLVERSION_TLSv1_2: -#if OPENSSL_VERSION_NUMBER >= 0x1000100FL + /* "--tlsv" options mean TLS >= version */ + 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; - ctx_options |= SSL_OP_NO_TLSv1; - ctx_options |= SSL_OP_NO_TLSv1_1; -#ifdef TLS1_3_VERSION - ctx_options |= SSL_OP_NO_TLSv1_3; -#endif - break; -#else - failf(data, OSSL_PACKAGE " was built without TLS 1.2 support"); - return CURLE_NOT_BUILT_IN; -#endif - case CURL_SSLVERSION_TLSv1_3: -#ifdef TLS1_3_VERSION - SSL_CTX_set_max_proto_version(connssl->ctx, TLS1_3_VERSION); - ctx_options |= SSL_OP_NO_SSLv2; - ctx_options |= SSL_OP_NO_SSLv3; - ctx_options |= SSL_OP_NO_TLSv1; - ctx_options |= SSL_OP_NO_TLSv1_1; - ctx_options |= SSL_OP_NO_TLSv1_2; - break; +#if (OPENSSL_VERSION_NUMBER >= 0x10100000L) /* 1.1.0 */ + result = ossl_set_ssl_version_min_max(cf, octx->ssl_ctx); #else - failf(data, OSSL_PACKAGE " was built without TLS 1.3 support"); - return CURLE_NOT_BUILT_IN; -#endif - - case CURL_SSLVERSION_SSLv2: -#ifndef OPENSSL_NO_SSL2 - ctx_options |= SSL_OP_NO_SSLv3; - ctx_options |= SSL_OP_NO_TLSv1; -#if OPENSSL_VERSION_NUMBER >= 0x1000100FL - ctx_options |= SSL_OP_NO_TLSv1_1; - ctx_options |= SSL_OP_NO_TLSv1_2; -#ifdef TLS1_3_VERSION - ctx_options |= SSL_OP_NO_TLSv1_3; -#endif + result = ossl_set_ssl_version_min_max_legacy(&ctx_options, cf, data); #endif + if(result != CURLE_OK) + return result; break; -#else - failf(data, OSSL_PACKAGE " was built without SSLv2 support"); - return CURLE_NOT_BUILT_IN; -#endif default: failf(data, "Unrecognized parameter passed via CURLOPT_SSLVERSION"); return CURLE_SSL_CONNECT_ERROR; } - SSL_CTX_set_options(connssl->ctx, ctx_options); + SSL_CTX_set_options(octx->ssl_ctx, ctx_options); -#ifdef HAS_NPN - if(conn->bits.tls_enable_npn) - SSL_CTX_set_next_proto_select_cb(connssl->ctx, select_next_proto_cb, conn); +#ifdef SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER + /* We do retry writes sometimes from another buffer address */ + SSL_CTX_set_mode(octx->ssl_ctx, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER); #endif #ifdef HAS_ALPN - if(conn->bits.tls_enable_alpn) { - int cur = 0; - unsigned char protocols[128]; - -#ifdef USE_NGHTTP2 - if(data->set.httpversion >= CURL_HTTP_VERSION_2) { - protocols[cur++] = NGHTTP2_PROTO_VERSION_ID_LEN; - - memcpy(&protocols[cur], NGHTTP2_PROTO_VERSION_ID, - NGHTTP2_PROTO_VERSION_ID_LEN); - cur += NGHTTP2_PROTO_VERSION_ID_LEN; - infof(data, "ALPN, offering %s\n", NGHTTP2_PROTO_VERSION_ID); + if(alpn && alpn_len) { + if(SSL_CTX_set_alpn_protos(octx->ssl_ctx, alpn, (int)alpn_len)) { + failf(data, "Error setting ALPN"); + return CURLE_SSL_CONNECT_ERROR; } -#endif - - protocols[cur++] = ALPN_HTTP_1_1_LENGTH; - memcpy(&protocols[cur], ALPN_HTTP_1_1, ALPN_HTTP_1_1_LENGTH); - cur += ALPN_HTTP_1_1_LENGTH; - infof(data, "ALPN, offering %s\n", ALPN_HTTP_1_1); - - /* expects length prefixed preference ordered list of protocols in wire - * format - */ - SSL_CTX_set_alpn_protos(connssl->ctx, protocols, cur); } #endif - if(ssl_cert || ssl_cert_type) { - if(!cert_stuff(conn, connssl->ctx, ssl_cert, ssl_cert_type, - SSL_SET_OPTION(key), SSL_SET_OPTION(key_type), - SSL_SET_OPTION(key_passwd))) { + if(ssl_cert || ssl_cert_blob || ssl_cert_type) { + if(!result && + !cert_stuff(data, octx->ssl_ctx, + ssl_cert, ssl_cert_blob, ssl_cert_type, + ssl_config->key, ssl_config->key_blob, + ssl_config->key_type, ssl_config->key_passwd)) + result = CURLE_SSL_CERTPROBLEM; + if(result) /* failf() is already done in cert_stuff() */ - return CURLE_SSL_CERTPROBLEM; - } - } - - ciphers = SSL_CONN_CONFIG(cipher_list); - if(!ciphers) - ciphers = (char *)DEFAULT_CIPHER_SELECTION; - if(!SSL_CTX_set_cipher_list(connssl->ctx, ciphers)) { - failf(data, "failed setting cipher list: %s", ciphers); - return CURLE_SSL_CIPHER; - } - infof(data, "Cipher selection: %s\n", ciphers); - -#ifdef USE_TLS_SRP - if(ssl_authtype == CURL_TLSAUTH_SRP) { - char * const ssl_username = SSL_SET_OPTION(username); - - infof(data, "Using TLS-SRP username: %s\n", ssl_username); - - if(!SSL_CTX_set_srp_username(connssl->ctx, ssl_username)) { - failf(data, "Unable to set SRP user name"); - return CURLE_BAD_FUNCTION_ARGUMENT; - } - if(!SSL_CTX_set_srp_password(connssl->ctx, SSL_SET_OPTION(password))) { - failf(data, "failed setting SRP password"); - return CURLE_BAD_FUNCTION_ARGUMENT; - } - if(!SSL_CONN_CONFIG(cipher_list)) { - infof(data, "Setting cipher list SRP\n"); - - if(!SSL_CTX_set_cipher_list(connssl->ctx, "SRP")) { - failf(data, "failed setting SRP cipher list"); - return CURLE_SSL_CIPHER; - } - } - } -#endif - - if(ssl_cafile || ssl_capath) { - /* tell SSL where to find CA certificates that are used to verify - the servers certificate. */ - if(!SSL_CTX_load_verify_locations(connssl->ctx, ssl_cafile, ssl_capath)) { - if(verifypeer) { - /* Fail if we insist on successfully verifying the server. */ - failf(data, "error setting certificate verify locations:\n" - " CAfile: %s\n 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:\n"); - } - } - else { - /* Everything is fine. */ - infof(data, "successfully set certificate verify locations:\n"); - } - infof(data, - " CAfile: %s\n" - " CApath: %s\n", - ssl_cafile ? ssl_cafile : "none", - ssl_capath ? ssl_capath : "none"); - } -#ifdef CURL_CA_FALLBACK - else if(verifypeer) { - /* verfying the peer without any CA certificates won't - work so use openssl's built in default as fallback */ - SSL_CTX_set_default_verify_paths(connssl->ctx); - } -#endif - - if(ssl_crlfile) { - /* tell SSL where to find CRL file that is used to check certificate - * revocation */ - lookup=X509_STORE_add_lookup(SSL_CTX_get_cert_store(connssl->ctx), - 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; - } - else { - /* Everything is fine. */ - infof(data, "successfully load CRL file:\n"); - X509_STORE_set_flags(SSL_CTX_get_cert_store(connssl->ctx), - X509_V_FLAG_CRL_CHECK|X509_V_FLAG_CRL_CHECK_ALL); - } - infof(data, " CRLfile: %s\n", ssl_crlfile); - } - - /* 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 which - gives us the same fix without as much of a performance hit (slight), so we - prefer that if available. - https://rt.openssl.org/Ticket/Display.html?id=3621&user=guest&pass=guest - */ -#if defined(X509_V_FLAG_TRUSTED_FIRST) && !defined(X509_V_FLAG_NO_ALT_CHAINS) - if(verifypeer) { - X509_STORE_set_flags(SSL_CTX_get_cert_store(connssl->ctx), - X509_V_FLAG_TRUSTED_FIRST); - } -#endif - - /* 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. */ - SSL_CTX_set_verify(connssl->ctx, - verifypeer ? SSL_VERIFY_PEER : SSL_VERIFY_NONE, NULL); - - /* give application a chance to interfere with SSL set up. */ - if(data->set.ssl.fsslctx) { - result = (*data->set.ssl.fsslctx)(data, connssl->ctx, - data->set.ssl.fsslctxp); - if(result) { - failf(data, "error signaled by ssl ctx callback"); return result; - } - } - - /* Lets make an SSL structure */ - if(connssl->handle) - SSL_free(connssl->handle); - connssl->handle = SSL_new(connssl->ctx); - if(!connssl->handle) { - failf(data, "SSL: couldn't create a context (handle)!"); - return CURLE_OUT_OF_MEMORY; - } - -#if (OPENSSL_VERSION_NUMBER >= 0x0090808fL) && !defined(OPENSSL_NO_TLSEXT) && \ - !defined(OPENSSL_NO_OCSP) - if(SSL_CONN_CONFIG(verifystatus)) - SSL_set_tlsext_status_type(connssl->handle, TLSEXT_STATUSTYPE_ocsp); -#endif - - SSL_set_connect_state(connssl->handle); - - connssl->server_cert = 0x0; -#ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME - if((0 == Curl_inet_pton(AF_INET, hostname, &addr)) && -#ifdef ENABLE_IPV6 - (0 == Curl_inet_pton(AF_INET6, hostname, &addr)) && -#endif - sni && - !SSL_set_tlsext_host_name(connssl->handle, hostname)) - infof(data, "WARNING: failed to configure server name indication (SNI) " - "TLS extension\n"); -#endif - - /* Check if there's a cached ID we can/should use here! */ - if(data->set.general_ssl.sessionid) { - void *ssl_sessionid = NULL; - - Curl_ssl_sessionid_lock(conn); - if(!Curl_ssl_getsessionid(conn, &ssl_sessionid, NULL, sockindex)) { - /* we got a session id, use it! */ - if(!SSL_set_session(connssl->handle, ssl_sessionid)) { - Curl_ssl_sessionid_unlock(conn); - failf(data, "SSL: SSL_set_session failed: %s", - ERR_error_string(ERR_get_error(), NULL)); - return CURLE_SSL_CONNECT_ERROR; - } - /* Informational message */ - infof(data, "SSL re-using session ID\n"); - } - Curl_ssl_sessionid_unlock(conn); - } - - if(conn->proxy_ssl[sockindex].use) { - BIO *const bio = BIO_new(BIO_f_ssl()); - DEBUGASSERT(ssl_connection_complete == conn->proxy_ssl[sockindex].state); - DEBUGASSERT(conn->proxy_ssl[sockindex].handle != NULL); - DEBUGASSERT(bio != NULL); - BIO_set_ssl(bio, conn->proxy_ssl[sockindex].handle, FALSE); - SSL_set_bio(connssl->handle, bio, bio); } - else if(!SSL_set_fd(connssl->handle, (int)sockfd)) { - /* pass the raw socket into the SSL layers */ - failf(data, "SSL: SSL_set_fd failed: %s", - ERR_error_string(ERR_get_error(), NULL)); - return CURLE_SSL_CONNECT_ERROR; - } - - connssl->connecting_state = ssl_connect_2; - - return CURLE_OK; -} - -static CURLcode ossl_connect_step2(struct connectdata *conn, int sockindex) -{ - struct Curl_easy *data = conn->data; - int err; - struct ssl_connect_data *connssl = &conn->ssl[sockindex]; - long * const certverifyresult = SSL_IS_PROXY() ? - &data->set.proxy_ssl.certverifyresult : &data->set.ssl.certverifyresult; - DEBUGASSERT(ssl_connect_2 == connssl->connecting_state - || ssl_connect_2_reading == connssl->connecting_state - || ssl_connect_2_writing == connssl->connecting_state); - - ERR_clear_error(); - - err = SSL_connect(connssl->handle); - - /* 1 is fine - 0 is "not successful but was shut down controlled" - <0 is "handshake was not successful, because a fatal error occurred" */ - if(1 != err) { - int detail = SSL_get_error(connssl->handle, err); - - if(SSL_ERROR_WANT_READ == detail) { - connssl->connecting_state = ssl_connect_2_reading; - return CURLE_OK; - } - else if(SSL_ERROR_WANT_WRITE == detail) { - connssl->connecting_state = ssl_connect_2_writing; - return CURLE_OK; - } - else { - /* untreated error */ - unsigned long errdetail; - char error_buffer[256]=""; /* OpenSSL documents that this must be at - least 256 bytes long. */ - 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 removes - 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)) { - result = CURLE_SSL_CACERT; - - lerr = SSL_get_verify_result(connssl->handle); - if(lerr != X509_V_OK) { - *certverifyresult = lerr; - snprintf(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"); - } - 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) { - const char * const hostname = SSL_IS_PROXY() ? - conn->http_proxy.host.name : conn->host.name; - const long int port = SSL_IS_PROXY() ? conn->port : conn->remote_port; - failf(data, "Unknown SSL protocol error in connection to %s:%ld ", - hostname, port); - return result; - } - - /* Could be a CERT problem */ - failf(data, "%s", error_buffer); - - return result; + ciphers = conn_config->cipher_list; + if(!ciphers && (peer->transport != TRNSPRT_QUIC)) + ciphers = DEFAULT_CIPHER_SELECTION; + if(ciphers) { + if(!SSL_CTX_set_cipher_list(octx->ssl_ctx, ciphers)) { + failf(data, "failed setting cipher list: %s", ciphers); + return CURLE_SSL_CIPHER; } + infof(data, "Cipher selection: %s", ciphers); } - else { - /* we have been connected fine, we're not waiting for anything else. */ - connssl->connecting_state = ssl_connect_3; - /* Informational message */ - infof(data, "SSL connection using %s / %s\n", - get_ssl_version_txt(connssl->handle), - SSL_get_cipher(connssl->handle)); - -#ifdef HAS_ALPN - /* Sets data and len to negotiated protocol, len is 0 if no protocol was - * negotiated - */ - if(conn->bits.tls_enable_alpn) { - const unsigned char *neg_protocol; - unsigned int len; - SSL_get0_alpn_selected(connssl->handle, &neg_protocol, &len); - if(len != 0) { - infof(data, "ALPN, server accepted to use %.*s\n", len, neg_protocol); - -#ifdef USE_NGHTTP2 - if(len == NGHTTP2_PROTO_VERSION_ID_LEN && - !memcmp(NGHTTP2_PROTO_VERSION_ID, neg_protocol, len)) { - conn->negnpn = CURL_HTTP_VERSION_2; - } - else -#endif - if(len == ALPN_HTTP_1_1_LENGTH && - !memcmp(ALPN_HTTP_1_1, neg_protocol, ALPN_HTTP_1_1_LENGTH)) { - conn->negnpn = CURL_HTTP_VERSION_1_1; - } +#ifdef HAVE_SSL_CTX_SET_CIPHERSUITES + { + const char *ciphers13 = conn_config->cipher_list13; + if(ciphers13) { + if(!SSL_CTX_set_ciphersuites(octx->ssl_ctx, ciphers13)) { + failf(data, "failed setting TLS 1.3 cipher suite: %s", ciphers13); + return CURLE_SSL_CIPHER; } - else - infof(data, "ALPN, server did not agree to a protocol\n"); + infof(data, "TLS 1.3 cipher selection: %s", ciphers13); } -#endif - - return CURLE_OK; } -} - -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 */ +#endif - return 0; -} +#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(octx->ssl_ctx, 1); +#endif -#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_FALSE - -static void pubkey_show(struct Curl_easy *data, - BIO *mem, - int num, - const char *type, - const char *name, -#ifdef HAVE_OPAQUE_RSA_DSA_DH - const +#ifdef HAVE_SSL_CTX_SET_EC_CURVES + { + const char *curves = conn_config->curves; + if(curves) { + if(!SSL_CTX_set1_curves_list(octx->ssl_ctx, curves)) { + failf(data, "failed setting curves list: '%s'", curves); + return CURLE_SSL_CIPHER; + } + } + } #endif - BIGNUM *bn) -{ - char *ptr; - char namebuf[32]; - snprintf(namebuf, sizeof(namebuf), "%s(%s)", type, name); +#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(bn) - BN_print(mem, bn); - push_certinfo(namebuf, num); -} + if(!SSL_CTX_set_srp_username(octx->ssl_ctx, ssl_username)) { + failf(data, "Unable to set SRP username"); + return CURLE_BAD_FUNCTION_ARGUMENT; + } + if(!SSL_CTX_set_srp_password(octx->ssl_ctx, ssl_password)) { + failf(data, "failed setting SRP password"); + return CURLE_BAD_FUNCTION_ARGUMENT; + } + if(!conn_config->cipher_list) { + infof(data, "Setting cipher list SRP"); -#ifdef HAVE_OPAQUE_RSA_DSA_DH -#define print_pubkey_BN(_type, _name, _num) \ - pubkey_show(data, mem, _num, #_type, #_name, _name) + if(!SSL_CTX_set_cipher_list(octx->ssl_ctx, "SRP")) { + failf(data, "failed setting SRP cipher list"); + return CURLE_SSL_CIPHER; + } + } + } +#endif -#else -#define print_pubkey_BN(_type, _name, _num) \ -do { \ - if(_type->_name) { \ - pubkey_show(data, mem, _num, #_type, #_name, _type->_name); \ - } \ -} WHILE_FALSE + /* 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(octx->ssl_ctx, + verifypeer ? SSL_VERIFY_PEER : SSL_VERIFY_NONE, NULL); + + /* Enable logging of secrets to the file specified in env SSLKEYLOGFILE. */ +#ifdef HAVE_KEYLOG_CALLBACK + if(Curl_tls_keylog_enabled()) { + SSL_CTX_set_keylog_callback(octx->ssl_ctx, ossl_keylog_callback); + } #endif -static int X509V3_ext(struct Curl_easy *data, - int certnum, - CONST_EXTS STACK_OF(X509_EXTENSION) *exts) -{ - int i; - size_t j; + if(cb_new_session) { + /* Enable the session cache because it is a prerequisite for the + * "new session" callback. Use the "external storage" mode to prevent + * OpenSSL from creating an internal session cache. + */ + SSL_CTX_set_session_cache_mode(octx->ssl_ctx, + SSL_SESS_CACHE_CLIENT | + SSL_SESS_CACHE_NO_INTERNAL); + SSL_CTX_sess_set_new_cb(octx->ssl_ctx, cb_new_session); + } - if((int)sk_X509_EXTENSION_num(exts) <= 0) - /* no extensions, bail out */ - return 1; + /* 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(!octx->x509_store_setup) { + result = Curl_ssl_setup_x509_store(cf, data, octx->ssl_ctx); + if(result) + return result; + octx->x509_store_setup = TRUE; + } + Curl_set_in_callback(data, true); + result = (*data->set.ssl.fsslctx)(data, octx->ssl_ctx, + data->set.ssl.fsslctxp); + Curl_set_in_callback(data, false); + if(result) { + failf(data, "error signaled by ssl ctx callback"); + return result; + } + } - 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 buf[512]; - char *ptr=buf; - char namebuf[128]; - BIO *bio_out = BIO_new(BIO_s_mem()); + /* Let's make an SSL structure */ + if(octx->ssl) + SSL_free(octx->ssl); + octx->ssl = SSL_new(octx->ssl_ctx); + if(!octx->ssl) { + failf(data, "SSL: could not create a context (handle)"); + return CURLE_OUT_OF_MEMORY; + } - if(!bio_out) - return 1; + SSL_set_app_data(octx->ssl, ssl_user_data); - obj = X509_EXTENSION_get_object(ext); +#if (OPENSSL_VERSION_NUMBER >= 0x0090808fL) && !defined(OPENSSL_NO_TLSEXT) && \ + !defined(OPENSSL_NO_OCSP) + if(conn_config->verifystatus) + SSL_set_tlsext_status_type(octx->ssl, TLSEXT_STATUSTYPE_ocsp); +#endif - asn1_object_dump(obj, namebuf, sizeof(namebuf)); +#if (defined(OPENSSL_IS_BORINGSSL) || defined(OPENSSL_IS_AWSLC)) && \ + defined(ALLOW_RENEG) + SSL_set_renegotiate_mode(octx->ssl, ssl_renegotiate_freely); +#endif - if(!X509V3_EXT_print(bio_out, ext, 0, 0)) - ASN1_STRING_print(bio_out, (ASN1_STRING *)X509_EXTENSION_get_data(ext)); + SSL_set_connect_state(octx->ssl); - BIO_get_mem_ptr(bio_out, &biomem); + octx->server_cert = 0x0; +#ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME + if(peer->sni) { + if(!SSL_set_tlsext_host_name(octx->ssl, peer->sni)) { + failf(data, "Failed set SNI"); + return CURLE_SSL_CONNECT_ERROR; + } + } - for(j = 0; j < (size_t)biomem->length; j++) { - const char *sep=""; - if(biomem->data[j] == '\n') { - sep=", "; - j++; /* skip the newline */ - }; - while((j<(size_t)biomem->length) && (biomem->data[j] == ' ')) - j++; - if(j<(size_t)biomem->length) - ptr+=snprintf(ptr, sizeof(buf)-(ptr-buf), "%s%c", sep, - biomem->data[j]); +#ifdef USE_ECH + if(ECH_ENABLED(data)) { + unsigned char *ech_config = NULL; + size_t ech_config_len = 0; + char *outername = data->set.str[STRING_ECH_PUBLIC]; + int trying_ech_now = 0; + + if(data->set.tls_ech & CURLECH_GREASE) { + infof(data, "ECH: will GREASE ClientHello"); +# ifdef OPENSSL_IS_BORINGSSL + SSL_set_enable_ech_grease(octx->ssl, 1); +# else + SSL_set_options(octx->ssl, SSL_OP_ECH_GREASE); +# endif } + else if(data->set.tls_ech & CURLECH_CLA_CFG) { +# ifdef OPENSSL_IS_BORINGSSL + /* have to do base64 decode here for boring */ + const char *b64 = data->set.str[STRING_ECH_CONFIG]; - Curl_ssl_push_certinfo(data, certnum, namebuf, buf); + if(!b64) { + infof(data, "ECH: ECHConfig from command line empty"); + return CURLE_SSL_CONNECT_ERROR; + } + ech_config_len = 2 * strlen(b64); + result = Curl_base64_decode(b64, &ech_config, &ech_config_len); + if(result || !ech_config) { + infof(data, "ECH: cannot base64 decode ECHConfig from command line"); + if(data->set.tls_ech & CURLECH_HARD) + return result; + } + if(SSL_set1_ech_config_list(octx->ssl, ech_config, + ech_config_len) != 1) { + infof(data, "ECH: SSL_ECH_set1_echconfig failed"); + if(data->set.tls_ech & CURLECH_HARD) { + free(ech_config); + return CURLE_SSL_CONNECT_ERROR; + } + } + free(ech_config); + trying_ech_now = 1; +# else + ech_config = (unsigned char *) data->set.str[STRING_ECH_CONFIG]; + if(!ech_config) { + infof(data, "ECH: ECHConfig from command line empty"); + return CURLE_SSL_CONNECT_ERROR; + } + ech_config_len = strlen(data->set.str[STRING_ECH_CONFIG]); + if(SSL_ech_set1_echconfig(octx->ssl, ech_config, ech_config_len) != 1) { + infof(data, "ECH: SSL_ECH_set1_echconfig failed"); + if(data->set.tls_ech & CURLECH_HARD) + return CURLE_SSL_CONNECT_ERROR; + } + else + trying_ech_now = 1; +# endif + infof(data, "ECH: ECHConfig from command line"); + } + else { + struct Curl_dns_entry *dns = NULL; + + if(peer->hostname) + dns = Curl_fetch_addr(data, peer->hostname, peer->port); + if(!dns) { + infof(data, "ECH: requested but no DNS info available"); + if(data->set.tls_ech & CURLECH_HARD) + return CURLE_SSL_CONNECT_ERROR; + } + else { + struct Curl_https_rrinfo *rinfo = NULL; + + rinfo = dns->hinfo; + if(rinfo && rinfo->echconfiglist) { + unsigned char *ecl = rinfo->echconfiglist; + size_t elen = rinfo->echconfiglist_len; + + infof(data, "ECH: ECHConfig from DoH HTTPS RR"); +# ifndef OPENSSL_IS_BORINGSSL + if(SSL_ech_set1_echconfig(octx->ssl, ecl, elen) != 1) { + infof(data, "ECH: SSL_ECH_set1_echconfig failed"); + if(data->set.tls_ech & CURLECH_HARD) + return CURLE_SSL_CONNECT_ERROR; + } +# else + if(SSL_set1_ech_config_list(octx->ssl, ecl, elen) != 1) { + infof(data, "ECH: SSL_set1_ech_config_list failed (boring)"); + if(data->set.tls_ech & CURLECH_HARD) + return CURLE_SSL_CONNECT_ERROR; + } +# endif + else { + trying_ech_now = 1; + infof(data, "ECH: imported ECHConfigList of length %zu", elen); + } + } + else { + infof(data, "ECH: requested but no ECHConfig available"); + if(data->set.tls_ech & CURLECH_HARD) + return CURLE_SSL_CONNECT_ERROR; + } + Curl_resolv_unlink(data, &dns); + } + } +# ifdef OPENSSL_IS_BORINGSSL + if(trying_ech_now && outername) { + infof(data, "ECH: setting public_name not supported with BoringSSL"); + return CURLE_SSL_CONNECT_ERROR; + } +# else + if(trying_ech_now && outername) { + infof(data, "ECH: inner: '%s', outer: '%s'", + peer->hostname ? peer->hostname : "NULL", outername); + result = SSL_ech_set_server_names(octx->ssl, + peer->hostname, outername, + 0 /* do send outer */); + if(result != 1) { + infof(data, "ECH: rv failed to set server name(s) %d [ERROR]", result); + return CURLE_SSL_CONNECT_ERROR; + } + } +# endif /* not BORING */ + if(trying_ech_now + && SSL_set_min_proto_version(octx->ssl, TLS1_3_VERSION) != 1) { + infof(data, "ECH: cannot force TLSv1.3 [ERROR]"); + return CURLE_SSL_CONNECT_ERROR; + } + } +#endif /* USE_ECH */ - BIO_free(bio_out); +#endif + octx->reused_session = FALSE; + if(ssl_config->primary.cache_session && transport == TRNSPRT_TCP) { + Curl_ssl_sessionid_lock(data); + if(!Curl_ssl_getsessionid(cf, data, peer, (void **)&der_sessionid, + &der_sessionid_size)) { + /* we got a session id, use it! */ + ssl_session = d2i_SSL_SESSION(NULL, &der_sessionid, + (long)der_sessionid_size); + if(ssl_session) { + if(!SSL_set_session(octx->ssl, ssl_session)) { + Curl_ssl_sessionid_unlock(data); + SSL_SESSION_free(ssl_session); + failf(data, "SSL: SSL_set_session failed: %s", + ossl_strerror(ERR_get_error(), error_buffer, + sizeof(error_buffer))); + return CURLE_SSL_CONNECT_ERROR; + } + SSL_SESSION_free(ssl_session); + /* Informational message */ + infof(data, "SSL reusing session ID"); + octx->reused_session = TRUE; + } + else { + Curl_ssl_sessionid_unlock(data); + return CURLE_SSL_CONNECT_ERROR; + } + } + Curl_ssl_sessionid_unlock(data); } - return 0; /* all is fine */ -} -static CURLcode get_cert_chain(struct connectdata *conn, - struct ssl_connect_data *connssl) + return CURLE_OK; +} +static CURLcode ossl_connect_step1(struct Curl_cfilter *cf, + struct Curl_easy *data) { + struct ssl_connect_data *connssl = cf->ctx; + struct ossl_ctx *octx = (struct ossl_ctx *)connssl->backend; + struct alpn_proto_buf proto; + BIO *bio; CURLcode result; - STACK_OF(X509) *sk; - int i; - struct Curl_easy *data = conn->data; - int numcerts; - BIO *mem; - sk = SSL_get_peer_cert_chain(connssl->handle); - if(!sk) { - return CURLE_OUT_OF_MEMORY; + DEBUGASSERT(ssl_connect_1 == connssl->connecting_state); + DEBUGASSERT(octx); + memset(&proto, 0, sizeof(proto)); +#ifdef HAS_ALPN + if(connssl->alpn) { + result = Curl_alpn_to_proto_buf(&proto, connssl->alpn); + if(result) { + failf(data, "Error determining ALPN"); + return CURLE_SSL_CONNECT_ERROR; + } } +#endif - numcerts = sk_X509_num(sk); - - result = Curl_ssl_init_certinfo(data, numcerts); - if(result) { + result = Curl_ossl_ctx_init(octx, cf, data, &connssl->peer, TRNSPRT_TCP, + proto.data, proto.len, NULL, NULL, + ossl_new_session_cb, cf); + if(result) return result; - } - mem = BIO_new(BIO_s_mem()); + octx->bio_method = ossl_bio_cf_method_create(); + if(!octx->bio_method) + return CURLE_OUT_OF_MEMORY; + bio = BIO_new(octx->bio_method); + if(!bio) + return CURLE_OUT_OF_MEMORY; - for(i = 0; i < numcerts; i++) { - ASN1_INTEGER *num; - X509 *x = sk_X509_value(sk, i); - EVP_PKEY *pubkey=NULL; - int j; - char *ptr; - CONST_ASN1_BIT_STRING ASN1_BIT_STRING *psig = NULL; + 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(octx->ssl, bio); + SSL_set0_wbio(octx->ssl, bio); +#else + SSL_set_bio(octx->ssl, bio, bio); +#endif - X509_NAME_print_ex(mem, X509_get_subject_name(x), 0, XN_FLAG_ONELINE); - push_certinfo("Subject", i); +#ifdef HAS_ALPN + if(connssl->alpn) { + Curl_alpn_to_proto_str(&proto, connssl->alpn); + infof(data, VTLS_INFOF_ALPN_OFFER_1STR, proto.data); + } +#endif + connssl->connecting_state = ssl_connect_2; + return CURLE_OK; +} - X509_NAME_print_ex(mem, X509_get_issuer_name(x), 0, XN_FLAG_ONELINE); - push_certinfo("Issuer", i); +#ifdef USE_ECH +/* If we have retry configs, then trace those out */ +static void ossl_trace_ech_retry_configs(struct Curl_easy *data, SSL* ssl, + int reason) +{ + CURLcode result = CURLE_OK; + size_t rcl = 0; + int rv = 1; +# ifndef OPENSSL_IS_BORINGSSL + char *inner = NULL; + unsigned char *rcs = NULL; + char *outer = NULL; +# else + const char *inner = NULL; + const uint8_t *rcs = NULL; + const char *outer = NULL; + size_t out_name_len = 0; + int servername_type = 0; +# endif + + /* nothing to trace if not doing ECH */ + if(!ECH_ENABLED(data)) + return; +# ifndef OPENSSL_IS_BORINGSSL + rv = SSL_ech_get_retry_config(ssl, &rcs, &rcl); +# else + SSL_get0_ech_retry_configs(ssl, &rcs, &rcl); + rv = (int)rcl; +# endif + + if(rv && rcs) { +# define HEXSTR_MAX 800 + char *b64str = NULL; + size_t blen = 0; + + result = Curl_base64_encode((const char *)rcs, rcl, + &b64str, &blen); + if(!result && b64str) + infof(data, "ECH: retry_configs %s", b64str); + free(b64str); +# ifndef OPENSSL_IS_BORINGSSL + rv = SSL_ech_get_status(ssl, &inner, &outer); + infof(data, "ECH: retry_configs for %s from %s, %d %d", + inner ? inner : "NULL", outer ? outer : "NULL", reason, rv); +#else + rv = SSL_ech_accepted(ssl); + servername_type = SSL_get_servername_type(ssl); + inner = SSL_get_servername(ssl, servername_type); + SSL_get0_ech_name_override(ssl, &outer, &out_name_len); + /* TODO: get the inner from boring */ + infof(data, "ECH: retry_configs for %s from %s, %d %d", + inner ? inner : "NULL", outer ? outer : "NULL", reason, rv); +#endif + } + else + infof(data, "ECH: no retry_configs (rv = %d)", rv); +# ifndef OPENSSL_IS_BORINGSSL + OPENSSL_free((void *)rcs); +# endif + return; +} - BIO_printf(mem, "%lx", X509_get_version(x)); - push_certinfo("Version", i); +#endif - 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); +static CURLcode ossl_connect_step2(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + int err; + struct ssl_connect_data *connssl = cf->ctx; + struct ossl_ctx *octx = (struct ossl_ctx *)connssl->backend; + struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data); + DEBUGASSERT(ssl_connect_2 == connssl->connecting_state); + DEBUGASSERT(octx); -#if defined(HAVE_X509_GET0_SIGNATURE) && defined(HAVE_X509_GET0_EXTENSIONS) - { - const X509_ALGOR *palg = NULL; - ASN1_STRING *a = ASN1_STRING_new(); - if(a) { - X509_get0_signature(&psig, &palg, x); - X509_signature_print(mem, palg, a); - ASN1_STRING_free(a); - - if(palg) { - i2a_ASN1_OBJECT(mem, palg->algorithm); - 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; + connssl->io_need = CURL_SSL_IO_NEED_NONE; + ERR_clear_error(); - i2a_ASN1_OBJECT(mem, cinf->signature->algorithm); - push_certinfo("Signature Algorithm", i); + err = SSL_connect(octx->ssl); + + if(!octx->x509_store_setup) { + /* After having send off the ClientHello, we prepare the x509 + * store to verify the coming certificate from the server */ + CURLcode result = Curl_ssl_setup_x509_store(cf, data, octx->ssl_ctx); + if(result) + return result; + octx->x509_store_setup = TRUE; + } - i2a_ASN1_OBJECT(mem, cinf->key->algor->algorithm); - push_certinfo("Public Key Algorithm", i); +#ifndef HAVE_KEYLOG_CALLBACK + /* If key logging is enabled, wait for the handshake to complete and then + * proceed with logging secrets (for TLS 1.2 or older). + */ + if(Curl_tls_keylog_enabled() && !octx->keylog_done) + ossl_log_tls12_secret(octx->ssl, &octx->keylog_done); +#endif - X509V3_ext(data, i, cinf->extensions); + /* 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(octx->ssl, err); + CURL_TRC_CF(data, cf, "SSL_connect() -> err=%d, detail=%d", err, detail); - psig = x->signature; + if(SSL_ERROR_WANT_READ == detail) { + CURL_TRC_CF(data, cf, "SSL_connect() -> want recv"); + connssl->io_need = CURL_SSL_IO_NEED_RECV; + return CURLE_OK; + } + if(SSL_ERROR_WANT_WRITE == detail) { + CURL_TRC_CF(data, cf, "SSL_connect() -> want send"); + connssl->io_need = CURL_SSL_IO_NEED_SEND; + return CURLE_OK; + } +#ifdef SSL_ERROR_WANT_ASYNC + if(SSL_ERROR_WANT_ASYNC == detail) { + CURL_TRC_CF(data, cf, "SSL_connect() -> want async"); + connssl->io_need = CURL_SSL_IO_NEED_RECV; + connssl->connecting_state = ssl_connect_2; + return CURLE_OK; + } +#endif +#ifdef SSL_ERROR_WANT_RETRY_VERIFY + if(SSL_ERROR_WANT_RETRY_VERIFY == detail) { + CURL_TRC_CF(data, cf, "SSL_connect() -> want retry_verify"); + connssl->io_need = CURL_SSL_IO_NEED_RECV; + connssl->connecting_state = ssl_connect_2; + return CURLE_OK; } #endif + else { + /* untreated error */ + sslerr_t errdetail; + char error_buffer[256]=""; + CURLcode result; + long lerr; + int lib; + int reason; - ASN1_TIME_print(mem, X509_get0_notBefore(x)); - push_certinfo("Start date", i); + /* the connection failed, we are not waiting for anything else. */ + connssl->connecting_state = ssl_connect_2; - ASN1_TIME_print(mem, X509_get0_notAfter(x)); - push_certinfo("Expire date", i); + /* Get the earliest error code from the thread's error queue and remove + the entry. */ + errdetail = ERR_get_error(); - pubkey = X509_get_pubkey(x); - if(!pubkey) - infof(data, " Unable to load public key\n"); - else { - int pktype; -#ifdef HAVE_OPAQUE_EVP_PKEY - pktype = EVP_PKEY_id(pubkey); -#else - pktype = pubkey->type; -#endif - switch(pktype) { - case EVP_PKEY_RSA: - { - RSA *rsa; -#ifdef HAVE_OPAQUE_EVP_PKEY - rsa = EVP_PKEY_get0_RSA(pubkey); -#else - rsa = pubkey->pkey.rsa; -#endif + /* Extract which lib and reason */ + lib = ERR_GET_LIB(errdetail); + reason = ERR_GET_REASON(errdetail); -#ifdef HAVE_OPAQUE_RSA_DSA_DH - { - const BIGNUM *n; - const BIGNUM *e; - const BIGNUM *d; - const BIGNUM *p; - const BIGNUM *q; - const BIGNUM *dmp1; - const BIGNUM *dmq1; - const BIGNUM *iqmp; - - RSA_get0_key(rsa, &n, &e, &d); - RSA_get0_factors(rsa, &p, &q); - RSA_get0_crt_params(rsa, &dmp1, &dmq1, &iqmp); - BN_print(mem, n); - push_certinfo("RSA Public Key", i); - print_pubkey_BN(rsa, n, i); - print_pubkey_BN(rsa, e, i); - print_pubkey_BN(rsa, d, i); - print_pubkey_BN(rsa, p, i); - print_pubkey_BN(rsa, q, i); - print_pubkey_BN(rsa, dmp1, i); - print_pubkey_BN(rsa, dmq1, i); - print_pubkey_BN(rsa, iqmp, i); - } -#else - BIO_printf(mem, "%d", BN_num_bits(rsa->n)); - push_certinfo("RSA Public Key", i); - print_pubkey_BN(rsa, n, i); - print_pubkey_BN(rsa, e, i); - print_pubkey_BN(rsa, d, i); - print_pubkey_BN(rsa, p, i); - print_pubkey_BN(rsa, q, i); - print_pubkey_BN(rsa, dmp1, i); - print_pubkey_BN(rsa, dmq1, i); - print_pubkey_BN(rsa, iqmp, i); -#endif + if((lib == ERR_LIB_SSL) && + ((reason == SSL_R_CERTIFICATE_VERIFY_FAILED) || + (reason == SSL_R_SSLV3_ALERT_CERTIFICATE_EXPIRED))) { + result = CURLE_PEER_FAILED_VERIFICATION; - break; + lerr = SSL_get_verify_result(octx->ssl); + 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 { + failf(data, "%s", "SSL certificate verification failed"); + return result; + } + } +#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)); } - case EVP_PKEY_DSA: - { - DSA *dsa; -#ifdef HAVE_OPAQUE_EVP_PKEY - dsa = EVP_PKEY_get0_DSA(pubkey); -#else - dsa = pubkey->pkey.dsa; #endif -#ifdef HAVE_OPAQUE_RSA_DSA_DH - { - const BIGNUM *p; - const BIGNUM *q; - const BIGNUM *g; - const BIGNUM *priv_key; - const BIGNUM *pub_key; +#ifdef USE_ECH + else if((lib == ERR_LIB_SSL) && +# ifndef OPENSSL_IS_BORINGSSL + (reason == SSL_R_ECH_REQUIRED)) { +# else + (reason == SSL_R_ECH_REJECTED)) { +# endif + + /* trace retry_configs if we got some */ + ossl_trace_ech_retry_configs(data, octx->ssl, reason); + + result = CURLE_ECH_REQUIRED; + ossl_strerror(errdetail, error_buffer, sizeof(error_buffer)); + } +#endif + else { + result = CURLE_SSL_CONNECT_ERROR; + ossl_strerror(errdetail, error_buffer, sizeof(error_buffer)); + } - DSA_get0_pqg(dsa, &p, &q, &g); - DSA_get0_key(dsa, &pub_key, &priv_key); + /* detail is already set to the SSL error above */ - print_pubkey_BN(dsa, p, i); - print_pubkey_BN(dsa, q, i); - print_pubkey_BN(dsa, g, i); - print_pubkey_BN(dsa, priv_key, i); - print_pubkey_BN(dsa, pub_key, i); - } -#else - print_pubkey_BN(dsa, p, i); - print_pubkey_BN(dsa, q, i); - print_pubkey_BN(dsa, g, i); - print_pubkey_BN(dsa, priv_key, i); - print_pubkey_BN(dsa, pub_key, i); -#endif - break; + /* If we e.g. use SSLv2 request-method and the server does not 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->peer.port); + return result; } - case EVP_PKEY_DH: - { - DH *dh; -#ifdef HAVE_OPAQUE_EVP_PKEY - dh = EVP_PKEY_get0_DH(pubkey); + + /* 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 are not waiting for anything else. */ + connssl->connecting_state = ssl_connect_3; + +#if (OPENSSL_VERSION_NUMBER >= 0x30000000L) + SSL_get_peer_signature_type_nid(octx->ssl, &psigtype_nid); +#if (OPENSSL_VERSION_NUMBER >= 0x30200000L) + negotiated_group_name = SSL_get0_group_name(octx->ssl); #else - dh = pubkey->pkey.dh; + negotiated_group_name = + OBJ_nid2sn(SSL_get_negotiated_group(octx->ssl) & 0x0000FFFF); #endif -#ifdef HAVE_OPAQUE_RSA_DSA_DH - { - const BIGNUM *p; - const BIGNUM *q; - const BIGNUM *g; - const BIGNUM *priv_key; - const BIGNUM *pub_key; - DH_get0_pqg(dh, &p, &q, &g); - DH_get0_key(dh, &pub_key, &priv_key); - print_pubkey_BN(dh, p, i); - print_pubkey_BN(dh, q, i); - print_pubkey_BN(dh, g, i); - print_pubkey_BN(dh, priv_key, i); - print_pubkey_BN(dh, pub_key, i); - } -#else - print_pubkey_BN(dh, p, i); - print_pubkey_BN(dh, g, i); - print_pubkey_BN(dh, priv_key, i); - print_pubkey_BN(dh, pub_key, i); #endif + + /* Informational message */ + infof(data, "SSL connection using %s / %s / %s / %s", + SSL_get_version(octx->ssl), + SSL_get_cipher(octx->ssl), + negotiated_group_name ? negotiated_group_name : "[blank]", + OBJ_nid2sn(psigtype_nid)); + +#ifdef USE_ECH +# ifndef OPENSSL_IS_BORINGSSL + if(ECH_ENABLED(data)) { + char *inner = NULL, *outer = NULL; + const char *status = NULL; + int rv; + + rv = SSL_ech_get_status(octx->ssl, &inner, &outer); + switch(rv) { + case SSL_ECH_STATUS_SUCCESS: + status = "succeeded"; break; - } -#if 0 - case EVP_PKEY_EC: /* symbol not present in OpenSSL 0.9.6 */ - /* left TODO */ + case SSL_ECH_STATUS_GREASE_ECH: + status = "sent GREASE, got retry-configs"; break; -#endif + case SSL_ECH_STATUS_GREASE: + status = "sent GREASE"; + break; + case SSL_ECH_STATUS_NOT_TRIED: + status = "not attempted"; + break; + case SSL_ECH_STATUS_NOT_CONFIGURED: + status = "not configured"; + break; + case SSL_ECH_STATUS_BACKEND: + status = "backend (unexpected)"; + break; + case SSL_ECH_STATUS_FAILED: + status = "failed"; + break; + case SSL_ECH_STATUS_BAD_CALL: + status = "bad call (unexpected)"; + break; + case SSL_ECH_STATUS_BAD_NAME: + status = "bad name (unexpected)"; + break; + default: + status = "unexpected status"; + infof(data, "ECH: unexpected status %d",rv); } - EVP_PKEY_free(pubkey); - } + infof(data, "ECH: result: status is %s, inner is %s, outer is %s", + (status ? status : "NULL"), + (inner ? inner : "NULL"), + (outer ? outer : "NULL")); + OPENSSL_free(inner); + OPENSSL_free(outer); + if(rv == SSL_ECH_STATUS_GREASE_ECH) { + /* trace retry_configs if we got some */ + ossl_trace_ech_retry_configs(data, octx->ssl, 0); + } + if(rv != SSL_ECH_STATUS_SUCCESS + && data->set.tls_ech & CURLECH_HARD) { + infof(data, "ECH: ech-hard failed"); + return CURLE_SSL_CONNECT_ERROR; + } + } + else { + infof(data, "ECH: result: status is not attempted"); + } +# endif /* BORING */ +#endif /* USE_ECH */ - if(psig) { - for(j = 0; j < psig->length; j++) - BIO_printf(mem, "%02x:", psig->data[j]); - push_certinfo("Signature", i); +#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(octx->ssl, &neg_protocol, &len); + + return Curl_alpn_set_negotiated(cf, data, neg_protocol, len); } +#endif - PEM_write_bio_X509(mem, x); - push_certinfo("Cert", i); + return CURLE_OK; } - - BIO_free(mem); - - return CURLE_OK; } /* * Heavily modified from: * https://www.owasp.org/index.php/Certificate_and_Public_Key_Pinning#OpenSSL */ -static CURLcode pkp_pin_peer_pubkey(struct Curl_easy *data, X509* cert, - const char *pinnedpubkey) +static CURLcode ossl_pkp_pin_peer_pubkey(struct Curl_easy *data, X509* cert, + const char *pinnedpubkey) { /* Scratch */ int len1 = 0, len2 = 0; @@ -2691,7 +4399,7 @@ static CURLcode pkp_pin_peer_pubkey(struct Curl_easy *data, X509* cert, /* Result is returned to caller */ CURLcode result = CURLE_SSL_PINNEDPUBKEYNOTMATCH; - /* if a path wasn't specified, don't pin */ + /* if a path was not specified, do not pin */ if(!pinnedpubkey) return CURLE_OK; @@ -2703,22 +4411,21 @@ static CURLcode pkp_pin_peer_pubkey(struct Curl_easy *data, X509* cert, /* Thanks to Viktor Dukhovni on the OpenSSL mailing list */ /* https://groups.google.com/group/mailing.openssl.users/browse_thread - /thread/d61858dae102c6c7 */ + /thread/d61858dae102c6c7 */ len1 = i2d_X509_PUBKEY(X509_get_X509_PUBKEY(cert), NULL); if(len1 < 1) break; /* failed */ - /* https://www.openssl.org/docs/crypto/buffer.html */ buff1 = temp = malloc(len1); if(!buff1) break; /* failed */ - /* https://www.openssl.org/docs/crypto/d2i_X509.html */ + /* https://docs.openssl.org/master/man3/d2i_X509/ */ 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 + * sized the buffer. it is pretty weak since they should always be the * same. But it gives us something to test. */ if((len1 != len2) || !temp || ((temp - buff1) != len1)) @@ -2730,134 +4437,249 @@ static CURLcode pkp_pin_peer_pubkey(struct Curl_easy *data, X509* cert, result = Curl_pin_peer_pubkey(data, pinnedpubkey, buff1, len1); } while(0); - /* https://www.openssl.org/docs/crypto/buffer.html */ if(buff1) free(buff1); return result; } -/* - * 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 connectdata *conn, - struct ssl_connect_data *connssl, - bool strict) +#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 + +#define MAX_CERT_NAME_LENGTH 2048 + +CURLcode Curl_oss_check_peer_cert(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct ossl_ctx *octx, + struct ssl_peer *peer) { + struct connectdata *conn = cf->conn; + struct ssl_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, len; - struct Curl_easy *data = conn->data; + long lerr; X509 *issuer; - FILE *fp; - char *buffer = data->state.buffer; + BIO *fp = NULL; + char error_buffer[256]=""; const char *ptr; - long * const certverifyresult = SSL_IS_PROXY() ? - &data->set.proxy_ssl.certverifyresult : &data->set.ssl.certverifyresult; BIO *mem = BIO_new(BIO_s_mem()); + bool strict = (conn_config->verifypeer || conn_config->verifyhost); + struct dynbuf dname; + + DEBUGASSERT(octx); + + Curl_dyn_init(&dname, MAX_CERT_NAME_LENGTH); + + 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) - /* we've been asked to gather certificate info! */ - (void)get_cert_chain(conn, connssl); + /* asked to gather certificate info */ + (void)ossl_certchain(data, octx->ssl); - connssl->server_cert = SSL_get_peer_certificate(connssl->handle); - if(!connssl->server_cert) { + octx->server_cert = SSL_get1_peer_certificate(octx->ssl); + if(!octx->server_cert) { + BIO_free(mem); if(!strict) return CURLE_OK; - failf(data, "SSL: couldn't get peer certificate!"); + failf(data, "SSL: could not get peer certificate"); return CURLE_PEER_FAILED_VERIFICATION; } - infof(data, "%s certificate:\n", SSL_IS_PROXY() ? "Proxy" : "Server"); + infof(data, "%s certificate:", + Curl_ssl_cf_is_proxy(cf) ? "Proxy" : "Server"); - rc = x509_name_oneline(X509_get_subject_name(connssl->server_cert), - buffer, BUFSIZE); - infof(data, " subject: %s\n", rc?"[NONE]":buffer); + result = x509_name_oneline(X509_get_subject_name(octx->server_cert), + &dname); + infof(data, " subject: %s", result ? "[NONE]" : Curl_dyn_ptr(&dname)); - ASN1_TIME_print(mem, X509_get0_notBefore(connssl->server_cert)); - len = BIO_get_mem_data(mem, (char **) &ptr); - infof(data, " start date: %.*s\n", len, ptr); - rc = BIO_reset(mem); - - ASN1_TIME_print(mem, X509_get0_notAfter(connssl->server_cert)); - len = BIO_get_mem_data(mem, (char **) &ptr); - infof(data, " expire date: %.*s\n", len, ptr); - rc = BIO_reset(mem); +#ifndef CURL_DISABLE_VERBOSE_STRINGS + { + long len; + ASN1_TIME_print(mem, X509_get0_notBefore(octx->server_cert)); + len = BIO_get_mem_data(mem, (char **) &ptr); + infof(data, " start date: %.*s", (int)len, ptr); + (void)BIO_reset(mem); + + ASN1_TIME_print(mem, X509_get0_notAfter(octx->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(SSL_CONN_CONFIG(verifyhost)) { - result = verifyhost(conn, connssl->server_cert); + if(conn_config->verifyhost) { + result = ossl_verifyhost(data, conn, peer, octx->server_cert); if(result) { - X509_free(connssl->server_cert); - connssl->server_cert = NULL; + X509_free(octx->server_cert); + octx->server_cert = NULL; + Curl_dyn_free(&dname); return result; } } - rc = x509_name_oneline(X509_get_issuer_name(connssl->server_cert), - buffer, BUFSIZE); - if(rc) { + result = x509_name_oneline(X509_get_issuer_name(octx->server_cert), + &dname); + if(result) { if(strict) - failf(data, "SSL: couldn't get X509-issuer name!"); - result = CURLE_SSL_CONNECT_ERROR; + failf(data, "SSL: could not get X509-issuer name"); + result = CURLE_PEER_FAILED_VERIFICATION; } else { - infof(data, " issuer: %s\n", buffer); + infof(data, " issuer: %s", Curl_dyn_ptr(&dname)); + Curl_dyn_free(&dname); /* We could do all sorts of certificate verification stuff here before deallocating the certificate. */ /* e.g. match issuer name with provided issuer certificate */ - if(SSL_SET_OPTION(issuercert)) { - fp = fopen(SSL_SET_OPTION(issuercert), FOPEN_READTEXT); - if(!fp) { - if(strict) - failf(data, "SSL: Unable to open issuer cert (%s)", - SSL_SET_OPTION(issuercert)); - X509_free(connssl->server_cert); - connssl->server_cert = NULL; - return CURLE_SSL_ISSUER_ERROR; + 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(octx->server_cert); + octx->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(octx->server_cert); + octx->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(octx->server_cert); + octx->server_cert = NULL; + return CURLE_SSL_ISSUER_ERROR; + } } - issuer = PEM_read_X509(fp, NULL, ZERO_NULL, NULL); + issuer = PEM_read_bio_X509(fp, NULL, ZERO_NULL, NULL); if(!issuer) { if(strict) failf(data, "SSL: Unable to read issuer cert (%s)", - SSL_SET_OPTION(issuercert)); - X509_free(connssl->server_cert); + conn_config->issuercert); + BIO_free(fp); X509_free(issuer); - fclose(fp); + X509_free(octx->server_cert); + octx->server_cert = NULL; return CURLE_SSL_ISSUER_ERROR; } - fclose(fp); - - if(X509_check_issued(issuer, connssl->server_cert) != X509_V_OK) { + if(X509_check_issued(issuer, octx->server_cert) != X509_V_OK) { if(strict) failf(data, "SSL: Certificate issuer check failed (%s)", - SSL_SET_OPTION(issuercert)); - X509_free(connssl->server_cert); + conn_config->issuercert); + BIO_free(fp); X509_free(issuer); - connssl->server_cert = NULL; + X509_free(octx->server_cert); + octx->server_cert = NULL; return CURLE_SSL_ISSUER_ERROR; } - infof(data, " SSL certificate issuer check ok (%s)\n", - SSL_SET_OPTION(issuercert)); + infof(data, " SSL certificate issuer check ok (%s)", + conn_config->issuercert); + BIO_free(fp); X509_free(issuer); } - lerr = *certverifyresult = SSL_get_verify_result(connssl->handle); - - if(*certverifyresult != X509_V_OK) { - if(SSL_CONN_CONFIG(verifypeer)) { + lerr = SSL_get_verify_result(octx->ssl); + 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) @@ -2867,124 +4689,99 @@ static CURLcode servercert(struct connectdata *conn, } else infof(data, " SSL certificate verify result: %s (%ld)," - " continuing anyway.\n", + " continuing anyway.", X509_verify_cert_error_string(lerr), lerr); } else - infof(data, " SSL certificate verify ok.\n"); + infof(data, " SSL certificate verify ok."); } + infof_certstack(data, octx->ssl); #if (OPENSSL_VERSION_NUMBER >= 0x0090808fL) && !defined(OPENSSL_NO_TLSEXT) && \ - !defined(OPENSSL_NO_OCSP) - if(SSL_CONN_CONFIG(verifystatus)) { - result = verifystatus(conn, connssl); + !defined(OPENSSL_NO_OCSP) + if(conn_config->verifystatus && !octx->reused_session) { + /* do not do this after Session ID reuse */ + result = verifystatus(cf, data, octx); if(result) { - X509_free(connssl->server_cert); - connssl->server_cert = NULL; + /* when verifystatus failed, remove the session id from the cache again + if present */ + if(!Curl_ssl_cf_is_proxy(cf)) { + void *old_ssl_sessionid = NULL; + bool incache; + Curl_ssl_sessionid_lock(data); + incache = !(Curl_ssl_getsessionid(cf, data, peer, + &old_ssl_sessionid, NULL)); + if(incache) { + infof(data, "Remove session ID again from cache"); + Curl_ssl_delsessionid(data, old_ssl_sessionid); + } + Curl_ssl_sessionid_unlock(data); + } + + X509_free(octx->server_cert); + octx->server_cert = NULL; return result; } } #endif if(!strict) - /* when not strict, we don't bother about the verify cert problems */ + /* when not strict, we do not bother about the verify cert problems */ result = CURLE_OK; - ptr = SSL_IS_PROXY() ? data->set.str[STRING_SSL_PINNEDPUBLICKEY_PROXY] : - data->set.str[STRING_SSL_PINNEDPUBLICKEY_ORIG]; +#ifndef CURL_DISABLE_PROXY + ptr = Curl_ssl_cf_is_proxy(cf) ? + data->set.str[STRING_SSL_PINNEDPUBLICKEY_PROXY] : + data->set.str[STRING_SSL_PINNEDPUBLICKEY]; +#else + ptr = data->set.str[STRING_SSL_PINNEDPUBLICKEY]; +#endif if(!result && ptr) { - result = pkp_pin_peer_pubkey(data, connssl->server_cert, ptr); + result = ossl_pkp_pin_peer_pubkey(data, octx->server_cert, ptr); if(result) - failf(data, "SSL: public key does not match pinned public key!"); + failf(data, "SSL: public key does not match pinned public key"); } - X509_free(connssl->server_cert); - connssl->server_cert = NULL; - connssl->connecting_state = ssl_connect_done; + X509_free(octx->server_cert); + octx->server_cert = NULL; return result; } -static CURLcode ossl_connect_step3(struct connectdata *conn, int sockindex) +static CURLcode ossl_connect_step3(struct Curl_cfilter *cf, + struct Curl_easy *data) { CURLcode result = CURLE_OK; - struct Curl_easy *data = conn->data; - struct ssl_connect_data *connssl = &conn->ssl[sockindex]; + struct ssl_connect_data *connssl = cf->ctx; + struct ossl_ctx *octx = (struct ossl_ctx *)connssl->backend; DEBUGASSERT(ssl_connect_3 == connssl->connecting_state); - if(data->set.general_ssl.sessionid) { - bool incache; - SSL_SESSION *our_ssl_sessionid; - void *old_ssl_sessionid = NULL; - - our_ssl_sessionid = SSL_get1_session(connssl->handle); - - /* SSL_get1_session() will increment the reference count and the session - will stay in memory until explicitly freed with SSL_SESSION_free(3), - regardless of its state. */ - - Curl_ssl_sessionid_lock(conn); - incache = !(Curl_ssl_getsessionid(conn, &old_ssl_sessionid, NULL, - sockindex)); - if(incache) { - if(old_ssl_sessionid != our_ssl_sessionid) { - infof(data, "old SSL session ID is stale, removing\n"); - Curl_ssl_delsessionid(conn, old_ssl_sessionid); - incache = FALSE; - } - } - - if(!incache) { - result = Curl_ssl_addsessionid(conn, our_ssl_sessionid, - 0 /* unknown size */, sockindex); - if(result) { - Curl_ssl_sessionid_unlock(conn); - failf(data, "failed to store ssl session"); - return result; - } - } - else { - /* Session was incache, so refcount already incremented earlier. - * Avoid further increments with each SSL_get1_session() call. - * This does not free the session as refcount remains > 0 - */ - SSL_SESSION_free(our_ssl_sessionid); - } - Curl_ssl_sessionid_unlock(conn); - } - /* * 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 + * man-in-the-middle attack; NEVERTHELESS, if we are told explicitly not to + * verify the peer, ignore faults and failures from the server cert * operations. */ - result = servercert(conn, connssl, (SSL_CONN_CONFIG(verifypeer) || - SSL_CONN_CONFIG(verifyhost))); - + result = Curl_oss_check_peer_cert(cf, data, octx, &connssl->peer); if(!result) connssl->connecting_state = ssl_connect_done; return result; } -static Curl_recv ossl_recv; -static Curl_send ossl_send; - -static CURLcode ossl_connect_common(struct connectdata *conn, - int sockindex, +static CURLcode ossl_connect_common(struct Curl_cfilter *cf, + struct Curl_easy *data, bool nonblocking, bool *done) { - CURLcode result; - struct Curl_easy *data = conn->data; - struct ssl_connect_data *connssl = &conn->ssl[sockindex]; - curl_socket_t sockfd = conn->sock[sockindex]; - time_t timeout_ms; + CURLcode result = CURLE_OK; + struct ssl_connect_data *connssl = cf->ctx; + curl_socket_t sockfd = Curl_conn_cf_get_socket(cf, data); int what; + connssl->io_need = CURL_SSL_IO_NEED_NONE; /* check if the connection has already been established */ if(ssl_connection_complete == connssl->state) { *done = TRUE; @@ -2992,59 +4789,52 @@ static CURLcode ossl_connect_common(struct connectdata *conn, } if(ssl_connect_1 == connssl->connecting_state) { - /* Find out how much more time we're allowed */ - timeout_ms = Curl_timeleft(data, NULL, TRUE); + /* Find out how much more time we are allowed */ + const timediff_t timeout_ms = Curl_timeleft(data, NULL, TRUE); if(timeout_ms < 0) { - /* no need to continue if time already is up */ + /* no need to continue if time is already up */ failf(data, "SSL connection timeout"); return CURLE_OPERATION_TIMEDOUT; } - result = ossl_connect_step1(conn, sockindex); + result = ossl_connect_step1(cf, data); if(result) - return result; + goto out; } - while(ssl_connect_2 == connssl->connecting_state || - ssl_connect_2_reading == connssl->connecting_state || - ssl_connect_2_writing == connssl->connecting_state) { + while(ssl_connect_2 == connssl->connecting_state) { /* check allowed time left */ - timeout_ms = Curl_timeleft(data, NULL, TRUE); + 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 = CURLE_OPERATION_TIMEDOUT; + goto out; } - /* 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; + /* if ssl is expecting something, check if it is available. */ + if(!nonblocking && connssl->io_need) { + curl_socket_t writefd = (connssl->io_need & CURL_SSL_IO_NEED_SEND) ? + sockfd : CURL_SOCKET_BAD; + curl_socket_t readfd = (connssl->io_need & CURL_SSL_IO_NEED_RECV) ? + sockfd : CURL_SOCKET_BAD; what = Curl_socket_check(readfd, CURL_SOCKET_BAD, writefd, - nonblocking?0:timeout_ms); + timeout_ms); if(what < 0) { /* fatal error */ failf(data, "select/poll on SSL socket, errno: %d", SOCKERRNO); - return CURLE_SSL_CONNECT_ERROR; + result = CURLE_SSL_CONNECT_ERROR; + goto out; } - else if(0 == what) { - if(nonblocking) { - *done = FALSE; - return CURLE_OK; - } - else { - /* timeout */ - failf(data, "SSL connection timeout"); - return CURLE_OPERATION_TIMEDOUT; - } + if(0 == what) { + /* timeout */ + failf(data, "SSL connection timeout"); + result = CURLE_OPERATION_TIMEDOUT; + goto out; } /* socket is readable or writable */ } @@ -3055,25 +4845,20 @@ static CURLcode ossl_connect_common(struct connectdata *conn, * 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(conn, sockindex); - 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; + result = ossl_connect_step2(cf, data); + if(result || (nonblocking && (ssl_connect_2 == connssl->connecting_state))) + goto out; } /* repeat step2 until all transactions are done. */ if(ssl_connect_3 == connssl->connecting_state) { - result = ossl_connect_step3(conn, sockindex); + result = ossl_connect_step3(cf, data); if(result) - return result; + goto out; } if(ssl_connect_done == connssl->connecting_state) { connssl->state = ssl_connection_complete; - conn->recv[sockindex] = ossl_recv; - conn->send[sockindex] = ossl_send; *done = TRUE; } else @@ -3082,22 +4867,24 @@ static CURLcode ossl_connect_common(struct connectdata *conn, /* Reset our connect state machine */ connssl->connecting_state = ssl_connect_1; - return CURLE_OK; +out: + return result; } -CURLcode Curl_ossl_connect_nonblocking(struct connectdata *conn, - int sockindex, - bool *done) +static CURLcode ossl_connect_nonblocking(struct Curl_cfilter *cf, + struct Curl_easy *data, + bool *done) { - return ossl_connect_common(conn, sockindex, TRUE, done); + return ossl_connect_common(cf, data, TRUE, done); } -CURLcode Curl_ossl_connect(struct connectdata *conn, int sockindex) +static CURLcode ossl_connect(struct Curl_cfilter *cf, + struct Curl_easy *data) { CURLcode result; bool done = FALSE; - result = ossl_connect_common(conn, sockindex, FALSE, &done); + result = ossl_connect_common(cf, data, FALSE, &done); if(result) return result; @@ -3106,20 +4893,21 @@ CURLcode Curl_ossl_connect(struct connectdata *conn, int sockindex) return CURLE_OK; } -bool Curl_ossl_data_pending(const struct connectdata *conn, int connindex) +static bool ossl_data_pending(struct Curl_cfilter *cf, + const struct Curl_easy *data) { - if(conn->ssl[connindex].handle) - /* SSL is in use */ - return (0 != SSL_pending(conn->ssl[connindex].handle) || - (conn->proxy_ssl[connindex].handle && - 0 != SSL_pending(conn->proxy_ssl[connindex].handle))) ? - TRUE : FALSE; - else - return FALSE; + struct ssl_connect_data *connssl = cf->ctx; + struct ossl_ctx *octx = (struct ossl_ctx *)connssl->backend; + + (void)data; + DEBUGASSERT(connssl && octx); + if(octx->ssl && SSL_pending(octx->ssl)) + return TRUE; + return FALSE; } -static ssize_t ossl_send(struct connectdata *conn, - int sockindex, +static ssize_t ossl_send(struct Curl_cfilter *cf, + struct Curl_easy *data, const void *mem, size_t len, CURLcode *curlcode) @@ -3127,121 +4915,323 @@ static ssize_t ossl_send(struct connectdata *conn, /* SSL_write() is said to return 'int' while write() and send() returns 'size_t' */ int err; - char error_buffer[256]; /* OpenSSL documents that this must be at least 256 - bytes long. */ - unsigned long sslerror; + char error_buffer[256]; + sslerr_t sslerror; int memlen; int rc; + struct ssl_connect_data *connssl = cf->ctx; + struct ossl_ctx *octx = (struct ossl_ctx *)connssl->backend; + + (void)data; + DEBUGASSERT(octx); ERR_clear_error(); + connssl->io_need = CURL_SSL_IO_NEED_NONE; memlen = (len > (size_t)INT_MAX) ? INT_MAX : (int)len; - rc = SSL_write(conn->ssl[sockindex].handle, mem, memlen); + rc = SSL_write(octx->ssl, mem, memlen); if(rc <= 0) { - err = SSL_get_error(conn->ssl[sockindex].handle, rc); + err = SSL_get_error(octx->ssl, rc); switch(err) { case SSL_ERROR_WANT_READ: + connssl->io_need = CURL_SSL_IO_NEED_RECV; + *curlcode = CURLE_AGAIN; + rc = -1; + goto out; 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; - return -1; + rc = -1; + goto out; case SSL_ERROR_SYSCALL: - failf(conn->data, "SSL_write() returned SYSCALL, errno = %d", - SOCKERRNO); + { + int sockerr = SOCKERRNO; + + if(octx->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 + msnprintf(error_buffer, sizeof(error_buffer), "%s", + SSL_ERROR_to_str(err)); + + failf(data, OSSL_PACKAGE " SSL_write: %s, errno %d", + error_buffer, sockerr); *curlcode = CURLE_SEND_ERROR; - return -1; - case SSL_ERROR_SSL: + 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(); - if(ERR_GET_LIB(sslerror) == ERR_LIB_SSL && - ERR_GET_REASON(sslerror) == SSL_R_BIO_NOT_SET && - conn->ssl[sockindex].state == ssl_connection_complete && - conn->proxy_ssl[sockindex].state == ssl_connection_complete) { - char ver[120]; - Curl_ossl_version(ver, 120); - failf(conn->data, "Error: %s does not support double SSL tunneling.", - ver); - } - else - failf(conn->data, "SSL_write() error: %s", - ossl_strerror(sslerror, error_buffer, sizeof(error_buffer))); + failf(data, "SSL_write() error: %s", + ossl_strerror(sslerror, error_buffer, sizeof(error_buffer))); *curlcode = CURLE_SEND_ERROR; - return -1; + 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; } - /* a true error */ - failf(conn->data, "SSL_write() return error %d", err); - *curlcode = CURLE_SEND_ERROR; - return -1; } *curlcode = CURLE_OK; + +out: return (ssize_t)rc; /* number of bytes */ } -static ssize_t ossl_recv(struct connectdata *conn, /* connection data */ - int num, /* socketindex */ +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]; /* OpenSSL documents that this must be at - least 256 bytes long. */ + 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_ctx *octx = (struct ossl_ctx *)connssl->backend; + + (void)data; + DEBUGASSERT(octx); ERR_clear_error(); + connssl->io_need = CURL_SSL_IO_NEED_NONE; buffsize = (buffersize > (size_t)INT_MAX) ? INT_MAX : (int)buffersize; - nread = (ssize_t)SSL_read(conn->ssl[num].handle, buf, buffsize); + nread = (ssize_t)SSL_read(octx->ssl, buf, buffsize); + if(nread <= 0) { /* failed SSL_read */ - int err = SSL_get_error(conn->ssl[num].handle, (int)nread); + int err = SSL_get_error(octx->ssl, (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: + *curlcode = CURLE_AGAIN; + nread = -1; + goto out; case SSL_ERROR_WANT_WRITE: - /* there's data pending, re-invoke SSL_read() */ + connssl->io_need = CURL_SSL_IO_NEED_SEND; *curlcode = CURLE_AGAIN; - return -1; + 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 */ + /* https://docs.openssl.org/master/man3/ERR_get_error/ */ + if(octx->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 */ - failf(conn->data, "SSL read: %s, errno %d", - ossl_strerror(sslerror, error_buffer, sizeof(error_buffer)), - SOCKERRNO); + 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 + msnprintf(error_buffer, sizeof(error_buffer), "%s", + SSL_ERROR_to_str(err)); + 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 do not 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; - return -1; + nread = -1; + goto out; } +#endif } } + +out: return nread; } -size_t Curl_ossl_version(char *buffer, size_t size) +static CURLcode ossl_get_channel_binding(struct Curl_easy *data, int sockindex, + struct dynbuf *binding) +{ + /* required for X509_get_signature_nid support */ +#if OPENSSL_VERSION_NUMBER > 0x10100000L + X509 *cert; + int algo_nid; + const EVP_MD *algo_type; + const char *algo_name; + unsigned int length; + unsigned char buf[EVP_MAX_MD_SIZE]; + + const char prefix[] = "tls-server-end-point:"; + struct connectdata *conn = data->conn; + struct Curl_cfilter *cf = conn->cfilter[sockindex]; + struct ossl_ctx *octx = NULL; + + do { + const struct Curl_cftype *cft = cf->cft; + struct ssl_connect_data *connssl = cf->ctx; + + if(cft->name && !strcmp(cft->name, "SSL")) { + octx = (struct ossl_ctx *)connssl->backend; + break; + } + + if(cf->next) + cf = cf->next; + + } while(cf->next); + + if(!octx) { + failf(data, + "Failed to find SSL backend for endpoint"); + return CURLE_SSL_ENGINE_INITFAILED; + } + + cert = SSL_get1_peer_certificate(octx->ssl); + if(!cert) { + /* No server certificate, don't do channel binding */ + return CURLE_OK; + } + + if(!OBJ_find_sigid_algs(X509_get_signature_nid(cert), &algo_nid, NULL)) { + failf(data, + "Unable to find digest NID for certificate signature algorithm"); + return CURLE_SSL_INVALIDCERTSTATUS; + } + + /* https://datatracker.ietf.org/doc/html/rfc5929#section-4.1 */ + if(algo_nid == NID_md5 || algo_nid == NID_sha1) { + algo_type = EVP_sha256(); + } + else { + algo_type = EVP_get_digestbynid(algo_nid); + if(!algo_type) { + algo_name = OBJ_nid2sn(algo_nid); + failf(data, "Could not find digest algorithm %s (NID %d)", + algo_name ? algo_name : "(null)", algo_nid); + return CURLE_SSL_INVALIDCERTSTATUS; + } + } + + if(!X509_digest(cert, algo_type, buf, &length)) { + failf(data, "X509_digest() failed"); + return CURLE_SSL_INVALIDCERTSTATUS; + } + + /* Append "tls-server-end-point:" */ + if(Curl_dyn_addn(binding, prefix, sizeof(prefix) - 1) != CURLE_OK) + return CURLE_OUT_OF_MEMORY; + /* Append digest */ + if(Curl_dyn_addn(binding, buf, length)) + return CURLE_OUT_OF_MEMORY; + + return CURLE_OK; +#else + /* No X509_get_signature_nid support */ + (void)data; /* unused */ + (void)sockindex; /* unused */ + (void)binding; /* unused */ + return CURLE_OK; +#endif +} + +static size_t ossl_version(char *buffer, size_t size) { -#ifdef OPENSSL_IS_BORINGSSL - return snprintf(buffer, size, OSSL_PACKAGE); -#else /* OPENSSL_IS_BORINGSSL */ +#ifdef LIBRESSL_VERSION_NUMBER +#ifdef HAVE_OPENSSL_VERSION + char *p; + size_t 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(); + ssleay_value = OpenSSL_version_num(); if(ssleay_value < 0x906000) { - ssleay_value=SSLEAY_VERSION_NUMBER; + ssleay_value = SSLEAY_VERSION_NUMBER; sub[0]='\0'; } else { @@ -3260,66 +5250,129 @@ size_t Curl_ossl_version(char *buffer, size_t size) sub[0]='\0'; } - return snprintf(buffer, size, "%s/%lx.%lx.%lx%s", - OSSL_PACKAGE, - (ssleay_value>>28)&0xf, - (ssleay_value>>20)&0xff, - (ssleay_value>>12)&0xff, - sub); + 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 */ -CURLcode Curl_ossl_random(struct Curl_easy *data, unsigned char *entropy, - size_t length) +static CURLcode ossl_random(struct Curl_easy *data, + unsigned char *entropy, size_t length) { int rc; if(data) { - if(Curl_ossl_seed(data)) /* Initiate the seed if not already done */ - return CURLE_FAILED_INIT; /* couldn't seed for some reason */ + if(ossl_seed(data)) /* Initiate the seed if not already done */ + return CURLE_FAILED_INIT; /* could not 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)); + rc = RAND_bytes(entropy, (ossl_valsize_t)curlx_uztosi(length)); return (rc == 1 ? CURLE_OK : CURLE_FAILED_INIT); } -void Curl_ossl_md5sum(unsigned char *tmp, /* input */ - size_t tmplen, - unsigned char *md5sum /* output */, - size_t unused) -{ - MD5_CTX MD5pw; - (void)unused; - MD5_Init(&MD5pw); - MD5_Update(&MD5pw, tmp, tmplen); - MD5_Final(md5sum, &MD5pw); -} - #if (OPENSSL_VERSION_NUMBER >= 0x0090800fL) && !defined(OPENSSL_NO_SHA256) -void Curl_ossl_sha256sum(const unsigned char *tmp, /* input */ - size_t tmplen, - unsigned char *sha256sum /* output */, - size_t unused) +static CURLcode ossl_sha256sum(const unsigned char *tmp, /* input */ + size_t tmplen, + unsigned char *sha256sum /* output */, + size_t unused) { - SHA256_CTX SHA256pw; - (void)unused; - SHA256_Init(&SHA256pw); - SHA256_Update(&SHA256pw, tmp, tmplen); - SHA256_Final(sha256sum, &SHA256pw); + 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 -bool Curl_ossl_cert_status_request(void) +static bool ossl_cert_status_request(void) { #if (OPENSSL_VERSION_NUMBER >= 0x0090808fL) && !defined(OPENSSL_NO_TLSEXT) && \ - !defined(OPENSSL_NO_OCSP) + !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_ctx *octx = (struct ossl_ctx *)connssl->backend; + DEBUGASSERT(octx); + return info == CURLINFO_TLS_SESSION ? + (void *)octx->ssl_ctx : (void *)octx->ssl; +} + +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 +#ifdef USE_ECH + SSLSUPP_ECH | +#endif + SSLSUPP_CA_CACHE | + SSLSUPP_HTTPS_PROXY | + SSLSUPP_CIPHER_LIST, + + sizeof(struct ossl_ctx), + + 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_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_recv, /* recv decrypted data */ + ossl_send, /* send data to encrypt */ + ossl_get_channel_binding /* get_channel_binding */ +}; + #endif /* USE_OPENSSL */ diff --git a/contrib/curl/lib/vtls/openssl.h b/contrib/curl/lib/vtls/openssl.h index b9648d5147..7aba947d18 100644 --- a/contrib/curl/lib/vtls/openssl.h +++ b/contrib/curl/lib/vtls/openssl.h @@ -7,11 +7,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2017, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -20,107 +20,92 @@ * 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 and openssl.c + * This header should only be needed to get included by vtls.c, openssl.c + * and ngtcp2.c */ +#include +#include #include "urldata.h" -CURLcode Curl_ossl_connect(struct connectdata *conn, int sockindex); -CURLcode Curl_ossl_connect_nonblocking(struct connectdata *conn, - int sockindex, - bool *done); - -/* close a SSL connection */ -void Curl_ossl_close(struct connectdata *conn, int sockindex); - -/* tell OpenSSL to close down all open information regarding connections (and - thus session ID caching etc) */ -void Curl_ossl_close_all(struct Curl_easy *data); - -/* Sets an OpenSSL engine */ -CURLcode Curl_ossl_set_engine(struct Curl_easy *data, const char *engine); - -/* function provided for the generic SSL-layer, called when a session id - should be freed */ -void Curl_ossl_session_free(void *ptr); - -/* Sets engine as default for all SSL operations */ -CURLcode Curl_ossl_set_engine_default(struct Curl_easy *data); - -/* Build list of OpenSSL engines */ -struct curl_slist *Curl_ossl_engines_list(struct Curl_easy *data); - -int Curl_ossl_init(void); -void Curl_ossl_cleanup(void); - -size_t Curl_ossl_version(char *buffer, size_t size); -int Curl_ossl_check_cxn(struct connectdata *cxn); -int Curl_ossl_shutdown(struct connectdata *conn, int sockindex); -bool Curl_ossl_data_pending(const struct connectdata *conn, - int connindex); - -/* return 0 if a find random is filled in */ -CURLcode Curl_ossl_random(struct Curl_easy *data, unsigned char *entropy, - size_t length); -void Curl_ossl_md5sum(unsigned char *tmp, /* input */ - size_t tmplen, - unsigned char *md5sum /* output */, - size_t unused); -void Curl_ossl_sha256sum(const unsigned char *tmp, /* input */ - size_t tmplen, - unsigned char *sha256sum /* output */, - size_t unused); - -bool Curl_ossl_cert_status_request(void); - -/* Support HTTPS-proxy */ -#define HTTPS_PROXY_SUPPORT 1 - -/* Set the API backend definition to OpenSSL */ -#define CURL_SSL_BACKEND CURLSSLBACKEND_OPENSSL - -/* this backend supports the CAPATH option */ -#define have_curlssl_ca_path 1 +/* Struct to hold a Curl OpenSSL instance */ +struct ossl_ctx { + /* these ones requires specific SSL-types */ + SSL_CTX* ssl_ctx; + SSL* ssl; + X509* server_cert; + BIO_METHOD *bio_method; + CURLcode io_result; /* result of last BIO cfilter operation */ +#ifndef HAVE_KEYLOG_CALLBACK + /* Set to true once a valid keylog entry has been created to avoid dupes. + This is a bool and not a bitfield because it is passed by address. */ + bool keylog_done; +#endif + BIT(x509_store_setup); /* x509 store has been set up */ + BIT(reused_session); /* session-ID was reused for this */ +}; + +typedef CURLcode Curl_ossl_ctx_setup_cb(struct Curl_cfilter *cf, + struct Curl_easy *data, + void *user_data); + +typedef int Curl_ossl_new_session_cb(SSL *ssl, SSL_SESSION *ssl_sessionid); + +CURLcode Curl_ossl_ctx_init(struct ossl_ctx *octx, + struct Curl_cfilter *cf, + struct Curl_easy *data, + struct ssl_peer *peer, + int transport, /* TCP or QUIC */ + const unsigned char *alpn, size_t alpn_len, + Curl_ossl_ctx_setup_cb *cb_setup, + void *cb_user_data, + Curl_ossl_new_session_cb *cb_new_session, + void *ssl_user_data); + +#if (OPENSSL_VERSION_NUMBER < 0x30000000L) +#define SSL_get1_peer_certificate SSL_get_peer_certificate +#endif -/* this backend supports CURLOPT_CERTINFO */ -#define have_curlssl_certinfo 1 +extern const struct Curl_ssl Curl_ssl_openssl; -/* this backend supports CURLOPT_SSL_CTX_* */ -#define have_curlssl_ssl_ctx 1 +/** + * 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); -/* this backend supports CURLOPT_PINNEDPUBLICKEY */ -#define have_curlssl_pinnedpubkey 1 +CURLcode Curl_ossl_ctx_configure(struct Curl_cfilter *cf, + struct Curl_easy *data, + SSL_CTX *ssl_ctx); -/* API setup for OpenSSL */ -#define curlssl_init Curl_ossl_init -#define curlssl_cleanup Curl_ossl_cleanup -#define curlssl_connect Curl_ossl_connect -#define curlssl_connect_nonblocking Curl_ossl_connect_nonblocking -#define curlssl_session_free(x) Curl_ossl_session_free(x) -#define curlssl_close_all Curl_ossl_close_all -#define curlssl_close Curl_ossl_close -#define curlssl_shutdown(x,y) Curl_ossl_shutdown(x,y) -#define curlssl_set_engine(x,y) Curl_ossl_set_engine(x,y) -#define curlssl_set_engine_default(x) Curl_ossl_set_engine_default(x) -#define curlssl_engines_list(x) Curl_ossl_engines_list(x) -#define curlssl_version Curl_ossl_version -#define curlssl_check_cxn Curl_ossl_check_cxn -#define curlssl_data_pending(x,y) Curl_ossl_data_pending(x,y) -#define curlssl_random(x,y,z) Curl_ossl_random(x,y,z) -#define curlssl_md5sum(a,b,c,d) Curl_ossl_md5sum(a,b,c,d) -#if (OPENSSL_VERSION_NUMBER >= 0x0090800fL) && !defined(OPENSSL_NO_SHA256) -#define curlssl_sha256sum(a,b,c,d) Curl_ossl_sha256sum(a,b,c,d) -#endif -#define curlssl_cert_status_request() Curl_ossl_cert_status_request() +/* + * Add a new session to the cache. Takes ownership of the session. + */ +CURLcode Curl_ossl_add_session(struct Curl_cfilter *cf, + struct Curl_easy *data, + const struct ssl_peer *peer, + SSL_SESSION *ssl_sessionid); -#define DEFAULT_CIPHER_SELECTION \ - "ALL:!EXPORT:!EXPORT40:!EXPORT56:!aNULL:!LOW:!RC4:@STRENGTH" +/* + * Get the server cert, verify it and show it, etc., only call failf() if + * ssl config verifypeer or -host is set. Otherwise all this is for + * informational purposes only! + */ +CURLcode Curl_oss_check_peer_cert(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct ossl_ctx *octx, + struct ssl_peer *peer); #endif /* USE_OPENSSL */ #endif /* HEADER_CURL_SSLUSE_H */ diff --git a/contrib/curl/lib/vtls/polarssl.c b/contrib/curl/lib/vtls/polarssl.c deleted file mode 100644 index e5346ce3dc..0000000000 --- a/contrib/curl/lib/vtls/polarssl.c +++ /dev/null @@ -1,825 +0,0 @@ -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) 2012 - 2016, Daniel Stenberg, , et al. - * Copyright (C) 2010 - 2011, Hoi-Ho Chan, - * - * This software is licensed 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. - * - ***************************************************************************/ - -/* - * Source file for all PolarSSL-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_POLARSSL - -#include -#include -#include -#include -#include -#include - -#if POLARSSL_VERSION_NUMBER < 0x01030000 -#error too old PolarSSL -#endif - -#include -#include -#include - -#include "urldata.h" -#include "sendf.h" -#include "inet_pton.h" -#include "polarssl.h" -#include "vtls.h" -#include "parsedate.h" -#include "connect.h" /* for the connect timeout */ -#include "select.h" -#include "strcase.h" -#include "polarssl_threadlock.h" -#include "curl_printf.h" -#include "curl_memory.h" -/* The last #include file should be: */ -#include "memdebug.h" - -/* 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 * POLARSSL_MPI_MAX_SIZE) -#define ECP_PUB_DER_MAX_BYTES (30 + 2 * POLARSSL_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) - -/* apply threading? */ -#if defined(USE_THREADS_POSIX) || defined(USE_THREADS_WIN32) -#define THREADING_SUPPORT -#endif - -#ifndef POLARSSL_ERROR_C -#define error_strerror(x,y,z) -#endif /* POLARSSL_ERROR_C */ - - -#if defined(THREADING_SUPPORT) -static entropy_context entropy; - -static int entropy_init_initialized = 0; - -/* start of entropy_init_mutex() */ -static void entropy_init_mutex(entropy_context *ctx) -{ - /* lock 0 = entropy_init_mutex() */ - Curl_polarsslthreadlock_lock_function(0); - if(entropy_init_initialized == 0) { - entropy_init(ctx); - entropy_init_initialized = 1; - } - Curl_polarsslthreadlock_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_polarsslthreadlock_lock_function(1); - ret = entropy_func(data, output, len); - Curl_polarsslthreadlock_unlock_function(1); - - return ret; -} -/* end of entropy_func_mutex() */ - -#endif /* THREADING_SUPPORT */ - -/* Define this to enable lots of debugging for PolarSSL */ -#undef POLARSSL_DEBUG - -#ifdef POLARSSL_DEBUG -static void polarssl_debug(void *context, int level, const char *line) -{ - struct Curl_easy *data = NULL; - - if(!context) - return; - - data = (struct Curl_easy *)context; - - infof(data, "%s", line); - (void) level; -} -#else -#endif - -/* ALPN for http2? */ -#ifdef POLARSSL_SSL_ALPN -# define HAS_ALPN -#endif - -static Curl_recv polarssl_recv; -static Curl_send polarssl_send; - - -static CURLcode -polarssl_connect_step1(struct connectdata *conn, - int sockindex) -{ - struct Curl_easy *data = conn->data; - struct ssl_connect_data* connssl = &conn->ssl[sockindex]; - const char *capath = SSL_CONN_CONFIG(CApath); - const char * const hostname = SSL_IS_PROXY() ? conn->http_proxy.host.name : - conn->host.name; - const long int port = SSL_IS_PROXY() ? conn->port : conn->remote_port; - int ret = -1; - char errorbuf[128]; - errorbuf[0]=0; - - /* PolarSSL only supports SSLv3 and TLSv1 */ - if(SSL_CONN_CONFIG(version) == CURL_SSLVERSION_SSLv2) { - failf(data, "PolarSSL does not support SSLv2"); - return CURLE_SSL_CONNECT_ERROR; - } - -#ifdef THREADING_SUPPORT - entropy_init_mutex(&entropy); - - if((ret = ctr_drbg_init(&connssl->ctr_drbg, entropy_func_mutex, &entropy, - NULL, 0)) != 0) { - error_strerror(ret, errorbuf, sizeof(errorbuf)); - failf(data, "Failed - PolarSSL: ctr_drbg_init returned (-0x%04X) %s\n", - -ret, errorbuf); - } -#else - entropy_init(&connssl->entropy); - - if((ret = ctr_drbg_init(&connssl->ctr_drbg, entropy_func, &connssl->entropy, - NULL, 0)) != 0) { - error_strerror(ret, errorbuf, sizeof(errorbuf)); - failf(data, "Failed - PolarSSL: ctr_drbg_init returned (-0x%04X) %s\n", - -ret, errorbuf); - } -#endif /* THREADING_SUPPORT */ - - /* Load the trusted CA */ - memset(&connssl->cacert, 0, sizeof(x509_crt)); - - if(SSL_CONN_CONFIG(CAfile)) { - ret = x509_crt_parse_file(&connssl->cacert, - SSL_CONN_CONFIG(CAfile)); - - if(ret<0) { - error_strerror(ret, errorbuf, sizeof(errorbuf)); - failf(data, "Error reading ca cert file %s - PolarSSL: (-0x%04X) %s", - SSL_CONN_CONFIG(CAfile), -ret, errorbuf); - - if(SSL_CONN_CONFIG(verifypeer)) - return CURLE_SSL_CACERT_BADFILE; - } - } - - if(capath) { - ret = x509_crt_parse_path(&connssl->cacert, capath); - - if(ret<0) { - error_strerror(ret, errorbuf, sizeof(errorbuf)); - failf(data, "Error reading ca cert path %s - PolarSSL: (-0x%04X) %s", - capath, -ret, errorbuf); - - if(SSL_CONN_CONFIG(verifypeer)) - return CURLE_SSL_CACERT_BADFILE; - } - } - - /* Load the client certificate */ - memset(&connssl->clicert, 0, sizeof(x509_crt)); - - if(SSL_SET_OPTION(cert)) { - ret = x509_crt_parse_file(&connssl->clicert, - SSL_SET_OPTION(cert)); - - if(ret) { - error_strerror(ret, errorbuf, sizeof(errorbuf)); - failf(data, "Error reading client cert file %s - PolarSSL: (-0x%04X) %s", - SSL_SET_OPTION(cert), -ret, errorbuf); - - return CURLE_SSL_CERTPROBLEM; - } - } - - /* Load the client private key */ - if(SSL_SET_OPTION(key)) { - pk_context pk; - pk_init(&pk); - ret = pk_parse_keyfile(&pk, SSL_SET_OPTION(key), - SSL_SET_OPTION(key_passwd)); - if(ret == 0 && !pk_can_do(&pk, POLARSSL_PK_RSA)) - ret = POLARSSL_ERR_PK_TYPE_MISMATCH; - if(ret == 0) - rsa_copy(&connssl->rsa, pk_rsa(pk)); - else - rsa_free(&connssl->rsa); - pk_free(&pk); - - if(ret) { - error_strerror(ret, errorbuf, sizeof(errorbuf)); - failf(data, "Error reading private key %s - PolarSSL: (-0x%04X) %s", - SSL_SET_OPTION(key), -ret, errorbuf); - - return CURLE_SSL_CERTPROBLEM; - } - } - - /* Load the CRL */ - memset(&connssl->crl, 0, sizeof(x509_crl)); - - if(SSL_SET_OPTION(CRLfile)) { - ret = x509_crl_parse_file(&connssl->crl, - SSL_SET_OPTION(CRLfile)); - - if(ret) { - error_strerror(ret, errorbuf, sizeof(errorbuf)); - failf(data, "Error reading CRL file %s - PolarSSL: (-0x%04X) %s", - SSL_SET_OPTION(CRLfile), -ret, errorbuf); - - return CURLE_SSL_CRL_BADFILE; - } - } - - infof(data, "PolarSSL: Connecting to %s:%d\n", hostname, port); - - if(ssl_init(&connssl->ssl)) { - failf(data, "PolarSSL: ssl_init failed"); - return CURLE_SSL_CONNECT_ERROR; - } - - switch(SSL_CONN_CONFIG(version)) { - case CURL_SSLVERSION_DEFAULT: - case CURL_SSLVERSION_TLSv1: - ssl_set_min_version(&connssl->ssl, SSL_MAJOR_VERSION_3, - SSL_MINOR_VERSION_1); - break; - case CURL_SSLVERSION_SSLv3: - ssl_set_min_version(&connssl->ssl, SSL_MAJOR_VERSION_3, - SSL_MINOR_VERSION_0); - ssl_set_max_version(&connssl->ssl, SSL_MAJOR_VERSION_3, - SSL_MINOR_VERSION_0); - infof(data, "PolarSSL: Forced min. SSL Version to be SSLv3\n"); - break; - case CURL_SSLVERSION_TLSv1_0: - ssl_set_min_version(&connssl->ssl, SSL_MAJOR_VERSION_3, - SSL_MINOR_VERSION_1); - ssl_set_max_version(&connssl->ssl, SSL_MAJOR_VERSION_3, - SSL_MINOR_VERSION_1); - infof(data, "PolarSSL: Forced min. SSL Version to be TLS 1.0\n"); - break; - case CURL_SSLVERSION_TLSv1_1: - ssl_set_min_version(&connssl->ssl, SSL_MAJOR_VERSION_3, - SSL_MINOR_VERSION_2); - ssl_set_max_version(&connssl->ssl, SSL_MAJOR_VERSION_3, - SSL_MINOR_VERSION_2); - infof(data, "PolarSSL: Forced min. SSL Version to be TLS 1.1\n"); - break; - case CURL_SSLVERSION_TLSv1_2: - ssl_set_min_version(&connssl->ssl, SSL_MAJOR_VERSION_3, - SSL_MINOR_VERSION_3); - ssl_set_max_version(&connssl->ssl, SSL_MAJOR_VERSION_3, - SSL_MINOR_VERSION_3); - infof(data, "PolarSSL: Forced min. SSL Version to be TLS 1.2\n"); - break; - case CURL_SSLVERSION_TLSv1_3: - failf(data, "PolarSSL: TLS 1.3 is not yet supported"); - return CURLE_SSL_CONNECT_ERROR; - default: - failf(data, "Unrecognized parameter passed via CURLOPT_SSLVERSION"); - return CURLE_SSL_CONNECT_ERROR; - } - - ssl_set_endpoint(&connssl->ssl, SSL_IS_CLIENT); - ssl_set_authmode(&connssl->ssl, SSL_VERIFY_OPTIONAL); - - ssl_set_rng(&connssl->ssl, ctr_drbg_random, - &connssl->ctr_drbg); - ssl_set_bio(&connssl->ssl, - net_recv, &conn->sock[sockindex], - net_send, &conn->sock[sockindex]); - - ssl_set_ciphersuites(&connssl->ssl, ssl_list_ciphersuites()); - - /* Check if there's a cached ID we can/should use here! */ - if(data->set.general_ssl.sessionid) { - void *old_session = NULL; - - Curl_ssl_sessionid_lock(conn); - if(!Curl_ssl_getsessionid(conn, &old_session, NULL, sockindex)) { - ret = ssl_set_session(&connssl->ssl, old_session); - if(ret) { - Curl_ssl_sessionid_unlock(conn); - failf(data, "ssl_set_session returned -0x%x", -ret); - return CURLE_SSL_CONNECT_ERROR; - } - infof(data, "PolarSSL re-using session\n"); - } - Curl_ssl_sessionid_unlock(conn); - } - - ssl_set_ca_chain(&connssl->ssl, - &connssl->cacert, - &connssl->crl, - hostname); - - ssl_set_own_cert_rsa(&connssl->ssl, - &connssl->clicert, &connssl->rsa); - - if(ssl_set_hostname(&connssl->ssl, hostname)) { - /* 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, "couldn't set hostname in PolarSSL"); - return CURLE_SSL_CONNECT_ERROR; - } - -#ifdef HAS_ALPN - if(conn->bits.tls_enable_alpn) { - static const char *protocols[3]; - int cur = 0; - -#ifdef USE_NGHTTP2 - if(data->set.httpversion >= CURL_HTTP_VERSION_2) { - protocols[cur++] = NGHTTP2_PROTO_VERSION_ID; - infof(data, "ALPN, offering %s\n", NGHTTP2_PROTO_VERSION_ID); - } -#endif - - protocols[cur++] = ALPN_HTTP_1_1; - infof(data, "ALPN, offering %s\n", ALPN_HTTP_1_1); - - protocols[cur] = NULL; - - ssl_set_alpn_protocols(&connssl->ssl, protocols); - } -#endif - -#ifdef POLARSSL_DEBUG - ssl_set_dbg(&connssl->ssl, polarssl_debug, data); -#endif - - connssl->connecting_state = ssl_connect_2; - - return CURLE_OK; -} - -static CURLcode -polarssl_connect_step2(struct connectdata *conn, - int sockindex) -{ - int ret; - struct Curl_easy *data = conn->data; - struct ssl_connect_data* connssl = &conn->ssl[sockindex]; - char buffer[1024]; - const char * const pinnedpubkey = SSL_IS_PROXY() ? - data->set.str[STRING_SSL_PINNEDPUBLICKEY_PROXY] : - data->set.str[STRING_SSL_PINNEDPUBLICKEY_ORIG]; - - - char errorbuf[128]; - errorbuf[0] = 0; - - conn->recv[sockindex] = polarssl_recv; - conn->send[sockindex] = polarssl_send; - - ret = ssl_handshake(&connssl->ssl); - - switch(ret) { - case 0: - break; - - case POLARSSL_ERR_NET_WANT_READ: - connssl->connecting_state = ssl_connect_2_reading; - return CURLE_OK; - - case POLARSSL_ERR_NET_WANT_WRITE: - connssl->connecting_state = ssl_connect_2_writing; - return CURLE_OK; - - default: - error_strerror(ret, errorbuf, sizeof(errorbuf)); - failf(data, "ssl_handshake returned - PolarSSL: (-0x%04X) %s", - -ret, errorbuf); - return CURLE_SSL_CONNECT_ERROR; - } - - infof(data, "PolarSSL: Handshake complete, cipher is %s\n", - ssl_get_ciphersuite(&conn->ssl[sockindex].ssl) ); - - ret = ssl_get_verify_result(&conn->ssl[sockindex].ssl); - - if(ret && SSL_CONN_CONFIG(verifypeer)) { - if(ret & BADCERT_EXPIRED) - failf(data, "Cert verify failed: BADCERT_EXPIRED"); - - if(ret & BADCERT_REVOKED) { - failf(data, "Cert verify failed: BADCERT_REVOKED"); - return CURLE_SSL_CACERT; - } - - if(ret & BADCERT_CN_MISMATCH) - failf(data, "Cert verify failed: BADCERT_CN_MISMATCH"); - - if(ret & BADCERT_NOT_TRUSTED) - failf(data, "Cert verify failed: BADCERT_NOT_TRUSTED"); - - return CURLE_PEER_FAILED_VERIFICATION; - } - - if(ssl_get_peer_cert(&(connssl->ssl))) { - /* If the session was resumed, there will be no peer certs */ - memset(buffer, 0, sizeof(buffer)); - - if(x509_crt_info(buffer, sizeof(buffer), (char *)"* ", - ssl_get_peer_cert(&(connssl->ssl))) != -1) - infof(data, "Dumping cert info:\n%s\n", buffer); - } - - /* adapted from mbedtls.c */ - if(pinnedpubkey) { - int size; - CURLcode result; - x509_crt *p; - unsigned char pubkey[PUB_DER_MAX_BYTES]; - const x509_crt *peercert; - - peercert = ssl_get_peer_cert(&connssl->ssl); - - if(!peercert || !peercert->raw.p || !peercert->raw.len) { - failf(data, "Failed due to missing peer certificate"); - return CURLE_SSL_PINNEDPUBKEYNOTMATCH; - } - - p = calloc(1, sizeof(*p)); - - if(!p) - return CURLE_OUT_OF_MEMORY; - - x509_crt_init(p); - - /* Make a copy of our const peercert because pk_write_pubkey_der - needs a non-const key, for now. - https://github.com/ARMmbed/mbedtls/issues/396 */ - if(x509_crt_parse_der(p, peercert->raw.p, peercert->raw.len)) { - failf(data, "Failed copying peer certificate"); - x509_crt_free(p); - free(p); - return CURLE_SSL_PINNEDPUBKEYNOTMATCH; - } - - size = pk_write_pubkey_der(&p->pk, pubkey, PUB_DER_MAX_BYTES); - - if(size <= 0) { - failf(data, "Failed copying public key from peer certificate"); - x509_crt_free(p); - free(p); - return CURLE_SSL_PINNEDPUBKEYNOTMATCH; - } - - /* 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); - if(result) { - x509_crt_free(p); - free(p); - return result; - } - - x509_crt_free(p); - free(p); - } - -#ifdef HAS_ALPN - if(conn->bits.tls_enable_alpn) { - const char *next_protocol = ssl_get_alpn_protocol(&connssl->ssl); - - if(next_protocol != NULL) { - infof(data, "ALPN, server accepted to use %s\n", next_protocol); - -#ifdef USE_NGHTTP2 - if(!strncmp(next_protocol, NGHTTP2_PROTO_VERSION_ID, - NGHTTP2_PROTO_VERSION_ID_LEN)) { - conn->negnpn = CURL_HTTP_VERSION_2; - } - else -#endif - if(!strncmp(next_protocol, ALPN_HTTP_1_1, ALPN_HTTP_1_1_LENGTH)) { - conn->negnpn = CURL_HTTP_VERSION_1_1; - } - } - else - infof(data, "ALPN, server did not agree to a protocol\n"); - } -#endif - - connssl->connecting_state = ssl_connect_3; - infof(data, "SSL connected\n"); - - return CURLE_OK; -} - -static CURLcode -polarssl_connect_step3(struct connectdata *conn, - int sockindex) -{ - CURLcode retcode = CURLE_OK; - struct ssl_connect_data *connssl = &conn->ssl[sockindex]; - struct Curl_easy *data = conn->data; - - DEBUGASSERT(ssl_connect_3 == connssl->connecting_state); - - if(data->set.general_ssl.sessionid) { - int ret; - ssl_session *our_ssl_sessionid; - void *old_ssl_sessionid = NULL; - - our_ssl_sessionid = malloc(sizeof(ssl_session)); - if(!our_ssl_sessionid) - return CURLE_OUT_OF_MEMORY; - - ssl_session_init(our_ssl_sessionid); - - ret = ssl_get_session(&connssl->ssl, our_ssl_sessionid); - if(ret) { - failf(data, "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(conn); - if(!Curl_ssl_getsessionid(conn, &old_ssl_sessionid, NULL, sockindex)) - Curl_ssl_delsessionid(conn, old_ssl_sessionid); - - retcode = Curl_ssl_addsessionid(conn, our_ssl_sessionid, 0, sockindex); - Curl_ssl_sessionid_unlock(conn); - if(retcode) { - free(our_ssl_sessionid); - failf(data, "failed to store ssl session"); - return retcode; - } - } - - connssl->connecting_state = ssl_connect_done; - - return CURLE_OK; -} - -static ssize_t polarssl_send(struct connectdata *conn, - int sockindex, - const void *mem, - size_t len, - CURLcode *curlcode) -{ - int ret = -1; - - ret = ssl_write(&conn->ssl[sockindex].ssl, - (unsigned char *)mem, len); - - if(ret < 0) { - *curlcode = (ret == POLARSSL_ERR_NET_WANT_WRITE) ? - CURLE_AGAIN : CURLE_SEND_ERROR; - ret = -1; - } - - return ret; -} - -void Curl_polarssl_close(struct connectdata *conn, int sockindex) -{ - rsa_free(&conn->ssl[sockindex].rsa); - x509_crt_free(&conn->ssl[sockindex].clicert); - x509_crt_free(&conn->ssl[sockindex].cacert); - x509_crl_free(&conn->ssl[sockindex].crl); - ssl_free(&conn->ssl[sockindex].ssl); -} - -static ssize_t polarssl_recv(struct connectdata *conn, - int num, - char *buf, - size_t buffersize, - CURLcode *curlcode) -{ - int ret = -1; - ssize_t len = -1; - - memset(buf, 0, buffersize); - ret = ssl_read(&conn->ssl[num].ssl, (unsigned char *)buf, buffersize); - - if(ret <= 0) { - if(ret == POLARSSL_ERR_SSL_PEER_CLOSE_NOTIFY) - return 0; - - *curlcode = (ret == POLARSSL_ERR_NET_WANT_READ) ? - CURLE_AGAIN : CURLE_RECV_ERROR; - return -1; - } - - len = ret; - - return len; -} - -void Curl_polarssl_session_free(void *ptr) -{ - ssl_session_free(ptr); - free(ptr); -} - -/* 1.3.10 was the first rebranded version. All new releases (in 1.3 branch and - higher) will be mbed TLS branded.. */ - -size_t Curl_polarssl_version(char *buffer, size_t size) -{ - unsigned int version = version_get_number(); - return snprintf(buffer, size, "%s/%d.%d.%d", - version >= 0x01030A00?"mbedTLS":"PolarSSL", - version>>24, (version>>16)&0xff, (version>>8)&0xff); -} - -static CURLcode -polarssl_connect_common(struct connectdata *conn, - int sockindex, - bool nonblocking, - bool *done) -{ - CURLcode result; - struct Curl_easy *data = conn->data; - struct ssl_connect_data *connssl = &conn->ssl[sockindex]; - curl_socket_t sockfd = conn->sock[sockindex]; - long 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; - } - - result = polarssl_connect_step1(conn, sockindex); - 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 */ - 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 = polarssl_connect_step2(conn, sockindex); - 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 = polarssl_connect_step3(conn, sockindex); - if(result) - return result; - } - - if(ssl_connect_done == connssl->connecting_state) { - connssl->state = ssl_connection_complete; - conn->recv[sockindex] = polarssl_recv; - conn->send[sockindex] = polarssl_send; - *done = TRUE; - } - else - *done = FALSE; - - /* Reset our connect state machine */ - connssl->connecting_state = ssl_connect_1; - - return CURLE_OK; -} - -CURLcode -Curl_polarssl_connect_nonblocking(struct connectdata *conn, - int sockindex, - bool *done) -{ - return polarssl_connect_common(conn, sockindex, TRUE, done); -} - - -CURLcode -Curl_polarssl_connect(struct connectdata *conn, - int sockindex) -{ - CURLcode result; - bool done = FALSE; - - result = polarssl_connect_common(conn, sockindex, FALSE, &done); - if(result) - return result; - - DEBUGASSERT(done); - - return CURLE_OK; -} - -/* - * return 0 error initializing SSL - * return 1 SSL initialized successfully - */ -int Curl_polarssl_init(void) -{ - return Curl_polarsslthreadlock_thread_setup(); -} - -void Curl_polarssl_cleanup(void) -{ - (void)Curl_polarsslthreadlock_thread_cleanup(); -} - - -int Curl_polarssl_data_pending(const struct connectdata *conn, int sockindex) -{ - return ssl_get_bytes_avail(&conn->ssl[sockindex].ssl) != 0; -} - -#endif /* USE_POLARSSL */ diff --git a/contrib/curl/lib/vtls/polarssl.h b/contrib/curl/lib/vtls/polarssl.h deleted file mode 100644 index 47af7b4173..0000000000 --- a/contrib/curl/lib/vtls/polarssl.h +++ /dev/null @@ -1,82 +0,0 @@ -#ifndef HEADER_CURL_POLARSSL_H -#define HEADER_CURL_POLARSSL_H -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) 2012 - 2016, Daniel Stenberg, , et al. - * Copyright (C) 2010, Hoi-Ho Chan, - * - * This software is licensed 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. - * - ***************************************************************************/ -#include "curl_setup.h" - -#ifdef USE_POLARSSL - -#include - -/* Called on first use PolarSSL, setup threading if supported */ -int Curl_polarssl_init(void); -void Curl_polarssl_cleanup(void); -int Curl_polarssl_data_pending(const struct connectdata *conn, int sockindex); - - -CURLcode Curl_polarssl_connect(struct connectdata *conn, int sockindex); - -CURLcode Curl_polarssl_connect_nonblocking(struct connectdata *conn, - int sockindex, - bool *done); - - /* close a SSL connection */ -void Curl_polarssl_close(struct connectdata *conn, int sockindex); - -void Curl_polarssl_session_free(void *ptr); -size_t Curl_polarssl_version(char *buffer, size_t size); -int Curl_polarssl_shutdown(struct connectdata *conn, int sockindex); - -/* Set the API backend definition to PolarSSL */ -#define CURL_SSL_BACKEND CURLSSLBACKEND_POLARSSL - -/* this backend supports the CAPATH option */ -#define have_curlssl_ca_path 1 - -/* this backends supports CURLOPT_PINNEDPUBLICKEY */ -#define have_curlssl_pinnedpubkey 1 - -/* API setup for PolarSSL */ -#define curlssl_init() Curl_polarssl_init() -#define curlssl_cleanup() Curl_polarssl_cleanup() -#define curlssl_connect Curl_polarssl_connect -#define curlssl_connect_nonblocking Curl_polarssl_connect_nonblocking -#define curlssl_session_free(x) Curl_polarssl_session_free(x) -#define curlssl_close_all(x) ((void)x) -#define curlssl_close Curl_polarssl_close -#define curlssl_shutdown(x,y) 0 -#define curlssl_set_engine(x,y) ((void)x, (void)y, CURLE_NOT_BUILT_IN) -#define curlssl_set_engine_default(x) ((void)x, CURLE_NOT_BUILT_IN) -#define curlssl_engines_list(x) ((void)x, (struct curl_slist *)NULL) -#define curlssl_version Curl_polarssl_version -#define curlssl_check_cxn(x) ((void)x, -1) -#define curlssl_data_pending(x,y) Curl_polarssl_data_pending(x, y) -#define curlssl_sha256sum(a,b,c,d) sha256(a,b,c,0) - -/* This might cause libcurl to use a weeker random! - TODO: implement proper use of Polarssl's CTR-DRBG or HMAC-DRBG and use that -*/ -#define curlssl_random(x,y,z) ((void)x, (void)y, (void)z, CURLE_NOT_BUILT_IN) - -#endif /* USE_POLARSSL */ -#endif /* HEADER_CURL_POLARSSL_H */ diff --git a/contrib/curl/lib/vtls/polarssl_threadlock.c b/contrib/curl/lib/vtls/polarssl_threadlock.c deleted file mode 100644 index b1eb7b746a..0000000000 --- a/contrib/curl/lib/vtls/polarssl_threadlock.c +++ /dev/null @@ -1,153 +0,0 @@ -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) 2013-2016, Daniel Stenberg, , et al. - * Copyright (C) 2010, 2011, Hoi-Ho Chan, - * - * This software is licensed 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. - * - ***************************************************************************/ -#include "curl_setup.h" - -#if (defined(USE_POLARSSL) || defined(USE_MBEDTLS)) && \ - (defined(USE_THREADS_POSIX) || defined(USE_THREADS_WIN32)) - -#if defined(USE_THREADS_POSIX) -# ifdef HAVE_PTHREAD_H -# include -# endif -#elif defined(USE_THREADS_WIN32) -# ifdef HAVE_PROCESS_H -# include -# endif -#endif - -#include "polarssl_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 PolarSSL. */ -static POLARSSL_MUTEX_T *mutex_buf = NULL; - -int Curl_polarsslthreadlock_thread_setup(void) -{ - int i; - int ret; - - mutex_buf = calloc(NUMT * sizeof(POLARSSL_MUTEX_T), 1); - if(!mutex_buf) - return 0; /* error, no number of threads defined */ - -#ifdef HAVE_PTHREAD_H - for(i = 0; i < NUMT; i++) { - ret = pthread_mutex_init(&mutex_buf[i], NULL); - if(ret) - return 0; /* pthread_mutex_init failed */ - } -#elif defined(HAVE_PROCESS_H) - for(i = 0; i < NUMT; i++) { - mutex_buf[i] = CreateMutex(0, FALSE, 0); - if(mutex_buf[i] == 0) - return 0; /* CreateMutex failed */ - } -#endif /* HAVE_PTHREAD_H */ - - return 1; /* OK */ -} - -int Curl_polarsslthreadlock_thread_cleanup(void) -{ - int i; - int ret; - - if(!mutex_buf) - return 0; /* error, no threads locks defined */ - -#ifdef HAVE_PTHREAD_H - for(i = 0; i < NUMT; i++) { - ret = pthread_mutex_destroy(&mutex_buf[i]); - if(ret) - return 0; /* pthread_mutex_destroy failed */ - } -#elif defined(HAVE_PROCESS_H) - for(i = 0; i < NUMT; i++) { - ret = CloseHandle(mutex_buf[i]); - if(!ret) - return 0; /* CloseHandle failed */ - } -#endif /* HAVE_PTHREAD_H */ - free(mutex_buf); - mutex_buf = NULL; - - return 1; /* OK */ -} - -int Curl_polarsslthreadlock_lock_function(int n) -{ - int ret; -#ifdef HAVE_PTHREAD_H - if(n < NUMT) { - ret = pthread_mutex_lock(&mutex_buf[n]); - if(ret) { - DEBUGF(fprintf(stderr, - "Error: polarsslthreadlock_lock_function failed\n")); - return 0; /* pthread_mutex_lock failed */ - } - } -#elif defined(HAVE_PROCESS_H) - if(n < NUMT) { - ret = (WaitForSingleObject(mutex_buf[n], INFINITE)==WAIT_FAILED?1:0); - if(ret) { - DEBUGF(fprintf(stderr, - "Error: polarsslthreadlock_lock_function failed\n")); - return 0; /* pthread_mutex_lock failed */ - } - } -#endif /* HAVE_PTHREAD_H */ - return 1; /* OK */ -} - -int Curl_polarsslthreadlock_unlock_function(int n) -{ - int ret; -#ifdef HAVE_PTHREAD_H - if(n < NUMT) { - ret = pthread_mutex_unlock(&mutex_buf[n]); - if(ret) { - DEBUGF(fprintf(stderr, - "Error: polarsslthreadlock_unlock_function failed\n")); - return 0; /* pthread_mutex_unlock failed */ - } - } -#elif defined(HAVE_PROCESS_H) - if(n < NUMT) { - ret = ReleaseMutex(mutex_buf[n]); - if(!ret) { - DEBUGF(fprintf(stderr, - "Error: polarsslthreadlock_unlock_function failed\n")); - return 0; /* pthread_mutex_lock failed */ - } - } -#endif /* HAVE_PTHREAD_H */ - return 1; /* OK */ -} - -#endif /* USE_POLARSSL || USE_MBEDTLS */ diff --git a/contrib/curl/lib/vtls/polarssl_threadlock.h b/contrib/curl/lib/vtls/polarssl_threadlock.h deleted file mode 100644 index dda5359b81..0000000000 --- a/contrib/curl/lib/vtls/polarssl_threadlock.h +++ /dev/null @@ -1,53 +0,0 @@ -#ifndef HEADER_CURL_POLARSSL_THREADLOCK_H -#define HEADER_CURL_POLARSSL_THREADLOCK_H -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) 2013-2015, Daniel Stenberg, , et al. - * Copyright (C) 2010, Hoi-Ho Chan, - * - * This software is licensed 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. - * - ***************************************************************************/ -#include "curl_setup.h" - -#if (defined USE_POLARSSL) || (defined USE_MBEDTLS) - -#if defined(USE_THREADS_POSIX) -# define POLARSSL_MUTEX_T pthread_mutex_t -#elif defined(USE_THREADS_WIN32) -# define POLARSSL_MUTEX_T HANDLE -#endif - -#if defined(USE_THREADS_POSIX) || defined(USE_THREADS_WIN32) - -int Curl_polarsslthreadlock_thread_setup(void); -int Curl_polarsslthreadlock_thread_cleanup(void); -int Curl_polarsslthreadlock_lock_function(int n); -int Curl_polarsslthreadlock_unlock_function(int n); - -#else - -#define Curl_polarsslthreadlock_thread_setup() 1 -#define Curl_polarsslthreadlock_thread_cleanup() 1 -#define Curl_polarsslthreadlock_lock_function(x) 1 -#define Curl_polarsslthreadlock_unlock_function(x) 1 - -#endif /* USE_THREADS_POSIX || USE_THREADS_WIN32 */ - -#endif /* USE_POLARSSL */ - -#endif /* HEADER_CURL_POLARSSL_THREADLOCK_H */ diff --git a/contrib/curl/lib/vtls/rustls.c b/contrib/curl/lib/vtls/rustls.c new file mode 100644 index 0000000000..ed17c8c265 --- /dev/null +++ b/contrib/curl/lib/vtls/rustls.c @@ -0,0 +1,1103 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Jacob Hoffman-Andrews, + * + * Copyright (C) kpcyrd, + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the 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 +#include + +#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 */ +#include "cipher_suite.h" +#include "rand.h" + +struct rustls_ssl_backend_data +{ + const struct rustls_client_config *config; + struct rustls_connection *conn; + size_t plain_out_buffered; + BIT(data_in_pending); + BIT(sent_shutdown); +}; + +/* For a given rustls_result error code, return the best-matching CURLcode. */ +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_RECV_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_in_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; + struct ssl_connect_data *const connssl = io_ctx->cf->ctx; + 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; + } + else if(nread == 0) + connssl->peer_closed = TRUE; + *out_n = (uintptr_t)nread; + CURL_TRC_CF(io_ctx->data, io_ctx->cf, "cf->next recv(len=%zu) -> %zd, %d", + len, nread, result); + 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, FALSE, + &result); + if(nwritten < 0) { + nwritten = 0; + if(CURLE_AGAIN == result) + ret = EAGAIN; + else + ret = EINVAL; + } + *out_n = (uintptr_t)nwritten; + CURL_TRC_CF(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_RECV_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", + (int)errorlen, errorbuf); + *err = map_error(rresult); + return -1; + } + + backend->data_in_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 is 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 will not 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_in_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_in_pending = FALSE; + } + else if(rresult == RUSTLS_RESULT_UNEXPECTED_EOF) { + failf(data, "rustls: peer closed TCP connection " + "without first closing TLS connection"); + *err = CURLE_RECV_ERROR; + nread = -1; + goto out; + } + else if(rresult != RUSTLS_RESULT_OK) { + /* n always equals 0 in this case, do not need to check it */ + char errorbuf[255]; + size_t errorlen; + rustls_error(rresult, errorbuf, sizeof(errorbuf), &errorlen); + failf(data, "rustls_connection_read: %.*s", (int)errorlen, errorbuf); + *err = CURLE_RECV_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; +} + +static CURLcode cr_flush_out(struct Curl_cfilter *cf, struct Curl_easy *data, + struct rustls_connection *rconn) +{ + struct io_ctx io_ctx; + rustls_io_result io_error; + size_t tlswritten = 0; + size_t tlswritten_total = 0; + CURLcode result = CURLE_OK; + + io_ctx.cf = cf; + io_ctx.data = data; + + while(rustls_connection_wants_write(rconn)) { + io_error = rustls_connection_write_tls(rconn, write_cb, &io_ctx, + &tlswritten); + if(io_error == EAGAIN || io_error == EWOULDBLOCK) { + CURL_TRC_CF(data, cf, "cf_send: EAGAIN after %zu bytes", + tlswritten_total); + return CURLE_AGAIN; + } + else if(io_error) { + char buffer[STRERROR_LEN]; + failf(data, "writing to socket: %s", + Curl_strerror(io_error, buffer, sizeof(buffer))); + return CURLE_SEND_ERROR; + } + if(tlswritten == 0) { + failf(data, "EOF in swrite"); + return CURLE_SEND_ERROR; + } + CURL_TRC_CF(data, cf, "cf_send: wrote %zu TLS bytes", tlswritten); + tlswritten_total += tlswritten; + } + return result; +} + +/* + * On each call: + * - Copy `plainlen` bytes into Rustls' plaintext input buffer (if > 0). + * - Fully drain Rustls' plaintext output buffer into the socket until + * we get either an error or EAGAIN/EWOULDBLOCK. + * + * it is okay to call this function with plainbuf == NULL and plainlen == 0. + * In that case, it will not 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; + size_t plainwritten = 0; + rustls_result rresult; + char errorbuf[256]; + size_t errorlen; + const unsigned char *buf = plainbuf; + size_t blen = plainlen; + ssize_t nwritten = 0; + + DEBUGASSERT(backend); + rconn = backend->conn; + DEBUGASSERT(rconn); + + CURL_TRC_CF(data, cf, "cf_send(len=%zu)", plainlen); + + /* If a previous send blocked, we already added its plain bytes + * to rustsls and must not do that again. Flush the TLS bytes and, + * if successful, deduct the previous plain bytes from the current + * send. */ + if(backend->plain_out_buffered) { + *err = cr_flush_out(cf, data, rconn); + CURL_TRC_CF(data, cf, "cf_send: flushing %zu previously added bytes -> %d", + backend->plain_out_buffered, *err); + if(*err) + return -1; + if(blen > backend->plain_out_buffered) { + blen -= backend->plain_out_buffered; + buf += backend->plain_out_buffered; + } + else + blen = 0; + nwritten += (ssize_t)backend->plain_out_buffered; + backend->plain_out_buffered = 0; + } + + if(blen > 0) { + CURL_TRC_CF(data, cf, "cf_send: adding %zu plain bytes to Rustls", blen); + rresult = rustls_connection_write(rconn, buf, blen, &plainwritten); + if(rresult != RUSTLS_RESULT_OK) { + rustls_error(rresult, errorbuf, sizeof(errorbuf), &errorlen); + failf(data, "rustls_connection_write: %.*s", (int)errorlen, errorbuf); + *err = CURLE_WRITE_ERROR; + return -1; + } + else if(plainwritten == 0) { + failf(data, "rustls_connection_write: EOF"); + *err = CURLE_WRITE_ERROR; + return -1; + } + } + + *err = cr_flush_out(cf, data, rconn); + if(*err) { + if(CURLE_AGAIN == *err) { + /* The TLS bytes may have been partially written, but we fail the + * complete send() and remember how much we already added to Rustls. */ + CURL_TRC_CF(data, cf, "cf_send: EAGAIN, remember we added %zu plain" + " bytes already to Rustls", blen); + backend->plain_out_buffered = plainwritten; + if(nwritten) { + *err = CURLE_OK; + return (ssize_t)nwritten; + } + } + return -1; + } + else + nwritten += (ssize_t)plainwritten; + + CURL_TRC_CF(data, cf, "cf_send(len=%zu) -> %d, %zd", + plainlen, *err, nwritten); + return nwritten; +} + +/* A server certificate verify callback for Rustls that always returns + RUSTLS_RESULT_OK, or in other words disable certificate verification. */ +static uint32_t +cr_verify_none(void *userdata UNUSED_PARAM, + const rustls_verify_server_cert_params *params UNUSED_PARAM) +{ + return RUSTLS_RESULT_OK; +} + +static int +read_file_into(const char *filename, + struct dynbuf *out) +{ + FILE *f = fopen(filename, FOPEN_READTEXT); + if(!f) { + return 0; + } + + while(!feof(f)) { + uint8_t buf[256]; + size_t rr = fread(buf, 1, sizeof(buf), f); + if(rr == 0 || + CURLE_OK != Curl_dyn_addn(out, buf, rr)) { + fclose(f); + return 0; + } + } + + return fclose(f) == 0; +} + +static void +cr_get_selected_ciphers(struct Curl_easy *data, + const char *ciphers12, + const char *ciphers13, + const struct rustls_supported_ciphersuite **selected, + size_t *selected_size) +{ + size_t supported_len = *selected_size; + size_t default_len = rustls_default_crypto_provider_ciphersuites_len(); + const struct rustls_supported_ciphersuite *entry; + const char *ciphers = ciphers12; + size_t count = 0, default13_count = 0, i, j; + const char *ptr, *end; + + DEBUGASSERT(default_len <= supported_len); + + if(!ciphers13) { + /* Add default TLSv1.3 ciphers to selection */ + for(j = 0; j < default_len; j++) { + entry = rustls_default_crypto_provider_ciphersuites_get(j); + if(rustls_supported_ciphersuite_protocol_version(entry) != + RUSTLS_TLS_VERSION_TLSV1_3) + continue; + + selected[count++] = entry; + } + + default13_count = count; + + if(!ciphers) + ciphers = ""; + } + else + ciphers = ciphers13; + +add_ciphers: + for(ptr = ciphers; ptr[0] != '\0' && count < supported_len; ptr = end) { + uint16_t id = Curl_cipher_suite_walk_str(&ptr, &end); + + /* Check if cipher is supported */ + if(id) { + for(i = 0; i < supported_len; i++) { + entry = rustls_default_crypto_provider_ciphersuites_get(i); + if(rustls_supported_ciphersuite_get_suite(entry) == id) + break; + } + if(i == supported_len) + id = 0; + } + if(!id) { + if(ptr[0] != '\0') + infof(data, "rustls: unknown cipher in list: \"%.*s\"", + (int) (end - ptr), ptr); + continue; + } + + /* No duplicates allowed (so selected cannot overflow) */ + for(i = 0; i < count && selected[i] != entry; i++); + if(i < count) { + if(i >= default13_count) + infof(data, "rustls: duplicate cipher in list: \"%.*s\"", + (int) (end - ptr), ptr); + continue; + } + + selected[count++] = entry; + } + + if(ciphers == ciphers13 && ciphers12) { + ciphers = ciphers12; + goto add_ciphers; + } + + if(!ciphers12) { + /* Add default TLSv1.2 ciphers to selection */ + for(j = 0; j < default_len; j++) { + entry = rustls_default_crypto_provider_ciphersuites_get(j); + if(rustls_supported_ciphersuite_protocol_version(entry) == + RUSTLS_TLS_VERSION_TLSV1_3) + continue; + + /* No duplicates allowed (so selected cannot overflow) */ + for(i = 0; i < count && selected[i] != entry; i++); + if(i < count) + continue; + + selected[count++] = entry; + } + } + + *selected_size = count; +} + +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_crypto_provider_builder *custom_provider_builder = NULL; + const struct rustls_crypto_provider *custom_provider = NULL; + struct rustls_connection *rconn = NULL; + struct rustls_client_config_builder *config_builder = NULL; + const struct rustls_root_cert_store *roots = NULL; + struct rustls_root_cert_store_builder *roots_builder = NULL; + struct rustls_web_pki_server_cert_verifier_builder *verifier_builder = NULL; + struct rustls_server_cert_verifier *server_cert_verifier = 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; + char errorbuf[256]; + size_t errorlen; + rustls_result result; + + DEBUGASSERT(backend); + rconn = backend->conn; + + { + uint16_t tls_versions[2] = { + RUSTLS_TLS_VERSION_TLSV1_2, + RUSTLS_TLS_VERSION_TLSV1_3, + }; + size_t tls_versions_len = 2; + const struct rustls_supported_ciphersuite **cipher_suites; + size_t cipher_suites_len = + rustls_default_crypto_provider_ciphersuites_len(); + + 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: + break; + case CURL_SSLVERSION_TLSv1_3: + tls_versions[0] = RUSTLS_TLS_VERSION_TLSV1_3; + tls_versions_len = 1; + break; + default: + failf(data, "rustls: unsupported minimum TLS version value"); + return CURLE_SSL_ENGINE_INITFAILED; + } + + switch(conn_config->version_max) { + case CURL_SSLVERSION_MAX_DEFAULT: + case CURL_SSLVERSION_MAX_NONE: + case CURL_SSLVERSION_MAX_TLSv1_3: + break; + case CURL_SSLVERSION_MAX_TLSv1_2: + if(tls_versions[0] == RUSTLS_TLS_VERSION_TLSV1_2) { + tls_versions_len = 1; + break; + } + FALLTHROUGH(); + case CURL_SSLVERSION_MAX_TLSv1_1: + case CURL_SSLVERSION_MAX_TLSv1_0: + default: + failf(data, "rustls: unsupported maximum TLS version value"); + return CURLE_SSL_ENGINE_INITFAILED; + } + + cipher_suites = malloc(sizeof(cipher_suites) * (cipher_suites_len)); + if(!cipher_suites) + return CURLE_OUT_OF_MEMORY; + + cr_get_selected_ciphers(data, + conn_config->cipher_list, + conn_config->cipher_list13, + cipher_suites, &cipher_suites_len); + if(cipher_suites_len == 0) { + failf(data, "rustls: no supported cipher in list"); + free(cipher_suites); + return CURLE_SSL_CIPHER; + } + + result = rustls_crypto_provider_builder_new_from_default( + &custom_provider_builder); + if(result != RUSTLS_RESULT_OK) { + failf(data, + "rustls: failed to create crypto provider builder from default"); + return CURLE_SSL_ENGINE_INITFAILED; + } + + result = + rustls_crypto_provider_builder_set_cipher_suites( + custom_provider_builder, + cipher_suites, + cipher_suites_len); + if(result != RUSTLS_RESULT_OK) { + failf(data, + "rustls: failed to set ciphersuites for crypto provider builder"); + rustls_crypto_provider_builder_free(custom_provider_builder); + return CURLE_SSL_ENGINE_INITFAILED; + } + + result = rustls_crypto_provider_builder_build( + custom_provider_builder, &custom_provider); + if(result != RUSTLS_RESULT_OK) { + failf(data, "rustls: failed to build custom crypto provider"); + rustls_crypto_provider_builder_free(custom_provider_builder); + return CURLE_SSL_ENGINE_INITFAILED; + } + + result = rustls_client_config_builder_new_custom(custom_provider, + tls_versions, + tls_versions_len, + &config_builder); + free(cipher_suites); + if(result != RUSTLS_RESULT_OK) { + failf(data, "rustls: failed to create client config"); + return CURLE_SSL_ENGINE_INITFAILED; + } + } + + rustls_crypto_provider_builder_free(custom_provider_builder); + rustls_crypto_provider_free(custom_provider); + + 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); + } + else if(ca_info_blob || ssl_cafile) { + roots_builder = rustls_root_cert_store_builder_new(); + + if(ca_info_blob) { + /* Enable strict parsing only if verification is not disabled. */ + result = rustls_root_cert_store_builder_add_pem(roots_builder, + 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_builder_free(roots_builder); + rustls_client_config_builder_free(config_builder); + return CURLE_SSL_CACERT_BADFILE; + } + } + else if(ssl_cafile) { + /* Enable strict parsing only if verification is not disabled. */ + result = rustls_root_cert_store_builder_load_roots_from_file( + roots_builder, ssl_cafile, verifypeer); + if(result != RUSTLS_RESULT_OK) { + failf(data, "rustls: failed to load trusted certificates"); + rustls_root_cert_store_builder_free(roots_builder); + rustls_client_config_builder_free(config_builder); + return CURLE_SSL_CACERT_BADFILE; + } + } + + result = rustls_root_cert_store_builder_build(roots_builder, &roots); + rustls_root_cert_store_builder_free(roots_builder); + if(result != RUSTLS_RESULT_OK) { + failf(data, "rustls: failed to build trusted root certificate store"); + rustls_client_config_builder_free(config_builder); + return CURLE_SSL_CACERT_BADFILE; + } + + verifier_builder = rustls_web_pki_server_cert_verifier_builder_new(roots); + rustls_root_cert_store_free(roots); + + if(conn_config->CRLfile) { + struct dynbuf crl_contents; + Curl_dyn_init(&crl_contents, SIZE_MAX); + if(!read_file_into(conn_config->CRLfile, &crl_contents)) { + failf(data, "rustls: failed to read revocation list file"); + Curl_dyn_free(&crl_contents); + rustls_web_pki_server_cert_verifier_builder_free(verifier_builder); + return CURLE_SSL_CRL_BADFILE; + } + + result = rustls_web_pki_server_cert_verifier_builder_add_crl( + verifier_builder, + Curl_dyn_uptr(&crl_contents), + Curl_dyn_len(&crl_contents)); + Curl_dyn_free(&crl_contents); + if(result != RUSTLS_RESULT_OK) { + failf(data, "rustls: failed to parse revocation list"); + rustls_web_pki_server_cert_verifier_builder_free(verifier_builder); + return CURLE_SSL_CRL_BADFILE; + } + } + + result = rustls_web_pki_server_cert_verifier_builder_build( + verifier_builder, &server_cert_verifier); + rustls_web_pki_server_cert_verifier_builder_free(verifier_builder); + if(result != RUSTLS_RESULT_OK) { + failf(data, "rustls: failed to build certificate verifier"); + rustls_server_cert_verifier_free(server_cert_verifier); + rustls_client_config_builder_free(config_builder); + return CURLE_SSL_CACERT_BADFILE; + } + + rustls_client_config_builder_set_server_verifier(config_builder, + server_cert_verifier); + rustls_server_cert_verifier_free(server_cert_verifier); + } + + result = rustls_client_config_builder_build( + config_builder, + &backend->config); + if(result != RUSTLS_RESULT_OK) { + failf(data, "rustls: failed to build client config"); + rustls_client_config_free(backend->config); + return CURLE_SSL_ENGINE_INITFAILED; + } + + DEBUGASSERT(rconn == NULL); + result = rustls_client_connection_new(backend->config, + connssl->peer.hostname, &rconn); + if(result != RUSTLS_RESULT_OK) { + rustls_error(result, errorbuf, sizeof(errorbuf), &errorlen); + failf(data, "rustls_client_connection_new: %.*s", (int)errorlen, errorbuf); + return CURLE_COULDNT_CONNECT; + } + DEBUGASSERT(rconn); + rustls_connection_set_userdata(rconn, backend); + backend->conn = rconn; + return CURLE_OK; +} + +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); + + CURL_TRC_CF(data, cf, "cr_connect_common, state=%d", connssl->state); + *done = FALSE; + if(!backend->conn) { + result = cr_init_backend(cf, data, + (struct rustls_ssl_backend_data *)connssl->backend); + CURL_TRC_CF(data, cf, "cr_connect_common, init backend -> %d", result); + if(result != CURLE_OK) { + return result; + } + 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. + */ + connssl->io_need = CURL_SSL_IO_NEED_NONE; + if(!rustls_connection_is_handshaking(rconn)) { + /* Rustls claims it is no longer handshaking *before* it has + * send its FINISHED message off. We attempt to let it write + * one more time. Oh my. + */ + cr_set_negotiated_alpn(cf, data, rconn); + cr_send(cf, data, NULL, 0, &tmperr); + if(tmperr == CURLE_AGAIN) { + connssl->io_need = CURL_SSL_IO_NEED_SEND; + return CURLE_OK; + } + else if(tmperr != CURLE_OK) { + return tmperr; + } + /* REALLY Done with the handshake. */ + { + uint16_t proto = rustls_connection_get_protocol_version(rconn); + uint16_t cipher = rustls_connection_get_negotiated_ciphersuite(rconn); + char buf[64] = ""; + const char *ver = "TLS version unknown"; + if(proto == RUSTLS_TLS_VERSION_TLSV1_3) + ver = "TLSv1.3"; + if(proto == RUSTLS_TLS_VERSION_TLSV1_2) + ver = "TLSv1.2"; + Curl_cipher_suite_get_str(cipher, buf, sizeof(buf), true); + infof(data, "rustls: handshake complete, %s, cipher: %s", + ver, buf); + } + connssl->state = ssl_connection_complete; + *done = TRUE; + return CURLE_OK; + } + + connssl->connecting_state = ssl_connect_2; + wants_read = rustls_connection_wants_read(rconn); + wants_write = rustls_connection_wants_write(rconn) || + backend->plain_out_buffered; + DEBUGASSERT(wants_read || wants_write); + writefd = wants_write ? sockfd : CURL_SOCKET_BAD; + readfd = wants_read ? sockfd : CURL_SOCKET_BAD; + + /* 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 %" FMT_TIMEDIFF_T " ms", + socket_check_timeout); + return CURLE_OPERATION_TIMEDOUT; + } + if(0 == what) { + CURL_TRC_CF(data, cf, "Curl_socket_check: %s would block", + wants_read&&wants_write ? "writing and reading" : + wants_write ? "writing" : "reading"); + if(wants_write) + connssl->io_need |= CURL_SSL_IO_NEED_SEND; + if(wants_read) + connssl->io_need |= CURL_SSL_IO_NEED_RECV; + return CURLE_OK; + } + /* socket is readable or writable */ + + if(wants_write) { + CURL_TRC_CF(data, cf, "rustls_connection wants us to write_tls."); + cr_send(cf, data, NULL, 0, &tmperr); + if(tmperr == CURLE_AGAIN) { + CURL_TRC_CF(data, cf, "writing would block"); + /* fall through */ + } + else if(tmperr != CURLE_OK) { + return tmperr; + } + } + + if(wants_read) { + CURL_TRC_CF(data, cf, "rustls_connection wants us to read_tls."); + if(tls_recv_more(cf, data, &tmperr) < 0) { + if(tmperr == CURLE_AGAIN) { + CURL_TRC_CF(data, cf, "reading would block"); + /* fall through */ + } + else if(tmperr == CURLE_RECV_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 cannot 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, struct Curl_easy *data) +{ + bool done; /* unused */ + return cr_connect_common(cf, data, true, &done); +} + +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 CURLcode +cr_shutdown(struct Curl_cfilter *cf, + struct Curl_easy *data, + bool send_shutdown, bool *done) +{ + struct ssl_connect_data *connssl = cf->ctx; + struct rustls_ssl_backend_data *backend = + (struct rustls_ssl_backend_data *)connssl->backend; + CURLcode result = CURLE_OK; + ssize_t nwritten, nread; + char buf[1024]; + size_t i; + + DEBUGASSERT(backend); + if(!backend->conn || cf->shutdown) { + *done = TRUE; + goto out; + } + + connssl->io_need = CURL_SSL_IO_NEED_NONE; + *done = FALSE; + + if(!backend->sent_shutdown) { + /* do this only once */ + backend->sent_shutdown = TRUE; + if(send_shutdown) { + rustls_connection_send_close_notify(backend->conn); + } + } + + nwritten = cr_send(cf, data, NULL, 0, &result); + if(nwritten < 0) { + if(result == CURLE_AGAIN) { + connssl->io_need = CURL_SSL_IO_NEED_SEND; + result = CURLE_OK; + goto out; + } + DEBUGASSERT(result); + CURL_TRC_CF(data, cf, "shutdown send failed: %d", result); + goto out; + } + + for(i = 0; i < 10; ++i) { + nread = cr_recv(cf, data, buf, (int)sizeof(buf), &result); + if(nread <= 0) + break; + } + + if(nread > 0) { + /* still data coming in? */ + } + else if(nread == 0) { + /* We got the close notify alert and are done. */ + *done = TRUE; + } + else if(result == CURLE_AGAIN) { + connssl->io_need = CURL_SSL_IO_NEED_RECV; + result = CURLE_OK; + } + else { + DEBUGASSERT(result); + CURL_TRC_CF(data, cf, "shutdown, error: %d", result); + } + +out: + cf->shutdown = (result || *done); + return result; +} + +static void +cr_close(struct Curl_cfilter *cf, struct Curl_easy *data) +{ + struct ssl_connect_data *connssl = cf->ctx; + struct rustls_ssl_backend_data *backend = + (struct rustls_ssl_backend_data *)connssl->backend; + + (void)data; + DEBUGASSERT(backend); + if(backend->conn) { + 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); +} + +static CURLcode +cr_random(struct Curl_easy *data, unsigned char *entropy, size_t length) +{ + rustls_result rresult = 0; + (void)data; + rresult = + rustls_default_crypto_provider_random(entropy, length); + return map_error(rresult); +} + +const struct Curl_ssl Curl_ssl_rustls = { + { CURLSSLBACKEND_RUSTLS, "rustls" }, + SSLSUPP_CAINFO_BLOB | /* supports */ + SSLSUPP_HTTPS_PROXY | + SSLSUPP_CIPHER_LIST | + SSLSUPP_TLS13_CIPHERSUITES, + sizeof(struct rustls_ssl_backend_data), + + Curl_none_init, /* init */ + Curl_none_cleanup, /* cleanup */ + cr_version, /* version */ + Curl_none_check_cxn, /* check_cxn */ + cr_shutdown, /* shutdown */ + cr_data_pending, /* data_pending */ + cr_random, /* random */ + Curl_none_cert_status_request, /* cert_status_request */ + cr_connect_blocking, /* connect */ + cr_connect_nonblocking, /* connect_nonblocking */ + Curl_ssl_adjust_pollset, /* adjust_pollset */ + cr_get_internals, /* get_internals */ + cr_close, /* close_one */ + Curl_none_close_all, /* close_all */ + 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 */ + cr_recv, /* recv decrypted data */ + cr_send, /* send data to encrypt */ + NULL, /* get_channel_binding */ +}; + +#endif /* USE_RUSTLS */ diff --git a/contrib/curl/lib/vtls/rustls.h b/contrib/curl/lib/vtls/rustls.h new file mode 100644 index 0000000000..bfbe23de3e --- /dev/null +++ b/contrib/curl/lib/vtls/rustls.h @@ -0,0 +1,35 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Jacob Hoffman-Andrews, + * + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the 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/contrib/curl/lib/vtls/schannel.c b/contrib/curl/lib/vtls/schannel.c index bd92399563..b39e99aaa7 100644 --- a/contrib/curl/lib/vtls/schannel.c +++ b/contrib/curl/lib/vtls/schannel.c @@ -5,13 +5,13 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 2012 - 2016, Marc Hoersken, - * Copyright (C) 2012, Mark Salisbury, - * Copyright (C) 2012 - 2017, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. + * Copyright (C) Marc Hoersken, + * Copyright (C) Mark Salisbury, * * This software is licensed 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. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -20,22 +20,13 @@ * 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 + * Source file for all Schannel-specific code for the TLS/SSL layer. No code * but vtls.c should ever call or use these functions. - * - */ - -/* - * Based upon the PolarSSL implementation in polarssl.c and polarssl.h: - * Copyright (C) 2010, 2011, Hoi-Ho Chan, - * - * Based upon the CyaSSL implementation in cyassl.c and cyassl.h: - * Copyright (C) 1998 - 2012, Daniel Stenberg, , et al. - * - * Thanks for code and inspiration! */ #include "curl_setup.h" @@ -43,25 +34,28 @@ #ifdef USE_SCHANNEL #ifndef USE_WINDOWS_SSPI -# error "Can't compile SCHANNEL support without SSPI." +# error "cannot compile SCHANNEL support without SSPI." #endif -#include "curl_sspi.h" #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 readyness */ +#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 "system_win32.h" -#include "hostcheck.h" +#include "multiif.h" +#include "version_win32.h" +#include "rand.h" - /* The last #include file should be: */ +/* The last #include file should be: */ #include "curl_memory.h" #include "memdebug.h" @@ -74,18 +68,86 @@ # 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__) */ -static Curl_recv schannel_recv; -static Curl_send schannel_send; +#ifndef CALG_SHA_256 +# define CALG_SHA_256 0x0000800c +#endif -#ifdef _WIN32_WCE -static CURLcode verify_certificate(struct connectdata *conn, int sockindex); +#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) { @@ -103,227 +165,1050 @@ static void InitSecBufferDesc(SecBufferDesc *desc, SecBuffer *BufArr, } static CURLcode -schannel_connect_step1(struct connectdata *conn, int sockindex) +schannel_set_ssl_version_min_max(DWORD *enabled_protocols, + struct Curl_cfilter *cf, + struct Curl_easy *data) { - ssize_t written = -1; - struct Curl_easy *data = conn->data; - struct ssl_connect_data *connssl = &conn->ssl[sockindex]; - SecBuffer outbuf; - SecBufferDesc outbuf_desc; - SecBuffer inbuf; - SecBufferDesc inbuf_desc; -#ifdef HAS_ALPN - unsigned char alpn_buffer[128]; + struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); + long ssl_version = conn_config->version; + long ssl_version_max = (long)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 - SCHANNEL_CRED schannel_cred; - SECURITY_STATUS sspi_status = SEC_E_OK; - struct curl_schannel_cred *old_cred = NULL; - struct in_addr addr; -#ifdef ENABLE_IPV6 - struct in6_addr addr6; + CIPHEROPTION(CALG_RSA_KEYX), + CIPHEROPTION(CALG_DES), +#ifdef CALG_3DES_112 + CIPHEROPTION(CALG_3DES_112), #endif - TCHAR *host_name; - CURLcode result; - const char * const hostname = SSL_IS_PROXY() ? conn->http_proxy.host.name : - conn->host.name; + 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}, +}; - infof(data, "schannel: SSL/TLS connection with %s port %hu (step 1/3)\n", - hostname, conn->remote_port); +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; - if(Curl_verify_windows_version(5, 1, 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: WinSSL version is old and may not be able to " - "connect to some servers due to lack of SNI, algorithms, etc.\n"); + 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 */ +} -#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. */ - connssl->use_alpn = conn->bits.tls_enable_alpn && - !GetProcAddress(GetModuleHandleA("ntdll"), - "wine_get_version") && - Curl_verify_windows_version(6, 3, PLATFORM_WINNT, - VERSION_GREATER_THAN_EQUAL); -#else - connssl->use_alpn = false; +#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_ID)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 = (DWORD)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 - connssl->cred = NULL; +static bool algo(const char *check, char *namep, size_t nlen) +{ + return (strlen(check) == nlen) && !strncmp(check, namep, nlen); +} - /* check for an existing re-usable credential handle */ - if(data->set.general_ssl.sessionid) { - Curl_ssl_sessionid_lock(conn); - if(!Curl_ssl_getsessionid(conn, (void **)&old_cred, NULL, sockindex)) { - connssl->cred = old_cred; - infof(data, "schannel: re-using existing credential handle\n"); +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); - /* increment the reference counter of the credential/session handle */ - connssl->cred->refcount++; - infof(data, "schannel: incremented credential handle refcount = %d\n", - connssl->cred->refcount); +#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")); } - Curl_ssl_sessionid_unlock(conn); + 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(!connssl->cred) { - /* setup Schannel API options */ - memset(&schannel_cred, 0, sizeof(schannel_cred)); - schannel_cred.dwVersion = SCHANNEL_CRED_VERSION; + 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(conn->ssl_config.verifypeer) { -#ifdef _WIN32_WCE - /* certificate validation on CE doesn't seem to work right; we'll - do it following a more manual process. */ - schannel_cred.dwFlags = SCH_CRED_MANUAL_CRED_VALIDATION | - SCH_CRED_IGNORE_NO_REVOCATION_CHECK | - SCH_CRED_IGNORE_REVOCATION_OFFLINE; + if(!ssl_config->auto_client_cert) { + flags &= ~(DWORD)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%lx", + 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%lx", + 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) { + char *path_utf8 = + curlx_convert_tchar_to_UTF8(cert_store_path); + failf(data, "schannel: Failed to open cert store %lx %s, " + "last error is 0x%lx", + cert_store_name, + (path_utf8 ? path_utf8 : "(unknown)"), + GetLastError()); + free(cert_store_path); + curlx_unicodefree(path_utf8); + 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 - schannel_cred.dwFlags = SCH_CRED_AUTO_CRED_VALIDATION; - /* TODO s/data->set.ssl.no_revoke/SSL_SET_OPTION(no_revoke)/g */ - if(data->set.ssl.no_revoke) - schannel_cred.dwFlags |= SCH_CRED_IGNORE_NO_REVOCATION_CHECK | - SCH_CRED_IGNORE_REVOCATION_OFFLINE; - else - schannel_cred.dwFlags |= SCH_CRED_REVOCATION_CHECK_CHAIN; + 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 - if(data->set.ssl.no_revoke) - infof(data, "schannel: disabled server certificate revocation " - "checks\n"); - else - infof(data, "schannel: checking server certificate revocation\n"); + + /* 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 *nameEnd; + + 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)) { + size_t n; + char *namep; + nameEnd = strchr(startCur, ':'); + n = nameEnd ? (size_t)(nameEnd - startCur) : strlen(startCur); + namep = startCur; + + if(disable_aes_gcm_sha384 && + algo("TLS_AES_256_GCM_SHA384", namep, n)) { + disable_aes_gcm_sha384 = FALSE; + } + else if(disable_aes_gcm_sha256 + && algo("TLS_AES_128_GCM_SHA256", namep, n)) { + disable_aes_gcm_sha256 = FALSE; + } + else if(disable_chacha_poly + && algo("TLS_CHACHA20_POLY1305_SHA256", namep, n)) { + disable_chacha_poly = FALSE; + } + else if(disable_aes_ccm_8_sha256 + && algo("TLS_AES_128_CCM_8_SHA256", namep, n)) { + disable_aes_ccm_8_sha256 = FALSE; + } + else if(disable_aes_ccm_sha256 + && algo("TLS_AES_128_CCM_SHA256", namep, n)) { + disable_aes_ccm_sha256 = FALSE; + } + else { + failf(data, "schannel: Unknown TLS 1.3 cipher: %.*s", (int)n, namep); + return CURLE_SSL_CIPHER; + } + + startCur = nameEnd; + if(startCur) + startCur++; + + algCount++; + } } - else { - schannel_cred.dwFlags = SCH_CRED_MANUAL_CRED_VALIDATION | - SCH_CRED_IGNORE_NO_REVOCATION_CHECK | - SCH_CRED_IGNORE_REVOCATION_OFFLINE; - infof(data, "schannel: disabled server certificate revocation checks\n"); + + 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; } - if(!conn->ssl_config.verifyhost) { - schannel_cred.dwFlags |= SCH_CRED_NO_SERVERNAME_CHECK; - infof(data, "schannel: verifyhost setting prevents Schannel from " - "comparing the supplied target name with the subject " - "names in server certificates.\n"); + /* 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++; } - switch(conn->ssl_config.version) { - case CURL_SSLVERSION_DEFAULT: - case CURL_SSLVERSION_TLSv1: - schannel_cred.grbitEnabledProtocols = SP_PROT_TLS1_0_CLIENT | - SP_PROT_TLS1_1_CLIENT | - SP_PROT_TLS1_2_CLIENT; - break; - case CURL_SSLVERSION_TLSv1_0: - schannel_cred.grbitEnabledProtocols = SP_PROT_TLS1_0_CLIENT; - break; - case CURL_SSLVERSION_TLSv1_1: - schannel_cred.grbitEnabledProtocols = SP_PROT_TLS1_1_CLIENT; - break; - case CURL_SSLVERSION_TLSv1_2: - schannel_cred.grbitEnabledProtocols = SP_PROT_TLS1_2_CLIENT; - break; - case CURL_SSLVERSION_TLSv1_3: - failf(data, "Schannel: TLS 1.3 is not yet supported"); - return CURLE_SSL_CONNECT_ERROR; - case CURL_SSLVERSION_SSLv3: - schannel_cred.grbitEnabledProtocols = SP_PROT_SSL3_CLIENT; - break; - case CURL_SSLVERSION_SSLv2: - schannel_cred.grbitEnabledProtocols = SP_PROT_SSL2_CLIENT; - break; - default: - failf(data, "Unrecognized parameter passed via CURLOPT_SSLVERSION"); - return CURLE_SSL_CONNECT_ERROR; + /* 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++; } - /* allocate memory for the re-usable credential handle */ - connssl->cred = (struct curl_schannel_cred *) - malloc(sizeof(struct curl_schannel_cred)); - if(!connssl->cred) { - failf(data, "schannel: unable to allocate memory"); - return CURLE_OUT_OF_MEMORY; + /* + 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 = (DWORD)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 = + Curl_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 + does not 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; } - memset(connssl->cred, 0, sizeof(struct curl_schannel_cred)); - connssl->cred->refcount = 1; - /* https://msdn.microsoft.com/en-us/library/windows/desktop/aa374716.aspx - */ +#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, + Curl_pSecFn->AcquireCredentialsHandle(NULL, (TCHAR*)UNISP_NAME, SECPKG_CRED_OUTBOUND, NULL, &schannel_cred, NULL, NULL, - &connssl->cred->cred_handle, - &connssl->cred->time_stamp); + &backend->cred->cred_handle, + &backend->cred->time_stamp); + } - if(sspi_status != SEC_E_OK) { - if(sspi_status == SEC_E_WRONG_PRINCIPAL) - failf(data, "schannel: SNI or certificate check failed: %s", - Curl_sspi_strerror(conn, sspi_status)); - else - failf(data, "schannel: AcquireCredentialsHandle failed: %s", - Curl_sspi_strerror(conn, sspi_status)); - Curl_safefree(connssl->cred); +#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->peer.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 does not 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 does not seem to work right; we will + * do it following a more manual process. */ + backend->use_manual_cred_validation = true; +#else +#error "compiler too old to support Windows CE requisite manual cert verify" +#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.cache_session) { + Curl_ssl_sessionid_lock(data); + if(!Curl_ssl_getsessionid(cf, data, &connssl->peer, + (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(Curl_inet_pton(AF_INET, hostname, &addr) -#ifdef ENABLE_IPV6 - || Curl_inet_pton(AF_INET6, hostname, &addr6) -#endif - ) { - infof(data, "schannel: using IP address, SNI is not supported by OS.\n"); + if(connssl->peer.type != CURL_SSL_PEER_DNS) { + infof(data, "schannel: using IP address, SNI is not supported by OS."); } #ifdef HAS_ALPN - if(connssl->use_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 the buffer. */ - extension_len = (unsigned int *)(&alpn_buffer[cur]); - cur += sizeof(unsigned int); + 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 *)&alpn_buffer[cur] = + *(unsigned int *)(void *)&alpn_buffer[cur] = SecApplicationProtocolNegotiationExt_ALPN; - cur += sizeof(unsigned int); + 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*)(&alpn_buffer[cur]); - cur += sizeof(unsigned short); + list_len = (unsigned short*)(void *)(&alpn_buffer[cur]); + cur += (int)sizeof(unsigned short); list_start_index = cur; -#ifdef USE_NGHTTP2 - if(data->set.httpversion >= CURL_HTTP_VERSION_2) { - memcpy(&alpn_buffer[cur], NGHTTP2_PROTO_ALPN, NGHTTP2_PROTO_ALPN_LEN); - cur += NGHTTP2_PROTO_ALPN_LEN; - infof(data, "schannel: ALPN, offering %s\n", NGHTTP2_PROTO_VERSION_ID); + result = Curl_alpn_to_proto_buf(&proto, connssl->alpn); + if(result) { + failf(data, "Error setting ALPN"); + return CURLE_SSL_CONNECT_ERROR; } -#endif - - alpn_buffer[cur++] = ALPN_HTTP_1_1_LENGTH; - memcpy(&alpn_buffer[cur], ALPN_HTTP_1_1, ALPN_HTTP_1_1_LENGTH); - cur += ALPN_HTTP_1_1_LENGTH; - infof(data, "schannel: ALPN, offering %s\n", ALPN_HTTP_1_1); + memcpy(&alpn_buffer[cur], proto.data, proto.len); + cur += proto.len; *list_len = curlx_uitous(cur - list_start_index); - *extension_len = *list_len + sizeof(unsigned int) + sizeof(unsigned short); + *extension_len = (unsigned int)(*list_len + + sizeof(unsigned int) + 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 - { + else { InitSecBuffer(&inbuf, SECBUFFER_EMPTY, NULL, 0); InitSecBufferDesc(&inbuf_desc, &inbuf, 1); } @@ -336,69 +1221,89 @@ schannel_connect_step1(struct connectdata *conn, int sockindex) InitSecBuffer(&outbuf, SECBUFFER_EMPTY, NULL, 0); InitSecBufferDesc(&outbuf_desc, &outbuf, 1); - /* setup request flags */ - connssl->req_flags = ISC_REQ_SEQUENCE_DETECT | ISC_REQ_REPLAY_DETECT | + /* 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 */ - connssl->ctxt = (struct curl_schannel_ctxt *) - malloc(sizeof(struct curl_schannel_ctxt)); - if(!connssl->ctxt) { + 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; } - memset(connssl->ctxt, 0, sizeof(struct curl_schannel_ctxt)); - - host_name = Curl_convert_UTF8_to_tchar(hostname); - if(!host_name) - 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 + At the moment we do not pass inbuf unless we are using ALPN since we only + use it for that, and WINE (for which we currently disable ALPN) is giving us problems with inbuf regardless. https://github.com/curl/curl/issues/983 */ - sspi_status = s_pSecFn->InitializeSecurityContext( - &connssl->cred->cred_handle, NULL, host_name, connssl->req_flags, 0, 0, - (connssl->use_alpn ? &inbuf_desc : NULL), - 0, &connssl->ctxt->ctxt_handle, - &outbuf_desc, &connssl->ret_flags, &connssl->ctxt->time_stamp); - - Curl_unicodefree(host_name); + sspi_status = Curl_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) { - if(sspi_status == SEC_E_WRONG_PRINCIPAL) + 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(conn, sspi_status)); - else + 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(conn, sspi_status)); - Curl_safefree(connssl->ctxt); - return CURLE_SSL_CONNECT_ERROR; + Curl_sspi_strerror(sspi_status, buffer, sizeof(buffer))); + return CURLE_SSL_CONNECT_ERROR; + } } - infof(data, "schannel: sending initial handshake data: " - "sending %lu bytes...\n", outbuf.cbBuffer); + DEBUGF(infof(data, "schannel: sending initial handshake data: " + "sending %lu bytes.", outbuf.cbBuffer)); /* send initial handshake data which is now stored in output buffer */ - result = Curl_write_plain(conn, conn->sock[sockindex], outbuf.pvBuffer, - outbuf.cbBuffer, &written); - s_pSecFn->FreeContextBuffer(outbuf.pvBuffer); + written = Curl_conn_cf_send(cf->next, data, + outbuf.pvBuffer, outbuf.cbBuffer, FALSE, + &result); + Curl_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; } - infof(data, "schannel: sent initial handshake data: " - "sent %zd bytes\n", written); + DEBUGF(infof(data, "schannel: sent initial handshake data: " + "sent %zd bytes", written)); - connssl->recv_unrecoverable_err = CURLE_OK; - connssl->recv_sspi_close_notify = false; - connssl->recv_connection_closed = false; + 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; @@ -407,88 +1312,91 @@ schannel_connect_step1(struct connectdata *conn, int sockindex) } static CURLcode -schannel_connect_step2(struct connectdata *conn, int sockindex) +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; - struct Curl_easy *data = conn->data; - struct ssl_connect_data *connssl = &conn->ssl[sockindex]; unsigned char *reallocated_buffer; - size_t reallocated_length; SecBuffer outbuf[3]; SecBufferDesc outbuf_desc; SecBuffer inbuf[2]; SecBufferDesc inbuf_desc; SECURITY_STATUS sspi_status = SEC_E_OK; - TCHAR *host_name; CURLcode result; bool doread; - const char * const hostname = SSL_IS_PROXY() ? conn->http_proxy.host.name : - conn->host.name; + const char *pubkey_ptr; - doread = (connssl->connecting_state != ssl_connect_2_writing) ? TRUE : FALSE; + DEBUGASSERT(backend); - infof(data, "schannel: SSL/TLS connection with %s port %hu (step 2/3)\n", - hostname, conn->remote_port); + doread = (connssl->io_need & CURL_SSL_IO_NEED_SEND) ? FALSE : TRUE; + connssl->io_need = CURL_SSL_IO_NEED_NONE; - if(!connssl->cred || !connssl->ctxt) + DEBUGF(infof(data, + "schannel: SSL/TLS connection with %s port %d (step 2/3)", + connssl->peer.hostname, connssl->peer.port)); + + if(!backend->cred || !backend->ctxt) return CURLE_SSL_CONNECT_ERROR; /* buffer to store previously received and decrypted data */ - if(connssl->decdata_buffer == NULL) { - connssl->decdata_offset = 0; - connssl->decdata_length = CURL_SCHANNEL_BUFFER_INIT_SIZE; - connssl->decdata_buffer = malloc(connssl->decdata_length); - if(connssl->decdata_buffer == NULL) { + 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(connssl->encdata_buffer == NULL) { - connssl->encdata_offset = 0; - connssl->encdata_length = CURL_SCHANNEL_BUFFER_INIT_SIZE; - connssl->encdata_buffer = malloc(connssl->encdata_length); - if(connssl->encdata_buffer == NULL) { + 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(connssl->encdata_length - connssl->encdata_offset < + if(backend->encdata_length - backend->encdata_offset < CURL_SCHANNEL_BUFFER_FREE_SIZE) { /* increase internal encrypted data buffer */ - reallocated_length = connssl->encdata_offset + + size_t reallocated_length = backend->encdata_offset + CURL_SCHANNEL_BUFFER_FREE_SIZE; - reallocated_buffer = realloc(connssl->encdata_buffer, + reallocated_buffer = realloc(backend->encdata_buffer, reallocated_length); - if(reallocated_buffer == NULL) { + if(!reallocated_buffer) { failf(data, "schannel: unable to re-allocate memory"); return CURLE_OUT_OF_MEMORY; } else { - connssl->encdata_buffer = reallocated_buffer; - connssl->encdata_length = reallocated_length; + backend->encdata_buffer = reallocated_buffer; + backend->encdata_length = reallocated_length; } } for(;;) { if(doread) { /* read encrypted handshake data from socket */ - result = Curl_read_plain(conn->sock[sockindex], - (char *) (connssl->encdata_buffer + - connssl->encdata_offset), - connssl->encdata_length - - connssl->encdata_offset, - &nread); + 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; - infof(data, "schannel: failed to receive handshake, " - "need more data\n"); + connssl->io_need = CURL_SSL_IO_NEED_RECV; + DEBUGF(infof(data, "schannel: failed to receive handshake, " + "need more data")); return CURLE_OK; } else if((result != CURLE_OK) || (nread == 0)) { @@ -498,15 +1406,18 @@ schannel_connect_step2(struct connectdata *conn, int sockindex) } /* increase encrypted data buffer offset */ - connssl->encdata_offset += nread; + backend->encdata_offset += nread; + backend->encdata_is_incomplete = false; + DEBUGF(infof(data, "schannel: encrypted data got %zd", nread)); } - infof(data, "schannel: encrypted data buffer: offset %zu length %zu\n", - connssl->encdata_offset, connssl->encdata_length); + 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(connssl->encdata_offset), - curlx_uztoul(connssl->encdata_offset)); + 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); @@ -516,35 +1427,30 @@ schannel_connect_step2(struct connectdata *conn, int sockindex) InitSecBuffer(&outbuf[2], SECBUFFER_EMPTY, NULL, 0); InitSecBufferDesc(&outbuf_desc, outbuf, 3); - if(inbuf[0].pvBuffer == NULL) { + 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, connssl->encdata_buffer, - connssl->encdata_offset); - - host_name = Curl_convert_UTF8_to_tchar(hostname); - if(!host_name) - return CURLE_OUT_OF_MEMORY; - - /* https://msdn.microsoft.com/en-us/library/windows/desktop/aa375924.aspx - */ - sspi_status = s_pSecFn->InitializeSecurityContext( - &connssl->cred->cred_handle, &connssl->ctxt->ctxt_handle, - host_name, connssl->req_flags, 0, 0, &inbuf_desc, 0, NULL, - &outbuf_desc, &connssl->ret_flags, &connssl->ctxt->time_stamp); + memcpy(inbuf[0].pvBuffer, backend->encdata_buffer, + backend->encdata_offset); - Curl_unicodefree(host_name); + sspi_status = Curl_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) { - connssl->connecting_state = ssl_connect_2_reading; - infof(data, "schannel: received incomplete message, need more data\n"); + backend->encdata_is_incomplete = true; + connssl->io_need = CURL_SSL_IO_NEED_RECV; + DEBUGF(infof(data, + "schannel: received incomplete message, need more data")); return CURLE_OK; } @@ -552,10 +1458,11 @@ schannel_connect_step2(struct connectdata *conn, int sockindex) 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 && - !(connssl->req_flags & ISC_REQ_USE_SUPPLIED_CREDS)) { - connssl->req_flags |= ISC_REQ_USE_SUPPLIED_CREDS; - connssl->connecting_state = ssl_connect_2_writing; - infof(data, "schannel: a client certificate has been requested\n"); + !(backend->req_flags & ISC_REQ_USE_SUPPLIED_CREDS)) { + backend->req_flags |= ISC_REQ_USE_SUPPLIED_CREDS; + connssl->io_need = CURL_SSL_IO_NEED_SEND; + DEBUGF(infof(data, + "schannel: a client certificate has been requested")); return CURLE_OK; } @@ -564,13 +1471,13 @@ schannel_connect_step2(struct connectdata *conn, int sockindex) 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) { - infof(data, "schannel: sending next handshake data: " - "sending %lu bytes...\n", outbuf[i].cbBuffer); + DEBUGF(infof(data, "schannel: sending next handshake data: " + "sending %lu bytes.", outbuf[i].cbBuffer)); /* send handshake token to server */ - result = Curl_write_plain(conn, conn->sock[sockindex], - outbuf[i].pvBuffer, outbuf[i].cbBuffer, - &written); + written = Curl_conn_cf_send(cf->next, data, + outbuf[i].pvBuffer, outbuf[i].cbBuffer, + FALSE, &result); if((result != CURLE_OK) || (outbuf[i].cbBuffer != (size_t) written)) { failf(data, "schannel: failed to send next handshake data: " @@ -580,27 +1487,51 @@ schannel_connect_step2(struct connectdata *conn, int sockindex) } /* free obsolete buffer */ - if(outbuf[i].pvBuffer != NULL) { - s_pSecFn->FreeContextBuffer(outbuf[i].pvBuffer); + if(outbuf[i].pvBuffer) { + Curl_pSecFn->FreeContextBuffer(outbuf[i].pvBuffer); } } } else { - if(sspi_status == SEC_E_WRONG_PRINCIPAL) + 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(conn, sspi_status)); - else + 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(conn, sspi_status)); - return CURLE_SSL_CONNECT_ERROR; + 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) { - infof(data, "schannel: encrypted data length: %lu\n", inbuf[1].cbBuffer); + 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 + 1) If we are renegotiating a connection and the handshake is already complete (from the server perspective), it can encrypted app data (not handshake data) in an extra buffer at this point. 2) (sspi_status == SEC_I_CONTINUE_NEEDED) We are negotiating a @@ -610,11 +1541,11 @@ schannel_connect_step2(struct connectdata *conn, int sockindex) */ /* check if the remaining data is less than the total amount and therefore begins after the already processed data */ - if(connssl->encdata_offset > inbuf[1].cbBuffer) { - memmove(connssl->encdata_buffer, - (connssl->encdata_buffer + connssl->encdata_offset) - + if(backend->encdata_offset > inbuf[1].cbBuffer) { + memmove(backend->encdata_buffer, + (backend->encdata_buffer + backend->encdata_offset) - inbuf[1].cbBuffer, inbuf[1].cbBuffer); - connssl->encdata_offset = inbuf[1].cbBuffer; + backend->encdata_offset = inbuf[1].cbBuffer; if(sspi_status == SEC_I_CONTINUE_NEEDED) { doread = FALSE; continue; @@ -622,74 +1553,195 @@ schannel_connect_step2(struct connectdata *conn, int sockindex) } } else { - connssl->encdata_offset = 0; + 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; + connssl->io_need = CURL_SSL_IO_NEED_RECV; return CURLE_OK; } /* check if the handshake is complete */ if(sspi_status == SEC_E_OK) { connssl->connecting_state = ssl_connect_3; - infof(data, "schannel: SSL/TLS handshake complete\n"); + DEBUGF(infof(data, "schannel: SSL/TLS handshake complete")); } -#ifdef _WIN32_WCE - /* Windows CE doesn't do any server certificate validation. - We have to do it manually. */ - if(conn->ssl_config.verifypeer) - return verify_certificate(conn, sockindex); +#ifndef CURL_DISABLE_PROXY + pubkey_ptr = Curl_ssl_cf_is_proxy(cf) ? + data->set.str[STRING_SSL_PINNEDPUBLICKEY_PROXY] : + data->set.str[STRING_SSL_PINNEDPUBLICKEY]; +#else + pubkey_ptr = data->set.str[STRING_SSL_PINNEDPUBLICKEY]; +#endif + if(pubkey_ptr) { + result = schannel_pkp_pin_peer_pubkey(cf, data, pubkey_ptr); + if(result) { + 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 will not 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 void schannel_session_free(void *sessionid, size_t idsize) +{ + /* this is expected to be called under sessionid lock */ + struct Curl_schannel_cred *cred = sessionid; + + (void)idsize; + if(cred) { + cred->refcount--; + if(cred->refcount == 0) { + Curl_pSecFn->FreeCredentialsHandle(&cred->cred_handle); + curlx_unicodefree(cred->sni_hostname); +#ifdef HAS_CLIENT_CERT_PATH + if(cred->client_cert_store) { + CertCloseStore(cred->client_cert_store, 0); + cred->client_cert_store = NULL; + } +#endif + Curl_safefree(cred); + } + } +} + static CURLcode -schannel_connect_step3(struct connectdata *conn, int sockindex) +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; - struct Curl_easy *data = conn->data; - struct ssl_connect_data *connssl = &conn->ssl[sockindex]; SECURITY_STATUS sspi_status = SEC_E_OK; CERT_CONTEXT *ccert_context = NULL; - const char * const hostname = SSL_IS_PROXY() ? conn->http_proxy.host.name : - conn->host.name; #ifdef HAS_ALPN SecPkgContext_ApplicationProtocol alpn_result; #endif DEBUGASSERT(ssl_connect_3 == connssl->connecting_state); + DEBUGASSERT(backend); - infof(data, "schannel: SSL/TLS connection with %s port %hu (step 3/3)\n", - hostname, conn->remote_port); + DEBUGF(infof(data, + "schannel: SSL/TLS connection with %s port %d (step 3/3)", + connssl->peer.hostname, connssl->peer.port)); - if(!connssl->cred) + if(!backend->cred) return CURLE_SSL_CONNECT_ERROR; /* check if the required context attributes are met */ - if(connssl->ret_flags != connssl->req_flags) { - if(!(connssl->ret_flags & ISC_RET_SEQUENCE_DETECT)) + if(backend->ret_flags != backend->req_flags) { + if(!(backend->ret_flags & ISC_RET_SEQUENCE_DETECT)) failf(data, "schannel: failed to setup sequence detection"); - if(!(connssl->ret_flags & ISC_RET_REPLAY_DETECT)) + if(!(backend->ret_flags & ISC_RET_REPLAY_DETECT)) failf(data, "schannel: failed to setup replay detection"); - if(!(connssl->ret_flags & ISC_RET_CONFIDENTIALITY)) + if(!(backend->ret_flags & ISC_RET_CONFIDENTIALITY)) failf(data, "schannel: failed to setup confidentiality"); - if(!(connssl->ret_flags & ISC_RET_ALLOCATED_MEMORY)) + if(!(backend->ret_flags & ISC_RET_ALLOCATED_MEMORY)) failf(data, "schannel: failed to setup memory allocation"); - if(!(connssl->ret_flags & ISC_RET_STREAM)) + if(!(backend->ret_flags & ISC_RET_STREAM)) failf(data, "schannel: failed to setup stream orientation"); return CURLE_SSL_CONNECT_ERROR; } #ifdef HAS_ALPN - if(connssl->use_alpn) { - sspi_status = s_pSecFn->QueryContextAttributes(&connssl->ctxt->ctxt_handle, - SECPKG_ATTR_APPLICATION_PROTOCOL, &alpn_result); + if(backend->use_alpn) { + sspi_status = + Curl_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"); @@ -698,81 +1750,62 @@ schannel_connect_step3(struct connectdata *conn, int sockindex) if(alpn_result.ProtoNegoStatus == SecApplicationProtocolNegotiationStatus_Success) { - - infof(data, "schannel: ALPN, server accepted to use %.*s\n", - alpn_result.ProtocolIdSize, alpn_result.ProtocolId); - -#ifdef USE_NGHTTP2 - if(alpn_result.ProtocolIdSize == NGHTTP2_PROTO_VERSION_ID_LEN && - !memcmp(NGHTTP2_PROTO_VERSION_ID, alpn_result.ProtocolId, - NGHTTP2_PROTO_VERSION_ID_LEN)) { - conn->negnpn = CURL_HTTP_VERSION_2; - } - else -#endif - if(alpn_result.ProtocolIdSize == ALPN_HTTP_1_1_LENGTH && - !memcmp(ALPN_HTTP_1_1, alpn_result.ProtocolId, - ALPN_HTTP_1_1_LENGTH)) { - conn->negnpn = CURL_HTTP_VERSION_1_1; + 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 - infof(data, "ALPN, server did not agree to a protocol\n"); + else { + if(!backend->recv_renegotiating) + Curl_alpn_set_negotiated(cf, data, NULL, 0); + } } #endif - /* save the current session data for possible re-use */ - if(data->set.general_ssl.sessionid) { - bool incache; - struct curl_schannel_cred *old_cred = NULL; - - Curl_ssl_sessionid_lock(conn); - incache = !(Curl_ssl_getsessionid(conn, (void **)&old_cred, NULL, - sockindex)); - if(incache) { - if(old_cred != connssl->cred) { - infof(data, "schannel: old credential handle is stale, removing\n"); - /* we're not taking old_cred ownership here, no refcount++ is needed */ - Curl_ssl_delsessionid(conn, (void *)old_cred); - incache = FALSE; - } - } - if(!incache) { - result = Curl_ssl_addsessionid(conn, (void *)connssl->cred, - sizeof(struct curl_schannel_cred), - sockindex); - if(result) { - Curl_ssl_sessionid_unlock(conn); - failf(data, "schannel: failed to store credential handle"); - return result; - } - else { - /* this cred session is now also referenced by sessionid cache */ - connssl->cred->refcount++; - infof(data, "schannel: stored credential handle in session cache\n"); - } - } - Curl_ssl_sessionid_unlock(conn); + /* save the current session data for possible reuse */ + if(ssl_config->primary.cache_session) { + Curl_ssl_sessionid_lock(data); + /* Up ref count since call takes ownership */ + backend->cred->refcount++; + result = Curl_ssl_set_sessionid(cf, data, &connssl->peer, backend->cred, + sizeof(struct Curl_schannel_cred), + schannel_session_free); + Curl_ssl_sessionid_unlock(data); + if(result) + return result; } if(data->set.ssl.certinfo) { - sspi_status = s_pSecFn->QueryContextAttributes(&connssl->ctxt->ctxt_handle, - SECPKG_ATTR_REMOTE_CERT_CONTEXT, &ccert_context); + int certs_count = 0; + sspi_status = + Curl_pSecFn->QueryContextAttributes(&backend->ctxt->ctxt_handle, + SECPKG_ATTR_REMOTE_CERT_CONTEXT, + &ccert_context); - if((sspi_status != SEC_E_OK) || (ccert_context == NULL)) { + if((sspi_status != SEC_E_OK) || !ccert_context) { failf(data, "schannel: failed to retrieve remote cert context"); - return CURLE_SSL_CONNECT_ERROR; + return CURLE_PEER_FAILED_VERIFICATION; } - result = Curl_ssl_init_certinfo(data, 1); - if(!result) { - if(((ccert_context->dwCertEncodingType & X509_ASN_ENCODING) != 0) && - (ccert_context->cbCertEncoded > 0)) { + traverse_cert_store(ccert_context, cert_counter_callback, &certs_count); - const char *beg = (const char *) ccert_context->pbCertEncoded; - const char *end = beg + ccert_context->cbCertEncoded; - result = Curl_extract_certinfo(conn, 0, beg, end); - } + 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) @@ -785,14 +1818,14 @@ schannel_connect_step3(struct connectdata *conn, int sockindex) } static CURLcode -schannel_connect_common(struct connectdata *conn, int sockindex, +schannel_connect_common(struct Curl_cfilter *cf, + struct Curl_easy *data, bool nonblocking, bool *done) { CURLcode result; - struct Curl_easy *data = conn->data; - struct ssl_connect_data *connssl = &conn->ssl[sockindex]; - curl_socket_t sockfd = conn->sock[sockindex]; - time_t timeout_ms; + 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 */ @@ -802,7 +1835,7 @@ schannel_connect_common(struct connectdata *conn, int sockindex, } if(ssl_connect_1 == connssl->connecting_state) { - /* check out how much more time we're allowed */ + /* check out how much more time we are allowed */ timeout_ms = Curl_timeleft(data, NULL, TRUE); if(timeout_ms < 0) { @@ -811,16 +1844,14 @@ schannel_connect_common(struct connectdata *conn, int sockindex, return CURLE_OPERATION_TIMEDOUT; } - result = schannel_connect_step1(conn, sockindex); + 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) { + while(ssl_connect_2 == connssl->connecting_state) { - /* check out how much more time we're allowed */ + /* check out how much more time we are allowed */ timeout_ms = Curl_timeleft(data, NULL, TRUE); if(timeout_ms < 0) { @@ -829,14 +1860,13 @@ schannel_connect_common(struct connectdata *conn, int sockindex, 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) { + /* if ssl is expecting something, check if it is available. */ + if(connssl->io_need) { - 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_socket_t writefd = (connssl->io_need & CURL_SSL_IO_NEED_SEND) ? + sockfd : CURL_SOCKET_BAD; + curl_socket_t readfd = (connssl->io_need & CURL_SSL_IO_NEED_RECV) ? + sockfd : CURL_SOCKET_BAD; what = Curl_socket_check(readfd, CURL_SOCKET_BAD, writefd, nonblocking ? 0 : timeout_ms); @@ -866,25 +1896,35 @@ schannel_connect_common(struct connectdata *conn, int sockindex, * ensuring that a client using select() or epoll() will always * have a valid fdset to wait on. */ - result = schannel_connect_step2(conn, sockindex); - if(result || (nonblocking && - (ssl_connect_2 == connssl->connecting_state || - ssl_connect_2_reading == connssl->connecting_state || - ssl_connect_2_writing == connssl->connecting_state))) + result = schannel_connect_step2(cf, data); + if(result || (nonblocking && (ssl_connect_2 == connssl->connecting_state))) return result; } /* repeat step2 until all transactions are done. */ if(ssl_connect_3 == connssl->connecting_state) { - result = schannel_connect_step3(conn, sockindex); + result = schannel_connect_step3(cf, data); if(result) return result; } if(ssl_connect_done == connssl->connecting_state) { connssl->state = ssl_connection_complete; - conn->recv[sockindex] = schannel_recv; - conn->send[sockindex] = schannel_send; + +#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 @@ -897,24 +1937,28 @@ schannel_connect_common(struct connectdata *conn, int sockindex, } static ssize_t -schannel_send(struct connectdata *conn, int sockindex, +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 *data = NULL; - struct ssl_connect_data *connssl = &conn->ssl[sockindex]; + 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(connssl->stream_sizes.cbMaximumMessage == 0) { - sspi_status = s_pSecFn->QueryContextAttributes( - &connssl->ctxt->ctxt_handle, + if(backend->stream_sizes.cbMaximumMessage == 0) { + sspi_status = Curl_pSecFn->QueryContextAttributes( + &backend->ctxt->ctxt_handle, SECPKG_ATTR_STREAM_SIZES, - &connssl->stream_sizes); + &backend->stream_sizes); if(sspi_status != SEC_E_OK) { *err = CURLE_SEND_ERROR; return -1; @@ -922,28 +1966,27 @@ schannel_send(struct connectdata *conn, int sockindex, } /* check if the buffer is longer than the maximum message length */ - if(len > connssl->stream_sizes.cbMaximumMessage) { - *err = CURLE_SEND_ERROR; - return -1; + if(len > backend->stream_sizes.cbMaximumMessage) { + len = backend->stream_sizes.cbMaximumMessage; } /* calculate the complete message length and allocate a buffer for it */ - data_len = connssl->stream_sizes.cbHeader + len + - connssl->stream_sizes.cbTrailer; - data = (unsigned char *) malloc(data_len); - if(data == NULL) { + 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, - data, connssl->stream_sizes.cbHeader); + ptr, backend->stream_sizes.cbHeader); InitSecBuffer(&outbuf[1], SECBUFFER_DATA, - data + connssl->stream_sizes.cbHeader, curlx_uztoul(len)); + ptr + backend->stream_sizes.cbHeader, curlx_uztoul(len)); InitSecBuffer(&outbuf[2], SECBUFFER_STREAM_TRAILER, - data + connssl->stream_sizes.cbHeader + len, - connssl->stream_sizes.cbTrailer); + ptr + backend->stream_sizes.cbHeader + len, + backend->stream_sizes.cbTrailer); InitSecBuffer(&outbuf[3], SECBUFFER_EMPTY, NULL, 0); InitSecBufferDesc(&outbuf_desc, outbuf, 4); @@ -951,7 +1994,7 @@ schannel_send(struct connectdata *conn, int sockindex, memcpy(outbuf[1].pvBuffer, buf, len); /* https://msdn.microsoft.com/en-us/library/windows/desktop/aa375390.aspx */ - sspi_status = s_pSecFn->EncryptMessage(&connssl->ctxt->ctxt_handle, 0, + sspi_status = Curl_pSecFn->EncryptMessage(&backend->ctxt->ctxt_handle, 0, &outbuf_desc, 0); /* check if the message was encrypted */ @@ -962,10 +2005,10 @@ schannel_send(struct connectdata *conn, int sockindex, 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 + it is important to send the full message which includes the header, + encrypted payload, and trailer. Until the client receives all the data a coherent message has not been delivered and the client - can't read any of it. + cannot read any of it. If we wanted to buffer the unwritten encrypted bytes, we would tell the client that all data it has requested to be sent has been @@ -979,32 +2022,29 @@ schannel_send(struct connectdata *conn, int sockindex, /* send entire message or fail */ while(len > (size_t)written) { - ssize_t this_write; - time_t timeleft; + ssize_t this_write = 0; int what; - - this_write = 0; - - timeleft = Curl_timeleft(conn->data, NULL, FALSE); - if(timeleft < 0) { + timediff_t timeout_ms = Curl_timeleft(data, NULL, FALSE); + if(timeout_ms < 0) { /* we already got the timeout */ - failf(conn->data, "schannel: timed out sending data " + failf(data, "schannel: timed out sending data " "(bytes sent: %zd)", written); *err = CURLE_OPERATION_TIMEDOUT; written = -1; break; } - - what = SOCKET_WRITABLE(conn->sock[sockindex], timeleft); + 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(conn->data, "select/poll on SSL socket, errno: %d", SOCKERRNO); + failf(data, "select/poll on SSL socket, errno: %d", SOCKERRNO); *err = CURLE_SEND_ERROR; written = -1; break; } else if(0 == what) { - failf(conn->data, "schannel: timed out sending data " + failf(data, "schannel: timed out sending data " "(bytes sent: %zd)", written); *err = CURLE_OPERATION_TIMEDOUT; written = -1; @@ -1012,8 +2052,9 @@ schannel_send(struct connectdata *conn, int sockindex, } /* socket is writable */ - result = Curl_write_plain(conn, conn->sock[sockindex], data + written, - len - written, &this_write); + this_write = Curl_conn_cf_send(cf->next, data, + ptr + written, len - written, + FALSE, &result); if(result == CURLE_AGAIN) continue; else if(result != CURLE_OK) { @@ -1032,7 +2073,7 @@ schannel_send(struct connectdata *conn, int sockindex, *err = CURLE_SEND_ERROR; } - Curl_safefree(data); + Curl_safefree(ptr); if(len == (size_t)written) /* Encrypted message including header, data and trailer entirely sent. @@ -1043,13 +2084,12 @@ schannel_send(struct connectdata *conn, int sockindex, } static ssize_t -schannel_recv(struct connectdata *conn, int sockindex, +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 Curl_easy *data = conn->data; - struct ssl_connect_data *connssl = &conn->ssl[sockindex]; + struct ssl_connect_data *connssl = cf->ctx; unsigned char *reallocated_buffer; size_t reallocated_length; bool done = FALSE; @@ -1059,10 +2099,15 @@ schannel_recv(struct connectdata *conn, int sockindex, /* 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 connssl->recv_unrecoverable_err unless in the cleanup. - * The pattern for return error is set *err, optional infof, goto cleanup. + * Do not 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 @@ -1070,92 +2115,93 @@ schannel_recv(struct connectdata *conn, int sockindex, * handled in the cleanup. */ - infof(data, "schannel: client wants to read %zu bytes\n", len); + DEBUGF(infof(data, "schannel: client wants to read %zu bytes", len)); *err = CURLE_OK; - if(len && len <= connssl->decdata_offset) { - infof(data, "schannel: enough decrypted data is already available\n"); + if(len && len <= backend->decdata_offset) { + infof(data, "schannel: enough decrypted data is already available"); goto cleanup; } - else if(connssl->recv_unrecoverable_err) { - *err = connssl->recv_unrecoverable_err; - infof(data, "schannel: an unrecoverable error occurred in a prior call\n"); + 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(connssl->recv_sspi_close_notify) { + 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\n"); + infof(data, "schannel: server indicated shutdown in a prior call"); goto cleanup; } - else if(!len) { - /* 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. - */ - ; /* do nothing */ - } - else if(!connssl->recv_connection_closed) { + /* it is debatable what to return when !len. Regardless we cannot return + immediately because there may be data to decrypt (in the case we want to + decrypt all encrypted cached data) so handle !len later in cleanup. + */ + else if(len && !backend->recv_connection_closed) { /* increase enc buffer in order to fit the requested amount of data */ - size = connssl->encdata_length - connssl->encdata_offset; + size = backend->encdata_length - backend->encdata_offset; if(size < CURL_SCHANNEL_BUFFER_FREE_SIZE || - connssl->encdata_length < min_encdata_length) { - reallocated_length = connssl->encdata_offset + - 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(connssl->encdata_buffer, + reallocated_buffer = realloc(backend->encdata_buffer, reallocated_length); - if(reallocated_buffer == NULL) { + if(!reallocated_buffer) { *err = CURLE_OUT_OF_MEMORY; failf(data, "schannel: unable to re-allocate memory"); goto cleanup; } - connssl->encdata_buffer = reallocated_buffer; - connssl->encdata_length = reallocated_length; - size = connssl->encdata_length - connssl->encdata_offset; - infof(data, "schannel: encdata_buffer resized %zu\n", - connssl->encdata_length); + 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)); } - infof(data, "schannel: encrypted data buffer: offset %zu length %zu\n", - connssl->encdata_offset, connssl->encdata_length); + DEBUGF(infof(data, + "schannel: encrypted data buffer: offset %zu length %zu", + backend->encdata_offset, backend->encdata_length)); /* read encrypted data from socket */ - *err = Curl_read_plain(conn->sock[sockindex], - (char *)(connssl->encdata_buffer + - connssl->encdata_offset), - size, &nread); + 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) - infof(data, "schannel: Curl_read_plain returned CURLE_AGAIN\n"); + DEBUGF(infof(data, + "schannel: recv returned CURLE_AGAIN")); else if(*err == CURLE_RECV_ERROR) - infof(data, "schannel: Curl_read_plain returned CURLE_RECV_ERROR\n"); + infof(data, "schannel: recv returned CURLE_RECV_ERROR"); else - infof(data, "schannel: Curl_read_plain returned error %d\n", *err); + infof(data, "schannel: recv returned error %d", *err); } else if(nread == 0) { - connssl->recv_connection_closed = true; - infof(data, "schannel: server closed the connection\n"); + backend->recv_connection_closed = true; + DEBUGF(infof(data, "schannel: server closed the connection")); } else if(nread > 0) { - connssl->encdata_offset += (size_t)nread; - infof(data, "schannel: encrypted data got %zd\n", nread); + backend->encdata_offset += (size_t)nread; + backend->encdata_is_incomplete = false; + DEBUGF(infof(data, "schannel: encrypted data got %zd", nread)); } } - infof(data, "schannel: encrypted data buffer: offset %zu length %zu\n", - connssl->encdata_offset, connssl->encdata_length); + DEBUGF(infof(data, + "schannel: encrypted data buffer: offset %zu length %zu", + backend->encdata_offset, backend->encdata_length)); /* decrypt loop */ - while(connssl->encdata_offset > 0 && sspi_status == SEC_E_OK && - (!len || connssl->decdata_offset < len || - connssl->recv_connection_closed)) { + 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, connssl->encdata_buffer, - curlx_uztoul(connssl->encdata_offset)); + 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); @@ -1164,8 +2210,8 @@ schannel_recv(struct connectdata *conn, int sockindex, InitSecBufferDesc(&inbuf_desc, inbuf, 4); /* https://msdn.microsoft.com/en-us/library/windows/desktop/aa375348.aspx - */ - sspi_status = s_pSecFn->DecryptMessage(&connssl->ctxt->ctxt_handle, + */ + sspi_status = Curl_pSecFn->DecryptMessage(&backend->ctxt->ctxt_handle, &inbuf_desc, 0, NULL); /* check if everything went fine (server may want to renegotiate @@ -1175,176 +2221,186 @@ schannel_recv(struct connectdata *conn, int sockindex, /* check for successfully decrypted data, even before actual renegotiation or shutdown of the connection context */ if(inbuf[1].BufferType == SECBUFFER_DATA) { - infof(data, "schannel: decrypted data length: %lu\n", - inbuf[1].cbBuffer); + 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(connssl->decdata_length - connssl->decdata_offset < size || - connssl->decdata_length < len) { + 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 = connssl->decdata_offset + size; + 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(connssl->decdata_buffer, + reallocated_buffer = realloc(backend->decdata_buffer, reallocated_length); - if(reallocated_buffer == NULL) { + if(!reallocated_buffer) { *err = CURLE_OUT_OF_MEMORY; failf(data, "schannel: unable to re-allocate memory"); goto cleanup; } - connssl->decdata_buffer = reallocated_buffer; - connssl->decdata_length = reallocated_length; + backend->decdata_buffer = reallocated_buffer; + backend->decdata_length = reallocated_length; } /* copy decrypted data to internal buffer */ size = inbuf[1].cbBuffer; if(size) { - memcpy(connssl->decdata_buffer + connssl->decdata_offset, + memcpy(backend->decdata_buffer + backend->decdata_offset, inbuf[1].pvBuffer, size); - connssl->decdata_offset += size; + backend->decdata_offset += size; } - infof(data, "schannel: decrypted data added: %zu\n", size); - infof(data, "schannel: decrypted data cached: offset %zu length %zu\n", - connssl->decdata_offset, connssl->decdata_length); + 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) { - infof(data, "schannel: encrypted data length: %lu\n", - inbuf[3].cbBuffer); + 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(connssl->encdata_offset > inbuf[3].cbBuffer) { + if(backend->encdata_offset > inbuf[3].cbBuffer) { /* move remaining encrypted data forward to the beginning of buffer */ - memmove(connssl->encdata_buffer, - (connssl->encdata_buffer + connssl->encdata_offset) - + memmove(backend->encdata_buffer, + (backend->encdata_buffer + backend->encdata_offset) - inbuf[3].cbBuffer, inbuf[3].cbBuffer); - connssl->encdata_offset = inbuf[3].cbBuffer; + backend->encdata_offset = inbuf[3].cbBuffer; } - infof(data, "schannel: encrypted data cached: offset %zu length %zu\n", - connssl->encdata_offset, connssl->encdata_length); + 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 */ - connssl->encdata_offset = 0; + 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\n"); + infof(data, "schannel: remote party requests renegotiation"); if(*err && *err != CURLE_AGAIN) { - infof(data, "schannel: can't renogotiate, an error is pending\n"); - goto cleanup; - } - if(connssl->encdata_offset) { - *err = CURLE_RECV_ERROR; - infof(data, "schannel: can't renogotiate, " - "encrypted data available\n"); + infof(data, "schannel: cannot renegotiate, an error is pending"); goto cleanup; } + /* begin renegotiation */ - infof(data, "schannel: renegotiating SSL/TLS connection\n"); + infof(data, "schannel: renegotiating SSL/TLS connection"); connssl->state = ssl_connection_negotiating; - connssl->connecting_state = ssl_connect_2_writing; - *err = schannel_connect_common(conn, sockindex, FALSE, &done); + connssl->connecting_state = ssl_connect_2; + connssl->io_need = CURL_SSL_IO_NEED_SEND; + backend->recv_renegotiating = true; + *err = schannel_connect_common(cf, data, FALSE, &done); + backend->recv_renegotiating = false; if(*err) { - infof(data, "schannel: renegotiation failed\n"); + infof(data, "schannel: renegotiation failed"); goto cleanup; } /* now retry receiving data */ sspi_status = SEC_E_OK; - infof(data, "schannel: SSL/TLS connection renegotiated\n"); + 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. */ - connssl->recv_sspi_close_notify = true; - if(!connssl->recv_connection_closed) { - connssl->recv_connection_closed = true; - infof(data, "schannel: server closed the connection\n"); - } + backend->recv_sspi_close_notify = true; + if(!backend->recv_connection_closed) + backend->recv_connection_closed = true; + infof(data, + "schannel: server close notification received (close_notify)"); 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\n"); + infof(data, "schannel: failed to decrypt data, need more data"); goto cleanup; } else { +#ifndef CURL_DISABLE_VERBOSE_STRINGS + char buffer[STRERROR_LEN]; + infof(data, "schannel: failed to read data from server: %s", + Curl_sspi_strerror(sspi_status, buffer, sizeof(buffer))); +#endif *err = CURLE_RECV_ERROR; - infof(data, "schannel: failed to read data from server: %s\n", - Curl_sspi_strerror(conn, sspi_status)); goto cleanup; } } - infof(data, "schannel: encrypted data buffer: offset %zu length %zu\n", - connssl->encdata_offset, connssl->encdata_length); + DEBUGF(infof(data, + "schannel: encrypted data buffer: offset %zu length %zu", + backend->encdata_offset, backend->encdata_length)); - infof(data, "schannel: decrypted data buffer: offset %zu length %zu\n", - connssl->decdata_offset, connssl->decdata_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 */ - infof(data, "schannel: schannel_recv cleanup\n"); + DEBUGF(infof(data, "schannel: schannel_recv cleanup")); /* Error if the connection has closed without a close_notify. - 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. + + The behavior here is a matter of debate. We do not want to be vulnerable + to a truncation attack however there is some browser precedent for + ignoring the close_notify for compatibility reasons. + + Additionally, Windows 2000 (v5.0) is a special case since it seems it + does not return close_notify. In that case if the connection was closed we + assume it was graceful (close_notify) since there does not seem to be a + way to tell. */ - if(len && !connssl->decdata_offset && connssl->recv_connection_closed && - !connssl->recv_sspi_close_notify) { - bool isWin2k = Curl_verify_windows_version(5, 0, PLATFORM_WINNT, - VERSION_EQUAL); + 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) - connssl->recv_sspi_close_notify = true; + backend->recv_sspi_close_notify = true; else { *err = CURLE_RECV_ERROR; - infof(data, "schannel: server closed abruptly (missing close_notify)\n"); + infof(data, "schannel: server closed abruptly (missing close_notify)"); } } /* Any error other than CURLE_AGAIN is an unrecoverable error. */ if(*err && *err != CURLE_AGAIN) - connssl->recv_unrecoverable_err = *err; + backend->recv_unrecoverable_err = *err; - size = len < connssl->decdata_offset ? len : connssl->decdata_offset; + size = len < backend->decdata_offset ? len : backend->decdata_offset; if(size) { - memcpy(buf, connssl->decdata_buffer, size); - memmove(connssl->decdata_buffer, connssl->decdata_buffer + size, - connssl->decdata_offset - size); - connssl->decdata_offset -= size; - - infof(data, "schannel: decrypted data returned %zu\n", size); - infof(data, "schannel: decrypted data buffer: offset %zu length %zu\n", - connssl->decdata_offset, connssl->decdata_length); + 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 && !connssl->recv_connection_closed) - *err = CURLE_AGAIN; + 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. + /* it is debatable what to return when !len. We could return whatever error + we got from decryption but instead we override here so the return is + consistent. */ if(!len) *err = CURLE_OK; @@ -1352,20 +2408,20 @@ schannel_recv(struct connectdata *conn, int sockindex, return *err ? -1 : 0; } -CURLcode -Curl_schannel_connect_nonblocking(struct connectdata *conn, int sockindex, - bool *done) +static CURLcode schannel_connect_nonblocking(struct Curl_cfilter *cf, + struct Curl_easy *data, + bool *done) { - return schannel_connect_common(conn, sockindex, TRUE, done); + return schannel_connect_common(cf, data, TRUE, done); } -CURLcode -Curl_schannel_connect(struct connectdata *conn, int sockindex) +static CURLcode schannel_connect(struct Curl_cfilter *cf, + struct Curl_easy *data) { CURLcode result; bool done = FALSE; - result = schannel_connect_common(conn, sockindex, FALSE, &done); + result = schannel_connect_common(cf, data, FALSE, &done); if(result) return result; @@ -1374,311 +2430,562 @@ Curl_schannel_connect(struct connectdata *conn, int sockindex) return CURLE_OK; } -bool Curl_schannel_data_pending(const struct connectdata *conn, int sockindex) +static bool schannel_data_pending(struct Curl_cfilter *cf, + const struct Curl_easy *data) { - const struct ssl_connect_data *connssl = &conn->ssl[sockindex]; - - if(connssl->use) /* SSL/TLS is in use */ - return (connssl->encdata_offset > 0 || - connssl->decdata_offset > 0) ? TRUE : FALSE; + 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) || + backend->recv_connection_closed || + backend->recv_sspi_close_notify || + backend->recv_unrecoverable_err); else return FALSE; } -void Curl_schannel_close(struct connectdata *conn, int sockindex) -{ - if(conn->ssl[sockindex].use) - /* if the SSL/TLS channel hasn't been shut down yet, do that now. */ - Curl_ssl_shutdown(conn, sockindex); -} - -int Curl_schannel_shutdown(struct connectdata *conn, int sockindex) +/* 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 CURLcode schannel_shutdown(struct Curl_cfilter *cf, + struct Curl_easy *data, + bool send_shutdown, bool *done) { /* See https://msdn.microsoft.com/en-us/library/windows/desktop/aa380138.aspx * Shutting Down an Schannel Connection */ - struct Curl_easy *data = conn->data; - struct ssl_connect_data *connssl = &conn->ssl[sockindex]; - const char * const hostname = SSL_IS_PROXY() ? conn->http_proxy.host.name : - conn->host.name; + struct ssl_connect_data *connssl = cf->ctx; + struct schannel_ssl_backend_data *backend = + (struct schannel_ssl_backend_data *)connssl->backend; + CURLcode result = CURLE_OK; + + if(cf->shutdown) { + *done = TRUE; + return CURLE_OK; + } + + DEBUGASSERT(data); + DEBUGASSERT(backend); + + /* Not supported in schannel */ + (void)send_shutdown; - infof(data, "schannel: shutting down SSL/TLS connection with %s port %hu\n", - hostname, conn->remote_port); + *done = FALSE; + if(backend->ctxt) { + infof(data, "schannel: shutting down SSL/TLS connection with %s port %d", + connssl->peer.hostname, connssl->peer.port); + } + + if(!backend->ctxt || cf->shutdown) { + *done = TRUE; + goto out; + } - if(connssl->cred && connssl->ctxt) { + if(backend->cred && backend->ctxt && !backend->sent_shutdown) { SecBufferDesc BuffDesc; SecBuffer Buffer; SECURITY_STATUS sspi_status; SecBuffer outbuf; SecBufferDesc outbuf_desc; - CURLcode result; - TCHAR *host_name; DWORD dwshut = SCHANNEL_SHUTDOWN; InitSecBuffer(&Buffer, SECBUFFER_TOKEN, &dwshut, sizeof(dwshut)); InitSecBufferDesc(&BuffDesc, &Buffer, 1); - sspi_status = s_pSecFn->ApplyControlToken(&connssl->ctxt->ctxt_handle, + sspi_status = Curl_pSecFn->ApplyControlToken(&backend->ctxt->ctxt_handle, &BuffDesc); - if(sspi_status != SEC_E_OK) + if(sspi_status != SEC_E_OK) { + char buffer[STRERROR_LEN]; failf(data, "schannel: ApplyControlToken failure: %s", - Curl_sspi_strerror(conn, sspi_status)); - - host_name = Curl_convert_UTF8_to_tchar(hostname); - if(!host_name) - return CURLE_OUT_OF_MEMORY; + Curl_sspi_strerror(sspi_status, buffer, sizeof(buffer))); + result = CURLE_SEND_ERROR; + goto out; + } /* setup output buffer */ InitSecBuffer(&outbuf, SECBUFFER_EMPTY, NULL, 0); InitSecBufferDesc(&outbuf_desc, &outbuf, 1); - sspi_status = s_pSecFn->InitializeSecurityContext( - &connssl->cred->cred_handle, - &connssl->ctxt->ctxt_handle, - host_name, - connssl->req_flags, + sspi_status = Curl_pSecFn->InitializeSecurityContext( + &backend->cred->cred_handle, + &backend->ctxt->ctxt_handle, + backend->cred->sni_hostname, + backend->req_flags, 0, 0, NULL, 0, - &connssl->ctxt->ctxt_handle, + &backend->ctxt->ctxt_handle, &outbuf_desc, - &connssl->ret_flags, - &connssl->ctxt->time_stamp); - - Curl_unicodefree(host_name); + &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; - result = Curl_write_plain(conn, conn->sock[sockindex], outbuf.pvBuffer, - outbuf.cbBuffer, &written); - - 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)\n", curl_easy_strerror(result), written); + ssize_t written = Curl_conn_cf_send(cf->next, data, + outbuf.pvBuffer, outbuf.cbBuffer, + FALSE, &result); + Curl_pSecFn->FreeContextBuffer(outbuf.pvBuffer); + if(!result) { + if(written < (ssize_t)outbuf.cbBuffer) { + /* TODO: handle partial sends */ + infof(data, "schannel: failed to send close msg: %s" + " (bytes written: %zd)", curl_easy_strerror(result), written); + result = CURLE_SEND_ERROR; + goto out; + } + backend->sent_shutdown = TRUE; + *done = TRUE; + } + else if(result == CURLE_AGAIN) { + connssl->io_need = CURL_SSL_IO_NEED_SEND; + result = CURLE_OK; + goto out; + } + else { + if(!backend->recv_connection_closed) { + infof(data, "schannel: error sending close msg: %d", result); + result = CURLE_SEND_ERROR; + goto out; + } + /* Looks like server already closed the connection. + * An error to send our close notify is not a failure. */ + *done = TRUE; + result = CURLE_OK; } } } + /* If the connection seems open and we have not seen the close notify + * from the server yet, try to receive it. */ + if(backend->cred && backend->ctxt && + !backend->recv_sspi_close_notify && !backend->recv_connection_closed) { + char buffer[1024]; + ssize_t nread; + + nread = schannel_recv(cf, data, buffer, sizeof(buffer), &result); + if(nread > 0) { + /* still data coming in? */ + } + else if(nread == 0) { + /* We got the close notify alert and are done. */ + backend->recv_connection_closed = TRUE; + *done = TRUE; + } + else if(nread < 0 && result == CURLE_AGAIN) { + connssl->io_need = CURL_SSL_IO_NEED_RECV; + } + else { + CURL_TRC_CF(data, cf, "SSL shutdown, error %d", result); + result = CURLE_RECV_ERROR; + } + } + +out: + cf->shutdown = (result || *done); + return result; +} + +static void schannel_close(struct Curl_cfilter *cf, struct Curl_easy *data) +{ + struct ssl_connect_data *connssl = cf->ctx; + struct schannel_ssl_backend_data *backend = + (struct schannel_ssl_backend_data *)connssl->backend; + + DEBUGASSERT(data); + DEBUGASSERT(backend); + /* free SSPI Schannel API security context handle */ - if(connssl->ctxt) { - infof(data, "schannel: clear security context handle\n"); - s_pSecFn->DeleteSecurityContext(&connssl->ctxt->ctxt_handle); - Curl_safefree(connssl->ctxt); + if(backend->ctxt) { + DEBUGF(infof(data, "schannel: clear security context handle")); + Curl_pSecFn->DeleteSecurityContext(&backend->ctxt->ctxt_handle); + Curl_safefree(backend->ctxt); } /* free SSPI Schannel API credential handle */ - if(connssl->cred) { - Curl_ssl_sessionid_lock(conn); - Curl_schannel_session_free(connssl->cred); - Curl_ssl_sessionid_unlock(conn); - connssl->cred = NULL; + if(backend->cred) { + Curl_ssl_sessionid_lock(data); + schannel_session_free(backend->cred, 0); + Curl_ssl_sessionid_unlock(data); + backend->cred = NULL; } /* free internal buffer for received encrypted data */ - if(connssl->encdata_buffer != NULL) { - Curl_safefree(connssl->encdata_buffer); - connssl->encdata_length = 0; - connssl->encdata_offset = 0; + 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(connssl->decdata_buffer != NULL) { - Curl_safefree(connssl->decdata_buffer); - connssl->decdata_length = 0; - connssl->decdata_offset = 0; + if(backend->decdata_buffer) { + Curl_safefree(backend->decdata_buffer); + backend->decdata_length = 0; + backend->decdata_offset = 0; } - - return CURLE_OK; } -void Curl_schannel_session_free(void *ptr) +static int schannel_init(void) { - /* this is expected to be called under sessionid lock */ - struct curl_schannel_cred *cred = ptr; + return (Curl_sspi_global_init() == CURLE_OK ? 1 : 0); +} - cred->refcount--; - if(cred->refcount == 0) { - s_pSecFn->FreeCredentialsHandle(&cred->cred_handle); - Curl_safefree(cred); - } +static void schannel_cleanup(void) +{ + Curl_sspi_global_cleanup(); } -int Curl_schannel_init(void) +static size_t schannel_version(char *buffer, size_t size) { - return (Curl_sspi_global_init() == CURLE_OK ? 1 : 0); + return msnprintf(buffer, size, "Schannel"); } -void Curl_schannel_cleanup(void) +static CURLcode schannel_random(struct Curl_easy *data UNUSED_PARAM, + unsigned char *entropy, size_t length) { - Curl_sspi_global_cleanup(); + (void)data; + + return Curl_win32_random(entropy, length); } -size_t Curl_schannel_version(char *buffer, size_t size) +static CURLcode schannel_pkp_pin_peer_pubkey(struct Curl_cfilter *cf, + struct Curl_easy *data, + const char *pinnedpubkey) { - size = snprintf(buffer, size, "WinSSL"); + 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 was not specified, do not 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 = + Curl_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); - return size; + if(pCertContextServer) + CertFreeCertificateContext(pCertContextServer); + + return result; } -CURLcode Curl_schannel_random(unsigned char *entropy, size_t length) +static void schannel_checksum(const unsigned char *input, + size_t inputlen, + unsigned char *checksum, + size_t checksumlen, + DWORD provType, + const unsigned int algId) { - HCRYPTPROV hCryptProv = 0; +#ifdef CURL_WINDOWS_UWP + (void)input; + (void)inputlen; + (void)provType; + (void)algId; + memset(checksum, 0, checksumlen); +#else + 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(&hCryptProv, NULL, NULL, PROV_RSA_FULL, + if(!CryptAcquireContext(&hProv, NULL, NULL, provType, CRYPT_VERIFYCONTEXT | CRYPT_SILENT)) - return CURLE_FAILED_INIT; + return; /* failed */ - if(!CryptGenRandom(hCryptProv, (DWORD)length, entropy)) { - CryptReleaseContext(hCryptProv, 0UL); - return CURLE_FAILED_INIT; - } + 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); - CryptReleaseContext(hCryptProv, 0UL); + if(hProv) + CryptReleaseContext(hProv, 0); +#endif +} + +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; } -#ifdef _WIN32_WCE -static CURLcode verify_certificate(struct connectdata *conn, int sockindex) +static void *schannel_get_internals(struct ssl_connect_data *connssl, + CURLINFO info UNUSED_PARAM) { - SECURITY_STATUS status; - struct Curl_easy *data = conn->data; - struct ssl_connect_data *connssl = &conn->ssl[sockindex]; - CURLcode result = CURLE_OK; - CERT_CONTEXT *pCertContextServer = NULL; - const CERT_CHAIN_CONTEXT *pChainContext = NULL; - const char * const conn_hostname = SSL_IS_PROXY() ? - conn->http_proxy.host.name : - conn->host.name; - - status = s_pSecFn->QueryContextAttributes(&connssl->ctxt->ctxt_handle, - SECPKG_ATTR_REMOTE_CERT_CONTEXT, - &pCertContextServer); - - if((status != SEC_E_OK) || (pCertContextServer == NULL)) { - failf(data, "schannel: Failed to read remote certificate context: %s", - Curl_sspi_strerror(conn, status)); - result = CURLE_PEER_FAILED_VERIFICATION; - } - - if(result == CURLE_OK) { - CERT_CHAIN_PARA ChainPara; - memset(&ChainPara, 0, sizeof(ChainPara)); - ChainPara.cbSize = sizeof(ChainPara); - - if(!CertGetCertificateChain(NULL, - pCertContextServer, - NULL, - pCertContextServer->hCertStore, - &ChainPara, - (data->set.ssl.no_revoke ? 0 : - CERT_CHAIN_REVOCATION_CHECK_CHAIN), - NULL, - &pChainContext)) { - failf(data, "schannel: CertGetCertificateChain failed: %s", - Curl_sspi_strerror(conn, GetLastError())); - 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(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 - failf(data, "schannel: CertGetCertificateChain error mask: 0x%08x", - dwTrustErrorMask); - result = CURLE_PEER_FAILED_VERIFICATION; - } + 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; + const struct curl_blob *ca_info_blob = conn_config->ca_info_blob; + struct schannel_cert_share *share; + 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) { + return NULL; + } + + share = Curl_hash_pick(&multi->proto_hash, + (void *)MPROTO_SCHANNEL_CERT_SHARE_KEY, + sizeof(MPROTO_SCHANNEL_CERT_SHARE_KEY)-1); + if(!share || !share->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, share->time); + if(elapsed_ms >= timeout_ms) { + return NULL; } } - if(result == CURLE_OK) { - if(conn->ssl_config.verifyhost) { - TCHAR cert_hostname_buff[256]; - DWORD len; + if(ca_info_blob) { + if(share->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(share->CAinfo_blob_digest, info_blob_digest, + CURL_SHA256_DIGEST_LENGTH)) { + return NULL; + } + } + else { + if(!conn_config->CAfile || !share->CAfile || + strcmp(share->CAfile, conn_config->CAfile)) { + return NULL; + } + } - /* TODO: Fix this for certificates with multiple alternative names. - Right now we're only asking for the first preferred alternative name. - Instead we'd need to do all via CERT_NAME_SEARCH_ALL_NAMES_FLAG - (if WinCE supports that?) and run this section in a loop for each. - https://msdn.microsoft.com/en-us/library/windows/desktop/aa376086.aspx - curl: (51) schannel: CertGetNameString() certificate hostname - (.google.com) did not match connection (google.com) - */ - len = CertGetNameString(pCertContextServer, - CERT_NAME_DNS_TYPE, - CERT_NAME_DISABLE_IE4_UTF8_FLAG, - NULL, - cert_hostname_buff, - 256); - if(len > 0) { - const 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 = Curl_convert_tchar_to_UTF8(cert_hostname_buff); - if(!cert_hostname) { - result = CURLE_OUT_OF_MEMORY; - } - else{ - int match_result; - - match_result = Curl_cert_hostcheck(cert_hostname, conn->host.name); - if(match_result == CURL_HOST_MATCH) { - infof(data, - "schannel: connection hostname (%s) validated " - "against certificate name (%s)\n", - conn->host.name, - cert_hostname); - result = CURLE_OK; - } - else{ - failf(data, - "schannel: connection hostname (%s) " - "does not match certificate name (%s)", - conn->host.name, - cert_hostname); - result = CURLE_PEER_FAILED_VERIFICATION; - } - Curl_unicodefree(cert_hostname); - } - } - else { - failf(data, - "schannel: CertGetNameString did not provide any " - "certificate name information"); - result = CURLE_PEER_FAILED_VERIFICATION; - } + return share->cert_store; +} + +static void schannel_cert_share_free(void *key, size_t key_len, void *p) +{ + struct schannel_cert_share *share = p; + DEBUGASSERT(key_len == (sizeof(MPROTO_SCHANNEL_CERT_SHARE_KEY)-1)); + DEBUGASSERT(!memcmp(MPROTO_SCHANNEL_CERT_SHARE_KEY, key, key_len)); + (void)key; + (void)key_len; + if(share->cert_store) { + CertCloseStore(share->cert_store, 0); + } + free(share->CAfile); + free(share); +} + +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; + const struct curl_blob *ca_info_blob = conn_config->ca_info_blob; + struct schannel_cert_share *share; + size_t CAinfo_blob_size = 0; + char *CAfile = NULL; + + DEBUGASSERT(multi); + + if(!multi) { + return false; + } + + share = Curl_hash_pick(&multi->proto_hash, + (void *)MPROTO_SCHANNEL_CERT_SHARE_KEY, + sizeof(MPROTO_SCHANNEL_CERT_SHARE_KEY)-1); + if(!share) { + share = calloc(1, sizeof(*share)); + if(!share) { + return false; + } + if(!Curl_hash_add2(&multi->proto_hash, + (void *)MPROTO_SCHANNEL_CERT_SHARE_KEY, + sizeof(MPROTO_SCHANNEL_CERT_SHARE_KEY)-1, + share, schannel_cert_share_free)) { + free(share); + return false; } } - if(pChainContext) - CertFreeCertificateChain(pChainContext); + if(ca_info_blob) { + schannel_sha256sum((const unsigned char *)ca_info_blob->data, + ca_info_blob->len, + share->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; + } + } + } - if(pCertContextServer) - CertFreeCertificateContext(pCertContextServer); + /* free old cache data */ + if(share->cert_store) { + CertCloseStore(share->cert_store, 0); + } + free(share->CAfile); - return result; + share->time = Curl_now(); + share->cert_store = cert_store; + share->CAinfo_blob_size = CAinfo_blob_size; + share->CAfile = CAfile; + return true; } -#endif /* _WIN32_WCE */ + +const struct Curl_ssl Curl_ssl_schannel = { + { CURLSSLBACKEND_SCHANNEL, "schannel" }, /* info */ + + SSLSUPP_CERTINFO | +#ifdef HAS_MANUAL_VERIFY_API + SSLSUPP_CAINFO_BLOB | +#endif +#ifndef CURL_WINDOWS_UWP + SSLSUPP_PINNEDPUBKEY | +#endif + SSLSUPP_TLS13_CIPHERSUITES | + SSLSUPP_CA_CACHE | + SSLSUPP_HTTPS_PROXY | + SSLSUPP_CIPHER_LIST, + + 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 */ + 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_recv, /* recv decrypted data */ + schannel_send, /* send data to encrypt */ + NULL, /* get_channel_binding */ +}; #endif /* USE_SCHANNEL */ diff --git a/contrib/curl/lib/vtls/schannel.h b/contrib/curl/lib/vtls/schannel.h index 8627c63c9e..b26334bcf8 100644 --- a/contrib/curl/lib/vtls/schannel.h +++ b/contrib/curl/lib/vtls/schannel.h @@ -7,12 +7,12 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 2012, Marc Hoersken, , et al. - * Copyright (C) 2012 - 2017, Daniel Stenberg, , et al. + * Copyright (C) Marc Hoersken, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -21,101 +21,66 @@ * 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 -#include "urldata.h" - -#ifndef UNISP_NAME_A -#define UNISP_NAME_A "Microsoft Unified Security Protocol Provider" -#endif - -#ifndef UNISP_NAME_W -#define UNISP_NAME_W L"Microsoft Unified Security Protocol Provider" -#endif - -#ifndef UNISP_NAME -#ifdef UNICODE -#define UNISP_NAME UNISP_NAME_W -#else -#define UNISP_NAME UNISP_NAME_A +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable: 4201) #endif +#include +#ifdef _MSC_VER +#pragma warning(pop) #endif - -#ifndef SP_PROT_SSL2_CLIENT -#define SP_PROT_SSL2_CLIENT 0x00000008 -#endif - -#ifndef SP_PROT_SSL3_CLIENT -#define SP_PROT_SSL3_CLIENT 0x00000008 -#endif - -#ifndef SP_PROT_TLS1_CLIENT -#define SP_PROT_TLS1_CLIENT 0x00000080 -#endif - -#ifndef SP_PROT_TLS1_0_CLIENT -#define SP_PROT_TLS1_0_CLIENT SP_PROT_TLS1_CLIENT +/* Wincrypt must be included before anything that could include OpenSSL. */ +#if defined(USE_WIN32_CRYPTO) +#include +/* 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 -#ifndef SP_PROT_TLS1_1_CLIENT -#define SP_PROT_TLS1_1_CLIENT 0x00000200 -#endif +#include +#include +#include "curl_sspi.h" -#ifndef SP_PROT_TLS1_2_CLIENT -#define SP_PROT_TLS1_2_CLIENT 0x00000800 -#endif +#include "cfilters.h" +#include "urldata.h" -#ifndef SECBUFFER_ALERT -#define SECBUFFER_ALERT 17 +/* has been included via the above . + * Or in case of ldap.c, it was included via . + * And since has this: + * #define X509_NAME ((LPCSTR) 7) + * + * And in BoringSSL's there is: + * typedef struct X509_name_st X509_NAME; + * etc. + * + * this will cause all kinds of C-preprocessing paste errors in + * BoringSSL's : 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 -/* Both schannel buffer sizes must be > 0 */ -#define CURL_SCHANNEL_BUFFER_INIT_SIZE 4096 -#define CURL_SCHANNEL_BUFFER_FREE_SIZE 1024 - - -CURLcode Curl_schannel_connect(struct connectdata *conn, int sockindex); - -CURLcode Curl_schannel_connect_nonblocking(struct connectdata *conn, - int sockindex, - bool *done); - -bool Curl_schannel_data_pending(const struct connectdata *conn, int sockindex); -void Curl_schannel_close(struct connectdata *conn, int sockindex); -int Curl_schannel_shutdown(struct connectdata *conn, int sockindex); -void Curl_schannel_session_free(void *ptr); - -int Curl_schannel_init(void); -void Curl_schannel_cleanup(void); -size_t Curl_schannel_version(char *buffer, size_t size); - -CURLcode Curl_schannel_random(unsigned char *entropy, size_t length); - -/* Set the API backend definition to Schannel */ -#define CURL_SSL_BACKEND CURLSSLBACKEND_SCHANNEL +extern const struct Curl_ssl Curl_ssl_schannel; -/* this backend supports CURLOPT_CERTINFO */ -#define have_curlssl_certinfo 1 +CURLcode Curl_verify_host(struct Curl_cfilter *cf, + struct Curl_easy *data); -/* API setup for Schannel */ -#define curlssl_init Curl_schannel_init -#define curlssl_cleanup Curl_schannel_cleanup -#define curlssl_connect Curl_schannel_connect -#define curlssl_connect_nonblocking Curl_schannel_connect_nonblocking -#define curlssl_session_free Curl_schannel_session_free -#define curlssl_close_all(x) ((void)x) -#define curlssl_close Curl_schannel_close -#define curlssl_shutdown Curl_schannel_shutdown -#define curlssl_set_engine(x,y) ((void)x, (void)y, CURLE_NOT_BUILT_IN) -#define curlssl_set_engine_default(x) ((void)x, CURLE_NOT_BUILT_IN) -#define curlssl_engines_list(x) ((void)x, (struct curl_slist *)NULL) -#define curlssl_version Curl_schannel_version -#define curlssl_check_cxn(x) ((void)x, -1) -#define curlssl_data_pending Curl_schannel_data_pending -#define curlssl_random(x,y,z) ((void)x, Curl_schannel_random(y,z)) +CURLcode Curl_verify_certificate(struct Curl_cfilter *cf, + struct Curl_easy *data); #endif /* USE_SCHANNEL */ #endif /* HEADER_CURL_SCHANNEL_H */ diff --git a/contrib/curl/lib/vtls/schannel_int.h b/contrib/curl/lib/vtls/schannel_int.h new file mode 100644 index 0000000000..026a1dbac3 --- /dev/null +++ b/contrib/curl/lib/vtls/schannel_int.h @@ -0,0 +1,187 @@ +#ifndef HEADER_CURL_SCHANNEL_INT_H +#define HEADER_CURL_SCHANNEL_INT_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Marc Hoersken, , et al. + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ +#include "curl_setup.h" + +#ifdef USE_SCHANNEL + +#include "vtls.h" + +#if (defined(__MINGW32__) || defined(CERT_CHAIN_REVOCATION_CHECK_CHAIN)) \ + && !defined(CURL_WINDOWS_UWP) +#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 + +#if defined(_MSC_VER) && (_MSC_VER <= 1600) +/* Workaround for warning: + 'type cast' : conversion from 'int' to 'LPCSTR' of greater size */ +#undef CERT_STORE_PROV_MEMORY +#undef CERT_STORE_PROV_SYSTEM_A +#undef CERT_STORE_PROV_SYSTEM_W +#define CERT_STORE_PROV_MEMORY ((LPCSTR)(size_t)2) +#define CERT_STORE_PROV_SYSTEM_A ((LPCSTR)(size_t)9) +#define CERT_STORE_PROV_SYSTEM_W ((LPCSTR)(size_t)10) +#endif + +#ifndef SCH_CREDENTIALS_VERSION + +#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 + cannot 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 + BIT(sent_shutdown); +}; + +/* key to use at `multi->proto_hash` */ +#define MPROTO_SCHANNEL_CERT_SHARE_KEY "tls:schannel:cert:share" + +struct schannel_cert_share { + unsigned char CAinfo_blob_digest[CURL_SHA256_DIGEST_LENGTH]; + 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/contrib/curl/lib/vtls/schannel_verify.c b/contrib/curl/lib/vtls/schannel_verify.c new file mode 100644 index 0000000000..9a166c2817 --- /dev/null +++ b/contrib/curl/lib/vtls/schannel_verify.c @@ -0,0 +1,794 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Marc Hoersken, + * Copyright (C) Mark Salisbury, + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +/* + * 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 "cannot 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 do not need to be terminated by a '\0'. + * Similar of macOS/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 '%lu' 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 hostnames. 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; +#if defined(CURL_WINDOWS_UWP) + (void)data; + (void)cert_context; + (void)host_names; + (void)length; +#else + 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 hostnames."); + break; + } + dns_w = entry->pwszDNSName; + /* pwszDNSName is in ia5 string format and hence does not contain any + * non-ASCII characters. */ + while(*dns_w != '\0') { + *current_pos++ = (TCHAR)(*dns_w++); + } + *current_pos++ = '\0'; + actual_length += (DWORD)current_length; + } + if(compute_content) { + /* Last string has double null-terminator. */ + *current_pos = '\0'; + } +#endif + 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 = + Curl_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 = + Curl_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%08lx", + 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/contrib/curl/lib/vtls/sectransp.c b/contrib/curl/lib/vtls/sectransp.c new file mode 100644 index 0000000000..27e2aea0ab --- /dev/null +++ b/contrib/curl/lib/vtls/sectransp.c @@ -0,0 +1,2778 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * Copyright (C) Nick Zitzmann, . + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the 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 Secure Transport-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_SECTRANSP + +#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" +#include "cipher_suite.h" + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunreachable-code" +#endif /* __clang__ */ + +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Waddress" +#endif + +#include + +#include +/* For some reason, when building for iOS, the omnibus header above does + * not include SecureTransport.h as of iOS SDK 5.1. */ +#include +#include +#include + +/* 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 backend 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 backend requires iOS or macOS." +#endif /* (TARGET_OS_MAC && !(TARGET_OS_EMBEDDED || TARGET_OS_IPHONE)) */ + +#if CURL_BUILD_MAC +#include +#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 cannot include because it is not 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; + BIT(sent_shutdown); +}; + +/* Create the list of default ciphers to use by making an intersection of the + * ciphers supported by Secure Transport and the list below, using the order + * of the former. + * This list is based on TLS recommendations by Mozilla, balancing between + * security and wide compatibility: "Most ciphers that are not clearly broken + * and dangerous to use are supported" + */ +static const uint16_t default_ciphers[] = { + TLS_RSA_WITH_3DES_EDE_CBC_SHA, /* 0x000A */ + TLS_RSA_WITH_AES_128_CBC_SHA, /* 0x002F */ + TLS_RSA_WITH_AES_256_CBC_SHA, /* 0x0035 */ + +#if CURL_BUILD_MAC_10_6 || CURL_BUILD_IOS + TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, /* 0xC009 */ + TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, /* 0xC00A */ + TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, /* 0xC013 */ + TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, /* 0xC014 */ +#endif /* CURL_BUILD_MAC_10_6 || CURL_BUILD_IOS */ + +#if CURL_BUILD_MAC_10_8 || CURL_BUILD_IOS + TLS_RSA_WITH_AES_128_CBC_SHA256, /* 0x003C */ + TLS_RSA_WITH_AES_256_CBC_SHA256, /* 0x003D */ + TLS_DHE_RSA_WITH_AES_128_CBC_SHA256, /* 0x0067 */ + TLS_DHE_RSA_WITH_AES_256_CBC_SHA256, /* 0x006B */ + TLS_RSA_WITH_AES_128_GCM_SHA256, /* 0x009C */ + TLS_RSA_WITH_AES_256_GCM_SHA384, /* 0x009D */ + TLS_DHE_RSA_WITH_AES_128_GCM_SHA256, /* 0x009E */ + TLS_DHE_RSA_WITH_AES_256_GCM_SHA384, /* 0x009F */ + TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256, /* 0xC023 */ + TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384, /* 0xC024 */ + TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256, /* 0xC027 */ + TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384, /* 0xC028 */ + TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, /* 0xC02B */ + TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, /* 0xC02C */ + TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, /* 0xC02F */ + TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, /* 0xC030 */ +#endif /* CURL_BUILD_MAC_10_8 || CURL_BUILD_IOS */ + +#if CURL_BUILD_MAC_10_13 || CURL_BUILD_IOS_11 + TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, /* 0xCCA8 */ + TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, /* 0xCCA9 */ + + /* TLSv1.3 is not supported by Secure Transport, but there is also other + * code referencing TLSv1.3, like: kTLSProtocol13 ? */ + TLS_AES_128_GCM_SHA256, /* 0x1301 */ + TLS_AES_256_GCM_SHA384, /* 0x1302 */ + TLS_CHACHA20_POLY1305_SHA256, /* 0x1303 */ +#endif /* CURL_BUILD_MAC_10_13 || CURL_BUILD_IOS_11 */ +}; + +#define DEFAULT_CIPHERS_LEN sizeof(default_ciphers)/sizeof(default_ciphers[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 macOS 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 does not 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, FALSE, + &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; +} + +#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 are not available under iOS or newer cats. 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 is 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, (CFIndex)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: could not 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 is 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 (does not 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 does not 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 macOS 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, + (CFIndex)blob->len); + status = (pkcs_data != NULL) ? errSecSuccess : errSecAllocate; + resource_imported = (pkcs_data != NULL); + } + else { + pkcs_url = + CFURLCreateFromFileSystemRepresentation(NULL, + (const UInt8 *)cPath, + (CFIndex)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 does not 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; +} + +static CURLcode +sectransp_set_ssl_version_min_max(struct Curl_easy *data, + struct st_ssl_backend_data *backend, + struct ssl_primary_config *conn_config) +{ +#if CURL_BUILD_MAC_10_8 || CURL_BUILD_IOS + OSStatus err; + SSLProtocol ver_min; + SSLProtocol ver_max; + +#if CURL_SUPPORT_MAC_10_7 + if(!&SSLSetProtocolVersionMax) + goto legacy; +#endif + + switch(conn_config->version) { + case CURL_SSLVERSION_DEFAULT: + case CURL_SSLVERSION_TLSv1: + case CURL_SSLVERSION_TLSv1_0: + ver_min = kTLSProtocol1; + break; + case CURL_SSLVERSION_TLSv1_1: + ver_min = kTLSProtocol11; + break; + case CURL_SSLVERSION_TLSv1_2: + ver_min = kTLSProtocol12; + break; + case CURL_SSLVERSION_TLSv1_3: + default: + failf(data, "SSL: unsupported minimum TLS version value"); + return CURLE_SSL_CONNECT_ERROR; + } + + switch(conn_config->version_max) { + case CURL_SSLVERSION_MAX_DEFAULT: + case CURL_SSLVERSION_MAX_NONE: + case CURL_SSLVERSION_MAX_TLSv1_3: + case CURL_SSLVERSION_MAX_TLSv1_2: + ver_max = kTLSProtocol12; + break; + case CURL_SSLVERSION_MAX_TLSv1_1: + ver_max = kTLSProtocol11; + break; + case CURL_SSLVERSION_MAX_TLSv1_0: + ver_max = kTLSProtocol1; + break; + default: + failf(data, "SSL: unsupported maximum TLS version value"); + return CURLE_SSL_CONNECT_ERROR; + } + + err = SSLSetProtocolVersionMin(backend->ssl_ctx, ver_min); + if(err != noErr) { + failf(data, "SSL: failed to set minimum TLS version"); + return CURLE_SSL_CONNECT_ERROR; + } + err = SSLSetProtocolVersionMax(backend->ssl_ctx, ver_max); + if(err != noErr) { + failf(data, "SSL: failed to set maximum TLS version"); + return CURLE_SSL_CONNECT_ERROR; + } + + return CURLE_OK; +#endif +#if CURL_SUPPORT_MAC_10_7 + goto legacy; +legacy: + switch(conn_config->version) { + case CURL_SSLVERSION_DEFAULT: + case CURL_SSLVERSION_TLSv1: + case CURL_SSLVERSION_TLSv1_0: + break; + default: + failf(data, "SSL: unsupported minimum TLS version value"); + return CURLE_SSL_CONNECT_ERROR; + } + + /* only TLS 1.0 is supported, disable SSL 3.0 and SSL 2.0 */ + SSLSetProtocolVersionEnabled(backend->ssl_ctx, kSSLProtocolAll, false); + SSLSetProtocolVersionEnabled(backend->ssl_ctx, kTLSProtocol1, true); + + return CURLE_OK; +#endif +} + +static int sectransp_cipher_suite_get_str(uint16_t id, char *buf, + size_t buf_size, bool prefer_rfc) +{ + /* are these fortezza suites even supported ? */ + if(id == SSL_FORTEZZA_DMS_WITH_FORTEZZA_CBC_SHA) + msnprintf(buf, buf_size, "%s", "SSL_FORTEZZA_DMS_WITH_FORTEZZA_CBC_SHA"); + else if(id == SSL_FORTEZZA_DMS_WITH_NULL_SHA) + msnprintf(buf, buf_size, "%s", "SSL_FORTEZZA_DMS_WITH_NULL_SHA"); + /* can TLS_EMPTY_RENEGOTIATION_INFO_SCSV even be set ? */ + else if(id == TLS_EMPTY_RENEGOTIATION_INFO_SCSV) + msnprintf(buf, buf_size, "%s", "TLS_EMPTY_RENEGOTIATION_INFO_SCSV"); + /* do we still need to support these SSL2-only ciphers ? */ + else if(id == SSL_RSA_WITH_RC2_CBC_MD5) + msnprintf(buf, buf_size, "%s", "SSL_RSA_WITH_RC2_CBC_MD5"); + else if(id == SSL_RSA_WITH_IDEA_CBC_MD5) + msnprintf(buf, buf_size, "%s", "SSL_RSA_WITH_IDEA_CBC_MD5"); + else if(id == SSL_RSA_WITH_DES_CBC_MD5) + msnprintf(buf, buf_size, "%s", "SSL_RSA_WITH_DES_CBC_MD5"); + else if(id == SSL_RSA_WITH_3DES_EDE_CBC_MD5) + msnprintf(buf, buf_size, "%s", "SSL_RSA_WITH_3DES_EDE_CBC_MD5"); + else + return Curl_cipher_suite_get_str(id, buf, buf_size, prefer_rfc); + return 0; +} + +static uint16_t sectransp_cipher_suite_walk_str(const char **str, + const char **end) +{ + uint16_t id = Curl_cipher_suite_walk_str(str, end); + size_t len = *end - *str; + + if(!id) { + /* are these fortezza suites even supported ? */ + if(strncasecompare("SSL_FORTEZZA_DMS_WITH_FORTEZZA_CBC_SHA", *str, len)) + id = SSL_FORTEZZA_DMS_WITH_FORTEZZA_CBC_SHA; + else if(strncasecompare("SSL_FORTEZZA_DMS_WITH_NULL_SHA", *str, len)) + id = SSL_FORTEZZA_DMS_WITH_NULL_SHA; + /* can TLS_EMPTY_RENEGOTIATION_INFO_SCSV even be set ? */ + else if(strncasecompare("TLS_EMPTY_RENEGOTIATION_INFO_SCSV", *str, len)) + id = TLS_EMPTY_RENEGOTIATION_INFO_SCSV; + /* do we still need to support these SSL2-only ciphers ? */ + else if(strncasecompare("SSL_RSA_WITH_RC2_CBC_MD5", *str, len)) + id = SSL_RSA_WITH_RC2_CBC_MD5; + else if(strncasecompare("SSL_RSA_WITH_IDEA_CBC_MD5", *str, len)) + id = SSL_RSA_WITH_IDEA_CBC_MD5; + else if(strncasecompare("SSL_RSA_WITH_DES_CBC_MD5", *str, len)) + id = SSL_RSA_WITH_DES_CBC_MD5; + else if(strncasecompare("SSL_RSA_WITH_3DES_EDE_CBC_MD5", *str, len)) + id = SSL_RSA_WITH_3DES_EDE_CBC_MD5; + } + return id; +} + +/* allocated memory must be freed */ +static SSLCipherSuite * sectransp_get_supported_ciphers(SSLContextRef ssl_ctx, + size_t *len) +{ + SSLCipherSuite *ciphers = NULL; + OSStatus err = noErr; + *len = 0; + + err = SSLGetNumberSupportedCiphers(ssl_ctx, len); + if(err != noErr) + goto failed; + + ciphers = malloc(*len * sizeof(SSLCipherSuite)); + if(!ciphers) + goto failed; + + err = SSLGetSupportedCiphers(ssl_ctx, ciphers, len); + if(err != noErr) + goto failed; + +#if CURL_BUILD_MAC + { + int maj = 0, min = 0; + GetDarwinVersionNumber(&maj, &min); + /* There is 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 macOS. */ + if(maj == 12 && min <= 3) { + size_t i = 0, j = 0; + for(; i < *len; i++) { + if(ciphers[i] >= 0xC001 && ciphers[i] <= 0xC032) + continue; + ciphers[j++] = ciphers[i]; + } + *len = j; + } + } +#endif + + return ciphers; +failed: + *len = 0; + Curl_safefree(ciphers); + return NULL; +} + +static CURLcode sectransp_set_default_ciphers(struct Curl_easy *data, + SSLContextRef ssl_ctx) +{ + CURLcode ret = CURLE_SSL_CIPHER; + size_t count = 0, i, j; + OSStatus err; + size_t supported_len; + SSLCipherSuite *ciphers = NULL; + + ciphers = sectransp_get_supported_ciphers(ssl_ctx, &supported_len); + if(!ciphers) { + failf(data, "SSL: Failed to get supported ciphers"); + goto failed; + } + + /* Intersect the ciphers supported by Secure Transport with the default + * ciphers, using the order of the former. */ + for(i = 0; i < supported_len; i++) { + for(j = 0; j < DEFAULT_CIPHERS_LEN; j++) { + if(default_ciphers[j] == ciphers[i]) { + ciphers[count++] = ciphers[i]; + break; + } + } + } + + if(count == 0) { + failf(data, "SSL: no supported default ciphers"); + goto failed; + } + + err = SSLSetEnabledCiphers(ssl_ctx, ciphers, count); + if(err != noErr) { + failf(data, "SSL: SSLSetEnabledCiphers() failed: OSStatus %d", err); + goto failed; + } + + ret = CURLE_OK; +failed: + Curl_safefree(ciphers); + return ret; +} + +static CURLcode sectransp_set_selected_ciphers(struct Curl_easy *data, + SSLContextRef ssl_ctx, + const char *ciphers) +{ + CURLcode ret = CURLE_SSL_CIPHER; + size_t count = 0, i; + const char *ptr, *end; + OSStatus err; + size_t supported_len; + SSLCipherSuite *supported = NULL; + SSLCipherSuite *selected = NULL; + + supported = sectransp_get_supported_ciphers(ssl_ctx, &supported_len); + if(!supported) { + failf(data, "SSL: Failed to get supported ciphers"); + goto failed; + } + + selected = malloc(supported_len * sizeof(SSLCipherSuite)); + if(!selected) { + failf(data, "SSL: Failed to allocate memory"); + goto failed; + } + + for(ptr = ciphers; ptr[0] != '\0' && count < supported_len; ptr = end) { + uint16_t id = sectransp_cipher_suite_walk_str(&ptr, &end); + + /* Check if cipher is supported */ + if(id) { + for(i = 0; i < supported_len && supported[i] != id; i++); + if(i == supported_len) + id = 0; + } + if(!id) { + if(ptr[0] != '\0') + infof(data, "SSL: unknown cipher in list: \"%.*s\"", (int) (end - ptr), + ptr); + continue; + } + + /* No duplicates allowed (so selected cannot overflow) */ + for(i = 0; i < count && selected[i] != id; i++); + if(i < count) { + infof(data, "SSL: duplicate cipher in list: \"%.*s\"", (int) (end - ptr), + ptr); + continue; + } + + selected[count++] = id; + } + + if(count == 0) { + failf(data, "SSL: no supported cipher in list"); + goto failed; + } + + err = SSLSetEnabledCiphers(ssl_ctx, selected, count); + if(err != noErr) { + failf(data, "SSL: SSLSetEnabledCiphers() failed: OSStatus %d", err); + goto failed; + } + + ret = CURLE_OK; +failed: + Curl_safefree(supported); + Curl_safefree(selected); + return ret; +} + +static void sectransp_session_free(void *sessionid, size_t idsize) +{ + /* ST, as of iOS 5 and Mountain Lion, has no public method of deleting a + cached session ID inside the Security framework. There is a private + function that does this, but I do not want to have to explain to you why I + got your application rejected from the App Store due to the use of a + private API, so the best we can do is free up our own char array that we + created way back in sectransp_connect_step1... */ + (void)idsize; + Curl_safefree(sessionid); +} + +static CURLcode sectransp_connect_step1(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + 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; + CURLcode result; +#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: could not create a context"); + return CURLE_OUT_OF_MEMORY; + } + } + else { + /* The old ST API does not exist under iOS, so do not 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: could not 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: could not 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 */ + + result = sectransp_set_ssl_version_min_max(data, backend, conn_config); + if(result != CURLE_OK) + return result; + +#if (CURL_BUILD_MAC_10_13 || CURL_BUILD_IOS_11) && \ + defined(HAVE_BUILTIN_AVAILABLE) + 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; + 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: cannot find the certificate \"%s\" and its private " + "key in the Keychain.", cert_showfilename_error); + break; + default: + failf(data, "SSL: cannot 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 does not 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. + (Secure Transport always validates 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: cannot 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.type != CURL_SSL_PEER_DNS) { + 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) { + result = sectransp_set_selected_ciphers(data, backend->ssl_ctx, ciphers); + } + else { + result = sectransp_set_default_ciphers(data, backend->ssl_ctx); + } + if(result != CURLE_OK) { + failf(data, "SSL: Unable to set ciphers for SSL/TLS handshake. " + "Error code: %d", (int)result); + 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 does not 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 is a cached ID we can/should use here! */ + if(ssl_config->primary.cache_session) { + char *ssl_sessionid; + size_t ssl_sessionid_len; + + Curl_ssl_sessionid_lock(data); + if(!Curl_ssl_getsessionid(cf, data, &connssl->peer, + (void **)&ssl_sessionid, &ssl_sessionid_len)) { + /* we got a session id, use it! */ + err = SSLSetPeerID(backend->ssl_ctx, ssl_sessionid, ssl_sessionid_len); + Curl_ssl_sessionid_unlock(data); + 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 is not one, then let's make one up! This has to be done prior + to starting the handshake. */ + else { + ssl_sessionid = + aprintf("%s:%d:%d:%s:%d", + ssl_cafile ? ssl_cafile : "(blob memory)", + verifypeer, conn_config->verifyhost, connssl->peer.hostname, + connssl->peer.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_set_sessionid(cf, data, &connssl->peer, ssl_sessionid, + ssl_sessionid_len, + sectransp_session_free); + Curl_ssl_sessionid_unlock(data); + if(result) + 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; + 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 CURLcode 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, (CFIndex)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; + CURLcode 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) +{ + CURLcode result; + unsigned char *certbuf; + size_t buflen; + bool free_certbuf = FALSE; + + if(ca_info_blob) { + CURL_TRC_CF(data, cf, "verify_peer, CA from config blob"); + certbuf = ca_info_blob->data; + buflen = ca_info_blob->len; + } + 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; + } + free_certbuf = TRUE; + } + else + return CURLE_SSL_CACERT_BADFILE; + + result = verify_cert_buf(cf, data, certbuf, buflen, ctx); + if(free_certbuf) + 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 was not specified, do not 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 = (size_t)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); + DEBUGASSERT(backend); + CURL_TRC_CF(data, cf, "connect_step2"); + + /* Here goes nothing: */ +check_handshake: + connssl->io_need = CURL_SSL_IO_NEED_NONE; + err = SSLHandshake(backend->ssl_ctx); + + if(err != noErr) { + switch(err) { + case errSSLWouldBlock: /* they are not done with us yet */ + connssl->io_need = backend->ssl_direction ? + CURL_SSL_IO_NEED_SEND : CURL_SSL_IO_NEED_RECV; + return CURLE_OK; + + /* The below is errSSLServerAuthCompleted; it is 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 did not match the server's + hostname: */ + 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 { + char cipher_str[64]; + /* we have been connected fine, we are 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); + + sectransp_cipher_suite_get_str((uint16_t) cipher, cipher_str, + sizeof(cipher_str), true); + switch(protocol) { + case kSSLProtocol2: + infof(data, "SSL 2.0 connection using %s", cipher_str); + break; + case kSSLProtocol3: + infof(data, "SSL 3.0 connection using %s", cipher_str); + break; + case kTLSProtocol1: + infof(data, "TLS 1.0 connection using %s", cipher_str); + break; +#if CURL_BUILD_MAC_10_8 || CURL_BUILD_IOS + case kTLSProtocol11: + infof(data, "TLS 1.1 connection using %s", cipher_str); + break; + case kTLSProtocol12: + infof(data, "TLS 1.2 connection using %s", cipher_str); + 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", cipher_str); + 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) && \ + defined(HAVE_BUILTIN_AVAILABLE) + 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); + + /* chosenProtocol is a reference to the string within alpnArr + and does not 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 does not 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 are 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) { + + /* 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 is available. */ + if(connssl->io_need) { + + curl_socket_t writefd = (connssl->io_need & CURL_SSL_IO_NEED_SEND) ? + sockfd : CURL_SOCKET_BAD; + curl_socket_t readfd = (connssl->io_need & CURL_SSL_IO_NEED_RECV) ? + 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))) + 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 ssize_t sectransp_recv(struct Curl_cfilter *cf, + struct Curl_easy *data, + char *buf, + size_t buffersize, + CURLcode *curlcode); + +static CURLcode sectransp_shutdown(struct Curl_cfilter *cf, + struct Curl_easy *data, + bool send_shutdown, bool *done) +{ + struct ssl_connect_data *connssl = cf->ctx; + struct st_ssl_backend_data *backend = + (struct st_ssl_backend_data *)connssl->backend; + CURLcode result = CURLE_OK; + ssize_t nread; + char buf[1024]; + size_t i; + + DEBUGASSERT(backend); + if(!backend->ssl_ctx || cf->shutdown) { + *done = TRUE; + goto out; + } + + connssl->io_need = CURL_SSL_IO_NEED_NONE; + *done = FALSE; + + if(send_shutdown && !backend->sent_shutdown) { + OSStatus err; + + CURL_TRC_CF(data, cf, "shutdown, send close notify"); + err = SSLClose(backend->ssl_ctx); + switch(err) { + case noErr: + backend->sent_shutdown = TRUE; + break; + case errSSLWouldBlock: + connssl->io_need = CURL_SSL_IO_NEED_SEND; + result = CURLE_OK; + goto out; + default: + CURL_TRC_CF(data, cf, "shutdown, error: %d", (int)err); + result = CURLE_SEND_ERROR; + goto out; + } + } + + for(i = 0; i < 10; ++i) { + if(!backend->sent_shutdown) { + nread = sectransp_recv(cf, data, buf, (int)sizeof(buf), &result); + } + else { + /* We would like to read the close notify from the server using + * Secure Transport, however SSLRead() no longer works after we + * sent the notify from our side. So, we just read from the + * underlying filter and hope it will end. */ + nread = Curl_conn_cf_recv(cf->next, data, buf, sizeof(buf), &result); + } + CURL_TRC_CF(data, cf, "shutdown read -> %zd, %d", nread, result); + if(nread <= 0) + break; + } + + if(nread > 0) { + /* still data coming in? */ + connssl->io_need = CURL_SSL_IO_NEED_RECV; + } + else if(nread == 0) { + /* We got the close notify alert and are done. */ + CURL_TRC_CF(data, cf, "shutdown done"); + *done = TRUE; + } + else if(result == CURLE_AGAIN) { + connssl->io_need = CURL_SSL_IO_NEED_RECV; + result = CURLE_OK; + } + else { + DEBUGASSERT(result); + CURL_TRC_CF(data, cf, "shutdown, error: %d", result); + } + +out: + cf->shutdown = (result || *done); + return result; +} + +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"); +#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 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() is not 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 is 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 did not 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 have 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 is 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 | + SSLSUPP_CIPHER_LIST, + + 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 */ + 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 */ + sectransp_recv, /* recv decrypted data */ + sectransp_send, /* send data to encrypt */ + NULL, /* get_channel_binding */ +}; + +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +#endif /* USE_SECTRANSP */ diff --git a/contrib/curl/lib/vtls/sectransp.h b/contrib/curl/lib/vtls/sectransp.h new file mode 100644 index 0000000000..0f1085ad91 --- /dev/null +++ b/contrib/curl/lib/vtls/sectransp.h @@ -0,0 +1,34 @@ +#ifndef HEADER_CURL_SECTRANSP_H +#define HEADER_CURL_SECTRANSP_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Nick Zitzmann, . + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ +#include "curl_setup.h" + +#ifdef USE_SECTRANSP + +extern const struct Curl_ssl Curl_ssl_sectransp; + +#endif /* USE_SECTRANSP */ +#endif /* HEADER_CURL_SECTRANSP_H */ diff --git a/contrib/curl/lib/vtls/vtls.c b/contrib/curl/lib/vtls/vtls.c index f40a97712e..a5cb7b0ffa 100644 --- a/contrib/curl/lib/vtls/vtls.c +++ b/contrib/curl/lib/vtls/vtls.c @@ -5,11 +5,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2017, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -18,6 +18,8 @@ * 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 @@ -28,17 +30,9 @@ to any specific SSL-layer. Curl_ssl_ - prefix for generic ones - Curl_ossl_ - prefix for OpenSSL ones - Curl_gtls_ - prefix for GnuTLS ones - Curl_nss_ - prefix for NSS ones - Curl_gskit_ - prefix for GSKit ones - Curl_polarssl_ - prefix for PolarSSL ones - Curl_cyassl_ - prefix for CyaSSL ones - Curl_schannel_ - prefix for Schannel SSPI ones - Curl_darwinssl_ - prefix for SecureTransport (Darwin) ones - - Note that this source code uses curlssl_* functions, and they are all - defines/macros #defined by the lib-specific header files. + + 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 @@ -57,8 +51,10 @@ #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" @@ -71,77 +67,341 @@ #include "warnless.h" #include "curl_base64.h" #include "curl_printf.h" +#include "inet_pton.h" +#include "connect.h" +#include "select.h" +#include "strdup.h" +#include "rand.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<var) { \ - dest->var = strdup(source->var); \ - if(!dest->var) \ - return FALSE; \ - } \ - else \ - dest->var = NULL; - -bool -Curl_ssl_config_matches(struct ssl_primary_config* data, - struct ssl_primary_config* needle) -{ - if((data->version == needle->version) && - (data->verifypeer == needle->verifypeer) && - (data->verifyhost == needle->verifyhost) && - Curl_safe_strcasecompare(data->CApath, needle->CApath) && - Curl_safe_strcasecompare(data->CAfile, needle->CAfile) && - Curl_safe_strcasecompare(data->clientcert, needle->clientcert) && - Curl_safe_strcasecompare(data->cipher_list, needle->cipher_list)) + 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 is 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 = { + { ALPN_H2 }, 1 +}; +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_PRIOR_KNOWLEDGE) + return &ALPN_SPEC_H2; + 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 do not 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.cache_session = TRUE; /* 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_clone_primary_ssl_config(struct ssl_primary_config *source, - struct ssl_primary_config *dest) +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->verifyhost = source->verifyhost; - dest->verifypeer = source->verifypeer; dest->version = source->version; + dest->version_max = source->version_max; + dest->verifypeer = source->verifypeer; + dest->verifyhost = source->verifyhost; + dest->verifystatus = source->verifystatus; + dest->cache_session = source->cache_session; + dest->ssl_options = source->ssl_options; - CLONE_STRING(CAfile); + CLONE_BLOB(cert_blob); + CLONE_BLOB(ca_info_blob); + CLONE_BLOB(issuercert_blob); CLONE_STRING(CApath); - CLONE_STRING(cipher_list); - CLONE_STRING(egdsocket); - CLONE_STRING(random_file); + 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; } -void Curl_free_primary_ssl_config(struct ssl_primary_config* sslc) +static void Curl_free_primary_ssl_config(struct ssl_primary_config *sslc) { - Curl_safefree(sslc->CAfile); Curl_safefree(sslc->CApath); - Curl_safefree(sslc->cipher_list); - Curl_safefree(sslc->egdsocket); - Curl_safefree(sslc->random_file); + 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; + } } -int Curl_ssl_backend(void) +#ifdef USE_SSL +static int multissl_setup(const struct Curl_ssl *backend); +#endif + +curl_sslbackend Curl_ssl_backend(void) { - return (int)CURL_SSL_BACKEND; +#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; +static bool init_ssl = FALSE; /** * Global SSL init @@ -156,147 +416,135 @@ int Curl_ssl_init(void) return 1; init_ssl = TRUE; /* never again */ - return curlssl_init(); -} - - -/* Global cleanup */ -void Curl_ssl_cleanup(void) -{ - if(init_ssl) { - /* only cleanup if we did a previous init */ - curlssl_cleanup(); - init_ssl = FALSE; - } + return Curl_ssl->init(); } static bool ssl_prefs_check(struct Curl_easy *data) { /* check for CURLOPT_SSLVERSION invalid parameter value */ - if((data->set.ssl.primary.version < 0) - || (data->set.ssl.primary.version >= CURL_SSLVERSION_LAST)) { + 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 CURLcode -ssl_connect_init_proxy(struct connectdata *conn, int sockindex) -{ - DEBUGASSERT(conn->bits.proxy_ssl_connected[sockindex]); - if(ssl_connection_complete == conn->ssl[sockindex].state && - !conn->proxy_ssl[sockindex].use) { -#if defined(HTTPS_PROXY_SUPPORT) - conn->proxy_ssl[sockindex] = conn->ssl[sockindex]; - memset(&conn->ssl[sockindex], 0, sizeof(conn->ssl[sockindex])); -#else - return CURLE_NOT_BUILT_IN; -#endif +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 CURLE_OK; + return ctx; } -CURLcode -Curl_ssl_connect(struct connectdata *conn, int sockindex) +static void cf_ctx_free(struct ssl_connect_data *ctx) { - CURLcode result; - - if(conn->bits.proxy_ssl_connected[sockindex]) { - result = ssl_connect_init_proxy(conn, sockindex); - if(result) - return result; + 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(conn->data)) + if(!ssl_prefs_check(data)) return CURLE_SSL_CONNECT_ERROR; /* mark this is being ssl-enabled from here on. */ - conn->ssl[sockindex].use = TRUE; - conn->ssl[sockindex].state = ssl_connection_negotiating; + connssl->state = ssl_connection_negotiating; - result = curlssl_connect(conn, sockindex); + result = Curl_ssl->connect_blocking(cf, data); - if(!result) - Curl_pgrsTime(conn->data, TIMER_APPCONNECT); /* SSL is connected */ + if(!result) { + DEBUGASSERT(connssl->state == ssl_connection_complete); + } return result; } -CURLcode -Curl_ssl_connect_nonblocking(struct connectdata *conn, int sockindex, - bool *done) +static CURLcode +ssl_connect_nonblocking(struct Curl_cfilter *cf, struct Curl_easy *data, + bool *done) { - CURLcode result; - if(conn->bits.proxy_ssl_connected[sockindex]) { - result = ssl_connect_init_proxy(conn, sockindex); - if(result) - return result; - } - - if(!ssl_prefs_check(conn->data)) + if(!ssl_prefs_check(data)) return CURLE_SSL_CONNECT_ERROR; /* mark this is being ssl requested from here on. */ - conn->ssl[sockindex].use = TRUE; -#ifdef curlssl_connect_nonblocking - result = curlssl_connect_nonblocking(conn, sockindex, done); -#else - *done = TRUE; /* fallback to BLOCKING */ - result = curlssl_connect(conn, sockindex); -#endif /* non-blocking connect support */ - if(!result && *done) - Curl_pgrsTime(conn->data, TIMER_APPCONNECT); /* SSL is connected */ - return result; + return Curl_ssl->connect_nonblocking(cf, data, done); } /* * Lock shared SSL session data */ -void Curl_ssl_sessionid_lock(struct connectdata *conn) +void Curl_ssl_sessionid_lock(struct Curl_easy *data) { - if(SSLSESSION_SHARED(conn->data)) - Curl_share_lock(conn->data, - CURL_LOCK_DATA_SSL_SESSION, CURL_LOCK_ACCESS_SINGLE); + 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 connectdata *conn) +void Curl_ssl_sessionid_unlock(struct Curl_easy *data) { - if(SSLSESSION_SHARED(conn->data)) - Curl_share_unlock(conn->data, CURL_LOCK_DATA_SSL_SESSION); + 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. + * Check if there is a session ID for the given connection in the cache, and if + * there is one suitable, it is provided. Returns TRUE when no entry matched. */ -bool Curl_ssl_getsessionid(struct connectdata *conn, +bool Curl_ssl_getsessionid(struct Curl_cfilter *cf, + struct Curl_easy *data, + const struct ssl_peer *peer, void **ssl_sessionid, - size_t *idsize, /* set 0 if unknown */ - int sockindex) + size_t *idsize) /* set 0 if unknown */ { - struct curl_ssl_session *check; - struct Curl_easy *data = conn->data; + 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; - const bool isProxy = CONNECT_PROXY_SSL(); - struct ssl_primary_config * const ssl_config = isProxy ? - &conn->proxy_ssl_config : - &conn->ssl_config; - const char * const name = isProxy ? conn->http_proxy.host.name : - conn->host.name; - int port = isProxy ? (int)conn->port : conn->remote_port; *ssl_sessionid = NULL; + if(!ssl_config) + return TRUE; - DEBUGASSERT(data->set.general_ssl.sessionid); + DEBUGASSERT(ssl_config->primary.cache_session); - if(!data->set.general_ssl.sessionid) - /* session ID re-use is disabled */ + if(!ssl_config->primary.cache_session || !data->state.session) + /* session ID reuse is disabled or the session cache has not been + setup */ return TRUE; /* Lock if shared */ @@ -310,16 +558,17 @@ bool Curl_ssl_getsessionid(struct connectdata *conn, if(!check->sessionid) /* not session ID means blank entry */ continue; - if(strcasecompare(name, check->name) && - ((!conn->bits.conn_to_host && !check->conn_to_host) || - (conn->bits.conn_to_host && check->conn_to_host && - strcasecompare(conn->conn_to_host.name, check->conn_to_host))) && - ((!conn->bits.conn_to_port && check->conn_to_port == -1) || - (conn->bits.conn_to_port && check->conn_to_port != -1 && - conn->conn_to_port == check->conn_to_port)) && - (port == check->remote_port) && - strcasecompare(conn->handler->scheme, check->scheme) && - Curl_ssl_config_matches(ssl_config, &check->ssl_config)) { + if(strcasecompare(peer->hostname, check->name) && + ((!cf->conn->bits.conn_to_host && !check->conn_to_host) || + (cf->conn->bits.conn_to_host && check->conn_to_host && + strcasecompare(cf->conn->conn_to_host.name, check->conn_to_host))) && + ((!cf->conn->bits.conn_to_port && check->conn_to_port == -1) || + (cf->conn->bits.conn_to_port && check->conn_to_port != -1 && + cf->conn->conn_to_port == check->conn_to_port)) && + (peer->port == check->remote_port) && + (peer->transport == check->transport) && + strcasecompare(cf->conn->handler->scheme, check->scheme) && + match_ssl_primary_config(data, conn_config, &check->ssl_config)) { /* yes, we have a session ID! */ (*general_age)++; /* increase general age */ check->age = *general_age; /* set this as used in this age */ @@ -331,21 +580,25 @@ bool Curl_ssl_getsessionid(struct connectdata *conn, } } + CURL_TRC_CF(data, cf, "%s cached session ID for %s://%s:%d", + no_match ? "No" : "Found", + cf->conn->handler->scheme, peer->hostname, peer->port); return no_match; } /* * Kill a single session ID entry in the cache. */ -void Curl_ssl_kill_session(struct curl_ssl_session *session) +void Curl_ssl_kill_session(struct Curl_ssl_session *session) { if(session->sessionid) { /* defensive check */ /* free the ID the SSL-layer specific way */ - curlssl_session_free(session->sessionid); + session->sessionid_free(session->sessionid, session->idsize); session->sessionid = NULL; + session->sessionid_free = NULL; session->age = 0; /* fresh */ Curl_free_primary_ssl_config(&session->ssl_config); @@ -358,13 +611,12 @@ void Curl_ssl_kill_session(struct curl_ssl_session *session) /* * Delete the given session ID from the cache. */ -void Curl_ssl_delsessionid(struct connectdata *conn, void *ssl_sessionid) +void Curl_ssl_delsessionid(struct Curl_easy *data, void *ssl_sessionid) { size_t i; - struct Curl_easy *data=conn->data; for(i = 0; i < data->set.general_ssl.max_ssl_sessions; i++) { - struct curl_ssl_session *check = &data->state.session[i]; + struct Curl_ssl_session *check = &data->state.session[i]; if(check->sessionid == ssl_sessionid) { Curl_ssl_kill_session(check); @@ -373,52 +625,66 @@ void Curl_ssl_delsessionid(struct connectdata *conn, void *ssl_sessionid) } } -/* - * 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 connectdata *conn, - void *ssl_sessionid, - size_t idsize, - int sockindex) +CURLcode Curl_ssl_set_sessionid(struct Curl_cfilter *cf, + struct Curl_easy *data, + const struct ssl_peer *peer, + void *ssl_sessionid, + size_t idsize, + Curl_ssl_sessionid_dtor *sessionid_free_cb) { + 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_easy *data=conn->data; /* the mother of all structs */ - struct curl_ssl_session *store = &data->state.session[0]; - long oldest_age=data->state.session[0].age; /* zero if unused */ - char *clone_host; - char *clone_conn_to_host; + struct Curl_ssl_session *store; + long oldest_age; + char *clone_host = NULL; + char *clone_conn_to_host = NULL; int conn_to_port; long *general_age; - const bool isProxy = CONNECT_PROXY_SSL(); - struct ssl_primary_config * const ssl_config = isProxy ? - &conn->proxy_ssl_config : - &conn->ssl_config; + void *old_sessionid; + size_t old_size; + CURLcode result = CURLE_OUT_OF_MEMORY; - DEBUGASSERT(data->set.general_ssl.sessionid); + DEBUGASSERT(ssl_sessionid); + DEBUGASSERT(sessionid_free_cb); - clone_host = strdup(isProxy ? conn->http_proxy.host.name : conn->host.name); - if(!clone_host) - return CURLE_OUT_OF_MEMORY; /* bail out */ + if(!data->state.session) { + sessionid_free_cb(ssl_sessionid, idsize); + return CURLE_OK; + } - if(conn->bits.conn_to_host) { - clone_conn_to_host = strdup(conn->conn_to_host.name); - if(!clone_conn_to_host) { - free(clone_host); - return CURLE_OUT_OF_MEMORY; /* bail out */ + if(!Curl_ssl_getsessionid(cf, data, peer, &old_sessionid, &old_size)) { + if((old_size == idsize) && + ((old_sessionid == ssl_sessionid) || + (idsize && !memcmp(old_sessionid, ssl_sessionid, idsize)))) { + /* the very same */ + sessionid_free_cb(ssl_sessionid, idsize); + return CURLE_OK; } + Curl_ssl_delsessionid(data, old_sessionid); + } + + store = &data->state.session[0]; + oldest_age = data->state.session[0].age; /* zero if unused */ + DEBUGASSERT(ssl_config->primary.cache_session); + (void)ssl_config; + + clone_host = strdup(peer->hostname); + if(!clone_host) + goto out; + + if(cf->conn->bits.conn_to_host) { + clone_conn_to_host = strdup(cf->conn->conn_to_host.name); + if(!clone_conn_to_host) + goto out; } - else - clone_conn_to_host = NULL; - if(conn->bits.conn_to_port) - conn_to_port = conn->conn_to_port; + 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 + /* Now we should add the session ID and the hostname to the cache, (remove the oldest if necessary) */ /* If using shared SSL session, lock! */ @@ -444,35 +710,59 @@ CURLcode Curl_ssl_addsessionid(struct connectdata *conn, store = &data->state.session[i]; /* use this slot */ /* now init the session struct wisely */ + if(!clone_ssl_primary_config(conn_config, &store->ssl_config)) { + Curl_free_primary_ssl_config(&store->ssl_config); + store->sessionid = NULL; /* let caller free sessionid */ + goto out; + } store->sessionid = ssl_sessionid; store->idsize = idsize; + store->sessionid_free = sessionid_free_cb; store->age = *general_age; /* set current age */ - /* free it if there's one already present */ + /* free it if there is 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->name = clone_host; /* clone hostname */ + clone_host = NULL; + store->conn_to_host = clone_conn_to_host; /* clone connect to hostname */ + clone_conn_to_host = NULL; store->conn_to_port = conn_to_port; /* connect to port number */ /* port number */ - store->remote_port = isProxy ? (int)conn->port : conn->remote_port; - store->scheme = conn->handler->scheme; - - if(!Curl_clone_primary_ssl_config(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; + store->remote_port = peer->port; + store->scheme = cf->conn->handler->scheme; + store->transport = peer->transport; + + result = CURLE_OK; + +out: + free(clone_host); + free(clone_conn_to_host); + if(result) { + failf(data, "Failed to add Session ID to cache for %s://%s:%d [%s]", + store->scheme, store->name, store->remote_port, + Curl_ssl_cf_is_proxy(cf) ? "PROXY" : "server"); + sessionid_free_cb(ssl_sessionid, idsize); + return result; } - + CURL_TRC_CF(data, cf, "Added Session ID to cache for %s://%s:%d [%s]", + store->scheme, store->name, store->remote_port, + Curl_ssl_cf_is_proxy(cf) ? "PROXY" : "server"); return CURLE_OK; } +CURLcode Curl_ssl_get_channel_binding(struct Curl_easy *data, int sockindex, + struct dynbuf *binding) +{ + if(Curl_ssl->get_channel_binding) + return Curl_ssl->get_channel_binding(data, sockindex, binding); + return CURLE_OK; +} void Curl_ssl_close_all(struct Curl_easy *data) { - size_t i; /* 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]); @@ -481,84 +771,49 @@ void Curl_ssl_close_all(struct Curl_easy *data) Curl_safefree(data->state.session); } - curlssl_close_all(data); + Curl_ssl->close_all(data); } -#if defined(USE_OPENSSL) || defined(USE_GNUTLS) || defined(USE_SCHANNEL) || \ - defined(USE_DARWINSSL) || defined(USE_POLARSSL) || defined(USE_NSS) || \ - defined(USE_MBEDTLS) -int Curl_ssl_getsock(struct connectdata *conn, curl_socket_t *socks, - int numsocks) +void Curl_ssl_adjust_pollset(struct Curl_cfilter *cf, struct Curl_easy *data, + struct easy_pollset *ps) { - struct ssl_connect_data *connssl = &conn->ssl[FIRSTSOCKET]; - - if(!numsocks) - return GETSOCK_BLANK; - - if(connssl->connecting_state == ssl_connect_2_writing) { - /* write mode */ - socks[0] = conn->sock[FIRSTSOCKET]; - return GETSOCK_WRITESOCK(0); - } - else if(connssl->connecting_state == ssl_connect_2_reading) { - /* read mode */ - socks[0] = conn->sock[FIRSTSOCKET]; - return GETSOCK_READSOCK(0); + struct ssl_connect_data *connssl = cf->ctx; + + if(connssl->io_need) { + curl_socket_t sock = Curl_conn_cf_get_socket(cf->next, data); + if(sock != CURL_SOCKET_BAD) { + if(connssl->io_need & CURL_SSL_IO_NEED_SEND) { + Curl_pollset_set_out_only(data, ps, sock); + CURL_TRC_CF(data, cf, "adjust_pollset, POLLOUT fd=%" FMT_SOCKET_T, + sock); + } + else { + Curl_pollset_set_in_only(data, ps, sock); + CURL_TRC_CF(data, cf, "adjust_pollset, POLLIN fd=%" FMT_SOCKET_T, + sock); + } + } } - - return GETSOCK_BLANK; -} -#else -int Curl_ssl_getsock(struct connectdata *conn, - curl_socket_t *socks, - int numsocks) -{ - (void)conn; - (void)socks; - (void)numsocks; - return GETSOCK_BLANK; -} -/* USE_OPENSSL || USE_GNUTLS || USE_SCHANNEL || USE_DARWINSSL || USE_NSS */ -#endif - -void Curl_ssl_close(struct connectdata *conn, int sockindex) -{ - DEBUGASSERT((sockindex <= 1) && (sockindex >= -1)); - curlssl_close(conn, sockindex); -} - -CURLcode Curl_ssl_shutdown(struct connectdata *conn, int sockindex) -{ - if(curlssl_shutdown(conn, sockindex)) - return CURLE_SSL_SHUTDOWN_FAILED; - - conn->ssl[sockindex].use = FALSE; /* get back to ordinary socket usage */ - conn->ssl[sockindex].state = ssl_connection_none; - - conn->recv[sockindex] = Curl_recv_plain; - conn->send[sockindex] = Curl_send_plain; - - return CURLE_OK; } /* Selects an SSL crypto engine */ CURLcode Curl_ssl_set_engine(struct Curl_easy *data, const char *engine) { - return curlssl_set_engine(data, 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 curlssl_set_engine_default(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 curlssl_engines_list(data); + return Curl_ssl->engines_list(data); } /* @@ -567,13 +822,13 @@ struct curl_slist *Curl_ssl_engines_list(struct Curl_easy *data) */ CURLcode Curl_ssl_initsessions(struct Curl_easy *data, size_t amount) { - struct curl_ssl_session *session; + 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)); + session = calloc(amount, sizeof(struct Curl_ssl_session)); if(!session) return CURLE_OUT_OF_MEMORY; @@ -584,38 +839,25 @@ CURLcode Curl_ssl_initsessions(struct Curl_easy *data, size_t amount) return CURLE_OK; } -size_t Curl_ssl_version(char *buffer, size_t size) -{ - return curlssl_version(buffer, size); -} - -/* - * 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 - */ -int Curl_ssl_check_cxn(struct connectdata *conn) -{ - return curlssl_check_cxn(conn); -} +static size_t multissl_version(char *buffer, size_t size); -bool Curl_ssl_data_pending(const struct connectdata *conn, - int connindex) +void Curl_ssl_version(char *buffer, size_t size) { - return curlssl_data_pending(conn, connindex); +#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) { - int i; struct curl_certinfo *ci = &data->info.certs; if(ci->num_of_certs) { /* free all individual lists used */ - for(i=0; inum_of_certs; i++) { + int i; + for(i = 0; i < ci->num_of_certs; i++) { curl_slist_free_all(ci->certinfo[i]); ci->certinfo[i] = NULL; } @@ -646,7 +888,7 @@ CURLcode Curl_ssl_init_certinfo(struct Curl_easy *data, int num) } /* - * 'value' is NOT a zero terminated string + * 'value' is NOT a null-terminated string */ CURLcode Curl_ssl_push_certinfo_len(struct Curl_easy *data, int certnum, @@ -655,28 +897,23 @@ CURLcode Curl_ssl_push_certinfo_len(struct Curl_easy *data, 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; + struct dynbuf build; - /* sprintf the label and colon */ - snprintf(output, outlen, "%s:", label); + DEBUGASSERT(certnum < ci->num_of_certs); - /* memcpy the value (it might not be zero terminated) */ - memcpy(&output[labellen+1], value, valuelen); + Curl_dyn_init(&build, CURL_X509_STR_MAX); - /* zero terminate the output */ - output[labellen + 1 + valuelen] = 0; + if(Curl_dyn_add(&build, label) || + Curl_dyn_addn(&build, ":", 1) || + Curl_dyn_addn(&build, value, valuelen)) + return CURLE_OUT_OF_MEMORY; - nl = Curl_slist_append_nodup(ci->certinfo[certnum], output); + nl = Curl_slist_append_nodup(ci->certinfo[certnum], + Curl_dyn_ptr(&build)); if(!nl) { - free(output); + Curl_dyn_free(&build); curl_slist_free_all(ci->certinfo[certnum]); result = CURLE_OUT_OF_MEMORY; } @@ -685,25 +922,16 @@ CURLcode Curl_ssl_push_certinfo_len(struct Curl_easy *data, return result; } -/* - * This is a convenience function for push_certinfo_len that takes a zero - * terminated value. - */ -CURLcode Curl_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); -} - +/* get 32 bits of random */ CURLcode Curl_ssl_random(struct Curl_easy *data, unsigned char *entropy, size_t length) { - return curlssl_random(data, entropy, length); + DEBUGASSERT(length == sizeof(int)); + if(Curl_ssl->random) + return Curl_ssl->random(data, entropy, length); + else + return CURLE_NOT_BUILT_IN; } /* @@ -774,18 +1002,12 @@ CURLcode Curl_pin_peer_pubkey(struct Curl_easy *data, { FILE *fp; unsigned char *buf = NULL, *pem_ptr = NULL; - long filesize; - size_t size, pem_len; - CURLcode pem_read; CURLcode result = CURLE_SSL_PINNEDPUBKEYNOTMATCH; -#ifdef curlssl_sha256sum - CURLcode encode; - size_t encodedlen, pinkeylen; - char *encoded, *pinkeycopy, *begin_pos, *end_pos; - unsigned char *sha256sumdigest = NULL; +#ifdef CURL_DISABLE_VERBOSE_STRINGS + (void)data; #endif - /* if a path wasn't specified, don't pin */ + /* if a path was not specified, do not pin */ if(!pinnedpubkey) return CURLE_OK; if(!pubkey || !pubkeylen) @@ -793,37 +1015,47 @@ CURLcode Curl_pin_peer_pubkey(struct Curl_easy *data, /* only do this if pinnedpubkey starts with "sha256//", length 8 */ if(strncmp(pinnedpubkey, "sha256//", 8) == 0) { -#ifdef curlssl_sha256sum + CURLcode encode; + size_t encodedlen = 0; + 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(SHA256_DIGEST_LENGTH); + sha256sumdigest = malloc(CURL_SHA256_DIGEST_LENGTH); if(!sha256sumdigest) return CURLE_OUT_OF_MEMORY; - curlssl_sha256sum(pubkey, pubkeylen, - sha256sumdigest, SHA256_DIGEST_LENGTH); - encode = Curl_base64_encode(data, (char *)sha256sumdigest, - SHA256_DIGEST_LENGTH, &encoded, &encodedlen); + 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, "\t public key hash: sha256//%s\n", encoded); + 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); + pinkeycopy = strdup(pinnedpubkey); 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 + * otherwise it will go to the end of the original string */ if(end_pos) end_pos[0] = '\0'; @@ -846,10 +1078,6 @@ CURLcode Curl_pin_peer_pubkey(struct Curl_easy *data, } while(end_pos && begin_pos); Curl_safefree(encoded); Curl_safefree(pinkeycopy); -#else - /* without sha256 support, this cannot match */ - (void)data; -#endif return result; } @@ -858,6 +1086,10 @@ CURLcode Curl_pin_peer_pubkey(struct Curl_easy *data, 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; @@ -869,7 +1101,7 @@ CURLcode Curl_pin_peer_pubkey(struct Curl_easy *data, /* * if the size of our certificate is bigger than the file - * size then it can't match + * size then it cannot match */ size = curlx_sotouz((curl_off_t) filesize); if(pubkeylen > size) @@ -887,7 +1119,7 @@ CURLcode Curl_pin_peer_pubkey(struct Curl_easy *data, 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 the sizes are the same, it cannot be base64 encoded, must be der */ if(pubkeylen == size) { if(!memcmp(pubkey, buf, pubkeylen)) result = CURLE_OK; @@ -895,18 +1127,18 @@ CURLcode Curl_pin_peer_pubkey(struct Curl_easy *data, } /* - * Otherwise we will assume it's PEM and try to decode it + * Otherwise we will assume it is 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 it was not 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 the size of our certificate does not match the size of + * the decoded file, they cannot be the same, otherwise compare */ if(pubkeylen == pem_len && !memcmp(pubkey, pem_ptr, pubkeylen)) result = CURLE_OK; @@ -919,51 +1151,1121 @@ CURLcode Curl_pin_peer_pubkey(struct Curl_easy *data, return result; } -#ifndef CURL_DISABLE_CRYPTO_AUTH -CURLcode Curl_ssl_md5sum(unsigned char *tmp, /* input */ - size_t tmplen, - unsigned char *md5sum, /* output */ - size_t md5len) -{ -#ifdef curlssl_md5sum - curlssl_md5sum(tmp, tmplen, md5sum, md5len); -#else - MD5_context *MD5pw; - - (void) md5len; - - MD5pw = Curl_MD5_init(Curl_DIGEST_MD5); - if(!MD5pw) - return CURLE_OUT_OF_MEMORY; - Curl_MD5_update(MD5pw, tmp, curlx_uztoui(tmplen)); - Curl_MD5_final(MD5pw, md5sum); -#endif - return CURLE_OK; -} -#endif - /* * Check whether the SSL backend supports the status_request extension. */ bool Curl_ssl_cert_status_request(void) { -#ifdef curlssl_cert_status_request - return curlssl_cert_status_request(); -#else - return FALSE; -#endif + return Curl_ssl->cert_status_request(); } /* * Check whether the SSL backend supports false start. */ -bool Curl_ssl_false_start(void) +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) +{ } + +CURLcode Curl_none_shutdown(struct Curl_cfilter *cf UNUSED_PARAM, + struct Curl_easy *data UNUSED_PARAM, + bool send_shutdown UNUSED_PARAM, + bool *done) +{ + (void)data; + (void)cf; + (void)send_shutdown; + /* Every SSL backend should have a shutdown implementation. Until we + * have implemented that, we put this fake in place. */ + *done = TRUE; + return CURLE_OK; +} + +int Curl_none_check_cxn(struct Curl_cfilter *cf, struct Curl_easy *data) +{ + (void)cf; + (void)data; + return -1; +} + +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) { -#ifdef curlssl_false_start - return curlssl_false_start(); -#else return FALSE; -#endif +} + +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 */ + NULL, /* 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_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 */ + multissl_recv_plain, /* recv decrypted data */ + multissl_send_plain, /* send data to encrypt */ + NULL, /* get_channel_binding */ +}; + +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_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_SECTRANSP) + &Curl_ssl_sectransp; +#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_GNUTLS) + &Curl_ssl_gnutls, +#endif +#if defined(USE_MBEDTLS) + &Curl_ssl_mbedtls, +#endif +#if defined(USE_OPENSSL) + &Curl_ssl_openssl, +#endif +#if defined(USE_SECTRANSP) + &Curl_ssl_sectransp, +#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 +}; + +/* 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 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) { + if(backends_len < size) + strcpy(buffer, backends); + else + *buffer = 0; /* did not fit */ + } + return 0; +} + +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->type = CURL_SSL_PEER_DNS; +} + +static void cf_close(struct Curl_cfilter *cf, struct Curl_easy *data) +{ + struct ssl_connect_data *connssl = cf->ctx; + if(connssl) { + Curl_ssl->close(cf, data); + connssl->state = ssl_connection_none; + Curl_ssl_peer_cleanup(&connssl->peer); + } + cf->connected = FALSE; +} + +static ssl_peer_type get_peer_type(const char *hostname) +{ + if(hostname && hostname[0]) { +#ifdef USE_IPV6 + struct in6_addr addr; +#else + struct in_addr addr; +#endif + if(Curl_inet_pton(AF_INET, hostname, &addr)) + return CURL_SSL_PEER_IPV4; +#ifdef USE_IPV6 + else if(Curl_inet_pton(AF_INET6, hostname, &addr)) { + return CURL_SSL_PEER_IPV6; + } +#endif + } + return CURL_SSL_PEER_DNS; +} + +CURLcode Curl_ssl_peer_init(struct ssl_peer *peer, struct Curl_cfilter *cf, + int transport) +{ + const char *ehostname, *edispname; + CURLcode result = CURLE_OUT_OF_MEMORY; + + /* We expect a clean struct, e.g. called only ONCE */ + DEBUGASSERT(peer); + DEBUGASSERT(!peer->hostname); + DEBUGASSERT(!peer->dispname); + DEBUGASSERT(!peer->sni); + /* We need the hostname for SNI negotiation. Once handshaked, this remains + * the SNI hostname for the TLS connection. When the connection is reused, + * the settings in cf->conn might change. We keep a copy of the hostname we + * use for SNI. + */ + peer->transport = transport; +#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; + peer->port = cf->conn->http_proxy.port; + } + else +#endif + { + ehostname = cf->conn->host.name; + edispname = cf->conn->host.dispname; + peer->port = cf->conn->remote_port; + } + + /* hostname MUST exist and not be empty */ + if(!ehostname || !ehostname[0]) { + result = CURLE_FAILED_INIT; + goto out; + } + + peer->hostname = strdup(ehostname); + if(!peer->hostname) + goto out; + if(!edispname || !strcmp(ehostname, edispname)) + peer->dispname = peer->hostname; + else { + peer->dispname = strdup(edispname); + if(!peer->dispname) + goto out; + } + peer->type = get_peer_type(peer->hostname); + if(peer->type == CURL_SSL_PEER_DNS) { + /* 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) + goto out; + Curl_strntolower(peer->sni, peer->hostname, len); + peer->sni[len] = 0; + } + } + result = CURLE_OK; + +out: + if(result) + Curl_ssl_peer_cleanup(peer); + return result; +} + +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; + } + + if(!cf->next) { + *done = FALSE; + return CURLE_FAILED_INIT; + } + + if(!cf->next->connected) { + result = cf->next->cft->do_connect(cf->next, data, blocking, done); + if(result || !*done) + return result; + } + + CF_DATA_SAVE(save, cf, data); + CURL_TRC_CF(data, cf, "cf_connect()"); + DEBUGASSERT(data->conn); + DEBUGASSERT(data->conn == cf->conn); + DEBUGASSERT(connssl); + + *done = FALSE; + if(!connssl->peer.hostname) { + result = Curl_ssl_peer_init(&connssl->peer, cf, TRNSPRT_TCP); + 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, + bool eos, CURLcode *err) +{ + struct cf_call_data save; + ssize_t nwritten = 0; + + (void)eos; + /* OpenSSL and maybe other TLS libs do not like 0-length writes. Skip. */ + *err = CURLE_OK; + if(len > 0) { + CF_DATA_SAVE(save, cf, data); + 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 CURLcode ssl_cf_shutdown(struct Curl_cfilter *cf, + struct Curl_easy *data, + bool *done) +{ + CURLcode result = CURLE_OK; + + *done = TRUE; + if(!cf->shutdown) { + struct cf_call_data save; + + CF_DATA_SAVE(save, cf, data); + result = Curl_ssl->shut_down(cf, data, TRUE, done); + CURL_TRC_CF(data, cf, "cf_shutdown -> %d, done=%d", result, *done); + CF_DATA_RESTORE(cf, save); + cf->shutdown = (result || *done); + } + return result; +} + +static void ssl_cf_adjust_pollset(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct easy_pollset *ps) +{ + struct cf_call_data save; + + CF_DATA_SAVE(save, cf, data); + Curl_ssl->adjust_pollset(cf, data, ps); + CF_DATA_RESTORE(cf, save); +} + +static CURLcode ssl_cf_cntrl(struct Curl_cfilter *cf, + 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, + ssl_cf_shutdown, + 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, +}; + +#ifndef CURL_DISABLE_PROXY + +struct Curl_cftype Curl_cft_ssl_proxy = { + "SSL-PROXY", + CF_TYPE_SSL|CF_TYPE_PROXY, + CURL_LOG_LVL_NONE, + ssl_cf_destroy, + ssl_cf_connect, + ssl_cf_close, + ssl_cf_shutdown, + Curl_cf_def_get_host, + ssl_cf_adjust_pollset, + ssl_cf_data_pending, + ssl_cf_send, + ssl_cf_recv, + ssl_cf_cntrl, + cf_ssl_is_alive, + Curl_cf_def_conn_keep_alive, + Curl_cf_def_query, +}; + +#endif /* !CURL_DISABLE_PROXY */ + +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, unsigned int ssl_option) +{ + (void)data; + return (Curl_ssl->supports & ssl_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) + return cf; +#ifndef CURL_DISABLE_PROXY + if(cf->cft == &Curl_cft_ssl_proxy) + return cf; +#endif + } + 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; +} + +static CURLcode vtls_shutdown_blocking(struct Curl_cfilter *cf, + struct Curl_easy *data, + bool send_shutdown, bool *done) +{ + struct ssl_connect_data *connssl = cf->ctx; + struct cf_call_data save; + CURLcode result = CURLE_OK; + timediff_t timeout_ms; + int what, loop = 10; + + if(cf->shutdown) { + *done = TRUE; + return CURLE_OK; + } + CF_DATA_SAVE(save, cf, data); + + *done = FALSE; + while(!result && !*done && loop--) { + timeout_ms = Curl_shutdown_timeleft(cf->conn, cf->sockindex, NULL); + + if(timeout_ms < 0) { + /* no need to continue if time is already up */ + failf(data, "SSL shutdown timeout"); + return CURLE_OPERATION_TIMEDOUT; + } + + result = Curl_ssl->shut_down(cf, data, send_shutdown, done); + if(result ||*done) + goto out; + + if(connssl->io_need) { + what = Curl_conn_cf_poll(cf, data, timeout_ms); + if(what < 0) { + /* fatal error */ + failf(data, "select/poll on SSL socket, errno: %d", SOCKERRNO); + result = CURLE_RECV_ERROR; + goto out; + } + else if(0 == what) { + /* timeout */ + failf(data, "SSL shutdown timeout"); + result = CURLE_OPERATION_TIMEDOUT; + goto out; + } + /* socket is readable or writable */ + } + } +out: + CF_DATA_RESTORE(cf, save); + cf->shutdown = (result || *done); + return result; +} + +CURLcode Curl_ssl_cfilter_remove(struct Curl_easy *data, + int sockindex, bool send_shutdown) +{ + struct Curl_cfilter *cf, *head; + CURLcode result = CURLE_OK; + + head = data->conn ? data->conn->cfilter[sockindex] : NULL; + for(cf = head; cf; cf = cf->next) { + if(cf->cft == &Curl_cft_ssl) { + bool done; + CURL_TRC_CF(data, cf, "shutdown and remove SSL, start"); + Curl_shutdown_start(data, sockindex, NULL); + result = vtls_shutdown_blocking(cf, data, send_shutdown, &done); + Curl_shutdown_clear(data, sockindex); + if(!result && !done) /* blocking failed? */ + result = CURLE_SSL_SHUTDOWN_FAILED; + Curl_conn_cf_discard_sub(head, cf, data, FALSE); + CURL_TRC_CF(data, cf, "shutdown and remove SSL, done -> %d", result); + break; + } + } + return result; +} + +bool Curl_ssl_cf_is_proxy(struct Curl_cfilter *cf) +{ + return (cf->cft->flags & CF_TYPE_SSL) && (cf->cft->flags & CF_TYPE_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) +{ + 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; + } +#endif +#ifdef USE_HTTP3 + else if(proto_len == ALPN_H3_LENGTH && + !memcmp(ALPN_H3, proto, ALPN_H3_LENGTH)) { + *palpn = CURL_HTTP_VERSION_3; + } +#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: + return CURLE_OK; } #endif /* USE_SSL */ diff --git a/contrib/curl/lib/vtls/vtls.h b/contrib/curl/lib/vtls/vtls.h index 2aabeda20a..fce1e00183 100644 --- a/contrib/curl/lib/vtls/vtls.h +++ b/contrib/curl/lib/vtls/vtls.h @@ -7,11 +7,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2016, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -20,69 +20,108 @@ * 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 "openssl.h" /* OpenSSL versions */ -#include "gtls.h" /* GnuTLS versions */ -#include "nssg.h" /* NSS versions */ -#include "gskit.h" /* Global Secure ToolKit versions */ -#include "polarssl.h" /* PolarSSL versions */ -#include "axtls.h" /* axTLS versions */ -#include "cyassl.h" /* CyaSSL versions */ -#include "schannel.h" /* Schannel SSPI version */ -#include "darwinssl.h" /* SecureTransport (Darwin) version */ -#include "mbedtls.h" /* mbedTLS versions */ +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 SSLSUPP_ECH (1<<7) +#define SSLSUPP_CA_CACHE (1<<8) +#define SSLSUPP_CIPHER_LIST (1<<9) /* supports TLS 1.0-1.2 ciphersuites */ + +#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 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 MD5_DIGEST_LENGTH -#define MD5_DIGEST_LENGTH 16 /* fixed size */ +#ifndef CURL_SHA256_DIGEST_LENGTH +#define CURL_SHA256_DIGEST_LENGTH 32 /* fixed size */ #endif -#ifndef SHA256_DIGEST_LENGTH -#define SHA256_DIGEST_LENGTH 32 /* fixed size */ -#endif +curl_sslbackend Curl_ssl_backend(void); -/* see https://tools.ietf.org/html/draft-ietf-tls-applayerprotoneg-04 */ -#define ALPN_HTTP_1_1_LENGTH 8 -#define ALPN_HTTP_1_1 "http/1.1" +/** + * Init ssl config for a new easy handle. + */ +void Curl_ssl_easy_config_init(struct Curl_easy *data); -/* set of helper macros for the backends to access the correct fields. For the - proxy or for the remote host - to properly support HTTPS proxy */ +/** + * Init the `data->set.ssl` and `data->set.proxy_ssl` for + * connection matching use. + */ +CURLcode Curl_ssl_easy_config_complete(struct Curl_easy *data); -#define SSL_IS_PROXY() (CURLPROXY_HTTPS == conn->http_proxy.proxytype && \ - ssl_connection_complete != conn->proxy_ssl[conn->sock[SECONDARYSOCKET] == \ - CURL_SOCKET_BAD ? FIRSTSOCKET : SECONDARYSOCKET].state) -#define SSL_SET_OPTION(var) (SSL_IS_PROXY() ? data->set.proxy_ssl.var : \ - data->set.ssl.var) -#define SSL_CONN_CONFIG(var) (SSL_IS_PROXY() ? \ - conn->proxy_ssl_config.var : conn->ssl_config.var) +/** + * 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); -bool Curl_ssl_config_matches(struct ssl_primary_config* data, - struct ssl_primary_config* needle); -bool Curl_clone_primary_ssl_config(struct ssl_primary_config *source, - struct ssl_primary_config *dest); -void Curl_free_primary_ssl_config(struct ssl_primary_config* sslc); -int Curl_ssl_getsock(struct connectdata *conn, curl_socket_t *socks, - int numsocks); +/** + * 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 `data` 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); -int Curl_ssl_backend(void); +/* 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, int transport); +/** + * 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); -CURLcode Curl_ssl_connect(struct connectdata *conn, int sockindex); -CURLcode Curl_ssl_connect_nonblocking(struct connectdata *conn, - int sockindex, - bool *done); /* 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); -void Curl_ssl_close(struct connectdata *conn, int sockindex); -CURLcode Curl_ssl_shutdown(struct connectdata *conn, int sockindex); 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); @@ -90,12 +129,10 @@ 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); -size_t Curl_ssl_version(char *buffer, size_t size); -bool Curl_ssl_data_pending(const struct connectdata *conn, - int connindex); -int Curl_ssl_check_cxn(struct connectdata *conn); +void Curl_ssl_version(char *buffer, size_t size); /* Certificate information list handling. */ +#define CURL_X509_STR_MAX 100000 void Curl_ssl_free_certinfo(struct Curl_easy *data); CURLcode Curl_ssl_init_certinfo(struct Curl_easy *data, int num); @@ -114,52 +151,29 @@ CURLcode Curl_ssl_push_certinfo(struct Curl_easy *data, int certnum, * 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 connectdata *conn); +void Curl_ssl_sessionid_lock(struct Curl_easy *data); /* Unlock session cache mutex */ -void Curl_ssl_sessionid_unlock(struct connectdata *conn); +void Curl_ssl_sessionid_unlock(struct Curl_easy *data); -/* 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 connectdata *conn, - void **ssl_sessionid, - size_t *idsize, /* set 0 if unknown */ - int sockindex); -/* 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 connectdata *conn, - void *ssl_sessionid, - size_t idsize, - int sockindex); /* 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); +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 connectdata *conn, void *ssl_sessionid); +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); -CURLcode Curl_ssl_md5sum(unsigned char *tmp, /* input */ - size_t tmplen, - unsigned char *md5sum, /* output */ - size_t md5len); /* Check pinned public key. */ CURLcode Curl_pin_peer_pubkey(struct Curl_easy *data, const char *pinnedpubkey, @@ -167,36 +181,99 @@ CURLcode Curl_pin_peer_pubkey(struct Curl_easy *data, bool Curl_ssl_cert_status_request(void); -bool Curl_ssl_false_start(void); +bool Curl_ssl_false_start(struct Curl_easy *data); + +/* The maximum size of the SSL channel binding is 85 bytes, as defined in + * RFC 5929, Section 4.1. The 'tls-server-end-point:' prefix is 21 bytes long, + * and SHA-512 is the longest supported hash algorithm, with a digest length of + * 64 bytes. + * The maximum size of the channel binding is therefore 21 + 64 = 85 bytes. + */ +#define SSL_CB_MAX_SIZE 85 + +/* Return the tls-server-end-point channel binding, including the + * 'tls-server-end-point:' prefix. + * If successful, the data is written to the dynbuf, and CURLE_OK is returned. + * The dynbuf MUST HAVE a minimum toobig size of SSL_CB_MAX_SIZE. + * If the dynbuf is too small, CURLE_OUT_OF_MEMORY is returned. + * If channel binding is not supported, binding stays empty and CURLE_OK is + * returned. + */ +CURLcode Curl_ssl_get_channel_binding(struct Curl_easy *data, int sockindex, + struct dynbuf *binding); #define SSL_SHUTDOWN_TIMEOUT 10000 /* ms */ -#else -/* Set the API backend definition to none */ -#define CURL_SSL_BACKEND CURLSSLBACKEND_NONE +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, bool send_shutdown); + +#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, unsigned 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; +#ifndef CURL_DISABLE_PROXY +extern struct Curl_cftype Curl_cft_ssl_proxy; +#endif + +#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_connect(x,y) CURLE_NOT_BUILT_IN #define Curl_ssl_close_all(x) Curl_nop_stmt -#define Curl_ssl_close(x,y) Curl_nop_stmt -#define Curl_ssl_shutdown(x,y) CURLE_NOT_BUILT_IN #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_send(a,b,c,d,e) -1 -#define Curl_ssl_recv(a,b,c,d,e) -1 #define Curl_ssl_initsessions(x,y) CURLE_OK -#define Curl_ssl_version(x,y) 0 -#define Curl_ssl_data_pending(x,y) 0 -#define Curl_ssl_check_cxn(x) 0 #define Curl_ssl_free_certinfo(x) Curl_nop_stmt -#define Curl_ssl_connect_nonblocking(x,y,z) CURLE_NOT_BUILT_IN #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() 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,c) 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/contrib/curl/lib/vtls/vtls_int.h b/contrib/curl/lib/vtls/vtls_int.h new file mode 100644 index 0000000000..ce5e7cf396 --- /dev/null +++ b/contrib/curl/lib/vtls/vtls_int.h @@ -0,0 +1,230 @@ +#ifndef HEADER_CURL_VTLS_INT_H +#define HEADER_CURL_VTLS_INT_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ +#include "curl_setup.h" +#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); + +/* enum for the nonblocking SSL connection state machine */ +typedef enum { + ssl_connect_1, + ssl_connect_2, + ssl_connect_3, + ssl_connect_done +} ssl_connect_state; + +typedef enum { + ssl_connection_none, + ssl_connection_negotiating, + ssl_connection_complete +} ssl_connection_state; + +#define CURL_SSL_IO_NEED_NONE (0) +#define CURL_SSL_IO_NEED_RECV (1<<0) +#define CURL_SSL_IO_NEED_SEND (1<<1) + +/* Information in each SSL cfilter context: cf->ctx */ +struct ssl_connect_data { + 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 */ + ssl_connection_state state; + ssl_connect_state connecting_state; + int io_need; /* TLS signals special SEND/RECV needs */ + BIT(use_alpn); /* if ALPN shall be used in handshake */ + BIT(peer_closed); /* peer has closed connection */ +}; + + +#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); + CURLcode (*shut_down)(struct Curl_cfilter *cf, struct Curl_easy *data, + bool send_shutdown, bool *done); + bool (*data_pending)(struct Curl_cfilter *cf, + const struct Curl_easy *data); + + /* 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/shutdown, adjust the pollset to include the socket + * for POLLOUT or POLLIN as needed. Mandatory. */ + void (*adjust_pollset)(struct Curl_cfilter *cf, struct Curl_easy *data, + struct easy_pollset *ps); + void *(*get_internals)(struct ssl_connect_data *connssl, CURLINFO info); + void (*close)(struct Curl_cfilter *cf, struct Curl_easy *data); + void (*close_all)(struct Curl_easy *data); + + 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); + + 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); + + CURLcode (*get_channel_binding)(struct Curl_easy *data, int sockindex, + struct dynbuf *binding); + +}; + +extern const struct Curl_ssl *Curl_ssl; + + +int Curl_none_init(void); +void Curl_none_cleanup(void); +CURLcode Curl_none_shutdown(struct Curl_cfilter *cf, struct Curl_easy *data, + bool send_shutdown, bool *done); +int Curl_none_check_cxn(struct Curl_cfilter *cf, struct Curl_easy *data); +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, + const struct ssl_peer *peer, + void **ssl_sessionid, + size_t *idsize); /* set 0 if unknown */ + +/* Set a TLS session ID for `peer`. Replaces an existing session ID if + * not already the very same. + * Sessionid mutex must be locked (see Curl_ssl_sessionid_lock). + * Call takes ownership of `ssl_sessionid`, using `sessionid_free_cb` + * to deallocate it. Is called in all outcomes, either right away or + * later when the session cache is cleaned up. + * Caller must ensure that it has properly shared ownership of this sessionid + * object with cache (e.g. incrementing refcount on success) + */ +CURLcode Curl_ssl_set_sessionid(struct Curl_cfilter *cf, + struct Curl_easy *data, + const struct ssl_peer *peer, + void *sessionid, + size_t sessionid_size, + Curl_ssl_sessionid_dtor *sessionid_free_cb); + +#include "openssl.h" /* OpenSSL versions */ +#include "gtls.h" /* GnuTLS versions */ +#include "wolfssl.h" /* wolfSSL versions */ +#include "schannel.h" /* Schannel SSPI version */ +#include "sectransp.h" /* Secure Transport (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/contrib/curl/lib/vtls/wolfssl.c b/contrib/curl/lib/vtls/wolfssl.c new file mode 100644 index 0000000000..dca57604d8 --- /dev/null +++ b/contrib/curl/lib/vtls/wolfssl.c @@ -0,0 +1,1956 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +/* + * 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 +#include + +#if LIBWOLFSSL_VERSION_HEX < 0x03004006 /* wolfSSL 3.4.6 (2015) */ +#error "wolfSSL version should be at least 3.4.6" +#endif + +/* 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 + +#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 +#include +#include +#include "wolfssl.h" + +/* The last #include files should be: */ +#include "curl_memory.h" +#include "memdebug.h" + +#ifdef USE_ECH +# include "curl_base64.h" +# define ECH_ENABLED(__data__) \ + (__data__->set.tls_ech && \ + !(__data__->set.tls_ech & CURLECH_DISABLE)\ + ) +#endif /* USE_ECH */ + +/* KEEP_PEER_CERT is a product of the presence of build time symbol + OPENSSL_EXTRA without NO_CERTS, depending on the version. KEEP_PEER_CERT is + in wolfSSL's settings.h, and the latter two are build time symbols in + 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 + +#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 wolfssl_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 WOLFSSL_HAVE_KYBER +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_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_ctx *backend = + (struct wolfssl_ctx *)connssl->backend; + struct Curl_easy *data = CF_DATA_CURRENT(cf); + ssize_t nwritten, skiplen = 0; + CURLcode result = CURLE_OK; + + DEBUGASSERT(data); + if(backend->shutting_down && backend->io_send_blocked_len && + (backend->io_send_blocked_len < blen)) { + /* bug in wolfSSL: + * It adds the close notify message again every time we retry + * sending during shutdown. */ + CURL_TRC_CF(data, cf, "bio_write, shutdown restrict send of %d" + " to %d bytes", blen, backend->io_send_blocked_len); + skiplen = (ssize_t)(blen - backend->io_send_blocked_len); + blen = backend->io_send_blocked_len; + } + nwritten = Curl_conn_cf_send(cf->next, data, buf, blen, FALSE, &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); + if(backend->shutting_down && !backend->io_send_blocked_len) + backend->io_send_blocked_len = blen; + } + else if(!result && skiplen) + nwritten += skiplen; + 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_ctx *backend = + (struct wolfssl_ctx *)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); + else if(nread == 0) + connssl->peer_closed = TRUE; + 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 */ + +static CURLcode populate_x509_store(struct Curl_cfilter *cf, + struct Curl_easy *data, + X509_STORE *store, + struct wolfssl_ctx *wssl) +{ + 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 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; + struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data); + bool imported_native_ca = false; + +#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(wssl->ctx) != WOLFSSL_SUCCESS) { + infof(data, "error importing native CA store, continuing anyway"); + } + else { + imported_native_ca = true; + infof(data, "successfully imported native CA store"); + wssl->x509_store_setup = TRUE; + } + } +#endif /* !NO_FILESYSTEM */ + + /* load certificate blob */ + if(ca_info_blob) { + if(wolfSSL_CTX_load_verify_buffer(wssl->ctx, ca_info_blob->data, + (long)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 { + infof(data, "successfully imported CA certificate blob"); + wssl->x509_store_setup = TRUE; + } + } + +#ifndef NO_FILESYSTEM + /* load trusted cacert from file if not blob */ + + CURL_TRC_CF(data, cf, "populate_x509_store, path=%s, blob=%d", + ssl_cafile ? ssl_cafile : "none", !!ca_info_blob); + if(!store) + return CURLE_OUT_OF_MEMORY; + + if((ssl_cafile || ssl_capath) && (!wssl->x509_store_setup)) { + int rc = + wolfSSL_CTX_load_verify_locations_ex(wssl->ctx, + ssl_cafile, + ssl_capath, + WOLFSSL_LOAD_FLAG_IGNORE_ERR); + if(SSL_SUCCESS != rc) { + if(conn_config->verifypeer) { + /* 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"); + } +#endif + (void)store; + wssl->x509_store_setup = TRUE; + return CURLE_OK; +} + +/* key to use at `multi->proto_hash` */ +#define MPROTO_WSSL_X509_KEY "tls:wssl:x509:share" + +struct wssl_x509_share { + char *CAfile; /* CAfile path used to generate X509 store */ + WOLFSSL_X509_STORE *store; /* cached X509 store or NULL if none */ + struct curltime time; /* when the cached store was created */ +}; + +static void wssl_x509_share_free(void *key, size_t key_len, void *p) +{ + struct wssl_x509_share *share = p; + DEBUGASSERT(key_len == (sizeof(MPROTO_WSSL_X509_KEY)-1)); + DEBUGASSERT(!memcmp(MPROTO_WSSL_X509_KEY, key, key_len)); + (void)key; + (void)key_len; + if(share->store) { + wolfSSL_X509_STORE_free(share->store); + } + free(share->CAfile); + free(share); +} + +static bool +cached_x509_store_expired(const struct Curl_easy *data, + const struct wssl_x509_share *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 wssl_x509_share *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; + struct wssl_x509_share *share; + WOLFSSL_X509_STORE *store = NULL; + + DEBUGASSERT(multi); + share = multi ? Curl_hash_pick(&multi->proto_hash, + (void *)MPROTO_WSSL_X509_KEY, + sizeof(MPROTO_WSSL_X509_KEY)-1) : NULL; + if(share && share->store && + !cached_x509_store_expired(data, share) && + !cached_x509_store_different(cf, share)) { + store = share->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; + struct wssl_x509_share *share; + + DEBUGASSERT(multi); + if(!multi) + return; + share = Curl_hash_pick(&multi->proto_hash, + (void *)MPROTO_WSSL_X509_KEY, + sizeof(MPROTO_WSSL_X509_KEY)-1); + + if(!share) { + share = calloc(1, sizeof(*share)); + if(!share) + return; + if(!Curl_hash_add2(&multi->proto_hash, + (void *)MPROTO_WSSL_X509_KEY, + sizeof(MPROTO_WSSL_X509_KEY)-1, + share, wssl_x509_share_free)) { + free(share); + return; + } + } + + if(wolfSSL_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(share->store) { + X509_STORE_free(share->store); + free(share->CAfile); + } + + share->time = Curl_now(); + share->store = store; + share->CAfile = CAfile; + } +} + +CURLcode Curl_wssl_setup_x509_store(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct wolfssl_ctx *wssl) +{ + 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; + WOLFSSL_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 wolfSSL'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 = cache_criteria_met ? get_cached_x509_store(cf, data) : NULL; + if(cached_store && wolfSSL_CTX_get_cert_store(wssl->ctx) == cached_store) { + /* The cached store is already in use, do nothing. */ + } + else if(cached_store && wolfSSL_X509_STORE_up_ref(cached_store)) { + wolfSSL_CTX_set_cert_store(wssl->ctx, cached_store); + } + else if(cache_criteria_met) { + /* wolfSSL's initial store in CTX is not shareable by default. + * Make a new one, suitable for adding to the cache. See #14278 */ + X509_STORE *store = wolfSSL_X509_STORE_new(); + if(!store) { + failf(data, "SSL: could not create a X509 store"); + return CURLE_OUT_OF_MEMORY; + } + wolfSSL_CTX_set_cert_store(wssl->ctx, store); + + result = populate_x509_store(cf, data, store, wssl); + if(!result) { + set_cached_x509_store(cf, data, store); + } + } + else { + /* We never share the CTX's store, use it. */ + X509_STORE *store = wolfSSL_CTX_get_cert_store(wssl->ctx); + result = populate_x509_store(cf, data, store, wssl); + } + + return result; +} + +#ifdef WOLFSSL_TLS13 +static CURLcode +wssl_add_default_ciphers(bool tls13, struct dynbuf *buf) +{ + int i; + char *str; + + for(i = 0; (str = wolfSSL_get_cipher_list(i)); i++) { + size_t n; + if((strncmp(str, "TLS13", 5) == 0) != tls13) + continue; + + n = strlen(str); + if(Curl_dyn_addn(buf, str, n) || Curl_dyn_addn(buf, ":", 1)) + return CURLE_OUT_OF_MEMORY; + } + + return CURLE_OK; +} +#endif + +#if LIBWOLFSSL_VERSION_HEX < 0x04002000 /* 4.2.0 (2019) */ +static int +wssl_legacy_CTX_set_min_proto_version(WOLFSSL_CTX* ctx, int version) +{ + int res; + switch(version) { + default: + case TLS1_VERSION: + res = wolfSSL_CTX_SetMinVersion(ctx, WOLFSSL_TLSV1); + if(res == WOLFSSL_SUCCESS) + return res; + FALLTHROUGH(); + case TLS1_1_VERSION: + res = wolfSSL_CTX_SetMinVersion(ctx, WOLFSSL_TLSV1_1); + if(res == WOLFSSL_SUCCESS) + return res; + FALLTHROUGH(); + case TLS1_2_VERSION: + res = wolfSSL_CTX_SetMinVersion(ctx, WOLFSSL_TLSV1_2); +#ifdef WOLFSSL_TLS13 + if(res == WOLFSSL_SUCCESS) + return res; + FALLTHROUGH(); + case TLS1_3_VERSION: + res = wolfSSL_CTX_SetMinVersion(ctx, WOLFSSL_TLSV1_3); +#endif + } + return res; +} +static int +wssl_legacy_CTX_set_max_proto_version(WOLFSSL_CTX* ctx, int version) +{ + (void) ctx, (void) version; + return WOLFSSL_NOT_IMPLEMENTED; +} +#define wolfSSL_CTX_set_min_proto_version wssl_legacy_CTX_set_min_proto_version +#define wolfSSL_CTX_set_max_proto_version wssl_legacy_CTX_set_max_proto_version +#endif + +/* + * 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) +{ + int res; + char *curves; + struct ssl_connect_data *connssl = cf->ctx; + struct wolfssl_ctx *backend = + (struct wolfssl_ctx *)connssl->backend; + struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); + const struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data); + WOLFSSL_METHOD* req_method = NULL; +#ifdef WOLFSSL_HAVE_KYBER + word16 pqkem = 0; + size_t idx = 0; +#endif + + DEBUGASSERT(backend); + + if(connssl->state == ssl_connection_complete) + return CURLE_OK; + +#if LIBWOLFSSL_VERSION_HEX < 0x04002000 /* 4.2.0 (2019) */ + req_method = wolfSSLv23_client_method(); +#else + req_method = wolfTLS_client_method(); +#endif + if(!req_method) { + failf(data, "wolfSSL: could not create a client 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, "wolfSSL: could not create a context"); + return CURLE_OUT_OF_MEMORY; + } + + switch(conn_config->version) { + case CURL_SSLVERSION_DEFAULT: + case CURL_SSLVERSION_TLSv1: + case CURL_SSLVERSION_TLSv1_0: + res = wolfSSL_CTX_set_min_proto_version(backend->ctx, TLS1_VERSION); + break; + case CURL_SSLVERSION_TLSv1_1: + res = wolfSSL_CTX_set_min_proto_version(backend->ctx, TLS1_1_VERSION); + break; + case CURL_SSLVERSION_TLSv1_2: + res = wolfSSL_CTX_set_min_proto_version(backend->ctx, TLS1_2_VERSION); + break; +#ifdef WOLFSSL_TLS13 + case CURL_SSLVERSION_TLSv1_3: + res = wolfSSL_CTX_set_min_proto_version(backend->ctx, TLS1_3_VERSION); + break; +#endif + default: + failf(data, "wolfSSL: unsupported minimum TLS version value"); + return CURLE_SSL_CONNECT_ERROR; + } + if(res != WOLFSSL_SUCCESS) { + failf(data, "wolfSSL: failed set the minimum TLS version"); + return CURLE_SSL_CONNECT_ERROR; + } + + switch(conn_config->version_max) { +#ifdef WOLFSSL_TLS13 + case CURL_SSLVERSION_MAX_TLSv1_3: + res = wolfSSL_CTX_set_max_proto_version(backend->ctx, TLS1_3_VERSION); + break; +#endif + case CURL_SSLVERSION_MAX_TLSv1_2: + res = wolfSSL_CTX_set_max_proto_version(backend->ctx, TLS1_2_VERSION); + break; + case CURL_SSLVERSION_MAX_TLSv1_1: + res = wolfSSL_CTX_set_max_proto_version(backend->ctx, TLS1_1_VERSION); + break; + case CURL_SSLVERSION_MAX_TLSv1_0: + res = wolfSSL_CTX_set_max_proto_version(backend->ctx, TLS1_VERSION); + break; + case CURL_SSLVERSION_MAX_DEFAULT: + case CURL_SSLVERSION_MAX_NONE: + res = WOLFSSL_SUCCESS; + break; + default: + failf(data, "wolfSSL: unsupported maximum TLS version value"); + return CURLE_SSL_CONNECT_ERROR; + } + if(res != WOLFSSL_SUCCESS) { + failf(data, "wolfSSL: failed set the maximum TLS version"); + return CURLE_SSL_CONNECT_ERROR; + } + +#ifndef WOLFSSL_TLS13 + { + char *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); + } + } +#else +#define MAX_CIPHER_LEN 1024 + if(conn_config->cipher_list || conn_config->cipher_list13) { + const char *ciphers12 = conn_config->cipher_list; + const char *ciphers13 = conn_config->cipher_list13; + struct dynbuf c; + CURLcode result; + Curl_dyn_init(&c, MAX_CIPHER_LEN); + + if(ciphers13) + result = Curl_dyn_add(&c, ciphers13); + else + result = wssl_add_default_ciphers(TRUE, &c); + + if(!result) { + if(ciphers12) + result = Curl_dyn_add(&c, ciphers12); + else + result = wssl_add_default_ciphers(FALSE, &c); + } + if(result) + return result; + + if(!SSL_CTX_set_cipher_list(backend->ctx, Curl_dyn_ptr(&c))) { + failf(data, "failed setting cipher list: %s", Curl_dyn_ptr(&c)); + Curl_dyn_free(&c); + return CURLE_SSL_CIPHER; + } + infof(data, "Cipher selection: %s", Curl_dyn_ptr(&c)); + Curl_dyn_free(&c); + } +#endif + + curves = conn_config->curves; + if(curves) { + +#ifdef WOLFSSL_HAVE_KYBER + for(idx = 0; gnm[idx].name != NULL; idx++) { + if(strncmp(curves, gnm[idx].name, strlen(gnm[idx].name)) == 0) { + pqkem = gnm[idx].group; + break; + } + } + + if(pqkem == 0) +#endif + { + if(!SSL_CTX_set1_curves_list(backend->ctx, curves)) { + failf(data, "failed setting curves list: '%s'", curves); + return CURLE_SSL_CIPHER; + } + } + } + + /* Load the client certificate, and private key */ +#ifndef NO_FILESYSTEM + if(ssl_config->primary.cert_blob || ssl_config->primary.clientcert) { + const char *cert_file = ssl_config->primary.clientcert; + const char *key_file = ssl_config->key; + const struct curl_blob *cert_blob = ssl_config->primary.cert_blob; + const struct curl_blob *key_blob = ssl_config->key_blob; + int file_type = wolfssl_do_file_type(ssl_config->cert_type); + int rc; + + switch(file_type) { + case WOLFSSL_FILETYPE_PEM: + rc = cert_blob ? + wolfSSL_CTX_use_certificate_chain_buffer(backend->ctx, + cert_blob->data, + (long)cert_blob->len) : + wolfSSL_CTX_use_certificate_chain_file(backend->ctx, cert_file); + break; + case WOLFSSL_FILETYPE_ASN1: + rc = cert_blob ? + wolfSSL_CTX_use_certificate_buffer(backend->ctx, cert_blob->data, + (long)cert_blob->len, file_type) : + wolfSSL_CTX_use_certificate_file(backend->ctx, cert_file, file_type); + break; + default: + failf(data, "unknown cert type"); + return CURLE_BAD_FUNCTION_ARGUMENT; + } + if(rc != 1) { + failf(data, "unable to use client certificate"); + return CURLE_SSL_CONNECT_ERROR; + } + + if(!key_blob && !key_file) { + key_blob = cert_blob; + key_file = cert_file; + } + else + file_type = wolfssl_do_file_type(ssl_config->key_type); + + rc = key_blob ? + wolfSSL_CTX_use_PrivateKey_buffer(backend->ctx, key_blob->data, + (long)key_blob->len, file_type) : + wolfSSL_CTX_use_PrivateKey_file(backend->ctx, key_file, file_type); + if(rc != 1) { + failf(data, "unable to set private key"); + return CURLE_SSL_CONNECT_ERROR; + } + } +#else /* NO_FILESYSTEM */ + if(ssl_config->primary.cert_blob) { + const struct curl_blob *cert_blob = ssl_config->primary.cert_blob; + const struct curl_blob *key_blob = ssl_config->key_blob; + int file_type = wolfssl_do_file_type(ssl_config->cert_type); + int rc; + + switch(file_type) { + case WOLFSSL_FILETYPE_PEM: + rc = wolfSSL_CTX_use_certificate_chain_buffer(backend->ctx, + cert_blob->data, + (long)cert_blob->len); + break; + case WOLFSSL_FILETYPE_ASN1: + rc = wolfSSL_CTX_use_certificate_buffer(backend->ctx, cert_blob->data, + (long)cert_blob->len, file_type); + break; + default: + failf(data, "unknown cert type"); + return CURLE_BAD_FUNCTION_ARGUMENT; + } + if(rc != 1) { + failf(data, "unable to use client certificate"); + return CURLE_SSL_CONNECT_ERROR; + } + + if(!key_blob) + key_blob = cert_blob; + else + file_type = wolfssl_do_file_type(ssl_config->key_type); + + if(wolfSSL_CTX_use_PrivateKey_buffer(backend->ctx, key_blob->data, + (long)key_blob->len, + 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(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; + if(!backend->x509_store_setup) { + result = Curl_wssl_setup_x509_store(cf, data, backend); + if(result) + return result; + } + 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 cannot 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: could not create a handle"); + return CURLE_OUT_OF_MEMORY; + } + +#ifdef WOLFSSL_HAVE_KYBER + if(pqkem) { + if(wolfSSL_UseKeyShare(backend->handle, pqkem) != WOLFSSL_SUCCESS) { + failf(data, "unable to use PQ 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, (unsigned int)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 is a cached ID we can/should use here! */ + if(ssl_config->primary.cache_session) { + void *ssl_sessionid = NULL; + + Curl_ssl_sessionid_lock(data); + if(!Curl_ssl_getsessionid(cf, data, &connssl->peer, + &ssl_sessionid, NULL)) { + /* we got a session id, use it! */ + if(!SSL_set_session(backend->handle, ssl_sessionid)) { + Curl_ssl_delsessionid(data, ssl_sessionid); + infof(data, "cannot use session ID, going on without"); + } + else + infof(data, "SSL reusing session ID"); + } + Curl_ssl_sessionid_unlock(data); + } + +#ifdef USE_ECH + if(ECH_ENABLED(data)) { + int trying_ech_now = 0; + + if(data->set.str[STRING_ECH_PUBLIC]) { + infof(data, "ECH: outername not (yet) supported with wolfSSL"); + return CURLE_SSL_CONNECT_ERROR; + } + if(data->set.tls_ech == CURLECH_GREASE) { + infof(data, "ECH: GREASE'd ECH not yet supported for wolfSSL"); + return CURLE_SSL_CONNECT_ERROR; + } + if(data->set.tls_ech & CURLECH_CLA_CFG + && data->set.str[STRING_ECH_CONFIG]) { + char *b64val = data->set.str[STRING_ECH_CONFIG]; + word32 b64len = 0; + + b64len = (word32) strlen(b64val); + if(b64len + && wolfSSL_SetEchConfigsBase64(backend->handle, b64val, b64len) + != WOLFSSL_SUCCESS) { + if(data->set.tls_ech & CURLECH_HARD) + return CURLE_SSL_CONNECT_ERROR; + } + else { + trying_ech_now = 1; + infof(data, "ECH: ECHConfig from command line"); + } + } + else { + struct Curl_dns_entry *dns = NULL; + + dns = Curl_fetch_addr(data, connssl->peer.hostname, connssl->peer.port); + if(!dns) { + infof(data, "ECH: requested but no DNS info available"); + if(data->set.tls_ech & CURLECH_HARD) + return CURLE_SSL_CONNECT_ERROR; + } + else { + struct Curl_https_rrinfo *rinfo = NULL; + + rinfo = dns->hinfo; + if(rinfo && rinfo->echconfiglist) { + unsigned char *ecl = rinfo->echconfiglist; + size_t elen = rinfo->echconfiglist_len; + + infof(data, "ECH: ECHConfig from DoH HTTPS RR"); + if(wolfSSL_SetEchConfigs(backend->handle, ecl, (word32) elen) != + WOLFSSL_SUCCESS) { + infof(data, "ECH: wolfSSL_SetEchConfigs failed"); + if(data->set.tls_ech & CURLECH_HARD) + return CURLE_SSL_CONNECT_ERROR; + } + else { + trying_ech_now = 1; + infof(data, "ECH: imported ECHConfigList of length %ld", elen); + } + } + else { + infof(data, "ECH: requested but no ECHConfig available"); + if(data->set.tls_ech & CURLECH_HARD) + return CURLE_SSL_CONNECT_ERROR; + } + Curl_resolv_unlink(data, &dns); + } + } + + if(trying_ech_now + && SSL_set_min_proto_version(backend->handle, TLS1_3_VERSION) != 1) { + infof(data, "ECH: cannot force TLSv1.3 [ERROR]"); + return CURLE_SSL_CONNECT_ERROR; + } + + } +#endif /* USE_ECH */ + +#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 char *wolfssl_strerror(unsigned long error, char *buf, + unsigned long size) +{ + DEBUGASSERT(size > 40); + *buf = '\0'; + + wolfSSL_ERR_error_string_n(error, buf, size); + + if(!*buf) { + const char *msg = error ? "Unknown error" : "No error"; + /* the string fits because the assert above assures this */ + strcpy(buf, msg); + } + + return buf; +} + + +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_ctx *backend = + (struct wolfssl_ctx *)connssl->backend; + struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); +#ifndef CURL_DISABLE_PROXY + const char * const pinnedpubkey = Curl_ssl_cf_is_proxy(cf) ? + data->set.str[STRING_SSL_PINNEDPUBLICKEY_PROXY] : + data->set.str[STRING_SSL_PINNEDPUBLICKEY]; +#else + const char * const pinnedpubkey = data->set.str[STRING_SSL_PINNEDPUBLICKEY]; +#endif + + DEBUGASSERT(backend); + + 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; + } + + 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; + result = Curl_wssl_setup_x509_store(cf, data, backend); + if(result) + return result; + } + + connssl->io_need = CURL_SSL_IO_NEED_NONE; + 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) { + int detail = wolfSSL_get_error(backend->handle, ret); + + if(SSL_ERROR_WANT_READ == detail) { + connssl->io_need = CURL_SSL_IO_NEED_RECV; + return CURLE_OK; + } + else if(SSL_ERROR_WANT_WRITE == detail) { + connssl->io_need = CURL_SSL_IO_NEED_SEND; + 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 + } + 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"); + } + } + else if(ASN_AFTER_DATE_E == detail) { + failf(data, "server verification failed: certificate has expired."); + return CURLE_PEER_FAILED_VERIFICATION; + } + else if(ASN_BEFORE_DATE_E == detail) { + failf(data, "server verification failed: certificate not valid yet."); + return CURLE_PEER_FAILED_VERIFICATION; + } +#ifdef USE_ECH + else if(-1 == detail) { + /* try access a retry_config ECHConfigList for tracing */ + byte echConfigs[1000]; + word32 echConfigsLen = 1000; + int rv = 0; + + /* this currently does not produce the retry_configs */ + rv = wolfSSL_GetEchConfigs(backend->handle, echConfigs, + &echConfigsLen); + if(rv != WOLFSSL_SUCCESS) { + infof(data, "Failed to get ECHConfigs"); + } + else { + char *b64str = NULL; + size_t blen = 0; + + rv = Curl_base64_encode((const char *)echConfigs, echConfigsLen, + &b64str, &blen); + if(!rv && b64str) + infof(data, "ECH: (not yet) retry_configs %s", b64str); + free(b64str); + } + } +#endif + else if(backend->io_result == CURLE_AGAIN) { + return CURLE_OK; + } + else { + char error_buffer[256]; + failf(data, "SSL_connect failed with error %d: %s", detail, + wolfssl_strerror((unsigned long)detail, error_buffer, + sizeof(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)); + wolfSSL_FreeX509(x509); + 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 void wolfssl_session_free(void *sessionid, size_t idsize) +{ + (void)idsize; + wolfSSL_SESSION_free(sessionid); +} + + +static CURLcode +wolfssl_connect_step3(struct Curl_cfilter *cf, struct Curl_easy *data) +{ + CURLcode result = CURLE_OK; + struct ssl_connect_data *connssl = cf->ctx; + struct wolfssl_ctx *backend = + (struct wolfssl_ctx *)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.cache_session) { + /* 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); + /* call takes ownership of `our_ssl_sessionid` */ + result = Curl_ssl_set_sessionid(cf, data, &connssl->peer, + our_ssl_sessionid, 0, + wolfssl_session_free); + Curl_ssl_sessionid_unlock(data); + if(result) { + failf(data, "failed to store ssl session"); + return result; + } + } + } + + 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_ctx *backend = + (struct wolfssl_ctx *)connssl->backend; + 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 is 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); + { + char error_buffer[256]; + failf(data, "SSL write: %s, errno %d", + wolfssl_strerror((unsigned long)err, error_buffer, + sizeof(error_buffer)), + SOCKERRNO); + } + *curlcode = CURLE_SEND_ERROR; + return -1; + } + } + CURL_TRC_CF(data, cf, "wolfssl_send(len=%zu) -> %d", len, rc); + return rc; +} + +static CURLcode wolfssl_shutdown(struct Curl_cfilter *cf, + struct Curl_easy *data, + bool send_shutdown, bool *done) +{ + struct ssl_connect_data *connssl = cf->ctx; + struct wolfssl_ctx *wctx = (struct wolfssl_ctx *)connssl->backend; + CURLcode result = CURLE_OK; + char buf[1024]; + char error_buffer[256]; + int nread = -1, err; + size_t i; + int detail; + + DEBUGASSERT(wctx); + if(!wctx->handle || cf->shutdown) { + *done = TRUE; + goto out; + } + + wctx->shutting_down = TRUE; + connssl->io_need = CURL_SSL_IO_NEED_NONE; + *done = FALSE; + if(!(wolfSSL_get_shutdown(wctx->handle) & SSL_SENT_SHUTDOWN)) { + /* We have not started the shutdown from our side yet. Check + * if the server already sent us one. */ + ERR_clear_error(); + nread = wolfSSL_read(wctx->handle, buf, (int)sizeof(buf)); + err = wolfSSL_get_error(wctx->handle, nread); + CURL_TRC_CF(data, cf, "wolfSSL_read, nread=%d, err=%d", nread, err); + if(!nread && err == SSL_ERROR_ZERO_RETURN) { + bool input_pending; + /* Yes, it did. */ + if(!send_shutdown) { + CURL_TRC_CF(data, cf, "SSL shutdown received, not sending"); + *done = TRUE; + goto out; + } + else if(!cf->next->cft->is_alive(cf->next, data, &input_pending)) { + /* Server closed the connection after its closy notify. It + * seems not interested to see our close notify, so do not + * send it. We are done. */ + CURL_TRC_CF(data, cf, "peer closed connection"); + connssl->peer_closed = TRUE; + *done = TRUE; + goto out; + } + } + } + + /* SSL should now have started the shutdown from our side. Since it + * was not complete, we are lacking the close notify from the server. */ + if(send_shutdown) { + ERR_clear_error(); + if(wolfSSL_shutdown(wctx->handle) == 1) { + CURL_TRC_CF(data, cf, "SSL shutdown finished"); + *done = TRUE; + goto out; + } + if(SSL_ERROR_WANT_WRITE == wolfSSL_get_error(wctx->handle, nread)) { + CURL_TRC_CF(data, cf, "SSL shutdown still wants to send"); + connssl->io_need = CURL_SSL_IO_NEED_SEND; + goto out; + } + /* Having sent the close notify, we use wolfSSL_read() to get the + * missing close notify from the server. */ + } + + for(i = 0; i < 10; ++i) { + ERR_clear_error(); + nread = wolfSSL_read(wctx->handle, buf, (int)sizeof(buf)); + if(nread <= 0) + break; + } + err = wolfSSL_get_error(wctx->handle, nread); + switch(err) { + case SSL_ERROR_ZERO_RETURN: /* no more data */ + CURL_TRC_CF(data, cf, "SSL shutdown received"); + *done = TRUE; + break; + case SSL_ERROR_NONE: /* just did not get anything */ + case SSL_ERROR_WANT_READ: + /* SSL has send its notify and now wants to read the reply + * from the server. We are not really interested in that. */ + CURL_TRC_CF(data, cf, "SSL shutdown sent, want receive"); + connssl->io_need = CURL_SSL_IO_NEED_RECV; + break; + case SSL_ERROR_WANT_WRITE: + CURL_TRC_CF(data, cf, "SSL shutdown send blocked"); + connssl->io_need = CURL_SSL_IO_NEED_SEND; + break; + default: + detail = wolfSSL_get_error(wctx->handle, err); + CURL_TRC_CF(data, cf, "SSL shutdown, error: '%s'(%d)", + wolfssl_strerror((unsigned long)err, error_buffer, + sizeof(error_buffer)), + detail); + result = CURLE_RECV_ERROR; + break; + } + +out: + cf->shutdown = (result || *done); + return result; +} + +static void wolfssl_close(struct Curl_cfilter *cf, struct Curl_easy *data) +{ + struct ssl_connect_data *connssl = cf->ctx; + struct wolfssl_ctx *backend = + (struct wolfssl_ctx *)connssl->backend; + + (void) data; + + DEBUGASSERT(backend); + + if(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_ctx *backend = + (struct wolfssl_ctx *)connssl->backend; + 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: + case SSL_ERROR_WANT_READ: + case SSL_ERROR_WANT_WRITE: + /* there is 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; + } + { + char error_buffer[256]; + failf(data, "SSL read: %s, errno %d", + wolfssl_strerror((unsigned long)err, error_buffer, + sizeof(error_buffer)), + SOCKERRNO); + } + *curlcode = CURLE_RECV_ERROR; + return -1; + } + } + CURL_TRC_CF(data, cf, "wolfssl_recv(len=%zu) -> %d", blen, nread); + return nread; +} + + +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_ctx *backend; + + (void)data; + DEBUGASSERT(ctx && ctx->backend); + + backend = (struct wolfssl_ctx *)ctx->backend; + if(backend->handle) /* SSL is in use */ + return (0 != wolfSSL_pending(backend->handle)) ? TRUE : FALSE; + else + return FALSE; +} + +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); + + /* 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 are 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) { + + /* 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 is available. */ + if(connssl->io_need) { + curl_socket_t writefd = (connssl->io_need & CURL_SSL_IO_NEED_SEND) ? + sockfd : CURL_SOCKET_BAD; + curl_socket_t readfd = (connssl->io_need & CURL_SSL_IO_NEED_RECV) ? + sockfd : CURL_SOCKET_BAD; + int 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))) + 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_ctx *backend = + (struct wolfssl_ctx *)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 | +#ifdef USE_ECH + SSLSUPP_ECH | +#endif + SSLSUPP_SSL_CTX | +#ifdef WOLFSSL_TLS13 + SSLSUPP_TLS13_CIPHERSUITES | +#endif + SSLSUPP_CA_CACHE | + SSLSUPP_CIPHER_LIST, + + sizeof(struct wolfssl_ctx), + + 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 */ + 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 */ + wolfssl_recv, /* recv decrypted data */ + wolfssl_send, /* send data to encrypt */ + NULL, /* get_channel_binding */ +}; + +#endif diff --git a/contrib/curl/lib/vtls/wolfssl.h b/contrib/curl/lib/vtls/wolfssl.h new file mode 100644 index 0000000000..318d8b4ab3 --- /dev/null +++ b/contrib/curl/lib/vtls/wolfssl.h @@ -0,0 +1,52 @@ +#ifndef HEADER_CURL_WOLFSSL_H +#define HEADER_CURL_WOLFSSL_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ +#include "curl_setup.h" + +#ifdef USE_WOLFSSL +#include +#include +#include +#include + +#include "urldata.h" + +extern const struct Curl_ssl Curl_ssl_wolfssl; + +struct wolfssl_ctx { + WOLFSSL_CTX *ctx; + WOLFSSL *handle; + CURLcode io_result; /* result of last BIO cfilter operation */ + int io_send_blocked_len; /* length of last BIO write that EAGAINed */ + BIT(x509_store_setup); /* x509 store has been set up */ + BIT(shutting_down); /* TLS is being shut down */ +}; + +CURLcode Curl_wssl_setup_x509_store(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct wolfssl_ctx *wssl); + +#endif /* USE_WOLFSSL */ +#endif /* HEADER_CURL_WOLFSSL_H */ diff --git a/contrib/curl/lib/vtls/x509asn1.c b/contrib/curl/lib/vtls/x509asn1.c new file mode 100644 index 0000000000..fe4a38b8f7 --- /dev/null +++ b/contrib/curl/lib/vtls/x509asn1.c @@ -0,0 +1,1259 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#if defined(USE_GNUTLS) || defined(USE_WOLFSSL) || \ + defined(USE_SCHANNEL) || defined(USE_SECTRANSP) || \ + defined(USE_MBEDTLS) + +#if defined(USE_WOLFSSL) || defined(USE_SCHANNEL) +#define WANT_PARSEX509 /* uses Curl_parseX509() */ +#endif + +#if defined(USE_GNUTLS) || defined(USE_SCHANNEL) || defined(USE_SECTRANSP) || \ + defined(USE_MBEDTLS) +#define WANT_EXTRACT_CERTINFO /* uses Curl_extract_certinfo() */ +#define WANT_PARSEX509 /* ... uses Curl_parseX509() */ +#endif + +#include +#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 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.10045.4.3.1", "ecdsa-with-SHA224" }, + { "1.2.840.10045.4.3.2", "ecdsa-with-SHA256" }, + { "1.2.840.10045.4.3.3", "ecdsa-with-SHA384" }, + { "1.2.840.10045.4.3.4", "ecdsa-with-SHA512" }, + { "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" }, + { "2.5.4.3", "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" }, + { "2.5.29.17", "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" }, + { "1.2.840.113549.1.9.2", "unstructuredName" }, + { (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 error code. + */ + +static CURLcode bool2str(struct dynbuf *store, + const char *beg, const char *end) +{ + if(end - beg != 1) + return CURLE_BAD_FUNCTION_ARGUMENT; + return Curl_dyn_add(store, *beg ? "TRUE": "FALSE"); +} + +/* + * Convert an ASN.1 octet string to a printable string. + * + * Return error code. + */ +static CURLcode octet2str(struct dynbuf *store, + const char *beg, const char *end) +{ + CURLcode result = CURLE_OK; + + while(!result && beg < end) + result = Curl_dyn_addf(store, "%02x:", (unsigned char) *beg++); + + return result; +} + +static CURLcode bit2str(struct dynbuf *store, + const char *beg, const char *end) +{ + /* Convert an ASN.1 bit string to a printable string. */ + + if(++beg > end) + return CURLE_BAD_FUNCTION_ARGUMENT; + return octet2str(store, beg, end); +} + +/* + * Convert an ASN.1 integer value into its string representation. + * + * Returns error. + */ +static CURLcode int2str(struct dynbuf *store, + const char *beg, const char *end) +{ + unsigned int val = 0; + size_t n = end - beg; + + if(!n) + return CURLE_BAD_FUNCTION_ARGUMENT; + + if(n > 4) + return octet2str(store, 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_dyn_addf(store, "%s%x", val >= 10 ? "0x" : "", val); +} + +/* + * Convert from an ASN.1 typed string to UTF8. + * + * The result is stored in a dynbuf that is inited by the user of this + * function. + * + * Returns error. + */ +static CURLcode +utf8asn1str(struct dynbuf *to, int type, const char *from, const char *end) +{ + size_t inlength = end - from; + int size = 1; + CURLcode result = CURLE_OK; + + 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 CURLE_BAD_FUNCTION_ARGUMENT; /* Conversion not supported. */ + } + + if(inlength % size) + /* Length inconsistent with character size. */ + return CURLE_BAD_FUNCTION_ARGUMENT; + + if(type == CURL_ASN1_UTF8_STRING) { + /* Just copy. */ + if(inlength) + result = Curl_dyn_addn(to, from, inlength); + } + else { + while(!result && (from < end)) { + char buf[4]; /* decode buffer */ + size_t charsize = 1; + unsigned int 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++; + } + if(wc >= 0x00000080) { + if(wc >= 0x00000800) { + if(wc >= 0x00010000) { + if(wc >= 0x00200000) { + /* Invalid char. size for target encoding. */ + return CURLE_WEIRD_SERVER_REPLY; + } + buf[3] = (char) (0x80 | (wc & 0x3F)); + wc = (wc >> 6) | 0x00010000; + charsize++; + } + buf[2] = (char) (0x80 | (wc & 0x3F)); + wc = (wc >> 6) | 0x00000800; + charsize++; + } + buf[1] = (char) (0x80 | (wc & 0x3F)); + wc = (wc >> 6) | 0x000000C0; + charsize++; + } + buf[0] = (char) wc; + result = Curl_dyn_addn(to, buf, charsize); + } + } + return result; +} + +/* + * Convert an ASN.1 OID into its dotted string representation. + * + * Return error code. + */ +static CURLcode encodeOID(struct dynbuf *store, + const char *beg, const char *end) +{ + unsigned int x; + unsigned int y; + CURLcode result = CURLE_OK; + + /* Process the first two numbers. */ + y = *(const unsigned char *) beg++; + x = y / 40; + y -= x * 40; + + result = Curl_dyn_addf(store, "%u.%u", x, y); + if(result) + return result; + + /* Process the trailing numbers. */ + while(beg < end) { + x = 0; + do { + if(x & 0xFF000000) + return 0; + y = *(const unsigned char *) beg++; + x = (x << 7) | (y & 0x7F); + } while(y & 0x80); + result = Curl_dyn_addf(store, ".%u", x); + } + return result; +} + +/* + * Convert an ASN.1 OID into its dotted or symbolic string representation. + * + * Return error code. + */ + +static CURLcode OID2str(struct dynbuf *store, + const char *beg, const char *end, bool symbolic) +{ + CURLcode result = CURLE_OK; + if(beg < end) { + if(symbolic) { + struct dynbuf buf; + Curl_dyn_init(&buf, CURL_X509_STR_MAX); + result = encodeOID(&buf, beg, end); + + if(!result) { + const struct Curl_OID *op = searchOID(Curl_dyn_ptr(&buf)); + if(op) + result = Curl_dyn_add(store, op->textoid); + else + result = Curl_dyn_add(store, Curl_dyn_ptr(&buf)); + Curl_dyn_free(&buf); + } + } + else + result = encodeOID(store, beg, end); + } + return result; +} + +static CURLcode GTime2str(struct dynbuf *store, + 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 && ISDIGIT(*fracp); 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 CURLE_BAD_FUNCTION_ARGUMENT; + } + + /* timezone follows optional fractional seconds. */ + tzp = fracp; + fracl = 0; /* no fractional seconds detected so far */ + if(fracp < end && (*fracp == '.' || *fracp == ',')) { + /* Have fractional seconds, e.g. "[.,]\d+". How many? */ + fracp++; /* should be a digit char or BAD ARGUMENT */ + tzp = fracp; + while(tzp < end && ISDIGIT(*tzp)) + tzp++; + if(tzp == fracp) /* never looped, no digit after [.,] */ + return CURLE_BAD_FUNCTION_ARGUMENT; + fracl = tzp - fracp; /* number of fractional sec digits */ + DEBUGASSERT(fracl > 0); + /* Strip trailing zeroes in fractional seconds. + * May reduce fracl to 0 if only '0's are present. */ + while(fracl && fracp[fracl - 1] == '0') + fracl--; + } + + /* Process timezone. */ + if(tzp >= end) { + tzp = ""; + tzl = 0; + } + else if(*tzp == 'Z') { + sep = " "; + tzp = "GMT"; + tzl = 3; + } + else if((*tzp == '+') || (*tzp == '-')) { + sep = " UTC"; + tzl = end - tzp; + } + else { + sep = " "; + tzl = end - tzp; + } + + return Curl_dyn_addf(store, + "%.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); +} + +#ifdef UNITTESTS +/* used by unit1656.c */ +CURLcode Curl_x509_GTime2str(struct dynbuf *store, + const char *beg, const char *end) +{ + return GTime2str(store, beg, end); +} +#endif + +/* + * Convert an ASN.1 UTC time to a printable string. + * + * Return error code. + */ +static CURLcode UTime2str(struct dynbuf *store, + 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"; + FALLTHROUGH(); + case 2: + break; + default: + return CURLE_BAD_FUNCTION_ARGUMENT; + } + + /* Process timezone. */ + if(tzp >= end) + return CURLE_BAD_FUNCTION_ARGUMENT; + if(*tzp == 'Z') { + tzp = "GMT"; + end = tzp + 3; + } + else + tzp++; + + tzl = end - tzp; + return Curl_dyn_addf(store, "%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 error + */ +static CURLcode ASN1tostr(struct dynbuf *store, + struct Curl_asn1Element *elem, int type) +{ + CURLcode result = CURLE_BAD_FUNCTION_ARGUMENT; + if(elem->constructed) + return result; /* No conversion of structured elements. */ + + if(!type) + type = elem->tag; /* Type not forced: use element tag as type. */ + + switch(type) { + case CURL_ASN1_BOOLEAN: + result = bool2str(store, elem->beg, elem->end); + break; + case CURL_ASN1_INTEGER: + case CURL_ASN1_ENUMERATED: + result = int2str(store, elem->beg, elem->end); + break; + case CURL_ASN1_BIT_STRING: + result = bit2str(store, elem->beg, elem->end); + break; + case CURL_ASN1_OCTET_STRING: + result = octet2str(store, elem->beg, elem->end); + break; + case CURL_ASN1_NULL: + result = Curl_dyn_addn(store, "", 1); + break; + case CURL_ASN1_OBJECT_IDENTIFIER: + result = OID2str(store, elem->beg, elem->end, TRUE); + break; + case CURL_ASN1_UTC_TIME: + result = UTime2str(store, elem->beg, elem->end); + break; + case CURL_ASN1_GENERALIZED_TIME: + result = GTime2str(store, elem->beg, elem->end); + break; + 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: + result = utf8asn1str(store, type, elem->beg, elem->end); + break; + } + + return result; +} + +/* + * ASCII encode distinguished name at `dn' into the store dynbuf. + * + * Returns error. + */ +static CURLcode encodeDN(struct dynbuf *store, struct Curl_asn1Element *dn) +{ + struct Curl_asn1Element rdn; + struct Curl_asn1Element atv; + struct Curl_asn1Element oid; + struct Curl_asn1Element value; + const char *p1; + const char *p2; + const char *p3; + const char *str; + CURLcode result = CURLE_OK; + bool added = FALSE; + struct dynbuf temp; + Curl_dyn_init(&temp, CURL_X509_STR_MAX); + + for(p1 = dn->beg; p1 < dn->end;) { + p1 = getASN1Element(&rdn, p1, dn->end); + if(!p1) { + result = CURLE_BAD_FUNCTION_ARGUMENT; + goto error; + } + for(p2 = rdn.beg; p2 < rdn.end;) { + p2 = getASN1Element(&atv, p2, rdn.end); + if(!p2) { + result = CURLE_BAD_FUNCTION_ARGUMENT; + goto error; + } + p3 = getASN1Element(&oid, atv.beg, atv.end); + if(!p3) { + result = CURLE_BAD_FUNCTION_ARGUMENT; + goto error; + } + if(!getASN1Element(&value, p3, atv.end)) { + result = CURLE_BAD_FUNCTION_ARGUMENT; + goto error; + } + Curl_dyn_reset(&temp); + result = ASN1tostr(&temp, &oid, 0); + if(result) + goto error; + + str = Curl_dyn_ptr(&temp); + + if(!str) { + result = CURLE_BAD_FUNCTION_ARGUMENT; + goto error; + } + + /* Encode delimiter. + If attribute has a short uppercase name, delimiter is ", ". */ + for(p3 = str; ISUPPER(*p3); p3++) + ; + if(added) { + if(p3 - str > 2) + result = Curl_dyn_addn(store, "/", 1); + else + result = Curl_dyn_addn(store, ", ", 2); + if(result) + goto error; + } + + /* Encode attribute name. */ + result = Curl_dyn_add(store, str); + if(result) + goto error; + + /* Generate equal sign. */ + result = Curl_dyn_addn(store, "=", 1); + if(result) + goto error; + + /* Generate value. */ + result = ASN1tostr(store, &value, 0); + if(result) + goto error; + Curl_dyn_reset(&temp); + added = TRUE; /* use separator for next */ + } + } +error: + Curl_dyn_free(&temp); + + return result; +} + +#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 + +static CURLcode dumpAlgo(struct dynbuf *store, + 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 CURLE_BAD_FUNCTION_ARGUMENT; + param->header = NULL; + param->tag = 0; + param->beg = param->end = end; + if(beg < end) { + const char *p = getASN1Element(param, beg, end); + if(!p) + return CURLE_BAD_FUNCTION_ARGUMENT; + } + return OID2str(store, 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); +} + +/* + * This is a convenience function for push_certinfo_len that takes a + * dynbuf value. + * + * It also does the verbose output if !certnum. + */ +static CURLcode ssl_push_certinfo_dyn(struct Curl_easy *data, + int certnum, + const char *label, + struct dynbuf *ptr) +{ + size_t valuelen = Curl_dyn_len(ptr); + char *value = Curl_dyn_ptr(ptr); + + CURLcode result = Curl_ssl_push_certinfo_len(data, certnum, label, + value, valuelen); + + if(!certnum && !result) + infof(data, " %s: %s", label, value); + + return result; +} + +static CURLcode do_pubkey_field(struct Curl_easy *data, int certnum, + const char *label, + struct Curl_asn1Element *elem) +{ + CURLcode result; + struct dynbuf out; + + Curl_dyn_init(&out, CURL_X509_STR_MAX); + + /* Generate a certificate information record for the public key. */ + + result = ASN1tostr(&out, elem, 0); + if(!result) { + if(data->set.ssl.certinfo) + result = ssl_push_certinfo_dyn(data, certnum, label, &out); + Curl_dyn_free(&out); + } + return result; +} + +/* 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 (%zu 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) == CURLE_OK + ? 0 : 1; + } + + /* 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 (%zu 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 error. + */ +static CURLcode DNtostr(struct dynbuf *store, + struct Curl_asn1Element *dn) +{ + return encodeDN(store, dn); +} + +CURLcode Curl_extract_certinfo(struct Curl_easy *data, + int certnum, + const char *beg, + const char *end) +{ + struct Curl_X509certificate cert; + struct Curl_asn1Element param; + char *certptr; + size_t clen; + struct dynbuf out; + CURLcode result = CURLE_OK; + unsigned int version; + const char *ptr; + int rc; + + if(!data->set.ssl.certinfo) + if(certnum) + return CURLE_OK; + + Curl_dyn_init(&out, CURL_X509_STR_MAX); + /* 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. */ + result = DNtostr(&out, &cert.subject); + if(result) + goto done; + if(data->set.ssl.certinfo) { + result = ssl_push_certinfo_dyn(data, certnum, "Subject", &out); + if(result) + goto done; + } + Curl_dyn_reset(&out); + + /* Issuer. */ + result = DNtostr(&out, &cert.issuer); + if(result) + goto done; + if(data->set.ssl.certinfo) { + result = ssl_push_certinfo_dyn(data, certnum, "Issuer", &out); + if(result) + goto done; + } + Curl_dyn_reset(&out); + + /* Version (always fits in less than 32 bits). */ + version = 0; + for(ptr = cert.version.beg; ptr < cert.version.end; ptr++) + version = (version << 8) | *(const unsigned char *) ptr; + if(data->set.ssl.certinfo) { + result = Curl_dyn_addf(&out, "%x", version); + if(result) + goto done; + result = ssl_push_certinfo_dyn(data, certnum, "Version", &out); + if(result) + goto done; + Curl_dyn_reset(&out); + } + + /* Serial number. */ + result = ASN1tostr(&out, &cert.serialNumber, 0); + if(result) + goto done; + if(data->set.ssl.certinfo) { + result = ssl_push_certinfo_dyn(data, certnum, "Serial Number", &out); + if(result) + goto done; + } + Curl_dyn_reset(&out); + + /* Signature algorithm .*/ + result = dumpAlgo(&out, ¶m, cert.signatureAlgorithm.beg, + cert.signatureAlgorithm.end); + if(result) + goto done; + if(data->set.ssl.certinfo) { + result = ssl_push_certinfo_dyn(data, certnum, "Signature Algorithm", + &out); + if(result) + goto done; + } + Curl_dyn_reset(&out); + + /* Start Date. */ + result = ASN1tostr(&out, &cert.notBefore, 0); + if(result) + goto done; + if(data->set.ssl.certinfo) { + result = ssl_push_certinfo_dyn(data, certnum, "Start Date", &out); + if(result) + goto done; + } + Curl_dyn_reset(&out); + + /* Expire Date. */ + result = ASN1tostr(&out, &cert.notAfter, 0); + if(result) + goto done; + if(data->set.ssl.certinfo) { + result = ssl_push_certinfo_dyn(data, certnum, "Expire Date", &out); + if(result) + goto done; + } + Curl_dyn_reset(&out); + + /* Public Key Algorithm. */ + result = dumpAlgo(&out, ¶m, cert.subjectPublicKeyAlgorithm.beg, + cert.subjectPublicKeyAlgorithm.end); + if(result) + goto done; + if(data->set.ssl.certinfo) { + result = ssl_push_certinfo_dyn(data, certnum, "Public Key Algorithm", + &out); + if(result) + goto done; + } + + rc = do_pubkey(data, certnum, Curl_dyn_ptr(&out), + ¶m, &cert.subjectPublicKey); + if(rc) { + result = CURLE_OUT_OF_MEMORY; /* the most likely error */ + goto done; + } + Curl_dyn_reset(&out); + + /* Signature. */ + result = ASN1tostr(&out, &cert.signature, 0); + if(result) + goto done; + if(data->set.ssl.certinfo) { + result = ssl_push_certinfo_dyn(data, certnum, "Signature", &out); + if(result) + goto done; + } + Curl_dyn_reset(&out); + + /* Generate PEM certificate. */ + result = Curl_base64_encode(cert.certificate.beg, + cert.certificate.end - cert.certificate.beg, + &certptr, &clen); + if(result) + goto done; + + /* Generate the final output certificate string. Format is: + -----BEGIN CERTIFICATE-----\n + \n + . + . + . + -----END CERTIFICATE-----\n + */ + + Curl_dyn_reset(&out); + + /* Build the certificate string. */ + result = Curl_dyn_add(&out, "-----BEGIN CERTIFICATE-----\n"); + if(!result) { + size_t j = 0; + + while(!result && (j < clen)) { + size_t chunksize = (clen - j) > 64 ? 64 : (clen - j); + result = Curl_dyn_addn(&out, &certptr[j], chunksize); + if(!result) + result = Curl_dyn_addn(&out, "\n", 1); + j += chunksize; + } + if(!result) + result = Curl_dyn_add(&out, "-----END CERTIFICATE-----\n"); + } + free(certptr); + if(!result) + if(data->set.ssl.certinfo) + result = ssl_push_certinfo_dyn(data, certnum, "Cert", &out); + +done: + if(result) + failf(data, "Failed extracting certificate chain"); + Curl_dyn_free(&out); + return result; +} + +#endif /* WANT_EXTRACT_CERTINFO */ + +#endif /* USE_GNUTLS or USE_WOLFSSL or USE_SCHANNEL or USE_SECTRANSP */ diff --git a/contrib/curl/lib/vtls/x509asn1.h b/contrib/curl/lib/vtls/x509asn1.h new file mode 100644 index 0000000000..5b48596c75 --- /dev/null +++ b/contrib/curl/lib/vtls/x509asn1.h @@ -0,0 +1,92 @@ +#ifndef HEADER_CURL_X509ASN1_H +#define HEADER_CURL_X509ASN1_H + +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ + +#include "curl_setup.h" + +#if defined(USE_GNUTLS) || defined(USE_WOLFSSL) || \ + defined(USE_SCHANNEL) || defined(USE_SECTRANSP) || \ + defined(USE_MBEDTLS) + +#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); + +#ifdef UNITTESTS +#if defined(USE_GNUTLS) || defined(USE_SCHANNEL) || defined(USE_SECTRANSP) || \ + defined(USE_MBEDTLS) + +/* used by unit1656.c */ +CURLcode Curl_x509_GTime2str(struct dynbuf *store, + const char *beg, const char *end); +#endif +#endif + +#endif /* USE_GNUTLS or USE_WOLFSSL or USE_SCHANNEL or USE_SECTRANSP */ +#endif /* HEADER_CURL_X509ASN1_H */ diff --git a/contrib/curl/lib/warnless.c b/contrib/curl/lib/warnless.c index 0c4472e4a9..c80937b843 100644 --- a/contrib/curl/lib/warnless.c +++ b/contrib/curl/lib/warnless.c @@ -5,11 +5,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2016, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -18,6 +18,8 @@ * 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" @@ -33,90 +35,33 @@ #endif /* __INTEL_COMPILER && __unix__ */ -#define BUILDING_WARNLESS_C 1 - #include "warnless.h" -#define CURL_MASK_SCHAR 0x7F -#define CURL_MASK_UCHAR 0xFF - -#if (SIZEOF_SHORT == 2) -# define CURL_MASK_SSHORT 0x7FFF -# define CURL_MASK_USHORT 0xFFFF -#elif (SIZEOF_SHORT == 4) -# define CURL_MASK_SSHORT 0x7FFFFFFF -# define CURL_MASK_USHORT 0xFFFFFFFF -#elif (SIZEOF_SHORT == 8) -# define CURL_MASK_SSHORT 0x7FFFFFFFFFFFFFFF -# define CURL_MASK_USHORT 0xFFFFFFFFFFFFFFFF -#else -# error "SIZEOF_SHORT not defined" -#endif - -#if (SIZEOF_INT == 2) -# define CURL_MASK_SINT 0x7FFF -# define CURL_MASK_UINT 0xFFFF -#elif (SIZEOF_INT == 4) -# define CURL_MASK_SINT 0x7FFFFFFF -# define CURL_MASK_UINT 0xFFFFFFFF -#elif (SIZEOF_INT == 8) -# define CURL_MASK_SINT 0x7FFFFFFFFFFFFFFF -# define CURL_MASK_UINT 0xFFFFFFFFFFFFFFFF -#elif (SIZEOF_INT == 16) -# define CURL_MASK_SINT 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF -# define CURL_MASK_UINT 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF -#else -# error "SIZEOF_INT not defined" -#endif - -#if (CURL_SIZEOF_LONG == 2) -# define CURL_MASK_SLONG 0x7FFFL -# define CURL_MASK_ULONG 0xFFFFUL -#elif (CURL_SIZEOF_LONG == 4) -# define CURL_MASK_SLONG 0x7FFFFFFFL -# define CURL_MASK_ULONG 0xFFFFFFFFUL -#elif (CURL_SIZEOF_LONG == 8) -# define CURL_MASK_SLONG 0x7FFFFFFFFFFFFFFFL -# define CURL_MASK_ULONG 0xFFFFFFFFFFFFFFFFUL -#elif (CURL_SIZEOF_LONG == 16) -# define CURL_MASK_SLONG 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFL -# define CURL_MASK_ULONG 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFUL -#else -# error "CURL_SIZEOF_LONG not defined" -#endif - -#if (CURL_SIZEOF_CURL_OFF_T == 2) -# define CURL_MASK_SCOFFT CURL_OFF_T_C(0x7FFF) -# define CURL_MASK_UCOFFT CURL_OFF_TU_C(0xFFFF) -#elif (CURL_SIZEOF_CURL_OFF_T == 4) -# define CURL_MASK_SCOFFT CURL_OFF_T_C(0x7FFFFFFF) -# define CURL_MASK_UCOFFT CURL_OFF_TU_C(0xFFFFFFFF) -#elif (CURL_SIZEOF_CURL_OFF_T == 8) -# define CURL_MASK_SCOFFT CURL_OFF_T_C(0x7FFFFFFFFFFFFFFF) -# define CURL_MASK_UCOFFT CURL_OFF_TU_C(0xFFFFFFFFFFFFFFFF) -#elif (CURL_SIZEOF_CURL_OFF_T == 16) -# define CURL_MASK_SCOFFT CURL_OFF_T_C(0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF) -# define CURL_MASK_UCOFFT CURL_OFF_TU_C(0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF) -#else -# error "CURL_SIZEOF_CURL_OFF_T not defined" -#endif - -#if (SIZEOF_SIZE_T == SIZEOF_SHORT) -# define CURL_MASK_SSIZE_T CURL_MASK_SSHORT -# define CURL_MASK_USIZE_T CURL_MASK_USHORT -#elif (SIZEOF_SIZE_T == SIZEOF_INT) -# define CURL_MASK_SSIZE_T CURL_MASK_SINT -# define CURL_MASK_USIZE_T CURL_MASK_UINT -#elif (SIZEOF_SIZE_T == CURL_SIZEOF_LONG) -# define CURL_MASK_SSIZE_T CURL_MASK_SLONG -# define CURL_MASK_USIZE_T CURL_MASK_ULONG -#elif (SIZEOF_SIZE_T == CURL_SIZEOF_CURL_OFF_T) -# define CURL_MASK_SSIZE_T CURL_MASK_SCOFFT -# define CURL_MASK_USIZE_T CURL_MASK_UCOFFT -#else -# error "SIZEOF_SIZE_T not defined" +#ifdef _WIN32 +#undef read +#undef write #endif +#include + +#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 */ @@ -155,25 +100,6 @@ unsigned char curlx_ultouc(unsigned long ulnum) #endif } -/* -** unsigned long to signed int -*/ - -int curlx_ultosi(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_SINT); - return (int)(ulnum & (unsigned long) CURL_MASK_SINT); - -#ifdef __INTEL_COMPILER -# pragma warning(pop) -#endif -} - /* ** unsigned size_t to signed curl_off_t */ @@ -183,12 +109,15 @@ 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); -#ifdef __INTEL_COMPILER +#if defined(__INTEL_COMPILER) || defined(_MSC_VER) # pragma warning(pop) #endif } @@ -223,7 +152,7 @@ unsigned long curlx_uztoul(size_t uznum) # pragma warning(disable:810) /* conversion may lose significant bits */ #endif -#if (CURL_SIZEOF_LONG < SIZEOF_SIZE_T) +#if ULONG_MAX < SIZE_T_MAX DEBUGASSERT(uznum <= (size_t) CURL_MASK_ULONG); #endif return (unsigned long)(uznum & (size_t) CURL_MASK_ULONG); @@ -244,7 +173,7 @@ unsigned int curlx_uztoui(size_t uznum) # pragma warning(disable:810) /* conversion may lose significant bits */ #endif -#if (SIZEOF_INT < SIZEOF_SIZE_T) +#if UINT_MAX < SIZE_T_MAX DEBUGASSERT(uznum <= (size_t) CURL_MASK_UINT); #endif return (unsigned int)(uznum & (size_t) CURL_MASK_UINT); @@ -266,7 +195,7 @@ int curlx_sltosi(long slnum) #endif DEBUGASSERT(slnum >= 0); -#if (SIZEOF_INT < CURL_SIZEOF_LONG) +#if INT_MAX < LONG_MAX DEBUGASSERT((unsigned long) slnum <= (unsigned long) CURL_MASK_SINT); #endif return (int)(slnum & (long) CURL_MASK_SINT); @@ -288,7 +217,7 @@ unsigned int curlx_sltoui(long slnum) #endif DEBUGASSERT(slnum >= 0); -#if (SIZEOF_INT < CURL_SIZEOF_LONG) +#if UINT_MAX < LONG_MAX DEBUGASSERT((unsigned long) slnum <= (unsigned long) CURL_MASK_UINT); #endif return (unsigned int)(slnum & (long) CURL_MASK_UINT); @@ -368,7 +297,7 @@ int curlx_sztosi(ssize_t sznum) #endif DEBUGASSERT(sznum >= 0); -#if (SIZEOF_INT < SIZEOF_SIZE_T) +#if INT_MAX < SSIZE_T_MAX DEBUGASSERT((size_t) sznum <= (size_t) CURL_MASK_SINT); #endif return (int)(sznum & (ssize_t) CURL_MASK_SINT); @@ -397,44 +326,6 @@ unsigned short curlx_uitous(unsigned int uinum) #endif } -/* -** unsigned int to unsigned char -*/ - -unsigned char curlx_uitouc(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_UCHAR); - return (unsigned char) (uinum & (unsigned int) CURL_MASK_UCHAR); - -#ifdef __INTEL_COMPILER -# pragma warning(pop) -#endif -} - -/* -** unsigned int to signed int -*/ - -int curlx_uitosi(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_SINT); - return (int) (uinum & (unsigned int) CURL_MASK_SINT); - -#ifdef __INTEL_COMPILER -# pragma warning(pop) -#endif -} - /* ** signed int to unsigned size_t */ @@ -476,7 +367,7 @@ curl_socket_t curlx_sitosk(int i) #endif /* USE_WINSOCK */ -#if defined(WIN32) || defined(_WIN32) +#if defined(_WIN32) ssize_t curlx_read(int fd, void *buf, size_t count) { @@ -488,56 +379,8 @@ ssize_t curlx_write(int fd, const void *buf, size_t count) return (ssize_t)write(fd, buf, curlx_uztoui(count)); } -#endif /* WIN32 || _WIN32 */ - -#if defined(__INTEL_COMPILER) && defined(__unix__) - -int curlx_FD_ISSET(int fd, fd_set *fdset) -{ - #pragma warning(push) - #pragma warning(disable:1469) /* clobber ignored */ - return FD_ISSET(fd, fdset); - #pragma warning(pop) -} - -void curlx_FD_SET(int fd, fd_set *fdset) -{ - #pragma warning(push) - #pragma warning(disable:1469) /* clobber ignored */ - FD_SET(fd, fdset); - #pragma warning(pop) -} - -void curlx_FD_ZERO(fd_set *fdset) -{ - #pragma warning(push) - #pragma warning(disable:593) /* variable was set but never used */ - FD_ZERO(fdset); - #pragma warning(pop) -} +#endif /* _WIN32 */ -unsigned short curlx_htons(unsigned short usnum) -{ -#if (__INTEL_COMPILER == 910) && defined(__i386__) - return (unsigned short)(((usnum << 8) & 0xFF00) | ((usnum >> 8) & 0x00FF)); -#else - #pragma warning(push) - #pragma warning(disable:810) /* conversion may lose significant bits */ - return htons(usnum); - #pragma warning(pop) -#endif -} - -unsigned short curlx_ntohs(unsigned short usnum) -{ -#if (__INTEL_COMPILER == 910) && defined(__i386__) - return (unsigned short)(((usnum << 8) & 0xFF00) | ((usnum >> 8) & 0x00FF)); -#else - #pragma warning(push) - #pragma warning(disable:810) /* conversion may lose significant bits */ - return ntohs(usnum); - #pragma warning(pop) -#endif -} - -#endif /* __INTEL_COMPILER && __unix__ */ +/* Ensure that warnless.h redefinitions continue to have an effect + in "unity" builds. */ +#undef HEADER_CURL_WARNLESS_H_REDEFS diff --git a/contrib/curl/lib/warnless.h b/contrib/curl/lib/warnless.h index ab6d29998d..6adf63a793 100644 --- a/contrib/curl/lib/warnless.h +++ b/contrib/curl/lib/warnless.h @@ -7,11 +7,11 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2016, Daniel Stenberg, , et al. + * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at https://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -20,18 +20,23 @@ * 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 /* 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_ultosi(unsigned long ulnum); - int curlx_uztosi(size_t uznum); curl_off_t curlx_uztoso(size_t uznum); @@ -54,10 +59,6 @@ int curlx_sztosi(ssize_t sznum); unsigned short curlx_uitous(unsigned int uinum); -unsigned char curlx_uitouc(unsigned int uinum); - -int curlx_uitosi(unsigned int uinum); - size_t curlx_sitouz(int sinum); #ifdef USE_WINSOCK @@ -68,46 +69,24 @@ curl_socket_t curlx_sitosk(int i); #endif /* USE_WINSOCK */ -#if defined(WIN32) || defined(_WIN32) +#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); -#ifndef BUILDING_WARNLESS_C -# 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 /* WIN32 || _WIN32 */ +#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); +#endif /* HEADER_CURL_WARNLESS_H */ -unsigned short curlx_ntohs(unsigned short usnum); +#ifndef HEADER_CURL_WARNLESS_H_REDEFS +#define HEADER_CURL_WARNLESS_H_REDEFS -#ifndef BUILDING_WARNLESS_C -# undef FD_ISSET -# define FD_ISSET(a,b) curlx_FD_ISSET((a),(b)) -# undef FD_SET -# define FD_SET(a,b) curlx_FD_SET((a),(b)) -# undef FD_ZERO -# define FD_ZERO(a) curlx_FD_ZERO((a)) -# undef htons -# define htons(a) curlx_htons((a)) -# undef ntohs -# define ntohs(a) curlx_ntohs((a)) +#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 /* __INTEL_COMPILER && __unix__ */ - -#endif /* HEADER_CURL_WARNLESS_H */ +#endif /* HEADER_CURL_WARNLESS_H_REDEFS */ diff --git a/contrib/curl/lib/wildcard.c b/contrib/curl/lib/wildcard.c deleted file mode 100644 index dbbe45f6fb..0000000000 --- a/contrib/curl/lib/wildcard.c +++ /dev/null @@ -1,69 +0,0 @@ -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) 1998 - 2016, Daniel Stenberg, , et al. - * - * This software is licensed as described in the file COPYING, which - * you should have received as part of this distribution. The terms - * are also available at https://curl.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. - * - ***************************************************************************/ - -#include "curl_setup.h" - -#include "wildcard.h" -#include "llist.h" -#include "fileinfo.h" -/* The last 3 #include files should be in this order */ -#include "curl_printf.h" -#include "curl_memory.h" -#include "memdebug.h" - -CURLcode Curl_wildcard_init(struct WildcardData *wc) -{ - DEBUGASSERT(wc->filelist == NULL); - /* now allocate only wc->filelist, everything else - will be allocated if it is needed. */ - wc->filelist = Curl_llist_alloc(Curl_fileinfo_dtor); - if(!wc->filelist) {; - return CURLE_OUT_OF_MEMORY; - } - return CURLE_OK; -} - -void Curl_wildcard_dtor(struct WildcardData *wc) -{ - if(!wc) - return; - - if(wc->tmp_dtor) { - wc->tmp_dtor(wc->tmp); - wc->tmp_dtor = ZERO_NULL; - wc->tmp = NULL; - } - DEBUGASSERT(wc->tmp == NULL); - - if(wc->filelist) { - Curl_llist_destroy(wc->filelist, NULL); - wc->filelist = NULL; - } - - free(wc->path); - wc->path = NULL; - free(wc->pattern); - wc->pattern = NULL; - - wc->customptr = NULL; - wc->state = CURLWC_INIT; -} diff --git a/contrib/curl/lib/wildcard.h b/contrib/curl/lib/wildcard.h deleted file mode 100644 index 7f61cd1732..0000000000 --- a/contrib/curl/lib/wildcard.h +++ /dev/null @@ -1,58 +0,0 @@ -#ifndef HEADER_CURL_WILDCARD_H -#define HEADER_CURL_WILDCARD_H -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) 2010 - 2013, Daniel Stenberg, , et al. - * - * This software is licensed as described in the file COPYING, which - * you should have received as part of this distribution. The terms - * are also available at https://curl.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. - * - ***************************************************************************/ - -#include - -/* list of wildcard process states */ -typedef enum { - CURLWC_INIT = 0, - 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 */ -} curl_wildcard_states; - -typedef void (*curl_wildcard_tmp_dtor)(void *ptr); - -/* struct keeping information about wildcard download process */ -struct WildcardData { - curl_wildcard_states state; - char *path; /* path to the directory, where we trying wildcard-match */ - char *pattern; /* wildcard pattern */ - struct curl_llist *filelist; /* llist with struct Curl_fileinfo */ - void *tmp; /* pointer to protocol specific temporary data */ - curl_wildcard_tmp_dtor tmp_dtor; - void *customptr; /* for CURLOPT_CHUNK_DATA pointer */ -}; - -CURLcode Curl_wildcard_init(struct WildcardData *wc); -void Curl_wildcard_dtor(struct WildcardData *wc); - -struct Curl_easy; - -#endif /* HEADER_CURL_WILDCARD_H */ diff --git a/contrib/curl/lib/ws.c b/contrib/curl/lib/ws.c new file mode 100644 index 0000000000..8b58f1afc6 --- /dev/null +++ b/contrib/curl/lib/ws.c @@ -0,0 +1,1388 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ +#include "curl_setup.h" +#include + +#if !defined(CURL_DISABLE_WEBSOCKETS) && !defined(CURL_DISABLE_HTTP) + +#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 "select.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 (unsigned char)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: + CURL_TRC_WRITE(data, "websocket, decoded %s [%s%s]", msg, + ws_frame_name_of_op(dec->head[0]), + (dec->head[0] & WSBIT_FIN) ? "" : " NON-FINAL"); + break; + default: + if(dec->head_len < dec->head_total) { + CURL_TRC_WRITE(data, "websocket, decoded %s [%s%s](%d/%d)", msg, + ws_frame_name_of_op(dec->head[0]), + (dec->head[0] & WSBIT_FIN) ? "" : " NON-FINAL", + dec->head_len, dec->head_total); + } + else { + CURL_TRC_WRITE(data, "websocket, decoded %s [%s%s payload=%" + FMT_OFF_T "/%" FMT_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; + } +} + +static CURLcode ws_send_raw_blocking(CURL *data, struct websocket *ws, + const char *buffer, size_t buflen); + +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: + if(dec->head[2] > 127) { + failf(data, "WS: frame length longer than 64 signed not supported"); + return CURLE_RECV_ERROR; + } + 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; + CURL_TRC_WRITE(data, "websocket, passed %zd bytes payload, %" + FMT_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); +} + +/* WebSockets decoding client writer */ +struct ws_cw_ctx { + struct Curl_cwriter super; + struct bufq buf; +}; + +static CURLcode ws_cw_init(struct Curl_easy *data, + struct Curl_cwriter *writer) +{ + struct ws_cw_ctx *ctx = writer->ctx; + (void)data; + Curl_bufq_init2(&ctx->buf, WS_CHUNK_SIZE, 1, BUFQ_OPT_SOFT_LIMIT); + return CURLE_OK; +} + +static void ws_cw_close(struct Curl_easy *data, struct Curl_cwriter *writer) +{ + struct ws_cw_ctx *ctx = writer->ctx; + (void) data; + Curl_bufq_free(&ctx->buf); +} + +struct ws_cw_dec_ctx { + struct Curl_easy *data; + struct websocket *ws; + struct Curl_cwriter *next_writer; + int cw_type; +}; + +static ssize_t ws_cw_dec_next(const unsigned char *buf, size_t buflen, + int frame_age, int frame_flags, + curl_off_t payload_offset, + curl_off_t payload_len, + void *user_data, + CURLcode *err) +{ + struct ws_cw_dec_ctx *ctx = user_data; + struct Curl_easy *data = ctx->data; + struct websocket *ws = ctx->ws; + curl_off_t remain = (payload_len - (payload_offset + buflen)); + + (void)frame_age; + 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) { + /* forward the decoded frame to the next client writer. */ + update_meta(ws, frame_age, frame_flags, payload_offset, + payload_len, buflen); + + *err = Curl_cwriter_write(data, ctx->next_writer, ctx->cw_type, + (const char *)buf, buflen); + if(*err) + return -1; + } + *err = CURLE_OK; + return (ssize_t)buflen; +} + +static CURLcode ws_cw_write(struct Curl_easy *data, + struct Curl_cwriter *writer, int type, + const char *buf, size_t nbytes) +{ + struct ws_cw_ctx *ctx = writer->ctx; + struct websocket *ws; + CURLcode result; + + if(!(type & CLIENTWRITE_BODY) || data->set.ws_raw_mode) + return Curl_cwriter_write(data, writer->next, type, buf, nbytes); + + ws = data->conn->proto.ws; + if(!ws) { + failf(data, "WS: not a websocket transfer"); + return CURLE_FAILED_INIT; + } + + if(nbytes) { + ssize_t nwritten; + nwritten = Curl_bufq_write(&ctx->buf, (const unsigned char *)buf, + nbytes, &result); + if(nwritten < 0) { + infof(data, "WS: error adding data to buffer %d", result); + return result; + } + } + + while(!Curl_bufq_is_empty(&ctx->buf)) { + struct ws_cw_dec_ctx pass_ctx; + pass_ctx.data = data; + pass_ctx.ws = ws; + pass_ctx.next_writer = writer->next; + pass_ctx.cw_type = type; + result = ws_dec_pass(&ws->dec, data, &ctx->buf, + ws_cw_dec_next, &pass_ctx); + if(result == CURLE_AGAIN) { + /* insufficient amount of data, keep it for later. + * we pretend to have written all since we have a copy */ + CURL_TRC_WRITE(data, "websocket, buffered incomplete frame head"); + return CURLE_OK; + } + else if(result) { + infof(data, "WS: decode error %d", (int)result); + return result; + } + } + + if((type & CLIENTWRITE_EOS) && !Curl_bufq_is_empty(&ctx->buf)) { + infof(data, "WS: decode ending with %zd frame bytes remaining", + Curl_bufq_len(&ctx->buf)); + return CURLE_RECV_ERROR; + } + + return CURLE_OK; +} + +/* WebSocket payload decoding client writer. */ +static const struct Curl_cwtype ws_cw_decode = { + "ws-decode", + NULL, + ws_cw_init, + ws_cw_write, + ws_cw_close, + sizeof(struct ws_cw_ctx) +}; + + +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=%" FMT_OFF_T "/%" FMT_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(payload_len < 0) { + failf(data, "WS: starting new frame with negative payload length %" + FMT_OFF_T, payload_len); + *err = CURLE_SEND_ERROR; + return -1; + } + + 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((int)flags & ~CURLWS_CONT); + 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 is not 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)) { + free(randstr); + 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; + struct Curl_cwriter *ws_dec_writer; + CURLcode result; + + DEBUGASSERT(data->conn); + ws = data->conn->proto.ws; + if(!ws) { + size_t chunk_size = WS_CHUNK_SIZE; + ws = calloc(1, sizeof(*ws)); + if(!ws) + return CURLE_OUT_OF_MEMORY; + data->conn->proto.ws = ws; +#ifdef DEBUGBUILD + { + char *p = getenv("CURL_WS_CHUNK_SIZE"); + if(p) { + long l = strtol(p, NULL, 10); + if(l > 0 && l <= (1*1024*1024)) { + chunk_size = (size_t)l; + } + } + } +#endif + CURL_TRC_WS(data, "WS, using chunk size %zu", chunk_size); + Curl_bufq_init2(&ws->recvbuf, chunk_size, WS_CHUNK_COUNT, + BUFQ_OPT_SOFT_LIMIT); + Curl_bufq_init2(&ws->sendbuf, 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]); + + /* Install our client writer that decodes WS frames payload */ + result = Curl_cwriter_create(&ws_dec_writer, data, &ws_cw_decode, + CURL_CW_CONTENT_DECODE); + if(result) + return result; + + result = Curl_cwriter_add(data, ws_dec_writer); + if(result) { + Curl_cwriter_free(data, ws_dec_writer); + return result; + } + + 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); + } + else { /* !connect_only */ + /* And pass any additional data to the writers */ + if(nread) { + result = Curl_client_write(data, CLIENTWRITE_BODY, (char *)mem, nread); + } + } + k->upgr101 = UPGR101_RECEIVED; + + return result; +} + +struct ws_collect { + struct Curl_easy *data; + unsigned char *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 + ctx->bufidx, 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; + struct ws_collect ctx; + + 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; + + memset(&ctx, 0, sizeof(ctx)); + ctx.data = data; + ctx.buffer = buffer; + ctx.buflen = buflen; + + while(1) { + CURLcode result; + + /* 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; + } + CURL_TRC_WS(data, "curl_ws_recv, added %zu bytes from network", + Curl_bufq_len(&ws->recvbuf)); + } + + result = ws_dec_pass(&ws->dec, data, &ws->recvbuf, + ws_client_collect, &ctx); + if(result == CURLE_AGAIN) { + if(!ctx.written) { + ws_dec_info(&ws->dec, data, "need more input"); + continue; /* nothing written, try more input */ + } + break; + } + else if(result) { + 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. */ + 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; + CURL_TRC_WS(data, "curl_ws_recv(len=%zu) -> %zu bytes (frame at %" + FMT_OFF_T ", %" FMT_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 blocking) +{ + if(!Curl_bufq_is_empty(&ws->sendbuf)) { + CURLcode result; + const unsigned char *out; + size_t outlen, n; + + while(Curl_bufq_peek(&ws->sendbuf, &out, &outlen)) { + if(blocking) { + result = ws_send_raw_blocking(data, ws, (char *)out, outlen); + n = result ? 0 : outlen; + } + else if(data->set.connect_only || Curl_is_in_callback(data)) + result = Curl_senddata(data, out, outlen, &n); + else { + result = Curl_xfer_send(data, out, outlen, FALSE, &n); + if(!result && !n && outlen) + result = CURLE_AGAIN; + } + + if(result == CURLE_AGAIN) { + CURL_TRC_WS(data, "flush EAGAIN, %zu bytes remain in buffer", + Curl_bufq_len(&ws->sendbuf)); + return result; + } + else if(result) { + failf(data, "WS: flush, write error %d", result); + return result; + } + else { + infof(data, "WS: flushed %zu bytes", n); + Curl_bufq_skip(&ws->sendbuf, n); + } + } + } + return CURLE_OK; +} + +static CURLcode ws_send_raw_blocking(CURL *data, struct websocket *ws, + const char *buffer, size_t buflen) +{ + CURLcode result = CURLE_OK; + size_t nwritten; + + (void)ws; + while(buflen) { + result = Curl_xfer_send(data, buffer, buflen, FALSE, &nwritten); + if(result) + return result; + DEBUGASSERT(nwritten <= buflen); + buffer += nwritten; + buflen -= nwritten; + if(buflen) { + curl_socket_t sock = data->conn->sock[FIRSTSOCKET]; + timediff_t left_ms; + int ev; + + CURL_TRC_WS(data, "ws_send_raw_blocking() partial, %zu left to send", + buflen); + left_ms = Curl_timeleft(data, NULL, FALSE); + if(left_ms < 0) { + failf(data, "Timeout waiting for socket becoming writable"); + return CURLE_SEND_ERROR; + } + + /* POLLOUT socket */ + if(sock == CURL_SOCKET_BAD) + return CURLE_SEND_ERROR; + ev = Curl_socket_check(CURL_SOCKET_BAD, CURL_SOCKET_BAD, sock, + left_ms ? left_ms : 500); + if(ev < 0) { + failf(data, "Error while waiting for socket becoming writable"); + return CURLE_SEND_ERROR; + } + } + } + return result; +} + +static CURLcode ws_send_raw(CURL *data, const void *buffer, + size_t buflen, size_t *pnwritten) +{ + struct websocket *ws = data->conn->proto.ws; + CURLcode result; + + if(!ws) { + failf(data, "Not a websocket transfer"); + return CURLE_SEND_ERROR; + } + if(!buflen) + return CURLE_OK; + + if(Curl_is_in_callback(data)) { + /* When invoked from inside callbacks, we do a blocking send as the + * callback will probably not implement partial writes that may then + * mess up the ws framing subsequently. + * We need any pending data to be flushed before sending. */ + result = ws_flush(data, ws, TRUE); + if(result) + return result; + result = ws_send_raw_blocking(data, ws, buffer, buflen); + } + else { + /* We need any pending data to be sent or EAGAIN this call. */ + result = ws_flush(data, ws, FALSE); + if(result) + return result; + result = Curl_senddata(data, buffer, buflen, pnwritten); + } + + CURL_TRC_WS(data, "ws_send_raw(len=%zu) -> %d, %zu", + buflen, result, *pnwritten); + return result; +} + +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 n; + size_t space, payload_added; + CURLcode result; + + CURL_TRC_WS(data, "curl_ws_send(len=%zu, fragsize=%" FMT_OFF_T + ", flags=%x), raw=%d", + buflen, fragsize, flags, data->set.ws_raw_mode); + *sent = 0; + if(!data->conn && data->set.connect_only) { + result = Curl_connect_only_attach(data); + if(result) + goto out; + } + if(!data->conn) { + failf(data, "No associated connection"); + result = CURLE_SEND_ERROR; + goto out; + } + if(!data->conn->proto.ws) { + failf(data, "Not a websocket transfer"); + result = CURLE_SEND_ERROR; + goto out; + } + ws = data->conn->proto.ws; + + /* try flushing any content still waiting to be sent. */ + result = ws_flush(data, ws, FALSE); + if(result) + goto out; + + if(data->set.ws_raw_mode) { + /* In raw mode, we write directly to the connection */ + if(fragsize || flags) { + failf(data, "ws_send, raw mode: fragsize and flags cannot be non-zero"); + return CURLE_BAD_FUNCTION_ARGUMENT; + } + result = ws_send_raw(data, buffer, buflen, sent); + goto out; + } + + /* Not RAW mode, buf we do the frame encoding */ + space = Curl_bufq_space(&ws->sendbuf); + CURL_TRC_WS(data, "curl_ws_send(len=%zu), sendbuf=%zu space_left=%zu", + buflen, Curl_bufq_len(&ws->sendbuf), space); + if(space < 14) { + result = CURLE_AGAIN; + goto out; + } + + 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) + goto out; + } + else { + if((curl_off_t)buflen > ws->enc.payload_remain) { + infof(data, "WS: unaligned frame size (sending %zu instead of %" + FMT_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) + goto out; + } + + n = ws_enc_write_payload(&ws->enc, data, + buffer, buflen, &ws->sendbuf, &result); + if(n < 0) + goto out; + payload_added = (size_t)n; + + while(!result && (buflen || !Curl_bufq_is_empty(&ws->sendbuf))) { + /* flush, blocking when in callback */ + result = ws_flush(data, ws, Curl_is_in_callback(data)); + if(!result) { + DEBUGASSERT(payload_added <= buflen); + /* all buffered data sent. Try sending the rest if there is any. */ + *sent += payload_added; + buffer = (const char *)buffer + payload_added; + buflen -= payload_added; + payload_added = 0; + if(buflen) { + n = ws_enc_write_payload(&ws->enc, data, + buffer, buflen, &ws->sendbuf, &result); + if(n < 0) + goto out; + payload_added = Curl_bufq_len(&ws->sendbuf); + } + } + else if(result == CURLE_AGAIN) { + /* partially sent. how much of the call data has been part of it? what + * should we report to out caller so it can retry/send the rest? */ + if(payload_added < buflen) { + /* We did not add everything the caller wanted. Return just + * the partial write to our buffer. */ + *sent = payload_added; + result = CURLE_OK; + goto out; + } + else if(!buflen) { + /* We have no payload to report a partial write. EAGAIN would make + * the caller repeat this and add the frame again. + * Flush blocking seems the only way out of this. */ + *sent = (size_t)n; + result = ws_flush(data, ws, TRUE); + goto out; + } + /* We added the complete data to our sendbuf. Report one byte less as + * sent. This partial success should make the caller invoke us again + * with the last byte. */ + *sent = payload_added - 1; + result = Curl_bufq_unwrite(&ws->sendbuf, 1); + if(!result) + result = CURLE_AGAIN; + } + } + +out: + CURL_TRC_WS(data, "curl_ws_send(len=%zu, fragsize=%" FMT_OFF_T + ", flags=%x, raw=%d) -> %d, %zu", + buflen, fragsize, flags, data->set.ws_raw_mode, result, *sent); + return result; +} + +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); + } +} + +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 Curl_http_setup_conn(data, conn); +} + + +static CURLcode 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; +} + +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 */ + Curl_http_getsock_do, /* doing_getsock */ + ZERO_NULL, /* domore_getsock */ + ZERO_NULL, /* perform_getsock */ + ws_disconnect, /* disconnect */ + Curl_http_write_resp, /* write_resp */ + Curl_http_write_resp_hd, /* write_resp_hd */ + ZERO_NULL, /* connection_check */ + ZERO_NULL, /* attach connection */ + PORT_HTTP, /* defport */ + CURLPROTO_WS, /* protocol */ + CURLPROTO_HTTP, /* family */ + PROTOPT_CREDSPERREQUEST | /* flags */ + PROTOPT_USERPWDCTRL +}; + +#ifdef USE_SSL +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 */ + Curl_http_getsock_do, /* doing_getsock */ + ZERO_NULL, /* domore_getsock */ + ZERO_NULL, /* perform_getsock */ + ws_disconnect, /* disconnect */ + Curl_http_write_resp, /* write_resp */ + Curl_http_write_resp_hd, /* write_resp_hd */ + ZERO_NULL, /* connection_check */ + ZERO_NULL, /* attach connection */ + PORT_HTTPS, /* defport */ + CURLPROTO_WSS, /* protocol */ + CURLPROTO_HTTP, /* family */ + PROTOPT_SSL | PROTOPT_CREDSPERREQUEST | /* flags */ + PROTOPT_USERPWDCTRL +}; +#endif + + +#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 /* !CURL_DISABLE_WEBSOCKETS */ diff --git a/contrib/curl/lib/ws.h b/contrib/curl/lib/ws.h new file mode 100644 index 0000000000..186cc2c63a --- /dev/null +++ b/contrib/curl/lib/ws.h @@ -0,0 +1,90 @@ +#ifndef HEADER_CURL_WS_H +#define HEADER_CURL_WS_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ +#include "curl_setup.h" + +#if !defined(CURL_DISABLE_WEBSOCKETS) && !defined(CURL_DISABLE_HTTP) + +#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); + +extern const struct Curl_handler Curl_handler_ws; +#ifdef USE_SSL +extern const struct Curl_handler Curl_handler_wss; +#endif + + +#else +#define Curl_ws_request(x,y) CURLE_OK +#define Curl_ws_free(x) Curl_nop_stmt +#endif + +#endif /* HEADER_CURL_WS_H */ diff --git a/contrib/curl/lib/x509asn1.c b/contrib/curl/lib/x509asn1.c deleted file mode 100644 index c4bc7c1fd7..0000000000 --- a/contrib/curl/lib/x509asn1.c +++ /dev/null @@ -1,1199 +0,0 @@ -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) 1998 - 2016, Daniel Stenberg, , et al. - * - * This software is licensed as described in the file COPYING, which - * you should have received as part of this distribution. The terms - * are also available at https://curl.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. - * - ***************************************************************************/ - -#include "curl_setup.h" - -#if defined(USE_GSKIT) || defined(USE_NSS) || defined(USE_GNUTLS) || \ - defined(USE_CYASSL) || defined(USE_SCHANNEL) - -#include -#include "urldata.h" -#include "strcase.h" -#include "hostcheck.h" -#include "vtls/vtls.h" -#include "sendf.h" -#include "inet_pton.h" -#include "curl_base64.h" -#include "x509asn1.h" - -/* The last 3 #include files should be in this order */ -#include "curl_printf.h" -#include "curl_memory.h" -#include "memdebug.h" - -/* For overflow checks. */ -#define CURL_SIZE_T_MAX ((size_t)-1) - - -/* 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 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 } -}; - -/* - * 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 pretention here to rewrite a full SSL library. - */ - - -const char *Curl_getASN1Element(curl_asn1Element *elem, - const char *beg, const char *end) -{ - unsigned char b; - unsigned long len; - 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 (const char *) 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 (const char *) NULL; /* Long tag values not supported here. */ - elem->tag = b; - - /* Process length. */ - if(beg >= end) - return (const char *) 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 (const char *) NULL; - elem->beg = beg; - while(beg < end && *beg) { - beg = Curl_getASN1Element(&lelem, beg, end); - if(!beg) - return (const char *) NULL; - } - if(beg >= end) - return (const char *) NULL; - elem->end = beg; - return beg + 1; - } - else if((unsigned)b > (size_t)(end - beg)) - return (const char *) NULL; /* Does not fit in source. */ - else { - /* Get long length. */ - len = 0; - do { - if(len & 0xFF000000L) - return (const char *) NULL; /* Lengths > 32 bits are not supported. */ - len = (len << 8) | (unsigned char) *beg++; - } while(--b); - } - if(len > (size_t)(end - beg)) - return (const char *) NULL; /* Element data does not fit in source. */ - elem->beg = beg; - elem->end = beg + len; - return elem->end; -} - -static const curl_OID * searchOID(const char *oid) -{ - const curl_OID *op; - - /* Search the null terminated OID or OID identifier in local table. - Return the table entry pointer or NULL if not found. */ - - for(op = OIDtable; op->numoid; op++) - if(!strcmp(op->numoid, oid) || strcasecompare(op->textoid, oid)) - return op; - - return (const curl_OID *) NULL; -} - -static const char *bool2str(const char *beg, const char *end) -{ - /* 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. */ - - if(end - beg != 1) - return (const char *) NULL; - return strdup(*beg? "TRUE": "FALSE"); -} - -static const char *octet2str(const char *beg, const char *end) -{ - size_t n = end - beg; - char *buf = NULL; - - /* Convert an ASN.1 octet string to a printable string. - Return the dynamically allocated string, or NULL if an error occurs. */ - - if(n <= (CURL_SIZE_T_MAX - 1) / 3) { - buf = malloc(3 * n + 1); - if(buf) - for(n = 0; beg < end; n += 3) - snprintf(buf + n, 4, "%02x:", *(const unsigned char *) beg++); - } - return 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 (const char *) NULL; - return octet2str(beg, end); -} - -static const char *int2str(const char *beg, const char *end) -{ - long val = 0; - size_t n = end - beg; - - /* 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. */ - - if(!n) - return (const char *) 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%lx", (val < 0 || val >= 10)? "0x": "", val); -} - -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; - int charsize; - unsigned int wc; - char *buf; - - /* 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. */ - - *to = (char *) 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 > (CURL_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;) { - 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; -} - -static const char *string2str(int type, const char *beg, const char *end) -{ - char *buf; - - /* Convert an ASN.1 String into its UTF-8 string representation. - Return the dynamically allocated string, or NULL if an error occurs. */ - - if(utf8asn1str(&buf, type, beg, end) < 0) - return (const char *) NULL; - return buf; -} - -static int encodeUint(char *buf, int n, unsigned int x) -{ - int i = 0; - unsigned int y = x / 10; - - /* Decimal ASCII encode unsigned integer `x' in the `n'-byte buffer at `buf'. - Return the total number of encoded digits, even if larger than `n'. */ - - if(y) { - i += encodeUint(buf, n, y); - x -= y * 10; - } - if(i < n) - buf[i] = (char) ('0' + x); - i++; - if(i < n) - buf[i] = '\0'; /* Store a terminator if possible. */ - return i; -} - -static int encodeOID(char *buf, int n, const char *beg, const char *end) -{ - int i = 0; - unsigned int x; - unsigned int y; - - /* 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 -1 if an error occurs. */ - - /* Process the first two numbers. */ - y = *(const unsigned char *) beg++; - x = y / 40; - y -= x * 40; - i += encodeUint(buf + i, n - i, x); - if(i < n) - buf[i] = '.'; - i++; - i += encodeUint(buf + i, n - i, y); - - /* Process the trailing numbers. */ - while(beg < end) { - if(i < n) - buf[i] = '.'; - i++; - x = 0; - do { - if(x & 0xFF000000) - return -1; - y = *(const unsigned char *) beg++; - x = (x << 7) | (y & 0x7F); - } while(y & 0x80); - i += encodeUint(buf + i, n - i, x); - } - if(i < n) - buf[i] = '\0'; - return i; -} - -static const char *OID2str(const char *beg, const char *end, bool symbolic) -{ - char *buf = (char *) NULL; - const curl_OID * op; - int n; - - /* Convert an ASN.1 OID into its dotted or symbolic string representation. - Return the dynamically allocated string, or NULL if an error occurs. */ - - if(beg < end) { - n = encodeOID((char *) NULL, -1, beg, end); - if(n >= 0) { - buf = malloc(n + 1); - if(buf) { - encodeOID(buf, n, beg, end); - buf[n] = '\0'; - - if(symbolic) { - 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]; - case 1: - sec2 = fracp[-1]; - break; - default: - return (const char *) 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? ".": "", fracl, fracp, - sep, tzl, tzp); -} - -static const char *UTime2str(const char *beg, const char *end) -{ - const char *tzp; - size_t tzl; - const char *sec; - - /* Convert an ASN.1 UTC time to a printable string. - Return the dynamically allocated string, or NULL if an error occurs. */ - - 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 (const char *) NULL; - } - - /* Process timezone. */ - if(tzp >= end) - return (const char *) 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, - tzl, tzp); -} - -const char *Curl_ASN1tostr(curl_asn1Element *elem, int type) -{ - /* Convert an ASN.1 element to a printable string. - Return the dynamically allocated string, or NULL if an error occurs. */ - - if(elem->constructed) - return (const char *) 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 (const char *) NULL; /* Unsupported. */ -} - -static ssize_t encodeDN(char *buf, size_t n, curl_asn1Element *dn) -{ - curl_asn1Element rdn; - curl_asn1Element atv; - curl_asn1Element oid; - curl_asn1Element value; - size_t l = 0; - const char *p1; - const char *p2; - const char *p3; - const char *str; - - /* ASCII encode distinguished name at `dn' into the `n'-byte buffer at `buf'. - Return the total string length, even if larger than `n'. */ - - for(p1 = dn->beg; p1 < dn->end;) { - p1 = Curl_getASN1Element(&rdn, p1, dn->end); - for(p2 = rdn.beg; p2 < rdn.end;) { - p2 = Curl_getASN1Element(&atv, p2, rdn.end); - p3 = Curl_getASN1Element(&oid, atv.beg, atv.end); - Curl_getASN1Element(&value, p3, atv.end); - str = Curl_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 < n) - buf[l] = *p3; - l++; - } - } - - /* Encode attribute name. */ - for(p3 = str; *p3; p3++) { - if(l < n) - buf[l] = *p3; - l++; - } - free((char *) str); - - /* Generate equal sign. */ - if(l < n) - buf[l] = '='; - l++; - - /* Generate value. */ - str = Curl_ASN1tostr(&value, 0); - if(!str) - return -1; - for(p3 = str; *p3; p3++) { - if(l < n) - buf[l] = *p3; - l++; - } - free((char *) str); - } - } - - return l; -} - -const char *Curl_DNtostr(curl_asn1Element *dn) -{ - char *buf = (char *) NULL; - ssize_t n = encodeDN(buf, 0, dn); - - /* Convert an ASN.1 distinguished name into a printable string. - Return the dynamically allocated string, or NULL if an error occurs. */ - - if(n >= 0) { - buf = malloc(n + 1); - if(buf) { - encodeDN(buf, n + 1, dn); - buf[n] = '\0'; - } - } - return (const char *) buf; -} - -/* - * X509 parser. - */ - -int Curl_parseX509(curl_X509certificate *cert, - const char *beg, const char *end) -{ - curl_asn1Element elem; - curl_asn1Element tbsCertificate; - const char *ccp; - static const char defaultVersion = 0; /* v1. */ - - /* ASN.1 parse an X509 certificate into structure subfields. - Syntax is assumed to have already been checked by the SSL backend. - See RFC 5280. */ - - cert->certificate.header = NULL; - cert->certificate.beg = beg; - cert->certificate.end = end; - - /* Get the sequence content. */ - if(!Curl_getASN1Element(&elem, beg, end)) - return -1; /* Invalid bounds/size. */ - beg = elem.beg; - end = elem.end; - - /* Get tbsCertificate. */ - beg = Curl_getASN1Element(&tbsCertificate, beg, end); - /* Skip the signatureAlgorithm. */ - beg = Curl_getASN1Element(&cert->signatureAlgorithm, beg, end); - /* Get the signatureValue. */ - Curl_getASN1Element(&cert->signature, beg, end); - - /* 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 = Curl_getASN1Element(&elem, beg, end); - if(elem.tag == 0) { - Curl_getASN1Element(&cert->version, elem.beg, elem.end); - beg = Curl_getASN1Element(&elem, beg, end); - } - cert->serialNumber = elem; - /* Get signature algorithm. */ - beg = Curl_getASN1Element(&cert->signatureAlgorithm, beg, end); - /* Get issuer. */ - beg = Curl_getASN1Element(&cert->issuer, beg, end); - /* Get notBefore and notAfter. */ - beg = Curl_getASN1Element(&elem, beg, end); - ccp = Curl_getASN1Element(&cert->notBefore, elem.beg, elem.end); - Curl_getASN1Element(&cert->notAfter, ccp, elem.end); - /* Get subject. */ - beg = Curl_getASN1Element(&cert->subject, beg, end); - /* Get subjectPublicKeyAlgorithm and subjectPublicKey. */ - beg = Curl_getASN1Element(&cert->subjectPublicKeyInfo, beg, end); - ccp = Curl_getASN1Element(&cert->subjectPublicKeyAlgorithm, - cert->subjectPublicKeyInfo.beg, - cert->subjectPublicKeyInfo.end); - Curl_getASN1Element(&cert->subjectPublicKey, ccp, - cert->subjectPublicKeyInfo.end); - /* 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 = Curl_getASN1Element(&elem, beg, end); - if(elem.tag == 1) { - cert->issuerUniqueID = elem; - if(beg < end) - beg = Curl_getASN1Element(&elem, beg, end); - } - if(elem.tag == 2) { - cert->subjectUniqueID = elem; - if(beg < end) - beg = Curl_getASN1Element(&elem, beg, end); - } - if(elem.tag == 3) - Curl_getASN1Element(&cert->extensions, elem.beg, elem.end); - return 0; -} - -static size_t copySubstring(char *to, const char *from) -{ - size_t i; - - /* Copy at most 64-characters, terminate with a newline and returns the - effective number of stored characters. */ - - for(i = 0; i < 64; i++) { - to[i] = *from; - if(!*from++) - break; - } - - to[i++] = '\n'; - return i; -} - -static const char *dumpAlgo(curl_asn1Element *param, - const char *beg, const char *end) -{ - curl_asn1Element oid; - - /* Get algorithm parameters and return algorithm name. */ - - beg = Curl_getASN1Element(&oid, beg, end); - param->header = NULL; - param->tag = 0; - param->beg = param->end = end; - if(beg < end) - Curl_getASN1Element(param, beg, end); - return OID2str(oid.beg, oid.end, TRUE); -} - -static void do_pubkey_field(struct Curl_easy *data, int certnum, - const char *label, curl_asn1Element *elem) -{ - const char *output; - - /* Generate a certificate information record for the public key. */ - - output = Curl_ASN1tostr(elem, 0); - if(output) { - if(data->set.ssl.certinfo) - Curl_ssl_push_certinfo(data, certnum, label, output); - if(!certnum) - infof(data, " %s: %s\n", label, output); - free((char *) output); - } -} - -static void do_pubkey(struct Curl_easy *data, int certnum, - const char *algo, curl_asn1Element *param, - curl_asn1Element *pubkey) -{ - curl_asn1Element elem; - curl_asn1Element pk; - const char *p; - const char *q; - unsigned long len; - unsigned int i; - - /* Generate all information records for the public key. */ - - /* Get the public key (single element). */ - Curl_getASN1Element(&pk, pubkey->beg + 1, pubkey->end); - - if(strcasecompare(algo, "rsaEncryption")) { - p = Curl_getASN1Element(&elem, pk.beg, pk.end); - /* Compute key length. */ - for(q = elem.beg; !*q && q < elem.end; q++) - ; - len = (unsigned long)((elem.end - q) * 8); - if(len) - 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)\n", len); - if(data->set.ssl.certinfo) { - q = curl_maprintf("%lu", len); - if(q) { - Curl_ssl_push_certinfo(data, certnum, "RSA Public Key", q); - free((char *) q); - } - } - /* Generate coefficients. */ - do_pubkey_field(data, certnum, "rsa(n)", &elem); - Curl_getASN1Element(&elem, p, pk.end); - do_pubkey_field(data, certnum, "rsa(e)", &elem); - } - else if(strcasecompare(algo, "dsa")) { - p = Curl_getASN1Element(&elem, param->beg, param->end); - do_pubkey_field(data, certnum, "dsa(p)", &elem); - p = Curl_getASN1Element(&elem, p, param->end); - do_pubkey_field(data, certnum, "dsa(q)", &elem); - Curl_getASN1Element(&elem, p, param->end); - do_pubkey_field(data, certnum, "dsa(g)", &elem); - do_pubkey_field(data, certnum, "dsa(pub_key)", &pk); - } - else if(strcasecompare(algo, "dhpublicnumber")) { - p = Curl_getASN1Element(&elem, param->beg, param->end); - do_pubkey_field(data, certnum, "dh(p)", &elem); - Curl_getASN1Element(&elem, param->beg, param->end); - do_pubkey_field(data, certnum, "dh(g)", &elem); - do_pubkey_field(data, certnum, "dh(pub_key)", &pk); - } -#if 0 /* Patent-encumbered. */ - else if(strcasecompare(algo, "ecPublicKey")) { - /* Left TODO. */ - } -#endif -} - -CURLcode Curl_extract_certinfo(struct connectdata *conn, - int certnum, - const char *beg, - const char *end) -{ - curl_X509certificate cert; - struct Curl_easy *data = conn->data; - curl_asn1Element param; - const char *ccp; - char *cp1; - size_t cl1; - char *cp2; - CURLcode result; - unsigned long 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_OUT_OF_MEMORY; - - /* Subject. */ - ccp = Curl_DNtostr(&cert.subject); - if(!ccp) - return CURLE_OUT_OF_MEMORY; - if(data->set.ssl.certinfo) - Curl_ssl_push_certinfo(data, certnum, "Subject", ccp); - if(!certnum) - infof(data, "%2d Subject: %s\n", certnum, ccp); - free((char *) ccp); - - /* Issuer. */ - ccp = Curl_DNtostr(&cert.issuer); - if(!ccp) - return CURLE_OUT_OF_MEMORY; - if(data->set.ssl.certinfo) - Curl_ssl_push_certinfo(data, certnum, "Issuer", ccp); - if(!certnum) - infof(data, " Issuer: %s\n", ccp); - free((char *) ccp); - - /* 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("%lx", version); - if(!ccp) - return CURLE_OUT_OF_MEMORY; - Curl_ssl_push_certinfo(data, certnum, "Version", ccp); - free((char *) ccp); - } - if(!certnum) - infof(data, " Version: %lu (0x%lx)\n", version + 1, version); - - /* Serial number. */ - ccp = Curl_ASN1tostr(&cert.serialNumber, 0); - if(!ccp) - return CURLE_OUT_OF_MEMORY; - if(data->set.ssl.certinfo) - Curl_ssl_push_certinfo(data, certnum, "Serial Number", ccp); - if(!certnum) - infof(data, " Serial Number: %s\n", ccp); - free((char *) ccp); - - /* Signature algorithm .*/ - ccp = dumpAlgo(¶m, cert.signatureAlgorithm.beg, - cert.signatureAlgorithm.end); - if(!ccp) - return CURLE_OUT_OF_MEMORY; - if(data->set.ssl.certinfo) - Curl_ssl_push_certinfo(data, certnum, "Signature Algorithm", ccp); - if(!certnum) - infof(data, " Signature Algorithm: %s\n", ccp); - free((char *) ccp); - - /* Start Date. */ - ccp = Curl_ASN1tostr(&cert.notBefore, 0); - if(!ccp) - return CURLE_OUT_OF_MEMORY; - if(data->set.ssl.certinfo) - Curl_ssl_push_certinfo(data, certnum, "Start Date", ccp); - if(!certnum) - infof(data, " Start Date: %s\n", ccp); - free((char *) ccp); - - /* Expire Date. */ - ccp = Curl_ASN1tostr(&cert.notAfter, 0); - if(!ccp) - return CURLE_OUT_OF_MEMORY; - if(data->set.ssl.certinfo) - Curl_ssl_push_certinfo(data, certnum, "Expire Date", ccp); - if(!certnum) - infof(data, " Expire Date: %s\n", ccp); - free((char *) ccp); - - /* Public Key Algorithm. */ - ccp = dumpAlgo(¶m, cert.subjectPublicKeyAlgorithm.beg, - cert.subjectPublicKeyAlgorithm.end); - if(!ccp) - return CURLE_OUT_OF_MEMORY; - if(data->set.ssl.certinfo) - Curl_ssl_push_certinfo(data, certnum, "Public Key Algorithm", ccp); - if(!certnum) - infof(data, " Public Key Algorithm: %s\n", ccp); - do_pubkey(data, certnum, ccp, ¶m, &cert.subjectPublicKey); - free((char *) ccp); - -/* TODO: extensions. */ - - /* Signature. */ - ccp = Curl_ASN1tostr(&cert.signature, 0); - if(!ccp) - return CURLE_OUT_OF_MEMORY; - if(data->set.ssl.certinfo) - Curl_ssl_push_certinfo(data, certnum, "Signature", ccp); - if(!certnum) - infof(data, " Signature: %s\n", ccp); - free((char *) ccp); - - /* Generate PEM certificate. */ - result = Curl_base64_encode(data, 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 - \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) - Curl_ssl_push_certinfo(data, certnum, "Cert", cp2); - if(!certnum) - infof(data, "%s\n", cp2); - free(cp2); - return CURLE_OK; -} - -#endif /* USE_GSKIT or USE_NSS or USE_GNUTLS or USE_CYASSL or USE_SCHANNEL */ - -#if defined(USE_GSKIT) - -static const char *checkOID(const char *beg, const char *end, - const char *oid) -{ - 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 = Curl_getASN1Element(&e, beg, end); - if(!ccp || e.tag != CURL_ASN1_OBJECT_IDENTIFIER) - return (const char *) NULL; - - p = OID2str(e.beg, e.end, FALSE); - if(!p) - return (const char *) NULL; - - matched = !strcmp(p, oid); - free((char *) p); - return matched? ccp: (const char *) NULL; -} - -CURLcode Curl_verifyhost(struct connectdata *conn, - const char *beg, const char *end) -{ - struct Curl_easy *data = conn->data; - curl_X509certificate cert; - curl_asn1Element dn; - curl_asn1Element elem; - curl_asn1Element ext; - curl_asn1Element name; - const char *p; - const char *q; - char *dnsname; - int matched = -1; - size_t addrlen = (size_t) -1; - ssize_t len; - const char * const hostname = SSL_IS_PROXY()? conn->http_proxy.host.name: - conn->host.name; - const char * const dispname = SSL_IS_PROXY()? - conn->http_proxy.host.dispname: - conn->host.dispname; -#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(!SSL_CONN_CONFIG(verifyhost)) - return CURLE_OK; - - if(Curl_parseX509(&cert, beg, end)) - return CURLE_PEER_FAILED_VERIFICATION; - - /* Get the server IP address. */ -#ifdef ENABLE_IPV6 - if(conn->bits.ipv6_ip && Curl_inet_pton(AF_INET6, hostname, &addr)) - addrlen = sizeof(struct in6_addr); - else -#endif - if(Curl_inet_pton(AF_INET, hostname, &addr)) - addrlen = sizeof(struct in_addr); - - /* Process extensions. */ - for(p = cert.extensions.beg; p < cert.extensions.end && matched != 1;) { - p = Curl_getASN1Element(&ext, p, cert.extensions.end); - /* Check if extension is a subjectAlternativeName. */ - ext.beg = checkOID(ext.beg, ext.end, sanOID); - if(ext.beg) { - ext.beg = Curl_getASN1Element(&elem, ext.beg, ext.end); - /* Skip critical if present. */ - if(elem.tag == CURL_ASN1_BOOLEAN) - ext.beg = Curl_getASN1Element(&elem, ext.beg, ext.end); - /* Parse the octet string contents: is a single sequence. */ - Curl_getASN1Element(&elem, elem.beg, elem.end); - /* Check all GeneralNames. */ - for(q = elem.beg; matched != 1 && q < elem.end;) { - q = Curl_getASN1Element(&name, q, elem.end); - 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, hostname); - else - matched = 0; - free(dnsname); - break; - - case 7: /* IP address. */ - matched = (size_t) (name.end - q) == addrlen && - !memcmp(&addr, q, addrlen); - break; - } - } - } - } - - switch(matched) { - case 1: - /* an alternative name matched the server hostname */ - infof(data, "\t subjectAltName: %s matched\n", dispname); - return CURLE_OK; - case 0: - /* an alternative name field existed, but didn't match and then - we MUST fail */ - infof(data, "\t subjectAltName does not match %s\n", 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 = Curl_getASN1Element(&dn, q, cert.subject.end); - for(p = dn.beg; p < dn.end;) { - p = Curl_getASN1Element(&elem, p, dn.end); - /* 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(!Curl_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, hostname)) { - infof(data, "\t common name: %s (matched)\n", dnsname); - free(dnsname); - return CURLE_OK; - } - else - failf(data, "SSL: certificate subject name '%s' does not match " - "target host name '%s'", dnsname, dispname); - free(dnsname); - } - - return CURLE_PEER_FAILED_VERIFICATION; -} - -#endif /* USE_GSKIT */ diff --git a/contrib/curl/lib/x509asn1.h b/contrib/curl/lib/x509asn1.h deleted file mode 100644 index ce40297927..0000000000 --- a/contrib/curl/lib/x509asn1.h +++ /dev/null @@ -1,134 +0,0 @@ -#ifndef HEADER_CURL_X509ASN1_H -#define HEADER_CURL_X509ASN1_H - -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) 1998 - 2016, Daniel Stenberg, , et al. - * - * This software is licensed as described in the file COPYING, which - * you should have received as part of this distribution. The terms - * are also available at https://curl.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. - * - ***************************************************************************/ - -#include "curl_setup.h" - -#if defined(USE_GSKIT) || defined(USE_NSS) || defined(USE_GNUTLS) || \ - defined(USE_CYASSL) || defined(USE_SCHANNEL) - -#include "urldata.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 - - -/* - * Types. - */ - -/* ASN.1 parsed element. */ -typedef struct { - 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. */ -} curl_asn1Element; - - -/* ASN.1 OID table entry. */ -typedef struct { - const char * numoid; /* Dotted-numeric OID. */ - const char * textoid; /* OID name. */ -} curl_OID; - - -/* X509 certificate: RFC 5280. */ -typedef struct { - curl_asn1Element certificate; - curl_asn1Element version; - curl_asn1Element serialNumber; - curl_asn1Element signatureAlgorithm; - curl_asn1Element signature; - curl_asn1Element issuer; - curl_asn1Element notBefore; - curl_asn1Element notAfter; - curl_asn1Element subject; - curl_asn1Element subjectPublicKeyInfo; - curl_asn1Element subjectPublicKeyAlgorithm; - curl_asn1Element subjectPublicKey; - curl_asn1Element issuerUniqueID; - curl_asn1Element subjectUniqueID; - curl_asn1Element extensions; -} curl_X509certificate; - - -/* - * Prototypes. - */ - -const char *Curl_getASN1Element(curl_asn1Element *elem, - const char *beg, const char *end); -const char *Curl_ASN1tostr(curl_asn1Element *elem, int type); -const char *Curl_DNtostr(curl_asn1Element *dn); -int Curl_parseX509(curl_X509certificate *cert, - const char *beg, const char *end); -CURLcode Curl_extract_certinfo(struct connectdata *conn, int certnum, - const char *beg, const char *end); -CURLcode Curl_verifyhost(struct connectdata *conn, - const char *beg, const char *end); -#endif /* USE_GSKIT or USE_NSS or USE_GNUTLS or USE_CYASSL or USE_SCHANNEL */ -#endif /* HEADER_CURL_X509ASN1_H */ diff --git a/contrib/curl/premake5.lua b/contrib/curl/premake5.lua index c06da95462..f30b91e34f 100644 --- a/contrib/curl/premake5.lua +++ b/contrib/curl/premake5.lua @@ -19,15 +19,18 @@ project "curl-lib" filter { "system:windows" } defines { "USE_SCHANNEL", "USE_WINDOWS_SSPI" } - links "crypt32" + links { "crypt32", "bcrypt" } filter { "system:macosx" } - defines { "USE_DARWINSSL" } + defines { "USE_SECTRANSP" } filter { "system:not windows", "system:not macosx" } defines { "USE_MBEDTLS" } - filter { "system:linux or bsd or solaris or haiku" } + filter { "system:linux or toolset:cosmocc"} + defines { "_GNU_SOURCE" } + + filter { "system:linux or bsd or solaris or haiku or toolset:cosmocc" } defines { "CURL_HIDDEN_SYMBOLS" } -- find the location of the ca bundle @@ -47,5 +50,5 @@ project "curl-lib" end end if ca then - defines { 'CURL_CA_BUNDLE="' .. ca .. '"' } + defines { 'CURL_CA_BUNDLE="' .. ca .. '"', 'CURL_CA_PATH="' .. path.getdirectory(ca) .. '"' } end diff --git a/premake5.lua b/premake5.lua index 5008147e4d..885a1a1e61 100644 --- a/premake5.lua +++ b/premake5.lua @@ -237,7 +237,7 @@ files { "src/**.rc" } filter "toolset:mingw" - links { "crypt32" } + links { "crypt32", "bcrypt" } filter "system:linux or bsd or hurd" defines { "LUA_USE_POSIX", "LUA_USE_DLOPEN" } diff --git a/src/host/curl_utils.c b/src/host/curl_utils.c index c6a0c45861..1fc4ac5b0e 100644 --- a/src/host/curl_utils.c +++ b/src/host/curl_utils.c @@ -9,7 +9,11 @@ #include "premake.h" #include +#if LIBCURL_VERSION_NUM >= 0x072000 +int curlProgressCallback(curl_state* state, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow) +#else int curlProgressCallback(curl_state* state, double dltotal, double dlnow, double ultotal, double ulnow) +#endif { lua_State* L = state->L; @@ -182,7 +186,11 @@ CURL* curlRequest(lua_State* L, curl_state* state, int optionsIndex, int progres { curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0); curl_easy_setopt(curl, CURLOPT_PROGRESSDATA, state); +#if LIBCURL_VERSION_NUM >= 0x072000 + curl_easy_setopt(curl, CURLOPT_XFERINFOFUNCTION, curlProgressCallback); +#else curl_easy_setopt(curl, CURLOPT_PROGRESSFUNCTION, curlProgressCallback); +#endif } // clear error buffer. diff --git a/src/host/curl_utils.h b/src/host/curl_utils.h index d8215bdd56..46c14b0c93 100644 --- a/src/host/curl_utils.h +++ b/src/host/curl_utils.h @@ -1,7 +1,7 @@ /** * \file curl_utils.h * \brief curl utilities for the http library. -* \author Copyright (c) 2017 Tom van Dijck, Joćo Matos and the Premake project +* \author Copyright (c) 2017 Tom van Dijck, JoĆ£o Matos and the Premake project */ #ifndef curl_utils_h #define curl_utils_h @@ -24,7 +24,12 @@ typedef struct struct curl_slist* headers; } curl_state; -int curlProgressCallback(curl_state* state, double dltotal, double dlnow, double ultotal, double ulnow); + +#if LIBCURL_VERSION_NUM >= 0x072000 +int curlProgressCallback(curl_state* state, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow); +#else +int curlProgressCallback(curl_state* state, double dltotal, double dlnow, double ultotal, double ulnow); +#endif size_t curlWriteCallback(char *ptr, size_t size, size_t nmemb, curl_state* state); CURL* curlRequest(lua_State* L, curl_state* state, int optionsIndex, int progressFnIndex, int headersIndex);