Opal ==== Opal is a Gemini server written in Rust. It is meant to serve dynamic content through CGI and does not serve static files. In a way, it is a companion project to the [Agate][agate] Gemini server which only serves static files. The project's goals are: - Focus on a small set of features (around CGI) but do them correctly. - Be nice with old/stupid hardware (TLS 1.2 is OK, be efficient, etc). - Don't add features unless someone actively wants them in. - Try to keep resources (binary size, memory, etc) under tight control. Opal uses the `openssl` Rust bindings, which work with OpenSSL and LibreSSL, so it should work properly on those platforms. I currently only support Linux systems but if there is interest in other platforms let's do this together! Opal is licensed as GPLv3. [agate]: https://github.com/mbrubeck/agate/ Usage ----- Use `opal -h` to get a list of options. There is no config file, every setting can be configured from the command line. - `-a, --address
`: specify the address(es) to listen to. - `-c, --cert `: server certificate path. - `-k, --key `: server private key path. - `-r, --root-path `: path to CGI scripts root. - `-e, --env `: additional environment variables for CGI scripts; this option can be used multiple times. You can specify multiple addresses to listen to by using several `-a` options. Note that if you just want to listen to both IPv4 and IPv6 on any interface, listening only on `[::]:1965` should suffice for systems with dual-stack enabled (default on many Linux systems, maybe not BSD). CGI support ----------- Opal tries to implement [RFC 3875][rfc3875] (CGI 1.1) and provides all the required environment variables to processes. It also add a bunch of Gemini specific variables, like a lot of other servers (Gemserv, Gmid, Gmnisrv, …). The environment for the subprocess is cleaned and should only contain those variables. [rfc3875]: https://datatracker.ietf.org/doc/html/rfc3875 | Presence | Variable | Description | |-------------|------------------------|------------------------------------------------------| | always | GATEWAY_INTERFACE | "CGI/1.1" | | always | REMOTE_ADDR | Peer IP address and port | | always | REMOTE_HOST | Same as REMOTE_ADDR | | always | REQUEST_METHOD | Empty string for compatibility | | always | SCRIPT_NAME | Script name part of the URL path | | always | SERVER_NAME | Hostname used for SNI | | always | SERVER_PORT | Port where the request has been received | | always | SERVER_PROTOCOL | "GEMINI" | | always | SERVER_SOFTWARE | "opal/version", e.g. "opal/0.1.0" | | always | GEMINI_DOCUMENT_ROOT | CGI root | | always | GEMINI_SCRIPT_FILENAME | CGI script that matched the URL path | | always | GEMINI_URL | Full URL, normalized | | always | GEMINI_URL_PATH | URL path, normalized | | always | TLS_VERSION | TLS version, e.g. "TLSv1.3" | | always | TLS_CIPHER | TLS cipher suite, e.g. "TLS_AES_256_GCM_SHA384" | | optional | PATH_INFO | Path passed to the CGI process after the script name | | optional | QUERY_STRING | Query string if provided, still URL-encoded | | client cert | AUTH_TYPE | "CERTIFICATE" if one is provided | | client cert | REMOTE_USER | Subject common name (empty if unavailable) | | client cert | TLS_CLIENT_ISSUER | Issuer common name (empty if unavailable) | | client cert | TLS_CLIENT_HASH | Digest of the DER reprensetation of the cert | | client cert | TLS_CLIENT_NOT_AFTER | Validity end date, RFC 3339 format | | client cert | TLS_CLIENT_NOT_BEFORE | Validity start date, RFC 3339 format | Opal does not provide `CONTENT_LENGTH`, `CONTENT_TYPE`, `REMOTE_IDENT` because they do not make much sense in Gemini. `PATH_TRANSLATED` is also not implemented by pure laziness. The `TLS_CLIENT_HASH` is a string that starts with "SHA256:" followed by the SHA256 digest of the DER representation of the client certificate, as an uppercase hex-string. It can be a bit confusing which variable represent what data, especially those related to the URL and the path. Take the following request as example: `gemini://localhost/env/sub1/sub2?search=élément`. Suppose our CGI root, in `/cgi`, contains the executable script named `env`. The variables will be: ``` GEMINI_DOCUMENT_ROOT=/cgi GEMINI_SCRIPT_FILENAME=/cgi/env GEMINI_URL=gemini://localhost/env/sub1/sub2?search=%C3%A9l%C3%A9ment GEMINI_URL_PATH=/env/sub1/sub2 SCRIPT_NAME=/env PATH_INFO=/sub1/sub2 QUERY_STRING=search=%C3%A9l%C3%A9ment ``` Roadmap ------- Things to consider: - Support SCGI; a bit more complex but should save resources on smol hardware. - Chroot; quite cheap and can bring a bit of peace of mind. Things that probably won't be considered: - Serve static files; so many other servers to that correctly already! - Any kind of security mechanism that is not properly motivated. - FastCGI; come on…