diff --git a/Cargo.lock b/Cargo.lock index d12fa0f..c064533 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -51,6 +51,7 @@ dependencies = [ "lazy_static", "linemux", "regex", + "thiserror", "tokio", ] @@ -398,6 +399,26 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "thiserror" +version = "1.0.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "tokio" version = "1.12.0" diff --git a/Cargo.toml b/Cargo.toml index f9b24a7..2e7d802 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,4 +11,5 @@ linemux = "0.2" tokio = { version = "1", features = ["rt-multi-thread", "macros"] } lazy_static = "1.4.0" regex = "1.5.4" -clap = "2.33.3" \ No newline at end of file +clap = "2.33.3" +thiserror = "1.0.26" diff --git a/src/clf.rs b/src/clf.rs index 1060e56..7fa0e02 100644 --- a/src/clf.rs +++ b/src/clf.rs @@ -1,28 +1,30 @@ -use std::net::IpAddr; - +use crate::utils::Error; use lazy_static::lazy_static; +use std::net::IpAddr; // TODO: allow user-provided list // TODO: match different error-levels (10 404, but only 5 401, etc...) lazy_static! { - static ref BAD_STATUSES: [&'static str; 2] = ["401", "429"]; + static ref BAD_STATUSES: [u32; 2] = [401, 429]; } -pub fn parse(line: &str) -> Option { +pub fn parse(line: &str) -> Result, Error> { // TODO: Use a proper parser ? let elts: Vec<&str> = line.split_whitespace().collect(); - let ip = elts[0].parse::().ok(); - let http_code = elts[elts.len() - 2] as &str; - BAD_STATUSES.iter().find_map( - |bad_status| { - if http_code == *bad_status { - ip - } else { - None - } - }, - ) + let ip_str = elts[0]; + let ip = ip_str.parse::().or(Err(Error::CantParse))?; + + let http_code_str = elts[elts.len() - 2] as &str; + let http_code = http_code_str.parse::().or(Err(Error::CantParse))?; + + for status in BAD_STATUSES.iter() { + if *status == http_code { + return Ok(Some(ip)); + } + } + + Ok(None) } #[cfg(test)] @@ -36,7 +38,10 @@ mod tests { "8.8.8.8 - p [25/Sep/2021:13:49:56 +0200] \"POST /some/rpc HTTP/2.0\" 429 923", ]; - vectors.iter().for_each(|e| assert!(parse(*e).is_some())) + vectors.iter().for_each(|e| { + let ret = parse(*e); + assert!(ret.unwrap().is_some()); + }) } #[test] @@ -46,6 +51,22 @@ mod tests { "8.8.8.8 - p [25/Sep/2021:13:49:56 +0200] \"POST /some/rpc HTTP/2.0\" 404 923", ]; - vectors.iter().for_each(|e| assert!(parse(*e).is_none())) + vectors.iter().for_each(|e| { + let ret = parse(*e); + assert!(ret.unwrap().is_none()); + }) + } + + #[test] + fn malformed() { + let vectors = [ + "8.8.8.8.8 - p [25/Sep/2021:13:49:56 +0200] \"POST /some/rpc HTTP/2.0\" 200 923", + "8.8.8.8 - p [25/Sep/2021:13:49:56 +0200] \"POST /some/rpc HTTP/2.0\"", + ]; + + vectors.iter().for_each(|e| { + let ret = parse(*e); + assert!(ret.is_err()); + }) } } diff --git a/src/main.rs b/src/main.rs index e1dc806..be25227 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,6 +6,7 @@ mod utils; mod jail; use crate::jail::Jail; +use crate::utils::Error; async fn run() -> Option<()> { let args = utils::cli().get_matches(); @@ -36,12 +37,18 @@ async fn run() -> Option<()> { let payload = line.line(); let path = line.source().display().to_string(); - if do_sshd && path.ends_with(path_sshd) { - sshd::parse(payload).and_then(|ip| jail.probe(ip)); + let res = if do_sshd && path.ends_with(path_sshd) { + sshd::parse(payload) } else if do_clf && path.ends_with(path_clf) { - clf::parse(payload).and_then(|ip| jail.probe(ip)); + clf::parse(payload) } else { - eprintln!("! unknown logline: {}", path); + Err(Error::UnknownError) + }; + + if let Ok(Some(ip)) = res { + jail.probe(ip); + } else { + eprintln!("! error processing logline: {}", path); } } diff --git a/src/sshd.rs b/src/sshd.rs index 28b3b9f..64acf54 100644 --- a/src/sshd.rs +++ b/src/sshd.rs @@ -1,7 +1,9 @@ +use crate::utils::Error; use std::net::IpAddr; use lazy_static::lazy_static; use regex::Regex; +use std::str::FromStr; struct Rule { matcher: String, @@ -15,7 +17,7 @@ lazy_static! { extractor: Regex::new(r"(from.)(.*)(.port)").unwrap(), }, Rule { - matcher: "Invalid user".to_string(), + matcher: "Invalid user ".to_string(), extractor: Regex::new(r"(from.)(.*)").unwrap(), }, Rule { @@ -25,16 +27,27 @@ lazy_static! { ]; } -pub fn parse(line: &str) -> Option { - let ret = SSHD_BAD.iter().find_map(|rule| { +pub fn parse(line: &str) -> Result, Error> { + let hits = SSHD_BAD.iter().find_map(|rule| { if line.contains(&rule.matcher) { - rule.extractor.captures(line)?.get(2) + rule.extractor.captures(line) } else { None } - })?; + }); - ret.as_str().parse::().ok() + if hits.is_none() { + return Ok(None); + } + + let ip = hits + .and_then(|c| c.get(2)) + .and_then(|m| IpAddr::from_str(m.as_str()).ok()); + + match ip { + Some(ip) => Ok(Some(ip)), + None => Err(Error::CantParse), + } } #[cfg(test)] @@ -49,7 +62,10 @@ mod tests { "Sep 26 06:25:32 livecompute sshd[23254]: Invalid user neal from 35.184.211.144" ]; - vectors.iter().for_each(|e| assert!(parse(*e).is_some())) + vectors.iter().for_each(|e| { + let ret = parse(*e); + assert!(ret.unwrap().is_some()); + }) } #[test] @@ -60,6 +76,22 @@ mod tests { "Sep 26 06:25:32 livecompute sshd[23254]: very good user neal from 35.184.211.144" ]; - vectors.iter().for_each(|e| assert!(parse(*e).is_none())) + vectors.iter().for_each(|e| { + let ret = parse(*e); + assert!(ret.unwrap().is_none()); + }) + } + + #[test] + fn malformed() { + let vectors = [ + "Sep 26 06:25:19 livecompute sshd[23246]: Failed password for root from 179.124.36.195.232 port 41883 ssh2", + "Sep 26 06:26:14 livecompute sshd[23292]: pam_unix(sshd:auth): authentication failure; logname= u =0 tty=ssh ruser= rhost=", + ]; + + vectors.iter().for_each(|e| { + let ret = parse(*e); + assert!(ret.is_err()); + }) } } diff --git a/src/utils.rs b/src/utils.rs index 596ab87..1d4d48c 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,5 +1,14 @@ use clap::{App, Arg}; +#[derive(Debug, thiserror::Error)] +#[allow(clippy::large_enum_variant)] +pub enum Error { + #[error("cant parse")] + CantParse, + #[error("general error")] + UnknownError, +} + pub fn cli() -> App<'static, 'static> { App::new("ban internets scanner fast 🍶") .version("v0.0.1")