Added send function
This commit is contained in:
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
81
src/main.rs
81
src/main.rs
@@ -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
44
src/status.rs
Normal 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",
|
||||||
|
_ => "",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
15
src/tls.rs
15
src/tls.rs
@@ -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());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user