Title : Add a TLS layer to your Gopher server
Author: Solène
Date  : 07 March 2019
Tags  : gopher openbsd


In this article I will explain how to setup a gopher server supporting
TLS. Gopher TLS support is not "official" as there is currently no RFC
to define it. It has been recently chose by the community how to make
it work, while keeping compatibility with old servers / clients.

The way to do it is really simple. 

Client A tries to connects to Server B, Client A tries TLS handshake,
if Server B answers correctly to the TLS handshakes, then Client A
sends the gopher request and Server B answers the gopher requests. If
Server B doesn't understand the TLS handshakes, then it will probably
output a regular gopher page, then this is throwed and Client A
retries the connection using plaintext gopher and Server B answers the
gopher request.

This is easy to achieve because gopher protocol doesn't require the
server to send anything to the client before the client sends its

The way to add the TLS layer and the dispatching can be achieved using
**sslh** and **relayd**. You could use haproxy instead of relayd, but
the latter is in OpenBSD base system so I will use it. Thanks parazyd
for sharing about sslh for this use case.

**sslh** is a protocol demultiplexer, it listens on a port, and
depending on what it receives, it will try to guess the protocol used
by the client and send it to the according backend. It's first purpose
was to make ssh available on port 443 while still having https daemon
working on that server.

Here is a schema of the setup

                            +→ relayd for TLS + forwarding
                            ↑                        ↓
                            ↑ tls?                   ↓
    client -> sslh TCP 70 → +                        ↓
                            ↓ not tls                ↓
                            ↓                        ↓
                            +→ → → → → → → gopher daemon on localhost

This method allows to wrap any server to make it TLS compatible. The
best case would be to have TLS compatibles servers which do all the
work without requiring sslh and something to add the TLS. But it's
currently a way to show TLS for gopher is real.

## Relayd

The relayd(1) part is easy, you first need a x509 certificate for the
TLS part, I will not explain here how to get one, there are already
plenty of how-to and one can use let's encrypt with acme-client(1) to
get one on OpenBSD.

We will write our configuration in **/etc/relayd.conf**

    log connection
    relay "gopher" {
        listen on port 7000 tls
        forward to port 7070

In this example, relayd listens on port 7000 and our gopher daemon
listens on port 7070. According to relayd.conf(5), relayd will look
for the certificate at the following places:
`/etc/ssl/private/$LISTEN_ADDRESS:$PORT.key` and
`/etc/ssl/$LISTEN_ADDRESS:$PORT.crt`, with the current example you
will need the files: /etc/ssl/private/ and

relayd can be enabled and started using rcctl:

    # rcctl enable relayd
    # rcctl start relayd

## Gopher daemon

Choose your favorite gopher daemon, I recommend geomyidae but any
other valid daemon will work, just make it listening on the correct
address and port combination.

    # pkg_add geomyidae
    # rcctl enable geomyidae
    # rcctl set geomyidae flags -p 7070
    # rcctl start geomyidae


We will use sslh_fork (but sslh_select would be valid too, they have
differents pros/cons). The `--tls` parameters tells where to forward a
TLS connection while `--ssh` will forward to the gopher daemon. This
is so because the protocol ssh is already configured within sslh and
acts exactly like a gopher daemon: the client doesn't expect the
server to be the first sending data.

    # pkg_add sslh
    # rcctl enable sslh_fork
    # rcctl set sslh_fork flags --tls --ssh -p
    # rcctl start sslh_fork

## Client

You can easily test if this works using openssl to connect by hand to the port 70

    $ openssl s_client -connect
You should see a lot of output, which is the TLS handshake, then you
can send a gopher request like "/" and you should get a result. Using
telnet on the same address and port should give the same result.

My gopher client **clic** already supports gopher TLS and is available
at git://bitreich.org/clic and only requires the ecl common lisp
interpreter to compile.