correctly adhere to spec in most request cases

This commit is contained in:
Jan Wolff 2020-05-26 06:49:20 +02:00
parent aa041cc4a6
commit 2ffc8ff0cc
5 changed files with 47 additions and 11 deletions

View file

@ -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"?
-----------------------

View file

@ -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

View file

@ -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(_) => {

View file

@ -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(_) => {}

View file

@ -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")
}