initial commit

This commit is contained in:
Jan Wolff 2020-05-14 21:45:59 +02:00
commit d9e5170676
11 changed files with 968 additions and 0 deletions

2
.gitattributes vendored Normal file
View file

@ -0,0 +1,2 @@
.gitattributes export-ignore
.gitignore export-ignore

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
/target

98
Cargo.lock generated Normal file
View file

@ -0,0 +1,98 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
[[package]]
name = "autocfg"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d"
[[package]]
name = "bitflags"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
[[package]]
name = "cc"
version = "1.0.53"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "404b1fe4f65288577753b17e3b36a04596ee784493ec249bf81c7f2d2acd751c"
[[package]]
name = "cfg-if"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
[[package]]
name = "foreign-types"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
dependencies = [
"foreign-types-shared",
]
[[package]]
name = "foreign-types-shared"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
[[package]]
name = "lazy_static"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "libc"
version = "0.2.70"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3baa92041a6fec78c687fa0cc2b3fae8884f743d672cf551bed1d6dac6988d0f"
[[package]]
name = "openssl"
version = "0.10.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cee6d85f4cb4c4f59a6a85d5b68a233d280c82e29e822913b9c8b129fbf20bdd"
dependencies = [
"bitflags",
"cfg-if",
"foreign-types",
"lazy_static",
"libc",
"openssl-sys",
]
[[package]]
name = "openssl-sys"
version = "0.9.56"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f02309a7f127000ed50594f0b50ecc69e7c654e16d41b4e8156d1b3df8e0b52e"
dependencies = [
"autocfg",
"cc",
"libc",
"pkg-config",
"vcpkg",
]
[[package]]
name = "pkg-config"
version = "0.3.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05da548ad6865900e60eaba7f589cc0783590a92e940c26953ff81ddbab2d677"
[[package]]
name = "sheldon_director"
version = "0.1.0"
dependencies = [
"openssl",
]
[[package]]
name = "vcpkg"
version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3fc439f2794e98976c88a2a2dafce96b930fe8010b0a256b3c2199a773933168"

9
Cargo.toml Normal file
View file

@ -0,0 +1,9 @@
[package]
name = "sheldon_director"
version = "0.1.0"
authors = ["Klockenschooster <slartibartfast@klockenschooster.de>"]
edition = "2018"
[dependencies]
openssl = { version = "0.10" }

20
LICENSE.md Normal file
View file

@ -0,0 +1,20 @@
Copyright (c) 2020 Jan Wolff
This software is provided 'as-is', without any express or implied warranty. In
no event will the authors be held liable for any damages arising from the use
of this software.
Permission is granted to anyone to use this software for any purpose, including
commercial applications, and to alter it and redistribute it freely, subject to
the following restrictions:
1. The origin of this software must not be misrepresented; you must not claim
that you wrote the original software. If you use this software in a product,
an acknowledgment in the product documentation would be appreciated but is
not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.

10
README.md Normal file
View file

