SCGI
This commit is contained in:
6
README
6
README
@@ -33,7 +33,11 @@ running:
|
|||||||
|
|
||||||
Thanks to tiwesdaeg for figuring it out.
|
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
|
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
|
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
16
cgi-scripts/scgi/scgi.pl
Executable 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
21
cgi-scripts/scgi/scgi.py
Executable 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()
|
||||||
@@ -18,6 +18,8 @@ cgi = true
|
|||||||
# cgipath is optional and only checked if cgi is true. It restricts cgi to only
|
# cgipath is optional and only checked if cgi is true. It restricts cgi to only
|
||||||
# this directory.
|
# this directory.
|
||||||
cgi = "/path/to/cgi-bin/"
|
cgi = "/path/to/cgi-bin/"
|
||||||
|
# scgi is optional
|
||||||
|
scgi = { "/scgi" = "localhost:4000" }
|
||||||
# cgienv is optional
|
# cgienv is optional
|
||||||
cgienv = { "GIT_PROJECT_ROOT" = "/srv/git" }
|
cgienv = { "GIT_PROJECT_ROOT" = "/srv/git" }
|
||||||
# usrdir is optional. it'll look in /home/usr/public_gemini
|
# usrdir is optional. it'll look in /home/usr/public_gemini
|
||||||
|
|||||||
119
src/cgi.rs
119
src/cgi.rs
@@ -3,43 +3,68 @@ use std::io;
|
|||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use tokio::process::Command;
|
use tokio::process::Command;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
use std::net::{SocketAddr, ToSocketAddrs};
|
||||||
|
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
||||||
|
use tokio::net::TcpStream;
|
||||||
|
|
||||||
use crate::config;
|
use crate::config;
|
||||||
use crate::conn;
|
use crate::conn;
|
||||||
use crate::logger;
|
use crate::logger;
|
||||||
use crate::status::Status;
|
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(
|
pub async fn cgi(
|
||||||
mut con: conn::Connection,
|
mut con: conn::Connection,
|
||||||
srv: &config::ServerCfg,
|
srv: &config::ServerCfg,
|
||||||
path: PathBuf,
|
path: PathBuf,
|
||||||
url: Url,
|
url: Url,
|
||||||
) -> Result<(), io::Error> {
|
) -> Result<(), io::Error> {
|
||||||
let mut envs = HashMap::new();
|
let mut envs = envs(con.peer_addr, srv, &url);
|
||||||
envs.insert("GATEWAY_INTERFACE", "CGI/1.1");
|
envs.insert("SCRIPT_NAME".to_string(), path.file_name().unwrap().to_str().unwrap().to_string());
|
||||||
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 cmd = Command::new(path.to_str().unwrap())
|
let cmd = Command::new(path.to_str().unwrap())
|
||||||
.env_clear()
|
.env_clear()
|
||||||
@@ -71,7 +96,49 @@ pub async fn cgi(
|
|||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
let cmd = String::from_utf8(cmd.stdout).unwrap();
|
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?;
|
con.send_raw(cmd.as_bytes()).await?;
|
||||||
return Ok(());
|
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(())
|
||||||
|
}
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ pub struct Server {
|
|||||||
pub usrdir: Option<bool>,
|
pub usrdir: Option<bool>,
|
||||||
pub proxy: Option<HashMap<String, String>>,
|
pub proxy: Option<HashMap<String, String>>,
|
||||||
pub redirect: Option<HashMap<String, String>>,
|
pub redirect: Option<HashMap<String, String>>,
|
||||||
|
pub scgi: Option<HashMap<String, String>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
|
|||||||
15
src/main.rs
15
src/main.rs
@@ -180,6 +180,21 @@ async fn handle_connection(
|
|||||||
None => {}
|
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();
|
let mut path = PathBuf::new();
|
||||||
|
|
||||||
if url.path().starts_with("/~") && srv.server.usrdir.unwrap_or(false) {
|
if url.path().starts_with("/~") && srv.server.usrdir.unwrap_or(false) {
|
||||||
|
|||||||
Reference in New Issue
Block a user