diff --git a/README.md b/README.md index 9745dbb..10e2605 100644 --- a/README.md +++ b/README.md @@ -7,9 +7,14 @@ For more information, see https://minecraft.wiki/w/Query ## Usage -McStatusFace can be run as a web server with `mcstatusface serve`. This will -provide server information in JSON format to requests on `GET /?s=`. -(e.g. `curl -sS "127.0.0.1:8080?s=127.0.0.1:25565" | jq .`) +McStatusFace can simply be run with `mcstatusface `. -Alternatively, you can simply run `mcstatusface `, and the +Alternatively, you can start a web interface with +`mcstatusface serve [address:[port]]`. + +This provides a web interface to query server information via a frontend, or +in JSON format if the request is cURLed. + +The server queries Minecraft servers when a `GET /?s=` request +is received. (e.g. `curl -sS "127.0.0.1:8080?s=127.0.0.1:25565" | jq .`) tool will provide server details in plain-text format. diff --git a/src/http.rs b/src/http.rs index bc2d69e..27bbdc4 100644 --- a/src/http.rs +++ b/src/http.rs @@ -1,4 +1,10 @@ -use std::{collections::HashMap, io::{BufRead, BufReader, Error, ErrorKind, Result, Write}, net::{SocketAddr, TcpListener, TcpStream}}; +use std::{ + collections::HashMap, + io::{BufRead, BufReader, Error, ErrorKind, Result, Write}, + net::{IpAddr, SocketAddr, TcpListener, TcpStream}, + str::FromStr, + sync::Arc, +}; use chrono::Local; @@ -45,10 +51,11 @@ pub struct Request<'a> { headers: HashMap<&'a str, &'a str>, query: HashMap<&'a str, &'a str>, body: Option, + real_address: IpAddr, } impl<'a> Request<'a> { - pub fn new(stream: &'a TcpStream, lines: &'a Vec) -> Result> { + pub fn new(stream: &'a TcpStream, lines: &'a Vec, trusted_proxies: Vec) -> Result> { let request_line = lines[0].as_str(); let request_line_split: Vec<&str> = request_line.split(" ").collect(); if request_line_split.len() < 3 { @@ -88,6 +95,22 @@ impl<'a> Request<'a> { } } + let mut real_address = IpAddr::from(stream.peer_addr().unwrap().ip()); + + headers.get("X-Forwarded-For").inspect(|address| { + match IpAddr::from_str(address) { + Ok(address) => { + for proxy in trusted_proxies { + if real_address == proxy { + real_address = address; + break; + } + } + } + Err(_) => {} + } + }); + let mut body: Option = None; if lines.len() > headers.len() + 2 && (method == "POST" || method == "PUT") && @@ -104,12 +127,16 @@ impl<'a> Request<'a> { headers, query, body, + real_address, }) } pub fn address(&self) -> Result { self.stream.peer_addr() } + pub fn real_address(&self) -> &IpAddr { + &self.real_address + } pub fn path(&self) -> &'a str { self.path } @@ -187,11 +214,12 @@ impl<'a> Response<'a> { pub struct HttpServer { address: String, port: u16, + trusted_proxies: Arc>, max_connections: usize, } impl HttpServer { - pub fn new(address: String, max_connections: usize) -> HttpServer { + pub fn new(address: String, max_connections: usize, trusted_proxies: Vec) -> HttpServer { let mut _address = address.clone(); let mut _port: u16 = 8080; match address.split_once(":") { @@ -204,6 +232,7 @@ impl HttpServer { HttpServer { address: _address, port: _port, + trusted_proxies: Arc::new(trusted_proxies), max_connections, } } @@ -217,8 +246,9 @@ impl HttpServer { for stream in listener.incoming() { match stream { Ok(stream) => { + let trusted_proxies = self.trusted_proxies.clone(); pool.execute(move || { - HttpServer::handle_client(&stream, handler); + HttpServer::handle_client(&stream, handler, trusted_proxies); }); } Err(e) => { @@ -230,7 +260,7 @@ impl HttpServer { Ok(()) } - fn handle_client(stream: &TcpStream, handler: HttpHandlerFunc) { + fn handle_client(stream: &TcpStream, handler: HttpHandlerFunc, trusted_proxies: Arc>) { let buf_reader = BufReader::new(stream); let http_request: Vec = buf_reader .lines() @@ -238,7 +268,7 @@ impl HttpServer { .take_while(|line| !line.is_empty()) .collect(); - let request = Request::new(stream, &http_request); + let request = Request::new(stream, &http_request, trusted_proxies.to_vec()); if request.is_err() { eprintln!("Failed to process request: {}", request.err().unwrap()); return; @@ -251,6 +281,8 @@ impl HttpServer { match handler(&request, response) { Ok(status) => { let end_date = Local::now(); + + println!( "[{}] {} {} {} - {} {} - {}ms - {} ({})", start_date.format("%Y-%m-%d %H:%M:%S"), @@ -265,7 +297,7 @@ impl HttpServer { (end_date - start_date).num_milliseconds(), request.headers().get("User-Agent").map_or("[]", |v| v), - request.address().unwrap().ip(), + request.real_address().to_string(), ); } Err(e) => { diff --git a/src/main.rs b/src/main.rs index f3e5190..75e4e5e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,6 @@ use std::io::{Result}; -use std::net::{ToSocketAddrs}; +use std::net::{IpAddr, ToSocketAddrs}; +use std::str::FromStr; use std::{env}; use mcstatusface::http::{HttpServer, StatusCode}; @@ -46,8 +47,22 @@ env!("CARGO_PKG_VERSION")); let mut address = "0.0.0.0:8080".to_string(); if args.len() > 2 { address = args[2].to_string() } + let trusted_proxies: Vec = + match env::var("MCSTATUSFACE_TRUSTED_PROXIES") { + Ok(envar) => { + let mut trusted_proxies: Vec = Vec::new(); + for addr in envar.split(",") { + match IpAddr::from_str(addr) { + Ok(addr) => { trusted_proxies.push(addr); } + Err(_) => {} + } + } + trusted_proxies + } + Err(_) => { vec![] } + }; - HttpServer::new(address, 64).start(|request, mut response| { + HttpServer::new(address, 64, trusted_proxies).start(|request, mut response| { response.status(StatusCode::OK); response.set_header("Content-Type", "text/plain".to_string()); response.set_header("x-powered-by", "GIRL FUEL".to_string()); @@ -132,7 +147,7 @@ env!("CARGO_PKG_VERSION")); // JSON response match request.query().get("s") { None => { - response.status(StatusCode::BadRequest); + response.status(StatusCode::OK); response.body("?s=\n".to_string()); return response.send(); }