Added time
This commit is contained in:
@@ -15,3 +15,4 @@ toml = "*"
|
|||||||
serde = "*"
|
serde = "*"
|
||||||
serde_derive = "*"
|
serde_derive = "*"
|
||||||
url = "*"
|
url = "*"
|
||||||
|
chrono = "0.4"
|
||||||
|
|||||||
24
src/cgi.rs
24
src/cgi.rs
@@ -1,7 +1,7 @@
|
|||||||
|
use std::collections::HashMap;
|
||||||
use std::io::{self, BufReader};
|
use std::io::{self, BufReader};
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
use std::collections::HashMap;
|
|
||||||
use tokio::net::TcpStream;
|
use tokio::net::TcpStream;
|
||||||
use tokio_rustls::server::TlsStream;
|
use tokio_rustls::server::TlsStream;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
@@ -9,20 +9,19 @@ use url::Url;
|
|||||||
use crate::config;
|
use crate::config;
|
||||||
use crate::status;
|
use crate::status;
|
||||||
use crate::util;
|
use crate::util;
|
||||||
|
use crate::Connection;
|
||||||
|
|
||||||
pub async fn cgi(
|
pub async fn cgi(con: Connection, path: PathBuf, url: Url) -> Result<(), io::Error> {
|
||||||
stream: TlsStream<TcpStream>,
|
|
||||||
path: PathBuf,
|
|
||||||
url: Url,
|
|
||||||
cfg: config::Config,
|
|
||||||
) -> Result<(), io::Error> {
|
|
||||||
|
|
||||||
let mut envs = HashMap::new();
|
let mut envs = HashMap::new();
|
||||||
envs.insert("GEMINI_URL", url.as_str());
|
envs.insert("GEMINI_URL", url.as_str());
|
||||||
envs.insert("SERVER_NAME", url.host_str().unwrap());
|
envs.insert("SERVER_NAME", url.host_str().unwrap());
|
||||||
envs.insert("SCRIPT_NAME", path.file_name().unwrap().to_str().unwrap());
|
envs.insert("SCRIPT_NAME", path.file_name().unwrap().to_str().unwrap());
|
||||||
envs.insert("SERVER_PROTOCOL", "GEMINI");
|
envs.insert("SERVER_PROTOCOL", "GEMINI");
|
||||||
// envs.insert("SERVER_PORT", &(cfg.port as str));
|
let addr = con.peer_addr.ip().to_string();
|
||||||
|
envs.insert("REMOTE_ADDR", &addr);
|
||||||
|
let port = con.peer_addr.port().to_string();
|
||||||
|
envs.insert("REMOTE_PORT", &port);
|
||||||
|
// envs.insert("SERVER_PORT", con.cfg.port.clone().to_string());
|
||||||
|
|
||||||
if let Some(q) = url.query() {
|
if let Some(q) = url.query() {
|
||||||
envs.insert("QUERY_STRING", q);
|
envs.insert("QUERY_STRING", q);
|
||||||
@@ -34,10 +33,13 @@ pub async fn cgi(
|
|||||||
.output()
|
.output()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
if !cmd.status.success() {
|
if !cmd.status.success() {
|
||||||
util::send(stream, status::Status::CGIError, "CGI Error!", None).await?;
|
util::send_status(con.stream, status::Status::CGIError, "CGI Error!").await?;
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
let cmd = String::from_utf8(cmd.stdout).unwrap();
|
let cmd = String::from_utf8(cmd.stdout).unwrap();
|
||||||
util::send(stream, status::Status::Success, "text/gemini", Some(cmd)).await?;
|
// if cmd.starts_with("20") {
|
||||||
|
util::send_raw(con.stream, cmd).await?;
|
||||||
|
//util::send_body(stream, status::Status::Success, "text/gemini", Some(cmd)).await?;
|
||||||
|
// }
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|||||||
99
src/main.rs
99
src/main.rs
@@ -9,6 +9,7 @@ use std::error::Error;
|
|||||||
use std::fs;
|
use std::fs;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::{self, BufReader};
|
use std::io::{self, BufReader};
|
||||||
|
use std::net::SocketAddr;
|
||||||
use std::net::ToSocketAddrs;
|
use std::net::ToSocketAddrs;
|
||||||
use std::os::unix::fs::PermissionsExt;
|
use std::os::unix::fs::PermissionsExt;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
@@ -24,6 +25,7 @@ use tokio_rustls::rustls::{Certificate, NoClientAuth, PrivateKey, ServerConfig};
|
|||||||
use tokio_rustls::server::TlsStream;
|
use tokio_rustls::server::TlsStream;
|
||||||
use tokio_rustls::TlsAcceptor;
|
use tokio_rustls::TlsAcceptor;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
use chrono::{DateTime, Utc};
|
||||||
|
|
||||||
mod cgi;
|
mod cgi;
|
||||||
mod config;
|
mod config;
|
||||||
@@ -31,76 +33,70 @@ mod status;
|
|||||||
mod tls;
|
mod tls;
|
||||||
mod util;
|
mod util;
|
||||||
|
|
||||||
|
pub struct Connection {
|
||||||
|
stream: TlsStream<TcpStream>,
|
||||||
|
peer_addr: SocketAddr,
|
||||||
|
hostname: String,
|
||||||
|
dir: String,
|
||||||
|
cgi: String,
|
||||||
|
}
|
||||||
|
|
||||||
fn get_content(path: PathBuf, u: url::Url) -> Result<String, io::Error> {
|
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"));
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut list = String::from("# Directory Listing\n\n");
|
let mut list = String::from("# Directory Listing\r\n\r\n");
|
||||||
|
list.push_str(format!("Path: {}\r\n\r\n", u.path()).as_str());
|
||||||
// needs work
|
// needs work
|
||||||
for file in fs::read_dir(path)? {
|
for file in fs::read_dir(path)? {
|
||||||
if let Ok(file) = file {
|
if let Ok(file) = file {
|
||||||
let f = file.file_name().to_str().unwrap().to_owned();
|
let f = file.file_name().to_str().unwrap().to_owned();
|
||||||
let p = u.join(&f).unwrap().as_str().to_owned();
|
let p = u.join(&f).unwrap().as_str().to_owned();
|
||||||
println!("=>\t{} {}\r\n", p, f);
|
list.push_str(format!("=> {} {}\r\n", p, f).as_str());
|
||||||
list.push_str(format!("=> {} {}\n", p, f).as_str());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return Ok(list);
|
return Ok(list);
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn handle_connection(
|
async fn handle_connection(mut con: Connection) -> Result<(), io::Error> {
|
||||||
mut stream: TlsStream<TcpStream>,
|
let now: DateTime<Utc> = Utc::now();
|
||||||
cfg: config::Config,
|
println!("{} New Connection: {}", now, con.peer_addr);
|
||||||
) -> Result<(), io::Error> {
|
|
||||||
let mut buffer = [0; 512];
|
let mut buffer = [0; 512];
|
||||||
stream.read(&mut buffer).await?;
|
con.stream.read(&mut buffer).await?;
|
||||||
let request = String::from_utf8_lossy(&buffer[..]).to_owned();
|
let request = String::from_utf8_lossy(&buffer[..]).to_owned();
|
||||||
println!("Request: {}", request);
|
println!("Request: {}", request);
|
||||||
|
|
||||||
let url = Url::parse(&request).unwrap();
|
let url = Url::parse(&request).unwrap();
|
||||||
|
|
||||||
|
if Some(con.hostname.as_str()) != url.host_str() {
|
||||||
|
util::send_status(con.stream, status::Status::PermanentFailure, "Url doesn't match certificate!").await?;
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
if url.scheme() != "gemini" {
|
if url.scheme() != "gemini" {
|
||||||
util::send(
|
util::send_status(
|
||||||
stream,
|
con.stream,
|
||||||
status::Status::ProxyRequestRefused,
|
status::Status::ProxyRequestRefused,
|
||||||
"Not a gemini scheme!\r\n",
|
"Not a gemini scheme!\r\n",
|
||||||
None,
|
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
if url.path().to_string().contains("..") {
|
if url.path().to_string().contains("..") {
|
||||||
util::send(
|
util::send_status(con.stream, status::Status::PermanentFailure, "Not in path!").await?;
|
||||||
stream,
|
|
||||||
status::Status::PermanentFailure,
|
|
||||||
"Not in path!",
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut dir = String::new();
|
let mut path = PathBuf::from(&con.dir);
|
||||||
let mut cgi = String::new();
|
|
||||||
for server in &cfg.server {
|
|
||||||
if Some(server.hostname.as_str()) == url.host_str() {
|
|
||||||
dir = server.dir.to_string();
|
|
||||||
if server.cgi.is_some() {
|
|
||||||
cgi = server.cgi.as_ref().unwrap().to_string();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut path = PathBuf::from(dir);
|
|
||||||
if url.path() != "" || url.path() != "/" {
|
if url.path() != "" || url.path() != "/" {
|
||||||
path.push(url.path().trim_start_matches("/"));
|
path.push(url.path().trim_start_matches("/"));
|
||||||
}
|
}
|
||||||
|
|
||||||
if !path.exists() {
|
if !path.exists() {
|
||||||
util::send(stream, status::Status::NotFound, "Not found!\r\n", None).await?;
|
util::send_status(con.stream, status::Status::NotFound, "Not found!\r\n").await?;
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -109,11 +105,10 @@ async fn handle_connection(
|
|||||||
|
|
||||||
if meta.is_dir() {
|
if meta.is_dir() {
|
||||||
if !url.path().ends_with("/") {
|
if !url.path().ends_with("/") {
|
||||||
util::send(
|
util::send_status(
|
||||||
stream,
|
con.stream,
|
||||||
status::Status::RedirectPermanent,
|
status::Status::RedirectPermanent,
|
||||||
format!("{}/\r\n", url).as_str(),
|
format!("{}/\r\n", url).as_str(),
|
||||||
None,
|
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
return Ok(());
|
return Ok(());
|
||||||
@@ -124,14 +119,14 @@ async fn handle_connection(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// add timeout
|
// add timeout
|
||||||
if cgi.trim_end_matches("/") == path.parent().unwrap().to_str().unwrap() {
|
if con.cgi.trim_end_matches("/") == path.parent().unwrap().to_str().unwrap() {
|
||||||
cgi::cgi(stream, path, url, cfg).await?;
|
cgi::cgi(con, path, url).await?;
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
let content = get_content(path, url)?;
|
let content = get_content(path, url)?;
|
||||||
util::send(
|
util::send_body(
|
||||||
stream,
|
con.stream,
|
||||||
status::Status::Success,
|
status::Status::Success,
|
||||||
"text/gemini",
|
"text/gemini",
|
||||||
Some(content),
|
Some(content),
|
||||||
@@ -143,6 +138,7 @@ async fn handle_connection(
|
|||||||
|
|
||||||
fn main() -> io::Result<()> {
|
fn main() -> io::Result<()> {
|
||||||
let cfg = config::Config::new("config.toml");
|
let cfg = config::Config::new("config.toml");
|
||||||
|
println!("Serving {} vhosts", cfg.server.len());
|
||||||
|
|
||||||
let addr = format!("{}:{}", cfg.host, cfg.port);
|
let addr = format!("{}:{}", cfg.host, cfg.port);
|
||||||
addr.to_socket_addrs()?
|
addr.to_socket_addrs()?
|
||||||
@@ -167,9 +163,30 @@ fn main() -> io::Result<()> {
|
|||||||
let cfg = cfg.clone();
|
let cfg = cfg.clone();
|
||||||
|
|
||||||
let fut = async move {
|
let fut = async move {
|
||||||
let stream = acceptor.accept(stream).await?;
|
let mut stream = acceptor.accept(stream).await?;
|
||||||
handle_connection(stream, cfg).await?;
|
let (_, sni) = TlsStream::get_mut(&mut stream);
|
||||||
println!("Hello: {}", peer_addr);
|
let sni = sni.get_sni_hostname();
|
||||||
|
let mut dir = String::new();
|
||||||
|
let mut cgi = String::new();
|
||||||
|
let mut hostname = String::new();
|
||||||
|
for server in &cfg.server {
|
||||||
|
if Some(server.hostname.as_str()) == sni {
|
||||||
|
hostname = sni.unwrap().to_string();
|
||||||
|
dir = server.dir.to_string();
|
||||||
|
if server.cgi.is_some() {
|
||||||
|
cgi = server.cgi.as_ref().unwrap().to_string();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let con = Connection {
|
||||||
|
stream,
|
||||||
|
peer_addr,
|
||||||
|
hostname,
|
||||||
|
dir,
|
||||||
|
cgi,
|
||||||
|
};
|
||||||
|
handle_connection(con).await?;
|
||||||
|
|
||||||
Ok(()) as io::Result<()>
|
Ok(()) as io::Result<()>
|
||||||
};
|
};
|
||||||
|
|||||||
13
src/tls.rs
13
src/tls.rs
@@ -1,15 +1,14 @@
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::error::Error;
|
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::{self, BufReader};
|
use std::io::{self, BufReader};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use rustls::sign::CertifiedKey;
|
use rustls::sign::CertifiedKey;
|
||||||
use rustls::sign::{RSASigningKey, Signer, SigningKey};
|
use rustls::sign::{RSASigningKey, SigningKey};
|
||||||
use rustls::ClientHello;
|
use rustls::ClientHello;
|
||||||
use rustls::{ResolvesServerCert, SignatureScheme};
|
use rustls::ResolvesServerCert;
|
||||||
use tokio_rustls::rustls::internal::pemfile::{certs, pkcs8_private_keys};
|
use tokio_rustls::rustls::internal::pemfile::{certs, pkcs8_private_keys};
|
||||||
use tokio_rustls::rustls::{Certificate, NoClientAuth, PrivateKey, ServerConfig};
|
use tokio_rustls::rustls::{Certificate, PrivateKey};
|
||||||
|
|
||||||
use crate::config;
|
use crate::config;
|
||||||
|
|
||||||
@@ -30,7 +29,7 @@ pub fn load_certs(path: &String) -> io::Result<Vec<Certificate>> {
|
|||||||
pub fn load_key(path: &String) -> PrivateKey {
|
pub fn load_key(path: &String) -> PrivateKey {
|
||||||
let keyfile = File::open(path).expect("cannot open private key file");
|
let keyfile = File::open(path).expect("cannot open private key file");
|
||||||
let mut reader = BufReader::new(keyfile);
|
let mut reader = BufReader::new(keyfile);
|
||||||
let key = pkcs8_private_keys(&mut reader).expect("file contains invalid rsa private key");
|
let key = pkcs8_private_keys(&mut reader).expect("file contains invalid pkcs8 private key");
|
||||||
return key[0].clone();
|
return key[0].clone();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -46,9 +45,7 @@ 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 {}", 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));
|
|
||||||
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));
|
||||||
@@ -58,8 +55,6 @@ impl CertResolver {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
println!("Successfully loaded {} TLS configurations", map.len());
|
|
||||||
|
|
||||||
Ok(CertResolver { map })
|
Ok(CertResolver { map })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
18
src/util.rs
18
src/util.rs
@@ -5,7 +5,16 @@ use tokio_rustls::server::TlsStream;
|
|||||||
|
|
||||||
use crate::status;
|
use crate::status;
|
||||||
|
|
||||||
pub async fn send(
|
pub async fn send_status(
|
||||||
|
stream: TlsStream<TcpStream>,
|
||||||
|
stat: status::Status,
|
||||||
|
meta: &str,
|
||||||
|
) -> Result<(), io::Error> {
|
||||||
|
send_body(stream, stat, meta, None).await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn send_body(
|
||||||
mut stream: TlsStream<TcpStream>,
|
mut stream: TlsStream<TcpStream>,
|
||||||
stat: status::Status,
|
stat: status::Status,
|
||||||
meta: &str,
|
meta: &str,
|
||||||
@@ -17,7 +26,12 @@ pub async fn send(
|
|||||||
if let Some(b) = body {
|
if let Some(b) = body {
|
||||||
s = format!("{}", b);
|
s = format!("{}", b);
|
||||||
}
|
}
|
||||||
stream.write_all(s.as_bytes()).await?;
|
send_raw(stream, s).await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn send_raw(mut stream: TlsStream<TcpStream>, body: String) -> Result<(), io::Error> {
|
||||||
|
stream.write_all(body.as_bytes()).await?;
|
||||||
stream.flush().await?;
|
stream.flush().await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user