Dirty WIP

This commit is contained in:
Crom (Thibaut CHARLES) 2020-10-20 17:31:25 +02:00
parent 2df8a9351f
commit d41d5bd3cd
Signed by: tcharles
GPG Key ID: 45A3D5F880B9E6D0
11 changed files with 1592 additions and 868 deletions

2031
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -9,6 +9,7 @@ libloading = "0.5.0"
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
serde_yaml = "0.8" serde_yaml = "0.8"
serde_json = "1.0" serde_json = "1.0"
serde_xml = "0.9"
getopts = "0.2" getopts = "0.2"
blurz = "0.4.0" blurz = "0.4.0"
regex = "1" regex = "1"
@ -19,3 +20,4 @@ simplelog = "0.5"
reqwest = "0.9" reqwest = "0.9"
percent-encoding = "1.0" percent-encoding = "1.0"
rand = "0.6" rand = "0.6"
trust-dns-resolver = "0.19"

View File

@ -1,6 +1,5 @@
#![feature(unrestricted_attribute_tokens)] #![feature(register_tool)]
#![feature(custom_attribute)] #![feature(register_attr)]
#![feature(try_from)]
mod filter; mod filter;
mod message; mod message;

83
src/monitors/dns.rs Normal file
View File

@ -0,0 +1,83 @@
pub use crate::message::{Level, Message};
pub use crate::monitor::*;
use serde::Deserialize;
// use std::net::*;
use trust_dns_resolver::config::{ResolverConfig, ResolverOpts};
use trust_dns_resolver::Resolver;
// #[derive(Debug)]
pub struct DNS {
config: DNSConfig,
resolver: Resolver,
}
#[derive(Debug, Deserialize)]
struct DNSConfig {
#[serde(default)]
records: Vec<DNSConfigRecord>,
#[serde(default)]
period: Option<f64>,
}
#[derive(Debug, Deserialize)]
struct DNSConfigRecord {
domain_name: String,
#[serde(default)]
resolver: Option<String>,
#[serde(default)]
entries: Vec<DNSConfigEntry>,
}
#[derive(Debug, Deserialize)]
struct DNSConfigEntry {
record_type: String,
#[serde(default)]
record_value: Option<String>,
#[serde(default)]
resolver: Option<String>,
}
impl Monitor for DNS {
fn new(config: serde_yaml::Value) -> Result<Self, Box<dyn std::error::Error>> {
let config: DNSConfig = serde_yaml::from_value(config)?;
let mut resolver =
Resolver::new(ResolverConfig::default(), ResolverOpts::default()).unwrap();
Ok(DNS {
config: config,
resolver: resolver,
})
}
fn run(&mut self, sender: &mpsc::Sender<Message>) {
for record in &self.config.records {
//let mut response = match &record.record_type {
// "ipv4" => self.resolver.ipv4_lookup(&record.domain_name),
// // "ipv6" => self.resolver.ipv6_lookup(&record.domain_name),
// _ => self.resolver.ipv4_lookup(&record.domain_name),
//};
// let mut response = self.resolver.lookup_ip(&record.domain_name);
// if let Err(e) = response {
// sender
// .send(Message {
// emitter: "dns".to_owned(),
// msg_type: "dns.resolve".to_owned(),
// level: Level::Issue,
// text: format!("Cannot resolve hostname {}", &record.domain_name),
// })
// .unwrap();
// continue;
// }
// let Ok(response) = response;
// while let Some(res) = response.next() {
// println!("res={:?}", res);
// }
}
let delay_ms = match self.config.period {
Some(sec) => (sec * 1000.0) as u64,
None => 2000,
};
std::thread::sleep(std::time::Duration::from_millis(delay_ms));
}
}

View File

