317 lines
8.2 KiB
Rust
317 lines
8.2 KiB
Rust
use std::collections::{HashMap, HashSet};
|
|
use std::str::FromStr;
|
|
|
|
use cvrf_xmlparser::{Severity, CUSA};
|
|
use lazy_static::lazy_static;
|
|
use rpm_rs::rpm::{get_installed_packages, Package};
|
|
use rpm_rs::rpmio::rpmvercmp;
|
|
use updateinfo_xmlparser::{RpmInfo, UpdateInfoDb};
|
|
|
|
use crate::analyzer::db::{CveDb, PackgeDb, SaDb};
|
|
use crate::cli::Cli;
|
|
|
|
lazy_static! {
|
|
pub static ref update_pkgs: PackgeDb = {
|
|
let mut updatedb = UpdateInfoDb::new();
|
|
updatedb.load_xml("test/updateinfo.xml").unwrap();
|
|
|
|
let mut pkgdb = PackgeDb::new();
|
|
pkgdb.load_from_updateinfodb(&updatedb);
|
|
pkgdb
|
|
};
|
|
pub static ref sadb: SaDb = {
|
|
let mut _sadb = SaDb::new();
|
|
|
|
_sadb.load_from_file("test/cvrf_sainfos.json").unwrap();
|
|
_sadb
|
|
};
|
|
pub static ref cvedb: CveDb = sadb.get_cvedb();
|
|
}
|
|
|
|
pub mod db;
|
|
|
|
struct Reporter {
|
|
// 已修复的安全更新
|
|
fixed: HashMap<CUSA, HashSet<RpmInfo>>,
|
|
|
|
// 可用的安全更新
|
|
avaliable: HashMap<CUSA, HashSet<RpmInfo>>,
|
|
}
|
|
|
|
impl Reporter {
|
|
// 新建一个空的 Reporter
|
|
pub fn new() -> Self {
|
|
let fixed = HashMap::new();
|
|
let avaliable = HashMap::new();
|
|
|
|
Self {
|
|
fixed, avaliable
|
|
}
|
|
}
|
|
|
|
// get fixed, 不可写
|
|
pub fn get_fixed(&self) -> &HashMap<CUSA, HashSet<RpmInfo>> {
|
|
&self.fixed
|
|
}
|
|
|
|
// get avaliable, 只读
|
|
pub fn get_avaliable(&self) -> &HashMap<CUSA, HashSet<RpmInfo>> {
|
|
&self.avaliable
|
|
}
|
|
|
|
// 添加包到已修复
|
|
pub fn add_to_fixed(&mut self, cusa: &CUSA, rpm: RpmInfo) {
|
|
// 可以找到则直接 insert
|
|
if let Some(rpms) = self.fixed.get_mut(cusa) {
|
|
rpms.insert(rpm);
|
|
} else {
|
|
let mut rpms = HashSet::new();
|
|
rpms.insert(rpm);
|
|
self.fixed.insert(cusa.clone(), rpms);
|
|
}
|
|
}
|
|
|
|
// 添加包到可用更新
|
|
pub fn add_to_avaliable(&mut self, cusa: &CUSA, rpm: RpmInfo) {
|
|
// 可以找到则直接 insert
|
|
if let Some(rpms) = self.avaliable.get_mut(cusa) {
|
|
rpms.insert(rpm);
|
|
} else {
|
|
let mut rpms = HashSet::new();
|
|
rpms.insert(rpm);
|
|
self.avaliable.insert(cusa.clone(), rpms);
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn cuvat_run(cli: &Cli) -> crate::Result<()> {
|
|
// 只报告 cve 相关
|
|
if cli.cves {
|
|
return list_cves(cli);
|
|
}
|
|
|
|
// 只报告 sa 相关
|
|
if cli.sas {
|
|
return list_sas(cli);
|
|
}
|
|
|
|
// 生成报告
|
|
if cli.report {
|
|
return repoter(cli);
|
|
}
|
|
|
|
// 默认报告格式
|
|
summary(cli)
|
|
}
|
|
|
|
fn list_cves(cli: &Cli) -> crate::Result<()> {
|
|
let reporter = _reporter()?;
|
|
let avaliable = reporter.get_avaliable();
|
|
let severity = Severity::from_str(&cli.severity)?;
|
|
let mut _cves = HashSet::new();
|
|
cli.sources.iter().for_each(|id| { _cves.insert(id); });
|
|
|
|
let mut cves = HashSet::new();
|
|
let mut msg = String::new();
|
|
|
|
// 仅针对可用更新
|
|
for (cusa, _) in avaliable {
|
|
// 过滤
|
|
if cusa.severity() < &severity {
|
|
continue;
|
|
}
|
|
|
|
cusa.cves().iter().for_each(|cve| {
|
|
if let Some(_) = _cves.get(&cve.id) {
|
|
let _ = cves.insert(cve.clone());
|
|
_cves.remove(&cve.id);
|
|
}
|
|
});
|
|
}
|
|
|
|
for cve in &cves {
|
|
if cli.info {
|
|
msg = format!("{msg}\n\n{cve:#?}");
|
|
} else {
|
|
msg = format!("{msg}\n{}", cve.id);
|
|
}
|
|
}
|
|
|
|
if !_cves.is_empty() {
|
|
msg = format!("{msg}\n\nUnaffected CVEs:\n{_cves:#?}")
|
|
}
|
|
|
|
println!("{msg}");
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn list_sas(cli: &Cli) -> crate::Result<()> {
|
|
let reporter = _reporter()?;
|
|
let avaliable = reporter.get_avaliable();
|
|
let severity = Severity::from_str(&cli.severity)?;
|
|
let mut _sas = HashSet::new();
|
|
cli.sources.iter().for_each(|id| { _sas.insert(id); });
|
|
|
|
let mut sas = HashSet::new();
|
|
let mut msg = String::new();
|
|
|
|
// 仅针对可用更新
|
|
for (cusa, _) in avaliable {
|
|
// 过滤
|
|
if cusa.severity() < &severity {
|
|
continue;
|
|
}
|
|
|
|
if let Some(_) = _sas.get(&cusa.id) {
|
|
let _ = sas.insert(cusa.clone());
|
|
_sas.remove(&cusa.id);
|
|
}
|
|
}
|
|
|
|
for sa in &sas {
|
|
if cli.info {
|
|
msg = format!("{msg}\n\n{sa:#?}");
|
|
} else {
|
|
msg = format!("{msg}\n{}", sa.id);
|
|
}
|
|
}
|
|
|
|
if !_sas.is_empty() {
|
|
msg = format!("{msg}\n\nUnaffected SAs:\n{_sas:#?}")
|
|
}
|
|
|
|
println!("{msg}");
|
|
|
|
Ok(())
|
|
}
|
|
|
|
// 最为详尽的报告,包括当前系统中软件包所涉及到,已修复和可用但未修复
|
|
// 的所有软件包的列表。
|
|
fn repoter(cli: &Cli) -> crate::Result<()> {
|
|
let reporter = _reporter()?;
|
|
let fixed = reporter.get_fixed();
|
|
let avaliable = reporter.get_avaliable();
|
|
let _ = fixed;
|
|
let _ = avaliable;
|
|
println!("TODO...");
|
|
Ok(())
|
|
}
|
|
|
|
fn summary(cli: &Cli) -> crate::Result<()> {
|
|
let reporter = _reporter()?;
|
|
let avaliable = reporter.get_avaliable();
|
|
let severity = Severity::from_str(&cli.severity)?;
|
|
let mut total = 0;
|
|
let mut res = vec![0; 5];
|
|
let mut lists = vec![vec![]; 5];
|
|
let mut msg = String::new();
|
|
|
|
for (cusa, _) in avaliable {
|
|
// 过滤
|
|
if cusa.severity() < &severity {
|
|
continue;
|
|
}
|
|
|
|
let pos: usize = cusa.severity().into();
|
|
total += 1;
|
|
res[pos] += 1;
|
|
lists[pos].push(cusa);
|
|
}
|
|
|
|
// 如果需要列出详细的 sa
|
|
if cli.list {
|
|
msg = format!("当前系统未修复的公告列表(共 {total} 个):\n");
|
|
for sas in &lists {
|
|
for sa in sas {
|
|
msg = format!("{msg}\n{:>4}{}: {:?}", "", sa.id(), sa.severity());
|
|
}
|
|
}
|
|
//msg = format!("{msg}\n\n");
|
|
} else {
|
|
msg = format!(
|
|
"{msg}
|
|
本次安全检查共扫描出 {} 个漏洞:
|
|
|
|
致命漏洞:{:>3} 个
|
|
高危漏洞:{:>3} 个
|
|
中危漏洞:{:>3} 个
|
|
低危漏洞:{:>3} 个
|
|
",
|
|
total, res[4], res[3], res[2], res[1]
|
|
);
|
|
}
|
|
|
|
println!("{msg}");
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn _reporter() -> crate::Result<Reporter> {
|
|
let mut reporter = Reporter::new();
|
|
// 当前系统所有已安装的 rpm 包
|
|
let installed = get_installed_packages();
|
|
// installed 已被消费掉
|
|
// latest_installed 是所有最新版本软件包的 Vec
|
|
let latest_installed: Vec<Package> = installed
|
|
.into_iter()
|
|
.map(|(_, pkgs)| {
|
|
let mut latest = pkgs[0].clone();
|
|
for pkg in pkgs.into_iter() {
|
|
latest = rpmdb_package_vercmp(latest, pkg);
|
|
}
|
|
latest
|
|
})
|
|
.collect();
|
|
|
|
// 获取 sa 更新列表
|
|
for pkg in latest_installed {
|
|
let name = pkg.name();
|
|
if let Some(updates) = update_pkgs.db().get(name) {
|
|
for update in updates {
|
|
// 因 updateinfo 里包含所有架构,故于此做个判断
|
|
// 是否考虑架构信息在不同的版本有所不同?
|
|
if update.arch() != pkg.arch().unwrap() {
|
|
continue;
|
|
}
|
|
let sainfo = sadb.db().get(update.sa()).unwrap();
|
|
// epoch 判断
|
|
match (update.epoch(), pkg.epoch()) {
|
|
(Some(_), None) => {
|
|
reporter.add_to_avaliable(&sainfo, update.clone());
|
|
}
|
|
(None, Some(_)) => {
|
|
reporter.add_to_fixed(&sainfo, update.clone());
|
|
}
|
|
_ => {}
|
|
}
|
|
|
|
// evr 对比
|
|
if rpmvercmp(&update.evr(), &pkg.evr()) > 0 {
|
|
reporter.add_to_avaliable(&sainfo, update.clone());
|
|
} else {
|
|
reporter.add_to_fixed(&sainfo, update.clone());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Ok(reporter)
|
|
}
|
|
|
|
// 对比两个 rpm Package 的版本,返回最新的一个
|
|
fn rpmdb_package_vercmp(pa: Package, pb: Package) -> Package {
|
|
// 首先进行 epoch 的比较
|
|
match (pa.epoch(), pb.epoch()) {
|
|
(Some(_), None) => return pa,
|
|
(None, Some(_)) => return pb,
|
|
_ => {} // 继续往下对比
|
|
}
|
|
|
|
if rpmvercmp(&pa.evr(), &pb.evr()) > 0 {
|
|
return pa;
|
|
} else {
|
|
return pb;
|
|
}
|
|
}
|