Line data Source code
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 0 : has_feature (const curl_version_info_data *vi,
47 : const char *name)
48 : {
49 0 : if (NULL == vi->feature_names)
50 0 : return false;
51 0 : for (const char *const *p = vi->feature_names; NULL != *p; p++)
52 0 : if (0 ==
53 0 : strcmp (*p,
54 : name))
55 0 : return true;
56 0 : 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 0 : starts_with (const char *s,
69 : const char *prefix)
70 : {
71 0 : return ( (NULL != s) &&
72 0 : (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 0 : curl_http3_is_conservative_ok (void)
88 : {
89 : #if LIBCURL_VERSION_NUM >= 0x075700
90 : const curl_version_info_data *vi
91 0 : = curl_version_info (CURLVERSION_NOW);
92 :
93 0 : if (! has_feature (vi,
94 : "HTTP3"))
95 0 : 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 0 : if (! starts_with (vi->quic_version,
104 : "ngtcp2/"))
105 0 : return false;
106 :
107 : /*
108 : * Conservative policy: don't use HTTP/3 with GnuTLS.
109 : * (too many bugs in recent releases still)
110 : */
111 0 : if (starts_with (vi->ssl_version,
112 : "GnuTLS/"))
113 0 : 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 0 : if (starts_with (vi->ssl_version,
123 : "OpenSSL/"))
124 0 : return true;
125 0 : if (starts_with (vi->ssl_version,
126 : "wolfSSL/"))
127 0 : return true;
128 : #endif
129 0 : return false;
130 : }
131 :
132 :
133 : void
134 869 : TALER_curl_set_http_version (CURL *eh,
135 : bool enable_http3)
136 : {
137 : static enum GNUNET_GenericReturnValue http3 = GNUNET_SYSERR;
138 869 : long version = CURL_HTTP_VERSION_1_1;
139 :
140 869 : if (enable_http3)
141 : {
142 0 : if (GNUNET_SYSERR == http3)
143 0 : http3 = curl_http3_is_conservative_ok ()
144 : ? GNUNET_YES
145 0 : : GNUNET_NO;
146 : #ifdef CURL_HTTP_VERSION_3
147 0 : 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 0 : 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 0 : version = CURL_HTTP_VERSION_2TLS;
160 : #endif
161 : }
162 : }
163 869 : GNUNET_assert (CURLE_OK ==
164 : curl_easy_setopt (eh,
165 : CURLOPT_HTTP_VERSION,
166 : version));
167 869 : }
168 :
169 :
170 : void
171 869 : TALER_curl_set_secure_redirect_policy (CURL *eh,
172 : const char *url)
173 : {
174 869 : GNUNET_assert (CURLE_OK ==
175 : curl_easy_setopt (eh,
176 : CURLOPT_FOLLOWLOCATION,
177 : 1L));
178 869 : 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 869 : GNUNET_assert (CURLE_OK ==
216 : curl_easy_setopt (eh,
217 : CURLOPT_MAXREDIRS,
218 : 5L));
219 869 : }
220 :
221 :
222 : enum GNUNET_GenericReturnValue
223 818 : 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 818 : str = json_dumps (body,
231 : JSON_COMPACT);
232 818 : if (NULL == str)
233 : {
234 0 : GNUNET_break (0);
235 0 : return GNUNET_SYSERR;
236 : }
237 818 : slen = strlen (str);
238 818 : if (TALER_CURL_COMPRESS_BODIES &&
239 818 : (! ctx->disable_compression) )
240 : {
241 : Bytef *cbuf;
242 : uLongf cbuf_size;
243 : int ret;
244 :
245 818 : cbuf_size = compressBound (slen);
246 818 : cbuf = GNUNET_malloc (cbuf_size);
247 818 : ret = compress (cbuf,
248 : &cbuf_size,
249 : (const Bytef *) str,
250 : slen);
251 818 : if (Z_OK != ret)
252 : {
253 : /* compression failed!? */
254 0 : GNUNET_break (0);
255 0 : GNUNET_free (cbuf);
256 0 : free (str);
257 0 : return GNUNET_SYSERR;
258 : }
259 818 : free (str);
260 818 : slen = (size_t) cbuf_size;
261 818 : ctx->json_enc = (char *) cbuf;
262 818 : GNUNET_assert (
263 : NULL !=
264 : (ctx->headers = curl_slist_append (
265 : ctx->headers,
266 : "Content-Encoding: deflate")));
267 : }
268 : else
269 : {
270 0 : ctx->json_enc = str;
271 : }
272 818 : 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 818 : GNUNET_assert (
280 : NULL !=
281 : (ctx->headers = curl_slist_append (
282 : ctx->headers,
283 : "Content-Type: application/json")));
284 :
285 818 : GNUNET_assert (CURLE_OK ==
286 : curl_easy_setopt (eh,
287 : CURLOPT_POSTFIELDS,
288 : ctx->json_enc));
289 818 : GNUNET_assert (CURLE_OK ==
290 : curl_easy_setopt (eh,
291 : CURLOPT_POSTFIELDSIZE,
292 : (long) slen));
293 818 : return GNUNET_OK;
294 : }
295 :
296 :
297 : void
298 828 : TALER_curl_easy_post_finished (struct TALER_CURL_PostContext *ctx)
299 : {
300 828 : curl_slist_free_all (ctx->headers);
301 828 : ctx->headers = NULL;
302 828 : GNUNET_free (ctx->json_enc);
303 828 : ctx->json_enc = NULL;
304 828 : }
|