]> git.ipfire.org Git - thirdparty/openssl.git/commitdiff
MVP demo TLS server
authorViktor Dukhovni <openssl-users@dukhovni.org>
Mon, 27 May 2024 11:35:56 +0000 (21:35 +1000)
committerTodd Short <todd.short@me.com>
Tue, 18 Jun 2024 17:49:11 +0000 (13:49 -0400)
- No concurrency, one client-at-a-time
- Blocking
- No client certs
- Fixed chain and key file names
- Minimal support for session resumption

Reviewed-by: Tomas Mraz <tomas@openssl.org>
Reviewed-by: Matt Caswell <matt@openssl.org>
Reviewed-by: Todd Short <todd.short@me.com>
(Merged from https://github.com/openssl/openssl/pull/24505)

demos/guide/Makefile
demos/guide/tls-server-block.c [new file with mode: 0644]
doc/build.info
doc/man7/ossl-guide-introduction.pod
doc/man7/ossl-guide-tls-client-block.pod
doc/man7/ossl-guide-tls-introduction.pod
doc/man7/ossl-guide-tls-server-block.pod [new file with mode: 0644]

index 29a0fd56e407280581bcc0701a7227dff94c561c..943d2f3ee78d3dd569fcedd65a597e922253207a 100644 (file)
@@ -5,6 +5,7 @@
 #    LD_LIBRARY_PATH=../.. ./tls-client-block www.example.com 443
 
 TESTS = tls-client-block \
+        tls-server-block \
         quic-client-block \
         quic-multi-stream \
         tls-client-non-block \
@@ -14,21 +15,28 @@ CFLAGS  = -I../../include -g -Wall
 LDFLAGS = -L../..
 LDLIBS  = -lcrypto -lssl
 
-all: $(TESTS)
+all: $(TESTS) chain
 
 tls-client-block: tls-client-block.o
+tls-server-block: tls-server-block.o
 quic-client-block: quic-client-block.o
 quic-multi-stream: quic-multi-stream.o
 tls-client-non-block: tls-client-non-block.o
 quic-client-non-block: quic-client-non-block.o
 
+chain: chain.pem
+pkey.pem:
+       openssl genpkey -algorithm rsa -out pkey.pem -pkeyopt rsa_keygen_bits:2048
+chain.pem: pkey.pem
+       openssl req -x509 -new -key pkey.pem -days 36500 -subj / -out chain.pem
+
 $(TESTS):
        $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $< $(LDLIBS)
 
 clean:
        $(RM) $(TESTS) *.o
 
-.PHONY: test
+.PHONY: test chain
 test: all
        @echo "\nTLS and QUIC tests:"
        @echo "skipped"
diff --git a/demos/guide/tls-server-block.c b/demos/guide/tls-server-block.c
new file mode 100644 (file)
index 0000000..45246ce
--- /dev/null
@@ -0,0 +1,284 @@
+/*
+ *  Copyright 2024 The OpenSSL Project Authors. All Rights Reserved.
+ *
+ *  Licensed under the Apache License 2.0 (the "License").  You may not use
+ *  this file except in compliance with the License.  You can obtain a copy
+ *  in the file LICENSE in the source distribution or at
+ *  https://www.openssl.org/source/license.html
+ */
+
+/*
+ * NB: Changes to this file should also be reflected in
+ * doc/man7/ossl-guide-tls-server-block.pod
+ */
+
+#include <string.h>
+
+/* Include the appropriate header file for SOCK_STREAM */
+#ifdef _WIN32 /* Windows */
+# include <stdarg.h>
+# include <winsock2.h>
+#else /* Linux/Unix */
+# include <err.h>
+# include <sys/socket.h>
+# include <sys/select.h>
+#endif
+
+#include <openssl/bio.h>
+#include <openssl/ssl.h>
+#include <openssl/err.h>
+
+static const char cache_id[] = "OpenSSL Demo Server";
+
+#ifdef _WIN32
+static const char *progname;
+
+static void vwarnx(const char *fmt, va_list ap)
+{
+    if (progname != NULL)
+        fprintf(stderr, "%s: ", progname);
+    vfprintf(stderr, fmt, ap);
+    putc('\n', stderr);
+}
+
+static void errx(int status, const char *fmt, ...)
+{
+    va_list ap;
+    va_start(ap, fmt);
+    vwarnx(fmt, ap);
+    va_end(ap);
+    exit(status);
+}
+
+static void warnx(const char *fmt, ...)
+{
+    va_list ap;
+    va_start(ap, fmt);
+    vwarnx(fmt, ap);
+    va_end(ap);
+}
+#endif
+
+/* Minimal TLS echo server. */
+int main(int argc, char *argv[])
+{
+    int res = EXIT_FAILURE;
+    long opts;
+    const char *hostport;
+    SSL_CTX *ctx = NULL;
+    BIO *acceptor_bio;
+
+#ifdef _WIN32
+    progname = argv[0];
+#endif
+
+    if (argc != 2)
+        errx(res, "Usage: %s [host:]port", argv[0]);
+    hostport = argv[1];
+
+    /*
+     * An SSL_CTX holds shared configuration information for multiple
+     * subsequent per-client SSL connections.
+     */
+    ctx = SSL_CTX_new(TLS_server_method());
+    if (ctx == NULL) {
+        ERR_print_errors_fp(stderr);
+        errx(res, "Failed to create server SSL_CTX");
+    }
+
+    /*
+     * TLS versions older than TLS 1.2 are deprecated by IETF and SHOULD
+     * be avoided if possible.
+     */
+    if (!SSL_CTX_set_min_proto_version(ctx, TLS1_2_VERSION)) {
+        SSL_CTX_free(ctx);
+        ERR_print_errors_fp(stderr);
+        errx(res, "Failed to set the minimum TLS protocol version");
+    }
+
+#if 0
+    /*
+     * In applications (e.g. SMTP) where most clients are performing
+     * unauthenticated opportunistic TLS it may make sense to set the security
+     * level to 0, allowing weaker encryption parameters, which are still
+     * stronger than a potential cleartext fallback.
+     *
+     * The default security level is 2 (as of OpenSSL 3.2), which is roughly
+     * equivalent to that of 112 bit symmetric keys, or 2048-bit RSA or
+     * finite-field Diffie-Hellman keys.  Notably, non-zero security levels no
+     * longer allow the use of SHA-1 in certificate signatures, key exchange
+     * or in the TLS 1.[01] PRF (so TLS 1.0 and 1.1 require security level 0).
+     */
+    SSL_CTX_set_security_level(ctx, 0);
+#endif
+
+    /*
+     * Tolerate clients hanging up without a TLS "shutdown".  Appropriate in all
+     * application protocols which perform their own message "framing", and
+     * don't rely on TLS to defend against "truncation" attacks.
+     */
+    opts = SSL_OP_IGNORE_UNEXPECTED_EOF;
+
+    /*
+     * Block potential CPU-exhaustion attacks by clients that request frequent
+     * renegotiation.  This is of course only effective if there are existing
+     * limits on initial full TLS handshake or connection rates.
+     */
+    opts |= SSL_OP_NO_RENEGOTIATION;
+
+    /*
+     * Most servers elect to use their own cipher preference rather than that of
+     * the client.
+     */
+    opts |= SSL_OP_CIPHER_SERVER_PREFERENCE;
+
+    /* Apply the selection options */
+    SSL_CTX_set_options(ctx, opts);
+
+    /*
+     * Load the server's certificate *chain* file (PEM format), which includes
+     * not only the leaf (end-entity) server certificate, but also any
+     * intermediate issuer-CA certificates.  The leaf certificate must be the
+     * first certificate in the file.
+     *
+     * In advanced use-cases this can be called multiple times, once per public
+     * key algorithm for which the server has a corresponding certificate.
+     * However, the corresponding private key (see below) must be loaded first,
+     * *before* moving on to the next chain file.
+     *
+     * The requisite files "chain.pem" and "pkey.pem" can be generated by running
+     * "make chain" in this directory.  If the server will be executed from some
+     * other directory, move or copy the files there.
+     */
+    if (SSL_CTX_use_certificate_chain_file(ctx, "chain.pem") <= 0) {
+        SSL_CTX_free(ctx);
+        ERR_print_errors_fp(stderr);
+        errx(res, "Failed to load the server certificate chain file");
+    }
+
+    /*
+     * Load the corresponding private key, this also checks that the private
+     * key matches the just loaded end-entity certificate.  It does not check
+     * whether the certificate chain is valid, the certificates could be
+     * expired, or may otherwise fail to form a chain that a client can validate.
+     */
+    if (SSL_CTX_use_PrivateKey_file(ctx, "pkey.pem", SSL_FILETYPE_PEM) <= 0) {
+        SSL_CTX_free(ctx);
+        ERR_print_errors_fp(stderr);
+        errx(res, "Error loading the server private key file, "
+                  "possible key/cert mismatch???");
+    }
+
+    /*
+     * Servers that want to enable session resumption must specify a cache id
+     * byte array, that identifies the server application, and reduces the
+     * chance of inappropriate cache sharing.
+     */
+    SSL_CTX_set_session_id_context(ctx, (void *)cache_id, sizeof(cache_id));
+    SSL_CTX_set_session_cache_mode(ctx, SSL_SESS_CACHE_SERVER);
+
+    /*
+     * How many client TLS sessions to cache.  The default is
+     * SSL_SESSION_CACHE_MAX_SIZE_DEFAULT (20k in recent OpenSSL versions),
+     * which may be too small or too large.
+     */
+    SSL_CTX_sess_set_cache_size(ctx, 1024);
+
+    /*
+     * Sessions older than this are considered a cache miss even if still in
+     * the cache.  The default is two hours.  Busy servers whose clients make
+     * many connections in a short burst may want a shorter timeout, on lightly
+     * loaded servers with sporadic connections from any given client, a longer
+     * time may be appropriate.
+     */
+    SSL_CTX_set_timeout(ctx, 3600);
+
+    /*
+     * Clients rarely employ certificate-based authentication, and so we don't
+     * require "mutual" TLS authentication (indeed there's no way to know
+     * whether or how the client authenticated the server, so the term "mutual"
+     * is potentially misleading).
+     *
+     * Since we're not soliciting or processing client certificates, we don't
+     * need to configure a trusted-certificate store, so no call to
+     * SSL_CTX_set_default_verify_paths() is needed.  The server's own
+     * certificate chain is assumed valid.
+     */
+    SSL_CTX_set_verify(ctx, SSL_VERIFY_NONE, NULL);
+
+    /*
+     * Create a listener socket wrapped in a BIO.
+     * The first call to BIO_do_accept() initialises the socket
+     */
+    acceptor_bio = BIO_new_accept(hostport);
+    if (acceptor_bio == NULL) {
+        SSL_CTX_free(ctx);
+        ERR_print_errors_fp(stderr);
+        errx(res, "Error creating acceptor bio");
+    }
+
+    BIO_set_bind_mode(acceptor_bio, BIO_BIND_REUSEADDR);
+    if (BIO_do_accept(acceptor_bio) <= 0) {
+        SSL_CTX_free(ctx);
+        ERR_print_errors_fp(stderr);
+        errx(res, "Error setting up acceptor socket");
+    }
+
+    /* Wait for incoming connection */
+    for (;;) {
+        BIO *client_bio;
+        SSL *ssl;
+        unsigned char buf[8192];
+        size_t nread;
+        size_t nwritten;
+        size_t total = 0;
+
+        /* Pristine error stack for each new connection */
+        ERR_clear_error();
+
+        /* Wait for the next client to connect */
+        if (BIO_do_accept(acceptor_bio) <= 0) {
+            /* Client went away before we accepted the connection */
+            continue;
+        }
+
+        /* Pop the client connection from the BIO chain */
+        client_bio = BIO_pop(acceptor_bio);
+        fprintf(stderr, "New client connection accepted\n");
+
+        /* Associate a new SSL handle with the new connection */
+        if ((ssl = SSL_new(ctx)) == NULL) {
+            ERR_print_errors_fp(stderr);
+            warnx("Error creating SSL handle for new connection");
+            BIO_free(client_bio);
+            continue;
+        }
+        SSL_set_bio(ssl, client_bio, client_bio);
+
+        /* Attempt an SSL handshake with the client */
+        if (SSL_accept(ssl) <= 0) {
+            ERR_print_errors_fp(stderr);
+            warnx("Error performing SSL handshake with client");
+            SSL_free(ssl);
+            continue;
+        }
+
+        while (SSL_read_ex(ssl, buf, sizeof(buf), &nread) > 0) {
+            if (SSL_write_ex(ssl, buf, nread, &nwritten) > 0 &&
+                nwritten == nread) {
+                total += nwritten;
+                continue;
+            }
+            warnx("Error echoing client input");
+            break;
+        }
+        fprintf(stderr, "Client connection closed, %zu bytes sent\n", total);
+        SSL_free(ssl);
+    }
+
+    /*
+     * Unreachable placeholder cleanup code, the above loop runs forever.
+     */
+    SSL_CTX_free(ctx);
+    return EXIT_SUCCESS;
+}
index cb9c76b6a3fd44565c8992b0b5c788f845387b29..f84364dba24be8b77fbc777966f0c5b93e51fda2 100644 (file)
@@ -4919,6 +4919,10 @@ DEPEND[html/man7/ossl-guide-tls-introduction.html]=man7/ossl-guide-tls-introduct
 GENERATE[html/man7/ossl-guide-tls-introduction.html]=man7/ossl-guide-tls-introduction.pod
 DEPEND[man/man7/ossl-guide-tls-introduction.7]=man7/ossl-guide-tls-introduction.pod
 GENERATE[man/man7/ossl-guide-tls-introduction.7]=man7/ossl-guide-tls-introduction.pod
+DEPEND[html/man7/ossl-guide-tls-server-block.html]=man7/ossl-guide-tls-server-block.pod
+GENERATE[html/man7/ossl-guide-tls-server-block.html]=man7/ossl-guide-tls-server-block.pod
+DEPEND[man/man7/ossl-guide-tls-server-block.7]=man7/ossl-guide-tls-server-block.pod
+GENERATE[man/man7/ossl-guide-tls-server-block.7]=man7/ossl-guide-tls-server-block.pod
 DEPEND[html/man7/ossl_store-file.html]=man7/ossl_store-file.pod
 GENERATE[html/man7/ossl_store-file.html]=man7/ossl_store-file.pod
 DEPEND[man/man7/ossl_store-file.7]=man7/ossl_store-file.pod
@@ -5135,6 +5139,7 @@ html/man7/ossl-guide-quic-multi-stream.html \
 html/man7/ossl-guide-tls-client-block.html \
 html/man7/ossl-guide-tls-client-non-block.html \
 html/man7/ossl-guide-tls-introduction.html \
+html/man7/ossl-guide-tls-server-block.html \
 html/man7/ossl_store-file.html \
 html/man7/ossl_store.html \
 html/man7/passphrase-encoding.html \
@@ -5279,6 +5284,7 @@ man/man7/ossl-guide-quic-multi-stream.7 \
 man/man7/ossl-guide-tls-client-block.7 \
 man/man7/ossl-guide-tls-client-non-block.7 \
 man/man7/ossl-guide-tls-introduction.7 \
+man/man7/ossl-guide-tls-server-block.7 \
 man/man7/ossl_store-file.7 \
 man/man7/ossl_store.7 \
 man/man7/passphrase-encoding.7 \
index b6c1f955bb4f23b0c26e8c22e8cdb6f67ab1ef94..4fd94f02723f30e225a33e7f13db458805f379d3 100644 (file)
@@ -79,6 +79,8 @@ The pages in the guide are as follows:
 
 =item L<ossl-guide-tls-client-non-block(7)>: Writing a simple nonblocking TLS client
 
+=item L<ossl-guide-tls-server-block(7)>: Writing a simple blocking TLS server
+
 =item L<ossl-guide-quic-introduction(7)>: An introduction to QUIC in OpenSSL
 
 =item L<ossl-guide-quic-client-block(7)>: Writing a simple blocking QUIC client
index 8f72fe3a1c034ea83c3ebaf283f753b1697cc952..34c6b36517e735e8a35bd68e53eb6698d84108b3 100644 (file)
@@ -564,6 +564,9 @@ intermediate CAs, or the issuer is simply unrecognised).
 See L<ossl-guide-tls-client-non-block(7)> to read a tutorial on how to modify
 the client developed on this page to support a nonblocking socket.
 
