Compare commits
No commits in common. "96b38f2c97093f5a038dbb0eb36798d3aa1e5c33" and "8a530d4f7d16351b4a83fbd1347d96e47d0f3ade" have entirely different histories.
96b38f2c97
...
8a530d4f7d
100
Cargo.lock
generated
100
Cargo.lock
generated
@ -111,12 +111,6 @@ version = "1.6.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "bddcadddf5e9015d310179a59bb28c4d4b9920ad0f11e8e14dbadf654890c9a6"
|
checksum = "bddcadddf5e9015d310179a59bb28c4d4b9920ad0f11e8e14dbadf654890c9a6"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "arrayvec"
|
|
||||||
version = "0.5.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "arrayvec"
|
name = "arrayvec"
|
||||||
version = "0.7.2"
|
version = "0.7.2"
|
||||||
@ -164,18 +158,6 @@ version = "2.2.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "24a6904aef64d73cf10ab17ebace7befb918b82164785cb89907993be7f83813"
|
checksum = "24a6904aef64d73cf10ab17ebace7befb918b82164785cb89907993be7f83813"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "bitvec"
|
|
||||||
version = "0.19.6"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "55f93d0ef3363c364d5976646a38f04cf67cfe1d4c8d160cdea02cab2c116b33"
|
|
||||||
dependencies = [
|
|
||||||
"funty",
|
|
||||||
"radium",
|
|
||||||
"tap",
|
|
||||||
"wyz",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "block-buffer"
|
name = "block-buffer"
|
||||||
version = "0.10.4"
|
version = "0.10.4"
|
||||||
@ -476,11 +458,9 @@ name = "domani"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"acme2",
|
"acme2",
|
||||||
"bytes",
|
|
||||||
"clap",
|
"clap",
|
||||||
"env_logger",
|
"env_logger",
|
||||||
"futures",
|
"futures",
|
||||||
"gemini",
|
|
||||||
"gix",
|
"gix",
|
||||||
"handlebars",
|
"handlebars",
|
||||||
"hex",
|
"hex",
|
||||||
@ -680,12 +660,6 @@ version = "0.1.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba"
|
checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "funty"
|
|
||||||
version = "1.1.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "fed34cd105917e91daa4da6b3728c47b068749d6a62c59811f06ed2ac71d9da7"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures"
|
name = "futures"
|
||||||
version = "0.3.28"
|
version = "0.3.28"
|
||||||
@ -775,18 +749,6 @@ dependencies = [
|
|||||||
"slab",
|
"slab",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "gemini"
|
|
||||||
version = "0.0.5"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "12a820f5a9ac6f433b34944dc8d17b759d5009275c8fe12f73b873153dbcd4e0"
|
|
||||||
dependencies = [
|
|
||||||
"nom 6.1.2",
|
|
||||||
"paste",
|
|
||||||
"thiserror",
|
|
||||||
"url",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "generic-array"
|
name = "generic-array"
|
||||||
version = "0.14.7"
|
version = "0.14.7"
|
||||||
@ -866,7 +828,7 @@ dependencies = [
|
|||||||
"btoi",
|
"btoi",
|
||||||
"gix-date",
|
"gix-date",
|
||||||
"itoa",
|
"itoa",
|
||||||
"nom 7.1.3",
|
"nom",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -929,7 +891,7 @@ dependencies = [
|
|||||||
"gix-sec",
|
"gix-sec",
|
||||||
"log",
|
"log",
|
||||||
"memchr",
|
"memchr",
|
||||||
"nom 7.1.3",
|
"nom",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"smallvec",
|
"smallvec",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
@ -1138,7 +1100,7 @@ dependencies = [
|
|||||||
"gix-validate",
|
"gix-validate",
|
||||||
"hex",
|
"hex",
|
||||||
"itoa",
|
"itoa",
|
||||||
"nom 7.1.3",
|
"nom",
|
||||||
"smallvec",
|
"smallvec",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
]
|
]
|
||||||
@ -1233,7 +1195,7 @@ dependencies = [
|
|||||||
"gix-hash",
|
"gix-hash",
|
||||||
"gix-transport",
|
"gix-transport",
|
||||||
"maybe-async",
|
"maybe-async",
|
||||||
"nom 7.1.3",
|
"nom",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -1264,7 +1226,7 @@ dependencies = [
|
|||||||
"gix-tempfile",
|
"gix-tempfile",
|
||||||
"gix-validate",
|
"gix-validate",
|
||||||
"memmap2",
|
"memmap2",
|
||||||
"nom 7.1.3",
|
"nom",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -1805,19 +1767,6 @@ version = "1.4.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "lexical-core"
|
|
||||||
version = "0.7.6"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "6607c62aa161d23d17a9072cc5da0be67cdfc89d3afb1e8d9c842bebc2525ffe"
|
|
||||||
dependencies = [
|
|
||||||
"arrayvec 0.5.2",
|
|
||||||
"bitflags 1.3.2",
|
|
||||||
"cfg-if",
|
|
||||||
"ryu",
|
|
||||||
"static_assertions",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libc"
|
name = "libc"
|
||||||
version = "0.2.142"
|
version = "0.2.142"
|
||||||
@ -2005,19 +1954,6 @@ dependencies = [
|
|||||||
"smallvec",
|
"smallvec",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "nom"
|
|
||||||
version = "6.1.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "e7413f999671bd4745a7b624bd370a569fb6bc574b23c83a3c5ed2e453f3d5e2"
|
|
||||||
dependencies = [
|
|
||||||
"bitvec",
|
|
||||||
"funty",
|
|
||||||
"lexical-core",
|
|
||||||
"memchr",
|
|
||||||
"version_check",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nom"
|
name = "nom"
|
||||||
version = "7.1.3"
|
version = "7.1.3"
|
||||||
@ -2145,12 +2081,6 @@ dependencies = [
|
|||||||
"windows-sys 0.45.0",
|
"windows-sys 0.45.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "paste"
|
|
||||||
version = "1.0.14"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pem"
|
name = "pem"
|
||||||
version = "2.0.1"
|
version = "2.0.1"
|
||||||
@ -2319,12 +2249,6 @@ dependencies = [
|
|||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "radium"
|
|
||||||
version = "0.5.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "941ba9d78d8e2f7ce474c015eea4d9c6d25b6a3327f9832ee29a4de27f91bbb8"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "radix_trie"
|
name = "radix_trie"
|
||||||
version = "0.2.1"
|
version = "0.2.1"
|
||||||
@ -2881,12 +2805,6 @@ dependencies = [
|
|||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "tap"
|
|
||||||
version = "1.0.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tempdir"
|
name = "tempdir"
|
||||||
version = "0.3.7"
|
version = "0.3.7"
|
||||||
@ -3203,7 +3121,7 @@ version = "3.0.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "794a32261a1f5eb6a4462c81b59cec87b5c27d5deea7dd1ac8fc781c41d226db"
|
checksum = "794a32261a1f5eb6a4462c81b59cec87b5c27d5deea7dd1ac8fc781c41d226db"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arrayvec 0.7.2",
|
"arrayvec",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -3590,9 +3508,3 @@ checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"winapi",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "wyz"
|
|
||||||
version = "0.2.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "85e60b0d1b5f99db2556934e21937020776a5d31520bf169e851ac44e6420214"
|
|
||||||
|
@ -31,7 +31,7 @@ mime_guess = "2.0.4"
|
|||||||
hyper = { version = "0.14.26", features = [ "server", "stream" ]}
|
hyper = { version = "0.14.26", features = [ "server", "stream" ]}
|
||||||
http = "0.2.9"
|
http = "0.2.9"
|
||||||
serde_urlencoded = "0.7.1"
|
serde_urlencoded = "0.7.1"
|
||||||
tokio-util = { version = "0.7.8", features = [ "io" ]}
|
tokio-util = "0.7.8"
|
||||||
acme2 = "0.5.1"
|
acme2 = "0.5.1"
|
||||||
openssl = "0.10.52"
|
openssl = "0.10.52"
|
||||||
rustls = "0.21.1"
|
rustls = "0.21.1"
|
||||||
@ -45,8 +45,6 @@ serde_yaml = "0.9.22"
|
|||||||
rand = "0.8.5"
|
rand = "0.8.5"
|
||||||
reqwest = "0.11.18"
|
reqwest = "0.11.18"
|
||||||
hyper-reverse-proxy = "0.5.1"
|
hyper-reverse-proxy = "0.5.1"
|
||||||
gemini = "0.0.5"
|
|
||||||
bytes = "1.4.0"
|
|
||||||
|
|
||||||
[patch.crates-io]
|
[patch.crates-io]
|
||||||
tokio-rustls = { git = "https://code.betamike.com/micropelago/tokio-rustls.git", branch = "start-handshake-into-inner" }
|
tokio-rustls = { git = "https://code.betamike.com/micropelago/tokio-rustls.git", branch = "start-handshake-into-inner" }
|
||||||
|
65
README.md
65
README.md
@ -7,17 +7,6 @@ their DNS server.
|
|||||||
|
|
||||||
[Demo which may or may not be live](https://domani.mediocregopher.com)
|
[Demo which may or may not be live](https://domani.mediocregopher.com)
|
||||||
|
|
||||||
Domani supports serving domains using the following protocols:
|
|
||||||
|
|
||||||
- HTTP
|
|
||||||
- HTTPS (with SSL certificates automatically retrieved using LetsEncrypt)
|
|
||||||
- [Gemini](https://gemini.circumlunar.space/)
|
|
||||||
|
|
||||||
Files are served as-is, with their extension being used to determine
|
|
||||||
Content-Type. If a directory is requested (e.g. `/some/dir/`) then `index.html`
|
|
||||||
will be requested if the protocol is HTTP, or `index.gmi` if the protocol is
|
|
||||||
gemini.
|
|
||||||
|
|
||||||
## Build
|
## Build
|
||||||
|
|
||||||
Domani uses nix flakes for building and setting up the development environment.
|
Domani uses nix flakes for building and setting up the development environment.
|
||||||
@ -71,13 +60,13 @@ domain:
|
|||||||
|
|
||||||
# An example built-in domain backed by a git repo.
|
# An example built-in domain backed by a git repo.
|
||||||
#git.example.com:
|
#git.example.com:
|
||||||
#kind: git
|
# kind: git
|
||||||
#url: "https://somewhere.com/some/repo.git"
|
# url: "https://somewhere.com/some/repo.git"
|
||||||
#branch_name: main
|
# branch_name: main
|
||||||
|
#
|
||||||
# If true then the built-in will be included in the web interface's
|
# # If true then the built-in will be included in the web interface's
|
||||||
# domain list, but will not be configurable in the web interface
|
# # domain list, but will not be configurable in the web interface
|
||||||
#public: false
|
# public: false
|
||||||
|
|
||||||
service:
|
service:
|
||||||
|
|
||||||
@ -113,9 +102,8 @@ service:
|
|||||||
# https_addr is set.
|
# https_addr is set.
|
||||||
#http_addr: "[::]:3080"
|
#http_addr: "[::]:3080"
|
||||||
|
|
||||||
# The address to listen for HTTPS requests on. Defaults to not having HTTP
|
# The address to listen for HTTPS requests on. This is optional.
|
||||||
# enabled. You can enable HTTPS by setting this to "[::]:443".
|
#https_addr: "[::]:443"
|
||||||
#https_addr: null
|
|
||||||
|
|
||||||
#proxied_domains:
|
#proxied_domains:
|
||||||
|
|
||||||
@ -129,6 +117,7 @@ service:
|
|||||||
# * dns.resolver_addr is ignored and the system-wide dns is used
|
# * dns.resolver_addr is ignored and the system-wide dns is used
|
||||||
#
|
#
|
||||||
#proxy.example.com:
|
#proxy.example.com:
|
||||||
|
# kind: http_proxy
|
||||||
# url: "http://some.other.service.com"
|
# url: "http://some.other.service.com"
|
||||||
#
|
#
|
||||||
# # Extra headers to add to proxied requests
|
# # Extra headers to add to proxied requests
|
||||||
@ -137,25 +126,6 @@ service:
|
|||||||
# value: "yet.another.service.com"
|
# value: "yet.another.service.com"
|
||||||
# - name: X-HEADER-TO-DELETE
|
# - name: X-HEADER-TO-DELETE
|
||||||
# value: ""
|
# value: ""
|
||||||
|
|
||||||
#gemini:
|
|
||||||
|
|
||||||
# The address to listen for gemini requests on. Set this to null to disable
|
|
||||||
# gemini support.
|
|
||||||
#gemini_addr: "[::]:3965"
|
|
||||||
|
|
||||||
#proxied_domains:
|
|
||||||
|
|
||||||
# An example built-in domain backed by a reverse-proxy to some other
|
|
||||||
# gemini server. Requests to this domain will have connections
|
|
||||||
# transparently proxied to the backing server.
|
|
||||||
#
|
|
||||||
# Proxies are currently limited in the following ways:
|
|
||||||
# * url must be to a gemini endpoint
|
|
||||||
# * dns.resolver_addr is ignored and the system-wide dns is used
|
|
||||||
#
|
|
||||||
#proxy.example.com:
|
|
||||||
# url: "gemini://some.other.service.com"
|
|
||||||
```
|
```
|
||||||
|
|
||||||
The YAML config file can be passed to the Domani process via the `--config-path`
|
The YAML config file can be passed to the Domani process via the `--config-path`
|
||||||
@ -189,25 +159,10 @@ nix develop
|
|||||||
|
|
||||||
Within the shell which opens you can do `cargo run` to start a local instance.
|
Within the shell which opens you can do `cargo run` to start a local instance.
|
||||||
|
|
||||||
Using the default configuration, the domain `domani-test.localhost` should be
|
|
||||||
immediately available at:
|
|
||||||
|
|
||||||
* `http://domani-test.localhost:3080`
|
|
||||||
* `gemini://domani-test.localhost:3965`
|
|
||||||
|
|
||||||
## Roadmap
|
## Roadmap
|
||||||
|
|
||||||
* Better web interface design.
|
|
||||||
* Tutorials aimed at beginner users.
|
|
||||||
|
|
||||||
* Support for more backends than just git repositories, including:
|
* Support for more backends than just git repositories, including:
|
||||||
* IPFS/IPNS
|
* IPFS/IPNS
|
||||||
* Small static files (e.g. for well-knowns)
|
* Small static files (e.g. for well-knowns)
|
||||||
* Google Drive
|
* Google Drive
|
||||||
* Dropbox
|
* Dropbox
|
||||||
|
|
||||||
* Automatic HTTP/gemtext rendering for markdown files.
|
|
||||||
* Automatic HTTP rendering for gemtext files.
|
|
||||||
|
|
||||||
* Ability to disable the web interface.
|
|
||||||
* Ability to disable HTTP completely.
|
|
||||||
|
@ -4,21 +4,7 @@ use crate::{domain, util};
|
|||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use std::{fs, path};
|
use std::{fs, path, sync};
|
||||||
|
|
||||||
pub trait Store {
|
|
||||||
fn get_certificate(
|
|
||||||
&self,
|
|
||||||
domain: &domain::Name,
|
|
||||||
) -> unexpected::Result<Option<(PrivateKey, Certificate)>>;
|
|
||||||
|
|
||||||
fn set_certificate(
|
|
||||||
&self,
|
|
||||||
domain: &domain::Name,
|
|
||||||
pkey: PrivateKey,
|
|
||||||
cert: Certificate,
|
|
||||||
) -> unexpected::Result<()>;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
struct StoredPKeyCert {
|
struct StoredPKeyCert {
|
||||||
@ -44,13 +30,11 @@ impl FSStore {
|
|||||||
domain.push_str(".json");
|
domain.push_str(".json");
|
||||||
self.cert_dir_path.join(domain)
|
self.cert_dir_path.join(domain)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
impl Store for FSStore {
|
|
||||||
fn get_certificate(
|
fn get_certificate(
|
||||||
&self,
|
&self,
|
||||||
domain: &domain::Name,
|
domain: &domain::Name,
|
||||||
) -> unexpected::Result<Option<(PrivateKey, Certificate)>> {
|
) -> unexpected::Result<(PrivateKey, Certificate)> {
|
||||||
let path = self.pkey_cert_path(domain);
|
let path = self.pkey_cert_path(domain);
|
||||||
|
|
||||||
let file = match util::open_file(path.as_path())
|
let file = match util::open_file(path.as_path())
|
||||||
@ -58,23 +42,9 @@ impl Store for FSStore {
|
|||||||
{
|
{
|
||||||
Some(file) => file,
|
Some(file) => file,
|
||||||
None => {
|
None => {
|
||||||
return Ok(None);
|
let pkey = PrivateKey::new();
|
||||||
}
|
let cert = Certificate::new_self_signed(&pkey, domain)
|
||||||
};
|
.or_unexpected_while("creating self-signed cert")?;
|
||||||
|
|
||||||
let stored: StoredPKeyCert =
|
|
||||||
serde_json::from_reader(file).or_unexpected_while("parsing json")?;
|
|
||||||
|
|
||||||
Ok(Some((stored.private_key, stored.cert)))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_certificate(
|
|
||||||
&self,
|
|
||||||
domain: &domain::Name,
|
|
||||||
pkey: PrivateKey,
|
|
||||||
cert: Certificate,
|
|
||||||
) -> unexpected::Result<()> {
|
|
||||||
let path = self.pkey_cert_path(domain);
|
|
||||||
|
|
||||||
let file = fs::File::create(path.as_path())
|
let file = fs::File::create(path.as_path())
|
||||||
.map_unexpected_while(|| format!("creating file {}", path.display()))?;
|
.map_unexpected_while(|| format!("creating file {}", path.display()))?;
|
||||||
@ -86,6 +56,46 @@ impl Store for FSStore {
|
|||||||
|
|
||||||
serde_json::to_writer(file, &stored).or_unexpected_while("writing cert to file")?;
|
serde_json::to_writer(file, &stored).or_unexpected_while("writing cert to file")?;
|
||||||
|
|
||||||
return Ok(());
|
return Ok((stored.private_key, stored.cert));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let stored: StoredPKeyCert =
|
||||||
|
serde_json::from_reader(file).or_unexpected_while("parsing json")?;
|
||||||
|
|
||||||
|
Ok((stored.private_key, stored.cert))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl rustls::server::ResolvesServerCert for FSStore {
|
||||||
|
fn resolve(
|
||||||
|
&self,
|
||||||
|
client_hello: rustls::server::ClientHello<'_>,
|
||||||
|
) -> Option<sync::Arc<rustls::sign::CertifiedKey>> {
|
||||||
|
let domain = client_hello.server_name()?;
|
||||||
|
|
||||||
|
let res: unexpected::Result<Option<sync::Arc<rustls::sign::CertifiedKey>>> = (|| {
|
||||||
|
let domain: domain::Name = domain
|
||||||
|
.parse()
|
||||||
|
.map_unexpected_while(|| format!("parsing domain {domain}"))?;
|
||||||
|
|
||||||
|
let (pkey, cert) = self
|
||||||
|
.get_certificate(&domain)
|
||||||
|
.or_unexpected_while("fetching pkey/cert")?;
|
||||||
|
|
||||||
|
let pkey = rustls::sign::any_supported_type(&pkey.into()).or_unexpected()?;
|
||||||
|
|
||||||
|
Ok(Some(sync::Arc::new(rustls::sign::CertifiedKey {
|
||||||
|
cert: vec![cert.into()],
|
||||||
|
key: pkey,
|
||||||
|
ocsp: None,
|
||||||
|
sct_list: None,
|
||||||
|
})))
|
||||||
|
})();
|
||||||
|
|
||||||
|
res.unwrap_or_else(|err| {
|
||||||
|
log::error!("Unexpected error getting cert for domain {domain}: {err}");
|
||||||
|
None
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
use crate::domain::{self, acme, checker, gemini, store, tls};
|
use crate::domain::{self, acme, checker, store};
|
||||||
use crate::error::unexpected::{self, Mappable};
|
use crate::error::unexpected::{self, Mappable};
|
||||||
use crate::{origin, task_stack, util};
|
use crate::{origin, task_stack, util};
|
||||||
|
|
||||||
@ -140,7 +140,7 @@ impl From<store::SetError> for SyncWithSettingsError {
|
|||||||
pub type GetAcmeHttp01ChallengeKeyError = acme::manager::GetHttp01ChallengeKeyError;
|
pub type GetAcmeHttp01ChallengeKeyError = acme::manager::GetHttp01ChallengeKeyError;
|
||||||
|
|
||||||
//#[mockall::automock]
|
//#[mockall::automock]
|
||||||
pub trait Manager: Sync + Send {
|
pub trait Manager: Sync + Send + rustls::server::ResolvesServerCert {
|
||||||
fn get_settings(&self, domain: &domain::Name) -> Result<GetSettingsResult, GetSettingsError>;
|
fn get_settings(&self, domain: &domain::Name) -> Result<GetSettingsResult, GetSettingsError>;
|
||||||
|
|
||||||
fn get_file<'store>(
|
fn get_file<'store>(
|
||||||
@ -178,7 +178,6 @@ pub struct ManagerImpl {
|
|||||||
domain_store: Box<dyn store::Store + Send + Sync>,
|
domain_store: Box<dyn store::Store + Send + Sync>,
|
||||||
domain_checker: checker::DNSChecker,
|
domain_checker: checker::DNSChecker,
|
||||||
acme_manager: Option<Box<dyn acme::manager::Manager + Send + Sync>>,
|
acme_manager: Option<Box<dyn acme::manager::Manager + Send + Sync>>,
|
||||||
gemini_store: Option<Box<dyn gemini::Store + Send + Sync>>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ManagerImpl {
|
impl ManagerImpl {
|
||||||
@ -186,14 +185,12 @@ impl ManagerImpl {
|
|||||||
OriginStore: origin::Store + Send + Sync + 'static,
|
OriginStore: origin::Store + Send + Sync + 'static,
|
||||||
DomainStore: store::Store + Send + Sync + 'static,
|
DomainStore: store::Store + Send + Sync + 'static,
|
||||||
AcmeManager: acme::manager::Manager + Send + Sync + 'static,
|
AcmeManager: acme::manager::Manager + Send + Sync + 'static,
|
||||||
GeminiStore: gemini::Store + Send + Sync + 'static,
|
|
||||||
>(
|
>(
|
||||||
task_stack: &mut task_stack::TaskStack<unexpected::Error>,
|
task_stack: &mut task_stack::TaskStack<unexpected::Error>,
|
||||||
origin_store: OriginStore,
|
origin_store: OriginStore,
|
||||||
domain_store: DomainStore,
|
domain_store: DomainStore,
|
||||||
domain_checker: checker::DNSChecker,
|
domain_checker: checker::DNSChecker,
|
||||||
acme_manager: Option<AcmeManager>,
|
acme_manager: Option<AcmeManager>,
|
||||||
gemini_store: Option<GeminiStore>,
|
|
||||||
) -> sync::Arc<Self> {
|
) -> sync::Arc<Self> {
|
||||||
let manager = sync::Arc::new(ManagerImpl {
|
let manager = sync::Arc::new(ManagerImpl {
|
||||||
origin_store: Box::from(origin_store),
|
origin_store: Box::from(origin_store),
|
||||||
@ -201,7 +198,6 @@ impl ManagerImpl {
|
|||||||
domain_checker,
|
domain_checker,
|
||||||
acme_manager: acme_manager
|
acme_manager: acme_manager
|
||||||
.map(|m| Box::new(m) as Box<dyn acme::manager::Manager + Send + Sync>),
|
.map(|m| Box::new(m) as Box<dyn acme::manager::Manager + Send + Sync>),
|
||||||
gemini_store: gemini_store.map(|m| Box::new(m) as Box<dyn gemini::Store + Send + Sync>),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
task_stack.push_spawn(|canceller| {
|
task_stack.push_spawn(|canceller| {
|
||||||
@ -252,23 +248,6 @@ impl ManagerImpl {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn sync_gemini_cert(&self, domain: &domain::Name) -> unexpected::Result<()> {
|
|
||||||
if let Some(ref gemini_store) = self.gemini_store {
|
|
||||||
if let Some(_) = gemini_store.get_certificate(domain).or_unexpected()? {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
// no cert/key stored for the domain, generate and store it
|
|
||||||
let pkey = tls::PrivateKey::new();
|
|
||||||
let cert = tls::Certificate::new_self_signed(&pkey, domain)
|
|
||||||
.or_unexpected_while("creating self-signed cert")?;
|
|
||||||
|
|
||||||
gemini_store.set_certificate(domain, pkey, cert)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Manager for ManagerImpl {
|
impl Manager for ManagerImpl {
|
||||||
@ -321,8 +300,6 @@ impl Manager for ManagerImpl {
|
|||||||
|
|
||||||
self.domain_store.set(&domain, &settings)?;
|
self.domain_store.set(&domain, &settings)?;
|
||||||
|
|
||||||
self.sync_gemini_cert(&domain)?;
|
|
||||||
|
|
||||||
self.sync_https_cert(domain).await?;
|
self.sync_https_cert(domain).await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@ -352,22 +329,14 @@ impl Manager for ManagerImpl {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct HttpsCertResolver(sync::Arc<ManagerImpl>);
|
impl rustls::server::ResolvesServerCert for ManagerImpl {
|
||||||
|
|
||||||
impl From<sync::Arc<ManagerImpl>> for HttpsCertResolver {
|
|
||||||
fn from(mgr: sync::Arc<ManagerImpl>) -> Self {
|
|
||||||
Self(mgr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl rustls::server::ResolvesServerCert for HttpsCertResolver {
|
|
||||||
fn resolve(
|
fn resolve(
|
||||||
&self,
|
&self,
|
||||||
client_hello: rustls::server::ClientHello<'_>,
|
client_hello: rustls::server::ClientHello<'_>,
|
||||||
) -> Option<sync::Arc<rustls::sign::CertifiedKey>> {
|
) -> Option<sync::Arc<rustls::sign::CertifiedKey>> {
|
||||||
let domain = client_hello.server_name()?;
|
let domain = client_hello.server_name()?;
|
||||||
|
|
||||||
match (self.0).acme_manager.as_ref()?.get_certificate(domain) {
|
match self.acme_manager.as_ref()?.get_certificate(domain) {
|
||||||
Err(acme::manager::GetCertificateError::NotFound) => {
|
Err(acme::manager::GetCertificateError::NotFound) => {
|
||||||
log::warn!("No cert found for domain {domain}");
|
log::warn!("No cert found for domain {domain}");
|
||||||
Ok(None)
|
Ok(None)
|
||||||
@ -391,53 +360,3 @@ impl rustls::server::ResolvesServerCert for HttpsCertResolver {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct GeminiCertResolver(sync::Arc<ManagerImpl>);
|
|
||||||
|
|
||||||
impl From<sync::Arc<ManagerImpl>> for GeminiCertResolver {
|
|
||||||
fn from(mgr: sync::Arc<ManagerImpl>) -> Self {
|
|
||||||
Self(mgr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl rustls::server::ResolvesServerCert for GeminiCertResolver {
|
|
||||||
fn resolve(
|
|
||||||
&self,
|
|
||||||
client_hello: rustls::server::ClientHello<'_>,
|
|
||||||
) -> Option<sync::Arc<rustls::sign::CertifiedKey>> {
|
|
||||||
let domain = client_hello.server_name()?;
|
|
||||||
|
|
||||||
let domain: domain::Name = match domain.parse() {
|
|
||||||
Ok(domain) => domain,
|
|
||||||
Err(e) => {
|
|
||||||
log::warn!("failed to parse domain name {domain}: {e}");
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let res: unexpected::Result<Option<sync::Arc<rustls::sign::CertifiedKey>>> = (|| {
|
|
||||||
(self.0)
|
|
||||||
.gemini_store
|
|
||||||
.as_ref()
|
|
||||||
.or_unexpected_while("gemini store is not enabled")?
|
|
||||||
.get_certificate(&domain)
|
|
||||||
.or_unexpected_while("fetching pkey/cert")?
|
|
||||||
.map(|(pkey, cert)| {
|
|
||||||
let pkey = rustls::sign::any_supported_type(&pkey.into()).or_unexpected()?;
|
|
||||||
|
|
||||||
Ok(sync::Arc::new(rustls::sign::CertifiedKey {
|
|
||||||
cert: vec![cert.into()],
|
|
||||||
key: pkey,
|
|
||||||
ocsp: None,
|
|
||||||
sct_list: None,
|
|
||||||
}))
|
|
||||||
})
|
|
||||||
.transpose()
|
|
||||||
})();
|
|
||||||
|
|
||||||
res.unwrap_or_else(|err| {
|
|
||||||
log::error!("Unexpected error getting cert for domain {domain}: {err}");
|
|
||||||
None
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -35,12 +35,6 @@ impl Certificate {
|
|||||||
.set_subject_name(&name)
|
.set_subject_name(&name)
|
||||||
.or_unexpected_while("setting subject name")?;
|
.or_unexpected_while("setting subject name")?;
|
||||||
|
|
||||||
// 1970/01/01
|
|
||||||
let not_before = Asn1Time::from_unix(0).expect("initializing not_before");
|
|
||||||
builder
|
|
||||||
.set_not_before(not_before.as_ref())
|
|
||||||
.or_unexpected_while("setting not_before")?;
|
|
||||||
|
|
||||||
// 9999/07/23
|
// 9999/07/23
|
||||||
let not_after = Asn1Time::from_unix(253388296800).expect("initializing not_after");
|
let not_after = Asn1Time::from_unix(253388296800).expect("initializing not_after");
|
||||||
builder
|
builder
|
||||||
|
23
src/main.rs
23
src/main.rs
@ -4,7 +4,7 @@ use clap::Parser;
|
|||||||
use futures::stream::StreamExt;
|
use futures::stream::StreamExt;
|
||||||
use signal_hook_tokio::Signals;
|
use signal_hook_tokio::Signals;
|
||||||
|
|
||||||
use std::path;
|
use std::{path, sync};
|
||||||
|
|
||||||
#[derive(Parser, Debug)]
|
#[derive(Parser, Debug)]
|
||||||
#[command(version)]
|
#[command(version)]
|
||||||
@ -87,8 +87,6 @@ async fn main() {
|
|||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
let gemini_enabled = config.service.gemini.gemini_addr.is_some();
|
|
||||||
|
|
||||||
let origin_store = domani::origin::git::FSStore::new(&config.origin)
|
let origin_store = domani::origin::git::FSStore::new(&config.origin)
|
||||||
.expect("git origin store initialization failed");
|
.expect("git origin store initialization failed");
|
||||||
|
|
||||||
@ -130,14 +128,9 @@ async fn main() {
|
|||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
let domain_gemini_store = if gemini_enabled {
|
let domain_gemini_store =
|
||||||
Some(
|
|
||||||
domani::domain::gemini::FSStore::new(&config.domain.store_dir_path.join("gemini"))
|
domani::domain::gemini::FSStore::new(&config.domain.store_dir_path.join("gemini"))
|
||||||
.unwrap_or_else(|e| panic!("domain gemini store initialization failed: {e}")),
|
.unwrap_or_else(|e| panic!("domain gemini store initialization failed: {e}"));
|
||||||
)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut task_stack = domani::task_stack::TaskStack::new();
|
let mut task_stack = domani::task_stack::TaskStack::new();
|
||||||
|
|
||||||
@ -147,24 +140,20 @@ async fn main() {
|
|||||||
domain_store,
|
domain_store,
|
||||||
domain_checker,
|
domain_checker,
|
||||||
domain_acme_manager,
|
domain_acme_manager,
|
||||||
domain_gemini_store,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
let _ = domani::service::http::Service::new(
|
let _ = domani::service::http::new(
|
||||||
&mut task_stack,
|
&mut task_stack,
|
||||||
domain_manager.clone(),
|
domain_manager.clone(),
|
||||||
domani::domain::manager::HttpsCertResolver::from(domain_manager.clone()),
|
domain_manager.clone(),
|
||||||
config.service.clone(),
|
config.service.clone(),
|
||||||
);
|
);
|
||||||
|
|
||||||
if gemini_enabled {
|
|
||||||
let _ = domani::service::gemini::Service::new(
|
let _ = domani::service::gemini::Service::new(
|
||||||
&mut task_stack,
|
&mut task_stack,
|
||||||
domain_manager.clone(),
|
sync::Arc::new(domain_gemini_store),
|
||||||
domani::domain::manager::GeminiCertResolver::from(domain_manager.clone()),
|
|
||||||
config.service,
|
config.service,
|
||||||
);
|
);
|
||||||
}
|
|
||||||
|
|
||||||
let mut signals =
|
let mut signals =
|
||||||
Signals::new(signal_hook::consts::TERM_SIGNALS).expect("initializing signals failed");
|
Signals::new(signal_hook::consts::TERM_SIGNALS).expect("initializing signals failed");
|
||||||
|
@ -332,7 +332,7 @@ impl super::Store for FSStore {
|
|||||||
|
|
||||||
// TODO this is very not ideal, the whole file is first read totally into memory, and then
|
// TODO this is very not ideal, the whole file is first read totally into memory, and then
|
||||||
// that is cloned.
|
// that is cloned.
|
||||||
let data = bytes::Bytes::copy_from_slice(file_object.data.as_slice());
|
let data = file_object.data.clone();
|
||||||
Ok(Box::pin(stream::once(async move { Ok(data) })))
|
Ok(Box::pin(stream::once(async move { Ok(data) })))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,32 +3,3 @@ pub mod gemini;
|
|||||||
pub mod http;
|
pub mod http;
|
||||||
|
|
||||||
pub use config::*;
|
pub use config::*;
|
||||||
|
|
||||||
use std::borrow;
|
|
||||||
|
|
||||||
fn append_index_to_path<'path, 'index>(
|
|
||||||
path: &'path str,
|
|
||||||
index: &'index str,
|
|
||||||
) -> borrow::Cow<'path, str> {
|
|
||||||
if path.len() == 0 {
|
|
||||||
let mut path = String::with_capacity(1 + index.len());
|
|
||||||
path.push('/');
|
|
||||||
path.push_str(index);
|
|
||||||
return borrow::Cow::Owned(path);
|
|
||||||
}
|
|
||||||
|
|
||||||
if path.ends_with('/') {
|
|
||||||
let mut indexed_path = String::with_capacity(path.len() + index.len());
|
|
||||||
indexed_path.push_str(path.as_ref());
|
|
||||||
indexed_path.push_str(index);
|
|
||||||
return borrow::Cow::Owned(indexed_path);
|
|
||||||
}
|
|
||||||
|
|
||||||
borrow::Cow::Borrowed(path)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn guess_mime(path: &str) -> String {
|
|
||||||
mime_guess::from_path(path)
|
|
||||||
.first_or_octet_stream()
|
|
||||||
.to_string()
|
|
||||||
}
|
|
||||||
|
@ -4,13 +4,12 @@ mod proxy;
|
|||||||
pub use config::*;
|
pub use config::*;
|
||||||
|
|
||||||
use crate::error::unexpected::{self, Mappable};
|
use crate::error::unexpected::{self, Mappable};
|
||||||
use crate::{domain, service, task_stack, util};
|
use crate::{domain, service, task_stack};
|
||||||
|
|
||||||
use std::sync;
|
use std::sync;
|
||||||
use tokio_util::sync::CancellationToken;
|
use tokio_util::sync::CancellationToken;
|
||||||
|
|
||||||
pub struct Service {
|
pub struct Service {
|
||||||
domain_manager: sync::Arc<dyn domain::manager::Manager>,
|
|
||||||
cert_resolver: sync::Arc<dyn rustls::server::ResolvesServerCert>,
|
cert_resolver: sync::Arc<dyn rustls::server::ResolvesServerCert>,
|
||||||
config: service::Config,
|
config: service::Config,
|
||||||
}
|
}
|
||||||
@ -25,92 +24,19 @@ enum HandleConnError {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Service {
|
impl Service {
|
||||||
pub fn new<CertResolver>(
|
pub fn new(
|
||||||
task_stack: &mut task_stack::TaskStack<unexpected::Error>,
|
task_stack: &mut task_stack::TaskStack<unexpected::Error>,
|
||||||
domain_manager: sync::Arc<dyn domain::manager::Manager>,
|
cert_resolver: sync::Arc<dyn rustls::server::ResolvesServerCert>,
|
||||||
cert_resolver: CertResolver,
|
|
||||||
config: service::Config,
|
config: service::Config,
|
||||||
) -> sync::Arc<Service>
|
) -> sync::Arc<Service> {
|
||||||
where
|
|
||||||
CertResolver: rustls::server::ResolvesServerCert + 'static,
|
|
||||||
{
|
|
||||||
let service = sync::Arc::new(Service {
|
let service = sync::Arc::new(Service {
|
||||||
domain_manager,
|
cert_resolver,
|
||||||
cert_resolver: sync::Arc::from(cert_resolver),
|
|
||||||
config,
|
config,
|
||||||
});
|
});
|
||||||
task_stack.push_spawn(|canceller| listen(service.clone(), canceller));
|
task_stack.push_spawn(|canceller| listen(service.clone(), canceller));
|
||||||
service
|
service
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn respond_conn<W>(
|
|
||||||
&self,
|
|
||||||
w: W,
|
|
||||||
code: &str,
|
|
||||||
meta: &str,
|
|
||||||
body: Option<util::BoxByteStream>,
|
|
||||||
) -> unexpected::Result<()>
|
|
||||||
where
|
|
||||||
W: tokio::io::AsyncWrite + Unpin,
|
|
||||||
{
|
|
||||||
use tokio::io::{copy, AsyncWriteExt, BufWriter};
|
|
||||||
let mut w = BufWriter::new(w);
|
|
||||||
|
|
||||||
w.write_all(code.as_bytes()).await.or_unexpected()?;
|
|
||||||
w.write_all(" ".as_bytes()).await.or_unexpected()?;
|
|
||||||
w.write_all(meta.as_bytes()).await.or_unexpected()?;
|
|
||||||
w.write_all("\r\n".as_bytes()).await.or_unexpected()?;
|
|
||||||
|
|
||||||
if let Some(body) = body {
|
|
||||||
let mut body = tokio_util::io::StreamReader::new(body);
|
|
||||||
copy(&mut body, &mut w).await.or_unexpected()?;
|
|
||||||
}
|
|
||||||
|
|
||||||
w.flush().await.or_unexpected()?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn serve_conn<IO>(&self, domain: &domain::Name, conn: IO) -> Result<(), HandleConnError>
|
|
||||||
where
|
|
||||||
IO: tokio::io::AsyncRead + tokio::io::AsyncWrite + Unpin,
|
|
||||||
{
|
|
||||||
use tokio::io::*;
|
|
||||||
|
|
||||||
let (r, w) = split(conn);
|
|
||||||
let mut r = BufReader::new(r);
|
|
||||||
|
|
||||||
let mut req = String::with_capacity(64);
|
|
||||||
r.read_line(&mut req)
|
|
||||||
.await
|
|
||||||
.map_err(|e| HandleConnError::ClientError(format!("failed to read request: {e}")))?;
|
|
||||||
|
|
||||||
let req = gemini::request::parse::request(req.as_bytes())
|
|
||||||
.map(|(_, req)| req)
|
|
||||||
.map_err(|e| HandleConnError::ClientError(format!("failed to parse request: {e}")))?
|
|
||||||
.into_gemini_request()
|
|
||||||
.map_err(|e| HandleConnError::ClientError(format!("failed to parse request: {e}")))?;
|
|
||||||
|
|
||||||
let path = service::append_index_to_path(req.path(), "index.gmi");
|
|
||||||
|
|
||||||
let f = match self.domain_manager.get_file(domain, &path) {
|
|
||||||
Ok(f) => f,
|
|
||||||
Err(domain::manager::GetFileError::DomainNotFound) => {
|
|
||||||
return Err(unexpected::Error::from("domain not found when serving file").into())
|
|
||||||
}
|
|
||||||
Err(domain::manager::GetFileError::FileNotFound) => {
|
|
||||||
return Ok(self.respond_conn(w, "51", "File not found", None).await?)
|
|
||||||
}
|
|
||||||
Err(domain::manager::GetFileError::Unexpected(e)) => return Err(e.into()),
|
|
||||||
};
|
|
||||||
|
|
||||||
let content_type = service::guess_mime(&path);
|
|
||||||
|
|
||||||
Ok(self
|
|
||||||
.respond_conn(w, "20", content_type.as_str(), Some(f))
|
|
||||||
.await?)
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn proxy_conn<IO>(
|
async fn proxy_conn<IO>(
|
||||||
&self,
|
&self,
|
||||||
proxied_domain: &ConfigProxiedDomain,
|
proxied_domain: &ConfigProxiedDomain,
|
||||||
@ -133,7 +59,7 @@ impl Service {
|
|||||||
async fn handle_conn(
|
async fn handle_conn(
|
||||||
&self,
|
&self,
|
||||||
conn: tokio::net::TcpStream,
|
conn: tokio::net::TcpStream,
|
||||||
tls_config: sync::Arc<rustls::ServerConfig>,
|
_tls_config: sync::Arc<rustls::ServerConfig>,
|
||||||
) -> Result<(), HandleConnError> {
|
) -> Result<(), HandleConnError> {
|
||||||
let teed_conn = {
|
let teed_conn = {
|
||||||
let (r, w) = tokio::io::split(conn);
|
let (r, w) = tokio::io::split(conn);
|
||||||
@ -166,8 +92,9 @@ impl Service {
|
|||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
let conn = start.into_stream(tls_config).await.or_unexpected()?;
|
return Err(HandleConnError::ClientError(format!(
|
||||||
self.serve_conn(&domain, conn).await
|
"unknown domain {domain}"
|
||||||
|
)));
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
return Err(unexpected::Error::from(
|
return Err(unexpected::Error::from(
|
||||||
@ -183,26 +110,20 @@ async fn listen(
|
|||||||
service: sync::Arc<Service>,
|
service: sync::Arc<Service>,
|
||||||
canceller: CancellationToken,
|
canceller: CancellationToken,
|
||||||
) -> unexpected::Result<()> {
|
) -> unexpected::Result<()> {
|
||||||
let addr = &service
|
|
||||||
.config
|
|
||||||
.gemini
|
|
||||||
.gemini_addr
|
|
||||||
.expect("listen called with gemini_addr not set");
|
|
||||||
|
|
||||||
let tls_config = sync::Arc::new(
|
let tls_config = sync::Arc::new(
|
||||||
rustls::server::ServerConfig::builder()
|
rustls::server::ServerConfig::builder()
|
||||||
.with_safe_defaults()
|
.with_safe_defaults()
|
||||||
.with_no_client_auth()
|
.with_no_client_auth() // TODO maybe this isn't right?
|
||||||
.with_cert_resolver(service.cert_resolver.clone()),
|
.with_cert_resolver(service.cert_resolver.clone()),
|
||||||
);
|
);
|
||||||
|
|
||||||
log::info!(
|
log::info!(
|
||||||
"Listening on gemini://{}:{}",
|
"Listening on gemini://{}:{}",
|
||||||
&service.config.primary_domain.clone(),
|
&service.config.primary_domain.clone(),
|
||||||
addr,
|
&service.config.gemini.gemini_addr.port(),
|
||||||
);
|
);
|
||||||
|
|
||||||
let listener = tokio::net::TcpListener::bind(addr)
|
let listener = tokio::net::TcpListener::bind(service.config.gemini.gemini_addr)
|
||||||
.await
|
.await
|
||||||
.or_unexpected_while("binding tcp socket")?;
|
.or_unexpected_while("binding tcp socket")?;
|
||||||
|
|
||||||
|
@ -6,8 +6,8 @@ use serde_with::{serde_as, TryFromInto};
|
|||||||
|
|
||||||
use std::{collections, net, str::FromStr};
|
use std::{collections, net, str::FromStr};
|
||||||
|
|
||||||
fn default_gemini_addr() -> Option<net::SocketAddr> {
|
fn default_gemini_addr() -> net::SocketAddr {
|
||||||
Some(net::SocketAddr::from_str("[::]:3965").unwrap())
|
net::SocketAddr::from_str("[::]:3965").unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Clone)]
|
#[derive(Deserialize, Serialize, Clone)]
|
||||||
@ -65,7 +65,7 @@ pub struct ConfigProxiedDomain {
|
|||||||
#[derive(Deserialize, Serialize, Clone)]
|
#[derive(Deserialize, Serialize, Clone)]
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
#[serde(default = "default_gemini_addr")]
|
#[serde(default = "default_gemini_addr")]
|
||||||
pub gemini_addr: Option<net::SocketAddr>,
|
pub gemini_addr: net::SocketAddr,
|
||||||
pub proxied_domains: collections::HashMap<domain::Name, ConfigProxiedDomain>,
|
pub proxied_domains: collections::HashMap<domain::Name, ConfigProxiedDomain>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -23,6 +23,31 @@ pub struct Service {
|
|||||||
config: service::Config,
|
config: service::Config,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn new(
|
||||||
|
task_stack: &mut task_stack::TaskStack<unexpected::Error>,
|
||||||
|
domain_manager: sync::Arc<dyn domain::manager::Manager>,
|
||||||
|
cert_resolver: sync::Arc<dyn rustls::server::ResolvesServerCert>,
|
||||||
|
config: service::Config,
|
||||||
|
) -> sync::Arc<Service> {
|
||||||
|
let https_enabled = config.http.https_addr.is_some();
|
||||||
|
|
||||||
|
let service = sync::Arc::new(Service {
|
||||||
|
domain_manager: domain_manager.clone(),
|
||||||
|
cert_resolver,
|
||||||
|
handlebars: tpl::get(),
|
||||||
|
config,
|
||||||
|
});
|
||||||
|
|
||||||
|
task_stack.push_spawn(|canceller| tasks::listen_http(service.clone(), canceller));
|
||||||
|
|
||||||
|
if https_enabled {
|
||||||
|
task_stack.push_spawn(|canceller| tasks::listen_https(service.clone(), canceller));
|
||||||
|
task_stack.push_spawn(|canceller| tasks::cert_refresher(service.clone(), canceller));
|
||||||
|
}
|
||||||
|
|
||||||
|
service
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
struct BasePresenter<'a, T> {
|
struct BasePresenter<'a, T> {
|
||||||
page_name: &'a str,
|
page_name: &'a str,
|
||||||
@ -52,39 +77,15 @@ struct DomainSyncArgs {
|
|||||||
url_encoded_domain_settings: util::UrlEncodedDomainSettings,
|
url_encoded_domain_settings: util::UrlEncodedDomainSettings,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Service {
|
impl<'svc> Service {
|
||||||
pub fn new<CertResolver>(
|
|
||||||
task_stack: &mut task_stack::TaskStack<unexpected::Error>,
|
|
||||||
domain_manager: sync::Arc<dyn domain::manager::Manager>,
|
|
||||||
cert_resolver: CertResolver,
|
|
||||||
config: service::Config,
|
|
||||||
) -> sync::Arc<Service>
|
|
||||||
where
|
|
||||||
CertResolver: rustls::server::ResolvesServerCert + 'static,
|
|
||||||
{
|
|
||||||
let https_enabled = config.http.https_addr.is_some();
|
|
||||||
|
|
||||||
let service = sync::Arc::new(Service {
|
|
||||||
domain_manager: domain_manager.clone(),
|
|
||||||
cert_resolver: sync::Arc::from(cert_resolver),
|
|
||||||
handlebars: tpl::get(),
|
|
||||||
config,
|
|
||||||
});
|
|
||||||
|
|
||||||
task_stack.push_spawn(|canceller| tasks::listen_http(service.clone(), canceller));
|
|
||||||
|
|
||||||
if https_enabled {
|
|
||||||
task_stack.push_spawn(|canceller| tasks::listen_https(service.clone(), canceller));
|
|
||||||
task_stack.push_spawn(|canceller| tasks::cert_refresher(service.clone(), canceller));
|
|
||||||
}
|
|
||||||
|
|
||||||
service
|
|
||||||
}
|
|
||||||
|
|
||||||
fn serve(&self, status_code: u16, path: &str, body: Body) -> Response<Body> {
|
fn serve(&self, status_code: u16, path: &str, body: Body) -> Response<Body> {
|
||||||
|
let content_type = mime_guess::from_path(path)
|
||||||
|
.first_or_octet_stream()
|
||||||
|
.to_string();
|
||||||
|
|
||||||
match Response::builder()
|
match Response::builder()
|
||||||
.status(status_code)
|
.status(status_code)
|
||||||
.header("Content-Type", service::guess_mime(path))
|
.header("Content-Type", content_type)
|
||||||
.body(body)
|
.body(body)
|
||||||
{
|
{
|
||||||
Ok(res) => res,
|
Ok(res) => res,
|
||||||
@ -160,10 +161,20 @@ impl Service {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn serve_origin(&self, domain: domain::Name, req: Request<Body>) -> Response<Body> {
|
async fn serve_origin(&self, domain: domain::Name, req: Request<Body>) -> Response<Body> {
|
||||||
let path = service::append_index_to_path(req.uri().path(), "index.html");
|
let mut path_owned;
|
||||||
|
let path = req.uri().path();
|
||||||
|
|
||||||
match self.domain_manager.get_file(&domain, &path) {
|
let path = match path.ends_with('/') {
|
||||||
Ok(f) => self.serve(200, &path, Body::wrap_stream(f)),
|
true => {
|
||||||
|
path_owned = String::from(path);
|
||||||
|
path_owned.push_str("index.html");
|
||||||
|
path_owned.as_str()
|
||||||
|
}
|
||||||
|
false => path,
|
||||||
|
};
|
||||||
|
|
||||||
|
match self.domain_manager.get_file(&domain, path) {
|
||||||
|
Ok(f) => self.serve(200, path, Body::wrap_stream(f)),
|
||||||
Err(domain::manager::GetFileError::DomainNotFound) => {
|
Err(domain::manager::GetFileError::DomainNotFound) => {
|
||||||
return self.render_error_page(404, "Domain not found")
|
return self.render_error_page(404, "Domain not found")
|
||||||
}
|
}
|
||||||
|
@ -10,6 +10,6 @@ pub fn open_file(path: &path::Path) -> io::Result<Option<fs::File>> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type BoxByteStream = futures::stream::BoxStream<'static, io::Result<bytes::Bytes>>;
|
pub type BoxByteStream = futures::stream::BoxStream<'static, io::Result<Vec<u8>>>;
|
||||||
|
|
||||||
pub type BoxFuture<'a, O> = pin::Pin<Box<dyn futures::Future<Output = O> + Send + 'a>>;
|
pub type BoxFuture<'a, O> = pin::Pin<Box<dyn futures::Future<Output = O> + Send + 'a>>;
|
||||||
|
Loading…
Reference in New Issue
Block a user