This commit is contained in:
int 80h 2020-05-17 00:17:35 -04:00
parent 5c7f0f2275
commit e9f71f600d
7 changed files with 153 additions and 27 deletions

6
README
View File

@ -33,7 +33,11 @@ running:
Thanks to tiwesdaeg for figuring it out.
## CGI Environments
## CGI and SCGI
There's example SCGI scripts for python and perl in the cgi-scripts directory.
### CGI Environments
In the configuration file there's "cgi" which is an optional bool to turn cgi
on. If it's true it'll run scripts from any directory. To limit it to only one

16
cgi-scripts/scgi/scgi.pl Executable file
View File

@ -0,0 +1,16 @@
#!/usr/local/bin/perl
use SCGI;
use IO::Socket;
my $socket = IO::Socket::INET->new(Listen => 5, ReuseAddr => 1, LocalPort => 4001)
or die "cannot bind to port 4001: $!";
my $scgi = SCGI->new($socket, blocking => 1);
while (my $request = $scgi->accept) {
$request->read_env;
read $request->connection, my $body, $request->env->{CONTENT_LENGTH};
print { $request->connection } "20\ttext/gemini\r\nperl\n";
}

21
cgi-scripts/scgi/scgi.py Executable file
View File

@ -0,0 +1,21 @@
#! /usr/local/bin/python3.6
import scgi
import scgi.scgi_server
class TimeHandler(scgi.scgi_server.SCGIHandler):
def produce(self, env, bodysize, input, output):
header = "20\ttext/gemini\r\n"
hi = "python\n"
output.write(header.encode())
output.write(hi.encode())
# Main program: create an SCGIServer object to
# listen on port 4000. We tell the SCGIServer the
# handler class that implements our application.
server = scgi.scgi_server.SCGIServer(
handler_class=TimeHandler,
port=4000
)
# Tell our SCGIServer to start servicing requests.
# This loops forever.
server.serve()

View File

@ -18,6 +18,8 @@ cgi = true
# cgipath is optional and only checked if cgi is true. It restricts cgi to only
# this directory.
cgi = "/path/to/cgi-bin/"
# scgi is optional
scgi = { "/scgi" = "localhost:4000" }
# cgienv is optional
cgienv = { "GIT_PROJECT_ROOT" = "/srv/git" }
# usrdir is optional. it'll look in /home/usr/public_gemini

View File