+See L<ossl-guide-tls-server-block(7)> for a tutorial on how to implement a
+simple TLS server handling one client at a time over a blocking socket.
+
 See L<ossl-guide-quic-client-block(7)> to read a tutorial on how to modify the
 client developed on this page to support QUIC instead of TLS.
 
index fd8184e5929494a4cd75eaea860400bb6ddfdbe6..d7c05f686914b984cff06b2ac3b429af68ffb9d8 100644 (file)
@@ -230,7 +230,8 @@ circumstances where you want them to be different.
 
 It is up to the application programmer to create the B<BIO> objects that are
 needed and supply them to the B<SSL> object. See
-L<ossl-guide-tls-client-block(7)> for further information.
+L<ossl-guide-tls-client-block(7)> and L<ossl-guide-tls-server-block(7)> for
+usage examples.
 
 Finally, an endpoint can establish a "session" with its peer. The session holds
 various TLS parameters about the connection between the client and the server.
@@ -295,15 +296,18 @@ object.
 
 =head1 FURTHER READING
 
-See L<ossl-guide-tls-client-block(7)> to see an example of applying these
+See L<ossl-guide-tls-client-block(7)> for an example of how to apply these
 concepts in order to write a simple TLS client based on a blocking socket.
+See L<ossl-guide-tls-server-block(7)> for an example of how to apply these
+concepts in order to write a simple TLS server handling one client at a time
+over a blocking socket.
 See L<ossl-guide-quic-introduction(7)> for an introduction to QUIC in OpenSSL.
 
 =head1 SEE ALSO
 
 L<ossl-guide-introduction(7)>, L<ossl-guide-libraries-introduction(7)>,
 L<ossl-guide-libssl-introduction(7)>, L<ossl-guide-tls-client-block(7)>,
