From 283653a775dffd133f8337c19ff91a60f41e1b55 Mon Sep 17 00:00:00 2001 From: Pierre Dubouilh Date: Sun, 17 Oct 2021 18:59:32 +0200 Subject: [PATCH] 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")