💾 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

View Raw

More Information

⬅️ Previous capture (2020-09-24)

🚧 View Differences

-=-=-=-=-=-=-

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 }