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

4
.gitignore vendored Normal file
View File

@@ -0,0 +1,4 @@
/target/
Cargo.lock
**/*.rs.bk
.gemgit

7
README
View File

@@ -8,6 +8,7 @@ A gemini server written in rust.
- CGI
- User directories
- Reverse proxy
- Redirect
## Installation and running
@@ -19,7 +20,11 @@ A gemini server written in rust.
## Create server key/cert pairs
## CGI
## CGI Environments
These variables are preset for you. If you need more you can define them in the
config file under "cgienv"
- GEMINI_URL
- SERVER_NAME
- SERVER_PROTOCOL

View File

@@ -9,7 +9,7 @@ fn main() {
_ => {
println!("10\tGopher url:\r\n");
return;
},
}
};
println!("30\t{}{}\r\n", BASE, query);
}

View File

@@ -2,6 +2,8 @@ port = 1965
# use "::" for ipv6 and ipv4 or "0.0.0.0" for ipv4 only
host = "::"
# There must be at least 1 server tag
# Server 1
[[server]]
hostname = "example.com"
dir = "/path/to/serv"
@@ -9,12 +11,17 @@ key = "/path/to/key"
cert = "/path/to/cert"
# cgi dir is optional
cgi = "/path/to/cgi-bin/"
# usrdir is optional. it'll look in /home/~usr/public_gemini
# cgienv is optional
cgienv = { "GIT_PROJECT_ROOT" = "/srv/git" }
# usrdir is optional. it'll look in /home/usr/public_gemini
usrdir = true
# proxy is optional
# path is what comes after the hostname e.g. example.com/path
proxy { path = "localhost:1966" }
# redirect is optional
redirect = { "/redirect" = "/", "/newdomain" = "gemini://example.net" }
# Server 2
[[server]]
hostname = "example.net"
dir = "/path/to/serv/"

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 {
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,21 +12,17 @@ pub struct Connection {
}
impl Connection {
pub async fn send_status(
&mut self,
stat: Status,
meta: Option<&str>,
) -> Result<(), io::Error> {
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(
pub async fn send_body(
&mut self,
stat: Status,
meta: Option<&str>,
body: Option<String>,
) -> Result<(), io::Error> {
) -> Result<(), io::Error> {
let meta = match meta {
Some(m) => m,
None => &stat.to_str(),
@@ -37,11 +33,11 @@ pub async fn send_body(
}
self.send_raw(s.as_bytes()).await?;
Ok(())
}
}
pub async fn send_raw(&mut self, body: &[u8]) -> Result<(), io::Error> {
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";
@@ -42,10 +42,11 @@ fn get_mime(path: &PathBuf) -> String {
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> {
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 {
Some(p) => {
if p != srv.port {
logger::logger(con.peer_addr, Status::ProxyRequestRefused, &request);
con.send_status(status::Status::ProxyRequestRefused, None).await?;
}},
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(s) => match srv.proxy.get(s[0]) {
Some(p) => {
revproxy::proxy(p.to_string(), url, con).await?;
return Ok(());
}
None => {}
},
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,7 +220,7 @@ 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?;
cgi::cgi(con, srv, path, url).await?;
return Ok(());
} else {
logger::logger(con.peer_addr, Status::CGIError, &request);
@@ -229,11 +242,7 @@ 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),
)
con.send_body(status::Status::Success, Some(mime.as_str()), Some(content))
.await?;
logger::logger(con.peer_addr, Status::Success, &request);
@@ -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,16 +34,16 @@ 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?;

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