rnetmon/src/monitors/dhcp_leases.rs

170 lines
6.2 KiB
Rust
Raw Normal View History

2019-02-05 07:10:55 +00:00
pub use crate::message::*;
pub use crate::monitor::*;
2019-02-14 07:21:22 +00:00
use serde::Deserialize;
2019-02-05 07:10:55 +00:00
extern crate regex;
use regex::Regex;
extern crate chrono;
2019-02-23 17:45:43 +00:00
use chrono::Local;
2019-02-05 07:10:55 +00:00
#[derive(Debug)]
pub struct DHCPLeases {
config: DHCPLeasesConfig,
rgx_lease: Regex,
rgx_mac: Regex,
rgx_date_start: Regex,
rgx_date_ends: Regex,
}
2019-02-14 07:21:22 +00:00
#[derive(Debug, PartialEq, Deserialize)]
2019-02-05 07:10:55 +00:00
struct DHCPLeasesConfig {
2019-02-14 07:21:22 +00:00
#[serde(default)]
2019-02-05 07:10:55 +00:00
path: String,
2019-02-14 07:21:22 +00:00
#[serde(default)]
mac_rules: HashMap<String, Option<Vec<String>>>,
2019-02-23 17:45:43 +00:00
#[serde(default)]
period: Option<f64>,
2019-02-05 07:10:55 +00:00
}
impl Monitor for DHCPLeases {
2019-02-23 17:45:43 +00:00
fn new(config: serde_yaml::Value) -> Result<Self, Box<dyn std::error::Error>> {
let config = serde_yaml::from_value(config)?;
2019-02-05 07:10:55 +00:00
// Regex compilation
let rgx_lease = Regex::new(r"(?s)lease\s+(\d+(?:\.\d+){3})\s*\{\n?(.*?)\}").unwrap();
let rgx_mac =
2019-02-17 22:50:18 +00:00
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();
2019-02-05 07:10:55 +00:00
2019-02-14 07:21:22 +00:00
Ok(DHCPLeases {
2019-02-05 07:10:55 +00:00
config: config,
rgx_lease: rgx_lease,
rgx_mac: rgx_mac,
rgx_date_start: rgx_date_start,
rgx_date_ends: rgx_date_ends,
2019-02-14 07:21:22 +00:00
})
2019-02-05 07:10:55 +00:00
}
fn run(&mut self, sender: &mpsc::Sender<Message>) {
use std::fs::File;
use std::io::prelude::*;
2019-02-14 07:21:22 +00:00
let config_file_path = match self.config.path.len() {
0 => "/var/lib/dhcp/dhcpd.leases",
_ => &self.config.path,
};
2019-02-05 07:10:55 +00:00
let mut config_file =
2019-02-14 07:21:22 +00:00
File::open(config_file_path).expect("Could not open DHCP leases file");
2019-02-05 07:10:55 +00:00
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) {
2019-02-23 17:45:43 +00:00
let ip = cap.get(1).unwrap().as_str();
2019-02-05 07:10:55 +00:00
let content = cap.get(2).unwrap().as_str();
2019-02-23 17:45:43 +00:00
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;
}
2019-02-14 07:21:22 +00:00
}
2019-02-05 07:10:55 +00:00
}
2019-02-23 17:45:43 +00:00
} 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();
2019-02-05 07:10:55 +00:00
}
}
2019-02-23 17:45:43 +00:00
} else {
log::warn!("No 'hardware ethernet' field found for IP {}", ip);
2019-02-05 07:10:55 +00:00
}
}
2019-02-14 07:21:22 +00:00
if unauthorized_macs.len() > 0 {
sender
.send(Message {
emitter: "dhcp_leases".to_owned(),
level: Level::Issue,
2019-02-23 17:45:43 +00:00
msg_type: "dhcp_leases.unknown_mac.sumup".to_owned(),
2019-02-14 07:21:22 +00:00
text: format!(
"The following macs are not allowed: {:?}",
unauthorized_macs
),
})
.unwrap();
}
2019-02-05 07:10:55 +00:00
2019-02-23 17:45:43 +00:00
std::thread::sleep(std::time::Duration::from_millis(
(self.config.period.unwrap_or(60.0) * 1000.0) as u64,
));
2019-02-05 07:10:55 +00:00
}
}
/*
lease 192.168.0.26 {
starts 4 2018/08/16 22:31:22;
ends 4 2018/08/16 23:01:22;
tstp 4 2018/08/16 23:01:22;
cltt 4 2018/08/16 22:31:22;
binding state free;
hardware ethernet 84:4b:f5:16:f4:94;
}
*/