@ -3,43 +3,68 @@ use std::io;
use std::path::PathBuf;
use tokio::process::Command;
use url::Url;
use std::net::{SocketAddr, ToSocketAddrs};
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::net::TcpStream;
use crate::config;
use crate::conn;
use crate::logger;
use crate::status::Status;
fn envs(peer_addr: SocketAddr, srv: &config::ServerCfg, url: &url::Url) -> HashMap<String, String> {
let mut envs = HashMap::new();
envs.insert("GATEWAY_INTERFACE".to_string(), "CGI/1.1".to_string());
envs.insert("GEMINI_URL".to_string(), url.to_string());
envs.insert("SERVER_NAME".to_string(), url.host_str().unwrap().to_string());
envs.insert("SERVER_PROTOCOL".to_string(), "GEMINI".to_string());
let addr = peer_addr.ip().to_string();
envs.insert("REMOTE_ADDR".to_string(), addr.clone());
envs.insert("REMOTE_HOST".to_string(), addr);
let port = peer_addr.port().to_string();
envs.insert("REMOTE_PORT".to_string(), port);
envs.insert("SERVER_SOFTWARE".to_string(), env!("CARGO_PKG_NAME").to_string());
if let Some(q) = url.query() {
envs.insert("QUERY_STRING".to_string(), q.to_string());
}
match &srv.server.cgienv {
Some(c) => {
for (k, v) in c.iter() {
envs.insert(k.clone(), v.clone());
}
}
None => {}
}
envs
}
fn check(byt: u8, peer_addr: SocketAddr, u: url::Url) -> bool {
match byt {
49 => {
logger::logger(peer_addr, Status::Input, u.as_str());
},
50 => {
logger::logger(peer_addr, Status::Success, u.as_str());
},
51..=54 => {}
_ => {
logger::logger(peer_addr, Status::CGIError, u.as_str());
return false;
},
}
true
}
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());
envs.insert("SERVER_NAME", url.host_str().unwrap());
envs.insert("SCRIPT_NAME", path.file_name().unwrap().to_str().unwrap());
envs.insert("SERVER_PROTOCOL", "GEMINI");
let addr = con.peer_addr.ip().to_string();
envs.insert("REMOTE_ADDR", &addr);
envs.insert("REMOTE_HOST", &addr);
let port = con.peer_addr.port().to_string();
envs.insert("REMOTE_PORT", &port);
envs.insert("SERVER_SOFTWARE", env!("CARGO_PKG_NAME"));
if let Some(q) = url.query() {
envs.insert("QUERY_STRING", q);
}
match &srv.server.cgienv {
Some(c) => {
for (k, v) in c.iter() {
envs.insert(&k, &v);
}
}
None => {}
}
let mut envs = envs(con.peer_addr, srv, &url);
envs.insert("SCRIPT_NAME".to_string(), path.file_name().unwrap().to_str().unwrap().to_string());
let cmd = Command::new(path.to_str().unwrap())
.env_clear()
@ -71,7 +96,49 @@ pub async fn cgi(
return Ok(());
}
let cmd = String::from_utf8(cmd.stdout).unwrap();
logger::logger(con.peer_addr, Status::Success, url.as_str());
if !check(cmd.as_bytes()[0], con.peer_addr, url) {
con.send_status(Status::CGIError, None).await?;
return Ok(());
}
con.send_raw(cmd.as_bytes()).await?;
return Ok(());
}
pub async fn scgi(addr: String, u: url::Url, mut con: conn::Connection, srv: &config::ServerCfg) -> Result<(), io::Error> {
let addr = addr
.to_socket_addrs()?
.next()
.ok_or_else(|| io::Error::from(io::ErrorKind::AddrNotAvailable))?;
let mut stream = match TcpStream::connect(&addr).await {
Ok(s) => s,
Err(_) => {
logger::logger(con.peer_addr, Status::CGIError, u.as_str());
con.send_status(Status::CGIError, None).await?;
return Ok(());
}
};
let envs = envs(con.peer_addr, srv, &u);
let len = 0usize;
let mut byt = String::from(format!("CONTENT_LENGTH\x00{}\x00SCGI\x001\x00
RQUEST_METHOD\x00POST\x00REQUEST_URI\x00{}\x00", len, u.path()));
for (k, v) in envs.iter() {
byt.push_str(&format!("{}\x00{}\x00", k, v));
}
byt = byt.len().to_string() + ":" + &byt + ",";
stream.write_all(byt.as_bytes()).await?;
stream.flush().await?;
let mut buf = vec![];
stream.read_to_end(&mut buf).await?;
let req = String::from_utf8_lossy(&buf[..]);
if !check(req.as_bytes()[0], con.peer_addr, u) {
con.send_status(Status::CGIError, None).await?;
return Ok(());
}
con.send_raw(req.as_bytes()).await?;
Ok(())
}

View File

@ -24,6 +24,7 @@ pub struct Server {
pub usrdir: Option<bool>,
pub proxy: Option<HashMap<String, String>>,
pub redirect: Option<HashMap<String, String>>,
pub scgi: Option<HashMap<String, String>>,
}
#[derive(Debug, Clone)]

View File

@ -180,6 +180,21 @@ async fn handle_connection(
None => {}
}
match &srv.server.scgi {
Some(sc) => {
let u = url.path().trim_end_matches("/");
match sc.get(u) {
Some(r) => {
cgi::scgi(r.to_string(), url, con, srv).await?;
return Ok(());
}
None => {}
}
},
None => {},
}
let mut path = PathBuf::new();
if url.path().starts_with("/~") && srv.server.usrdir.unwrap_or(false) {