From 85d85a4ae4692bc80f3d8f5115eaeef89b3a20c4 Mon Sep 17 00:00:00 2001 From: Paul Lecuq Date: Fri, 27 May 2022 13:59:17 +0200 Subject: [PATCH] initial version of ipblc after blparser renaming --- .drone.yml | 39 ++ .gitignore | 6 + Cargo.lock | 1277 ++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 40 ++ Cross.toml | 2 + Dockerfile | 6 + README.md | 86 +++ src/config/mod.rs | 384 +++++++++++++ src/db.rs | 47 ++ src/firewall/mod.rs | 95 ++++ src/ip.rs | 252 +++++++++ src/ipblc/full.rs | 73 +++ src/ipblc/inc.rs | 180 ++++++ src/ipblc/mod.rs | 79 +++ src/main.rs | 17 + src/regexps/date.txt | 1 + src/regexps/ipv4.txt | 1 + src/regexps/ipv6.txt | 1 + src/utils.rs | 82 +++ src/zmqcom.rs | 15 + tests/test.py | 12 + tests/testfw.rs | 50 ++ 22 files changed, 2745 insertions(+) create mode 100644 .drone.yml create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 Cross.toml create mode 100644 Dockerfile create mode 100644 README.md create mode 100644 src/config/mod.rs create mode 100644 src/db.rs create mode 100644 src/firewall/mod.rs create mode 100644 src/ip.rs create mode 100644 src/ipblc/full.rs create mode 100644 src/ipblc/inc.rs create mode 100644 src/ipblc/mod.rs create mode 100644 src/main.rs create mode 100644 src/regexps/date.txt create mode 100644 src/regexps/ipv4.txt create mode 100644 src/regexps/ipv6.txt create mode 100644 src/utils.rs create mode 100644 src/zmqcom.rs create mode 100755 tests/test.py create mode 100644 tests/testfw.rs diff --git a/.drone.yml b/.drone.yml new file mode 100644 index 0000000..678ff0c --- /dev/null +++ b/.drone.yml @@ -0,0 +1,39 @@ +--- +kind: pipeline +type: docker +name: default + +steps: + - name: test and build + image: rust:1 + commands: + - apt-get update -y + - apt-get install -y libzmq3-dev libnftnl-dev libmnl-dev + - cargo build --verbose --all + - cargo test --verbose --all + when: + event: push + - name: release + image: rust:1 + commands: + - apt-get update -y + - apt-get install -y libzmq3-dev libnftnl-dev libmnl-dev + - cargo build --release --verbose --all + - cd target/release + - tar -czvf ipblc-${DRONE_TAG}.tar.gz ipblc + when: + event: tag + - name: publish + image: plugins/gitea-release + settings: + base_url: https://git.paulbsd.com + api_key: + from_secret: gitea_token + files: "target/release/*.tar.gz" + checksum: + - sha256 + - sha512 + environment: + PLUGIN_TITLE: "" + when: + event: tag diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3766596 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +*.json +*.swp +/*.gz +/perf* +/sample +/target \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..f5197ee --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,1277 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "aho-corasick" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" +dependencies = [ + "memchr", +] + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "base64" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bumpalo" +version = "3.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4a45a46ab1f2412e53d3a0ade76ffad2025804294569aae387231a0cd6e0899" + +[[package]] +name = "bytes" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" + +[[package]] +name = "cc" +version = "1.0.73" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "chrono" +version = "0.4.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" +dependencies = [ + "libc", + "num-integer", + "num-traits", + "serde", + "time", + "winapi", +] + +[[package]] +name = "clap" +version = "3.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2dbdf4bdacb33466e854ce889eee8dfd5729abf7ccd7664d0a2d60cd384440b" +dependencies = [ + "atty", + "bitflags", + "clap_lex", + "indexmap", + "strsim", + "termcolor", + "textwrap", +] + +[[package]] +name = "clap_lex" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a37c35f1112dad5e6e0b1adaff798507497a18fceeb30cceb3bae7d1427b9213" +dependencies = [ + "os_str_bytes", +] + +[[package]] +name = "encoding_rs" +version = "0.8.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9852635589dc9f9ea1b6fe9f05b50ef208c85c834a562f0c6abb1c475736ec2b" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "err-derive" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c34a887c8df3ed90498c1c437ce21f211c8e27672921a8ffa293cb8d6d4caa9e" +dependencies = [ + "proc-macro-error", + "proc-macro2", + "quote", + "rustversion", + "syn", + "synstructure", +] + +[[package]] +name = "error-chain" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9435d864e017c3c6afeac1654189b06cdb491cf2ff73dbf0d73b0f292f42ff8" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "form_urlencoded" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191" +dependencies = [ + "matches", + "percent-encoding", +] + +[[package]] +name = "futures-channel" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3083ce4b914124575708913bca19bfe887522d6e2e6d0952943f5eac4a74010" +dependencies = [ + "futures-core", +] + +[[package]] +name = "futures-core" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c09fd04b7e4073ac7156a9539b57a484a8ea920f79c7c675d05d289ab6110d3" + +[[package]] +name = "futures-sink" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21163e139fa306126e6eedaf49ecdb4588f939600f0b1e770f4205ee4b7fa868" + +[[package]] +name = "futures-task" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c66a976bf5909d801bbef33416c41372779507e7a6b3a5e25e4749c58f776a" + +[[package]] +name = "futures-util" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8b7abd5d659d9b90c8cba917f6ec750a74e2dc23902ef9cd4cc8c8b22e6036a" +dependencies = [ + "futures-core", + "futures-task", + "pin-project-lite", + "pin-utils", +] + +[[package]] +name = "h2" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37a82c6d637fc9515a4694bbf1cb2457b79d81ce52b3108bdeea58b07dd34a57" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "http" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff8670570af52249509a86f5e3e18a08c60b177071826898fde8997cf5f6bfbb" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" +dependencies = [ + "bytes", + "http", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "496ce29bb5a52785b44e0f7ca2847ae0bb839c9bd28f69acac9b99d461c0c04c" + +[[package]] +name = "httpdate" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" + +[[package]] +name = "hyper" +version = "0.14.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b26ae0a80afebe130861d90abf98e3814a4f28a4c6ffeb5ab8ebb2be311e0ef2" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d87c48c02e0dc5e3b849a2041db3029fd066650f8f717c07bf8ed78ccb895cac" +dependencies = [ + "http", + "hyper", + "rustls", + "tokio", + "tokio-rustls", +] + +[[package]] +name = "idna" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" +dependencies = [ + "matches", + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "indexmap" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f647032dfaa1f8b6dc29bd3edb7bbef4861b8b8007ebb118d6db284fd59f6ee" +dependencies = [ + "autocfg", + "hashbrown", +] + +[[package]] +name = "ipblc" +version = "1.0.0" +dependencies = [ + "chrono", + "clap", + "ipnet", + "lazy_static", + "mnl", + "nftnl", + "nix", + "regex", + "reqwest", + "serde", + "serde_json", + "tokio", + "zmq", +] + +[[package]] +name = "ipnet" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879d54834c8c76457ef4293a689b2a8c59b076067ad77b15efafbb05f92a592b" + +[[package]] +name = "itoa" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112c678d4050afce233f4f2852bb2eb519230b3cf12f33585275537d7e41578d" + +[[package]] +name = "js-sys" +version = "0.3.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "671a26f820db17c2a2750743f1dd03bafd15b98c9f30c7c2628c024c05d73397" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836" + +[[package]] +name = "lock_api" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "327fa5b6a6940e4699ec49a9beae1ea4845c6bab9314e4f84ac68742139d8c53" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "matches" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" + +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + +[[package]] +name = "memoffset" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" +dependencies = [ + "autocfg", +] + +[[package]] +name = "metadeps" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73b122901b3a675fac8cecf68dcb2f0d3036193bc861d1ac0e1c337f7d5254c2" +dependencies = [ + "error-chain", + "pkg-config", + "toml", +] + +[[package]] +name = "mime" +version = "0.3.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" + +[[package]] +name = "mio" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "713d550d9b44d89174e066b7a6217ae06234c10cb47819a88290d2b353c31799" +dependencies = [ + "libc", + "log", + "wasi 0.11.0+wasi-snapshot-preview1", + "windows-sys", +] + +[[package]] +name = "mnl" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1a5469630da93e1813bb257964c0ccee3b26b6879dd858039ddec35cc8681ed" +dependencies = [ + "libc", + "log", + "mnl-sys", +] + +[[package]] +name = "mnl-sys" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9750685b201e1ecfaaf7aa5d0387829170fa565989cc481b49080aa155f70457" +dependencies = [ + "libc", + "pkg-config", +] + +[[package]] +name = "nftnl" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9201688bd0bc571dfa4c21ce0a525480c8b782776cf88e12571fa89108dd920" +dependencies = [ + "bitflags", + "err-derive", + "log", + "nftnl-sys", +] + +[[package]] +name = "nftnl-sys" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26b5c587b6a5e76a3a5d51e0a757ae66dbff38c277563485807ae979ce361b56" +dependencies = [ + "cfg-if", + "libc", + "pkg-config", +] + +[[package]] +name = "nix" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f17df307904acd05aa8e32e97bb20f2a0df1728bbc2d771ae8f9a90463441e9" +dependencies = [ + "bitflags", + "cfg-if", + "libc", + "memoffset", +] + +[[package]] +name = "num-integer" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_cpus" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "once_cell" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7709cef83f0c1f58f666e746a08b21e0085f7440fa6a29cc194d68aac97a4225" + +[[package]] +name = "os_str_bytes" +version = "6.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21326818e99cfe6ce1e524c2a805c189a99b5ae555a35d19f9a284b427d86afa" + +[[package]] +name = "parking_lot" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87f5ec2493a61ac0506c0f4199f99070cbe83857b0337006a30f3e6719b8ef58" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09a279cbf25cb0757810394fbc1e359949b59e348145c643a939a525692e6929" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-sys", +] + +[[package]] +name = "percent-encoding" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" + +[[package]] +name = "pin-project-lite" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkg-config" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1df8c4ec4b0627e53bdf214615ad287367e482558cf84b109250b37464dc03ae" + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro2" +version = "1.0.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c54b25569025b7fc9651de43004ae593a75ad88543b17178aa5e1b9c4f15f56f" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1feb54ed693b93a84e14094943b84b7c4eae204c512b7ccb95ab0c66d278ad1" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "redox_syscall" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62f25bc4c7e55e0b0b7a1d43fb893f4fa1361d0abe38b9ce4f323c2adfe6ef42" +dependencies = [ + "bitflags", +] + +[[package]] +name = "regex" +version = "1.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d83f127d94bdbcda4c8cc2e50f6f84f4b611f69c902699ca385a39c3a75f9ff1" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.6.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49b3de9ec5dc0a3417da371aab17d729997c15010e7fd24ff707773a33bddb64" + +[[package]] +name = "reqwest" +version = "0.11.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46a1f7aa4f35e5e8b4160449f51afc758f0ce6454315a9fa7d0d113e958c41eb" +dependencies = [ + "base64", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "hyper", + "hyper-rustls", + "ipnet", + "js-sys", + "lazy_static", + "log", + "mime", + "percent-encoding", + "pin-project-lite", + "rustls", + "rustls-pemfile", + "serde", + "serde_json", + "serde_urlencoded", + "tokio", + "tokio-rustls", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "webpki-roots", + "winreg", +] + +[[package]] +name = "ring" +version = "0.16.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" +dependencies = [ + "cc", + "libc", + "once_cell", + "spin", + "untrusted", + "web-sys", + "winapi", +] + +[[package]] +name = "rustls" +version = "0.20.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aab8ee6c7097ed6057f43c187a62418d0c05a4bd5f18b3571db50ee0f9ce033" +dependencies = [ + "log", + "ring", + "sct", + "webpki", +] + +[[package]] +name = "rustls-pemfile" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ee86d63972a7c661d1536fefe8c3c8407321c3df668891286de28abcd087360" +dependencies = [ + "base64", +] + +[[package]] +name = "rustversion" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2cc38e8fa666e2de3c4aba7edeb5ffc5246c1c2ed0e3d17e560aeeba736b23f" + +[[package]] +name = "ryu" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3f6f92acf49d1b98f7a81226834412ada05458b7364277387724a237f062695" + +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + +[[package]] +name = "sct" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "serde" +version = "1.0.137" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61ea8d54c77f8315140a05f4c7237403bf38b72704d031543aa1d16abbf517d1" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.137" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f26faba0c3959972377d3b2d306ee9f71faee9714294e41bb777f83f88578be" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b7ce2b32a1aed03c558dc61a5cd328f15aff2dbc17daad8fb8af04d2100e15c" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0" +dependencies = [ + "libc", +] + +[[package]] +name = "slab" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb703cfe953bccee95685111adeedb76fabe4e97549a58d16f03ea7b9367bb32" + +[[package]] +name = "smallvec" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83" + +[[package]] +name = "socket2" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66d72b759436ae32898a2af0a14218dbf55efde3feeb170eb623637db85ee1e0" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "spin" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" + +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + +[[package]] +name = "syn" +version = "1.0.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbaf6116ab8924f39d52792136fb74fd60a80194cf1b1c6ffa6453eef1c3f942" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "synstructure" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "unicode-xid", +] + +[[package]] +name = "termcolor" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "textwrap" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb" + +[[package]] +name = "time" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255" +dependencies = [ + "libc", + "wasi 0.10.0+wasi-snapshot-preview1", + "winapi", +] + +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" + +[[package]] +name = "tokio" +version = "1.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4903bf0427cf68dddd5aa6a93220756f8be0c34fcfa9f5e6191e103e15a31395" +dependencies = [ + "bytes", + "libc", + "memchr", + "mio", + "num_cpus", + "once_cell", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "winapi", +] + +[[package]] +name = "tokio-macros" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b557f72f448c511a979e2564e55d74e6c4432fc96ff4f6241bc6bded342643b7" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tokio-rustls" +version = "0.23.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c43ee83903113e03984cb9e5cebe6c04a5116269e900e3ddba8f068a62adda59" +dependencies = [ + "rustls", + "tokio", + "webpki", +] + +[[package]] +name = "tokio-util" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f988a1a1adc2fb21f9c12aa96441da33a1728193ae0b95d2be22dbd17fcb4e5c" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", + "tracing", +] + +[[package]] +name = "toml" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "736b60249cb25337bc196faa43ee12c705e426f3d55c214d73a4e7be06f92cb4" + +[[package]] +name = "tower-service" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "360dfd1d6d30e05fda32ace2c8c70e9c0a9da713275777f5a4dbb8a1893930c6" + +[[package]] +name = "tracing" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0ecdcb44a79f0fe9844f0c4f33a342cbcbb5117de8001e6ba0dc2351327d09" +dependencies = [ + "cfg-if", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc6b8ad3567499f98a1db7a752b07a7c8c7c7c34c332ec00effb2b0027974b7c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f54c8ca710e81886d498c2fd3331b56c93aa248d49de2222ad2742247c60072f" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "try-lock" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" + +[[package]] +name = "unicode-bidi" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" + +[[package]] +name = "unicode-ident" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d22af068fba1eb5edcb4aea19d382b2a3deb4c8f9d475c589b6ada9e0fd493ee" + +[[package]] +name = "unicode-normalization" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d54590932941a9e9266f0832deed84ebe1bf2e4c9e4a3554d393d18f5e854bf9" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-xid" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "957e51f3646910546462e67d5f7599b9e4fb8acdd304b087a6494730f9eebf04" + +[[package]] +name = "untrusted" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" + +[[package]] +name = "url" +version = "2.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c" +dependencies = [ + "form_urlencoded", + "idna", + "matches", + "percent-encoding", +] + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "want" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" +dependencies = [ + "log", + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.10.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.80" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27370197c907c55e3f1a9fbe26f44e937fe6451368324e009cba39e139dc08ad" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.80" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53e04185bfa3a779273da532f5025e33398409573f348985af9a1cbf3774d3f4" +dependencies = [ + "bumpalo", + "lazy_static", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f741de44b75e14c35df886aff5f1eb73aa114fa5d4d00dcd37b5e01259bf3b2" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.80" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17cae7ff784d7e83a2fe7611cfe766ecf034111b49deb850a3dc7699c08251f5" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.80" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99ec0dc7a4756fffc231aab1b9f2f578d23cd391390ab27f952ae0c9b3ece20b" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.80" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d554b7f530dee5964d9a9468d95c1f8b8acae4f282807e7d27d4b03099a46744" + +[[package]] +name = "web-sys" +version = "0.3.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b17e741662c70c8bd24ac5c5b18de314a2c26c32bf8346ee1e6f53de919c283" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f095d78192e208183081cc07bc5515ef55216397af48b873e5edcd72637fa1bd" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "webpki-roots" +version = "0.22.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d8de8415c823c8abd270ad483c6feeac771fad964890779f9a8cb24fbbc1bf" +dependencies = [ + "webpki", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" +dependencies = [ + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" + +[[package]] +name = "windows_i686_gnu" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" + +[[package]] +name = "windows_i686_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" + +[[package]] +name = "winreg" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d" +dependencies = [ + "winapi", +] + +[[package]] +name = "zmq" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aad98a7a617d608cd9e1127147f630d24af07c7cd95ba1533246d96cbdd76c66" +dependencies = [ + "bitflags", + "libc", + "log", + "zmq-sys", +] + +[[package]] +name = "zmq-sys" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d33a2c51dde24d5b451a2ed4b488266df221a5eaee2ee519933dc46b9a9b3648" +dependencies = [ + "libc", + "metadeps", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..4580562 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,40 @@ +[package] +name = "ipblc" +version = "1.0.0" +edition = "2021" +authors = ["PaulBSD "] +description = "ipblc is a tool that search and send attacking ip addresses to ipbl" +repository = "https://git.paulbsd.com/paulbsd/ipblc" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +chrono = { version = "0.4", features = ["serde"] } +clap = "3.1" +ipnet = "2.5" +lazy_static = "1.4" +mnl = "0.2" +nftnl = "0.6" +nix = "0.24" +regex = "1.5" +reqwest = { version = "0.11", default-features = false, features = ["json","rustls-tls"] } +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" +tokio = { version = "1.18", features = ["full"] } +zmq = "0.9" + +# [target.aarch64-unknown-linux-gnu.dependencies] +# flate2 = { version = "1.0", features = ["cloudflare_zlib"] } + +# [target.aarch64-linux-android.dependencies] +# flate2 = { version = "1.0", features = ["zlib"] } + +# [target.armv7-unknown-linux-gnueabihf.dependencies] +# flate2 = { version = "1.0", features = ["zlib"] } + +# [target.x86_64-unknown-linux-gnu.dependencies] +# flate2 = { version = "1.0", features = ["cloudflare_zlib"] } + +[profile.release] +debug = false +opt-level = 3 \ No newline at end of file diff --git a/Cross.toml b/Cross.toml new file mode 100644 index 0000000..2f5ddcb --- /dev/null +++ b/Cross.toml @@ -0,0 +1,2 @@ +[target.aarch64-unknown-linux-musl] +image = "cross-arm64:latest" diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..a9a1404 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,6 @@ +FROM rustembedded/cross:aarch64-unknown-linux-musl + +RUN dpkg --add-architecture arm64 +RUN apt-get update +RUN apt-get install -y libasound2-dev:arm64 libzmq3-dev libnftnl-dev libmnl-dev libmnl0:arm64 libnftnl7:arm64 libmnl0:amd64 libnftnl0:arm64 +RUN apt-get clean diff --git a/README.md b/README.md new file mode 100644 index 0000000..c1c2cc6 --- /dev/null +++ b/README.md @@ -0,0 +1,86 @@ +# ipblc + +[![Build Status](https://drone.paulbsd.com/api/badges/paulbsd/ipblc/status.svg)](https://drone.paulbsd.com/paulbsd/ipblc) + +## Summary + +ipblc is a tool that search and send attacking ip addresses to ipbl +It's notification features are based on zeromq + +## Howto + +### Build + +- Dev: + +```bash +cargo build +``` + +- Release (with compiler optimizations) + +```bash +cargo build --release +``` + +### Usage + +``` +USAGE: + ipblc [OPTIONS] + +OPTIONS: + -d Enable debugging + -h, --help Print help information + -s, --server Sets a ipbl server [default: https://ipbl.paulbsd.com] + -V, --version Print version informatio +``` + +### TODO + +- ✅ Config centralization (Main config in ipbl) +- ✅ Handles date in log +- ✅ fine grain file opening +- ✅ Handle zeromq data transfer +- ✅ Code optimizations (WIP) +- ✅ Error handing when fetching config + +### Date formats + +``` +nginx: 2022-02-09T10:05:02+01:00 +ssh: 2022-02-09T09:29:15.797469+01:00 +openvpn: 2022-02-09 09:58:59 +mail: 2022-02-09T09:59:31.228303+01:00 +``` + +## License + +```text +Copyright (c) 2021, 2022 PaulBSD +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +The views and conclusions contained in the software and documentation are those +of the authors and should not be interpreted as representing official policies, +either expressed or implied, of this project. +``` diff --git a/src/config/mod.rs b/src/config/mod.rs new file mode 100644 index 0000000..2c148f4 --- /dev/null +++ b/src/config/mod.rs @@ -0,0 +1,384 @@ +use crate::ip::*; +use crate::utils::*; + +use chrono::prelude::*; +use chrono::Duration; +use clap::{Arg, ArgMatches, Command}; +use nix::sys::inotify::{AddWatchFlags, InitFlags, Inotify, WatchDescriptor}; +use regex::Regex; +use reqwest::{Client, Error as ReqError, Response}; +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; +use std::hash::{Hash, Hasher}; +use std::path::Path; + +const SERVER: &str = "ipbl.paulbsd.com"; + +#[derive(Debug, Clone)] +pub struct Context { + pub blocklist: HashMap, + pub cfg: Config, + pub client: Client, + pub discovery: Discovery, + pub flags: Flags, + pub hostname: String, + pub instance: Box, + pub sas: HashMap, + pub hashwd: HashMap, +} + +#[derive(Debug, Clone)] +pub struct SetMap { + pub filename: String, + pub fullpath: String, + pub regex: Regex, + pub set: Set, + pub watchedfiles: HashMap, + pub wd: WatchDescriptor, +} + +#[derive(Debug, Clone)] +pub struct Flags { + pub debug: bool, + pub interval: usize, + pub server: String, +} + +impl Context { + pub async fn new() -> Self { + // Get flags + let debug = Context::argparse().is_present("debug"); + let server = Context::argparse() + .value_of("server") + .unwrap_or(format!("https://{}", SERVER).as_str()) + .to_string(); + + // Build context + let mut ctx = Context { + cfg: Config::new(), + flags: Flags { + debug: debug, + server: server, + interval: 60, + }, + hostname: gethostname(true), + discovery: Discovery { + version: "1.0".to_string(), + urls: HashMap::new(), + }, + client: Client::builder() + .user_agent(format!( + "{}/{}@{}", + env!("CARGO_PKG_NAME"), + env!("CARGO_PKG_VERSION"), + gethostname(false) + )) + .build() + .unwrap(), + sas: HashMap::new(), + instance: Box::new(Inotify::init(InitFlags::empty()).unwrap()), + blocklist: HashMap::new(), + hashwd: HashMap::new(), + }; + ctx.discovery = ctx.discovery().await.unwrap(); + print!("Loading config ... "); + match ctx.load().await { + Ok(_) => {} + Err(err) => { + println!("error loading config: {err}"); + std::process::exit(1); + } + } + ctx + } + + pub fn argparse() -> ArgMatches { + Command::new(env!("CARGO_PKG_NAME")) + .version(env!("CARGO_PKG_VERSION")) + .author(env!("CARGO_PKG_AUTHORS")) + .about(env!("CARGO_PKG_DESCRIPTION")) + .arg( + Arg::new("server") + .short('s') + .long("server") + .value_name("server") + .default_value("https://ipbl.paulbsd.com") + .help("Sets a http server") + .takes_value(true), + ) + .arg( + Arg::new("debug") + .short('d') + .takes_value(false) + .help("Enable debugging"), + ) + .get_matches() + } + + pub async fn discovery(&self) -> Result { + let resp: Result = self + .client + .get(format!("{server}/discovery", server = self.flags.server)) + .send() + .await; + let req = match resp { + Ok(re) => re, + Err(err) => return Err(err), + }; + let data: Discovery = match req.json().await { + Ok(res) => res, + Err(err) => return Err(err), + }; + Ok(data) + } + + pub async fn load(&mut self) -> Result<(), Box> { + self.cfg.load(self.to_owned()).await?; + self.create_sas().await?; + Ok(()) + } + + pub async fn get_blocklist(&self) -> Vec { + let mut res: Vec = vec![]; + for (_, v) in self.blocklist.iter() { + res.push(v.clone()); + } + res + } + + pub async fn gc_blocklist(&mut self) -> Vec { + let now: DateTime = Local::now().trunc_subsecs(0); + let delta: Duration = Duration::minutes(self.flags.interval as i64); + let mindate = now - delta; + let mut toremove: Vec = vec![]; + + // nightly, future use + //let drained: HashMap = ctx.blocklist.drain_filter(|k,v| v.parse_date() < mindate) + for (k, v) in self.blocklist.clone().iter() { + if v.parse_date() < mindate { + self.blocklist.remove(&k.to_string()).unwrap(); + toremove.push(v.clone()); + } + } + toremove + } + + pub async fn update_blocklist(&mut self, ip: &IpData) { + if !self.blocklist.contains_key(&ip.ip) { + self.blocklist.insert(ip.ip.clone(), ip.clone()); + } + } + + pub async fn create_sas(&mut self) -> Result<(), Box> { + for set in &self.cfg.sets { + let p = Path::new(set.path.as_str()); + if p.is_dir() { + let res = match self.hashwd.get(&set.path.to_string()) { + Some(wd) => *wd, + None => { + let res = self + .instance + .add_watch(set.path.as_str(), AddWatchFlags::IN_MODIFY) + .unwrap(); + self.hashwd.insert(set.path.to_string(), res); + res + } + }; + let fullpath: String = match set.filename.as_str() { + "" => set.path.clone(), + _ => { + format!( + "{path}/{filename}", + path = set.path, + filename = set.filename.clone() + ) + } + }; + self.sas.insert( + set.t.clone(), + SetMap { + filename: set.filename.clone(), + fullpath: fullpath, + set: set.clone(), + regex: Regex::new(set.regex.as_str()).unwrap(), + wd: res, + watchedfiles: HashMap::new(), + }, + ); + } + } + Ok(()) + } +} + +#[derive(Debug, Deserialize, Serialize, Clone)] +pub struct Config { + pub sets: Vec, + #[serde(skip_serializing)] + pub trustnets: Vec, + pub zmq: HashMap, +} + +impl Config { + pub fn new() -> Self { + Self { + sets: vec![ + Set { + t: "smtp".to_string(), + filename: "mail.log".to_string(), + regex: "(SASL LOGIN authentication failed)".to_string(), + path: "/var/log".to_string(), + }, + Set { + t: "ssh".to_string(), + filename: "auth.log".to_string(), + regex: "(Invalid user|BREAK|not allowed because|no matching key exchange method found)".to_string(), + path: "/var/log".to_string(), + }, + Set { + t: "http".to_string(), + filename: "".to_string(), + regex: "(anonymousfox.co)".to_string(), + path: "/var/log/nginx".to_string(), + } + ,Set { + t: "openvpn".to_string(), + filename: "status".to_string(), + regex: "(UNDEF)".to_string(), + path: "/var/run/openvpn".to_string(), + }, + ], + trustnets: vec![ + "127.0.0.0/8".to_string(), + "10.0.0.0/8".to_string(), + "172.16.0.0/12".to_string(), + "192.168.0.0/16".to_string(), + ], + zmq: HashMap::from([("pubsub".to_string(),ZMQ{ + t: "pubsub".to_string(), + hostname: SERVER.to_string(), + port: 9999, + subscription: "ipbl".to_string(), + }),("reqrep".to_string(),ZMQ { + t: "reqrep".to_string(), + hostname: SERVER.to_string(), + port: 9998, + subscription: String::new(), + })]) + } + } + + pub async fn load(&mut self, ctx: Context) -> Result<(), ReqError> { + self.get_sets(&ctx).await?; + self.get_trustnets(&ctx).await?; + self.get_zmq_config(&ctx).await?; + Ok(()) + } + + async fn get_trustnets(&mut self, ctx: &Context) -> Result<(), ReqError> { + let resp: Result = ctx + .client + .get(format!( + "{server}/config/trustlist", + server = ctx.flags.server + )) + .send() + .await; + let req = match resp { + Ok(re) => re, + Err(err) => return Err(err), + }; + let data: Vec = match req.json::>().await { + Ok(res) => res, + Err(err) => return Err(err), + }; + self.trustnets = data; + Ok(()) + } + + async fn get_sets(&mut self, ctx: &Context) -> Result<(), ReqError> { + let resp: Result = ctx + .client + .get(format!("{server}/config/sets", server = ctx.flags.server)) + .send() + .await; + let req = match resp { + Ok(re) => re, + Err(err) => return Err(err), + }; + let data: Vec = match req.json::>().await { + Ok(res) => res, + Err(err) => return Err(err), + }; + self.sets = data; + Ok(()) + } + + async fn get_zmq_config(&mut self, ctx: &Context) -> Result<(), ReqError> { + let resp: Result = ctx + .client + .get(format!("{server}/config/zmq", server = ctx.flags.server)) + .send() + .await; + let req = match resp { + Ok(re) => re, + Err(err) => return Err(err), + }; + let data: HashMap = match req.json::>().await { + Ok(res) => { + let mut out: HashMap = HashMap::new(); + res.into_iter().map(|x| x).for_each(|x| { + out.insert(x.t.to_string(), x); + }); + out + } + Err(err) => return Err(err), + }; + self.zmq = data; + Ok(()) + } +} + +#[derive(Debug, Deserialize, Serialize, Clone)] +pub struct Set { + #[serde(rename = "type")] + pub t: String, + pub filename: String, + pub regex: String, + pub path: String, +} + +#[derive(Debug, Deserialize, Serialize, Clone)] +pub struct ZMQ { + #[serde(rename = "type")] + pub t: String, + pub hostname: String, + pub port: i64, + pub subscription: String, +} + +#[derive(Debug, Deserialize, Serialize, Clone)] +pub struct Discovery { + pub version: String, + pub urls: HashMap, +} + +#[derive(Debug, Deserialize, Serialize, Clone)] +pub struct URL { + pub key: String, + pub path: String, +} + +impl PartialEq for Set { + fn eq(&self, other: &Self) -> bool { + self.t == other.t + } +} + +impl Eq for Set {} + +impl Hash for Set { + fn hash(&self, state: &mut H) { + self.t.hash(state); + } +} diff --git a/src/db.rs b/src/db.rs new file mode 100644 index 0000000..9a79785 --- /dev/null +++ b/src/db.rs @@ -0,0 +1,47 @@ +use crate::config::*; +use crate::ip::*; + +use futures::executor::block_on; +use rbatis; +use rbatis::crud::{Skip, CRUD}; +use rbatis::rbatis::Rbatis; +use rbatis::DateTimeUtc; +use std::process::exit; + +const DBTYPE: &'static str = "postgres"; + +pub async fn init(rb: &Rbatis, cfg: &Config) { + let connection: String = format!( + "{t}://{username}:{password}@{hostname}:{port}/{name}", + t = DBTYPE, + username = cfg.dbusername, + password = cfg.dbpassword, + hostname = cfg.dbhostname, + port = cfg.dbport, + name = cfg.dbname, + ) + .to_string(); + let link = rb.link(connection.as_str()).await; + match link { + Ok(_) => (), + Err(err) => { + println!("Please check connection parameters: {err}"); + exit(1); + } + } +} + +pub fn push_ip(ip: String, src: &String, rb: &Rbatis) { + let i = IP { + id: Some(0), + ip: Some(ip), + rdns: None, + src: Some(src.to_string()), + created: Some(DateTimeUtc::now()), + updated: Some(DateTimeUtc::now()), + }; + match block_on(rb.save(&i, &[Skip::Column("id")])) { + Ok(r) => println!("{r:?}"), + Err(err) => println!("{error}", error = err.to_string()), + }; +} diff --git a/src/firewall/mod.rs b/src/firewall/mod.rs new file mode 100644 index 0000000..8bf42d7 --- /dev/null +++ b/src/firewall/mod.rs @@ -0,0 +1,95 @@ +use crate::ip::*; + +use nftnl::{nft_expr, Batch, Chain, FinalizedBatch, ProtoFamily, Rule, Table}; +use std::{ffi::CString, io::*, net::Ipv4Addr}; + +pub fn init(tablename: &String) -> (Batch, Table) { + let mut batch = Batch::new(); + + let table = Table::new( + &CString::new(tablename.as_str()).unwrap(), + ProtoFamily::Ipv4, + ); + + batch.add(&table, nftnl::MsgType::Add); + batch.add(&table, nftnl::MsgType::Del); + + batch.add(&table, nftnl::MsgType::Add); + (batch, table) +} + +pub fn block( + tablename: &String, + ips_add: &Vec, + ret: &mut Vec, +) -> std::result::Result<(), Error> { + // convert chain + let ips_add = convert(ips_add); + let (mut batch, table) = init(tablename); + + // build chain + let mut chain = Chain::new(&CString::new(tablename.as_str()).unwrap(), &table); + chain.set_hook(nftnl::Hook::In, 1); + chain.set_policy(nftnl::Policy::Accept); + + // add chain + batch.add(&chain, nftnl::MsgType::Add); + + // build and add rules + for ip in ips_add.clone() { + let mut rule = Rule::new(&chain); + rule.add_expr(&nft_expr!(payload ipv4 saddr)); + rule.add_expr(&nft_expr!(cmp == ip)); + rule.add_expr(&nft_expr!(ct state)); + rule.add_expr(&nft_expr!(bitwise mask 10u32, xor 0u32)); + rule.add_expr(&nft_expr!(cmp != 0u32)); + rule.add_expr(&nft_expr!(counter)); + rule.add_expr(&nft_expr!(verdict drop)); + batch.add(&rule, nftnl::MsgType::Add); + } + + // validate and send batch + let finalized_batch = batch.finalize(); + send_and_process(&finalized_batch)?; + ret.push(format!( + "nftables: {length} ip in memory", + length = ips_add.len() + )); + Ok(()) +} + +fn send_and_process(batch: &FinalizedBatch) -> std::result::Result<(), Error> { + let seq: u32 = 2; + let socket = mnl::Socket::new(mnl::Bus::Netfilter)?; + socket.send_all(batch)?; + let mut buffer = vec![0; nftnl::nft_nlmsg_maxsize() as usize]; + while let Some(message) = socket_recv(&socket, &mut buffer[..])? { + match mnl::cb_run(message, seq, socket.portid())? { + mnl::CbResult::Stop => { + break; + } + mnl::CbResult::Ok => (), + } + } + Ok(()) +} + +fn socket_recv<'a>( + socket: &mnl::Socket, + buf: &'a mut [u8], +) -> std::result::Result, Error> { + let ret = socket.recv(buf)?; + if ret > 0 { + Ok(Some(&buf[..ret])) + } else { + Ok(None) + } +} + +fn convert(input: &Vec) -> Vec { + let mut output: Vec = vec![]; + for val in input { + output.push(val.ip.parse::().unwrap()); + } + output +} diff --git a/src/ip.rs b/src/ip.rs new file mode 100644 index 0000000..20914f3 --- /dev/null +++ b/src/ip.rs @@ -0,0 +1,252 @@ +use crate::config::Context; +use crate::utils::*; + +use chrono::prelude::*; +use ipnet::IpNet; +use lazy_static::lazy_static; +use regex::Regex; +use reqwest::Error as ReqError; +use serde::{Deserialize, Serialize}; +use std::cmp::Ordering; +use std::fmt::{Display, Formatter}; +use std::io::{BufRead, BufReader, Read}; +use std::net::IpAddr; + +lazy_static! { + static ref R_IPV4: Regex = Regex::new(include_str!("regexps/ipv4.txt")).unwrap(); + static ref R_IPV6: Regex = Regex::new(include_str!("regexps/ipv6.txt")).unwrap(); + static ref R_DATE: Regex = Regex::new(include_str!("regexps/date.txt")).unwrap(); +} + +#[derive(Clone, Debug, Serialize, Deserialize, Eq)] +pub struct IpData { + pub ip: String, + pub src: String, + pub date: String, + pub hostname: String, +} + +impl IpData { + pub fn parse_date(&self) -> DateTime { + DateTime::parse_from_rfc3339(self.date.as_str()).unwrap() + } +} + +impl PartialEq for IpData { + fn eq(&self, other: &IpData) -> bool { + self.ip.as_bytes() == other.ip.as_bytes() && self.src == other.src + } + fn ne(&self, other: &IpData) -> bool { + !self.eq(other) + } +} + +impl Ord for IpData { + fn cmp(&self, other: &IpData) -> Ordering { + self.ip.as_bytes().cmp(&other.ip.as_bytes()) + } +} + +impl PartialOrd for IpData { + fn partial_cmp(&self, other: &IpData) -> Option { + Some(self.cmp(&other)) + } +} + +impl Display for IpData { + fn fmt(&self, f: &mut Formatter) -> std::fmt::Result { + write!( + f, + "ip: {ip}, src: {src}, date: {date}, hostname: {hostname}", + ip = self.ip, + src = self.src, + date = self.date, + hostname = self.hostname, + ) + } +} + +pub async fn push_ip(ctx: &Context, ip: &IpData, ret: &mut Vec) -> Result<(), ReqError> { + let result: String; + let mut data: Vec = vec![]; + + data.push(IpData { + ip: ip.ip.to_string(), + src: ip.src.to_string(), + date: ip.date.to_string(), + hostname: ip.hostname.to_string(), + }); + + let resp = ctx + .client + .post(format!("{server}/ips", server = ctx.flags.server)) + .json(&data) + .send() + .await?; + ret.push(format!("status: {status}", status = resp.status())); + + let res = resp.text().await.unwrap(); + + if res.trim().len() > 0 { + result = res.trim().to_string(); + } else { + result = "".to_string(); + } + + ret.push(format!("response: {result}")); + Ok(()) +} + +pub async fn _push_ip_bulk( + ctx: &Context, + ips: &Vec, + ret: &mut Vec, +) -> Result<(), ReqError> { + let result: String; + let mut data: Vec = vec![]; + + for ip in ips { + data.push(IpData { + ip: ip.ip.to_string(), + src: ip.src.to_string(), + date: ip.date.to_string(), + hostname: ip.hostname.to_string(), + }) + } + + let resp = ctx + .client + .post(format!("{server}/ips", server = ctx.flags.server)) + .json(&data) + .send() + .await?; + ret.push(format!("status: {status}", status = resp.status())); + + let res = resp.text().await.unwrap(); + + if res.trim().len() > 0 { + result = res.trim().to_string(); + } else { + result = "".to_string(); + } + + ret.push(format!("response: {result}")); + Ok(()) +} + +pub fn filter( + lines: Box, + list: &mut Vec, + trustnets: &Vec, + regex: &Regex, + src: &String, + lastprocess: &DateTime, +) -> isize { + let mut ips = 0; + let hostname = gethostname(true); + for line in BufReader::new(lines).lines() { + if let Ok(l) = line { + if regex.is_match(l.as_str()) { + let s_ipaddr: String; + + match R_IPV4.captures(l.as_str()) { + Some(sv4) => { + s_ipaddr = sv4.get(0).unwrap().as_str().to_string(); + } + None => { + continue; + /*match R_IPV6.captures(l.as_str()) { + Some(sv6) => { + s_ipaddr = sv6.get(0).unwrap().as_str().to_string(); + } + None => { + continue; + } + };*/ + } + }; + + let s_date: DateTime; + match R_DATE.captures(l.as_str()) { + Some(sdt) => { + s_date = parse_date(sdt); + if &s_date < lastprocess { + continue; + } + } + None => { + s_date = Local::now(); + } + }; + + let ipaddr: IpAddr = match s_ipaddr.parse() { + Ok(ip) => ip, + Err(err) => { + println!("unparseable IP: {err} {s_ipaddr}"); + continue; + } + }; + + if !is_trusted(&ipaddr, &trustnets) { + list.push(IpData { + ip: s_ipaddr, + src: src.to_owned(), + date: s_date.to_rfc3339().to_owned(), + hostname: hostname.to_owned(), + }); + ips += 1; + }; + } + } + } + ips +} + +fn parse_date(input: regex::Captures) -> DateTime { + let mut ymd: Vec = vec![]; + let mut hms: Vec = vec![]; + + let (daterange, hourrange) = (2..5, 5..8); + + for i in daterange { + ymd.push(input.get(i).unwrap().as_str().parse::().unwrap()); + } + for i in hourrange { + hms.push(input.get(i).unwrap().as_str().parse::().unwrap()); + } + + let date = Local + .ymd(ymd[0] as i32, ymd[1] as u32, ymd[2] as u32) + .and_hms(hms[0] as u32, hms[1] as u32, hms[2] as u32); + date +} + +fn is_trusted(ip: &IpAddr, trustnets: &Vec) -> bool { + for net in trustnets { + if net.contains(ip) { + return true; + } + } + false +} + +pub async fn _get_last(ctx: &Context) -> Result, ReqError> { + let resp = ctx + .client + .get(format!("{server}/ips/last", server = ctx.flags.server)) + .query(&[("interval", "3 hours")]) + .send() + .await; + + let req = match resp { + Ok(re) => re, + Err(err) => return Err(err), + }; + + let data: Vec = match req.json::>().await { + Ok(res) => res, + Err(err) => return Err(err), + }; + + Ok(data) +} diff --git a/src/ipblc/full.rs b/src/ipblc/full.rs new file mode 100644 index 0000000..94e6068 --- /dev/null +++ b/src/ipblc/full.rs @@ -0,0 +1,73 @@ +use crate::api; +use crate::config::*; +use crate::ip; +use crate::utils::*; + +use chrono::prelude::*; +use regex::Regex; +use std::sync::Arc; + +pub async fn process(ctx: Arc) { + let arcctx = Arc::clone(&ctx); + let hostname = arcctx.hostname.as_str().to_string(); + + loop { + let mut ret = String::new(); + ret.push_str(format!("host: {hostname}, ", hostname = hostname).as_str()); + let list = process_lines(arcctx.clone()).await; + + loop { + match api::push_ip_bulk(&list, arcctx.flags.server.clone(), &mut ret).await { + Ok(_) => { + break println!("{ret}"); + } + Err(err) => { + println!("{err}"); + sleep(1); + } + }; + } + sleep(ctx.flags.interval as u64); + println!("{ret}"); + } +} + +async fn process_lines(ctx: Arc) -> Vec { + let mut ret = String::new(); + let mut list: Vec = vec![]; + let trustnets = build_trustnets(ctx.cfg.trustnets.clone()); + let lastprocess = Local::now(); + + for folder in &ctx.cfg.folders { + for set in &folder.sets { + let regex = Regex::new(set.regex.as_str()).unwrap(); + let filenames = get_filenames(&set.filename); + let mut numip = 0; + + for filename in filenames { + numip = numip + + super::handle_file( + &mut list, + &filename, + set, + ®ex, + &lastprocess, + &trustnets, + ) + .await; + } + ret.push_str(format!("{t}: {numip}, ", t = set.t, numip = numip).as_str()); + } + } + + let oldlen = list.len(); + let newlen = dedup(&mut list); + ret.push_str( + format!( + "dedup ratio: {percent:.2}%, ", + percent = ((oldlen - newlen) as f64 / oldlen as f64) * 100. + ) + .as_str(), + ); + list +} diff --git a/src/ipblc/inc.rs b/src/ipblc/inc.rs new file mode 100644 index 0000000..e76a36b --- /dev/null +++ b/src/ipblc/inc.rs @@ -0,0 +1,180 @@ +use super::*; + +use chrono::prelude::*; +use std::collections::HashMap; +use std::sync::Arc; +use tokio::sync::mpsc::{channel, Receiver, Sender}; +use tokio::sync::Mutex; + +const BL_CHAN_SIZE: usize = 32; +const ZMQ_CHAN_SIZE: usize = 64; + +pub async fn process(ctx: &Arc>) { + println!( + "Launching {} version {}", + env!("CARGO_PKG_NAME"), + env!("CARGO_PKG_VERSION") + ); + + let (ipdatatx, mut ipdatarx): (Sender, Receiver) = channel(ZMQ_CHAN_SIZE); + + // initialize the firewall table + //firewall::init(&env!("CARGO_PKG_NAME").to_string()); + + // initialize zeromq sockets + let reqsocket; + let subsocket; + { + let ctxarc = Arc::clone(&ctx); + let zmqctx = ctxarc.lock().await; + reqsocket = zconnect(&zmqctx.cfg.zmq.get("reqrep").unwrap(), zmq::REQ) + .await + .unwrap(); + subsocket = zconnect(&zmqctx.cfg.zmq.get("pubsub").unwrap(), zmq::SUB) + .await + .unwrap(); + } + + listenpubsub(&ctx, ipdatatx.clone(), subsocket).await; + + let mut blrx = watchfiles(&ctx).await; + + let ctxarc = Arc::clone(&ctx); + tokio::spawn(async move { + compare_files_changes(&ctxarc, &mut blrx, &ipdatatx).await; + }); + + loop { + let mut ret: Vec = Vec::new(); + + // wait for logs parse and zmq channel receive + let ip = ipdatarx.recv().await.unwrap(); + + // lock the context mutex + let ctxarc = Arc::clone(&ctx); + let mut ctx = ctxarc.lock().await; + + // refresh context blocklist + ctx.update_blocklist(&ip).await; + ctx.gc_blocklist().await; + + // send ip list to ws and zmq sockets + if ip.hostname == ctx.hostname { + send_to_ipbl_ws(&ctx, &ip, &mut ret).await; + send_to_ipbl_zmq(&reqsocket, &ip).await; + } + + // apply firewall blocking + firewall::block( + &env!("CARGO_PKG_NAME").to_string(), + &ctx.get_blocklist().await, + &mut ret, + ) + .unwrap(); + + // log lines + println!("{ret}", ret = ret.join(", ")); + + // reload configuration from the server + match ctx.load().await { + Ok(_) => {} + Err(err) => { + println!("error loading config: {err}"); + } + } + } +} + +async fn watchfiles(ctx: &Arc>) -> Receiver { + let (bltx, blrx): (Sender, Receiver) = channel(BL_CHAN_SIZE); + let ctx = Arc::clone(ctx); + tokio::spawn(async move { + loop { + let events: Vec; + { + let ctx = ctx.lock().await; + events = ctx.instance.read_events().unwrap(); + } + + for event in events { + let date: DateTime = Local::now().trunc_subsecs(0); + bltx.send(FileEvent { + ie: event, + date: date, + }) + .await + .unwrap(); + } + } + }); + blrx +} + +async fn get_last_file_size(watchedfiles: &mut HashMap, path: &str) -> u64 { + let currentlen = match std::fs::metadata(&path.to_string()) { + Ok(u) => u.len().clone(), + Err(_) => 0u64, + }; + let lastlen = match watchedfiles.insert(path.to_string(), currentlen) { + Some(u) => u, + None => 0, + }; + lastlen +} + +async fn compare_files_changes( + ctx: &Arc>, + inotifyrx: &mut Receiver, + ipdatatx: &Sender, +) { + let mut trustnets; + loop { + let modifiedfiles = inotifyrx.recv().await.unwrap(); + let mut list: Vec = vec![]; + + let mut ctx = ctx.lock().await; + trustnets = build_trustnets(&ctx.cfg.trustnets); + + match modifiedfiles.ie.name { + Some(name) => { + let inotify_filename = name.to_str().unwrap(); + for sak in &mut ctx.clone().sas.keys() { + let sa = &mut ctx.sas.get_mut(sak).unwrap(); + if modifiedfiles.ie.wd == sa.wd { + let handle_filename: String; + if sa.filename.as_str() == "" { + handle_filename = format!("{}/{}", &sa.fullpath, inotify_filename); + } else if inotify_filename.starts_with(sa.filename.as_str()) { + handle_filename = sa.fullpath.to_owned(); + } else { + continue; + } + + let filesize = + get_last_file_size(&mut sa.watchedfiles, &handle_filename).await; + + match read_lines(&handle_filename, filesize) { + Some(lines) => { + filter( + lines, + &mut list, + &trustnets, + &sa.regex, + &sa.set.t, + &modifiedfiles.date, + ); + } + None => {} + }; + break; + } + } + drop(ctx); + for ip in list { + ipdatatx.send(ip).await.unwrap(); + } + } + None => {} + } + } +} diff --git a/src/ipblc/mod.rs b/src/ipblc/mod.rs new file mode 100644 index 0000000..e5883b5 --- /dev/null +++ b/src/ipblc/mod.rs @@ -0,0 +1,79 @@ +pub mod inc; + +use crate::config::*; +use crate::firewall; +use crate::ip::*; +use crate::utils::*; +use crate::zmqcom::*; + +use chrono::prelude::{DateTime, Local}; +use nix::sys::inotify::InotifyEvent; +use std::sync::Arc; +use tokio::sync::mpsc::Sender; +use tokio::sync::Mutex; + +pub struct FileEvent { + pub ie: InotifyEvent, + pub date: DateTime, +} + +impl std::fmt::Debug for FileEvent { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{ie:?}", ie = self.ie) + } +} + +async fn send_to_ipbl_zmq(socket: &zmq::Socket, ip: &IpData) { + let msg = format!("{value}", value = serde_json::to_string(&ip).unwrap()); + socket.send(&msg, 0).unwrap(); + socket.recv_string(0).unwrap().unwrap(); +} + +async fn send_to_ipbl_ws(ctx: &Context, ip: &IpData, ret: &mut Vec) { + ret.push(format!("host: {hostname}", hostname = ctx.hostname)); + loop { + match push_ip(&ctx, &ip, ret).await { + Ok(_) => { + break; + } + Err(err) => { + println!("{err}"); + sleep(1); + } + }; + } +} + +async fn listenpubsub(ctx: &Arc>, txpubsub: Sender, socket: zmq::Socket) { + let ctx = ctx.lock().await; + let prefix = format!( + "{subscription} ", + subscription = ctx.cfg.zmq.get("pubsub").unwrap().subscription + ); + socket + .set_subscribe(ctx.cfg.zmq.get("pubsub").unwrap().subscription.as_bytes()) + .expect("failed setting subscription"); + drop(ctx); + + tokio::spawn(async move { + loop { + let msgs: Option = match socket.recv_string(0) { + Ok(s) => match s { + Ok(ss) => Some(ss), + Err(_) => None, + }, + Err(_) => None, + }; + match msgs { + Some(ss) => { + let msg = ss.strip_prefix(prefix.as_str()).unwrap(); + let tosend: IpData = serde_json::from_str(msg).unwrap(); + if tosend.hostname != gethostname(true) { + txpubsub.send(tosend).await.unwrap(); + } + } + None => {} + }; + } + }); +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..d3ae296 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,17 @@ +mod config; +mod firewall; +mod ip; +mod ipblc; +mod utils; +mod zmqcom; + +use config::Context; +use std::sync::Arc; +use tokio::sync::Mutex; + +#[tokio::main] +pub async fn main() { + // Create a new context + let ctx = Arc::new(Mutex::new(Context::new().await)); + ipblc::inc::process(&ctx).await; +} diff --git a/src/regexps/date.txt b/src/regexps/date.txt new file mode 100644 index 0000000..caead4e --- /dev/null +++ b/src/regexps/date.txt @@ -0,0 +1 @@ +((\d{1,4})[-/](\d{2})[-/](\d{2})[T ](\d{2}):(\d{2}):(\d{2})((\.\d{6})?[+-](\d{2}):(\d{2}))?) \ No newline at end of file diff --git a/src/regexps/ipv4.txt b/src/regexps/ipv4.txt new file mode 100644 index 0000000..2a4c436 --- /dev/null +++ b/src/regexps/ipv4.txt @@ -0,0 +1 @@ +(?:[0-9]{1,3}\.){3}[0-9]{1,3} \ No newline at end of file diff --git a/src/regexps/ipv6.txt b/src/regexps/ipv6.txt new file mode 100644 index 0000000..d69593c --- /dev/null +++ b/src/regexps/ipv6.txt @@ -0,0 +1 @@ +((^\s*((([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]))\s*$)|(^\s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(%.+)?\s*$)) \ No newline at end of file diff --git a/src/utils.rs b/src/utils.rs new file mode 100644 index 0000000..2b729a1 --- /dev/null +++ b/src/utils.rs @@ -0,0 +1,82 @@ +use ipnet::IpNet; +use lazy_static::lazy_static; +use nix::unistd; +use regex::Regex; +use std::boxed::Box; +use std::fs::File; +use std::io::*; +use std::path::Path; +use std::time::Duration; + +lazy_static! { + static ref R_FILE_GZIP: Regex = Regex::new(r".*\.gz.*").unwrap(); +} + +pub fn read_lines(filename: &String, offset: u64) -> Option> { + let mut file = match File::open(filename) { + Ok(f) => f, + Err(err) => { + println!("{err}"); + return None; + } + }; + file.seek(SeekFrom::Start(offset)).unwrap(); + let lines: Box = Box::new(BufReader::new(file)); + Some(lines) +} + +pub fn _dedup(list: &mut Vec) -> usize { + // Begin with sorting entries + list.sort(); + // Then deduplicate + list.dedup(); + // Return the length + list.len() +} + +pub fn build_trustnets(cfgtrustnets: &Vec) -> Vec { + let mut trustnets: Vec = vec![]; + for trustnet in cfgtrustnets { + match trustnet.parse() { + Ok(net) => trustnets.push(net), + Err(err) => { + println!("error parsing {trustnet}, error: {err}"); + } + }; + } + trustnets +} + +pub fn sleep(seconds: u64) { + std::thread::sleep(Duration::from_secs(seconds)); +} + +pub fn gethostname(show_fqdn: bool) -> String { + let mut buf = [0u8; 64]; + let hostname_cstr = unistd::gethostname(&mut buf).expect("Failed getting hostname"); + let fqdn = hostname_cstr + .to_str() + .expect("Hostname wasn't valid UTF-8") + .to_string(); + let hostname: Vec<&str> = fqdn.split(".").collect(); + if show_fqdn { + return fqdn; + } + hostname[0].to_string() +} + +pub fn _search_subfolders(path: &Path) -> Vec { + let dirs = std::fs::read_dir(path).unwrap(); + let mut folders: Vec = vec![]; + for dir in dirs { + let dirpath = dir.unwrap().path(); + let path = Path::new(dirpath.as_path()); + if path.is_dir() { + folders.push(dirpath.to_str().unwrap().to_string()); + for f in _search_subfolders(path) { + folders.push(f); + } + } + } + folders +} diff --git a/src/zmqcom.rs b/src/zmqcom.rs new file mode 100644 index 0000000..62061c3 --- /dev/null +++ b/src/zmqcom.rs @@ -0,0 +1,15 @@ +use crate::config::*; + +use zmq; + +const ZMQPROTO: &str = "tcp"; + +pub async fn zconnect(zmqcfg: &ZMQ, zmqtype: zmq::SocketType) -> Result { + let zctx = zmq::Context::new(); + let zmqhost = &zmqcfg.hostname; + let zmqport = zmqcfg.port; + let socket = zctx.socket(zmqtype).unwrap(); + let connectstring = format!("{ZMQPROTO}://{zmqhost}:{zmqport}"); + socket.connect(&connectstring.as_str())?; + Ok(socket) +} diff --git a/tests/test.py b/tests/test.py new file mode 100755 index 0000000..8744a73 --- /dev/null +++ b/tests/test.py @@ -0,0 +1,12 @@ +#!/usr/bin/python3 + +import zmq + +context = zmq.Context() +socket = context.socket(zmq.SUB) +socket.connect("tcp://sys01:9999") +socket.setsockopt_string(zmq.SUBSCRIBE, "ipbl") + +while True: + msg = socket.recv_string() + print(msg) diff --git a/tests/testfw.rs b/tests/testfw.rs new file mode 100644 index 0000000..9ecabab --- /dev/null +++ b/tests/testfw.rs @@ -0,0 +1,50 @@ +use ipnet::Ipv4Net; +use nftnl::{nft_set, set::Set, Batch, FinalizedBatch, ProtoFamily, Table}; +use std::{ffi::CString, io::*, net::Ipv4Addr}; + +fn main() -> std::result::Result<(), Error> { + let mut batch = Batch::new(); + let table = Table::new(&CString::new("aa").unwrap(), ProtoFamily::Inet); + let mut set: Set = nft_set!( + &CString::new("blacklist").unwrap(), + 1, + &table, + ProtoFamily::Inet + ); + let toadd = "9.9.9.8".parse::().unwrap(); + set.add(&toadd); + println!("2"); + batch.add(&set, nftnl::MsgType::Add); + let finalized_batch = batch.finalize(); + send_and_process(&finalized_batch)?; + Ok(()) +} + +fn send_and_process(batch: &FinalizedBatch) -> std::result::Result<(), Error> { + let socket = mnl::Socket::new(mnl::Bus::Netfilter)?; + socket.send_all(batch)?; + let portid = socket.portid(); + let mut buffer = vec![0; nftnl::nft_nlmsg_maxsize() as usize]; + let very_unclear_what_this_is_for = 2; + while let Some(message) = socket_recv(&socket, &mut buffer[..])? { + match mnl::cb_run(message, very_unclear_what_this_is_for, portid)? { + mnl::CbResult::Stop => { + break; + } + mnl::CbResult::Ok => (), + } + } + Ok(()) +} + +fn socket_recv<'a>( + socket: &mnl::Socket, + buf: &'a mut [u8], +) -> std::result::Result, Error> { + let ret = socket.recv(buf)?; + if ret > 0 { + Ok(Some(&buf[..ret])) + } else { + Ok(None) + } +}