From 6741f48285832a7b617fd080289244a701930d46 Mon Sep 17 00:00:00 2001 From: Pierre Dubouilh Date: Sun, 17 Oct 2021 15:59:03 +0200 Subject: [PATCH 1/3] bubble up parsing errors --- Cargo.lock | 21 ++++++++++++++++++++ Cargo.toml | 3 ++- src/clf.rs | 55 ++++++++++++++++++++++++++++++++++++---------------- src/main.rs | 15 ++++++++++---- src/sshd.rs | 48 +++++++++++++++++++++++++++++++++++++-------- src/utils.rs | 9 +++++++++ 6 files changed, 121 insertions(+), 30 deletions(-) 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") From 283653a775dffd133f8337c19ff91a60f41e1b55 Mon Sep 17 00:00:00 2001 From: Pierre Dubouilh Date: Sun, 17 Oct 2021 18:59:32 +0200 Subject: [PATCH 2/3] anyhow --- Cargo.lock | 28 +++++------------- Cargo.toml | 2 +- src/clf.rs | 8 ++--- src/jail.rs | 83 +++++++++++++++++++++++----------------------------- src/main.rs | 77 +++++++++++++++++++++++++++++++----------------- src/sshd.rs | 6 ++-- src/utils.rs | 9 ------ 7 files changed, 101 insertions(+), 112 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c064533..040405c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -20,6 +20,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "anyhow" +version = "1.0.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61604a8f862e1d5c3229fdd78f8b02c68dcf73a4c4b05fd636d12240aaa242c1" + [[package]] name = "atty" version = "0.2.14" @@ -47,11 +53,11 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" name = "blockfast" version = "0.1.0" dependencies = [ + "anyhow", "clap", "lazy_static", "linemux", "regex", - "thiserror", "tokio", ] @@ -399,26 +405,6 @@ 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 2e7d802..25f875d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,4 +12,4 @@ tokio = { version = "1", features = ["rt-multi-thread", "macros"] } lazy_static = "1.4.0" regex = "1.5.4" clap = "2.33.3" -thiserror = "1.0.26" +anyhow = "1.0.44" diff --git a/src/clf.rs b/src/clf.rs index 7fa0e02..972e27f 100644 --- a/src/clf.rs +++ b/src/clf.rs @@ -1,4 +1,4 @@ -use crate::utils::Error; +use anyhow::Result; use lazy_static::lazy_static; use std::net::IpAddr; @@ -8,15 +8,15 @@ lazy_static! { static ref BAD_STATUSES: [u32; 2] = [401, 429]; } -pub fn parse(line: &str) -> Result, Error> { +pub fn parse(line: &str) -> Result> { // TODO: Use a proper parser ? let elts: Vec<&str> = line.split_whitespace().collect(); let ip_str = elts[0]; - let ip = ip_str.parse::().or(Err(Error::CantParse))?; + let ip = ip_str.parse::()?; let http_code_str = elts[elts.len() - 2] as &str; - let http_code = http_code_str.parse::().or(Err(Error::CantParse))?; + let http_code = http_code_str.parse::()?; for status in BAD_STATUSES.iter() { if *status == http_code { diff --git a/src/jail.rs b/src/jail.rs index ef45127..f64e17c 100644 --- a/src/jail.rs +++ b/src/jail.rs @@ -1,9 +1,10 @@ use std::collections::HashMap; use std::net::IpAddr; -use std::panic::panic_any; use std::process::Command; use std::sync::Mutex; +use anyhow::*; + pub struct Jail { jailtime: u32, allowance: u8, @@ -11,10 +12,11 @@ pub struct Jail { } const JAIL_NAME: &str = "blockfast_jail"; -const GENERAL_PANIC_MSG: &str = + +const ERR_MSG: &str = "error using ipset/iptables, maybe it's not installed, this program isn't running as root ?"; -fn ipset_init() -> Option<()> { +fn ipset_init() -> Result<()> { let init0 = format!("ipset create {} hash:ip timeout 0", JAIL_NAME); let init1 = format!( "iptables -I INPUT 1 -m set -j DROP --match-set {} src", @@ -30,41 +32,37 @@ fn ipset_init() -> Option<()> { let args2: Vec<&str> = init2.split_whitespace().collect(); // create - let out = Command::new("sudo").args(args0).output().ok()?; - - if out.status.code()? != 0 { - let already_exists = std::str::from_utf8(&out.stderr) - .ok()? - .contains("set with the same name already exists"); + let out = Command::new("sudo").args(args0).output()?; + if out.status.code() != Some(0) { + let already_exists = + std::str::from_utf8(&out.stderr)?.contains("set with the same name already exists"); if already_exists { - return None; + return Ok(()); } else { eprintln!("{:?}", out); - panic_any(GENERAL_PANIC_MSG); + bail!(ERR_MSG); } } // setup input - let out_input = Command::new("sudo").args(args1).output().ok()?; - - if out_input.status.code()? != 0 { - eprintln!("{:?}", out_input); - panic_any(GENERAL_PANIC_MSG); + let out = Command::new("sudo").args(args1).output()?; + if out.status.code() != Some(0) { + eprintln!("{:?}", out); + bail!(ERR_MSG); } // setup fwd - let out_fwd = Command::new("sudo").args(args2).output().ok()?; - - if out_fwd.status.code()? != 0 { - eprintln!("{:?}", out_fwd); - panic_any(GENERAL_PANIC_MSG); + let out = Command::new("sudo").args(args2).output()?; + if out.status.code() != Some(0) { + eprintln!("{:?}", out); + bail!(ERR_MSG); } - None + Ok(()) } -fn ipset_block(jailtime: u32, ip: IpAddr) -> Option<()> { +fn ipset_block(jailtime: u32, ip: IpAddr) -> Result<()> { let sentence = format!( "ipset add {} {} timeout {}", JAIL_NAME, @@ -73,36 +71,29 @@ fn ipset_block(jailtime: u32, ip: IpAddr) -> Option<()> { ); let sentence_sl: Vec<&str> = sentence.split_whitespace().collect(); - let out = Command::new("sudo").args(sentence_sl).output().ok()?; - - if out.status.code()? != 0 { - return None; + let out = Command::new("sudo").args(sentence_sl).output()?; + if out.status.code() != Some(0) { + eprintln!("{:?}", out); + bail!("error executing ipset ban"); } - Some(()) + Ok(()) } impl Jail { - pub fn new(allowance: u8, jailtime: u32) -> Jail { - if ipset_init().is_some() { - panic_any(GENERAL_PANIC_MSG); - }; + pub fn new(allowance: u8, jailtime: u32) -> Result { + ipset_init()?; - eprintln!( - "+ jail setup, allowing {} offences, jailtime: {}s", - allowance, jailtime - ); - - Jail { + Ok(Jail { allowance, jailtime, remand: Mutex::new(HashMap::new()), - } + }) } - pub fn probe(&self, ip: IpAddr) -> Option<()> { + pub fn probe(&self, ip: IpAddr) -> Result { let should_ban = { - let mut locked_map = self.remand.lock().ok()?; + let mut locked_map = self.remand.lock().map_err(|_| anyhow!("cant lock"))?; // TODO: set time of last offence, and add grace let hits = *locked_map.entry(ip).and_modify(|e| *e += 1).or_insert(1); @@ -116,12 +107,10 @@ impl Jail { }; if should_ban { - match ipset_block(self.jailtime, ip) { - Some(_) => eprintln!("~ {} going to jail", ip), - None => eprintln!("! ERR {} going to jail", ip), - } + ipset_block(self.jailtime, ip)?; + Ok(true) + } else { + Ok(false) } - - None } } diff --git a/src/main.rs b/src/main.rs index be25227..037988b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,4 @@ +use anyhow::*; use linemux::MuxedLines; mod clf; @@ -6,30 +7,61 @@ mod utils; mod jail; use crate::jail::Jail; -use crate::utils::Error; -async fn run() -> Option<()> { +fn judge(path_sshd: &str, path_clf: &str, payload: &str, path: &str, jail: &Jail) -> Result<()> { + let do_sshd = !path_sshd.is_empty(); + let do_clf = !path_clf.is_empty(); + let mut target = ""; + + let ret_parse = if do_sshd && path.ends_with(path_sshd) { + target = "sshd"; + sshd::parse(payload) + } else if do_clf && path.ends_with(path_clf) { + target = "clf "; + clf::parse(payload) + } else { + Err(anyhow!("cant locate file !")) + }; + + let ip = match ret_parse? { + Some(ip) => ip, + None => return Ok(()), + }; + + if jail.probe(ip)? { + eprintln!("~ {} - too many infraction, jailtime for: {}", target, ip); + } + + Ok(()) +} +async fn run() -> Result<()> { let args = utils::cli().get_matches(); - let mut lines = MuxedLines::new().ok()?; + let mut lines = MuxedLines::new()?; // jail - let jailtime: u32 = args.value_of("jailtime")?.parse().ok()?; - let allowance: u8 = args.value_of("allowance")?.parse().ok()?; - let jail = Jail::new(allowance, jailtime); + let jailtime_str = args.value_of("jailtime").unwrap_or(""); + let jailtime = jailtime_str.parse().context("parsing jailtime")?; + + let allowance_str = args.value_of("allowance").unwrap_or(""); + let allowance = allowance_str.parse().context("parsing allowance")?; + + let jail = Jail::new(allowance, jailtime)?; + eprintln!( + "+ jail setup, offences allowed: {}, jailtime {}s", + allowance, jailtime + ); // sshd let path_sshd = args.value_of("sshd_logpath").unwrap_or(""); - let do_sshd = !path_sshd.is_empty(); - if do_sshd { - lines.add_file(path_sshd).await.ok()?; + if !path_sshd.is_empty() { + lines.add_file(path_sshd).await?; eprintln!("+ starting with sshd parsing at {}", path_sshd); } // common log format let path_clf = args.value_of("clf_logpath").unwrap_or(""); - let do_clf = !path_clf.is_empty(); - if do_clf { - lines.add_file(path_clf).await.ok()?; + if !path_clf.is_empty() { + lines.add_file(path_clf).await?; eprintln!("+ starting with clf parsing at {}", path_clf); } @@ -37,28 +69,19 @@ async fn run() -> Option<()> { let payload = line.line(); let path = line.source().display().to_string(); - 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) - } else { - Err(Error::UnknownError) - }; - - if let Ok(Some(ip)) = res { - jail.probe(ip); - } else { - eprintln!("! error processing logline: {}", path); + if let Err(err) = judge(path_sshd, path_clf, payload, &path, &jail) { + eprintln!("! ERR {:?} - file {}", err, path) } } - Some(()) + Ok(()) } #[tokio::main] async fn main() -> std::io::Result<()> { - let _ = run().await; - eprintln!("! ERR"); + let ret = run().await; + let _ = ret.map_err(|e| eprintln!("! ERROR {:?}", e)); + eprintln!("\n"); let _ = utils::cli().print_help(); Ok(()) } diff --git a/src/sshd.rs b/src/sshd.rs index 64acf54..2d19bda 100644 --- a/src/sshd.rs +++ b/src/sshd.rs @@ -1,4 +1,4 @@ -use crate::utils::Error; +use anyhow::*; use std::net::IpAddr; use lazy_static::lazy_static; @@ -27,7 +27,7 @@ lazy_static! { ]; } -pub fn parse(line: &str) -> Result, Error> { +pub fn parse(line: &str) -> Result> { let hits = SSHD_BAD.iter().find_map(|rule| { if line.contains(&rule.matcher) { rule.extractor.captures(line) @@ -46,7 +46,7 @@ pub fn parse(line: &str) -> Result, Error> { match ip { Some(ip) => Ok(Some(ip)), - None => Err(Error::CantParse), + None => Err(anyhow!("cant parse sshd entry")), } } diff --git a/src/utils.rs b/src/utils.rs index 1d4d48c..596ab87 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,14 +1,5 @@ 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") From 4d2aee51782ab053fcdbd386e4295318d1f0a907 Mon Sep 17 00:00:00 2001 From: Pierre Dubouilh Date: Sun, 17 Oct 2021 20:49:37 +0200 Subject: [PATCH 3/3] use proper enum return type --- src/clf.rs | 21 ++++++++++++++------- src/jail.rs | 8 +++++--- src/main.rs | 32 ++++++++++++++++++++++---------- src/sshd.rs | 25 ++++++++++++++++--------- src/utils.rs | 16 ++++++++++++++++ 5 files changed, 73 insertions(+), 29 deletions(-) diff --git a/src/clf.rs b/src/clf.rs index 972e27f..63453ad 100644 --- a/src/clf.rs +++ b/src/clf.rs @@ -1,3 +1,4 @@ +use crate::utils::ParsingStatus; use anyhow::Result; use lazy_static::lazy_static; use std::net::IpAddr; @@ -8,7 +9,7 @@ lazy_static! { static ref BAD_STATUSES: [u32; 2] = [401, 429]; } -pub fn parse(line: &str) -> Result> { +pub fn parse(line: &str) -> Result { // TODO: Use a proper parser ? let elts: Vec<&str> = line.split_whitespace().collect(); @@ -20,11 +21,11 @@ pub fn parse(line: &str) -> Result> { for status in BAD_STATUSES.iter() { if *status == http_code { - return Ok(Some(ip)); + return Ok(ParsingStatus::BadEntry(ip)); } } - Ok(None) + Ok(ParsingStatus::OkEntry) } #[cfg(test)] @@ -39,8 +40,11 @@ mod tests { ]; vectors.iter().for_each(|e| { - let ret = parse(*e); - assert!(ret.unwrap().is_some()); + let ret = parse(*e).unwrap(); + match ret { + ParsingStatus::BadEntry(_) => {} + _ => panic!("bad parsing"), + } }) } @@ -52,8 +56,11 @@ mod tests { ]; vectors.iter().for_each(|e| { - let ret = parse(*e); - assert!(ret.unwrap().is_none()); + let ret = parse(*e).unwrap(); + match ret { + ParsingStatus::OkEntry => {} + _ => panic!("bad parsing"), + } }) } diff --git a/src/jail.rs b/src/jail.rs index f64e17c..1038c09 100644 --- a/src/jail.rs +++ b/src/jail.rs @@ -5,6 +5,8 @@ use std::sync::Mutex; use anyhow::*; +use crate::utils::JailStatus; + pub struct Jail { jailtime: u32, allowance: u8, @@ -91,7 +93,7 @@ impl Jail { }) } - pub fn probe(&self, ip: IpAddr) -> Result { + pub fn probe(&self, ip: IpAddr) -> Result { let should_ban = { let mut locked_map = self.remand.lock().map_err(|_| anyhow!("cant lock"))?; @@ -108,9 +110,9 @@ impl Jail { if should_ban { ipset_block(self.jailtime, ip)?; - Ok(true) + Ok(JailStatus::Jailed(ip)) } else { - Ok(false) + Ok(JailStatus::Remand) } } } diff --git a/src/main.rs b/src/main.rs index 037988b..17a4800 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,8 +7,15 @@ mod utils; mod jail; use crate::jail::Jail; +use crate::utils::*; -fn judge(path_sshd: &str, path_clf: &str, payload: &str, path: &str, jail: &Jail) -> Result<()> { +fn judge( + path_sshd: &str, + path_clf: &str, + payload: &str, + path: &str, + jail: &Jail, +) -> Result { let do_sshd = !path_sshd.is_empty(); let do_clf = !path_clf.is_empty(); let mut target = ""; @@ -24,16 +31,16 @@ fn judge(path_sshd: &str, path_clf: &str, payload: &str, path: &str, jail: &Jail }; let ip = match ret_parse? { - Some(ip) => ip, - None => return Ok(()), + ParsingStatus::OkEntry => return Ok(Judgment::Good), + ParsingStatus::BadEntry(ip) => ip, }; - if jail.probe(ip)? { - eprintln!("~ {} - too many infraction, jailtime for: {}", target, ip); + match jail.probe(ip)? { + JailStatus::Remand => Ok(Judgment::Remand), + JailStatus::Jailed(ip) => Ok(Judgment::Bad(target, ip)), } - - Ok(()) } + async fn run() -> Result<()> { let args = utils::cli().get_matches(); let mut lines = MuxedLines::new()?; @@ -69,9 +76,14 @@ async fn run() -> Result<()> { let payload = line.line(); let path = line.source().display().to_string(); - if let Err(err) = judge(path_sshd, path_clf, payload, &path, &jail) { - eprintln!("! ERR {:?} - file {}", err, path) - } + match judge(path_sshd, path_clf, payload, &path, &jail) { + Err(err) => eprintln!("! ERR {:?} - file {}", err, path), + Ok(Judgment::Good) => {} + Ok(Judgment::Remand) => {} + Ok(Judgment::Bad(target, ip)) => { + eprintln!("~ too many infraction, {} jailtime for: {}", target, ip) + } + }; } Ok(()) diff --git a/src/sshd.rs b/src/sshd.rs index 2d19bda..17a93aa 100644 --- a/src/sshd.rs +++ b/src/sshd.rs @@ -1,10 +1,11 @@ use anyhow::*; -use std::net::IpAddr; - use lazy_static::lazy_static; use regex::Regex; +use std::net::IpAddr; use std::str::FromStr; +use crate::utils::ParsingStatus; + struct Rule { matcher: String, extractor: Regex, @@ -27,7 +28,7 @@ lazy_static! { ]; } -pub fn parse(line: &str) -> Result> { +pub fn parse(line: &str) -> Result { let hits = SSHD_BAD.iter().find_map(|rule| { if line.contains(&rule.matcher) { rule.extractor.captures(line) @@ -37,7 +38,7 @@ pub fn parse(line: &str) -> Result> { }); if hits.is_none() { - return Ok(None); + return Ok(ParsingStatus::OkEntry); } let ip = hits @@ -45,7 +46,7 @@ pub fn parse(line: &str) -> Result> { .and_then(|m| IpAddr::from_str(m.as_str()).ok()); match ip { - Some(ip) => Ok(Some(ip)), + Some(ip) => Ok(ParsingStatus::BadEntry(ip)), None => Err(anyhow!("cant parse sshd entry")), } } @@ -63,8 +64,11 @@ mod tests { ]; vectors.iter().for_each(|e| { - let ret = parse(*e); - assert!(ret.unwrap().is_some()); + let ret = parse(*e).unwrap(); + match ret { + ParsingStatus::BadEntry(_) => {} + _ => panic!("bad parsing"), + } }) } @@ -77,8 +81,11 @@ mod tests { ]; vectors.iter().for_each(|e| { - let ret = parse(*e); - assert!(ret.unwrap().is_none()); + let ret = parse(*e).unwrap(); + match ret { + ParsingStatus::OkEntry => {} + _ => panic!("bad parsing"), + } }) } diff --git a/src/utils.rs b/src/utils.rs index 596ab87..afd98a8 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,4 +1,20 @@ use clap::{App, Arg}; +use std::net::IpAddr; + +pub enum ParsingStatus { + OkEntry, + BadEntry(IpAddr), +} +pub enum Judgment { + Good, + Remand, + Bad(&'static str, IpAddr), +} + +pub enum JailStatus { + Remand, + Jailed(IpAddr), +} pub fn cli() -> App<'static, 'static> { App::new("ban internets scanner fast 🍶")