xref: /src/crypto/openssl/test/quic-openssl-docker/hq-interop/quic-hq-interop-server.c (revision f25b8c9fb4f58cf61adb47d7570abe7caa6d385d)
1 /*
2  *  Copyright 2024-2025 The OpenSSL Project Authors. All Rights Reserved.
3  *
4  *  Licensed under the Apache License 2.0 (the "License").  You may not use
5  *  this file except in compliance with the License.  You can obtain a copy
6  *  in the file LICENSE in the source distribution or at
7  *  https://www.openssl.org/source/license.html
8  */
9 
10 /**
11  * @file quic-hq-interop-server.c
12  * @brief Minimal QUIC HTTP/0.9 server implementation.
13  *
14  * This file implements a lightweight QUIC server supporting the HTTP/0.9
15  * protocol for interoperability testing. It includes functions for setting
16  * up a secure QUIC connection, handling ALPN negotiation, and serving client
17  * requests.  Intended for use with the quic-interop-runner
18  * available at https://interop.seemann.io
19  *
20  * Key functionalities:
21  * - Setting up SSL_CTX with QUIC support.
22  * - Negotiating ALPN strings during the TLS handshake.
23  * - Listening and accepting incoming QUIC connections.
24  * - Handling client requests via HTTP/0.9 protocol.
25  *
26  * Usage:
27  *   <port> <server.crt> <server.key>
28  * The server binds to the specified port and serves files using the given
29  * certificate and private key.
30  *
31  * Environment variables:
32  * - FILEPREFIX: Specifies the directory containing files to serve.
33  *   Defaults to "./downloads" if not set.
34  * - SSLKEYLOGFILE: specifies that keylogging should be performed on the server
35  *   should be set to a file name to record keylog data to
36  * - NO_ADDR_VALIDATE: Disables server address validation of clients
37  *
38  */
39 
40 #include <string.h>
41 
42 /* Include the appropriate header file for SOCK_STREAM */
43 #ifdef _WIN32
44 #include <stdarg.h>
45 #include <winsock2.h>
46 #include <ws2tcpip.h>
47 #else
48 #include <sys/socket.h>
49 #include <netinet/in.h>
50 #include <unistd.h>
51 #endif
52 
53 #include <openssl/bio.h>
54 #include <openssl/ssl.h>
55 #include <openssl/err.h>
56 #include <openssl/quic.h>
57 
58 #define BUF_SIZE 4096
59 
60 /**
61  * @brief ALPN (Application-Layer Protocol Negotiation) identifier for QUIC.
62  *
63  * This constant defines the ALPN string used during the TLS handshake
64  * to negotiate the application-layer protocol between the client and
65  * the server. It specifies "hq-interop" as the supported protocol.
66  *
67  * Format:
68  * - The first byte represents the length of the ALPN string.
69  * - Subsequent bytes represent the ASCII characters of the protocol name.
70  *
71  * Value:
72  * - Protocol: "hq-interop"
73  * - Length: 10 bytes
74  *
75  * Usage:
76  * This is passed to the ALPN callback function to validate and
77  * negotiate the desired protocol during the TLS handshake.
78  */
79 static const unsigned char alpn_ossltest[] = {
80     10,
81     'h',
82     'q',
83     '-',
84     'i',
85     'n',
86     't',
87     'e',
88     'r',
89     'o',
90     'p',
91 };
92 
93 /**
94  * @brief Directory prefix for serving requested files.
95  *
96  * This variable specifies the directory path used as the base location
97  * for serving files in response to client requests. It is used to construct
98  * the full file path for requested resources.
99  *
100  * Default:
101  * - If not set via the FILEPREFIX environment variable, it defaults to
102  *   "./downloads".
103  *
104  * Usage:
105  * - Updated at runtime based on the FILEPREFIX environment variable.
106  * - Used to locate and serve files during incoming requests.
107  */
108 static char *fileprefix = NULL;
109 
110 /**
111  * @brief Callback for ALPN (Application-Layer Protocol Negotiation) selection.
112  *
113  * This function is invoked during the TLS handshake on the server side to
114  * validate and negotiate the desired ALPN (Application-Layer Protocol
115  * Negotiation) protocol with the client. It ensures that the negotiated
116  * protocol matches the predefined "hq-interop" string.
117  *
118  * @param ssl       Pointer to the SSL connection object.
119  * @param[out] out  Pointer to the negotiated ALPN protocol string.
120  * @param[out] out_len Length of the negotiated ALPN protocol string.
121  * @param in        Pointer to the client-provided ALPN protocol list.
122  * @param in_len    Length of the client-provided ALPN protocol list.
123  * @param arg       Optional user-defined argument (unused in this context).
124  *
125  * @return SSL_TLSEXT_ERR_OK on successful ALPN negotiation,
126  *         SSL_TLSEXT_ERR_ALERT_FATAL otherwise.
127  *
128  * Usage:
129  * - This function is set as the ALPN selection callback in the SSL_CTX
130  *   using `SSL_CTX_set_alpn_select_cb`.
131  * - Ensures that only the predefined ALPN protocol is accepted.
132  *
133  * Note:
134  * - The predefined protocol is specified in the `alpn_ossltest` array.
135  */
select_alpn(SSL * ssl,const unsigned char ** out,unsigned char * out_len,const unsigned char * in,unsigned int in_len,void * arg)136 static int select_alpn(SSL *ssl, const unsigned char **out,
137     unsigned char *out_len, const unsigned char *in,
138     unsigned int in_len, void *arg)
139 {
140     /*
141      * Use the next_proto helper function here.
142      * This scans the list of alpns we support and matches against
143      * what the client is requesting
144      */
145     if (SSL_select_next_proto((unsigned char **)out, out_len, alpn_ossltest,
146             sizeof(alpn_ossltest), in,
147             in_len)
148         == OPENSSL_NPN_NEGOTIATED)
149         return SSL_TLSEXT_ERR_OK;
150     return SSL_TLSEXT_ERR_ALERT_FATAL;
151 }
152 
153 /**
154  * @brief Creates and configures an SSL_CTX for a QUIC server.
155  *
156  * This function initializes an SSL_CTX object with the QUIC server method
157  * and configures it using the provided certificate and private key. The
158  * context is prepared for handling secure QUIC connections and performing
159  * ALPN (Application-Layer Protocol Negotiation).
160  *
161  * @param cert_path Path to the server's certificate chain file in PEM format.
162  *                  The chain file must include the server's leaf certificate
163  *                  followed by intermediate CA certificates.
164  * @param key_path  Path to the server's private key file in PEM format. The
165  *                  private key must correspond to the leaf certificate in
166  *                  the chain file.
167  *
168  * @return Pointer to the initialized SSL_CTX on success, or NULL on failure.
169  *
170  * Configuration:
171  * - Loads the certificate chain and private key into the context.
172  * - Disables client certificate verification (no mutual TLS).
173  * - Sets up the ALPN selection callback for protocol negotiation.
174  *
175  * Error Handling:
176  * - If any step fails (e.g., loading the certificate or key), the function
177  *   frees the SSL_CTX and returns NULL.
178  *
179  * Usage:
180  * - Call this function to create an SSL_CTX before starting the QUIC server.
181  * - Ensure valid paths for the certificate and private key are provided.
182  *
183  * Note:
184  * - The ALPN callback only supports the predefined protocol defined in
185  *   `alpn_ossltest`.
186  */
create_ctx(const char * cert_path,const char * key_path)187 static SSL_CTX *create_ctx(const char *cert_path, const char *key_path)
188 {
189     SSL_CTX *ctx;
190 
191     /*
192      * An SSL_CTX holds shared configuration information for multiple
193      * subsequent per-client connections. We specifically load a QUIC
194      * server method here.
195      */
196     ctx = SSL_CTX_new(OSSL_QUIC_server_method());
197     if (ctx == NULL)
198         goto err;
199 
200     /*
201      * Load the server's certificate *chain* file (PEM format), which includes
202      * not only the leaf (end-entity) server certificate, but also any
203      * intermediate issuer-CA certificates.  The leaf certificate must be the
204      * first certificate in the file.
205      *
206      * In advanced use-cases this can be called multiple times, once per public
207      * key algorithm for which the server has a corresponding certificate.
208      * However, the corresponding private key (see below) must be loaded first,
209      * *before* moving on to the next chain file.
210      *
211      * The requisite files "chain.pem" and "pkey.pem" can be generated by running
212      * "make chain" in this directory.  If the server will be executed from some
213      * other directory, move or copy the files there.
214      */
215     if (SSL_CTX_use_certificate_chain_file(ctx, cert_path) <= 0) {
216         fprintf(stderr, "couldn't load certificate file: %s\n", cert_path);
217         goto err;
218     }
219 
220     /*
221      * Load the corresponding private key, this also checks that the private
222      * key matches the just loaded end-entity certificate.  It does not check
223      * whether the certificate chain is valid, the certificates could be
224      * expired, or may otherwise fail to form a chain that a client can validate.
225      */
226     if (SSL_CTX_use_PrivateKey_file(ctx, key_path, SSL_FILETYPE_PEM) <= 0) {
227         fprintf(stderr, "couldn't load key file: %s\n", key_path);
228         goto err;
229     }
230 
231     /*
232      * Since we're not soliciting or processing client certificates, we don't
233      * need to configure a trusted-certificate store, so no call to
234      * SSL_CTX_set_default_verify_paths() is needed.  The server's own
235      * certificate chain is assumed valid.
236      */
237     SSL_CTX_set_verify(ctx, SSL_VERIFY_NONE, NULL);
238 
239     /* Setup ALPN negotiation callback to decide which ALPN is accepted. */
240     SSL_CTX_set_alpn_select_cb(ctx, select_alpn, NULL);
241 
242     return ctx;
243 
244 err:
245     SSL_CTX_free(ctx);
246     return NULL;
247 }
248 
249 /**
250  * @brief Creates and binds a UDP socket to the specified port.
251  *
252  * This function initializes a new UDP socket, binds it to the specified
253  * port on the local host, and returns the socket file descriptor for
254  * further use.
255  *
256  * @param port The port number to which the UDP socket should be bound.
257  *
258  * @return On success, returns the BIO of the created socket.
259  *         On failure, returns NULL.
260  *
261  * Steps:
262  * - Creates a new UDP socket using the `socket` system call.
263  * - Configures the socket address structure to bind to the specified port
264  *   on the local host.
265  * - Binds the socket to the port using the `bind` system call.
266  *
267  * Error Handling:
268  * - If socket creation or binding fails, an error message is printed to
269  *   `stderr`, the socket (if created) is closed, and -1 is returned.
270  *
271  * Usage:
272  * - Call this function to set up a socket for handling incoming QUIC
273  *   connections.
274  *
275  * Notes:
276  * - This function assumes UDP (`SOCK_DGRAM`).
277  * - This function accepts on both IPv4 and IPv6.
278  * - The specified port is converted to network byte order using `htons`.
279  */
create_socket(uint16_t port)280 static BIO *create_socket(uint16_t port)
281 {
282     int fd = -1;
283     BIO *sock = NULL;
284     BIO_ADDR *addr = NULL;
285     int opt = 0;
286 #ifdef _WIN32
287     struct in6_addr in6addr_any;
288 
289     memset(&in6addr_any, 0, sizeof(in6addr_any));
290 #endif
291 
292     /* Retrieve the file descriptor for a new UDP socket */
293     if ((fd = BIO_socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP, 0)) < 0) {
294         fprintf(stderr, "cannot create socket");
295         goto err;
296     }
297 
298     /*
299      * IPv6_V6ONLY is only available on some platforms. If it is defined,
300      * disable it to accept both IPv4 and IPv6 connections. Otherwise, the
301      * server will only accept IPv6 connections.
302      */
303 #ifdef IPV6_V6ONLY
304     if (setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &opt, sizeof(opt)) < 0) {
305         fprintf(stderr, "setsockopt IPV6_V6ONLY failed");
306         goto err;
307     }
308 #endif
309 
310     /*
311      * Create a new BIO_ADDR
312      */
313     addr = BIO_ADDR_new();
314     if (addr == NULL) {
315         fprintf(stderr, "Unable to create BIO_ADDR\n");
316         goto err;
317     }
318 
319     /*
320      * Build an INADDR_ANY BIO_ADDR
321      */
322     if (!BIO_ADDR_rawmake(addr, AF_INET6, &in6addr_any, sizeof(in6addr_any), htons(port))) {
323         fprintf(stderr, "unable to bind to port %d\n", port);
324         goto err;
325     }
326 
327     /* Bind to the new UDP socket */
328     if (!BIO_bind(fd, addr, 0)) {
329         fprintf(stderr, "cannot bind to %u\n", port);
330         goto err;
331     }
332 
333     /*
334      * Create a new datagram socket
335      */
336     sock = BIO_new(BIO_s_datagram());
337     if (sock == NULL) {
338         fprintf(stderr, "cannot create dgram bio\n");
339         goto err;
340     }
341 
342     /*
343      * associate the underlying socket with the dgram BIO
344      */
345     if (!BIO_set_fd(sock, fd, BIO_CLOSE)) {
346         fprintf(stderr, "Unable to set fd of dgram sock\n");
347         goto err;
348     }
349 
350     /*
351      * Free our allocated addr
352      */
353     BIO_ADDR_free(addr);
354     return sock;
355 
356 err:
357     BIO_ADDR_free(addr);
358     BIO_free(sock);
359     BIO_closesocket(fd);
360     return NULL;
361 }
362 
363 /**
364  * @brief Handles I/O failures on an SSL stream based on the result code.
365  *
366  * This function processes the result of an SSL I/O operation and handles
367  * different types of errors that may occur during the operation. It takes
368  * appropriate actions such as retrying the operation, reporting errors, or
369  * returning specific status codes based on the error type.
370  *
371  * @param ssl A pointer to the SSL object representing the stream.
372  * @param res The result code from the SSL I/O operation.
373  * @return An integer indicating the outcome:
374  *         - 0: EOF, indicating the stream has been closed.
375  *         - -1: A fatal error occurred or the stream has been reset.
376  *
377  *
378  * @note If the failure is due to an SSL verification error, additional
379  * information will be logged to stderr.
380  */
handle_io_failure(SSL * ssl,int res)381 static int handle_io_failure(SSL *ssl, int res)
382 {
383     switch (SSL_get_error(ssl, res)) {
384     case SSL_ERROR_ZERO_RETURN:
385         /* EOF */
386         return 0;
387 
388     case SSL_ERROR_SYSCALL:
389         return -1;
390 
391     case SSL_ERROR_SSL:
392         /*
393          * Some stream fatal error occurred. This could be because of a
394          * stream reset - or some failure occurred on the underlying
395          * connection.
396          */
397         switch (SSL_get_stream_read_state(ssl)) {
398         case SSL_STREAM_STATE_RESET_REMOTE:
399             fprintf(stderr, "Stream reset occurred\n");
400             /*
401              * The stream has been reset but the connection is still
402              * healthy.
403              */
404             break;
405 
406         case SSL_STREAM_STATE_CONN_CLOSED:
407             fprintf(stderr, "Connection closed\n");
408             /* Connection is already closed. */
409             break;
410 
411         default:
412             fprintf(stderr, "Unknown stream failure\n");
413             break;
414         }
415         /*
416          * If the failure is due to a verification error we can get more
417          * information about it from SSL_get_verify_result().
418          */
419         if (SSL_get_verify_result(ssl) != X509_V_OK)
420             fprintf(stderr, "Verify error: %s\n",
421                 X509_verify_cert_error_string(SSL_get_verify_result(ssl)));
422         return -1;
423 
424     default:
425         return -1;
426     }
427 }
428 
429 /**
430  * @brief Processes a new incoming QUIC stream for an HTTP/0.9 GET request.
431  *
432  * This function reads an HTTP/0.9 GET request from the provided QUIC stream,
433  * retrieves the requested file from the server's file system, and sends the
434  * file contents back to the client over the stream.
435  *
436  * @param Pointer to the SSL object representing the QUIC stream.
437  *
438  * Operation:
439  * - Reads the HTTP/0.9 GET request from the client.
440  * - Parses the request to extract the requested file name.
441  * - Constructs the file path using the `fileprefix` directory.
442  * - Reads the requested file in chunks and sends it to the client.
443  * - Concludes the QUIC stream once the file is fully sent.
444  *
445  * Error Handling:
446  * - If the request is invalid or the file cannot be opened, appropriate
447  *   error messages are logged, and the function exits without sending data.
448  * - Errors during file reading or writing to the stream are handled, with
449  *   retries for buffer-related issues (e.g., full send buffer).
450  *
451  * Notes:
452  * - The request is expected to be a valid HTTP/0.9 GET request.
453  * - File paths are sanitized to prevent path traversal vulnerabilities.
454  * - The function uses blocking operations for reading and writing data.
455  *
456  * Usage:
457  * - Called for each accepted QUIC stream to handle client requests.
458  */
process_new_stream(SSL * stream)459 static void process_new_stream(SSL *stream)
460 {
461     unsigned char buf[BUF_SIZE];
462     char path[BUF_SIZE];
463     char *req = (char *)buf;
464     char *reqname;
465     char *creturn;
466     size_t nread;
467     BIO *readbio;
468     size_t bytes_read = 0;
469     size_t bytes_written = 0;
470     size_t offset = 0;
471     int rc;
472     int ret;
473     size_t total_read = 0;
474 
475     memset(buf, 0, BUF_SIZE);
476     for (;;) {
477         nread = 0;
478         ret = SSL_read_ex(stream, &buf[total_read],
479             sizeof(buf) - total_read - 1, &nread);
480         total_read += nread;
481         if (ret <= 0) {
482             ret = handle_io_failure(stream, ret);
483             if (ret == 0) {
484                 /* EOF condition, fin bit set, we got the whole request */
485                 break;
486             } else {
487                 /* permanent failure, abort */
488                 fprintf(stderr, "Failure on stream\n");
489                 return;
490             }
491         }
492     }
493 
494     /* We should have a valid http 0.9 GET request here */
495     fprintf(stderr, "Request is %s\n", req);
496 
497     /* Look for the last '/' char in the request */
498     reqname = strrchr(req, '/');
499     if (reqname == NULL)
500         return;
501     reqname++;
502 
503     /* Requests have a trailing \r\n, eliminate them */
504     creturn = strchr(reqname, '\r');
505     if (creturn != NULL)
506         *creturn = '\0';
507 
508     snprintf(path, BUF_SIZE, "%s/%s", fileprefix, reqname);
509 
510     fprintf(stderr, "Serving %s\n", path);
511     readbio = BIO_new_file(path, "r");
512     if (readbio == NULL) {
513         fprintf(stderr, "Unable to open %s\n", path);
514         ERR_print_errors_fp(stderr);
515         goto done;
516     }
517 
518     /* Read the readbio file into a buffer, and just send it to the requestor */
519     while (BIO_eof(readbio) <= 0) {
520         bytes_read = 0;
521         if (!BIO_read_ex(readbio, buf, BUF_SIZE, &bytes_read)) {
522             if (BIO_eof(readbio) <= 0) {
523                 fprintf(stderr, "Failed to read from %s\n", path);
524                 ERR_print_errors_fp(stderr);
525                 goto out;
526             } else {
527                 break;
528             }
529         }
530 
531         offset = 0;
532         for (;;) {
533             bytes_written = 0;
534             rc = SSL_write_ex(stream, &buf[offset], bytes_read, &bytes_written);
535             if (rc <= 0) {
536                 rc = SSL_get_error(stream, rc);
537                 switch (rc) {
538                 case SSL_ERROR_WANT_WRITE:
539                     fprintf(stderr, "Send buffer full, retrying\n");
540                     continue;
541                     break;
542                 default:
543                     fprintf(stderr, "Unhandled error cause %d\n", rc);
544                     goto done;
545                     break;
546                 }
547             }
548             bytes_read -= bytes_written;
549             offset += bytes_written;
550             bytes_written = 0;
551             if (bytes_read == 0)
552                 break;
553         }
554     }
555 
556 done:
557     if (!SSL_stream_conclude(stream, 0))
558         fprintf(stderr, "Failed to conclude stream\n");
559 
560 out:
561     BIO_free(readbio);
562     return;
563 }
564 
565 /**
566  * @brief Runs the QUIC server to accept and handle client connections.
567  *
568  * This function initializes a QUIC listener, binds it to the provided UDP
569  * socket, and enters a loop to accept client connections and process incoming
570  * QUIC streams. Each connection is handled until termination, and streams are
571  * processed individually using the `process_new_stream` function.
572  *
573  * @param ctx Pointer to the SSL_CTX object configured for QUIC.
574  * @param sock  BIO of the bound UDP socket.
575  *
576  * @return Returns 0 on error; otherwise, the server runs indefinitely.
577  *
578  * Operation:
579  * - Creates a QUIC listener using the provided SSL_CTX and associates it
580  *   with the specified UDP socket.
581  * - Waits for incoming QUIC connections and accepts them.
582  * - For each connection:
583  *   - Accepts incoming streams.
584  *   - Processes each stream using `process_new_stream`.
585  *   - Shuts down the connection upon completion.
586  *
587  * Error Handling:
588  * - If listener creation or connection acceptance fails, the function logs
589  *   an error message and exits the loop.
590  * - Cleans up allocated resources (e.g., listener, connection) on failure.
591  *
592  * Usage:
593  * - Call this function in the main server loop after setting up the
594  *   SSL_CTX and binding a UDP socket.
595  *
596  * Notes:
597  * - Uses blocking operations for listener, connection, and stream handling.
598  * - Incoming streams are processed based on the configured stream policy.
599  * - The server runs in an infinite loop unless a fatal error occurs.
600  */
run_quic_server(SSL_CTX * ctx,BIO * sock)601 static int run_quic_server(SSL_CTX *ctx, BIO *sock)
602 {
603     int ok = 0;
604     SSL *listener, *conn, *stream;
605     unsigned long errcode;
606     uint64_t flags = 0;
607 
608     /*
609      * If NO_ADDR_VALIDATE exists in our environment
610      * then disable address validation on our listener
611      */
612     if (getenv("NO_ADDR_VALIDATE") != NULL)
613         flags |= SSL_LISTENER_FLAG_NO_VALIDATE;
614 
615     /*
616      * Create a new QUIC listener. Listeners, and other QUIC objects, default
617      * to operating in blocking mode. The configured behaviour is inherited by
618      * child objects.
619      */
620     if ((listener = SSL_new_listener(ctx, flags)) == NULL)
621         goto err;
622 
623     /* Provide the listener with our UDP socket. */
624     SSL_set_bio(listener, sock, sock);
625 
626     /* Begin listening. */
627     if (!SSL_listen(listener))
628         goto err;
629 
630     /*
631      * Begin an infinite loop of listening for connections. We will only
632      * exit this loop if we encounter an error.
633      */
634     for (;;) {
635         /* Pristine error stack for each new connection */
636         ERR_clear_error();
637 
638         /* Block while waiting for a client connection */
639         printf("Waiting for connection\n");
640         conn = SSL_accept_connection(listener, 0);
641         if (conn == NULL) {
642             fprintf(stderr, "error while accepting connection\n");
643             goto err;
644         }
645         printf("Accepted new connection\n");
646 
647         /*
648          * QUIC requires that we inform the connection that
649          * we always want to accept new streams, rather than reject them
650          * Additionally, while we don't make an explicit call here, we
651          * are using the default stream mode, as would be specified by
652          * a call to SSL_set_default_stream_mode
653          */
654         if (!SSL_set_incoming_stream_policy(conn,
655                 SSL_INCOMING_STREAM_POLICY_ACCEPT,
656                 0)) {
657             fprintf(stderr, "Failed to set incoming stream policy\n");
658             goto close_conn;
659         }
660 
661         /*
662          * Until the connection is closed, accept incoming stream
663          * requests and serve them
664          */
665         for (;;) {
666             /*
667              * Note that SSL_accept_stream is blocking here, as the
668              * conn SSL object inherited the default blocking property
669              * from its parent, the listener SSL object.  As such there
670              * is no need to handle retry failures here.
671              */
672             stream = SSL_accept_stream(conn, 0);
673             if (stream == NULL) {
674                 /*
675                  * If we don't get a stream, either we
676                  * Hit a legitimate error, and should bail out
677                  * or
678                  * The Client closed the connection, and there are no
679                  * more incoming streams expected
680                  *
681                  * Filter on the shutdown error, and only print an error
682                  * message if the cause is not SHUTDOWN
683                  */
684                 ERR_print_errors_fp(stderr);
685                 errcode = ERR_get_error();
686                 if (ERR_GET_REASON(errcode) != SSL_R_PROTOCOL_IS_SHUTDOWN)
687                     fprintf(stderr, "Failure in accept stream, error %s\n",
688                         ERR_reason_error_string(errcode));
689                 break;
690             }
691             process_new_stream(stream);
692             SSL_free(stream);
693         }
694 
695         /*
696          * Shut down the connection. We may need to call this multiple times
697          * to ensure the connection is shutdown completely.
698          */
699     close_conn:
700         while (SSL_shutdown(conn) != 1)
701             continue;
702 
703         SSL_free(conn);
704     }
705 
706 err:
707     SSL_free(listener);
708     return ok;
709 }
710 
711 /**
712  * @brief Entry point for the minimal QUIC HTTP/0.9 server.
713  *
714  * This function initializes the server, sets up a QUIC context, binds a UDP
715  * socket to the specified port, and starts the main QUIC server loop to handle
716  * client connections and requests.
717  *
718  * @param argc Number of command-line arguments.
719  * @param argv Array of command-line arguments:
720  *             - argv[0]: Program name.
721  *             - argv[1]: Port number to bind the server.
722  *             - argv[2]: Path to the server's certificate file (PEM format).
723  *             - argv[3]: Path to the server's private key file (PEM format).
724  *
725  * @return Returns EXIT_SUCCESS on successful execution, or EXIT_FAILURE
726  *         on error.
727  *
728  * Operation:
729  * - Validates the command-line arguments.
730  * - Reads the FILEPREFIX environment variable to set the file prefix for
731  *   serving files (default is "./downloads").
732  * - Creates an SSL_CTX with QUIC support using the provided certificate and
733  *   key files.
734  * - Parses and validates the port number.
735  * - Creates and binds a UDP socket to the specified port.
736  * - Starts the server loop using `run_quic_server` to accept and process
737  *   client connections.
738  *
739  * Error Handling:
740  * - If any initialization step fails (e.g., invalid arguments, socket
741  *   creation, context setup), appropriate error messages are logged, and
742  *   the program exits with EXIT_FAILURE.
743  *
744  * Usage:
745  * - Run the program with the required arguments to start the server:
746  *   `./server <port> <server.crt> <server.key>`
747  *
748  * Notes:
749  * - Ensure that the certificate and key files exist and are valid.
750  * - The server serves files from the directory specified by FILEPREFIX.
751  */
main(int argc,char * argv[])752 int main(int argc, char *argv[])
753 {
754     int res = EXIT_FAILURE;
755     SSL_CTX *ctx = NULL;
756     BIO *sock = NULL;
757     unsigned long port;
758 
759     if (argc != 4) {
760         fprintf(stderr, "usage: %s <port> <server.crt> <server.key>\n", argv[0]);
761         goto out;
762     }
763 
764     fileprefix = getenv("FILEPREFIX");
765     if (fileprefix == NULL)
766         fileprefix = "./downloads";
767 
768     fprintf(stderr, "Fileprefix is %s\n", fileprefix);
769 
770     /* Create SSL_CTX that supports QUIC. */
771     if ((ctx = create_ctx(argv[2], argv[3])) == NULL) {
772         ERR_print_errors_fp(stderr);
773         fprintf(stderr, "Failed to create context\n");
774         goto out;
775     }
776 
777     /* Parse port number from command line arguments. */
778     port = strtoul(argv[1], NULL, 0);
779     if (port == 0 || port > UINT16_MAX) {
780         fprintf(stderr, "Failed to parse port number\n");
781         goto out;
782     }
783     fprintf(stderr, "Binding to port %lu\n", port);
784 
785     /* Create and bind a UDP socket. */
786     if ((sock = create_socket((uint16_t)port)) == NULL) {
787         ERR_print_errors_fp(stderr);
788         fprintf(stderr, "Failed to create socket\n");
789         goto out;
790     }
791 
792     /* QUIC server connection acceptance loop. */
793     if (!run_quic_server(ctx, sock)) {
794         ERR_print_errors_fp(stderr);
795         fprintf(stderr, "Failed to run quic server\n");
796         goto out;
797     }
798 
799     res = EXIT_SUCCESS;
800 out:
801     /* Free resources. */
802     SSL_CTX_free(ctx);
803     BIO_free(sock);
804     return res;
805 }
806