curl.c (8308B)
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 str = json_dumps (body, 231 JSON_COMPACT); 232 if (NULL == str) 233 { 234 GNUNET_break (0); 235 return GNUNET_SYSERR; 236 } 237 slen = strlen (str); 238 if (TALER_CURL_COMPRESS_BODIES && 239 (! ctx->disable_compression) ) 240 { 241 Bytef *cbuf; 242 uLongf cbuf_size; 243 int ret; 244 245 cbuf_size = compressBound (slen); 246 cbuf = GNUNET_malloc (cbuf_size); 247 ret = compress (cbuf, 248 &cbuf_size, 249 (const Bytef *) str, 250 slen); 251 if (Z_OK != ret) 252 { 253 /* compression failed!? */ 254 GNUNET_break (0); 255 GNUNET_free (cbuf); 256 free (str); 257 return GNUNET_SYSERR; 258 } 259 free (str); 260 slen = (size_t) cbuf_size; 261 ctx->json_enc = (char *) cbuf; 262 GNUNET_assert ( 263 NULL != 264 (ctx->headers = curl_slist_append ( 265 ctx->headers, 266 "Content-Encoding: deflate"))); 267 } 268 else 269 { 270 ctx->json_enc = str; 271 } 272 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 273 "Uploading JSON of %d bytes (%s)\n", 274 (int) slen, 275 (TALER_CURL_COMPRESS_BODIES && 276 (! ctx->disable_compression) ) 277 ? "compressed" 278 : "uncompressed"); 279 GNUNET_assert ( 280 NULL != 281 (ctx->headers = curl_slist_append ( 282 ctx->headers, 283 "Content-Type: application/json"))); 284 285 GNUNET_assert (CURLE_OK == 286 curl_easy_setopt (eh, 287 CURLOPT_POSTFIELDS, 288 ctx->json_enc)); 289 GNUNET_assert (CURLE_OK == 290 curl_easy_setopt (eh, 291 CURLOPT_POSTFIELDSIZE, 292 (long) slen)); 293 return GNUNET_OK; 294 } 295 296 297 void 298 TALER_curl_easy_post_finished (struct TALER_CURL_PostContext *ctx) 299 { 300 curl_slist_free_all (ctx->headers); 301 ctx->headers = NULL; 302 GNUNET_free (ctx->json_enc); 303 ctx->json_enc = NULL; 304 }