summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJan Wolff <janw@mailbox.org>2020-05-26 06:49:20 +0200
committerJan Wolff <janw@mailbox.org>2020-05-26 06:49:20 +0200
commit2ffc8ff0ccb0bfad7d69104cbc00b167589c780b (patch)
treeff344bee57957c237bba560d59713a09ce3f3189
parentaa041cc4a6d2ed4c817eadfdd36d3bd73d0f0cf1 (diff)
correctly adhere to spec in most request cases
-rw-r--r--README.md13
-rw-r--r--doc/sheldond.conf4
-rw-r--r--src/server/handler.rs31
-rw-r--r--src/server/mod.rs4
-rw-r--r--src/server/response.rs6
5 files changed, 47 insertions, 11 deletions
diff --git a/README.md b/README.md
index 9a134dd..c5d2d59 100644
--- a/README.md
+++ b/README.md
@@ -63,6 +63,19 @@ Note: This sets the user to `nobody` and the group to `nobody` as well. This
naming scheme is not consistent for all Unix systems... Try changing the group
name to `nogroup` if the software fails to start.
+Testing
+-------
+
+As you may have spotted, I did not get around to write a test suite for this.
+The server's behavior can be tested using the
+[gemini-diagnostics](https://github.com/michael-lazar/gemini-diagnostics) suite
+by michael-lazar. It passes all "important" tests (some malformed requests
+are still handled). Most importantly: the URLDotEscape tests fails. This does
+not mean you can successfully a URL escape attack against this, rather the URL
+library I use already parses out any superfluous ..'s.
+e.g. "localhost/../../../etc/passwd" already became "localhost/etc/passwd" once
+I receive the parsed URL from the library.
+
Why "Sheldon Director"?
-----------------------
diff --git a/doc/sheldond.conf b/doc/sheldond.conf
index 7c39f06..1c04c87 100644
--- a/doc/sheldond.conf
+++ b/doc/sheldond.conf
@@ -5,8 +5,8 @@ default_host = localhost
gem_root = ./doc
# you can define as many of these as you like
-listen = [::1]:1965
-listen = 127.0.0.1:1965
+listen = 0.0.0.0:1965
+listen = [::]:1965
# privilege level for the server to drop to after initializing
user = nobody
diff --git a/src/server/handler.rs b/src/server/handler.rs
index f32a015..db45073 100644
--- a/src/server/handler.rs
+++ b/src/server/handler.rs
@@ -20,11 +20,10 @@ fn send_header(stream: &mut SslStream<TcpStream>, header: &response::Header) {
}
pub fn handle_request(config: &ServerConfig, mut stream: SslStream<TcpStream>) {
- let mut buffer = [0; 1026];
+ let mut buffer = [0; 1024];
match stream.ssl_read(&mut buffer) {
Ok(s) => {
- if s == 0 {
- println!("received empty request buffer");
+ if s == 0 || s > 1025 {
send_header(&mut stream, &response::bad_request());
return;
}
@@ -47,7 +46,11 @@ pub fn handle_request(config: &ServerConfig, mut stream: SslStream<TcpStream>) {
let location = match Url::parse(&request) {
Ok(url) => url,
- Err(_) => config.default_host.join(&request).unwrap(),
+ Err(_) => {
+ println!("received invalid request url");
+ send_header(&mut stream, &response::bad_request());
+ return;
+ },
};
handle_response(config, location, &mut stream);
@@ -69,25 +72,39 @@ fn write_line<T: Write>(line: &[u8], stream: &mut BufWriter<T>) -> Result<(), Er
fn handle_response(config: &ServerConfig, url: Url, mut stream: &mut SslStream<TcpStream>) {
println!("responding for: {}", url);
- if url.scheme() != "gemini" {
- send_header(&mut stream, &response::permanent_failure());
+ // url scheme must be either "gemini://" or "//" (empty)
+ if url.scheme() != "gemini" && url.scheme() != "" {
+ send_header(&mut stream, &response::proxy_request_refused());
return;
}
+ // url request host must match
if url.host() != config.default_host.host() {
send_header(&mut stream, &response::proxy_request_refused());
return;
}
+ // TODO: also drop on incorrect port in request
+
let rel_path = match Path::new(url.path()).strip_prefix("/") {
Ok(path) => path,
Err(_) => {
- Path::new("")
+ // empty path, gemini spec says to redirect client to root
+ send_header(&mut stream, &response::redirect_permanent(
+ config.default_host.as_str(),
+ ));
+ return;
}
};
let path = gen_path_index(&config.gem_root.join(rel_path));
+ // make sure we can't escape gem_root
+ if !path.starts_with(&config.gem_root) {
+ send_header(&mut stream, &response::bad_request());
+ return;
+ }
+
let file = match File::open(&path) {
Ok(file) => file,
Err(_) => {
diff --git a/src/server/mod.rs b/src/server/mod.rs
index 9909fd2..a38e194 100644
--- a/src/server/mod.rs
+++ b/src/server/mod.rs
@@ -24,7 +24,7 @@ pub struct ServerConfig {
impl ServerConfig {
pub fn new() -> ServerConfig {
ServerConfig {
- default_host: Url::parse("gemini://localhost").unwrap(),
+ default_host: Url::parse("gemini://localhost/").unwrap(),
gem_root: PathBuf::from(""),
addrs: Vec::new(),
user: unistd::getuid(),
@@ -35,7 +35,7 @@ impl ServerConfig {
}
pub fn set_default_host(&mut self, default_host: String) {
- let mut url = Url::parse("gemini://default").unwrap();
+ let mut url = Url::parse("gemini://default/").unwrap();
match url.set_host(Some(default_host.as_str())) {
Ok(_) => {}
diff --git a/src/server/response.rs b/src/server/response.rs
index 913d881..263b2e3 100644
--- a/src/server/response.rs
+++ b/src/server/response.rs
@@ -3,6 +3,7 @@ use std::vec::Vec;
#[derive(Copy, Clone)]
pub enum Status {
Success = 20,
+ RedirectPermanent = 31,
PermanentFailure = 50,
NotFound = 51,
ProxyRequestRefused = 53,
@@ -31,6 +32,10 @@ impl Header {
}
}
+pub fn redirect_permanent(meta: &str) -> Header {
+ Header::new(Status::RedirectPermanent, meta)
+}
+
pub fn permanent_failure() -> Header {
Header::new(Status::PermanentFailure, "permanent failure")
}
@@ -46,3 +51,4 @@ pub fn proxy_request_refused() -> Header {
pub fn bad_request() -> Header {
Header::new(Status::BadRequest, "bad request")
}
+