💾 Archived View for 80h.dev › projects › gemserv › files › src › cgi.rs.gemini captured on 2022-06-12 at 00:48:39. Gemini links have been rewritten to link to archived content
⬅️ Previous capture (2022-03-01)
-=-=-=-=-=-=-
01 #![cfg(any(feature = "cgi", feature = "scgi"))]
02 use std::collections::HashMap;
03 use std::io;
04 use std::net::SocketAddr;
05
06 #[cfg(feature = "cgi")]
07 use tokio::process::Command;
08 #[cfg(feature = "cgi")]
09 use std::path::PathBuf;
10
11 #[cfg(feature = "scgi")]
12 use std::net::ToSocketAddrs;
13 #[cfg(feature = "scgi")]
14 use tokio::io::{AsyncReadExt, AsyncWriteExt};
15 #[cfg(feature = "scgi")]
16 use tokio::net::TcpStream;
17
18 use crate::config;
19 use crate::conn;
20 use crate::logger;
21 use crate::status::Status;
22 use crate::util;
23
24 #[cfg(any(feature = "cgi", feature = "scgi"))]
25 fn envs(peer_addr: SocketAddr, x509: Option<openssl::x509::X509>, srv: &config::ServerCfg, url: &url::Url) -> HashMap<String, String> {
26 let mut envs = HashMap::new();
27 envs.insert("GATEWAY_INTERFACE".to_string(), "CGI/1.1".to_string());
28 envs.insert("GEMINI_URL".to_string(), url.to_string());
29 envs.insert("SERVER_NAME".to_string(), url.host_str().unwrap().to_string());
30 envs.insert("SERVER_PROTOCOL".to_string(), "GEMINI".to_string());
31 let addr = peer_addr.ip().to_string();
32 envs.insert("REMOTE_ADDR".to_string(), addr.clone());
33 envs.insert("REMOTE_HOST".to_string(), addr);
34 let port = peer_addr.port().to_string();
35 envs.insert("REMOTE_PORT".to_string(), port);
36 envs.insert("SERVER_SOFTWARE".to_string(), env!("CARGO_PKG_NAME").to_string());
37
38 if let Some(q) = url.query() {
39 envs.insert("QUERY_STRING".to_string(), q.to_string());
40 }
41
42 match x509 {
43 Some(x) => {
44 envs.insert("AUTH_TYPE".to_string(), "Certificate".to_string());
45
46 let cn = x.subject_name().entries_by_nid(openssl::nid::Nid::COMMONNAME);
47 for c in cn {
48 let cd = match c.data().as_utf8() {
49 Ok(n) => n.to_string(),
50 _ => "".to_string(),
51 };
52 envs.insert("REMOTE_USER".to_string(), cd);
53 }
54
55 envs.insert("TLS_CLIENT_HASH".to_string(), util::fingerhex(&x));
56 },
57 None => {},
58 }
59
60 match &srv.server.cgienv {
61 Some(c) => {
62 for (k, v) in c.iter() {
63 envs.insert(k.clone(), v.clone());
64 }
65 }
66 None => {}
67 }
68 envs
69 }
70
71 #[cfg(any(feature = "cgi", feature = "scgi"))]
72 fn check(byt: u8, peer_addr: SocketAddr, u: &url::Url) -> bool {
73 match byt {
74 49 => {
75 logger::logger(peer_addr, Status::Input, u.as_str());
76 },
77 50 => {
78 logger::logger(peer_addr, Status::Success, u.as_str());
79 },
80 51..=54 => {}
81 _ => {
82 logger::logger(peer_addr, Status::CGIError, u.as_str());
83 return false;
84 },
85 }
86 true
87 }
88
89 #[cfg(feature = "cgi")]
90 pub async fn cgi(
91 con: &mut conn::Connection,
92 srv: &config::ServerCfg,
93 path: PathBuf,
94 url: &url::Url,
95 script_name: String,
96 path_info: String
97 ) -> Result<(), io::Error> {
98
99 let x509 = con.stream.ssl().peer_certificate();
100 let mut envs = envs(con.peer_addr, x509, srv, &url);
101 envs.insert("SCRIPT_NAME".into(), script_name);
102 envs.insert("PATH_INFO".into(), path_info);
103
104 match path.parent() {
105 Some(p) => {
106 std::env::set_current_dir(p)?;
107 },
108 None => {},
109 }
110
111 let cmd = Command::new(path.to_str().unwrap())
112 .env_clear()
113 .envs(&envs)
114 .output();
115
116 let cmd = match tokio::time::timeout(tokio::time::Duration::from_secs(5), cmd).await {
117 Ok(c) => {
118 match c {
119 Ok(cc) => cc,
120
121 Err(_) => {
122 logger::logger(con.peer_addr, Status::CGIError, url.as_str());
123 con.send_status(Status::CGIError, None).await?;
124 return Ok(());
125 },
126 }
127 },
128 Err(_) => {
129 logger::logger(con.peer_addr, Status::CGIError, url.as_str());
130 con.send_status(Status::CGIError, None).await?;
131 return Ok(());
132 },
133 };
134
135 if !cmd.status.success() {
136 logger::logger(con.peer_addr, Status::CGIError, url.as_str());
137 con.send_status(Status::CGIError, None).await?;
138 return Ok(());
139 }
140 let cmd = String::from_utf8(cmd.stdout).unwrap();
141 if !check(cmd.as_bytes()[0], con.peer_addr, url) {
142 con.send_status(Status::CGIError, None).await?;
143 return Ok(());
144 }
145
146 con.send_raw(cmd.as_bytes()).await?;
147 return Ok(());
148 }
149
150 #[cfg(feature = "scgi")]
151 pub async fn scgi(addr: String, u: url::Url, mut con: conn::Connection, srv: &config::ServerCfg) -> Result<(), io::Error> {
152 let addr = addr
153 .to_socket_addrs()?
154 .next()
155 .ok_or_else(|| io::Error::from(io::ErrorKind::AddrNotAvailable))?;
156
157 let mut stream = match TcpStream::connect(&addr).await {
158 Ok(s) => s,
159 Err(_) => {
160 logger::logger(con.peer_addr, Status::CGIError, u.as_str());
161 con.send_status(Status::CGIError, None).await?;
162 return Ok(());
163 }
164 };
165 let x509 = con.stream.ssl().peer_certificate();
166 let envs = envs(con.peer_addr, x509, srv, &u);
167 let len = 0usize;
168 let mut byt = String::from(format!("CONTENT_LENGTH\x00{}\x00SCGI\x001\x00
169 RQUEST_METHOD\x00POST\x00REQUEST_URI\x00{}\x00", len, u.path()));
170 for (k, v) in envs.iter() {
171 byt.push_str(&format!("{}\x00{}\x00", k, v));
172 }
173 byt = byt.len().to_string() + ":" + &byt + ",";
174
175 stream.write_all(byt.as_bytes()).await?;
176 stream.flush().await?;
177
178 let mut buf = vec![];
179 if let Err(_) = tokio::time::timeout(
180 tokio::time::Duration::from_secs(5), stream.read_to_end(&mut buf)).await {
181 logger::logger(con.peer_addr, Status::CGIError, u.as_str());
182 con.send_status(Status::CGIError, None).await?;
183 return Ok(());
184 }
185 let req = String::from_utf8_lossy(&buf[..]);
186 if !check(req.as_bytes()[0], con.peer_addr, &u) {
187 con.send_status(Status::CGIError, None).await?;
188 return Ok(());
189 }
190
191 con.send_raw(req.as_bytes()).await?;
192 Ok(())
193 }