use std::collections::HashMap; use std::fs; use std::io::Write; use std::path::{Path, PathBuf}; use lazy_static::lazy_static; use regex::Regex; use tracing::{debug, error, info, trace}; use tracing_subscriber::{fmt, EnvFilter}; use config::RepairConfig; use csaf_parser::CSAF; pub mod cli; mod config; /// 定义 crate::Error /// 大部分函数返回的错误 pub type Error = Box; /// 定义 crate::Result pub type Result = std::result::Result; lazy_static! { pub static ref NVR_RE: Regex = Regex::new( // %{name}-%{version}-%{release}.[oexxxx.xxx.rpm] r"^([a-zA-Z0-9\-_+]+)-([0-9a-zA-Z\._+]+)-([0-9a-zA-Z\._-]+).(oe[0-9a-z]+.[0-9a-z]+.rpm)" ) .unwrap(); } pub fn cumain() -> Result<()> { set_up_logging()?; let cli = cli::parse(); match cli.subcommand { cli::CliSub::Convert(cli) => covert(&cli), cli::CliSub::Db(cli) => sadb(&cli), cli::CliSub::Auto(_) => auto(), } } fn set_up_logging() -> crate::Result<()> { fmt() .with_ansi(false) .with_timer(fmt::time::OffsetTime::local_rfc_3339().unwrap()) .with_line_number(true) .with_env_filter(EnvFilter::from_default_env()) .try_init() } /// 可使用 convert 函数将 csaf 格式文件转换并输出至指定的 cusa 文件。 /// /// 例: /// /// ```no_run /// use csaf2cusa::cli::ConvertCli /// /// let cli = ConvertCli::new( /// // input /// "xxx-csaf.json".to_string, /// // output /// Some("pkg_version_said.json".to_string), /// // print to screen /// false, /// ); /// /// csaf2cusa::Convert(cli)?; /// ``` pub fn covert(cli: &cli::ConvertCli) -> Result<()> { // 检查 input,此为必须项 let input = Path::new(&cli.input); if !input.is_file() { return Err("输入的文件不是有效的文件路径!".into()); } // 从 input 读取 csaf,并转换为 cusa let csaf = CSAF::from(&cli.input)?; let cusa = csaf.sainfo(); let data = serde_json::to_string_pretty(&cusa)?; // 是否将 cusa 输出至指定文件 if let Some(output) = &cli.output { let output = Path::new(output); // output 所在父路径须提前创建 let parent = output.parent().unwrap(); if !parent.exists() { return Err("无法访问输出文件所在路径,请确认!".into()); } // 写入文件 let mut json_file = fs::OpenOptions::new() .read(true) .write(true) .create(true) .open(output)?; json_file.write(data.as_bytes())?; json_file.flush()?; // 在输出到文件模式下,默认不再标准输出打印 CUSA if !cli.print { return Ok(()); } } // 在标准输出打印 CUSA println!("{}", data); Ok(()) } pub fn sadb(cli: &cli::SaDbCli) -> Result<()> { let source = &cli.from; // 只要文件 let files = walk_dir(source, true); let mut sadb = HashMap::new(); let mut cvedb = HashMap::new(); for _file in files { match _file.extension() { Some(tail) => { if tail != "json" { continue; } // 从 json 中读取 csaf 并转换为 cusa let file = if let Some(file) = _file.to_str() { file } else { continue; }; let csaf = CSAF::from(file)?; let sa = csaf.sainfo(); // cvedb sa.cves.clone().into_iter().for_each(|cve| { cvedb.insert(cve.id.clone(), cve); }); // sadb if let Some(_) = sadb.insert(sa.id.clone(), sa) { // unimplemented!(); } } _ => {} } } // 写入 sadb 文件 let data = serde_json::to_string_pretty(&sadb)?; let mut sadb_file = fs::OpenOptions::new() .read(true) .write(true) .create(true) .open(&cli.sa)?; sadb_file.write(data.as_bytes())?; sadb_file.flush()?; // 写入 cvedb 文件 let data = serde_json::to_string_pretty(&cvedb)?; let mut cvedb_file = fs::OpenOptions::new() .read(true) .write(true) .create(true) .open(&cli.cve)?; cvedb_file.write(data.as_bytes())?; cvedb_file.flush()?; Ok(()) } // 递归去读一个路径,返回一个文件列表 #[allow(dead_code)] fn walk_dir>(path: P, nodir: bool) -> Vec { let mut res = vec![]; for entry in std::fs::read_dir(path).expect("read_dir call failed!") { let entry = entry.unwrap().path(); if entry.is_dir() { if !nodir { res.push(entry.clone()); } res.append(&mut walk_dir(entry, nodir)); } else { // 暂不处理链接等情况 res.push(entry); } } res } /// 从配置文件中读取 csaf 和 cusa 数据库的路径,并执行自动转换操作 pub fn auto() -> Result<()> { // 默认配置为,但也可读取执行命令路径下的配置 let config = { let default = Path::new("/etc/cuvars/csaf2cusa.json"); if default.is_file() { default.to_str().unwrap() } else { "csaf2cusa.json" } }; info!("Config file is: {}", config); let auto = config::AutoConfig::from(config)?; info!("Load AutoConfig: {:?}", auto); let files = walk_dir(auto.source(), true); for _file in files { match _file.extension() { Some(tail) => { if tail != "json" { continue; } // 从 json 中读取 csaf 并转换为 cusa let file = if let Some(file) = _file.to_str() { file } else { continue; }; trace!("parsing {}", file); let csaf = CSAF::from(file)?; _save_2_cusa_db(auto.target(), &csaf)?; } _ => {} } } Ok(()) } fn _save_2_cusa_db(dbpath: &str, csaf: &CSAF) -> Result<()> { let mut db = PathBuf::from(dbpath); let component = csaf.affected_component(); trace!("Get affected_component: {}", component); // 这里随便取一个 src 包名 let _src = csaf.fixed_srcs()[0].product_id.as_str(); // TODO: may empty if _src == "" { error!( "{}: ProductTree->packages[\"src\"] have empty productid, ignore it!", csaf.id() ); return Ok(()); } db.push(_src.chars().next().unwrap().to_string()); // TODO!() // db: "cusas/l/log4j,mybatis,netty,springframework,wildfly-security-manager,wildfly-elytron,wildfly-build-tools,wildfly-common,wildfly-core,thrift,json-lib,datanucleus-core,jgroups,mx4j,jboss-logging,infinispan,datanucleus-rdbms,avalon-logkit,datanucleus-api-jdo,avalon-framework,HikariCP,metrics" // Error: Os { code: 63, kind: InvalidFilename, message: "File name too long" } db.push(component); // 读取修复配置 let mut repairconf = db.clone(); repairconf.push("config.json"); let repairconfig = match config::RepairConfig::from(&repairconf) { Ok(repairconfig) => repairconfig, _ => { // 这里临时处理下,不应该创建 // TO BE DELETE let parent = repairconf.parent().unwrap(); match fs::create_dir_all(parent) { Ok(_) => {} Err(e) => { error!("sa_id: {}, {}", csaf.id(), e.to_string()); return Ok(()); } } let repairconfig = RepairConfig::new("22.03-LTS".to_string(), true, "".to_string()); // 写入默认配置 let data = serde_json::to_string_pretty(&repairconfig)?; let mut repair = fs::OpenOptions::new() .read(true) .write(true) .create(true) .open(repairconf)?; repair.write(data.as_bytes())?; repair.flush()?; repairconfig } }; // 是否影响当前软件包,即作上游判断 let mut not_affected = true; for product in csaf.affected_products() { if product.product_id.ends_with(repairconfig.upstream()) { not_affected = false; } } if not_affected { debug!( "{} not effected {}'s {}, skip...", csaf.id(), repairconfig.upstream(), component ); return Ok(()); } // 增加 sa 信息 let mut nvr = String::new(); for product in &csaf.fixed_srcs() { if product .product_identification_helper .cpe .ends_with(repairconfig.upstream()) { nvr = if let Some(caps) = NVR_RE.captures(&product.product_id) { format!("{}-{}-{}", &caps[1], &caps[2], &caps[3]) } else { product.product_id.clone() }; nvr.push_str(&format!("_{}.json", csaf.id())); break; } } db.push(&nvr); if db.exists() { debug!("{} is already converted!", csaf.id()); return Ok(()); } let data = serde_json::to_string_pretty(&csaf.sainfo())?; let mut sa_file = fs::OpenOptions::new() .read(true) .write(true) .create(true) .open(db)?; sa_file.write(data.as_bytes())?; sa_file.flush()?; Ok(()) }