#![cfg_attr(test, allow(clippy::unwrap_used, clippy::expect_used))]
mod bodyfile;
mod findings;
use std::io::{Read, Seek, Write};
pub use dar::{CrcStatus, DarEntry, DarError, DarReader, EntryKind, SliceReader};
pub use findings::{Anomaly, AnomalyKind, Severity};
const FAR_FUTURE_EPOCH_SECS: i64 = 4_102_444_800;
pub trait DarBodyfile {
fn bodyfile(&self) -> String;
}
impl DarBodyfile for DarEntry {
fn bodyfile(&self) -> String {
bodyfile::line(self)
}
}
pub trait DarAudit {
fn audit(&self) -> Vec<Anomaly>;
fn write_bodyfile<W: Write>(&self, out: &mut W) -> std::io::Result<()>;
}
impl<R: Read + Seek> DarAudit for DarReader<R> {
fn audit(&self) -> Vec<Anomaly> {
let mut anomalies = Vec::new();
if !self.is_complete() {
anomalies.push(Anomaly::new(AnomalyKind::IncompleteCatalog {
entries_recovered: self.entry_count(),
}));
}
let mut seen: std::collections::HashSet<Vec<u8>> = std::collections::HashSet::new();
let mut dup_seen: std::collections::HashSet<Vec<u8>> = std::collections::HashSet::new();
for e in self.iter_entries() {
let path = String::from_utf8_lossy(&e.path).into_owned();
if e.path.first() == Some(&b'/') {
anomalies.push(Anomaly::new(AnomalyKind::AbsolutePath {
path: path.clone(),
}));
}
if e.path.split(|&b| b == b'/').any(|c| c == b"..") {
anomalies.push(Anomaly::new(AnomalyKind::ParentTraversal {
path: path.clone(),
}));
}
if e.path.iter().any(|&b| b < 0x20 || b == 0x7f) {
anomalies.push(Anomaly::new(AnomalyKind::ControlCharsInName {
path: path.clone(),
}));
}
for (field, t) in [("atime", e.atime), ("mtime", e.mtime)]
.into_iter()
.chain(e.ctime.map(|c| ("ctime", c)))
{
if t > FAR_FUTURE_EPOCH_SECS {
anomalies.push(Anomaly::new(AnomalyKind::FutureTimestamp {
path: path.clone(),
field,
epoch_secs: t,
}));
}
}
if !seen.insert(e.path.clone()) && dup_seen.insert(e.path.clone()) {
anomalies.push(Anomaly::new(AnomalyKind::DuplicatePath { path }));
}
}
anomalies.sort_by_key(|a| std::cmp::Reverse(a.severity));
anomalies
}
fn write_bodyfile<W: Write>(&self, out: &mut W) -> std::io::Result<()> {
for entry in self.iter_entries() {
writeln!(out, "{}", entry.bodyfile())?;
}
Ok(())
}
}