Added send function

This commit is contained in:
int 80h
2020-04-22 20:15:23 -04:00
parent a3d3cc7214
commit ac75542dc6
4 changed files with 114 additions and 32 deletions

View File

@@ -13,7 +13,7 @@ pub struct Config {
#[derive(Debug, Deserialize, Clone)] #[derive(Debug, Deserialize, Clone)]
pub struct Server { pub struct Server {
pub url: String, pub hostname: String,
pub dir: String, pub dir: String,
pub key: String, pub key: String,
pub cert: String, pub cert: String,
@@ -25,10 +25,10 @@ impl Config {
let config: Config = toml::from_str(&fd).unwrap(); let config: Config = toml::from_str(&fd).unwrap();
return config; return config;
} }
pub fn to_map(&self /*cfg: &config::Config*/) -> HashMap<String, String> { pub fn to_map(&self) -> HashMap<String, String> {
let mut map = HashMap::new(); let mut map = HashMap::new();
for srv in &self.server { for srv in &self.server {
map.insert(srv.url.clone(), srv.dir.clone()); map.insert(srv.hostname.clone(), srv.dir.clone());
} }
map map
} }

View File

@@ -1,4 +1,5 @@
#![allow(unused_imports)] #![allow(unused_imports)]
#![allow(dead_code)]
#[macro_use] #[macro_use]
extern crate serde_derive; extern crate serde_derive;
@@ -9,6 +10,7 @@ use std::fs;
use std::fs::File; use std::fs::File;
use std::io::{self, BufReader}; use std::io::{self, BufReader};
use std::net::ToSocketAddrs; use std::net::ToSocketAddrs;
use std::os::unix::fs::PermissionsExt;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::sync::Arc; use std::sync::Arc;
use tokio::io::AsyncWriteExt; use tokio::io::AsyncWriteExt;
@@ -23,9 +25,27 @@ use tokio_rustls::TlsAcceptor;
use url::Url; use url::Url;
mod config; mod config;
mod status;
mod tls; mod tls;
fn get_content(mut path: PathBuf, u: url::Url) -> Result<String, io::Error> { async fn send(
mut stream: TlsStream<TcpStream>,
stat: status::Status,
meta: String,
body: Option<String>,
) -> Result<(), io::Error> {
let mut s = format!("{}\t{}\r\n", stat as u8, meta);
stream.write_all(s.as_bytes()).await?;
stream.flush().await?;
if let Some(b) = body {
s = format!("{}", b);
}
stream.write_all(s.as_bytes()).await?;
stream.flush().await?;
Ok(())
}
fn get_content(path: PathBuf, u: url::Url) -> Result<String, io::Error> {
let meta = fs::metadata(&path).expect("Unable to read metadata"); let meta = fs::metadata(&path).expect("Unable to read metadata");
if meta.is_file() { if meta.is_file() {
return Ok(std::fs::read_to_string(path).expect("Unable to read file")); return Ok(std::fs::read_to_string(path).expect("Unable to read file"));
@@ -56,20 +76,30 @@ async fn handle_connection(
let url = Url::parse(&request).unwrap(); let url = Url::parse(&request).unwrap();
if url.scheme() != "gemini" { if url.scheme() != "gemini" {
stream.write_all(&b"53\tnot gemini scheme!\r\n"[..]).await?; send(
stream.flush().await?; stream,
panic!("Not gemini scheme"); status::Status::ProxyRequestRefused,
"Not a gemini scheme!\r\n".to_string(),
None,
)
.await?;
return Ok(());
} }
if url.path().to_string().contains("..") { if url.path().to_string().contains("..") {
stream.write_all(&b"50\tNot in path\r\n"[..]).await?; send(
stream.flush().await?; stream,
panic!("Contains .."); status::Status::PermanentFailure,
"Not in path!".to_string(),
None,
)
.await?;
return Ok(());
} }
let mut dir = String::new(); let mut dir = String::new();
for server in cfg.server { for server in cfg.server {
if Some(server.url.as_str()) == url.host_str() { if Some(server.hostname.as_str()) == url.host_str() {
dir = server.dir; dir = server.dir;
} }
} }
@@ -80,31 +110,42 @@ async fn handle_connection(
} }
if !path.exists() { if !path.exists() {
stream.write_all(&b"51\tNot found!\r\n"[..]).await?; send(
stream.flush().await?; stream,
status::Status::NotFound,
"Not found!\r\n".to_string(),
None,
)
.await?;
return Ok(()); return Ok(());
} }
// add error // add error
let meta = fs::metadata(&path).expect("Unable to read metadata"); let meta = fs::metadata(&path).expect("Unable to read metadata");
if meta.is_dir() { if meta.is_dir() {
if !url.path().ends_with("/") { if !url.path().ends_with("/") {
stream send(
.write_all(format!("31\t{}/\r\n", url).as_bytes()) stream,
.await?; status::Status::RedirectPermanent,
stream.flush().await?; format!("{}/\r\n", url),
None,
)
.await?;
return Ok(());
} }
if path.join("index.gemini").exists() { if path.join("index.gemini").exists() {
path.push("index.gemini"); path.push("index.gemini");
} }
} }
stream.write_all(&b"20\ttext/gemini\r\n"[..]).await?;
stream.flush().await?;
let content = get_content(path, url)?; let content = get_content(path, url)?;
stream.write_all(content.as_bytes()).await?; send(
stream.flush().await?; stream,
status::Status::Success,
"text/gemini".to_string(),
Some(content),
)
.await?;
Ok(()) Ok(())
} }

44
src/status.rs Normal file
View File

@@ -0,0 +1,44 @@
use std::fmt;
#[repr(u8)]
#[derive(Debug)]
pub enum Status {
Input = 10,
Success = 20,
SuccessEndOfSession = 21,
RedirectTemporary = 30,
RedirectPermanent = 31,
TemporaryFailure = 40,
ServerUnavailable = 41,
CGIError = 42,
ProxyError = 43,
SlowDown = 44,
PermanentFailure = 50,
NotFound = 51,
Gone = 52,
ProxyRequestRefused = 53,
BadRequest = 59,
ClientCertificateRequired = 60,
TransientCertificateRequested = 61,
AuthorisedCertificateRequired = 62,
CertificateNotAccepted = 63,
FutureCertificateRejected = 64,
ExpiredCertificateRejected = 65,
}
impl fmt::Display for Status {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{:?}", self)
// or, alternatively:
// fmt::Debug::fmt(self, f)
}
}
impl Status {
pub fn as_str(&self) -> &'static str {
match *self {
Status::Success => "20\t",
_ => "",
}
}
}

View File

@@ -34,6 +34,8 @@ pub fn load_key(path: &String) -> PrivateKey {
return key[0].clone(); return key[0].clone();
} }
// Rustls won't let self signed certs be used with sni which gemini requires.
// At 1.4.2 in https://gemini.circumlunar.space/docs/spec-spec.txt
pub struct CertResolver { pub struct CertResolver {
map: HashMap<String, Box<CertifiedKey>>, map: HashMap<String, Box<CertifiedKey>>,
} }
@@ -44,19 +46,14 @@ impl CertResolver {
for server in cfg.server.iter() { for server in cfg.server.iter() {
let key = load_key(&server.key); let key = load_key(&server.key);
// .chain_err(|| format!("Failed to load private key from {}", https.key_file))?; // .chain_err(|| format!("Failed to load private key from {}", server.key))?;
let certs = load_certs(&server.cert).unwrap(); let certs = load_certs(&server.cert).unwrap();
// .chain_err(|| format!("Failed to load certificate from {}", server.cert)); // .chain_err(|| format!("Failed to load certificate from {}", server.cert));
/*
let signer: Arc<Box<CertifiedKey>> = Arc::new(Box::new(
CertifiedKey::new(&key).map_err(|_| format!("Failed to create signer for {}", server.url))
));
*/
let signing_key = RSASigningKey::new(&key).unwrap(); let signing_key = RSASigningKey::new(&key).unwrap();
let signing_key_boxed: Arc<Box<dyn SigningKey>> = Arc::new(Box::new(signing_key)); let signing_key_boxed: Arc<Box<dyn SigningKey>> = Arc::new(Box::new(signing_key));
map.insert( map.insert(
server.url.clone(), server.hostname.clone(),
Box::new(rustls::sign::CertifiedKey::new(certs, signing_key_boxed)), Box::new(rustls::sign::CertifiedKey::new(certs, signing_key_boxed)),
); );
} }
@@ -69,8 +66,8 @@ impl CertResolver {
impl ResolvesServerCert for CertResolver { impl ResolvesServerCert for CertResolver {
fn resolve(&self, client_hello: ClientHello) -> Option<CertifiedKey> { fn resolve(&self, client_hello: ClientHello) -> Option<CertifiedKey> {
if let Some(url) = client_hello.server_name() { if let Some(hostname) = client_hello.server_name() {
if let Some(cert) = self.map.get(url.into()) { if let Some(cert) = self.map.get(hostname.into()) {
return Some(*cert.clone()); return Some(*cert.clone());
} }
} }