diff --git a/.gitignore b/.gitignore index 1a4595e..f5908da 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ /target *.log +*.db diff --git a/Cargo.lock b/Cargo.lock index b7bf90c..73469b6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,24 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "ahash" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "allocator-api2" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" + [[package]] name = "android-tzdata" version = "0.1.1" @@ -41,6 +59,12 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "bitflags" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" + [[package]] name = "bumpalo" version = "3.16.0" @@ -106,6 +130,18 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" +[[package]] +name = "fallible-iterator" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" + +[[package]] +name = "fallible-streaming-iterator" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" + [[package]] name = "fnv" version = "1.0.7" @@ -128,6 +164,19 @@ name = "hashbrown" version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" +dependencies = [ + "ahash", + "allocator-api2", +] + +[[package]] +name = "hashlink" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "692eaaf7f7607518dd3cef090f1474b61edc5301d8012f09579920df68b725ee" +dependencies = [ + "hashbrown", +] [[package]] name = "hermit-abi" @@ -198,6 +247,17 @@ version = "0.2.153" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" +[[package]] +name = "libsqlite3-sys" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c10584274047cb335c23d3e61bcef8e323adae7c5c8c760540f73610177fc3f" +dependencies = [ + "cc", + "pkg-config", + "vcpkg", +] + [[package]] name = "lock_api" version = "0.4.9" @@ -332,6 +392,12 @@ version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" +[[package]] +name = "pkg-config" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" + [[package]] name = "ppv-lite86" version = "0.2.17" @@ -392,7 +458,21 @@ version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" dependencies = [ - "bitflags", + "bitflags 1.3.2", +] + +[[package]] +name = "rusqlite" +version = "0.31.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b838eba278d213a8beaf485bd313fd580ca4505a00d5871caeb1457c55322cae" +dependencies = [ + "bitflags 2.5.0", + "fallible-iterator", + "fallible-streaming-iterator", + "hashlink", + "libsqlite3-sys", + "smallvec", ] [[package]] @@ -486,6 +566,7 @@ dependencies = [ "log", "log4rs", "rand", + "rusqlite", "serde", "tokio", "toml", @@ -654,6 +735,18 @@ version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" @@ -874,3 +967,23 @@ checksum = "f0c976aaaa0e1f90dbb21e9587cdaf1d9679a1cde8875c0d6bd83ab96a208352" dependencies = [ "memchr", ] + +[[package]] +name = "zerocopy" +version = "0.7.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.59", +] diff --git a/Cargo.toml b/Cargo.toml index 951067b..6437e06 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,6 +9,7 @@ edition = "2021" log = "0.4.21" log4rs = "1.3.0" rand = "0.8.5" +rusqlite = { version = "0.31.0", features = ["bundled"] } serde = { version = "1.0.197", features = ["derive"] } tokio = { version = "1", features = ["full"] } toml = "0.8.12" diff --git a/README.md b/README.md index e00754b..7157fab 100644 --- a/README.md +++ b/README.md @@ -18,3 +18,8 @@ This way attacker will be entertained for some time... :D ## Configure Read config.toml and adapt it to your preferences. Keep in mind that for docker use you want to keep log_file="CONSOLE". + +## Action Script +You can configure action script by adding it to config.toml. +When a new suspicious IP address is detected, the script will be executed. See +action_script.sh for an example. diff --git a/action_script.sh b/action_script.sh new file mode 100755 index 0000000..cc470f3 --- /dev/null +++ b/action_script.sh @@ -0,0 +1,18 @@ +#!/bin/sh +# Script to run when a new suspicious IP address is detected. + +# Example for Linux: +# iptables -I INPUT -s $1 -j DROP +# +# Example for OpenBSD: +# Having a table in pf.conf: +# table persist file "/etc/blacklist" +# block drop in quick from to any +# then add/delete dinamically: +# pfctl -t badhosts -T add $1 +# pfctl -t badhosts -T delete $1 + +# Example for "debugging": +echo $1 > /tmp/foo + + diff --git a/config.toml b/config.toml index 7e11de1..69a5df5 100644 --- a/config.toml +++ b/config.toml @@ -14,3 +14,10 @@ level = "INFO" [tarpit] # delay in milliseconds delay = 1500 + +[sqlite] +location = "tarpit.db" + +[action] +# path to action script, remember give it execution permission +script = "./action_script.sh" diff --git a/src/config.rs b/src/config.rs index aea654e..8910d60 100644 --- a/src/config.rs +++ b/src/config.rs @@ -4,7 +4,9 @@ use serde::{Deserialize, Serialize}; struct ConfigToml { listen: Option, log: Option, - tarpit: Option + tarpit: Option, + sqlite: Option, + action: Option, } #[derive(Serialize, Deserialize, Debug)] @@ -24,6 +26,16 @@ struct ConfigTomlTarpit { delay: Option, } +#[derive(Serialize, Deserialize, Debug)] +struct ConfigTomlSqlite { + location: Option, +} + +#[derive(Serialize, Deserialize, Debug)] +struct ConfigTomlAction { + script: Option, +} + #[derive(Serialize, Deserialize, Debug)] pub struct Config { pub bind_addr: String, @@ -31,6 +43,8 @@ pub struct Config { pub log_file: String, pub log_level: String, pub delay: u64, + pub database_location: String, + pub action_script: String } impl Config { @@ -57,7 +71,9 @@ impl Config { ConfigToml { listen: None, log: None, - tarpit: None + tarpit: None, + sqlite: None, + action: None } }); @@ -82,12 +98,24 @@ impl Config { None => 0 }; + let database_location = match config_toml.sqlite { + Some(ConfigTomlSqlite { location }) => location.unwrap_or("./tarpit.db".to_owned()), + None => "./tarpit.db".to_owned() + }; + + let action_script = match config_toml.action { + Some(ConfigTomlAction { script }) => script.unwrap_or("".to_owned()), + None => "".to_owned() + }; + Config { bind_addr, bind_port, log_file, log_level, - delay + delay, + database_location, + action_script } } } diff --git a/src/database.rs b/src/database.rs new file mode 100644 index 0000000..f10eb94 --- /dev/null +++ b/src/database.rs @@ -0,0 +1,72 @@ +use rusqlite::{Connection, Result}; +use std::time::SystemTime; + +#[allow(dead_code)] +pub struct Suspicious { + ip_addr: String, + banned: u8, + create_time: u64, +} + +pub fn init_db(path: &str) -> Result { + let conn = Connection::open(path)?; + conn.execute( + "CREATE TABLE IF NOT EXISTS suspicious ( + ip_addr VARCHAR(128) PRIMARY KEY, + banned INTEGER NOT NULL, + create_time INTEGER NOT NULL + )", [])?; + + Ok(conn) +} + +#[allow(dead_code)] +pub fn get_all_suspicious(conn: &Connection) -> Result, rusqlite::Error> { + let mut stmt = conn.prepare("SELECT * FROM suspicious")?; + let rows = stmt.query_map([], |row| { + Ok(Suspicious { + ip_addr: row.get(0)?, + banned: row.get(1)?, + create_time: row.get(2)?, + }) + })?; + + Ok(rows.collect::, _>>()?) +} + +pub fn get_suspicious_by_ip_addr(conn: &Connection, ip_addr: &str) -> Result { + let mut stmt = conn.prepare("SELECT * FROM suspicious WHERE ip_addr = ?")?; + let row = stmt.query_row([ip_addr], |row| { + Ok(Suspicious { + ip_addr: row.get(0)?, + banned: row.get(1)?, + create_time: row.get(2)?, + }) + })?; + Ok(row) +} + +pub fn add_suspicious(conn: &Connection, mut ip_addr: &str) -> Result<(), rusqlite::Error> { + + //if ip_addr has ":" in it means it has port, we insert only IP addr + if ip_addr.contains(":") { + ip_addr = ip_addr.split(":").collect::>()[0]; + } + + let epoch = SystemTime::now() + .duration_since(SystemTime::UNIX_EPOCH) + .unwrap() + .as_secs() + .to_string(); + + let mut stmt = conn.prepare("INSERT OR IGNORE INTO suspicious (ip_addr, banned, create_time) VALUES (?, ?, ?)")?; + stmt.execute([ip_addr, "0", epoch.as_str()])?; + Ok(()) +} + +#[allow(dead_code)] +pub fn delete_suspicious(conn: &Connection, ip_addr: &str) -> Result<(), rusqlite::Error> { + let mut stmt = conn.prepare("DELETE FROM suspicious WHERE ip_addr = ?")?; + stmt.execute([ip_addr])?; + Ok(()) +} diff --git a/src/main.rs b/src/main.rs index 0f7dd37..7890004 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,12 +5,14 @@ */ mod sip; mod config; +mod database; use crate::sip::generic::*; use crate::sip::not_found::*; use crate::sip::options::*; use crate::sip::register::*; use crate::sip::unauthorized::*; +use crate::database::*; use rand::Rng; use std::error::Error; use std::net::SocketAddr; @@ -22,12 +24,15 @@ use log4rs::append::file::FileAppender; use log4rs::append::console::ConsoleAppender; use log4rs::config::{Appender, Config, Root}; use log4rs::encode::pattern::PatternEncoder; +use rusqlite::Connection; +use std::process::Command; struct Server { socket: UdpSocket, buf: Vec, to_send: Option<(usize, SocketAddr)>, - config: config::Config + config: config::Config, + db_con: Connection } impl Server { @@ -36,7 +41,8 @@ impl Server { socket, mut buf, mut to_send, - config + config, + db_con } = self; loop { @@ -44,8 +50,33 @@ impl Server { // If so then we try to send it back to the original source, waiting // until it's writable and we're able to do so. if let Some((size, peer)) = to_send { - log::info!("Suspicious peer: {}", peer); - // + // save peer + let str_peer_ip = peer.ip().to_string(); + match get_suspicious_by_ip_addr(&db_con, str_peer_ip.as_str()) { + Ok(_) => { + // was added to database, do nothing + // log::info!("Peer already added to database"); + }, + Err(_e) => { + // was not in database + // log::info!("Error getting suspicious peer from database: {}", _e); + log::info!("Suspicious peer: {}", peer.ip()); + match add_suspicious(&db_con, str_peer_ip.as_str()) { + Ok(_) => { + // launch action script + let output = Command::new(config.action_script.to_owned()) + .arg(str_peer_ip.as_str()) + .output(); + match output { + Ok(_) => log::info!("Action script executed"), + Err(e) => log::info!("Error executing action script {}", e) + } + }, + Err(_) => return Err(io::Error::new(io::ErrorKind::Other, "Error adding suspicious peer to database")), + } + } + } + // test type of message received let msg = String::from_utf8(buf[..size].to_vec()).unwrap(); match msg { @@ -206,6 +237,8 @@ async fn main() -> Result<(), Box> { log4rs::init_config(logconfig).unwrap(); + let db_con = init_db(config.database_location.as_str())?; + let addr = format!("{}:{}", config.bind_addr, config.bind_port); let socket = UdpSocket::bind(&addr).await?; log::info!("Listening on: {}", socket.local_addr()?); @@ -215,6 +248,7 @@ async fn main() -> Result<(), Box> { buf: vec![0; 1024], to_send: None, config, + db_con }; // This starts the server task. diff --git a/src/sip/forbidden.rs b/src/sip/forbidden.rs index d3c229a..a50b1ec 100644 --- a/src/sip/forbidden.rs +++ b/src/sip/forbidden.rs @@ -19,6 +19,7 @@ pub struct Forbidden { pub content_length: Option, // 0 } +#[allow(dead_code)] impl Forbidden { pub fn serialize(&self) -> Vec { let mut preout = format!(