curl.c (8644B)
1 /* 2 This file is part of TALER 3 Copyright (C) 2019-2024 Taler Systems SA 4 5 TALER is free software; you can redistribute it and/or modify it 6 under the terms of the GNU General Public License as published 7 by the Free Software Foundation; either version 3, or (at your 8 option) any later version. 9 10 TALER is distributed in the hope that it will be useful, but 11 WITHOUT ANY WARRANTY; without even the implied warranty of 12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 GNU General Public License for more details. 14 15 You should have received a copy of the GNU General Public 16 License along with TALER; see the file COPYING. If not, see 17 <http://www.gnu.org/licenses/> 18 */ 19 /** 20 * @file curl/curl.c 21 * @brief Helper routines for interactions with libcurl 22 * @author Christian Grothoff 23 */ 24 #include "taler/taler_curl_lib.h" 25 #include <curl/curl.h> 26 27 28 #if TALER_CURL_COMPRESS_BODIES 29 #include <zlib.h> 30 #endif 31 32 33 /* libcurl versions before 7.87.0 lack 'feature_names' in the version 34 info, and versions before 7.66.0 lack 'quic_version' and HTTP/3 35 altogether. With those we simply never consider HTTP/3 usable. */ 36 #if LIBCURL_VERSION_NUM >= 0x075700 37 38 /** 39 * Check if feature @a name is in the @a vi 40 * 41 * @param vi version data to test 42 * @param name feature to test for 43 * @return true if feature is supported by curl 44 */ 45 static bool 46 has_feature (const curl_version_info_data *vi, 47 const char *name) 48 { 49 if (NULL == vi->feature_names) 50 return false; 51 for (const char *const *p = vi->feature_names; NULL != *p; p++) 52 if (0 == 53 strcmp (*p, 54 name)) 55 return true; 56 return false; 57 } 58 59 60 /** 61 * Check if @a s is non-NULL and starts with @a prefix 62 * 63 * @param s string to check, can be NULL 64 * @param prefix prefix to check for 65 * @return true if @a s starts with @a prefix 66 */ 67 static bool 68 starts_with (const char *s, 69 const char *prefix) 70 { 71 return ( (NULL != s) && 72 (0 == strncmp (s, 73 prefix, 74 strlen (prefix)) ) ); 75 } 76 77 78 #endif 79 80 81 /** 82 * Check if using HTTP/3 is likely OK with our version of libcurl. 83 * 84 * @return true if HTTP/3 should be well-supported 85 */ 86 static bool 87 curl_http3_is_conservative_ok (void) 88 { 89 #if LIBCURL_VERSION_NUM >= 0x075700 90 const curl_version_info_data *vi 91 = curl_version_info (CURLVERSION_NOW); 92 93 if (! has_feature (vi, 94 "HTTP3")) 95 return false; 96 97 /* 98 * Require a QUIC backend we regard as production-ready. 99 * 100 * curl currently considers ngtcp2 non-experimental. 101 * quiche is still experimental. 102 */ 103 if (! starts_with (vi->quic_version, 104 "ngtcp2/")) 105 return false; 106 107 /* 108 * Conservative policy: don't use HTTP/3 with GnuTLS. 109 * (too many bugs in recent releases still) 110 */ 111 if (starts_with (vi->ssl_version, 112 "GnuTLS/")) 113 return false; 114 115 /* 116 * At this point the remaining ngtcp2 TLS configurations 117 * are the OpenSSL family and wolfSSL. 118 * 119 * We deliberately whitelist them rather than assuming 120 * every possible future TLS backend is safe. 121 */ 122 if (starts_with (vi->ssl_version, 123 "OpenSSL/")) 124 return true; 125 if (starts_with (vi->ssl_version, 126 "wolfSSL/")) 127 return true; 128 #endif 129 return false; 130 } 131 132 133 void 134 TALER_curl_set_http_version (CURL *eh, 135 bool enable_http3) 136 { 137 static enum GNUNET_GenericReturnValue http3 = GNUNET_SYSERR; 138 long version = CURL_HTTP_VERSION_1_1; 139 140 if (enable_http3) 141 { 142 if (GNUNET_SYSERR == http3) 143 http3 = curl_http3_is_conservative_ok () 144 ? GNUNET_YES 145 : GNUNET_NO; 146 #ifdef CURL_HTTP_VERSION_3 147 if (GNUNET_YES == http3) 148 { 149 /* Falls back to HTTP/2 or HTTP/1.1 if the server does 150 not speak HTTP/3. */ 151 version = CURL_HTTP_VERSION_3; 152 } 153 else 154 #endif 155 { 156 #ifdef CURL_HTTP_VERSION_2TLS 157 /* HTTP/3 support of this libcurl build is spotty, at 158 most use HTTP/2 (which falls back to HTTP/1.1). */ 159 version = CURL_HTTP_VERSION_2TLS; 160 #endif 161 } 162 } 163 GNUNET_assert (CURLE_OK == 164 curl_easy_setopt (eh, 165 CURLOPT_HTTP_VERSION, 166 version)); 167 } 168 169 170 void 171 TALER_curl_set_secure_redirect_policy (CURL *eh, 172 const char *url) 173 { 174 GNUNET_assert (CURLE_OK == 175 curl_easy_setopt (eh, 176 CURLOPT_FOLLOWLOCATION, 177 1L)); 178 GNUNET_assert ( (0 == strncasecmp (url, 179 "https://", 180 strlen ("https://"))) || 181 (0 == strncasecmp (url, 182 "http://", 183 strlen ("http://"))) ); 184 #ifdef CURLOPT_REDIR_PROTOCOLS_STR 185 if (0 == strncasecmp (url, 186 "https://", 187 strlen ("https://"))) 188 GNUNET_assert (CURLE_OK == 189 curl_easy_setopt (eh, 190 CURLOPT_REDIR_PROTOCOLS_STR, 191 "https")); 192 else 193 GNUNET_assert (CURLE_OK == 194 curl_easy_setopt (eh, 195 CURLOPT_REDIR_PROTOCOLS_STR, 196 "http,https")); 197 #else 198 #ifdef CURLOPT_REDIR_PROTOCOLS 199 if (0 == strncasecmp (url, 200 "https://", 201 strlen ("https://"))) 202 GNUNET_assert (CURLE_OK == 203 curl_easy_setopt (eh, 204 CURLOPT_REDIR_PROTOCOLS, 205 CURLPROTO_HTTPS)); 206 else 207 GNUNET_assert (CURLE_OK == 208 curl_easy_setopt (eh, 209 CURLOPT_REDIR_PROTOCOLS, 210 CURLPROTO_HTTP | CURLPROTO_HTTPS)); 211 #endif 212 #endif 213 /* limit MAXREDIRS to 5 as a simple security measure against 214 a potential infinite loop caused by a malicious target */ 215 GNUNET_assert (CURLE_OK == 216 curl_easy_setopt (eh, 217 CURLOPT_MAXREDIRS, 218 5L)); 219 } 220 221 222 enum GNUNET_GenericReturnValue 223 TALER_curl_easy_post (struct TALER_CURL_PostContext *ctx, 224 CURL *eh, 225 const json_t *body) 226 { 227 char *str; 228 size_t slen; 229 230 /* In case this context is re-used (i.e. on a retry), we must 231 not leak the body of the previous request. */ 232 GNUNET_free (ctx->json_enc); 233 str = json_dumps (body, 234 JSON_COMPACT); 235 if (NULL == str) 236 { 237 GNUNET_break (0); 238 return GNUNET_SYSERR; 239 } 240 slen = strlen (str); 241 if (TALER_CURL_COMPRESS_BODIES && 242 (! ctx->disable_compression) ) 243 { 244 Bytef *cbuf; 245 uLongf cbuf_size; 246 int ret; 247 248 cbuf_size = compressBound (slen); 249 cbuf = GNUNET_malloc (cbuf_size); 250 ret = compress (cbuf, 251 &cbuf_size, 252 (const Bytef *) str, 253 slen); 254 if (Z_OK != ret) 255 { 256 /* compression failed!? */ 257 GNUNET_break (0); 258 GNUNET_free (cbuf); 259 free (str); 260 return GNUNET_SYSERR; 261 } 262 free (str); 263 slen = (size_t) cbuf_size; 264 ctx->json_enc = (char *) cbuf; 265 GNUNET_assert ( 266 NULL != 267 (ctx->headers = curl_slist_append ( 268 ctx->headers, 269 "Content-Encoding: deflate"))); 270 } 271 else 272 { 273 /* @a str was allocated by jansson, but ctx->json_enc is 274 freed using GNUNET_free(); so we must copy. */ 275 ctx->json_enc = GNUNET_strndup (str, 276 slen); 277 free (str); 278 } 279 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 280 "Uploading JSON of %d bytes (%s)\n", 281 (int) slen, 282 (TALER_CURL_COMPRESS_BODIES && 283 (! ctx->disable_compression) ) 284 ? "compressed" 285 : "uncompressed"); 286 GNUNET_assert ( 287 NULL != 288 (ctx->headers = curl_slist_append ( 289 ctx->headers, 290 "Content-Type: application/json"))); 291 292 GNUNET_assert (CURLE_OK == 293 curl_easy_setopt (eh, 294 CURLOPT_POSTFIELDS, 295 ctx->json_enc)); 296 GNUNET_assert (CURLE_OK == 297 curl_easy_setopt (eh, 298 CURLOPT_POSTFIELDSIZE, 299 (long) slen)); 300 return GNUNET_OK; 301 } 302 303 304 void 305 TALER_curl_easy_post_finished (struct TALER_CURL_PostContext *ctx) 306 { 307 curl_slist_free_all (ctx->headers); 308 ctx->headers = NULL; 309 GNUNET_free (ctx->json_enc); 310 ctx->json_enc = NULL; 311 }