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