1 #[macro_use]
2 extern crate serde_derive;
3 
4 use futures_util::future::TryFutureExt;
5 use mime_guess;
6 use openssl::ssl::NameType;
7 use std::env;
8 use std::fs;
9 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 
get_mime(path: &PathBuf) -> String29 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 
get_binary(mut con: conn::Connection, path: PathBuf, meta: String) -> io::Result<()>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 
get_content(path: PathBuf, u: url::Url) -> Result<String, io::Error>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")]
handle_cgi( con: &mut conn::Connection, srv: &config::ServerCfg, request: &str, url: &Url, full_path: &PathBuf, ) -> Result<bool, io::Error>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.
handle_connection( mut con: conn::Connection, srv: &config::ServerCfg, ) -> Result<(), io::Error>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 
main() -> io::Result<()>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 }
490