@ -0,0 +1,10 @@
Sheldon Director
================
A Gemini Server written in Rust.
Why "Sheldon Director"?
-----------------------
Because it is the real name of that villain in _Kim Possible_ who went by the
pseudonym "Gemini". (https://kimpossible.fandom.com/wiki/Gemini)

32
doc/cert.pem Normal file
View file

@ -0,0 +1,32 @@
-----BEGIN CERTIFICATE-----
MIIFlTCCA32gAwIBAgIUPqRiFp5Gc1b2Frbab/WcQ4Xdg74wDQYJKoZIhvcNAQEL
BQAwWTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM
GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDESMBAGA1UEAwwJbG9jYWxob3N0MCAX
DTIwMDUxNDE4NDI0NVoYDzIxMjAwNDIwMTg0MjQ1WjBZMQswCQYDVQQGEwJBVTET
MBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQ
dHkgTHRkMRIwEAYDVQQDDAlsb2NhbGhvc3QwggIiMA0GCSqGSIb3DQEBAQUAA4IC
DwAwggIKAoICAQCqDaso0s8+Rt9fILlF7koLcYJCjs+MjB0kpS7itdJj1YXW6a5l
zIRP9bMSSftVmHGR1qFo6suKbVEayCkTrw3PU6w7iW7CMiRK6XL1Ix4B5BYEymKx
ihA1geRQZkKL8UHfudnpq7+yTqwKXdOu4KrRUbMdIiOlaIhhK4/DZk8gxmZpiv81
xqohtyVsr7Z8yKkSoX7TySPvn3qx83BgeoGSqe/pX4quKVKj8ylkfdyKlaVPLGvS
ktTfr2rUYtRz1+xOEEYxUdaZBRZ2/G1QaeELWimwPTAmg9KWCfA0/6TWMDvh2Btq
J0qJtcu43QVFf4AxoUQ/S/Mf4Zmc/3gfOcEHrjnNube0xaUfMdjErhwPmXKc0E78
hd9jlkJaICdGSEC19DZBt8LinyTMr6+2E/YyD+bl+LUL8uI7m9zFgJYlLXUeZvab
PvcxUq72pP6/j+9fGlT2JXSZ6bIDXyDbgz0QmZ5Rh+q5fwSAtfkSE16cx87aOM48
iI98c4joN9FXAVwNT53ga/dxwCqQZH4Sd024DEeo2Gk5dWyPTUnENcl4vW1Twa70
1gY60YqHw5UgH9DnBN+8yWjFHlbL4aZvbLdTsQOCkHMxoTt5vGP2e5XHh0Etk1oK
5E7FtXOFJ125PcPZ9BlyyyaKxR9ykpMYNtHJjScQPTlMhku/8aK3sb3eRwIDAQAB
o1MwUTAdBgNVHQ4EFgQUWrJXR3N6gnEkBQHACPj0Db+TtoAwHwYDVR0jBBgwFoAU
WrJXR3N6gnEkBQHACPj0Db+TtoAwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0B
AQsFAAOCAgEAKLhG4+kNvoVCyByrcSUU9rhUZxn1ezAQJcUO3ETZIE1lU1Z46rMk
9he4+sPTDxEEoynL8wseAStn+CVVd8Leer5QZEWqwaM+LkcjicYr+U8CNQ1p28Gy
oiSH0rK7RzkhRDwmnMYAx9skpem/nLj21RH4gPBUYaW5mU05O4F3vZvWZfwfGJi8
MSvPOVUZVUIJZFqNAECKxGbzneO3iKLETnDdAnw2VgLwTcvLfkbtBxbNwEgTgV8+
sWbmgTlPX1ToRdL2Gz5pp0hz6B94H0HoUO4yH2Afzb5O6+sKxLoIBq7S8PBURP0A
F+mtnU/VWJBNf52KRVHLWApdtieaqHIrHt9qbWsJ2bx+P3z5SkjOJbLDo06pAp4h
uYZuRiykUW+bMfv5Ec+qKUJVidG2J7YKpq3ghzdmebnZ+d/D/5KkEl22EnXw48Mj
r+ynJlq+EK5jQc0Y3CRt/ggjrqvmtE5Mqb6ltoVG4/yNa8z0tjza/B17tmYaCC/W
L7I/2KY7tKUuOk9JjWbTikTpGccn496QZlcpNWxiiK3qDOeah1iRLBqIm6HQeg5B
Vz3YWRir/L2QbkDFy6Lxy/28C0hbEljYV5rvuKjx5a4o8nUUcOPhiDTrpCpGKLZ8
uQ/9hYrLWWDYAREeNyuUHyyDyWGObqBr8W/vbQder2t8cSUZBhHR69k=
-----END CERTIFICATE-----

52
doc/key.pem Normal file
View file

@ -0,0 +1,52 @@
-----BEGIN PRIVATE KEY-----
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
Qdcfj82isy1JR/mZp2t+sasS1DGxNg==
-----END PRIVATE KEY-----

649
doc/spec-spec.txt Normal file
View file

@ -0,0 +1,649 @@
-----------------------------
Project Gemini
"Speculative specification"
v0.11.0, March 1st 2020
-----------------------------
This is an increasingly less rough sketch of an actual spec for
Project Gemini. Although not finalised yet, further changes to the
specification are likely to be relatively small. You can write code
to this pseudo-specification and be confident that it probably won't
become totally non-functional due to massive changes next week, but
you are still urged to keep an eye on ongoing development of the
protocol and make changes as required.
This is provided mostly so that people can quickly get up to speed on
what I'm thinking without having to read lots and lots of old phlog
posts and keep notes.
Feedback on any part of this is extremely welcome, please email
solderpunk@sdf.org.
-----------------------------
1. Overview
Gemini is a client-server protocol featuring request-response
transactions, broadly similar to gopher or HTTP. Connections are
closed at the end of a single transaction and cannot be reused. When
Gemini is served over TCP/IP, servers should listen on port 1965 (the
first manned Gemini mission, Gemini 3, flew in March '65). This is an
unprivileged port, so it's very easy to run a server as a "nobody"
user, even if e.g. the server is written in Go and so can't drop
privileges in the traditional fashion.
1.1 Gemini transactions
There is one kind of Gemini transaction, roughly equivalent to a
gopher request or a HTTP "GET" request. Transactions happen as
follows:
C: Opens connection
S: Accepts connection
C/S: Complete TLS handshake (see 1.4)
C: Validates server certificate (see 1.4.2)
C: Sends request (one CRLF terminated line) (see 1.2)
S: Sends response header (one CRFL terminated line), closes connection
under non-success conditions (see 1.3.1, 1.3.2)
S: Sends response body (text or binary data) (see 1.3.3)
S: Closes connection
C: Handles response (see 1.3.4)
1.2 Gemini requests
Gemini requests are a single CRLF-terminated line with the following
structure:
<URL><CR><LF>
<URL> is a UTF-8 encoded absolute URL, of maximum length 1024 bytes.
If the scheme of the URL is not specified, a scheme of gemini:// is
implied.
Sending an absolute URL instead of only a path or selector is
effectively equivalent to building in a HTTP "Host" header. It
permits virtual hosting of multiple Gemini domains on the same IP
address. It also allows servers to optionally act as proxies.
Including schemes other than gemini:// in requests allows servers to
optionally act as protocol-translating gateways to e.g. fetch gopher
resources over Gemini. Proxying is optional and the vast majority of
servers are expected to only respond to requests for resources at
their own domain(s).
1.3 Responses
Gemini response consist of a single CRLF-terminated header line,
optionally followed by a response body.
1.3.1 Response headers
Gemini response headers look like this:
<STATUS><whitespace><META><CR><LF>
<STATUS> is a two-digit numeric status code, as described below in
1.3.2 and in Appendix 1.
<whitespace> is any non-zero number of consecutive spaces or tabs.
<META> is a UTF-8 encoded string of maximum length 1024, whose meaning
is <STATUS> dependent.
If <STATUS> does not belong to the "SUCCESS" range of codes, then the
server MUST close the connection after sending the header and MUST NOT
send a response body.
If a server sends a <STATUS> which is not a two-digit number or a
<META> which exceeds 1024, the client SHOULD close the connection and
disregard the response header, informing the user of an error.
1.3.2 Status codes
Gemini uses two-digit numeric status codes. Related status codes share
the same first digit. Importantly, the first digit of Gemini status
codes do not group codes into vague categories like "client error" and
"server error" as per HTTP. Instead, the first digit alone provides
enough information for a client to determine how to handle the
response. By design, it is possible to write a simple but feature
complete client which only looks at the first digit. The second digit
provides more fine-grained information, for unambiguous server logging,
to allow writing comfier interactive clients which provide a slightly
more streamlined user interface, and to allow writing more robust and
intelligent automated clients like content aggregators, search engine
crawlers, etc.
The first digit of a response code unambiguously places the response
into one of six categories, which define the semantics of the <META>
line.
1 INPUT
The requested resource accepts a line of textual user input.
The <META> line is a prompt which should be displayed to the
user. The same resource should then be requested again with
the user's input included as a query component. Queries are
included in requests as per the usual generic URL definition
in RFC3986, i.e. separated from the path by a ?. There is no
response body.
2 SUCCESS
The request was handled successfully and a response body will
follow the response header. The <META> line is a MIME media
type which applies to the response body.
3 REDIRECT
The server is redirecting the client to a new location for the
requested resource. There is no response body. The header
text is a new URL for the requested resource. The URL may be
absolute or relative. The redirect should be considered
temporary, i.e. clients should continue to request the
resource at the original address and should not performance
convenience actions like automatically updating bookmarks.
There is no response body.
4 TEMPORARY FAILURE
The request has failed. There is no response body. The
nature of the failure is temporary, i.e. an identical request
MAY succeed in the future. The contents of <META> may provide
additional information on the failure, and should be displayed
to human users.
5 PERMANENT FAILURE
The request has failed. There is no response body. The
nature of the failure is permanent, i.e. identical future
requests will reliably fail for the same reason. The contents
of <META> may provide additional information on the failure,
and should be displayed to human users. Automatic clients
such as aggregators or indexing crawlers should should not
repeat this request.
6 CLIENT CERTIFICATE REQUIRED
The requested resource requires client-certificate
authentication to access. If the request was made without a
certificate, it should be repeated with one. If the request
was made with a certificate, the server did not accept it and
the request should be repeated with a different certificate.
The contents of <META> may provide additional information on
certificate requirements or the reason a certificate was
rejected.
Note that for basic interactive clients for human use, errors 4 and 5
may be effectively handled identically, by simply displaying the
contents of <META> under a heading of "ERROR". The
temporary/permanent error distinction is primarily relevant to
well-behaving automated clients. Basic clients may also choose not to
support client-certificate authentication, in which case only four
distinct status handling routines are required (for statuses beginning
with 1, 2, 3 or a combined 4-or-5).
The full two-digit system is detailed in Appendix 1. Note that for
each of the six valid first digits, a code with a second digit of zero
corresponds is a generic status of that kind with no special
semantics. This means that basic servers without any advanced
functionality need only be able to return codes of 10, 20, 30, 40 or
50.
The Gemini status code system has been carefully designed so that the
increased power (and correspondingly increased complexity) of the
second digits is entirely "opt-in" on the part of both servers and
clients.
1.3.3 Response bodies
Response bodies are just raw content, text or binary, ala gopher.
There is no support for compression, chunking or any other kind of
content or transfer encoding. The server closes the connection after
the final byte, there is no "end of response" signal like gopher's
lonely dot.
Response bodies only accompany responses whose header indicates a
SUCCESS status (i.e. a status code whose first digit is 2). For such
responses, <META> is a MIME media type as defined in RFC 2046.
If a MIME type begins with "text/" and no charset is explicitly given,
the charset should be assumed to be UTF-8. Compliant clients MUST
support UTF-8-encoded text/* responses. Clients MAY optionally
support other encodings. Clients receiving a response in a charset
they cannot decode SHOULD gracefully inform the user what happened
instead of displaying garbage.
If <META> is an empty string, the MIME type MUST default to
"text/gemini; charset=utf-8".
1.3.4 Response body handling
Response handling by clients should be informed by the provided MIME
type information. Gemini defines one MIME type of its own
(text/gemini) whose handling is discussed below in 1.3.5. In all
other cases, clients should do "something sensible" based on the MIME
type. Minimalistic clients might adopt a strategy of printing all
other text/* responses to the screen without formatting and saving
all non-text responses to the disk. Clients for unix systems may
consult /etc/mailcap to find installed programs for handling non-text
types.
1.3.5 text/gemini responses
1.3.5.1 Overview
In the same sense that HTML is the "native" response format of HTTP
and plain text is the native response format of gopher, Gemini defines
its own native response format - though of course, thanks to the
inclusion of a MIME type in the response header Gemini can be used to
serve plain text, rich text, HTML, Markdown, LaTeX, etc.
Response bodies of type "text/gemini" are a kind of lightweight
hypertext format, which takes inspiration from gophermaps and from
Markdown. The format permits richer typographic possibilities than
the plain text of Gopher, but remains extremely easy to parse. The
format is line-oriented, and a satisfactory rendering can be achieved
with a single pass of a document, processing each line independently.
As per gopher, links can only be displayed one per line, encouraging
neat, list-like structure.
Similar to how the two-digit Gemini status codes were designed so that
simple clients can function correctly while ignoring the second digit,
the text/gemini format has been designed so that simple clients can
ignore the more advanced features and still remain very usable.
1.3.5.2 Line-orientation
As mentioned, the text/gemini format is line-oriented. Each line of a
text/gemini document has a single "line type". It is possible to
unambiguously determine a line's type purely by inspecting its first
three characters. A line's type determines the manner in which it
should be presented to the user. Any details of presentation or
rendering associated with a particular line type are strictly limited
in scope to that individual line.
There are 6 different line types in total. However, a fully
functional and specification compliant Gemini client need only
recognise and handle 4 of them - these are the "core line types",
(see 1.3.5.3). Advanced clients can also handle the additional
"advanced line types" (see 1.3.5.4). Simple clients can treat all
advanced line types as one of the core line types and still offer an
adequate user experience.
1.3.5.3 Core line types
The four core line types are:
1.3.5.3.1 Text lines
Text lines are the most fundamenal line type - any line which does
not match the definition of another line type defined below defaults
to being a text line. The majority of lines in a typical text/gemini
document will be text lines.
Text lines should be presented to the user, after being wrapped to
the appropriate width for the client's viewport (see below). Text
lines may be presented to the user in a visually pleasing manner
for general reading, the precise meaning of which is at the
client's discretion. For example, variable width fonts may be used,
spacing may be normalised, with spaces between sentences being made
wider than spacing between words, and other such typographical
niceties may be applied. Clients may permit users to customise the
appearance of text lines by altering the font, font size, text and
background colour, etc. Authors should not expect to exercise any
control over the precise rendering of their text lines, only of their
actual textual content. Content such as ASCII art, computer source
code, etc. which may appear incorrectly when treated as such should
be enclosed beween preformatting toggle lines (see 1.3.5.3.3).
Blank lines are instances of text lines and have no special meaning.
They should be rendered individually as vertical blank space each
time they occur. In this way they are analogous to <br/> tags in HTML.
Consecutive blank lines should NOT be collapsed into a fewer blank
lines. Note also that consecutive non-blank text lines do not form
any kind of coherent unit or block such as a "paragraph": all text
lines are independent entities.
Text lines which are longer than can fit on a client's display device
SHOULD be "wrapped" to fit, i.e. long lines should be split (ideally
at whitespace or at hyphens) into multiple consecutive lines of a
device-appropriate width. This wrapping is applied to each line of
text independently. Multiple consecutive lines which are shorter
than the client's display device MUST NOT be combined into fewer,
longer lines.
In order to take full advantage of this method of text formatting,
authors of text/gemini content SHOULD avoid hard-wrapping to a
pecific fixed width, in contrast to the convention in Gopherspace
where text is typically wrapped at 80 characters or fewer. Instead,
text which should be displayed as a contiguous block should be written
as a single long line. Most text editors can be configured to
"soft-wrap", i.e. to write this kind of file while displaying the long
lines wrapped to fit the author's display device.
Authors who insist on hard-wrapping their content MUST be aware that
the content will display neatly on clients whose display device is as
wide as the hard-wrapped length or wider, but will appear with
irregular line widths on narrower clients.
1.3.5.3.2 Link lines
Lines beginning with the two characters "=>" are link lines, which
have the following syntax:
=>[<whitespace>]<URL>[<whitespace><USER-FRIENDLY LINK NAME>]<CR><LF>
where:
* <whitespace> is any non-zero number of consecutive spaces or
tabs
* Square brackets indicate that the enclosed content is
optional.
* <URL> is a URL, which may be absolute or relative. If the URL does
not include a scheme, a scheme of gemini:// is implied.
All the following examples are valid link lines:
=> gemini://example.org/
=> gemini://example.org/ An example link
=> gemini://example.org/foo Another example link at the same host
=>gemini://example.org/bar Yet another example link at the same host
=> foo/bar/baz.txt A relative link
=> gopher://example.org:70/1 A gopher link
Note that link URLs may have schemes other than gemini://. This means
that Gemini documents can simply and elegantly link to documents
hosted via other protocols, unlike gophermaps which can only link to
non-gopher content via a non-standard adaptation of the `h` item-type.
Clients can present links to users in whatever fashion the client
author wishes.
1.3.5.3.3 Preformatting toggle lines
Any line whose first three characters are "```" (i.e. three
consecutive back ticks with no leading whitespace) are preformatted
toggle lines. These lines should NOT be included in the rendered
output shown to the user. Instead, these lines toggle the parser
between preformatted mode being "on" or "off". Preformatted mode
should be "off" at the beginning of a document. The current status
of preformatted mode is the only internal state a parser is required
to maintain. When preformatted mode is "on", the usual rules for
identifying line types are suspended, and all lines should be
identified as preformatted text lines (see 1.3.5.3.4).
Preformatting toggle lines can be thought of as analogous to <pre> and
</pre> tags in HTML.
1.3.5.3.4 Preformatted text lines
Preformatted text lines should be presented to the user in a "neutral",
monowidth font without any alteration to whitespace or stylistic
enhancements. Graphical clients should use scrolling mechanisms to
present preformatted text lines which are longer than the client
viewport, in preference to wrapping. In displaying preformatted text
lines, clients should keep in mind applications like ASCII art and
computer source code: in particular, source code in langugaes with
significant whitespace (e.g. Python) should be able to be copied and
pasted from the client into a file and interpreted/compiled without
any problems arising from the client's manner of displaying them.
1.3.5.4 Advanced line types
The following advanced line types MAY be recognised by advanced
clients. Simple clients may treat them all as text lines as per
1.3.5.3.1 without any loss of essential function.
1.3.5.4.1 Heading lines
Lines beginning with "#" are heading lines. Heading lines consist of
one, two or three consecutive "#" characters, followed by optional
whitespace, followed by heading text. The number of # characters
indicates the "level" of header; #, ## and ### can be thought of as
analogous to <h1>, <h2> and <h3> in HTML.
Heading text should be presented to the user, and clients MAY use
special formatting, e.g. a larger or bold font, to indicate its
status as a header (simple clients may simply print the line,
including its leading #s, without any styling at all). However, the
main motivation for the definition of heading lines is not stylistic
but to provide a machine-readable representation of the internal
structure of the document. Advanced clients can use this information
to, e.g. display an automatically generated and hierarchically
formatted "table of contents" for a long document in a side-pane,
allowing users to easily jump to specific sections without excessive
scrolling. CMS-style tools automatically generating menus or
Atom/RSS feeds for a directory of text/gemini files can use first
heading in the file as a human-friendly title.
1.3.5.4.2 Unordered list items
Lines beginning with a * are unordered list items. This line type
exists purely for stylistic reasons. The * may be replaced in
advanced clients by a bullet symbol. Any text after the * character
should be presented to the user as if it were a text line, i.e.
wrapped to fit the viewport and formatted "nicely". Advanced clients
can take the space of the bullet symbol into account when wrapping
long list items to ensure that all lines of text corresponding to
the item are offset an equal distance from the left of the screen.
1.4 TLS
1.4.1 Version requirements
Use of TLS for Gemini transactions is mandatory.
Servers MUST use TLS version 1.2 or higher and SHOULD use TLS version
1.3 or higher. Clients MAY refuse to connect to servers using TLS
version 1.2 or lower.
1.4.2 Server certificate validation
Clients can validate TLS connections however they like (including not
at all) but the strongly RECOMMENDED approach is to implement a
lightweight "TOFU" certificate-pinning system which treats self-signed
certificates as first- class citizens. This greatly reduces TLS
overhead on the network (only one cert needs to be sent, not a whole
chain) and lowers the barrier to entry for setting up a Gemini site
(no need to pay a CA or setup a Let's Encrypt cron job, just make a
cert and go).
TOFU stands for "Trust On First Use" and is public-key security model
similar to that used by OpenSSH. The first time a Gemini client
connects to a server, it accepts whatever certificate it is presented.
That certificate's fingerprint and expiry date are saved in a
persistent database (like the .known_hosts file for SSH), associated
with the server's hostname. On all subsequent connections to that
hostname, the received certificate's fingerprint is computed and
compared to the one in the database. If the certificate is not the
one previously received, but the previous certificate's expiry date
has not passed, the user is shown a warning, analogous to the one web
browser users are shown when receiving a certificate without a
signature chain leading to a trusted CA.
This model is by no means perfect, but it is not awful and is vastly
superior to just accepting self-signed certificates unconditionally.
1.4.3 Transient client certificate sessions
Self-signed client certificates can optionally be used by Gemini
clients to permit servers to recognise subsequent requests from the
same client as belonging to a single "session". This facilitates
maintaining state in server-side applications. The functionality is
very similar to HTTP cookies, but with important differences.
Whereas HTTP cookies are originally created by a webserver and given
to a client via a response header, client certificates are created by
the client and given to the server as part of the TLS handshake:
Client certificates are fundamentally a client-centric means of
identification. Further, whereas HTTP cookies can be "resurrected" by
webservers after a client deletes them if the server recognises the
client by means of browser finger-printing or some other tracking
technology (leading to unkillable "super cookies"), if a client
deletes a client certificate and also the accompanying private key
(which the server has never seen), then the session ID can never be
recreated. Thus clients not only need to opt in to a certificate
session, but once they have done so they retain a guaranteed ability
to opt out of it at any point and the server cannot defeat this
ability.
Gemini requests typically will be made without a client certificate
being sent to the server. If a requested resource is part of a
server-side application which requires persistent state, a Gemini
server can return a status code of 61 (see Appendix 1 below) to
request that the client repeat the request with a "transient
certificate" to initiate a client certificate section.
Interactive clients for human users MUST inform users that such a
session has been requested and require the user to approve generation
of such a certificate. Transient certificates MUST NOT be generated
automatically.
Transient certificates are limited in scope to a particular domain.
Transient certificates MUST NOT be reused across different domains.
Transient certificates MUST be permanently deleted when the matching
server issues a response with a status code of 21 (see Appendix 1
below).
Transient certificates MUST be permanently deleted when the client
process terminates.
Transient certificates SHOULD be permanently deleted after not having
been used for more than 24 hours.
Appendix 1. Full two digit status codes
10 INPUT
As per definition of single-digit code 1 in 1.3.2.
20 SUCCESS
As per definition of single-digit code 2 in 1.3.2.
21 SUCCESS - END OF CLIENT CERTIFICATE SESSION
The request was handled successfully and a response body will
follow the response header. The <META> line is a MIME media
type which applies to the response body. In addition, the
server is signalling the end of a transient client certificate
session which was previously initiated with a status 61
response. The client should immediately and permanently
delete the certificate and accompanying private key which was
used in this request.
30 REDIRECT - TEMPORARY
As per definition of single-digit code 3 in 1.3.2.
31 REDIRECT - PERMANENT
The requested resource should be consistently requested from
the new URL provided in future. Tools like search engine
indexers or content aggregators should update their
configurations to avoid requesting the old URL, and end-user
clients may automatically update bookmarks, etc. Note that
clients which only pay attention to the initial digit of
status codes will treat this as a temporary redirect. They
will still end up at the right place, they just won't be able
to make use of the knowledge that this redirect is permanent,
so they'll pay a small performance penalty by having to follow
the redirect each time.
40 TEMPORARY FAILURE
As per definition of single-digit code 4 in 1.3.2.
41 SERVER UNAVAILABLE
The server is unavailable due to overload or maintenance.
(cf HTTP 503)
42 CGI ERROR
A CGI process, or similar system for generating dynamic
content, died unexpectedly or timed out.
43 PROXY ERROR
A proxy request failed because the server was unable to
successfully complete a transaction with the remote host.
(cf HTTP 502, 504)
44 SLOW DOWN
Rate limiting is in effect. <META> is an integer number of
seconds which the client must wait before another request is
made to this server.
(cf HTTP 429)
50 PERMANENT FAILURE
As per definition of single-digit code 5 in 1.3.2.
51 NOT FOUND
The requested resource could not be found but may be available
in the future.
(cf HTTP 404)
(struggling to remember this important status code? Easy:
you can't find things hidden at Area 51!)
52 GONE
The resource requested is no longer available and will not be
available again. Search engines and similar tools should
remove this resource from their indices. Content aggregators
should stop requesting the resource and convey to their human
users that the subscribed resource is gone.
(cf HTTP 410)
53 PROXY REQUEST REFUSED
The request was for a resource at a domain not served by the
server and the server does not accept proxy requests.
59 BAD REQUEST
The server was unable to parse the client's request,
presumably due to a malformed request.
(cf HTTP 400)
60 CLIENT CERTIFICATE REQUIRED
As per definition of single-digit code 6 in 1.3.2.
61 TRANSIENT CERTIFICATE REQUESTED
The server is requesting the initiation of a transient client
certificate session, as described in 1.4.3. The client should
ask the user if they want to accept this and, if so, generate
a disposable key/cert pair and re-request the resource using it.
The key/cert pair should be destroyed when the client quits,
or some reasonable time after it was last used (24 hours?
Less?)
62 AUTHORISED CERTIFICATE REQUIRED
This resource is protected and a client certificate which the
server accepts as valid must be used - a disposable key/cert
generated on the fly in response to this status is not
appropriate as the server will do something like compare the
certificate fingerprint against a white-list of allowed
certificates. The client should ask the user if they want to
use a pre-existing certificate from a stored "key chain".
63 CERTIFICATE NOT ACCEPTED
The supplied client certificate is not valid for accessing the
requested resource.
64 FUTURE CERTIFICATE REJECTED
The supplied client certificate was not accepted because its
validity start date is in the future.
65 EXPIRED CERTIFICATE REJECTED
The supplied client certificate was not accepted because its
expiry date has passed.

45
src/main.rs Normal file
View file

@ -0,0 +1,45 @@
use openssl::ssl::{SslMethod, SslAcceptor, SslStream, SslFiletype};
use std::net::{TcpListener, TcpStream, SocketAddr};
use std::sync::Arc;
use std::thread;
mod response;
fn build_acceptor() -> std::sync::Arc<SslAcceptor> {
let mut acceptor = SslAcceptor::mozilla_intermediate_v5(SslMethod::tls()).unwrap();
acceptor.set_private_key_file("doc/key.pem", SslFiletype::PEM).unwrap();
acceptor.set_certificate_chain_file("doc/cert.pem").unwrap();
acceptor.check_private_key().unwrap();
return Arc::new(acceptor.build());
}
fn handle_client(mut stream: SslStream<TcpStream>) {
let mut buffer = [0; 1026];
stream.ssl_read(&mut buffer);
let request = String::from_utf8(buffer.to_vec()).unwrap();
let header = response::Header::new(response::Status::Success, "text/gemini".to_string());
let response = response::Response::new(header, [].to_vec());
stream.ssl_write(&response.format());
}
fn main() {
let addrs = [
SocketAddr::from(([127, 0, 0, 1], 1965)),
];
let acceptor = build_acceptor();
let listener = TcpListener::bind(&addrs[..]).unwrap();
for stream in listener.incoming() {
match stream {
Ok(stream) => {
let acceptor = acceptor.clone();
thread::spawn(move || {
let stream = acceptor.accept(stream).unwrap();
handle_client(stream);
});
}
Err(e) => { /* connection failed */ }
}
}
}

50
src/response.rs Normal file
View file

@ -0,0 +1,50 @@
use std::vec;
#[derive(Copy, Clone)]
pub enum Status {
Input = 1,
Success = 2,
Redirect = 3,
TemporaryFailure = 4,
PermanentFailure = 5,
ClientCertificateRequired = 6,
}
pub struct Header {
status: Status,
meta: String,
}
pub struct Response {
header: Header,
data: std::vec::Vec<u8>,
}
impl Header {
pub fn new(status: Status, meta: String) -> Header {
return Header{
status: status,
meta: meta,
}
}
pub fn format(&self) -> String {
let status: u8 = self.status as u8;
return format!("{} {}\r\n", status * 10, self.meta)
}
}
impl Response {
pub fn new(header: Header, data: std::vec::Vec<u8>) -> Response {
return Response{
header: header,
data: data,
}
}
pub fn format(&self) -> std::vec::Vec<u8> {
let mut resp: std::vec::Vec<u8> = self.header.format().as_bytes().to_vec();
resp.extend(&self.data);
return resp;
}
}