-L<ossl-guide-quic-introduction(7)>
+L<ossl-guide-tls-server-block(7)>, L<ossl-guide-quic-introduction(7)>
 
 =head1 COPYRIGHT
 
diff --git a/doc/man7/ossl-guide-tls-server-block.pod b/doc/man7/ossl-guide-tls-server-block.pod
new file mode 100644 (file)
index 0000000..e6bd2b0
--- /dev/null
@@ -0,0 +1,329 @@
+=pod
+
+=begin comment
+
+NB: Changes to the source code samples in this file should also be reflected in
+demos/guide/tls-server-block.c
+
+=end comment
+
+=head1 NAME
+
+ossl-guide-tls-server-block
+- OpenSSL Guide: Writing a simple blocking TLS server
+
+=head1 SIMPLE BLOCKING TLS SERVER EXAMPLE
+
+This page will present various source code samples demonstrating how to write a
+simple, non-concurrent, TLS "echo" server application which accepts one client
+connection at a time, echoing input from the client back to the same client.
+Once the current client disconnects, the next client connection is accepted.
+
+Both the acceptor socket and client connections are "blocking".  A more typical
+server might use nonblocking sockets with an event loop and callbacks for I/O
+events.
+
+The complete source code for this example blocking TLS server is available in
+the B<demos/guide> directory of the OpenSSL source distribution in the file
+B<tls-server-block.c>. It is also available online at
+L<https://github.com/openssl/openssl/blob/master/demos/guide/tls-server-block.c>.
+
+We assume that you already have OpenSSL installed on your system; that you
+already have some fundamental understanding of OpenSSL concepts and TLS (see
+L<ossl-guide-libraries-introduction(7)> and L<ossl-guide-tls-introduction(7)>);
+and that you know how to write and build C code and link it against the
+libcrypto and libssl libraries that are provided by OpenSSL. It also assumes
+that you have a basic understanding of TCP/IP and sockets.
+
+=head2 Creating the SSL_CTX and SSL objects
+
+The first step is to create an B<SSL_CTX> object for our server. We use the
+L<SSL_CTX_new(3)> function for this purpose. We could alternatively use
+L<SSL_CTX_new_ex(3)> if we want to associate the B<SSL_CTX> with a particular
+B<OSSL_LIB_CTX> (see L<ossl-guide-libraries-introduction(7)> to learn about
+B<OSSL_LIB_CTX>). We pass as an argument the return value of the function
+L<TLS_server_method(3)>. You should use this method whenever you are writing a
+TLS server. This method will automatically use TLS version negotiation to select
+the highest version of the protocol that is mutually supported by both the
+server and the client.
+
+    /*
+     * An SSL_CTX holds shared configuration information for multiple
+     * subsequent per-client SSL connections.
+     */
+    ctx = SSL_CTX_new(TLS_server_method());
+    if (ctx == NULL) {
+        ERR_print_errors_fp(stderr);
+        errx(res, "Failed to create server SSL_CTX");
+    }
+
+We would also like to restrict the TLS versions that we are willing to accept to
+TLSv1.2 or above. TLS protocol versions earlier than that are generally to be
+avoided where possible. We can do that using
+L<SSL_CTX_set_min_proto_version(3)>:
+
+    /*
+     * TLS versions older than TLS 1.2 are deprecated by IETF and SHOULD
+     * be avoided if possible.
+     */
+    if (!SSL_CTX_set_min_proto_version(ctx, TLS1_2_VERSION)) {
+        SSL_CTX_free(ctx);
+        ERR_print_errors_fp(stderr);
+        errx(res, "Failed to set the minimum TLS protocol version");
+    }
+
+Next we configure some option flags, see L<SSL_CTX_set_options(3)> for details:
+
+    /*
+     * Tolerate clients hanging up without a TLS "shutdown".  Appropriate in all
+     * application protocols which perform their own message "framing", and
+     * don't rely on TLS to defend against "truncation" attacks.
+     */
+    opts = SSL_OP_IGNORE_UNEXPECTED_EOF;
+
+    /*
+     * Block potential CPU-exhaustion attacks by clients that request frequent
+     * renegotiation.  This is of course only effective if there are existing
+     * limits on initial full TLS handshake or connection rates.
+     */
+    opts |= SSL_OP_NO_RENEGOTIATION;
+
+    /*
+     * Most servers elect to use their own cipher preference rather than that of
+     * the client.
+     */
+    opts |= SSL_OP_CIPHER_SERVER_PREFERENCE;
+
+    /* Apply the selection options */
+    SSL_CTX_set_options(ctx, opts);
+
+Servers need a private key and certificate.  Though anonymous ciphers (no
+server certificate) are possible in TLS 1.2, they are rarely applicable, and
+are not currently defined for TLS 1.3.  Additional intermediate issuer CA
+certificates are often also required, and both the server (end-entity or EE)
+certificate and the issuer ("chain") certificates are most easily configured in
+a single "chain file".  Below we load such a chain file (the EE certificate
+must appear first), and then load the corresponding private key, checking that
+it matches the server certificate.  No checks are performed to check the
+integrity of the chain (CA signatures or certificate expiration dates, for
+example).
+
+    /*
+     * Load the server's certificate *chain* file (PEM format), which includes
+     * not only the leaf (end-entity) server certificate, but also any
+     * intermediate issuer-CA certificates.  The leaf certificate must be the
+     * first certificate in the file.
+     *
+     * In advanced use-cases this can be called multiple times, once per public
+     * key algorithm for which the server has a corresponding certificate.
+     * However, the corresponding private key (see below) must be loaded first,
+     * *before* moving on to the next chain file.
+     */
+    if (SSL_CTX_use_certificate_chain_file(ctx, "chain.pem") <= 0) {
+        SSL_CTX_free(ctx);
+        ERR_print_errors_fp(stderr);
+        errx(res, "Failed to load the server certificate chain file");
+    }
+
+    /*
+     * Load the corresponding private key, this also checks that the private
+     * key matches the just loaded end-entity certificate.  It does not check
+     * whether the certificate chain is valid, the certificates could be
+     * expired, or may otherwise fail to form a chain that a client can validate.
+     */
+    if (SSL_CTX_use_PrivateKey_file(ctx, "pkey.pem", SSL_FILETYPE_PEM) <= 0) {
+        SSL_CTX_free(ctx);
+        ERR_print_errors_fp(stderr);
+        errx(res, "Error loading the server private key file, "
+                  "possible key/cert mismatch???");
+    }
+
+Next we enable session caching, which makes it possible for clients to more
+efficiently make additional TLS connections after completing an initial full
+TLS handshake.  With TLS 1.3, session resumption typically still performs a fresh
+key agreement, but the certificate exchange is avoided.
+
+    /*
+     * Servers that want to enable session resumption must specify a cache id
+     * byte array, that identifies the server application, and reduces the
+     * chance of inappropriate cache sharing.
+     */
+    SSL_CTX_set_session_id_context(ctx, (void *)cache_id, sizeof(cache_id));
+    SSL_CTX_set_session_cache_mode(ctx, SSL_SESS_CACHE_SERVER);
+
+    /*
+     * How many client TLS sessions to cache.  The default is
+     * SSL_SESSION_CACHE_MAX_SIZE_DEFAULT (20k in recent OpenSSL versions),
+     * which may be too small or too large.
+     */
+    SSL_CTX_sess_set_cache_size(ctx, 1024);
+
+    /*
+     * Sessions older than this are considered a cache miss even if still in
+     * the cache.  The default is two hours.  Busy servers whose clients make
+     * many connections in a short burst may want a shorter timeout, on lightly
+     * loaded servers with sporadic connections from any given client, a longer
+     * time may be appropriate.
+     */
+    SSL_CTX_set_timeout(ctx, 3600);
+
+Most servers, including this one, do not solicit client certificates.  We
+therefore do not need a "trust store" and allow the handshake to complete even
+when the client does not present a certificate.  Note: Even if a client did
+present a trusted ceritificate, for it to be useful, the server application
+would still need custom code to use the verified identity to grant nondefault
+access to that particular client.  Some servers grant access to all clients
+with certificates from a private CA, this then requires processing of
+certificate revocation lists to deauthorise a client.  It is often simpler and
+more secure to instead keep a list of authorised public keys.
+
+Though this is the default setting, we explicitly call the
+L<SSL_CTX_set_verify(3)> function and pass the B<SSL_VERIFY_NONE> value to it.
+The final argument to this function is a callback that you can optionally
+supply to override the default handling for certificate verification. Most
+applications do not need to do this so this can safely be set to NULL to get
+the default handling.
+
+    /*
+     * Clients rarely employ certificate-based authentication, and so we don't
+     * require "mutual" TLS authentication (indeed there's no way to know
+     * whether or how the client authenticated the server, so the term "mutual"
+     * is potentially misleading).
+     *
+     * Since we're not soliciting or processing client certificates, we don't
+     * need to configure a trusted-certificate store, so no call to
+     * SSL_CTX_set_default_verify_paths() is needed.  The server's own
+     * certificate chain is assumed valid.
+     */
+    SSL_CTX_set_verify(ctx, SSL_VERIFY_NONE, NULL);
+
+That is all the setup that we need to do for the B<SSL_CTX>.  Next we create an
+acceptor BIO on which to accept client connections.  This just records the
+intended port (and optional "host:" prefix), without actually creating the
+socket.  This delayed processing allows the programmer to specify additional
+behaviours before the listening socket is actually created.
+
+    /*
+     * Create a listener socket wrapped in a BIO.
+     * The first call to BIO_do_accept() initialises the socket
+     */
+    acceptor_bio = BIO_new_accept(hostport);
+    if (acceptor_bio == NULL) {
+        SSL_CTX_free(ctx);
+        ERR_print_errors_fp(stderr);
+        errx(res, "Error creating acceptor bio");
+    }
+
+Servers almost always want to use the "SO_REUSEADDR" option to avoid startup
+failures if there are still lingering client connections, so we do that before
+making the B<first> call to L<BIO_do_accept(3)> which creates the listening
+socket, without accepting a client connection.  Subsequent calls to the same
+function will accept new connections.
+
+    BIO_set_bind_mode(acceptor_bio, BIO_BIND_REUSEADDR);
+    if (BIO_do_accept(acceptor_bio) <= 0) {
+        SSL_CTX_free(ctx);
+        ERR_print_errors_fp(stderr);
+        errx(res, "Error setting up acceptor socket");
+    }
+
+=head2 Server loop
+
+The server now enters a "forever" loop handling one client connection at a
+time.  Before each connection we clear the OpenSSL error stack, so that any
+error reports are related to just the new connection.
+
+    /* Pristine error stack for each new connection */
+    ERR_clear_error();
+
+At this point the server blocks to accept the next client:
+
+    /* Wait for the next client to connect */
+    if (BIO_do_accept(acceptor_bio) <= 0) {
+        /* Client went away before we accepted the connection */
+        continue;
+    }
+
+On success the accepted client connection has been wrapped in a fresh BIO and
+pushed onto the end of the acceptor BIO chain.  We pop it off returning the
+acceptor BIO to its initial state.
+
+    /* Pop the client connection from the BIO chain */
+    client_bio = BIO_pop(acceptor_bio);
+    fprintf(stderr, "New client connection accepted\n");
+
+Next, we create an B<SSL> object by calling the B<SSL_new(3)> function and
+passing the B<SSL_CTX> we created as an argument.  The client connection BIO is
+configured as the I/O conduit for this SSL handle.  SSL_set_bio transfers
+ownership of the BIO or BIOs involved (our B<client_bio>) to the SSL handle.
+
+    /* Associate a new SSL handle with the new connection */
+    if ((ssl = SSL_new(ctx)) == NULL) {
+        ERR_print_errors_fp(stderr);
+        warnx("Error creating SSL handle for new connection");
+        BIO_free(client_bio);
+        continue;
+    }
+    SSL_set_bio(ssl, client_bio, client_bio);
+
+And now we're ready to attempt the SSL handshake.  With a blocking socket
+OpenSSL will perform all the read and write operations required to complete the
+handshake (or detect and report a failure) before returning.
+
+    /* Attempt an SSL handshake with the client */
+    if (SSL_accept(ssl) <= 0) {
+        ERR_print_errors_fp(stderr);
+        warnx("Error performing SSL handshake with client");
+        SSL_free(ssl);
+        continue;
+    }
+
+With the handshake complete, the server loops echoing client input back to the
+client:
+
+    while (SSL_read_ex(ssl, buf, sizeof(buf), &nread) > 0) {
+        if (SSL_write_ex(ssl, buf, nread, &nwritten) > 0 &&
+            nwritten == nread) {
+            total += nwritten;
+            continue;
+        }
+        warnx("Error echoing client input");
+        break;
+    }
+
+Once the client closes its connection, we report the number of bytes sent to
+B<stderr> and free the SSL handle, which also frees the B<client_bio> and
+closes the underlying socket.
+
+    fprintf(stderr, "Client connection closed, %zu bytes sent\n", total);
+    SSL_free(ssl);
+
+The server is now ready to accept the next client connection.
+
+=head2 Final clean up
+
+If the server could somehow manage to break out of the infinite loop, and
+be ready to exit, it would first deallocate the constructed B<SSL_CTX>.
+
+    /*
+     * Unreachable placeholder cleanup code, the above loop runs forever.
+     */
+    SSL_CTX_free(ctx);
+    return EXIT_SUCCESS;
+
+=head1 SEE ALSO
+
+L<ossl-guide-introduction(7)>, L<ossl-guide-libraries-introduction(7)>,
+L<ossl-guide-libssl-introduction(7)>, L<ossl-guide-tls-introduction(7)>,
+L<ossl-guide-tls-client-non-block(7)>, L<ossl-guide-quic-client-block(7)>
+
+=head1 COPYRIGHT
+
+Copyright 2024 The OpenSSL Project Authors. All Rights Reserved.
+
+Licensed under the Apache License 2.0 (the "License").  You may not use
+this file except in compliance with the License.  You can obtain a copy
+in the file LICENSE in the source distribution or at
+L<https://www.openssl.org/source/license.html>.
+
+=cut