💾 Archived View for 80h.dev › projects › gemserv › files › src › main.rs.gemini captured on 2022-03-01 at 15:23:35. Gemini links have been rewritten to link to archived content
⬅️ Previous capture (2020-09-24)
-=-=-=-=-=-=-
01 #[macro_use]
02 extern crate serde_derive;
03
04 use futures_util::future::TryFutureExt;
05 use mime_guess;
06 use openssl::ssl::NameType;
07 use std::env;
08 use std::fs;
09 use std::fs::File;
10 use std::io::{self, BufRead, BufReader};
11 use std::net::ToSocketAddrs;
12 use std::os::unix::fs::PermissionsExt;
13 use std::path::{Path, PathBuf};
14 use tokio::net::TcpListener;
15 use tokio::prelude::*;
16 use tokio::runtime;
17 use url::Url;
18
19 mod cgi;
20 mod config;
21 mod status;
22 use status::Status;
23 mod conn;
24 mod logger;
25 mod revproxy;
26 mod tls;
27 mod util;
28
29 fn get_mime(path: &PathBuf) -> String {
30 let mut mime = "text/gemini".to_string();
31 if path.is_dir() {
32 return mime;
33 }
34 let ext = match path.extension() {
35 Some(p) => p.to_str().unwrap(),
36 None => return "text/plain".to_string(),
37 };
38
39 mime = match ext {
40 "gemini" => mime,
41 "gmi" => mime,
42 _ => {
43 match mime_guess::from_ext(ext).first() {
44 Some(m) => m.essence_str().to_string(),
45 None => "text/plain".to_string(),
46 }
47 },
48 };
49
50 return mime;
51 }
52
53 async fn get_binary(mut con: conn::Connection, path: PathBuf, meta: String) -> io::Result<()> {
54 let fd = File::open(path)?;
55 let mut reader = BufReader::with_capacity(1024 * 1024, fd);
56 con.send_status(status::Status::Success, Some(&meta))
57 .await?;
58 loop {
59 let len = {
60 let buf = reader.fill_buf()?;
61 con.send_raw(buf).await?;
62 buf.len()
63 };
64 if len == 0 {
65 break;
66 }
67 reader.consume(len);
68 }
69 Ok(())
70 }
71
72 async fn get_content(path: PathBuf, u: url::Url) -> Result<String, io::Error> {
73 let meta = tokio::fs::metadata(&path).await?;
74 if meta.is_file() {
75 return Ok(tokio::fs::read_to_string(path).await?);
76 }
77
78 let mut dirs: Vec<String> = Vec::new();
79 let mut files: Vec<String> = Vec::new();
80
81 // needs work
82 for file in fs::read_dir(&path)? {
83 if let Ok(file) = file {
84 let m = file.metadata()?;
85 let perm = m.permissions();
86 if perm.mode() & 0o0444 != 0o0444 {
87 continue;
88 }
89 let file = file.path();
90 let p = file.strip_prefix(&path).unwrap();
91 let ps = match p.to_str() {
92 Some(s) => s,
93 None => continue,
94 };
95 let ep = match u.join(ps) {
96 Ok(p) => p,
97 _ => continue,
98 };
99 if m.is_dir() {
100 dirs.push(format!("=> {}/ {}/\r\n", ep, p.display()));
101 } else {
102 files.push(format!("=> {}/ {}\r\n", ep, p.display()));
103 }
104 }
105 }
106
107 dirs.sort();
108 files.sort();
109
110 let mut list = String::from("# Directory Listing\r\n\r\n");
111 list.push_str(&format!("Path: {}\r\n\r\n", u.path()));
112
113 for dir in dirs {
114 list.push_str(&dir);
115 }
116 for file in files {
117 list.push_str(&file);
118 }
119
120 return Ok(list);
121 }
122
123 // Handle CGI and return Ok(true), or indicate this request wasn't for CGI with Ok(false)
124 #[cfg(feature = "cgi")]
125 async fn handle_cgi(
126 con: &mut conn::Connection,
127 srv: &config::ServerCfg,
128 request: &str,
129 url: &Url,
130 full_path: &PathBuf,
131 ) -> Result<bool, io::Error> {
132 if srv.server.cgi.unwrap_or(false) {
133 let mut path = full_path.clone();
134 let mut segments = url.path_segments().unwrap();
135 let mut path_info = "".to_string();
136
137 // Find an ancestor url that matches a file
138 while !path.exists() {
139 if let Some(segment) = segments.next_back() {
140 path.pop();
141 path_info = format!("/{}{}", &segment, path_info);
142 } else {
143 return Ok(false);
144 }
145 }
146 let script_name = format!("/{}", segments.collect::<Vec<_>>().join("/"));
147
148 let meta = tokio::fs::metadata(&path).await?;
149 let perm = meta.permissions();
150
151 match &srv.server.cgipath {
152 Some(c) => {
153 if path.starts_with(c) {
154 if perm.mode() & 0o0111 == 0o0111 {
155 cgi::cgi(con, srv, path, url, script_name, path_info).await?;
156 return Ok(true);
157 } else {
158 logger::logger(con.peer_addr, Status::CGIError, request);
159 con.send_status(Status::CGIError, None).await?;
160 return Ok(true);
161 }
162 }
163 },
164 None => {
165 if meta.is_file() && perm.mode() & 0o0111 == 0o0111 {
166 cgi::cgi(con, srv, path, url, script_name, path_info).await?;
167 return Ok(true);
168 }
169 },
170 }
171 }
172 Ok(false)
173 }
174
175 // TODO Rewrite this monster.
176 async fn handle_connection(
177 mut con: conn::Connection,
178 srv: &config::ServerCfg,
179 ) -> Result<(), io::Error> {
180 let index = match &srv.server.index {
181 Some(i) => i.clone(),
182 None => "index.gemini".to_string(),
183 };
184 let mut buffer = [0; 1024];
185 if let Err(_) = tokio::time::timeout(tokio::time::Duration::from_secs(5), con.stream.read(&mut buffer)).await {
186 logger::logger(con.peer_addr, Status::BadRequest, "");
187 con.send_status(Status::BadRequest, None).await?;
188 return Ok(());
189 }
190 let mut request = match String::from_utf8(buffer[..].to_vec()) {
191 Ok(request) => request,
192 Err(_) => {
193 logger::logger(con.peer_addr, Status::BadRequest, "");
194 con.send_status(Status::BadRequest, None).await?;
195 return Ok(());
196 }
197 };
198 if request.starts_with("//") {
199 request = request.replacen("//", "gemini://", 1);
200 }
201
202 if request.ends_with("\n") {
203 request.pop();
204 if request.ends_with("\r") {
205 request.pop();
206 }
207 }
208
209 let url = match Url::parse(&request) {
210 Ok(url) => url,
211 Err(_) => {
212 logger::logger(con.peer_addr, Status::BadRequest, &request);
213 con.send_status(Status::BadRequest, None).await?;
214 return Ok(());
215 }
216 };
217
218 if Some(srv.server.hostname.as_str()) != url.host_str() {
219 logger::logger(con.peer_addr, Status::ProxyRequestRefused, &request);
220 con.send_status(Status::ProxyRequestRefused, None).await?;
221 return Ok(());
222 }
223
224 match url.port() {
225 Some(p) => {
226 if p != srv.port {
227 logger::logger(con.peer_addr, Status::ProxyRequestRefused, &request);
228 con.send_status(status::Status::ProxyRequestRefused, None)
229 .await?;
230 }
231 }
232 None => {}
233 }
234
235 if url.scheme() != "gemini" {
236 logger::logger(con.peer_addr, Status::ProxyRequestRefused, &request);
237 con.send_status(Status::ProxyRequestRefused, None).await?;
238 return Ok(());
239 }
240
241 match &srv.server.redirect {
242 Some(re) => {
243 let u = url.path().trim_end_matches("/");
244 match re.get(u) {
245 Some(r) => {
246 logger::logger(con.peer_addr, Status::RedirectTemporary, &request);
247 con.send_status(Status::RedirectTemporary, Some(r)).await?;
248 return Ok(());
249 }
250 None => {}
251 }
252 }
253 None => {}
254 }
255
256 #[cfg(feature = "proxy")]
257 if let Some(pr) = &srv.server.proxy_all {
258 let host_port: Vec<&str> = pr.splitn(2, ':').collect();
259 let host = host_port[0];
260 let port: Option<u16>;
261 if host_port.len() == 2 {
262 port = host_port[1].parse().ok();
263 } else {
264 port = None;
265 }
266
267 let mut upstream_url = url.clone();
268 upstream_url.set_host(Some(host)).unwrap();
269 upstream_url.set_port(port).unwrap();
270
271 revproxy::proxy_all(pr, upstream_url, con).await?;
272 return Ok(());
273 }
274
275 #[cfg(feature = "proxy")]
276 match &srv.server.proxy {
277 Some(pr) => match url.path_segments().map(|c| c.collect::<Vec<_>>()) {
278 Some(s) => match pr.get(s[0]) {
279 Some(p) => {
280 revproxy::proxy(p.to_string(), url, con).await?;
281 return Ok(());
282 }
283 None => {}
284 },
285 None => {}
286 },
287 None => {}
288 }
289
290 #[cfg(feature = "scgi")]
291 match &srv.server.scgi {
292 Some(sc) => {
293 let u = url.path().trim_end_matches("/");
294 match sc.get(u) {
295 Some(r) => {
296 cgi::scgi(r.to_string(), url, con, srv).await?;
297 return Ok(());
298 }
299 None => {}
300 }
301 },
302 None => {},
303 }
304
305
306 let mut path = PathBuf::new();
307
308 if url.path().starts_with("/~") && srv.server.usrdir.unwrap_or(false) {
309 let usr = url.path().trim_start_matches("/~");
310 let usr: Vec<&str> = usr.splitn(2, "/").collect();
311 path.push("/home/");
312 if usr.len() == 2 {
313 path.push(format!("{}/{}/{}", usr[0], "public_gemini", util::url_decode(usr[1].as_bytes())));
314 } else {
315 path.push(format!("{}/{}/", usr[0], "public_gemini"));
316 }
317 } else {
318 path.push(&srv.server.dir);
319 if url.path() != "" || url.path() != "/" {
320 let decoded = util::url_decode(url.path().trim_start_matches("/").as_bytes());
321 path.push(decoded);
322 }
323 }
324
325 if !path.exists() {
326 // See if it's a subpath of a CGI script before returning NotFound
327 #[cfg(feature = "cgi")]
328 if handle_cgi(&mut con, srv, &request, &url, &path).await? {
329 return Ok(());
330 }
331
332 logger::logger(con.peer_addr, Status::NotFound, &request);
333 con.send_status(Status::NotFound, None).await?;
334 return Ok(());
335 }
336
337 let mut meta = tokio::fs::metadata(&path).await?;
338 let mut perm = meta.permissions();
339
340 // TODO fix me
341 // This block is terrible
342 if meta.is_dir() {
343 if !url.path().ends_with("/") {
344 logger::logger(con.peer_addr, Status::RedirectPermanent, &request);
345 con.send_status(
346 Status::RedirectPermanent,
347 Some(format!("{}/", url).as_str()),
348 )
349 .await?;
350 return Ok(());
351 }
352 if path.join(&index).exists() {
353 path.push(index);
354 meta = tokio::fs::metadata(&path).await?;
355 perm = meta.permissions();
356 if perm.mode() & 0o0444 != 0o444 {
357 let mut p = path.clone();
358 p.pop();
359 path.push(format!("{}/", p.display()));
360 meta = tokio::fs::metadata(&path).await?;
361 perm = meta.permissions();
362 }
363 }
364 }
365
366 #[cfg(feature = "cgi")]
367 if handle_cgi(&mut con, srv, &request, &url, &path).await? {
368 return Ok(());
369 }
370
371 if meta.is_file() && perm.mode() & 0o0111 == 0o0111 {
372 logger::logger(con.peer_addr, Status::NotFound, &request);
373 con.send_status(Status::NotFound, None).await?;
374 return Ok(());
375 }
376
377 if perm.mode() & 0o0444 != 0o0444 {
378 logger::logger(con.peer_addr, Status::NotFound, &request);
379 con.send_status(Status::NotFound, None).await?;
380 return Ok(());
381 }
382
383 let mut mime = get_mime(&path);
384 if mime == "text/gemini" && srv.server.lang.is_some() {
385 mime += &("; lang=".to_string() + &srv.server.lang.to_owned().unwrap());
386 }
387 if !mime.starts_with("text/") {
388 logger::logger(con.peer_addr, Status::Success, &request);
389 get_binary(con, path, mime).await?;
390 return Ok(());
391 }
392 let content = get_content(path, url).await?;
393 con.send_body(status::Status::Success, Some(&mime), Some(content))
394 .await?;
395 logger::logger(con.peer_addr, Status::Success, &request);
396
397 Ok(())
398 }
399
400 fn main() -> io::Result<()> {
401 let args: Vec<String> = env::args().collect();
402 if args.len() != 2 {
403 println!("Please run with the path to the config file.");
404 return Ok(());
405 }
406 let p = Path::new(&args[1]);
407 if !p.exists() {
408 println!("Config file doesn't exist");
409 return Ok(());
410 }
411
412 let cfg = match config::Config::new(&p) {
413 Ok(c) => c,
414 Err(e) => {
415 eprintln!("Config error: {}", e);
416 return Ok(());
417 },
418 };
419 let loglev = match &cfg.log {
420 None => log::Level::Info,
421 Some(l) => {
422 match l.as_str() {
423 "error" => log::Level::Error,
424 "warn" => log::Level::Warn,
425 "info" => log::Level::Info,
426 _ => {
427 eprintln!("Incorrect log level in config file.");
428 return Ok(());
429 },
430 }
431 },
432 };
433 simple_logger::init_with_level(loglev).unwrap();
434 let cmap = cfg.to_map();
435 let default = &cfg.server[0].hostname;
436 println!("Serving {} vhosts", cfg.server.len());
437
438 let addr = format!("{}:{}", cfg.host, cfg.port);
439 addr.to_socket_addrs()?
440 .next()
441 .ok_or_else(|| io::Error::from(io::ErrorKind::AddrNotAvailable))?;
442
443 let mut runtime = runtime::Builder::new()
444 .threaded_scheduler()
445 .enable_io()
446 .enable_time()
447 .build()?;
448
449 let handle = runtime.handle().clone();
450
451 let acceptor = tls::acceptor_conf(cfg.clone())?;
452
453 let fut = async {
454 let mut listener = TcpListener::bind(&addr).await?;
455 loop {
456 let (stream, peer_addr) = listener.accept().await?;
457 let acceptor = acceptor.clone();
458 let cmap = cmap.clone();
459 let default = default.clone();
460
461 let fut = async move {
462 let stream = match tokio_openssl::accept(&acceptor, stream).await {
463 Ok(s) => s,
464 Err(e) => {
465 log::error!("Error: {}",e);
466 return Ok(());
467 },
468 };
469
470 let srv = match stream.ssl().servername(NameType::HOST_NAME) {
471 Some(s) => match cmap.get(s) {
472 Some(ss) => ss,
473 None => cmap.get(&default).unwrap(),
474 },
475 None => cmap.get(&default).unwrap(),
476 };
477
478 let con = conn::Connection { stream, peer_addr };
479 handle_connection(con, srv).await?;
480
481 Ok(()) as io::Result<()>
482 };
483
484 handle.spawn(fut.unwrap_or_else(|err| eprintln!("{:?}", err)));
485 }
486 };
487
488 runtime.block_on(fut)
489 }