@ -22,8 +22,7 @@ struct DummySenderConfig {
impl Monitor for DummySender { impl Monitor for DummySender {
fn new(config: serde_yaml::Value) -> Result<Self, Box<dyn std::error::Error>> { fn new(config: serde_yaml::Value) -> Result<Self, Box<dyn std::error::Error>> {
let config: DummySenderConfig = let config: DummySenderConfig = serde_yaml::from_value(config)?;
serde_yaml::from_value(config).expect("Invalid config for dummy_sender");
Ok(DummySender { Ok(DummySender {
cnt: 0, cnt: 0,

View File

@ -1,5 +1,8 @@
pub mod dhcp_leases; pub mod dhcp_leases;
pub mod dns;
pub mod dummy_sender; pub mod dummy_sender;
pub mod nmap_scanner;
pub mod ping;
pub mod wifi_availability; pub mod wifi_availability;
use crate::monitor::*; use crate::monitor::*;
@ -12,6 +15,9 @@ pub fn factory(
"dummy_sender" => Ok(Box::new(dummy_sender::DummySender::new(config)?)), "dummy_sender" => Ok(Box::new(dummy_sender::DummySender::new(config)?)),
"wifi_availability" => Ok(Box::new(wifi_availability::WifiAvailability::new(config)?)), "wifi_availability" => Ok(Box::new(wifi_availability::WifiAvailability::new(config)?)),
"dhcp_leases" => Ok(Box::new(dhcp_leases::DHCPLeases::new(config)?)), "dhcp_leases" => Ok(Box::new(dhcp_leases::DHCPLeases::new(config)?)),
"nmap_scanner" => Ok(Box::new(nmap_scanner::NmapScanner::new(config)?)),
"ping" => Ok(Box::new(ping::Ping::new(config)?)),
"dns" => Ok(Box::new(dns::DNS::new(config)?)),
_ => panic!("Unknown monitor name: {}", name), _ => panic!("Unknown monitor name: {}", name),
} }
} }

View File

@ -3,17 +3,13 @@ pub use crate::monitor::*;
use serde::Deserialize; use serde::Deserialize;
extern crate regex; extern crate regex;
use regex::Regex; // use regex::Regex;
extern crate chrono; extern crate chrono;
use chrono::Local; // use chrono::Local;
#[derive(Debug)] #[derive(Debug)]
pub struct NmapScanner { pub struct NmapScanner {
config: NmapScannerConfig, config: NmapScannerConfig,
rgx_lease: Regex,
rgx_mac: Regex,
rgx_date_start: Regex,
rgx_date_ends: Regex,
} }
#[derive(Debug, PartialEq, Deserialize)] #[derive(Debug, PartialEq, Deserialize)]
struct NmapScannerConfig { struct NmapScannerConfig {
@ -21,144 +17,62 @@ struct NmapScannerConfig {
args: Vec<String>, args: Vec<String>,
#[serde(default)] #[serde(default)]
ip_range: Option<String>, ip_range: Option<String>,
#[serde(default)]
period: Option<f64>,
} }
impl Monitor for NmapScanner { impl Monitor for NmapScanner {
fn new( fn new(config: serde_yaml::Value) -> Result<Self, Box<dyn std::error::Error>> {
config: serde_yaml::Value, let mut config: NmapScannerConfig = serde_yaml::from_value(config)?;
) -> Result<Self, Box<dyn std::error::Error>> {
let mut config = serde_yaml::from_value(config)?;
if config.ip_range.is_none() { if config.ip_range.is_none() {
// Detect current subnet // Detect current subnet
config.ip_range = Some("192.168.0.1-254"); config.ip_range = Some("192.168.0.1-254".to_owned());
} }
// Regex compilation // Regex compilation
let rgx_lease = Regex::new(r"(?s)lease\s+(\d+(?:\.\d+){3})\s*\{\n?(.*?)\}").unwrap(); // let rgx_lease = Regex::new(r"(?s)lease\s+(\d+(?:\.\d+){3})\s*\{\n?(.*?)\}").unwrap();
let rgx_mac = // let rgx_mac =
Regex::new(r"(?m)^\s*hardware\s+ethernet\s([a-f0-9]{2}(?::[a-f0-9]{2}){5})\s*;") // Regex::new(r"(?m)^\s*hardware\s+ethernet\s([a-f0-9]{2}(?::[a-f0-9]{2}){5})\s*;")
.unwrap(); // .unwrap();
let rgx_date_start = Regex::new(r"(?m)^\s*starts\s+\d+\s+(.*?)\s*;").unwrap(); // let rgx_date_start = Regex::new(r"(?m)^\s*starts\s+\d+\s+(.*?)\s*;").unwrap();
let rgx_date_ends = Regex::new(r"(?m)^\s*ends\s+\d+\s+(.*?)\s*;").unwrap(); // let rgx_date_ends = Regex::new(r"(?m)^\s*ends\s+\d+\s+(.*?)\s*;").unwrap();
Ok(NmapScanner { Ok(NmapScanner { config: config })
config: config,
rgx_lease: rgx_lease,
rgx_mac: rgx_mac,
rgx_date_start: rgx_date_start,
rgx_date_ends: rgx_date_ends,
})
} }
fn run(&mut self, sender: &mpsc::Sender<Message>) { fn run(&mut self, sender: &mpsc::Sender<Message>) {
// ex: nmap -sV -T4 -O -F --version-light 192.168.0.1-254
use std::process::{Command, Stdio};
let res = Command::new("nmap")
.args(&self.config.args)
.arg(&self.config.ip_range.clone().unwrap())
.args(vec!["-oX", "-"])
.stderr(Stdio::inherit())
.output()
.expect("failed to execute process");
nmap -sV -T4 -O -F --version-light 192.168.0.1-254 if res.status.success() {
let output = std::str::from_utf8(&res.stdout).expect("UTF-8 decoding error");
// println!("{}", output);
use serde_xml::value::{Content, Element};
let xml: Element = serde_xml::de::from_str(&output).expect("Bad XML format");
if let Content::Members(nodes) = xml.members {
if let Some(hosts) = nodes.get("host") {
for host_node in hosts {
if let Content::Members(host) = &host_node.members {
let address_nodes = host.get("address").unwrap();
let address = &address_nodes[0].attributes.get("addr").unwrap()[0];
let address_nodes = host.get("address").unwrap();
use std::fs::File; println!("address={:?}", address);
use std::io::prelude::*;
let config_file_path = match self.config.path.len() {
0 => "/var/lib/dhcp/dhcpd.leases",
_ => &self.config.path,
};
let mut config_file =
File::open(config_file_path).expect("Could not open DHCP leases file");
let mut config_content = String::new();
config_file
.read_to_string(&mut config_content)
.expect("Could not read DHCP leases file");
let mut unauthorized_macs: Vec<String> = vec![];
for cap in self.rgx_lease.captures_iter(&config_content) {
let ip = cap.get(1).unwrap().as_str();
let content = cap.get(2).unwrap().as_str();
if let Some(mac_cap) = self.rgx_mac.captures(content) {
let mac = mac_cap.get(1).unwrap().as_str();
let starts_str = self
.rgx_date_start
.captures(content)
.expect("No 'starts' field found in lease")
.get(1)
.unwrap()
.as_str();
let starts =
chrono::naive::NaiveDateTime::parse_from_str(starts_str, "%Y/%m/%d %H:%M:%S")
.expect(&format!("Bad date format: '{}'", starts_str));
let ends_str = self
.rgx_date_ends
.captures(content)
.expect("No 'ends' field found in lease")
.get(1)
.unwrap()
.as_str();
let ends =
chrono::naive::NaiveDateTime::parse_from_str(ends_str, "%Y/%m/%d %H:%M:%S")
.expect(&format!("Bad date format: '{}'", ends_str));
let now = Local::now().naive_utc();
if starts <= now && now < ends {
log::debug!("Found an active DHCP lease for {}", ip);
// Lease is active
if let Some(rules) = self.config.mac_rules.get(mac) {
// Found rules
if let Some(rules_list) = rules {
// Rules contains one or more entries
for rule in rules_list {
if content.find(rule).is_none() {
unauthorized_macs.push(mac.to_owned());
sender
.send(Message {
emitter: "dhcp_leases".to_owned(),
level: Level::Issue,
msg_type: "dhcp_leases.unauthorized_mac.rule"
.to_owned(),
text: format!(
"Mismatching rule '{}' for device {} at IP {}",
rule, mac, ip
),
})
.unwrap();
break;
} }
} }
} }
} else {
unauthorized_macs.push(mac.to_owned());
sender
.send(Message {
emitter: "dhcp_leases".to_owned(),
level: Level::Issue,
msg_type: "dhcp_leases.unauthorized_mac.unknown".to_owned(),
text: format!("Unknown device {} using IP {}", mac, ip),
})
.unwrap();
} }
} // println!("{:?}", );
} else {
log::warn!("No 'hardware ethernet' field found for IP {}", ip);
}
}
if unauthorized_macs.len() > 0 {
sender
.send(Message {
emitter: "dhcp_leases".to_owned(),
level: Level::Issue,
msg_type: "dhcp_leases.unknown_mac.sumup".to_owned(),
text: format!(
"The following macs are not allowed: {:?}",
unauthorized_macs
),
})
.unwrap();
} }
std::thread::sleep(std::time::Duration::from_millis( std::thread::sleep(std::time::Duration::from_millis(

57
src/monitors/ping.rs Normal file
View File

@ -0,0 +1,57 @@
use crate::message::{Level, Message};
use crate::monitor::*;
use serde::Deserialize;
#[derive(Debug)]
pub struct Ping {
config: PingConfig,
}
#[derive(Debug, Deserialize)]
struct PingConfig {
targets: Vec<PingConfigTarget>,
#[serde(default)]
period: Option<u64>,
}
#[derive(Debug, Deserialize)]
struct PingConfigTarget {
host: String,
#[serde(default)]
ping_args: Vec<String>,
#[serde(default)]
allowed_fails: Option<u64>,
#[serde(default)]
allowed_loss: Option<u64>,
}
impl Monitor for Ping {
fn new(config: serde_yaml::Value) -> Result<Self, Box<dyn std::error::Error>> {
let config = serde_yaml::from_value(config).expect("Invalid config for ping");
Ok(Ping { config: config })
}
fn run(&mut self, sender: &mpsc::Sender<Message>) {
for target in &self.config.targets {
use std::process::{Command, Stdio};
let res = Command::new("ping")
.args(&target.ping_args)
.arg(&target.host)
.stdout(Stdio::null())
.status()
.expect("failed to execute process");
if !res.success() {
sender
.send(Message {
emitter: "ping".to_owned(),
msg_type: "ping.failed".to_owned(),
level: Level::Issue,
text: format!("Cannot ping {}", &target.host),
})
.unwrap();
}
}
std::thread::sleep(std::time::Duration::from_millis(2000))
}
}

View File

@ -18,7 +18,7 @@ struct WifiAvailabilityConfig {
impl Monitor for WifiAvailability { impl Monitor for WifiAvailability {
fn new(config: serde_yaml::Value) -> Result<Self, Box<dyn std::error::Error>> { fn new(config: serde_yaml::Value) -> Result<Self, Box<dyn std::error::Error>> {
let config = serde_yaml::from_value(config).expect("Invalid config for wifi_availability"); let config = serde_yaml::from_value(config)?;
let wa = WifiAvailability { config: config }; let wa = WifiAvailability { config: config };
// Activate iface // Activate iface

View File

@ -8,9 +8,9 @@ pub struct Espeak {
} }
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
struct EspeakConfig { struct EspeakConfig {
#[serde(default = "espeak".to_owned())] // #[serde(default = "espeak")]
espeak: String, espeak: String,
#[serde(default = vec![])] // #[serde(default = vec![])]
args: Vec<String>, args: Vec<String>,
} }

View File

@ -96,52 +96,8 @@ impl BluetoothLightbulb {
.expect("Could not start discovery session"); .expect("Could not start discovery session");
} }
} }
}
impl Output for BluetoothLightbulb { fn get_targets(&self) -> Vec<(BluetoothDevice, BluetoothGATTCharacteristic)> {
fn new(config: serde_yaml::Value) -> Result<Self, Box<dyn std::error::Error>> {
let config: BluetoothLightbulbConfig = serde_yaml::from_value(config)?;
let msg_types = config
.msg_types
.iter()
.map(|(k, v)| {
(
Glob::new(k)
.expect("Invalid glob pattern")
.compile_matcher(),
v.clone(),
)
})
.collect();
let levels = config
.levels
.iter()
.map(|(k, v)| (k.clone(), v.clone()))
.collect();
let session = BluetoothSession::create_session(None)?;
let ret = BluetoothLightbulb {
config: config,
msg_types: msg_types,
levels: levels,
session: session,
};
// Initialize bluetooth adapter
let adapter = BluetoothAdapter::init(&ret.session)?;
// Power on
if !adapter.is_powered().unwrap() {
log::info!("bluetooth adapter {}: Set power ON", adapter.get_id());
adapter
.set_powered(true)
.expect("Cannot power on bluetooth device");
}
Ok(ret)
}
fn process_message(&mut self, message: Message) {
let adapter = let adapter =
BluetoothAdapter::init(&self.session).expect("Could not initialize bluetooth adapter"); BluetoothAdapter::init(&self.session).expect("Could not initialize bluetooth adapter");
@ -209,9 +165,64 @@ impl Output for BluetoothLightbulb {
macs_not_found.keys() macs_not_found.keys()
); );
self.initiate_scan(&adapter); self.initiate_scan(&adapter);
return;
} }
return targets;
}
}
impl Output for BluetoothLightbulb {
fn new(config: serde_yaml::Value) -> Result<Self, Box<dyn std::error::Error>> {
let config: BluetoothLightbulbConfig = serde_yaml::from_value(config)?;
let msg_types = config
.msg_types
.iter()
.map(|(k, v)| {
(
Glob::new(k)
.expect("Invalid glob pattern")
.compile_matcher(),
v.clone(),
)
})
.collect();
let levels = config
.levels
.iter()
.map(|(k, v)| (k.clone(), v.clone()))
.collect();
let session = BluetoothSession::create_session(None)?;
let ret = BluetoothLightbulb {
config: config,
msg_types: msg_types,
levels: levels,
session: session,
};
// Initialize bluetooth adapter
let adapter = BluetoothAdapter::init(&ret.session)?;
// Power on
if !adapter.is_powered().unwrap() {
log::info!("bluetooth adapter {}: Set power ON", adapter.get_id());
adapter
.set_powered(true)
.expect("Cannot power on bluetooth device");
}
let targets = ret.get_targets();
ret.set_color(&targets, 0.0, 1.0, 0.0);
std::thread::sleep(std::time::Duration::from_millis(200));
ret.set_color(&targets, 0.0, 0.0, 0.0);
Ok(ret)
}
fn process_message(&mut self, message: Message) {
let targets = self.get_targets();
// Merge LightConfig properties // Merge LightConfig properties
let mut cfg = LightConfig { let mut cfg = LightConfig {
color: Some(0xffffff), color: Some(0xffffff),