Added redirect and cgienv

This commit is contained in:
int 80h
2020-05-13 21:42:00 -04:00
parent 929a11a91c
commit dfae3705c4
12 changed files with 178 additions and 130 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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();

View File

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

View File

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