Added redirect and cgienv
This commit is contained in:
18
src/cgi.rs
18
src/cgi.rs
@@ -1,14 +1,20 @@
|
||||
use std::collections::HashMap;
|
||||
use std::io;
|
||||
use std::path::{PathBuf};
|
||||
use std::path::PathBuf;
|
||||
use std::process::Command;
|
||||
use url::Url;
|
||||
|
||||
use crate::status::Status;
|
||||
use crate::config;
|
||||
use crate::conn;
|
||||
use crate::logger;
|
||||
use crate::status::Status;
|
||||
|
||||
pub async fn cgi(mut con: conn::Connection, path: PathBuf, url: Url) -> Result<(), io::Error> {
|
||||
pub async fn cgi(
|
||||
mut con: conn::Connection,
|
||||
srv: &config::ServerCfg,
|
||||
path: PathBuf,
|
||||
url: Url,
|
||||
) -> Result<(), io::Error> {
|
||||
let mut envs = HashMap::new();
|
||||
envs.insert("GATEWAY_INTERFACE", "CGI/1.1");
|
||||
envs.insert("GEMINI_URL", url.as_str());
|
||||
@@ -26,6 +32,12 @@ pub async fn cgi(mut con: conn::Connection, path: PathBuf, url: Url) -> Result<(
|
||||
envs.insert("QUERY_STRING", q);
|
||||
}
|
||||
|
||||
if srv.cgienv.len() != 0 {
|
||||
for (k, v) in srv.cgienv.iter() {
|
||||
envs.insert(&k, &v);
|
||||
}
|
||||
};
|
||||
|
||||
let cmd = Command::new(path.to_str().unwrap())
|
||||
.env_clear()
|
||||
.envs(&envs)
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
extern crate serde_derive;
|
||||
extern crate toml;
|
||||
use std::collections::HashMap;
|
||||
use toml::de::Error;
|
||||
use std::path::Path;
|
||||
use toml::de::Error;
|
||||
|
||||
#[derive(Debug, Deserialize, Clone)]
|
||||
pub struct Config {
|
||||
@@ -18,8 +18,10 @@ pub struct Server {
|
||||
pub key: String,
|
||||
pub cert: String,
|
||||
pub cgi: Option<String>,
|
||||
pub cgienv: Option<HashMap<String, String>>,
|
||||
pub usrdir: Option<bool>,
|
||||
pub proxy: Option<HashMap<String, String>>,
|
||||
pub redirect: Option<HashMap<String, String>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
@@ -29,9 +31,11 @@ pub struct ServerCfg {
|
||||
pub key: String,
|
||||
pub cert: String,
|
||||
pub cgi: String,
|
||||
pub cgienv: HashMap<String, String>,
|
||||
pub usrdir: bool,
|
||||
pub port: u16,
|
||||
pub proxy: HashMap<String, String>,
|
||||
pub redirect: HashMap<String, String>,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
@@ -54,18 +58,36 @@ impl Config {
|
||||
let mut pmap = HashMap::new();
|
||||
match &srv.proxy {
|
||||
Some(pr) => pmap = pr.clone(),
|
||||
None => {},
|
||||
None => {}
|
||||
};
|
||||
map.insert(srv.hostname.clone(), ServerCfg {
|
||||
hostname: srv.hostname.clone(),
|
||||
dir: srv.dir.clone(),
|
||||
key: srv.key.clone(),
|
||||
cert: srv.cert.clone(),
|
||||
cgi: cgi,
|
||||
usrdir: usrdir,
|
||||
port: self.port.clone(),
|
||||
proxy: pmap.clone(),
|
||||
});
|
||||
|
||||
let mut rmap = HashMap::new();
|
||||
match &srv.redirect {
|
||||
Some(r) => rmap = r.clone(),
|
||||
None => {}
|
||||
};
|
||||
|
||||
let mut cmap = HashMap::new();
|
||||
match &srv.cgienv {
|
||||
Some(c) => cmap = c.clone(),
|
||||
None => {}
|
||||
};
|
||||
|
||||
map.insert(
|
||||
srv.hostname.clone(),
|
||||
ServerCfg {
|
||||
hostname: srv.hostname.clone(),
|
||||
dir: srv.dir.clone(),
|
||||
key: srv.key.clone(),
|
||||
cert: srv.cert.clone(),
|
||||
cgi: cgi,
|
||||
cgienv: cmap.clone(),
|
||||
usrdir: usrdir,
|
||||
port: self.port.clone(),
|
||||
proxy: pmap.clone(),
|
||||
redirect: rmap.clone(),
|
||||
},
|
||||
);
|
||||
}
|
||||
map
|
||||
}
|
||||
|
||||
56
src/conn.rs
56
src/conn.rs
@@ -12,36 +12,32 @@ pub struct Connection {
|
||||
}
|
||||
|
||||
impl Connection {
|
||||
pub async fn send_status(
|
||||
&mut self,
|
||||
stat: Status,
|
||||
meta: Option<&str>,
|
||||
) -> Result<(), io::Error> {
|
||||
self.send_body(stat, meta, None).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn send_body(
|
||||
&mut self,
|
||||
stat: Status,
|
||||
meta: Option<&str>,
|
||||
body: Option<String>,
|
||||
) -> Result<(), io::Error> {
|
||||
let meta = match meta {
|
||||
Some(m) => m,
|
||||
None => &stat.to_str(),
|
||||
};
|
||||
let mut s = format!("{}\t{}\r\n", stat as u8, meta);
|
||||
if let Some(b) = body {
|
||||
s += &b;
|
||||
pub async fn send_status(&mut self, stat: Status, meta: Option<&str>) -> Result<(), io::Error> {
|
||||
self.send_body(stat, meta, None).await?;
|
||||
Ok(())
|
||||
}
|
||||
self.send_raw(s.as_bytes()).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn send_raw(&mut self, body: &[u8]) -> Result<(), io::Error> {
|
||||
self.stream.write_all(body).await?;
|
||||
self.stream.flush().await?;
|
||||
Ok(())
|
||||
}
|
||||
pub async fn send_body(
|
||||
&mut self,
|
||||
stat: Status,
|
||||
meta: Option<&str>,
|
||||
body: Option<String>,
|
||||
) -> Result<(), io::Error> {
|
||||
let meta = match meta {
|
||||
Some(m) => m,
|
||||
None => &stat.to_str(),
|
||||
};
|
||||
let mut s = format!("{}\t{}\r\n", stat as u8, meta);
|
||||
if let Some(b) = body {
|
||||
s += &b;
|
||||
}
|
||||
self.send_raw(s.as_bytes()).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn send_raw(&mut self, body: &[u8]) -> Result<(), io::Error> {
|
||||
self.stream.write_all(body).await?;
|
||||
self.stream.flush().await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,10 @@
|
||||
use std::net::SocketAddr;
|
||||
use log::{info, warn};
|
||||
use crate::status;
|
||||
use log::{info, warn};
|
||||
use std::net::SocketAddr;
|
||||
|
||||
pub fn logger(addr: SocketAddr, stat: status::Status, req: &str) {
|
||||
match stat as u8 {
|
||||
20..=29 => info!(
|
||||
"remote={} status={} request={}",
|
||||
addr, stat as u8, req
|
||||
),
|
||||
_ => warn!(
|
||||
"remote={} status={} request={}",
|
||||
addr, stat as u8, req
|
||||
),
|
||||
20..=29 => info!("remote={} status={} request={}", addr, stat as u8, req),
|
||||
_ => warn!("remote={} status={} request={}", addr, stat as u8, req),
|
||||
}
|
||||
}
|
||||
|
||||
114
src/main.rs
114
src/main.rs
@@ -2,30 +2,30 @@
|
||||
extern crate serde_derive;
|
||||
|
||||
use futures_util::future::TryFutureExt;
|
||||
use mime;
|
||||
use mime_guess;
|
||||
use openssl::ssl::NameType;
|
||||
use std::env;
|
||||
use std::fs;
|
||||
use std::fs::File;
|
||||
use std::io::{self, BufReader, BufRead};
|
||||
use std::io::{self, BufRead, BufReader};
|
||||
use std::net::ToSocketAddrs;
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::env;
|
||||
use tokio::io::AsyncWriteExt;
|
||||
use tokio::net::TcpListener;
|
||||
use tokio::prelude::*;
|
||||
use tokio::runtime;
|
||||
use openssl::ssl::NameType;
|
||||
use url::Url;
|
||||
use mime_guess;
|
||||
use mime;
|
||||
|
||||
mod cgi;
|
||||
mod config;
|
||||
mod status;
|
||||
use status::Status;
|
||||
mod tls;
|
||||
mod conn;
|
||||
mod revproxy;
|
||||
mod logger;
|
||||
mod revproxy;
|
||||
mod tls;
|
||||
|
||||
fn get_mime(path: &PathBuf) -> String {
|
||||
let mut mime = "text/gemini";
|
||||
@@ -38,14 +38,15 @@ fn get_mime(path: &PathBuf) -> String {
|
||||
if ext != "gemini" {
|
||||
m = mime_guess::from_ext(ext).first().unwrap();
|
||||
mime = m.essence_str();
|
||||
}
|
||||
}
|
||||
mime.to_string()
|
||||
}
|
||||
|
||||
async fn get_binary(mut con: conn::Connection, path:PathBuf, meta: String) -> io::Result<()> {
|
||||
async fn get_binary(mut con: conn::Connection, path: PathBuf, meta: String) -> io::Result<()> {
|
||||
let fd = File::open(path)?;
|
||||
let mut reader = BufReader::with_capacity(1024*1024,fd);
|
||||
con.send_status(status::Status::Success, Some(&meta)).await?;
|
||||
let mut reader = BufReader::with_capacity(1024 * 1024, fd);
|
||||
con.send_status(status::Status::Success, Some(&meta))
|
||||
.await?;
|
||||
loop {
|
||||
let len = {
|
||||
let buf = reader.fill_buf()?;
|
||||
@@ -53,7 +54,7 @@ async fn get_binary(mut con: conn::Connection, path:PathBuf, meta: String) -> io
|
||||
buf.len()
|
||||
};
|
||||
if len == 0 {
|
||||
break
|
||||
break;
|
||||
}
|
||||
reader.consume(len);
|
||||
}
|
||||
@@ -74,7 +75,7 @@ fn get_content(path: PathBuf, u: url::Url) -> Result<String, io::Error> {
|
||||
let m = file.metadata()?;
|
||||
let perm = m.permissions();
|
||||
if perm.mode() & 0o0444 != 0o0444 {
|
||||
continue
|
||||
continue;
|
||||
}
|
||||
let file = file.path();
|
||||
let p = file.strip_prefix(&path).unwrap();
|
||||
@@ -88,9 +89,11 @@ fn get_content(path: PathBuf, u: url::Url) -> Result<String, io::Error> {
|
||||
return Ok(list);
|
||||
}
|
||||
|
||||
|
||||
// TODO Rewrite this monster.
|
||||
async fn handle_connection(mut con: conn::Connection, srv: &config::ServerCfg) -> Result<(), io::Error> {
|
||||
// TODO Rewrite this monster.
|
||||
async fn handle_connection(
|
||||
mut con: conn::Connection,
|
||||
srv: &config::ServerCfg,
|
||||
) -> Result<(), io::Error> {
|
||||
let mut buffer = [0; 1024];
|
||||
con.stream.read(&mut buffer).await?;
|
||||
let mut request = match String::from_utf8(buffer[..].to_vec()) {
|
||||
@@ -98,7 +101,7 @@ async fn handle_connection(mut con: conn::Connection, srv: &config::ServerCfg) -
|
||||
Err(_) => {
|
||||
logger::logger(con.peer_addr, Status::BadRequest, "");
|
||||
con.send_status(Status::BadRequest, None).await?;
|
||||
return Ok(())
|
||||
return Ok(());
|
||||
}
|
||||
};
|
||||
if request.starts_with("//") {
|
||||
@@ -110,7 +113,7 @@ async fn handle_connection(mut con: conn::Connection, srv: &config::ServerCfg) -
|
||||
Err(_) => {
|
||||
logger::logger(con.peer_addr, Status::BadRequest, &request);
|
||||
con.send_status(Status::BadRequest, None).await?;
|
||||
return Ok(())
|
||||
return Ok(());
|
||||
}
|
||||
};
|
||||
|
||||
@@ -121,52 +124,62 @@ async fn handle_connection(mut con: conn::Connection, srv: &config::ServerCfg) -
|
||||
}
|
||||
|
||||
match url.port() {
|
||||
Some(p) => { if p != srv.port {
|
||||
logger::logger(con.peer_addr, Status::ProxyRequestRefused, &request);
|
||||
con.send_status(status::Status::ProxyRequestRefused, None).await?;
|
||||
}},
|
||||
Some(p) => {
|
||||
if p != srv.port {
|
||||
logger::logger(con.peer_addr, Status::ProxyRequestRefused, &request);
|
||||
con.send_status(status::Status::ProxyRequestRefused, None)
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
|
||||
if url.scheme() != "gemini" {
|
||||
logger::logger(con.peer_addr, Status::ProxyRequestRefused, &request);
|
||||
con.send_status(Status::ProxyRequestRefused, None)
|
||||
.await?;
|
||||
con.send_status(Status::ProxyRequestRefused, None).await?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if srv.redirect.len() != 0 {
|
||||
let u = url.path().trim_end_matches("/");
|
||||
match srv.redirect.get(u) {
|
||||
Some(r) => {
|
||||
logger::logger(con.peer_addr, Status::RedirectTemporary, &request);
|
||||
con.send_status(Status::RedirectTemporary, Some(r)).await?;
|
||||
return Ok(());
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
}
|
||||
if srv.proxy.len() != 0 {
|
||||
match url.path_segments().map(|c| c.collect::<Vec<_>>()) {
|
||||
Some(s) => {
|
||||
match srv.proxy.get(s[0]) {
|
||||
Some(p) => {
|
||||
revproxy::proxy(p.to_string(), url, con).await?;
|
||||
return Ok(());
|
||||
},
|
||||
None => {},
|
||||
Some(s) => match srv.proxy.get(s[0]) {
|
||||
Some(p) => {
|
||||
revproxy::proxy(p.to_string(), url, con).await?;
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
None => {},
|
||||
}
|
||||
None => {}
|
||||
},
|
||||
None => {}
|
||||
}
|
||||
}
|
||||
|
||||
let mut path = PathBuf::new();
|
||||
|
||||
if url.path().starts_with("/~") && srv.usrdir == true{
|
||||
if url.path().starts_with("/~") && srv.usrdir == true {
|
||||
let usr = url.path().trim_start_matches("/~");
|
||||
let usr: Vec<&str> = usr.splitn(2, "/").collect();
|
||||
path.push("/home/");
|
||||
if usr.len() == 2 {
|
||||
path.push(format!("{}/{}/{}", usr[0], "public_gemini", usr[1]));
|
||||
} else {
|
||||
path.push(format!("{}/{}/",usr[0], "public_gemini"));
|
||||
path.push(format!("{}/{}/", usr[0], "public_gemini"));
|
||||
}
|
||||
} else {
|
||||
path.push(&srv.dir);
|
||||
if url.path() != "" || url.path() != "/" {
|
||||
path.push(url.path().trim_start_matches("/"));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if !path.exists() {
|
||||
@@ -197,7 +210,7 @@ async fn handle_connection(mut con: conn::Connection, srv: &config::ServerCfg) -
|
||||
if perm.mode() & 0o0444 != 0o444 {
|
||||
let mut p = path.clone();
|
||||
p.pop();
|
||||
path.push(format!("{}/",p.display()));
|
||||
path.push(format!("{}/", p.display()));
|
||||
meta = fs::metadata(&path).expect("Unable to read metadata");
|
||||
perm = meta.permissions();
|
||||
}
|
||||
@@ -207,8 +220,8 @@ async fn handle_connection(mut con: conn::Connection, srv: &config::ServerCfg) -
|
||||
// TODO add timeout
|
||||
if srv.cgi.trim_end_matches("/") == path.parent().unwrap().to_str().unwrap() {
|
||||
if perm.mode() & 0o0111 == 0o0111 {
|
||||
cgi::cgi(con, path, url).await?;
|
||||
return Ok(());
|
||||
cgi::cgi(con, srv, path, url).await?;
|
||||
return Ok(());
|
||||
} else {
|
||||
logger::logger(con.peer_addr, Status::CGIError, &request);
|
||||
con.send_status(Status::CGIError, None).await?;
|
||||
@@ -229,12 +242,8 @@ async fn handle_connection(mut con: conn::Connection, srv: &config::ServerCfg) -
|
||||
return Ok(());
|
||||
}
|
||||
let content = get_content(path, url)?;
|
||||
con.send_body(
|
||||
status::Status::Success,
|
||||
Some(mime.as_str()),
|
||||
Some(content),
|
||||
)
|
||||
.await?;
|
||||
con.send_body(status::Status::Success, Some(mime.as_str()), Some(content))
|
||||
.await?;
|
||||
logger::logger(con.peer_addr, Status::Success, &request);
|
||||
|
||||
Ok(())
|
||||
@@ -279,7 +288,9 @@ fn main() -> io::Result<()> {
|
||||
let cmap = cmap.clone();
|
||||
|
||||
let fut = async move {
|
||||
let mut stream = tokio_openssl::accept(&acceptor, stream).await.expect("Couldn't accept");
|
||||
let mut stream = tokio_openssl::accept(&acceptor, stream)
|
||||
.await
|
||||
.expect("Couldn't accept");
|
||||
let sni = match stream.ssl().servername(NameType::HOST_NAME) {
|
||||
Some(s) => s,
|
||||
None => return Ok(()),
|
||||
@@ -292,13 +303,10 @@ fn main() -> io::Result<()> {
|
||||
stream.write_all(b"59\tNotFound!\r\n").await?;
|
||||
stream.flush().await?;
|
||||
return Ok(());
|
||||
},
|
||||
};
|
||||
|
||||
let con = conn::Connection {
|
||||
stream,
|
||||
peer_addr,
|
||||
}
|
||||
};
|
||||
|
||||
let con = conn::Connection { stream, peer_addr };
|
||||
handle_connection(con, srv).await?;
|
||||
|
||||
Ok(()) as io::Result<()>
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
use openssl::ssl::{SslConnector, SslMethod};
|
||||
use std::io;
|
||||
use std::net::ToSocketAddrs;
|
||||
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
||||
use tokio::net::TcpStream;
|
||||
use openssl::ssl::{SslConnector, SslMethod};
|
||||
|
||||
use crate::conn;
|
||||
use crate::status::Status;
|
||||
use crate::logger;
|
||||
use crate::status::Status;
|
||||
|
||||
pub async fn proxy(addr: String, u: url::Url, mut con: conn::Connection) -> Result<(), io::Error> {
|
||||
let p: Vec<&str> = u.path().trim_start_matches("/").splitn(2, "/").collect();
|
||||
@@ -18,7 +18,7 @@ pub async fn proxy(addr: String, u: url::Url, mut con: conn::Connection) -> Resu
|
||||
if p[1] == "" || p[1] == "/" {
|
||||
logger::logger(con.peer_addr, Status::NotFound, u.as_str());
|
||||
con.send_status(Status::NotFound, None).await?;
|
||||
return Ok(())
|
||||
return Ok(());
|
||||
}
|
||||
let addr = addr
|
||||
.to_socket_addrs()?
|
||||
@@ -34,20 +34,20 @@ pub async fn proxy(addr: String, u: url::Url, mut con: conn::Connection) -> Resu
|
||||
Err(_) => {
|
||||
logger::logger(con.peer_addr, Status::ProxyError, u.as_str());
|
||||
con.send_status(Status::ProxyError, None).await?;
|
||||
return Ok(())
|
||||
},
|
||||
return Ok(());
|
||||
}
|
||||
};
|
||||
let mut stream = match tokio_openssl::connect(config, "localhost", stream).await {
|
||||
Ok(s) => s,
|
||||
Err(_) => {
|
||||
logger::logger(con.peer_addr, Status::ProxyError, u.as_str());
|
||||
con.send_status(Status::ProxyError, None).await?;
|
||||
return Ok(())
|
||||
},
|
||||
return Ok(());
|
||||
}
|
||||
};
|
||||
stream.write_all(p[1].as_bytes()).await?;
|
||||
stream.flush().await?;
|
||||
|
||||
|
||||
let mut buf = vec![];
|
||||
stream.read_to_end(&mut buf).await?;
|
||||
// let req = String::from_utf8(buf[..].to_vec()).unwrap();
|
||||
|
||||
@@ -52,7 +52,7 @@ impl Status {
|
||||
Status::FutureCertificateRejected => "Future Certificate Rejected",
|
||||
Status::ExpiredCertificateRejected => "Expired Certificate Rejected",
|
||||
};
|
||||
return meta
|
||||
return meta;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,4 +61,3 @@ impl fmt::Display for Status {
|
||||
write!(f, "{:?}", self)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
17
src/tls.rs
17
src/tls.rs
@@ -2,12 +2,12 @@ extern crate openssl;
|
||||
extern crate tokio_openssl;
|
||||
use std::collections::HashMap;
|
||||
|
||||
use openssl::ssl::{SslAcceptor, SslFiletype, SslMethod};
|
||||
use openssl::ssl::SniError;
|
||||
use openssl::error::ErrorStack;
|
||||
use openssl::ssl::NameType;
|
||||
use openssl::ssl::SniError;
|
||||
use openssl::ssl::SslContextBuilder;
|
||||
use openssl::ssl::SslVersion;
|
||||
use openssl::ssl::NameType;
|
||||
use openssl::ssl::{SslAcceptor, SslFiletype, SslMethod};
|
||||
|
||||
use crate::config;
|
||||
|
||||
@@ -22,15 +22,15 @@ pub fn acceptor_conf(cfg: config::Config) -> Result<SslAcceptor, ErrorStack> {
|
||||
Ok(c) => c,
|
||||
Err(e) => {
|
||||
eprintln!("Error: Can't load key file");
|
||||
return Err(e)
|
||||
},
|
||||
return Err(e);
|
||||
}
|
||||
};
|
||||
match ctx.set_certificate_chain_file(&server.cert) {
|
||||
Ok(c) => c,
|
||||
Err(e) => {
|
||||
eprintln!("Error: Can't load cert file");
|
||||
return Err(e)
|
||||
},
|
||||
return Err(e);
|
||||
}
|
||||
};
|
||||
let ctx = ctx.build();
|
||||
map.insert(server.hostname.clone(), ctx.clone());
|
||||
@@ -53,7 +53,8 @@ pub fn acceptor_conf(cfg: config::Config) -> Result<SslAcceptor, ErrorStack> {
|
||||
} else {
|
||||
&map.get(&"default".to_string()).expect("Can't get default")
|
||||
}
|
||||
}).expect("Can't get sni");
|
||||
})
|
||||
.expect("Can't get sni");
|
||||
Ok(())
|
||||
});
|
||||
Ok(acceptor.build())
|
||||
|
||||
Reference in New Issue
Block a user