Skip to content

bhavanki/doppio

Repository files navigation

Doppio

Doppio logo

A Gemini server.

Java CI with Maven CodeQL

Trying It Out

$ docker run --rm -d -p 1965:1965 -e DOPPIO_HOSTNAME=example.com \
  bhavanki/doppio:latest

Use a hostname that resolves to the Docker host. Then, point your favorite Gemini client to your host (e.g., gemini://example.com/) to see the default welcome page. Welcome to Geminispace!

Building

Use Apache Maven.

$ mvn package

The result is a shaded executable JAR.

Running

Certificate Generation

Gemini requires a server certificate. You can generate one yourself, or let Doppio generate a temporary, non-persisted certificate that expires after one day.

This example keytool command generates an elliptic curve (EC) key and corresponding certificate.

$ keytool -genkeypair -keystore doppio.jks -storepass doppio -keyalg EC

Alternatively, use openssl (again, this example generates an EC key).

$ openssl ecparam -genkey -name prime256v1 -out doppio.key
$ openssl req -new -key doppio.key -x509 -out doppio.crt
$ openssl pkcs12 -export -inkey doppio.key -in doppio.crt -out doppio.p12

Use a common name that matches your hostname.

Server Properties File

Then, create a server properties file. Use doppio-example.yaml or doppio-example.properties as an example. Set the following properties:

  • host must match the name on the server certificate.
  • keystore must point to the keystore (e.g., JKS or PKCS#12 file) containing the server's private key, if not using a temporary certificate.
  • keystorePassword must contain the password for the keystore, if not using a temporary certificate.

When using a temporary certificate, do not set keystore and keystorePassword.

When running Doppio in a container, set path server properties such as keystore to point to paths in the container, not on the host.

Running Directly

Run the JAR.

$ java -jar target/doppio-*.jar doppio.yaml

Running via Docker

Run the Doppio Docker image.

  • Mount your server properties file to /etc/doppio/doppio.yaml.
  • The image is set up by default to generate a temporary certificate. To use a persistent one, mount it to the path specified by keystore in the server properties file.
$ docker run --rm -d -p 1965:1965 \
  -e DOPPIO_HOSTNAME=example.com \
  --mount type=bind,src=/host/path/to/doppio.yaml,dst=/etc/doppio/doppio.yaml \
  --mount type=bind,src=/host/path/to/keystore.jks,dst=/etc/doppio/keystore.jks \
  bhavanki/doppio:latest

Ports

Doppio listens on two ports:

  • a server port for Gemini connections: configuration property port, default 1965
  • a control port for control commands: configuration property controlPort, default 31965

Connections to the control port may only be made from the loopback address. If the controlPort configuration property is set to -1, then Doppio does not open a control port.

The control port is not supported when running Doppio in a container.

Temporary Certificate Caveat

Doppio uses "internal proprietary API" code from the sun.security package to generate temporary server certificates. So, this feature might not work on JDKs besides the Oracle JDK and OpenJDK.

Control Commands

A control command is a single line of text.

  • shutdown: gracefully shuts down the server

An easy way to send control commands is with netcat.

$ nc localhost 31965
shutdown

Access Log

A log directory may be configured with the logDir configuration property. When the property is set, Doppio writes an access log to a file "access.log" in that directory. The log follows the Apache Common Log Format (CLF), with the following minor caveats.

  • The second field in each line, the RFC 1413 client identity, is never provided.
  • The remote user is the subject DN of the client's authenticated certificate. The value is URL-encoded, primarily to avoid spaces in the logged value.

The Doppio Docker image establishes /var/log/doppio as a volume for logging.

Static File Support

Place static resources in the configured root directory. The Doppio Docker image establishes /var/gemini as a volume for static resources. To bind mount a directory on the Docker host to that location:

... --mount type=bind,src=/host/path/to/root,dst=/var/gemini ...

By default, resource content is streamed to clients exactly as it is in its resource. To force the conversion of line endings in text resources to canonical form (CRLF or "\r\n"), set the forceCanonicalText server property to true.

Content type is detected using Java's built-in mechanism, with additional support for recognizing a configurable set of file suffixes for text/gemini resources (defaults .gmi and .gemini).

Charset for text resources is optionally detected using juniversalchardet. By default, charset detection is disabled. Caveats:

  • Charset detection is always based on heuristics, so the detector may guess incorrectly sometimes.
  • Detection requires reading the text resource an additional time before it is served, negatively affecting performance.

When a directory is requested, Doppio looks for an index file, ending with any supported filename suffix for text/gemini files (e.g., index.gmi), and returns the first one it finds. Otherwise, it returns a 51 (not found) response.

Favicon Support

Set the favicon server property to an emoji to configure a favicon. Doppio then serves a favicon document in accordance with the favicon RFC. If the property is not set, a literal favicon document may still be created at and served from /favicon.txt.

CGI Support

Place CGI scripts in the configured CGI directory. If no directory is configured, Doppio does not run scripts, even if they reside in a directory that seems like a valid CGI directory (e.g., some "cgi-bin" directory).

Doppio tries to follow RFC 3875 in its CGI support. Here are salient limitations and variations from the standard in the implementation.

  • UTF-8 is expected for script response headers.
  • The following meta-variables (environment variables) are not supported, since they are not applicable to Gemini: CONTENT_LENGTH, CONTENT_TYPE. The REMOTE_IDENT meta-variable is not implemented. REQUEST_METHOD is set to an empty string.
  • Request bodies are not supported, since Gemini does not support them. The URI query string and extra path information are the only input mechanisms.
  • NPH (Non-Parsed Header) scripts are not supported.
  • Doppio does not check if a client redirect response is well-formed in terms of response headers.
  • Client redirect responses with document are not supported, because Gemini does not permit response bodies in redirects.
  • Status codes 20 and 30 are used as defaults for successful responses and redirects, instead of (HTTP) 200 and 302. The "bad request" status code is 59 instead of (HTTP) 400.

Local redirects are supported, up to the maximum per request configured with the maxLocalRedirects server property.

The following TLS-related meta-variables are also supported. Those in the middle column are always set when applicable. Those in the right column, derived from Apache mod_ssl, are only set when the useModSslCgiMetaVars server configuration property is set to true.

definition TLS meta-variable SSL (mod_ssl) meta-variable
cipher name TLS_CIPHER SSL_CIPHER
TLS protocol version TLS_VERSION SSL_PROTOCOL
session ID TLS_SESSION_ID SSL_SESSION_ID
client certificate serial number TLS_CLIENT_SERIAL SSL_CLIENT_M_SERIAL
client certificate version TLS_CLIENT_VERSION SSL_CLIENT_M_VERSION
client certificate fingerprint TLS_CLIENT_HASH -
client certificate issuer DN TLS_CLIENT_ISSUER SSL_CLIENT_I_DN
client certificate subject DN TLS_CLIENT_SUBJECT SSL_CLIENT_S_DN
client certificate validity start timestamp TLS_CLIENT_NOT_BEFORE SSL_CLIENT_V_START
client certificate validity end timestamp TLS_CLIENT_NOT_AFTER SSL_CLIENT_V_END
client certificate validity remaining time, in days TLS_CLIENT_REMAIN SSL_CLIENT_V_REMAIN

Text output from CGI scripts is subject to line ending conversion if the forceCanonicalText server property is set to true. Because CGI scripts emit their own response headers, Doppio does not detect content type or charset for them.

Secure Domains

Note: This feature replaces "secure directories" in earlier versions of Doppio.

A secure domain is a combination of:

  • a resource directory
  • an optional truststore

Place resources (static or CGI) which should require client authentication in the directory of one of the configured secure domains. If a secure domain has a truststore, then Doppio authorizes client certificates against that truststore. So, the truststore may contain individual self-signed certificates, or the root certificate for trusted authorities, or both.

If a secure domain has no truststore, then Doppio still requires client authentication, but accepts any client certificate.

Doppio validates a client certificate (e.g., checks its valid date range) only when authentication is required for a requested resource.

CGI scripts may roll their own client authentication and authorization code instead of relying on a secure domain. In this case, however, the remote user is not available in the access log for a request, since Doppio is not performing the authentication itself.

To add a certificate to a new or existing truststore, use the example script or keytool directly:

$ keytool -importcert -file trustedcert.pem -alias trustedcert \
  -keystore domaints.jks

Automatic Atom Feed Generation

Doppio can automatically generate an Atom feed for Gemini index pages that follow the Subscribing to Gemini pages specification. Enable this for pages by listing their paths relative to the server root in the feedPages configuration property.

feedPages:
  - gemlog/index.gmi
  - gemlog2/index.gmi

When Doppio receives a request for an "atom.xml" file in a directory that matches a feed page, it returns the generated feed content.

  • Requests for the feed page itself still work.
  • Only one feed per directory is supported. If there are multiple feed pages listed for a single directory, the first one listed wins.
  • Automatic feed generation for CGI is not supported.
  • A feed page must use the UTF-8 charset.

License

GNU Affero General Public License v3

The example systemd service file and example truststore script are separately available under the MIT License.