Line data Source code
1 : #define _GNU_SOURCE
2 : #include "fd_sshttp_private.h"
3 : #include "fd_ssarchive.h"
4 :
5 : #include "../../../third_party/picohttpparser/picohttpparser.h"
6 : #include "../../../waltz/openssl/fd_openssl_tile.h"
7 : #include "../../../waltz/openssl/fd_openssl.h"
8 : #include "../../../util/log/fd_log.h"
9 : #include "../../../waltz/http/fd_http.h"
10 :
11 : FD_STATIC_ASSERT( FD_HASH_FOOTPRINT==32UL, resolved_hash_sz );
12 :
13 : #include <unistd.h>
14 : #include <errno.h>
15 : #include <poll.h>
16 : #include <stdlib.h>
17 :
18 : #include <sys/socket.h>
19 : #include <netinet/in.h>
20 :
21 : _Bool fd_sshttp_fuzz = 0;
22 :
23 : FD_FN_CONST ulong
24 0 : fd_sshttp_align( void ) {
25 0 : return alignof(fd_sshttp_t);
26 0 : }
27 :
28 : FD_FN_CONST ulong
29 0 : fd_sshttp_footprint( void ) {
30 0 : ulong l;
31 0 : l = FD_LAYOUT_INIT;
32 0 : l = FD_LAYOUT_APPEND( l, alignof(fd_sshttp_t), sizeof(fd_sshttp_t) );
33 0 : return FD_LAYOUT_FINI( l, fd_sshttp_align() );
34 0 : }
35 :
36 : void *
37 0 : fd_sshttp_new( void * shmem ) {
38 0 : if( FD_UNLIKELY( !shmem ) ) {
39 0 : FD_LOG_WARNING(( "NULL shmem" ));
40 0 : return NULL;
41 0 : }
42 :
43 0 : if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)shmem, fd_sshttp_align() ) ) ) {
44 0 : FD_LOG_WARNING(( "unaligned shmem" ));
45 0 : return NULL;
46 0 : }
47 :
48 0 : FD_SCRATCH_ALLOC_INIT( l, shmem );
49 0 : fd_sshttp_t * sshttp = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_sshttp_t), sizeof(fd_sshttp_t) );
50 :
51 0 : sshttp->state = FD_SSHTTP_STATE_INIT;
52 0 : sshttp->sockfd = -1;
53 0 : sshttp->content_len = 0UL;
54 0 : fd_cstr_fini( sshttp->snapshot_name );
55 0 : sshttp->resolved_slot = 0UL;
56 0 : fd_memset( sshttp->resolved_hash, 0, FD_HASH_FOOTPRINT );
57 :
58 0 : #if FD_HAS_OPENSSL
59 0 : sshttp->ssl = NULL;
60 0 : sshttp->ssl_ctx = NULL;
61 :
62 0 : if( !fd_sshttp_fuzz ) {
63 0 : SSL_CTX * ssl_ctx = SSL_CTX_new( TLS_client_method() );
64 0 : if( FD_UNLIKELY( !ssl_ctx ) ) {
65 0 : FD_LOG_ERR(( "SSL_CTX_new failed" ));
66 0 : }
67 :
68 0 : if( FD_UNLIKELY( !SSL_CTX_set_min_proto_version( ssl_ctx, TLS1_3_VERSION ) ) ) {
69 0 : FD_LOG_ERR(( "SSL_CTX_set_min_proto_version(ssl_ctx,TLS1_3_VERSION) failed" ));
70 0 : }
71 :
72 : /* transferring ownership of ssl_ctx by assignment */
73 0 : sshttp->ssl_ctx = ssl_ctx;
74 :
75 0 : fd_ossl_load_certs( sshttp->ssl_ctx );
76 0 : }
77 0 : #endif
78 :
79 0 : FD_COMPILER_MFENCE();
80 0 : sshttp->magic = FD_SSHTTP_MAGIC;
81 0 : FD_COMPILER_MFENCE();
82 :
83 0 : return (void *)sshttp;
84 0 : }
85 :
86 : fd_sshttp_t *
87 0 : fd_sshttp_join( void * shhttp ) {
88 0 : if( FD_UNLIKELY( !shhttp ) ) {
89 0 : FD_LOG_WARNING(( "NULL shhttp" ));
90 0 : return NULL;
91 0 : }
92 :
93 0 : if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)shhttp, fd_sshttp_align() ) ) ) {
94 0 : FD_LOG_WARNING(( "misaligned shhttp" ));
95 0 : return NULL;
96 0 : }
97 :
98 0 : fd_sshttp_t * sshttp = (fd_sshttp_t *)shhttp;
99 :
100 0 : if( FD_UNLIKELY( sshttp->magic!=FD_SSHTTP_MAGIC ) ) {
101 0 : FD_LOG_WARNING(( "bad magic" ));
102 0 : return NULL;
103 0 : }
104 :
105 0 : return sshttp;
106 0 : }
107 :
108 : #if FD_HAS_OPENSSL
109 : static int
110 0 : http_init_ssl( fd_sshttp_t * http ) {
111 0 : FD_TEST( http->hostname );
112 0 : FD_TEST( http->ssl_ctx );
113 :
114 0 : http->ssl = SSL_new( http->ssl_ctx );
115 0 : if( FD_UNLIKELY( !http->ssl ) ) {
116 0 : FD_LOG_WARNING(( "SSL_new failed for %s", http->hostname ));
117 0 : return -1;
118 0 : }
119 :
120 0 : static uchar const alpn_protos[] = { 8, 'h', 't', 't', 'p', '/', '1', '.', '1' };
121 0 : int alpn_res = SSL_set_alpn_protos( http->ssl, alpn_protos, sizeof(alpn_protos) );
122 0 : if( FD_UNLIKELY( alpn_res!=0 ) ) {
123 0 : FD_LOG_WARNING(( "SSL_set_alpn_protos failed (%d) for %s", alpn_res, http->hostname ));
124 0 : SSL_free( http->ssl ); http->ssl = NULL;
125 0 : return -1;
126 0 : }
127 :
128 : /* set SNI and hostname verification */
129 0 : long sni_res = SSL_set_tlsext_host_name( http->ssl, http->hostname );
130 0 : if( FD_UNLIKELY( !sni_res ) ) {
131 0 : FD_LOG_WARNING(( "SSL_set_tlsext_host_name failed (%ld) for %s", sni_res, http->hostname ));
132 0 : SSL_free( http->ssl ); http->ssl = NULL;
133 0 : return -1;
134 0 : }
135 0 : int set1_host_res = SSL_set1_host( http->ssl, http->hostname );
136 0 : if( FD_UNLIKELY( !set1_host_res ) ) {
137 0 : FD_LOG_WARNING(( "SSL_set1_host failed (%d) for %s", set1_host_res, http->hostname ));
138 0 : SSL_free( http->ssl ); http->ssl = NULL;
139 0 : return -1;
140 0 : }
141 0 : return 0;
142 0 : }
143 : #endif
144 :
145 : int
146 : fd_sshttp_init( fd_sshttp_t * http,
147 : fd_ip4_port_t addr,
148 : char const * hostname,
149 : int is_https,
150 : char const * path,
151 : ulong path_len,
152 : ulong hops,
153 0 : long now ) {
154 0 : FD_TEST( http->state==FD_SSHTTP_STATE_INIT );
155 :
156 0 : http->hostname = hostname;
157 0 : http->is_https = is_https;
158 :
159 0 : if( FD_LIKELY( is_https ) ) {
160 0 : #if FD_HAS_OPENSSL
161 0 : if( FD_UNLIKELY( http_init_ssl( http ) ) ) return -1;
162 : #else
163 : FD_LOG_ERR(( "cannot make HTTPS connection without OpenSSL" ));
164 : #endif
165 0 : }
166 :
167 0 : if( hops!=ULONG_MAX ) {
168 0 : http->hops = hops;
169 0 : fd_cstr_fini( http->snapshot_name );
170 0 : http->resolved_slot = 0UL;
171 0 : fd_memset( http->resolved_hash, 0, FD_HASH_FOOTPRINT );
172 0 : }
173 0 : http->request_sent = 0UL;
174 0 : int fmt_ok;
175 0 : if( FD_LIKELY( is_https ) ) {
176 0 : fmt_ok = fd_cstr_printf_check( http->request, sizeof(http->request), &http->request_len,
177 0 : "GET %.*s HTTP/1.1\r\n"
178 0 : "User-Agent: Firedancer\r\n"
179 0 : "Accept: */*\r\n"
180 0 : "Accept-Encoding: identity\r\n"
181 0 : "Host: %s\r\n\r\n",
182 0 : (int)path_len, path, hostname );
183 0 : } else {
184 0 : fmt_ok = fd_cstr_printf_check( http->request, sizeof(http->request), &http->request_len,
185 0 : "GET %.*s HTTP/1.1\r\n"
186 0 : "User-Agent: Firedancer\r\n"
187 0 : "Accept: */*\r\n"
188 0 : "Accept-Encoding: identity\r\n"
189 0 : "Host: " FD_IP4_ADDR_FMT "\r\n\r\n",
190 0 : (int)path_len, path, FD_IP4_ADDR_FMT_ARGS( addr.addr ) );
191 0 : }
192 0 : if( FD_UNLIKELY( !fmt_ok ) ) {
193 0 : FD_LOG_WARNING(( "HTTP request too long for %.*s", (int)path_len, path ));
194 0 : #if FD_HAS_OPENSSL
195 0 : if( FD_LIKELY( http->ssl ) ) { SSL_free( http->ssl ); http->ssl = NULL; }
196 0 : #endif
197 0 : return -1;
198 0 : }
199 :
200 0 : http->response_len = 0UL;
201 0 : http->content_len = 0UL;
202 0 : http->content_read = 0UL;
203 0 : http->empty_recvs = 0UL;
204 :
205 0 : http->addr = addr;
206 0 : http->sockfd = socket( AF_INET, SOCK_STREAM|SOCK_NONBLOCK, 0 );
207 0 : if( FD_UNLIKELY( -1==http->sockfd ) ) {
208 0 : FD_LOG_WARNING(( "socket() failed (%d-%s) for " FD_IP4_ADDR_FMT ":%hu", errno, fd_io_strerror( errno ),
209 0 : FD_IP4_ADDR_FMT_ARGS( http->addr.addr ), fd_ushort_bswap( http->addr.port ) ));
210 0 : #if FD_HAS_OPENSSL
211 0 : if( FD_LIKELY( http->ssl ) ) { SSL_free( http->ssl ); http->ssl = NULL; }
212 0 : #endif
213 0 : return -1;
214 0 : }
215 :
216 0 : struct sockaddr_in addr_in = {
217 0 : .sin_family = AF_INET,
218 0 : .sin_port = addr.port,
219 0 : .sin_addr = { .s_addr = addr.addr }
220 0 : };
221 :
222 0 : if( FD_LIKELY( -1==connect( http->sockfd, fd_type_pun_const( &addr_in ), sizeof(addr_in) ) ) ) {
223 0 : if( FD_UNLIKELY( errno!=EINPROGRESS ) ) {
224 0 : FD_LOG_WARNING(( "connect() failed (%d-%s) to " FD_IP4_ADDR_FMT ":%hu", errno, fd_io_strerror( errno ),
225 0 : FD_IP4_ADDR_FMT_ARGS( http->addr.addr ), fd_ushort_bswap( http->addr.port ) ));
226 0 : if( FD_UNLIKELY( -1==close( http->sockfd ) ) ) FD_LOG_ERR(( "close() failed (%d-%s) for " FD_IP4_ADDR_FMT ":%hu", errno, fd_io_strerror( errno ),
227 0 : FD_IP4_ADDR_FMT_ARGS( http->addr.addr ), fd_ushort_bswap( http->addr.port ) ));
228 0 : http->sockfd = -1;
229 0 : #if FD_HAS_OPENSSL
230 0 : if( FD_LIKELY( http->ssl ) ) { SSL_free( http->ssl ); http->ssl = NULL; }
231 0 : #endif
232 0 : return -1;
233 0 : }
234 0 : }
235 :
236 0 : if( FD_LIKELY( is_https ) ) {
237 0 : #if FD_HAS_OPENSSL
238 0 : if( FD_UNLIKELY( !fd_openssl_ssl_set_fd( http->ssl, http->sockfd ) ) ) {
239 0 : FD_LOG_WARNING(( "fd_openssl_ssl_set_fd failed for " FD_IP4_ADDR_FMT ":%hu",
240 0 : FD_IP4_ADDR_FMT_ARGS( http->addr.addr ), fd_ushort_bswap( http->addr.port ) ));
241 0 : if( FD_UNLIKELY( -1==close( http->sockfd ) ) ) {
242 0 : FD_LOG_ERR(( "close() failed (%d-%s) for " FD_IP4_ADDR_FMT ":%hu", errno, fd_io_strerror( errno ),
243 0 : FD_IP4_ADDR_FMT_ARGS( http->addr.addr ), fd_ushort_bswap( http->addr.port ) ));
244 0 : }
245 0 : http->sockfd = -1;
246 0 : SSL_free( http->ssl ); http->ssl = NULL;
247 0 : return -1;
248 0 : }
249 0 : #endif
250 0 : http->state = FD_SSHTTP_STATE_CONNECT;
251 0 : http->deadline = now + FD_SSHTTP_DEADLINE_NANOS;
252 0 : } else {
253 0 : http->state = FD_SSHTTP_STATE_REQ;
254 0 : http->deadline = now + FD_SSHTTP_DEADLINE_NANOS;
255 0 : }
256 :
257 0 : return 0;
258 0 : }
259 :
260 : #if FD_HAS_OPENSSL
261 : static int
262 : http_connect_ssl( fd_sshttp_t * http,
263 0 : long now ) {
264 0 : if( FD_UNLIKELY( now>http->deadline ) ) {
265 0 : FD_LOG_WARNING(( "deadline exceeded during connect to " FD_IP4_ADDR_FMT ":%hu",
266 0 : FD_IP4_ADDR_FMT_ARGS( http->addr.addr ), fd_ushort_bswap( http->addr.port ) ));
267 0 : fd_sshttp_cancel( http );
268 0 : return FD_SSHTTP_ADVANCE_ERROR;
269 0 : }
270 :
271 0 : FD_TEST( http->ssl );
272 0 : int ssl_err = SSL_connect( http->ssl );
273 0 : if( FD_UNLIKELY( ssl_err!=1 ) ) {
274 0 : int ssl_err_code = SSL_get_error( http->ssl, ssl_err );
275 0 : if( FD_UNLIKELY( ssl_err_code!=SSL_ERROR_WANT_READ && ssl_err_code!=SSL_ERROR_WANT_WRITE ) ) {
276 0 : FD_LOG_WARNING(( "SSL_connect failed (%d-%s) to " FD_IP4_ADDR_FMT ":%hu", ssl_err_code, fd_openssl_ssl_strerror( ssl_err_code ),
277 0 : FD_IP4_ADDR_FMT_ARGS( http->addr.addr ), fd_ushort_bswap( http->addr.port ) ));
278 0 : fd_sshttp_cancel( http );
279 0 : return FD_SSHTTP_ADVANCE_ERROR;
280 0 : }
281 : /* in progress */
282 0 : return FD_SSHTTP_ADVANCE_AGAIN;
283 0 : }
284 :
285 0 : http->state = FD_SSHTTP_STATE_REQ;
286 0 : http->deadline = now + FD_SSHTTP_DEADLINE_NANOS;
287 0 : return FD_SSHTTP_ADVANCE_AGAIN;
288 0 : }
289 :
290 : static int
291 : http_shutdown_ssl( fd_sshttp_t * http,
292 0 : long now ) {
293 0 : if( FD_UNLIKELY( now>http->deadline ) ) {
294 0 : FD_LOG_WARNING(( "deadline exceeded during shutdown for " FD_IP4_ADDR_FMT ":%hu",
295 0 : FD_IP4_ADDR_FMT_ARGS( http->addr.addr ), fd_ushort_bswap( http->addr.port ) ));
296 0 : fd_sshttp_cancel( http );
297 0 : return FD_SSHTTP_ADVANCE_ERROR;
298 0 : }
299 :
300 0 : int res = SSL_shutdown( http->ssl );
301 0 : if( FD_LIKELY( res<=0 ) ) {
302 0 : int ssl_err_code = SSL_get_error( http->ssl, res );
303 0 : if( FD_UNLIKELY( ssl_err_code!=SSL_ERROR_WANT_READ && ssl_err_code!=SSL_ERROR_WANT_WRITE && res!=0 ) ) {
304 0 : FD_LOG_WARNING(( "SSL_shutdown failed (%d-%s) for " FD_IP4_ADDR_FMT ":%hu", ssl_err_code, fd_openssl_ssl_strerror( ssl_err_code ),
305 0 : FD_IP4_ADDR_FMT_ARGS( http->addr.addr ), fd_ushort_bswap( http->addr.port ) ));
306 0 : fd_sshttp_cancel( http );
307 0 : return FD_SSHTTP_ADVANCE_ERROR;
308 0 : }
309 :
310 0 : return FD_SSHTTP_ADVANCE_AGAIN;
311 0 : }
312 :
313 0 : http->state = http->next_state;
314 0 : return FD_SSHTTP_ADVANCE_AGAIN;
315 0 : }
316 :
317 : static long
318 : http_recv_ssl( fd_sshttp_t * http,
319 : void * buf,
320 0 : ulong bufsz ) {
321 0 : int read_res = SSL_read( http->ssl, buf, (int)bufsz );
322 0 : if( FD_UNLIKELY( read_res<=0 ) ) {
323 0 : int ssl_err = SSL_get_error( http->ssl, read_res );
324 :
325 0 : if( FD_UNLIKELY( ssl_err!=SSL_ERROR_WANT_READ && ssl_err!=SSL_ERROR_WANT_WRITE ) ) {
326 0 : FD_LOG_WARNING(( "SSL_read failed (%d-%s) from " FD_IP4_ADDR_FMT ":%hu", ssl_err, fd_openssl_ssl_strerror( ssl_err ),
327 0 : FD_IP4_ADDR_FMT_ARGS( http->addr.addr ), fd_ushort_bswap( http->addr.port ) ));
328 0 : return FD_SSHTTP_ADVANCE_ERROR;
329 0 : }
330 :
331 0 : return FD_SSHTTP_ADVANCE_AGAIN;
332 0 : }
333 :
334 0 : return (long)read_res;
335 0 : }
336 :
337 : static long
338 : http_send_ssl( fd_sshttp_t * http,
339 : void * buf,
340 0 : ulong bufsz ) {
341 0 : int write_res = SSL_write( http->ssl, buf, (int)bufsz );
342 0 : if( FD_UNLIKELY( write_res<=0 ) ) {
343 0 : int ssl_err = SSL_get_error( http->ssl, write_res );
344 :
345 0 : if( FD_UNLIKELY( ssl_err!=SSL_ERROR_WANT_READ && ssl_err!=SSL_ERROR_WANT_WRITE ) ) {
346 0 : FD_LOG_WARNING(( "SSL_write failed (%d-%s) to " FD_IP4_ADDR_FMT ":%hu", ssl_err, fd_openssl_ssl_strerror( ssl_err ),
347 0 : FD_IP4_ADDR_FMT_ARGS( http->addr.addr ), fd_ushort_bswap( http->addr.port ) ));
348 0 : return FD_SSHTTP_ADVANCE_ERROR;
349 0 : }
350 :
351 0 : return FD_SSHTTP_ADVANCE_AGAIN;
352 0 : }
353 :
354 0 : return (long)write_res;
355 0 : }
356 :
357 : static int
358 : setup_redirect( fd_sshttp_t * http,
359 0 : long now ) {
360 0 : fd_sshttp_cancel( http );
361 0 : if( FD_UNLIKELY( fd_sshttp_init( http, http->addr, http->hostname, http->is_https, http->location, http->location_len, ULONG_MAX, now ) ) ) {
362 0 : return FD_SSHTTP_ADVANCE_ERROR;
363 0 : }
364 0 : return FD_SSHTTP_ADVANCE_AGAIN;
365 0 : }
366 :
367 : #endif
368 :
369 : void
370 0 : fd_sshttp_cancel( fd_sshttp_t * http ) {
371 0 : if( FD_LIKELY( http->state!=FD_SSHTTP_STATE_INIT && -1!=http->sockfd ) ) {
372 0 : if( FD_UNLIKELY( -1==close( http->sockfd ) ) ) FD_LOG_ERR(( "close() failed (%d-%s) for " FD_IP4_ADDR_FMT ":%hu", errno, fd_io_strerror( errno ),
373 0 : FD_IP4_ADDR_FMT_ARGS( http->addr.addr ), fd_ushort_bswap( http->addr.port ) ));
374 0 : http->sockfd = -1;
375 0 : }
376 0 : http->state = FD_SSHTTP_STATE_INIT;
377 :
378 0 : #if FD_HAS_OPENSSL
379 0 : if( FD_LIKELY( http->ssl ) ) {
380 0 : SSL_free( http->ssl );
381 0 : http->ssl = NULL;
382 0 : }
383 0 : #endif
384 0 : }
385 :
386 : static long
387 : http_send( fd_sshttp_t * http,
388 : void * buf,
389 0 : ulong bufsz ) {
390 0 : #if FD_HAS_OPENSSL
391 0 : if( FD_LIKELY( http->is_https ) ) return http_send_ssl( http, buf, bufsz );
392 0 : #endif
393 :
394 0 : long sent = sendto( http->sockfd, buf, bufsz, MSG_NOSIGNAL, NULL, 0 );
395 0 : if( FD_UNLIKELY( -1==sent && errno==EAGAIN ) ) return FD_SSHTTP_ADVANCE_AGAIN;
396 0 : else if( FD_UNLIKELY( -1==sent ) ) {
397 0 : FD_LOG_WARNING(( "sendto() failed (%d-%s) to " FD_IP4_ADDR_FMT ":%hu", errno, fd_io_strerror( errno ),
398 0 : FD_IP4_ADDR_FMT_ARGS( http->addr.addr ), fd_ushort_bswap( http->addr.port ) ));
399 0 : fd_sshttp_cancel( http );
400 0 : return FD_SSHTTP_ADVANCE_ERROR;
401 0 : }
402 :
403 0 : return sent;
404 0 : }
405 :
406 : static long
407 : http_recv( fd_sshttp_t * http,
408 : void * buf,
409 0 : ulong bufsz ) {
410 0 : #if FD_HAS_OPENSSL
411 0 : if( FD_LIKELY( http->is_https ) ) return http_recv_ssl( http, buf, bufsz );
412 0 : #endif
413 :
414 0 : long read = recvfrom( http->sockfd, buf, bufsz, 0, NULL, NULL );
415 0 : if( FD_UNLIKELY( -1==read && errno==EAGAIN ) ) {
416 0 : if( FD_UNLIKELY( ++http->empty_recvs>8UL && !fd_sshttp_fuzz ) ) {
417 : /* If we have gone several iterations without having any data to
418 : read, sleep the thread for up to one millisecond, or until
419 : the socket is readable again, whichever comes first. */
420 0 : struct pollfd pfd = {
421 0 : .fd = http->sockfd,
422 0 : .events = POLLIN,
423 0 : };
424 0 : if( FD_UNLIKELY( -1==fd_syscall_poll( &pfd, 1 /*fds*/, 1 /*ms*/ ) ) ) {
425 0 : if( FD_UNLIKELY( errno!=EINTR ) ) {
426 0 : FD_LOG_WARNING(( "fd_syscall_poll() failed (%d-%s) for " FD_IP4_ADDR_FMT ":%hu", errno, fd_io_strerror( errno ),
427 0 : FD_IP4_ADDR_FMT_ARGS( http->addr.addr ), fd_ushort_bswap( http->addr.port ) ));
428 0 : fd_sshttp_cancel( http );
429 0 : return FD_SSHTTP_ADVANCE_ERROR;
430 0 : }
431 0 : }
432 0 : }
433 0 : return FD_SSHTTP_ADVANCE_AGAIN;
434 0 : } else if( FD_UNLIKELY( -1==read ) ) {
435 0 : FD_LOG_WARNING(( "recvfrom() failed (%d-%s) from " FD_IP4_ADDR_FMT ":%hu", errno, fd_io_strerror( errno ),
436 0 : FD_IP4_ADDR_FMT_ARGS( http->addr.addr ), fd_ushort_bswap( http->addr.port ) ));
437 0 : fd_sshttp_cancel( http );
438 0 : return FD_SSHTTP_ADVANCE_ERROR;
439 0 : }
440 0 : http->empty_recvs = 0UL;
441 :
442 0 : return read;
443 0 : }
444 :
445 : static int
446 : send_request( fd_sshttp_t * http,
447 0 : long now ) {
448 0 : if( FD_UNLIKELY( now>http->deadline ) ) {
449 0 : FD_LOG_WARNING(( "timeout sending request to " FD_IP4_ADDR_FMT ":%hu",
450 0 : FD_IP4_ADDR_FMT_ARGS( http->addr.addr ), fd_ushort_bswap( http->addr.port ) ));
451 0 : fd_sshttp_cancel( http );
452 0 : return FD_SSHTTP_ADVANCE_ERROR;
453 0 : }
454 :
455 0 : long sent = http_send( http, http->request+http->request_sent, http->request_len-http->request_sent );
456 0 : if( FD_UNLIKELY( sent<=0 ) ) return (int)sent;
457 :
458 0 : http->request_sent += (ulong)sent;
459 0 : if( FD_UNLIKELY( http->request_sent==http->request_len ) ) {
460 0 : http->state = FD_SSHTTP_STATE_RESP;
461 0 : http->response_len = 0UL;
462 0 : http->deadline = now + FD_SSHTTP_DEADLINE_NANOS;
463 0 : }
464 :
465 0 : return FD_SSHTTP_ADVANCE_AGAIN;
466 0 : }
467 :
468 : static int
469 : follow_redirect( fd_sshttp_t * http,
470 : struct phr_header * headers,
471 : ulong header_cnt,
472 0 : long now ) {
473 0 : if( FD_UNLIKELY( !http->hops ) ) {
474 0 : FD_LOG_WARNING(( "too many redirects (remaining %lu) from " FD_IP4_ADDR_FMT ":%hu", http->hops,
475 0 : FD_IP4_ADDR_FMT_ARGS( http->addr.addr ), fd_ushort_bswap( http->addr.port ) ));
476 0 : fd_sshttp_cancel( http );
477 0 : return FD_SSHTTP_ADVANCE_ERROR;
478 0 : }
479 : /* The check above guarantees hops>0. */
480 0 : http->hops--;
481 :
482 0 : ulong location_len = 0UL;
483 0 : char const * location = NULL;
484 :
485 0 : for( ulong i=0UL; i<header_cnt; i++ ) {
486 0 : if( FD_UNLIKELY( headers[ i ].name_len == 8 && !strncasecmp( headers[ i ].name, "location", headers[ i ].name_len ) ) ) {
487 0 : if( FD_UNLIKELY( !headers [ i ].value_len || headers[ i ].value[ 0 ]!='/' ) ) {
488 0 : FD_LOG_WARNING(( "invalid location header `%.*s` from " FD_IP4_ADDR_FMT ":%hu", (int)headers[ i ].value_len, headers[ i ].value,
489 0 : FD_IP4_ADDR_FMT_ARGS( http->addr.addr ), fd_ushort_bswap( http->addr.port ) ));
490 0 : fd_sshttp_cancel( http );
491 0 : return FD_SSHTTP_ADVANCE_ERROR;
492 0 : }
493 :
494 0 : location_len = headers[ i ].value_len;
495 0 : location = headers[ i ].value;
496 :
497 0 : if( FD_UNLIKELY( location_len>=PATH_MAX-1UL ) ) {
498 0 : FD_LOG_WARNING(( "location header too long `%.*s` from " FD_IP4_ADDR_FMT ":%hu", (int)location_len, location,
499 0 : FD_IP4_ADDR_FMT_ARGS( http->addr.addr ), fd_ushort_bswap( http->addr.port ) ));
500 0 : fd_sshttp_cancel( http );
501 0 : return FD_SSHTTP_ADVANCE_ERROR;
502 0 : }
503 :
504 0 : char snapshot_name[ PATH_MAX ];
505 0 : fd_memcpy( snapshot_name, location+1UL, location_len-1UL );
506 0 : snapshot_name[ location_len-1UL ] = '\0';
507 :
508 0 : int is_zstd;
509 0 : ulong full_entry_slot, incremental_entry_slot;
510 0 : uchar decoded_hash[ FD_HASH_FOOTPRINT ];
511 0 : int err = fd_ssarchive_parse_filename( snapshot_name, &full_entry_slot, &incremental_entry_slot, decoded_hash, &is_zstd );
512 :
513 0 : if( FD_UNLIKELY( err || !is_zstd ) ) {
514 0 : FD_LOG_WARNING(( "unrecognized snapshot file `%s` in redirect location header from " FD_IP4_ADDR_FMT ":%hu", snapshot_name,
515 0 : FD_IP4_ADDR_FMT_ARGS( http->addr.addr ), fd_ushort_bswap( http->addr.port ) ));
516 0 : fd_sshttp_cancel( http );
517 0 : return FD_SSHTTP_ADVANCE_ERROR;
518 0 : }
519 :
520 0 : http->resolved_slot = (incremental_entry_slot!=ULONG_MAX)
521 0 : ? incremental_entry_slot : full_entry_slot;
522 0 : fd_memcpy( http->resolved_hash, decoded_hash, FD_HASH_FOOTPRINT );
523 :
524 0 : char encoded_hash[ FD_BASE58_ENCODED_32_SZ ];
525 0 : fd_base58_encode_32( decoded_hash, NULL, encoded_hash );
526 :
527 0 : if( FD_LIKELY( incremental_entry_slot!=ULONG_MAX ) ) {
528 0 : FD_TEST( fd_cstr_printf_check( http->snapshot_name, PATH_MAX, NULL, "incremental-snapshot-%lu-%lu-%s.tar.zst", full_entry_slot, incremental_entry_slot, encoded_hash ) );
529 0 : } else {
530 0 : FD_TEST( fd_cstr_printf_check( http->snapshot_name, PATH_MAX, NULL, "snapshot-%lu-%s.tar.zst", full_entry_slot, encoded_hash ) );
531 0 : }
532 0 : break;
533 0 : }
534 0 : }
535 :
536 0 : if( FD_UNLIKELY( !location_len ) ) {
537 0 : FD_LOG_WARNING(( "no location header in redirect response from " FD_IP4_ADDR_FMT ":%hu",
538 0 : FD_IP4_ADDR_FMT_ARGS( http->addr.addr ), fd_ushort_bswap( http->addr.port ) ));
539 0 : fd_sshttp_cancel( http );
540 0 : return FD_SSHTTP_ADVANCE_ERROR;
541 0 : }
542 :
543 : /* Pre-validate that the redirect request will fit in the request
544 : buffer. The request is rebuilt from scratch by fd_sshttp_init
545 : during the redirect, but the format must match so that a path
546 : accepted here will not overflow in fd_sshttp_init. */
547 0 : int pre_check;
548 0 : if( FD_LIKELY( http->is_https ) ) {
549 0 : pre_check = fd_cstr_printf_check( http->request, sizeof(http->request), &http->request_len,
550 0 : "GET %.*s HTTP/1.1\r\n"
551 0 : "User-Agent: Firedancer\r\n"
552 0 : "Accept: */*\r\n"
553 0 : "Accept-Encoding: identity\r\n"
554 0 : "Host: %s\r\n\r\n",
555 0 : (int)location_len, location, http->hostname );
556 0 : } else {
557 0 : pre_check = fd_cstr_printf_check( http->request, sizeof(http->request), &http->request_len,
558 0 : "GET %.*s HTTP/1.1\r\n"
559 0 : "User-Agent: Firedancer\r\n"
560 0 : "Accept: */*\r\n"
561 0 : "Accept-Encoding: identity\r\n"
562 0 : "Host: " FD_IP4_ADDR_FMT "\r\n\r\n",
563 0 : (int)location_len, location, FD_IP4_ADDR_FMT_ARGS( http->addr.addr ) );
564 0 : }
565 0 : if( FD_UNLIKELY( !pre_check ) ) {
566 0 : FD_LOG_WARNING(( "redirect request too long `%.*s` from " FD_IP4_ADDR_FMT ":%hu", (int)location_len, location,
567 0 : FD_IP4_ADDR_FMT_ARGS( http->addr.addr ), fd_ushort_bswap( http->addr.port ) ));
568 0 : fd_sshttp_cancel( http );
569 0 : return FD_SSHTTP_ADVANCE_ERROR;
570 0 : }
571 :
572 0 : FD_LOG_INFO(( "following redirect to %s://" FD_IP4_ADDR_FMT ":%hu%.*s", http->is_https ? "https" : "http",
573 0 : FD_IP4_ADDR_FMT_ARGS( http->addr.addr ), fd_ushort_bswap( http->addr.port ),
574 0 : (int)location_len, location ));
575 :
576 0 : if( FD_UNLIKELY( http->is_https ) ) {
577 0 : http->next_state = FD_SSHTTP_STATE_REDIRECT;
578 0 : http->state = FD_SSHTTP_STATE_SHUTTING_DOWN;
579 0 : http->location_len = location_len;
580 0 : FD_TEST( location_len<PATH_MAX-1UL );
581 0 : fd_memcpy( http->location, location, location_len );
582 0 : http->location[ location_len ] = '\0';
583 0 : } else {
584 0 : if( FD_LIKELY( !fd_sshttp_fuzz ) ) {
585 0 : fd_sshttp_cancel( http );
586 0 : if( FD_UNLIKELY( fd_sshttp_init( http, http->addr, http->hostname, http->is_https, location, location_len, ULONG_MAX, now ) ) ) {
587 0 : return FD_SSHTTP_ADVANCE_ERROR;
588 0 : }
589 0 : } else {
590 0 : http->state = FD_SSHTTP_STATE_RESP;
591 0 : http->response_len = 0UL;
592 0 : }
593 0 : }
594 :
595 0 : return FD_SSHTTP_ADVANCE_AGAIN;
596 0 : }
597 :
598 : static int
599 : read_response( fd_sshttp_t * http,
600 : ulong * data_len,
601 : uchar * data,
602 0 : long now ) {
603 0 : if( FD_UNLIKELY( now>http->deadline ) ) {
604 0 : FD_LOG_WARNING(( "timeout reading response from " FD_IP4_ADDR_FMT ":%hu",
605 0 : FD_IP4_ADDR_FMT_ARGS( http->addr.addr ), fd_ushort_bswap( http->addr.port ) ));
606 0 : fd_sshttp_cancel( http );
607 0 : return FD_SSHTTP_ADVANCE_ERROR;
608 0 : }
609 :
610 0 : long read = http_recv( http, http->response+http->response_len, sizeof(http->response)-http->response_len );
611 0 : if( FD_UNLIKELY( read<=0 ) ) return (int)read;
612 :
613 0 : http->response_len += (ulong)read;
614 :
615 0 : int minor_version;
616 0 : int status;
617 0 : const char * message;
618 0 : ulong message_len;
619 0 : struct phr_header headers[ 128UL ];
620 0 : ulong header_cnt = 128UL;
621 0 : int parsed = phr_parse_response( http->response,
622 0 : http->response_len,
623 0 : &minor_version,
624 0 : &status,
625 0 : &message,
626 0 : &message_len,
627 0 : headers,
628 0 : &header_cnt,
629 0 : http->response_len - (ulong)read );
630 0 : if( FD_UNLIKELY( parsed==-1 ) ) {
631 0 : FD_LOG_WARNING(( "malformed response headers from " FD_IP4_ADDR_FMT ":%hu",
632 0 : FD_IP4_ADDR_FMT_ARGS( http->addr.addr ), fd_ushort_bswap( http->addr.port ) ));
633 0 : fd_sshttp_cancel( http );
634 0 : return FD_SSHTTP_ADVANCE_ERROR;
635 0 : } else if( parsed==-2 ) {
636 0 : return FD_SSHTTP_ADVANCE_AGAIN;
637 0 : }
638 :
639 0 : int is_redirect = (status==301) | (status==302) | (status==303) | (status==307) | (status==308);
640 0 : if( FD_UNLIKELY( is_redirect ) ) {
641 0 : return follow_redirect( http, headers, header_cnt, now );
642 0 : }
643 :
644 0 : if( FD_UNLIKELY( status!=200 ) ) {
645 0 : FD_LOG_WARNING(( "unexpected response status %d %.*s from " FD_IP4_ADDR_FMT ":%hu", status, (int)message_len, message,
646 0 : FD_IP4_ADDR_FMT_ARGS( http->addr.addr ), fd_ushort_bswap( http->addr.port ) ));
647 0 : fd_sshttp_cancel( http );
648 0 : return FD_SSHTTP_ADVANCE_ERROR;
649 0 : }
650 :
651 0 : http->content_read = 0UL;
652 0 : http->content_len = ULONG_MAX;
653 0 : for( ulong i=0UL; i<header_cnt; i++ ) {
654 0 : if( FD_LIKELY( headers[i].name_len!=14UL ) ) continue;
655 0 : if( FD_LIKELY( strncasecmp( headers[i].name, "content-length", 14UL ) ) ) continue;
656 :
657 0 : ulong val = 0UL;
658 0 : if( FD_UNLIKELY( fd_http_parse_content_len( headers[i].value, (ulong)headers[i].value_len, &val ) || val==0UL ) ) {
659 0 : FD_LOG_WARNING(( "invalid content-length in response from " FD_IP4_ADDR_FMT ":%hu", FD_IP4_ADDR_FMT_ARGS( http->addr.addr ), fd_ushort_bswap( http->addr.port ) ));
660 0 : fd_sshttp_cancel( http );
661 0 : return FD_SSHTTP_ADVANCE_ERROR;
662 0 : }
663 0 : http->content_len = val;
664 0 : break;
665 0 : }
666 :
667 0 : if( FD_UNLIKELY( http->content_len==ULONG_MAX ) ) {
668 0 : FD_LOG_WARNING(( "no content-length header in response from " FD_IP4_ADDR_FMT ":%hu",
669 0 : FD_IP4_ADDR_FMT_ARGS( http->addr.addr ), fd_ushort_bswap( http->addr.port ) ));
670 0 : fd_sshttp_cancel( http );
671 0 : return FD_SSHTTP_ADVANCE_ERROR;
672 0 : }
673 :
674 0 : http->state = FD_SSHTTP_STATE_DL;
675 0 : if( FD_UNLIKELY( (ulong)parsed<http->response_len ) ) {
676 : /* Body bytes past the caller's buffer are kept in response, with
677 : response_len repurposed as the residual length, drained by
678 : read_body before it reads the socket again. */
679 0 : ulong leftover = fd_ulong_min( http->response_len - (ulong)parsed, http->content_len );
680 0 : ulong copy_len = fd_ulong_min( leftover, *data_len );
681 0 : fd_memcpy( data, http->response+parsed, copy_len );
682 0 : memmove( http->response, http->response+(ulong)parsed+copy_len, leftover-copy_len );
683 0 : http->response_len = leftover-copy_len;
684 0 : http->content_read += copy_len;
685 0 : *data_len = copy_len;
686 0 : return FD_SSHTTP_ADVANCE_DATA;
687 0 : } else {
688 0 : FD_TEST( http->response_len==(ulong)parsed );
689 0 : http->response_len = 0UL;
690 0 : return FD_SSHTTP_ADVANCE_AGAIN;
691 0 : }
692 0 : }
693 :
694 : static int
695 : read_body( fd_sshttp_t * http,
696 : ulong * data_len,
697 : uchar * data,
698 0 : long now ) {
699 0 : if( FD_UNLIKELY( http->content_read>=http->content_len ) ) {
700 0 : if( FD_UNLIKELY( http->is_https ) ) {
701 0 : http->next_state = FD_SSHTTP_STATE_DONE;
702 0 : http->state = FD_SSHTTP_STATE_SHUTTING_DOWN;
703 0 : http->deadline = now + FD_SSHTTP_DEADLINE_NANOS;
704 0 : return FD_SSHTTP_ADVANCE_AGAIN;
705 0 : } else {
706 0 : fd_sshttp_cancel( http );
707 0 : http->state = FD_SSHTTP_STATE_INIT;
708 0 : return FD_SSHTTP_ADVANCE_DONE;
709 0 : }
710 0 : }
711 :
712 0 : FD_TEST( http->content_read<http->content_len );
713 :
714 0 : if( FD_UNLIKELY( http->response_len ) ) { /* residual body bytes from read_response */
715 0 : ulong copy_len = fd_ulong_min( http->response_len, *data_len );
716 0 : fd_memcpy( data, http->response, copy_len );
717 0 : memmove( http->response, http->response+copy_len, http->response_len-copy_len );
718 0 : http->response_len -= copy_len;
719 0 : http->content_read += copy_len;
720 0 : *data_len = copy_len;
721 0 : return FD_SSHTTP_ADVANCE_DATA;
722 0 : }
723 :
724 0 : long read = http_recv( http, data, fd_ulong_min( *data_len, http->content_len-http->content_read ) );
725 0 : if( FD_UNLIKELY( read<=0 ) ) return (int)read;
726 :
727 0 : *data_len = (ulong)read;
728 0 : http->content_read += (ulong)read;
729 :
730 0 : return FD_SSHTTP_ADVANCE_DATA;
731 0 : }
732 :
733 : char const *
734 0 : fd_sshttp_snapshot_name( fd_sshttp_t const * http ) {
735 0 : return http->snapshot_name;
736 0 : }
737 :
738 : ulong
739 0 : fd_sshttp_content_len( fd_sshttp_t const * http ) {
740 0 : return http->content_len;
741 0 : }
742 :
743 : ulong
744 0 : fd_sshttp_resolved_slot( fd_sshttp_t const * http ) {
745 0 : return http->resolved_slot;
746 0 : }
747 :
748 : uchar const *
749 0 : fd_sshttp_resolved_hash( fd_sshttp_t const * http ) {
750 0 : return http->resolved_hash;
751 0 : }
752 :
753 : int
754 : fd_sshttp_advance( fd_sshttp_t * http,
755 : ulong * data_len,
756 : uchar * data,
757 : int * downloading,
758 0 : long now ) {
759 0 : *downloading = 0;
760 0 : switch( http->state ) {
761 0 : case FD_SSHTTP_STATE_INIT: return FD_SSHTTP_ADVANCE_AGAIN;
762 0 : #if FD_HAS_OPENSSL
763 0 : case FD_SSHTTP_STATE_CONNECT: return http_connect_ssl( http, now );
764 0 : case FD_SSHTTP_STATE_SHUTTING_DOWN: return http_shutdown_ssl( http, now );
765 0 : case FD_SSHTTP_STATE_REDIRECT: return setup_redirect( http, now );
766 0 : #endif
767 0 : case FD_SSHTTP_STATE_REQ: return send_request( http, now );
768 0 : case FD_SSHTTP_STATE_RESP: return read_response( http, data_len, data, now );
769 0 : case FD_SSHTTP_STATE_DL: *downloading = 1; return read_body( http, data_len, data, now );
770 0 : case FD_SSHTTP_STATE_DONE:
771 0 : fd_sshttp_cancel( http );
772 0 : http->state = FD_SSHTTP_STATE_INIT;
773 0 : return FD_SSHTTP_ADVANCE_DONE;
774 0 : default: return FD_SSHTTP_ADVANCE_ERROR;
775 0 : }
776 0 : }
|