Dirty WIP
This commit is contained in:
parent
2df8a9351f
commit
d41d5bd3cd
2031
Cargo.lock
generated
2031
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@ -9,6 +9,7 @@ libloading = "0.5.0"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_yaml = "0.8"
|
||||
serde_json = "1.0"
|
||||
serde_xml = "0.9"
|
||||
getopts = "0.2"
|
||||
blurz = "0.4.0"
|
||||
regex = "1"
|
||||
@ -18,4 +19,5 @@ log = "0.4"
|
||||
simplelog = "0.5"
|
||||
reqwest = "0.9"
|
||||
percent-encoding = "1.0"
|
||||
rand = "0.6"
|
||||
rand = "0.6"
|
||||
trust-dns-resolver = "0.19"
|
||||
|
@ -1,6 +1,5 @@
|
||||
#![feature(unrestricted_attribute_tokens)]
|
||||
#![feature(custom_attribute)]
|
||||
#![feature(try_from)]
|
||||
#![feature(register_tool)]
|
||||
#![feature(register_attr)]
|
||||
|
||||
mod filter;
|
||||
mod message;
|
||||
|
83
src/monitors/dns.rs
Normal file
83
src/monitors/dns.rs
Normal 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));
|
||||
}
|
||||
}
|
@ -22,8 +22,7 @@ struct DummySenderConfig {
|
||||
|
||||
impl Monitor for DummySender {
|
||||
fn new(config: serde_yaml::Value) -> Result<Self, Box<dyn std::error::Error>> {
|
||||
let config: DummySenderConfig =
|
||||
serde_yaml::from_value(config).expect("Invalid config for dummy_sender");
|
||||
let config: DummySenderConfig = serde_yaml::from_value(config)?;
|
||||
|
||||
Ok(DummySender {
|
||||
cnt: 0,
|
||||
|
@ -1,5 +1,8 @@
|
||||
pub mod dhcp_leases;
|
||||
pub mod dns;
|
||||
pub mod dummy_sender;
|
||||
pub mod nmap_scanner;
|
||||
pub mod ping;
|
||||
pub mod wifi_availability;
|
||||
|
||||
use crate::monitor::*;
|
||||
@ -12,6 +15,9 @@ pub fn factory(
|
||||
"dummy_sender" => Ok(Box::new(dummy_sender::DummySender::new(config)?)),
|
||||
"wifi_availability" => Ok(Box::new(wifi_availability::WifiAvailability::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),
|
||||
}
|
||||
}
|
||||
|
@ -3,17 +3,13 @@ pub use crate::monitor::*;
|
||||
use serde::Deserialize;
|
||||
|
||||
extern crate regex;
|
||||
use regex::Regex;
|
||||
// use regex::Regex;
|
||||
extern crate chrono;
|
||||
use chrono::Local;
|
||||
// use chrono::Local;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct NmapScanner {
|
||||
config: NmapScannerConfig,
|
||||
rgx_lease: Regex,
|
||||
rgx_mac: Regex,
|
||||
rgx_date_start: Regex,
|
||||
rgx_date_ends: Regex,
|
||||
}
|
||||
#[derive(Debug, PartialEq, Deserialize)]
|
||||
struct NmapScannerConfig {
|
||||
@ -21,144 +17,62 @@ struct NmapScannerConfig {
|
||||
args: Vec<String>,
|
||||
#[serde(default)]
|
||||
ip_range: Option<String>,
|
||||
#[serde(default)]
|
||||
period: Option<f64>,
|
||||
}
|
||||
|
||||
impl Monitor for NmapScanner {
|
||||
fn new(
|
||||
config: serde_yaml::Value,
|
||||
) -> Result<Self, Box<dyn std::error::Error>> {
|
||||
let mut config = serde_yaml::from_value(config)?;
|
||||
fn new(config: serde_yaml::Value) -> Result<Self, Box<dyn std::error::Error>> {
|
||||
let mut config: NmapScannerConfig = serde_yaml::from_value(config)?;
|
||||
|
||||
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
|
||||
let rgx_lease = Regex::new(r"(?s)lease\s+(\d+(?:\.\d+){3})\s*\{\n?(.*?)\}").unwrap();
|
||||
let rgx_mac =
|
||||
Regex::new(r"(?m)^\s*hardware\s+ethernet\s([a-f0-9]{2}(?::[a-f0-9]{2}){5})\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_lease = Regex::new(r"(?s)lease\s+(\d+(?:\.\d+){3})\s*\{\n?(.*?)\}").unwrap();
|
||||
// let rgx_mac =
|
||||
// Regex::new(r"(?m)^\s*hardware\s+ethernet\s([a-f0-9]{2}(?::[a-f0-9]{2}){5})\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();
|
||||
|
||||
Ok(NmapScanner {
|
||||
config: config,
|
||||
rgx_lease: rgx_lease,
|
||||
rgx_mac: rgx_mac,
|
||||
rgx_date_start: rgx_date_start,
|
||||
rgx_date_ends: rgx_date_ends,
|
||||
})
|
||||
Ok(NmapScanner { config: config })
|
||||
}
|
||||
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;
|
||||
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;
|
||||
}
|
||||
}
|
||||
println!("address={:?}", address);
|
||||
}
|
||||
} 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();
|
||||
}
|
||||
}
|
||||
} 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();
|
||||
// println!("{:?}", );
|
||||
}
|
||||
|
||||
std::thread::sleep(std::time::Duration::from_millis(
|
||||
|
57
src/monitors/ping.rs
Normal file
57
src/monitors/ping.rs
Normal 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))
|
||||
}
|
||||
}
|
@ -18,7 +18,7 @@ struct WifiAvailabilityConfig {
|
||||
|
||||
impl Monitor for WifiAvailability {
|
||||
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 };
|
||||
// Activate iface
|
||||
|
@ -8,9 +8,9 @@ pub struct Espeak {
|
||||
}
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct EspeakConfig {
|
||||
#[serde(default = "espeak".to_owned())]
|
||||
// #[serde(default = "espeak")]
|
||||
espeak: String,
|
||||
#[serde(default = vec![])]
|
||||
// #[serde(default = vec![])]
|
||||
args: Vec<String>,
|
||||
}
|
||||
|
||||
|
@ -96,52 +96,8 @@ impl BluetoothLightbulb {
|
||||
.expect("Could not start discovery session");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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");
|
||||
}
|
||||
|
||||
Ok(ret)
|
||||
}
|
||||
fn process_message(&mut self, message: Message) {
|
||||
fn get_targets(&self) -> Vec<(BluetoothDevice, BluetoothGATTCharacteristic)> {
|
||||
let adapter =
|
||||
BluetoothAdapter::init(&self.session).expect("Could not initialize bluetooth adapter");
|
||||
|
||||
@ -209,9 +165,64 @@ impl Output for BluetoothLightbulb {
|
||||
macs_not_found.keys()
|
||||
);
|
||||
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
|
||||
let mut cfg = LightConfig {
|
||||
color: Some(0xffffff),
|
||||
|
Loading…
Reference in New Issue
Block a user