Files
watchman/src/watchman.rs
2025-09-17 13:18:56 -04:00

203 lines
7.3 KiB
Rust

// "Son of man, I have made thee a watchman unto the house of Israel; therefore hear the word at my mouth, and give them warning from me." — Ezekiel 3:17 (KJV)
// "I will take my stand at my watchpost and station myself on the tower." — Habakkuk 2:1 (ESV)
use crate::monitors::devices::USBMon;
use crate::monitors::filechanges::FileChanges;
// use crate::monitors::network::NETMon;
use crate::monitors::ssh_burn_file::SSHBurnMon;
use crate::monitors::clients::NewClientMon;
use config::Config;
use std::path::Path;
use std::process::exit;
use std::sync::{Arc, Mutex};
use std::time::{Duration, SystemTime};
use crate::arguments::Arguments;
use crate::config::setup_service;
use crate::monitors::network::NETMon;
use crate::util::{get_cache_dir, get_config_dir, write_default_blacklist, write_default_config};
pub trait EventMonitor {
async fn check(&mut self);
}
#[derive(Debug)]
pub enum Monitors {
USBMon(USBMon),
NetMon(NETMon),
SSHBurnMon(SSHBurnMon),
FileChanges(FileChanges),
NewClients(NewClientMon),
}
pub struct AppState {
pub mon_usb: bool,
pub detection_triggered: bool,
pub monitors: Arc<Mutex<Vec<Monitors>>>,
pub settings_map: Config,
}
impl AppState {
pub fn new() -> Self {
AppState {
mon_usb: true,
detection_triggered: false,
monitors: Arc::new(Mutex::new(vec![])),
settings_map: Config::default(),
}
}
pub fn config(&mut self, args: Arguments) {
// check for config file if it doesn't exist write default config
log::info!("Configuring..");
let cache_dir = get_cache_dir();
let config_dir = get_config_dir();
let mut settings_file_path = format!("{}config/settings.toml", config_dir);
let mut webhook = String::new();
if let Some(wh) = args.webhook_url {
webhook = wh
}
if let Some(service_command) = args.service {
match service_command.as_str() {
"install" => {
log::info!("Installing..");
setup_service();
if !Path::new(settings_file_path.as_str()).exists() {
log::warn!("Settings.toml does not exist; writing default and exiting after service install");
crate::util::write_default_config(settings_file_path.clone(), webhook.clone());
exit(0);
}
}
_ => {}
}
}
if let Some(config_path) = args.config_path {
settings_file_path = config_path.to_string();
}
if !Path::new(settings_file_path.as_str()).exists() {
log::warn!("Settings.toml does not exist; writing default");
write_default_config(settings_file_path.clone(), webhook);
}
let blacklist_file_path = format!("{}config/file_mon_blacklist", config_dir);
if !Path::new(blacklist_file_path.as_str()).exists() {
log::warn!("file_mon_blacklist does not exist; creating default");
write_default_blacklist(blacklist_file_path.clone());
}
let config = Config::builder();
let settings = match config
.add_source(config::File::with_name(settings_file_path.as_str()))
.build()
{
Ok(s) => s,
Err(e) => {
log::error!("Configuration error: unable to parse settings: {}", e);
exit(2);
}
};
// initialize monitoring modules
let mut monitors: Vec<Monitors> = vec![];
if settings
.get::<String>("usb_mon_enabled")
.unwrap_or("false".to_string())
.eq_ignore_ascii_case("true")
{
monitors.push(Monitors::USBMon(USBMon::new(settings.clone())));
}
// if settings
// .get::<String>("net_mon_enabled")
// .unwrap()
// .eq_ignore_ascii_case("true")
// {
// monitors.push(Monitors::NetMon(NETMon::new(settings.clone())));
// }
if settings
.get::<String>("burn_file_mon_enabled")
.unwrap_or("false".to_string())
.eq_ignore_ascii_case("true")
{
monitors.push(Monitors::SSHBurnMon(SSHBurnMon::new(settings.clone())));
}
if settings
.get::<String>("fs_mon_enabled")
.unwrap_or("false".to_string())
.eq_ignore_ascii_case("true")
{
monitors.push(Monitors::FileChanges(FileChanges::new(
settings.clone(),
blacklist_file_path,
cache_dir,
)));
}
// Enable new client scanning monitor if configured
if settings
.get::<String>("client_scan_enabled")
.unwrap_or("false".to_string())
.eq_ignore_ascii_case("true")
{
monitors.push(Monitors::NewClients(NewClientMon::new(settings.clone())));
}
self.monitors.clone_from(&Arc::new(Mutex::new(monitors)));
self.settings_map.clone_from(&settings)
}
pub async fn run(&mut self) {
// Defaults if not configured or parse fails
let n_fs_check_tick: u64 = self
.settings_map
.get::<String>("fs_tick_delay_seconds").ok()
.and_then(|v| match v.parse::<u64>() { Ok(n) => Some(n), Err(e) => { log::warn!("Invalid fs_tick_delay_seconds '{}': {}. Using default 300.", v, e); None } })
.unwrap_or(300);
let n_tick: u64 = self
.settings_map
.get::<String>("tick_delay_seconds").ok()
.and_then(|v| match v.parse::<u64>() { Ok(n) => Some(n), Err(e) => { log::warn!("Invalid tick_delay_seconds '{}': {}. Using default 5.", v, e); None } })
.unwrap_or(5);
let mut last = SystemTime::now();
loop {
let mut binding = self.monitors.lock();
let bind = binding.as_mut().unwrap();
for i in bind.iter_mut() {
match i {
Monitors::USBMon(e) => {
e.check().await;
}
// Monitors::NetMon(e) => {
// e.check().await;
// }
Monitors::SSHBurnMon(e) => {
e.check().await;
}
Monitors::FileChanges(e) => {
let now = SystemTime::now();
let dur_since = match now.duration_since(last) {
Ok(d) => d,
Err(e) => {
log::warn!("SystemTime went backwards: {}", e);
Duration::new(0, 0)
}
};
if dur_since.as_secs() > n_fs_check_tick {
last = SystemTime::now();
e.check().await;
}
}
Monitors::NewClients(e) => {
e.check().await;
}
Monitors::NetMon(e) => {
e.check().await
}
}
tokio::time::sleep(Duration::new(n_tick, 0)).await;
}
}
}
}