1#![deny(clippy::mem_forget)]
2
3use ascii_table_rs::{AsciiTable, CellValue};
4use fenir::cpe::Cpe;
5use fenir::cve::{Cve, Score, create_cve_from, cve_from_cwe};
6use fenir::cwe::show_weaknesses;
7use fenir::database::{
8 MitreDefinition, execute_query, find_capec_by_id, find_cwe_by_id, parse_id_to_u32,
9};
10use fenir::facilities::{Cleaning, Uppercase, Wording};
11use fenir::facilities::{
12 FIELD_SEPARATOR_STRING, FIELD_SEPARATOR_STRING_LONG, Reduce, build_dates_range, print_values,
13};
14use fenir::network::{check_mitre_data, check_nvd_api_key, check_nvd_api_key_warning};
15use fenir::package::concepts::{filtering_cve, print_cve, simplify_cve_list_content};
16use fenir::package::errors::{write_error_message, write_error_message_and_exit};
17use fenir::query::QueryType::{
18 ByCpeVulnerable, ByCve, ByCveCpeName, ByCveExploited, ByCveNew, ByCveUpdated, ByCwe,
19};
20use fenir::query::{QueryType, build_query, create_query_dates};
21use fenir::{header, no_argument_and_exit, section, section_level, show_version};
22use serde_json::Value;
23use serde_json::Value::Null;
24use std::io::stdout;
25use std::process::exit;
26use termint::enums::Color;
27use termint::widgets::ToSpan;
28use treelog::builder::TreeBuilder;
29
30#[cfg(target_os = "linux")]
31use crate::package::{get_changelog_for, get_package};
32
33#[cfg(target_os = "linux")]
34use fenir::os::{OSFamily, SupportedOs};
35
36#[cfg(target_os = "linux")]
37use fenir::package::concepts::show_cve_list;
38
39#[cfg(target_os = "linux")]
40use fenir::package::concepts::Changelog;
41
42#[cfg(target_os = "linux")]
43use fenir::package::errors::stop_if_invalid_package;
44
45#[cfg(target_os = "linux")]
47mod package {
48 use crate::package::os::{Debian, RedHat};
49 use core::str;
50 use fenir::facilities::{restore_current_dir_and_remove_temp_dir, show_command_result};
51 use fenir::os::{OSFamily, SupportedOs};
52 use fenir::package::concepts::{
53 Package, PackageStatus, Repository, Request, changelog_result, default_package,
54 };
55 use fenir::package::errors::{write_error_message, write_error_message_and_exit};
56 use std::env;
57 use std::fs::File;
58 use std::io::{BufReader, Read};
59 use std::path::Path;
60 use std::process::{Child, Output, Stdio};
61 use std::string::FromUtf8Error;
62
63 pub mod os {
65 pub struct Debian;
67
68 pub struct RedHat;
70 }
71
72 impl Request for Debian {
73 fn ask_package(&self, package: &str, version: &str) -> Package {
74 let command_output: Result<String, FromUtf8Error> = if version.is_empty() {
75 let command_result = std::process::Command::new("/usr/bin/dpkg-query")
76 .arg("-W")
77 .arg(package)
78 .stderr(Stdio::null())
79 .output()
80 .expect("Fail to run dpkg-query command");
81
82 show_command_result(command_result)
83 } else if version.eq("next") {
84 let command_result = std::process::Command::new("/usr/bin/apt")
85 .arg("list")
86 .arg("--upgradable")
87 .arg(package)
88 .arg("-qq")
89 .stderr(Stdio::null())
90 .output()
91 .expect("Fail to run apt-get list");
92
93 show_command_result(command_result)
94 } else {
95 let cmd_package = std::process::Command::new("/usr/bin/dpkg-query")
96 .arg("-W")
97 .arg(package)
98 .stderr(Stdio::null())
99 .stdout(Stdio::piped())
100 .spawn()
101 .unwrap();
102
103 let cmd_version = grep_version(version, cmd_package);
104
105 show_command_result(cmd_version)
106 };
107
108 extract_package_result(command_output)
109 }
110
111 fn ask_changelog_package(&self, package: Package) -> String {
112 let changelog_command = std::process::Command::new("/usr/bin/apt")
113 .args([
114 "changelog",
115 format!("{}={}", package.name, package.version).as_str(),
116 ])
117 .stderr(Stdio::null())
118 .output()
119 .expect("Fail to run apt command");
120
121 changelog_result(
122 package.clone(),
123 changelog_command,
124 self.read_changelog_files(package.clone()),
125 )
126 }
127
128 fn read_changelog_files(&self, package: Package) -> String {
129 let changelog_file = match get_debian_changelog_for("/usr/share/doc", &package) {
130 Ok(file) => file,
131 Err(err) => {
132 write_error_message(package.name.as_str(), Some(err));
133 return String::new();
134 }
135 };
136
137 let result = decode_changelog(changelog_file);
138 result.unwrap_or_else(|_| String::new())
139 }
140 }
141
142 fn decode_changelog(file: File) -> Result<String, &'static str> {
166 let gz_decoder = flate2::read::GzDecoder::new(BufReader::new(file));
167 match debian_changelog::ChangeLog::read(BufReader::new(gz_decoder)) {
168 Ok(changelog) => {
169 let _ = std::fs::remove_dir_all("/var/tmp/get-cve");
170 Ok(changelog.to_string())
171 }
172 _ => Err("Failed for changelog reading"),
173 }
174 }
175
176 impl Request for RedHat {
177 fn ask_package(&self, package: &str, version: &str) -> Package {
178 let command_output: Result<String, FromUtf8Error> = if version.is_empty() {
179 let command_result = std::process::Command::new("/usr/bin/rpm")
180 .arg("-qa")
181 .arg("--qf")
182 .arg("%{NAME}.%{ARCH}\t%{VERSION}-%{RELEASE}")
183 .arg(package)
184 .stderr(Stdio::null())
185 .output()
186 .expect("Error on rpm command running");
187
188 show_command_result(command_result)
189 } else {
190 let command_result = std::process::Command::new("/usr/bin/rpm")
191 .arg("-qa")
192 .arg("--qf")
193 .arg("%{NAME}.%{ARCH}\t%{VERSION}-%{RELEASE}")
194 .arg(package)
195 .stderr(Stdio::null())
196 .stdout(Stdio::piped())
197 .spawn()
198 .expect("Error on rpm command running");
199
200 let grep_result = grep_version(version, command_result);
201 show_command_result(grep_result)
202 };
203
204 extract_package_result(command_output)
205 }
206
207 fn ask_changelog_package(&self, package: Package) -> String {
208 let changelog_command = std::process::Command::new("/usr/bin/rpm")
209 .arg("-q")
210 .arg("--changelog")
211 .arg(package.name.as_str())
212 .stderr(Stdio::null())
213 .output()
214 .expect("Impossible to run rpm command for changelog");
215
216 changelog_result(
217 package.clone(),
218 changelog_command,
219 self.read_changelog_files(package.clone()),
220 )
221 }
222
223 fn read_changelog_files(&self, package: Package) -> String {
224 let package_name = extract_package_name(&package.name);
225 let changelog_path = format!("/usr/share/doc/{}", package_name);
226
227 match get_changelog_files(changelog_path) {
228 Some(changelog_files) => merge_files_content(changelog_files),
229 _ => String::new(),
230 }
231 }
232 }
233
234 fn extract_package_name(pkg_name: &str) -> String {
250 if pkg_name.contains('.') {
251 pkg_name.split('.').next().unwrap().to_string()
252 } else {
253 pkg_name.to_string()
254 }
255 }
256
257 fn get_changelog_files<P: AsRef<Path>>(path: P) -> Option<Vec<std::fs::DirEntry>> {
270 let dir = std::fs::read_dir(path).ok()?;
271 Some(dir.flatten().collect())
272 }
273
274 fn merge_files_content(files: Vec<std::fs::DirEntry>) -> String {
291 files
292 .iter()
293 .filter_map(|file| {
294 let path = file.path();
295 if path.is_file() {
296 let file = File::open(&path).ok()?;
297 let mut buffer = BufReader::new(file);
298 let mut content = String::new();
299 buffer.read_to_string(&mut content).ok()?;
300 Some(content)
301 } else {
302 None
303 }
304 })
305 .collect::<String>()
306 }
307
308 pub fn get_package(argument: String, version: Option<String>) -> Package {
319 if SupportedOs::supported_os() == OSFamily::Debian {
320 let repository = Repository { repos: Debian };
321 Request::ask_package(
322 &repository.repos,
323 argument.as_str(),
324 version.unwrap_or_default().as_str(),
325 )
326 } else if SupportedOs::supported_os() == OSFamily::RedHat {
327 let repository = Repository { repos: RedHat };
328 Request::ask_package(
329 &repository.repos,
330 argument.as_str(),
331 version.unwrap_or_default().as_str(),
332 )
333 } else {
334 default_package()
335 }
336 }
337
338 pub fn get_changelog_for(package: &Package) -> String {
348 if SupportedOs::supported_os() == OSFamily::Debian {
349 let repository = Repository { repos: Debian };
350 Request::ask_changelog_package(&repository.repos, package.clone())
351 } else {
352 let repository = Repository { repos: RedHat };
353 Request::ask_changelog_package(&repository.repos, package.clone())
354 }
355 }
356
357 fn get_debian_changelog_for(path: &str, package: &Package) -> Result<File, &'static str> {
378 match package.status {
379 PackageStatus::Uninstalled => get_next_changelog_file(&mut package.clone()),
380 _ => {
381 let changelogs = [
382 format!("{}/{}/changelog.Debian.gz", path, package.name),
383 format!("{}/{}/changelog.gz", path, package.name),
384 ];
385
386 let mut idx = 0usize;
387 let mut result = Err("No alternative changelog");
388 while idx < changelogs.len() && result.is_err() {
389 let changelog = changelogs[idx].clone();
390 idx += 1;
391 result = match File::open(changelog) {
392 Ok(file) => Ok(file),
393 _ => Err("No alternative changelog"),
394 };
395 }
396
397 result
398 }
399 }
400 }
401
402 fn get_next_changelog_file(package: &mut Package) -> Result<File, &'static str> {
429 let current_dir = env::current_dir();
430 let current_dir_clone = env::current_dir();
431 let _ = std::fs::create_dir("/var/tmp/get-cve");
432 env::set_current_dir("/var/tmp/get-cve").expect("Error setting tmp directory");
433
434 println!("Download {package}");
435
436 let command_download = std::process::Command::new("/usr/bin/apt-get")
437 .arg("download")
438 .arg(package.name.as_str())
439 .stderr(Stdio::null())
440 .current_dir("/var/tmp/get-cve")
441 .output()
442 .expect("Error on apt-get command running");
443
444 if command_download.status.success() {
445 println!("Decompress {package}");
446 let package_deb = glob::glob(format!("{}*.deb", package.name).as_str());
447 if let Err(_item) = package_deb {
448 restore_current_dir_and_remove_temp_dir(current_dir, "get-cve");
449 write_error_message_and_exit(
450 "Error, problem on packages into temp \
451 directory",
452 None,
453 );
454 } else {
455 let package_name = package_deb.unwrap().next().unwrap();
456 let command_decompress = std::process::Command::new("/usr/bin/dpkg")
457 .current_dir("/var/tmp/get-cve/")
458 .arg("-x")
459 .arg(package_name.unwrap())
460 .arg(".")
461 .output()
462 .expect("Error on decompress command running");
463
464 if !command_decompress.status.success() {
465 restore_current_dir_and_remove_temp_dir(current_dir_clone, "get-cve");
466 write_error_message_and_exit(
467 "Decompression error",
468 Option::from(
469 String::from_utf8(command_decompress.stderr)
470 .unwrap()
471 .as_str(),
472 ),
473 );
474 } else {
475 println!("Search changelog for {package}");
476 package.status = PackageStatus::Installed;
477 return get_debian_changelog_for("/var/tmp/get-cve/usr/share/doc", package);
478 }
479 }
480 }
481 Err("Error on package download")
482 }
483
484 fn extract_package_result(command_output: Result<String, FromUtf8Error>) -> Package {
501 let output_string = command_output.unwrap();
502 if output_string.contains("upgradable") {
503 let elements = output_string.split("/").collect::<Vec<&str>>();
504 let version_elements = elements[1].split(' ').collect::<Vec<&str>>();
505
506 Package {
507 name: String::from(elements[0]),
508 version: String::from(version_elements[1].trim()),
509 status: PackageStatus::Uninstalled,
510 }
511 } else {
512 let elements: Vec<&str> = output_string.as_str().split('\t').collect();
513 if elements.len() < 2 {
514 write_error_message_and_exit("Package not found", None);
515 default_package()
516 } else {
517 Package {
518 name: String::from(elements[0]),
519 version: String::from(elements[1].trim()),
520 status: PackageStatus::Installed,
521 }
522 }
523 }
524 }
525
526 fn grep_version(version: &str, pipe_package: Child) -> Output {
549 std::process::Command::new("/usr/bin/grep")
550 .arg("-w")
551 .arg(version)
552 .stdin(Stdio::from(pipe_package.stdout.unwrap()))
553 .stderr(Stdio::null())
554 .output()
555 .expect("Error on grep on version")
556 }
557}
558
559trait CvssColor {
560 fn cvss_color(&self) -> (Color, Color);
561}
562
563impl CvssColor for f64 {
564 fn cvss_color(&self) -> (Color, Color) {
565 match *self {
566 0.0 => (Color::Default, Color::Default),
567 value if value > 0.0 && value < 4.0 => {
568 (Color::Rgb(255, 255, 255), Color::Rgb(95, 177, 88))
569 }
570 value if (4.0..7.0).contains(&value) => (Color::Black, Color::Rgb(249, 238, 86)),
571 value if (7.0..9.0).contains(&value) => {
572 (Color::Rgb(255, 255, 255), Color::Rgb(232, 152, 63))
573 }
574 _ => (Color::Rgb(255, 255, 255), Color::Rgb(176, 54, 52)),
575 }
576 }
577}
578
579#[cfg(target_os = "linux")]
580pub fn special_options(args: &[String]) {
581 let mut filter: Option<&String> = None;
582 if let Some(idx_filter) = args.iter().position(|a| a == "--filter") {
583 filter = args.get(idx_filter + 1);
584 }
585
586 let (package_name, mut package_version) = extract_argument(args[0].clone());
587
588 if SupportedOs::supported_os() == OSFamily::Debian && args.iter().any(|a| a == "--next") {
589 package_version = Option::from(String::from("next"));
590 }
591
592 let package = get_package(package_name, package_version);
593 stop_if_invalid_package(&package);
594
595 let changelog_content = get_changelog_for(&package);
596 let changelog_cve_list = filtering_cve(package.cve_list(&changelog_content), filter);
597 if args.iter().any(|a| a == "--long" || a == "-L") {
598 if check_nvd_api_key().is_none() {
599 check_nvd_api_key_warning();
600 }
601
602 let mut cve_list = Vec::new();
603 for cve_value in changelog_cve_list {
604 eprint!(
605 "Search CVE list for package: {package}. It will take a while... {}\r",
606 cve_value.reference
607 );
608 let result = execute_query(build_query(ByCve, cve_value.reference.as_str()));
609 if result != Null {
610 let cve = create_cve_from(&result["vulnerabilities"][0]);
611 cve_list.push(cve);
612 }
613 }
614 show_cves(args, &mut cve_list);
615 } else {
616 show_cve_list(changelog_cve_list, package);
617 }
618}
619
620pub fn common_options(args: &mut [String]) {
621 if args.iter().any(|a| a == "version" || a == "v") {
622 show_version(env!("CARGO_PKG_VERSION"));
623 exit(0);
624 }
625
626 #[cfg(not(target_os = "linux"))]
627 if check_nvd_api_key().is_none() {
628 check_nvd_api_key_warning();
629 }
630
631 if args.iter().any(|a| a == "check" || a == "c") {
632 check_mitre_data(Cve::define());
633 exit(0);
634 }
635
636 if args.iter().any(|a| a == "e" || a == "exploited") {
637 let mut cve_list = standard_or_filtered_list(args, extract_cve_list("", ByCveExploited));
638 show_cves(args, &mut cve_list);
639 exit(0);
640 }
641
642 if let Some(cpe_string) = args
643 .iter()
644 .position(|a| a == "for-cpe" || a == "f")
645 .and_then(|i| args.get(i + 1))
646 {
647 let cpe = Cpe::from(cpe_string);
648 let mut result = run_search_cpe(cpe, args);
649 show_cves(args, &mut result);
650
651 exit(0);
652 } else {
653 no_argument_and_exit!();
654 }
655
656 if let Some(new_option) = args.iter().position(|a| a == "new" || a == "n") {
657 let dates_range = build_dates_range(Some(new_option), args);
658 let mut cve_list =
659 standard_or_filtered_list(args, extract_cve_list(dates_range.as_str(), ByCveNew));
660 show_cves(args, &mut cve_list);
661 exit(0);
662 }
663
664 if let Some(updated_option) = args.iter().position(|a| a == "updated" || a == "u") {
665 let dates_range = build_dates_range(Some(updated_option), args);
666 let mut cve_list =
667 standard_or_filtered_list(args, extract_cve_list(dates_range.as_str(), ByCveUpdated));
668 show_cves(args, &mut cve_list);
669 exit(0);
670 }
671
672 if let Some(cwe_option) = args.iter().position(|a| a == "cwe" || a == "w") {
673 run_search_cwe(args, Some(cwe_option));
674 exit(0);
675 }
676
677 if let Some(search_option) = args.iter().position(|a| a == "search" || a == "s") {
678 let result = run_search_cve_string(args, Some(search_option));
679 let mut values = standard_or_filtered_list(args, result);
680 show_cves(args, &mut values);
681 exit(0);
682 }
683
684 let key_cve = args
685 .iter()
686 .position(|a| a.to_ascii_lowercase().contains("cve"));
687 if let Some(pos) = key_cve
688 && pos == 0
689 {
690 run_search_cve(args, key_cve);
691 exit(0);
692 }
693}
694
695fn standard_or_filtered_list(args: &mut [String], cve_list: Vec<Cve>) -> Vec<Cve> {
696 match args.iter().position(|a| a == "--filter") {
697 Some(pos) => filtering_cve(cve_list, args.get(pos + 1)),
698 _ => cve_list,
699 }
700}
701
702fn show_cves(args: &[String], cve_list: &mut [Cve]) {
703 println!();
704 if !args.iter().any(|a| a == "--long" || a == "-L") {
705 show_cve_values(cve_list);
706 } else {
707 if let Some(pos) = args.iter().position(|a| a == "--sort_by")
708 && let Some(criteria) = args.get(pos + 1)
709 {
710 let mut sort_score_by = String::new();
711 if let Some(sorting_criteria) = args.get(pos + 2) {
712 sort_score_by = sorting_criteria.clone();
713 }
714
715 match criteria.as_str() {
716 "score" => match sort_score_by.as_str() {
717 "v4" => cve_list.sort_by(|a, b| {
718 b.score_v4
719 .clone()
720 .unwrap()
721 .value()
722 .total_cmp(&a.score_v4.clone().unwrap().value())
723 }),
724
725 _ => cve_list.sort_by(|a, b| {
726 b.score_v3
727 .clone()
728 .unwrap()
729 .value()
730 .total_cmp(&a.score_v3.clone().unwrap().value())
731 }),
732 },
733
734 "description" => cve_list.sort_by(|a, b| {
735 a.description
736 .clone()
737 .unwrap()
738 .cmp(&b.description.clone().unwrap())
739 }),
740
741 "cpe" => cve_list
742 .sort_by(|a, b| a.cpe_id.clone().unwrap().cmp(&b.cpe_id.clone().unwrap())),
743 "weaknesses" => cve_list.sort_by(|a, b| {
744 b.weaknesses
745 .clone()
746 .unwrap()
747 .cmp(&a.weaknesses.clone().unwrap())
748 }),
749
750 _ => (),
751 };
752 }
753
754 if args.iter().any(|a| a == "--to_csv") {
755 show_as_csv(cve_list, args.iter().any(|a| a == "with_headers"));
756 } else {
757 show_as_table(cve_list);
758 }
759 }
760}
761
762fn show_as_csv(cve_list: &mut [Cve], with_headers: bool) {
763 if !cve_list.is_empty() {
764 let mut writer = csv::Writer::from_writer(stdout());
765 if with_headers {
766 writer
767 .write_record([
768 "cve",
769 "description",
770 "score v3",
771 "level v3",
772 "score v4",
773 "level v4",
774 "cpe",
775 "weaknesses",
776 ])
777 .expect("Failed to write CSV headers");
778 }
779
780 cve_list.iter_mut().for_each(|cve| {
781 writer
782 .write_record([
783 cve.reference.clone(),
784 cve.description.clone().unwrap(),
785 cve.score_v3.clone().unwrap().value().to_string(),
786 cve.score_v3.clone().unwrap().label().to_string(),
787 cve.score_v4.clone().unwrap().value().to_string(),
788 cve.score_v4.clone().unwrap().label().to_string(),
789 cve.cpe_id.clone().unwrap(),
790 cve.weaknesses.clone().unwrap(),
791 ])
792 .expect("Failed to write CSV content");
793 });
794
795 writer.flush().expect("Failed to flush CSV writer");
796 }
797}
798
799fn show_cve_values(cve_list: &[Cve]) {
800 cve_list.iter().for_each(|c| println!("{c}"));
801}
802
803fn run_search_cve_string(args: &mut [String], position: Option<usize>) -> Vec<Cve> {
804 let result = match args.iter().position(|a| a == "--strict" || a == "-S") {
805 Some(pos) => {
806 let values = args.get(pos + 1);
807 if let Some(item) = values {
808 execute_query(build_query(QueryType::ByCveSearchStrict, item.as_str()))
809 } else {
810 write_error_message_and_exit("Missing criteria", None);
811 Null
812 }
813 }
814
815 _ => match args.get(position.unwrap() + 1) {
816 Some(values) => {
817 execute_query(build_query(QueryType::ByCveSearchString, values.as_str()))
818 }
819 _ => {
820 write_error_message_and_exit("Missing criteria", None);
821 Null
822 }
823 },
824 };
825 let mut cves = cve_from_new_query(&result);
826 simplify_cve_list_content(&mut cves);
827 cves
828}
829
830#[cfg(target_os = "linux")]
848fn extract_argument(argument: String) -> (String, Option<String>) {
849 let package_name: String;
850 let mut package_version: Option<String> = None;
851
852 if argument.contains("=") {
853 let component: Vec<&str> = argument.split("=").collect();
854 package_name = String::from(component[0]);
855 package_version = Some(String::from(component[1]));
856 } else {
857 package_name = argument.clone();
858 }
859
860 (package_name, package_version)
861}
862
863fn extract_cve_list(criteria: &str, cve_type: QueryType) -> Vec<Cve> {
882 let result = execute_query(build_query(cve_type.clone(), criteria));
883 if !result.is_null() && result["resultsPerPage"] != 0 {
884 let mut start_index = result["startIndex"].clone().as_u64().unwrap();
885 let total_results = result["totalResults"].clone().as_u64().unwrap();
886
887 let mut cve_list = cve_from_new_query(&result);
888 start_index += cve_list.len() as u64;
889 while cve_list.len() < (total_results as usize) {
890 let query = format!(
891 "{}&startIndex={}",
892 build_query(cve_type.clone(), criteria).unwrap(),
893 start_index
894 );
895 let result = execute_query(Ok(query));
896 let mut cves = cve_from_new_query(&result);
897 start_index += cves.len() as u64;
898 cve_list.append(&mut cves);
899 }
900
901 simplify_cve_list_content(&mut cve_list);
902 return cve_list;
903 } else {
904 write_error_message_and_exit("No result found", None)
905 }
906
907 Vec::new()
908}
909
910fn show_as_table(cve_list: &[Cve]) {
911 let mut table = AsciiTable::new("CVE list");
912
913 table.set_headers(vec![
914 "CVE",
915 "Description",
916 "Score v3",
917 "Score v4",
918 "CPE",
919 "Weaknesses",
920 ]);
921
922 cve_list.iter().for_each(|cve| {
923 let description = cve.description.clone().unwrap_or(String::from("None"));
924 let cpe_id = cve.cpe_id.clone().unwrap_or(String::from("None"));
925 let weaknesses = cve.weaknesses.clone().unwrap_or(String::from("None"));
926
927 let score_v3 = format_score(cve.score_v3.clone().unwrap());
928 let score_v4 = format_score(cve.score_v4.clone().unwrap());
929
930 table.add_row(vec![
931 CellValue::Str(cve.reference.clone()),
932 CellValue::Str(description.replace("\n", " ").reduce(70)),
933 CellValue::Str(score_v3.to_string()),
934 CellValue::Str(score_v4.to_string()),
935 CellValue::Str(cpe_id.reduce(40)),
936 CellValue::Str(weaknesses.reduce(15)),
937 ])
938 });
939
940 table.render()
941}
942
943fn format_score(cve_score: Score) -> String {
944 let (color_fg, color_bg) = cve_score.value().cvss_color();
945 format!(
946 "{:^18}",
947 format!(
948 " {} - {} ",
949 format!("{0:.1}", cve_score.value()),
950 cve_score.label()
951 )
952 )
953 .fg(color_fg)
954 .bg(color_bg)
955 .to_string()
956}
957
958fn run_search_cpe(cpe: Cpe, args: &mut [String]) -> Vec<Cve> {
974 if let Some(new_date_option) = args.iter().position(|a| a == "--new") {
975 let dates = build_dates_range(Some(new_date_option), args);
976 let query_dates = create_query_dates(ByCveNew, dates.as_str());
977 let cpe_query = build_query(ByCveCpeName, cpe.name().as_str());
978 standard_or_filtered_list(
979 args,
980 extract_cve_list(
981 format!("{}&{}", cpe_query.unwrap(), query_dates).as_str(),
982 ByCveCpeName,
983 ),
984 )
985 } else if let Some(updated_date_option) = args.iter().position(|a| a == "--updated") {
986 let dates = build_dates_range(Some(updated_date_option), args);
987 let query_dates = create_query_dates(ByCveUpdated, dates.as_str());
988 let cpe_query = build_query(ByCveCpeName, cpe.name().as_str());
989 standard_or_filtered_list(
990 args,
991 extract_cve_list(
992 format!("{}&{}", cpe_query.unwrap(), query_dates).as_str(),
993 ByCveCpeName,
994 ),
995 )
996 } else if args.iter().any(|a| a == "--vul" || a == "-v") {
997 standard_or_filtered_list(args, extract_cve_list(cpe.name().as_str(), ByCpeVulnerable))
998 } else {
999 standard_or_filtered_list(args, extract_cve_list(cpe.name().as_str(), ByCveCpeName))
1000 }
1001}
1002
1003fn run_search_cve(args: &mut [String], key_cve: Option<usize>) {
1027 if let Some(cve_value) = args.get(key_cve.unwrap()) {
1028 let result = if cve_value == &String::from("--cpe") {
1029 println!("Searching...");
1030 execute_query(build_query(ByCveCpeName, args[2].as_str()))
1031 } else {
1032 execute_query(build_query(ByCve, cve_value.as_str()))
1033 };
1034
1035 if args.iter().any(|a| a == "--long" || a == "-L") {
1036 let mut cve = vec![create_cve_from(&result["vulnerabilities"][0])];
1037 if args.iter().any(|a| a == "--to_csv") {
1038 show_as_csv(&mut cve, args.iter().any(|a| a == "with_headers"));
1039 } else {
1040 show_as_table(&cve);
1041 }
1042 return;
1043 }
1044
1045 if result["resultsPerPage"] == 0 || result == Null {
1046 write_error_message_and_exit("CVE not found", None);
1047 }
1048
1049 if args.iter().any(|a| a == "--schema" || a == "-S") {
1050 show_schema(&result);
1051 return;
1052 }
1053
1054 println!("{}", header!(cve_value));
1055 if cve_value == &String::from("--cpe") {
1056 cve_from_new_query(&result).iter().for_each(print_cve);
1057 return;
1058 }
1059
1060 let mut cumulative_args = 0;
1061
1062 if args.iter().any(|a| a == "--score" || a == "-s") {
1063 let include_data = args.iter().any(|a| a == "--data" || a == "-D");
1064 if include_data {
1065 cumulative_args += 1;
1066 }
1067 if args.iter().any(|a| a == "v3") {
1068 CveDisplayType::ByScores.show(&result, Some("v3"), Some(include_data));
1069 cumulative_args += 1;
1070 } else if args.iter().any(|a| a == "v4") {
1071 CveDisplayType::ByScores.show(&result, Some("v4"), Some(include_data));
1072 cumulative_args += 1;
1073 } else {
1074 CveDisplayType::ByScores.show(&result, Some("all"), Some(include_data));
1075 cumulative_args += 1;
1076 }
1077 }
1078
1079 if args.iter().any(|a| a == "--cwe" || a == "-w") {
1080 CveDisplayType::ByCwe.show(&result, None, None);
1081 cumulative_args += 1;
1082 }
1083
1084 if args.iter().any(|a| a == "--lang" || a == "-l") {
1085 CveDisplayType::ByLanguages.show(&result, None, None);
1086 cumulative_args += 1;
1087 }
1088
1089 if let Some(key_description) = args.iter().position(|a| a == "--desc" || a == "-d") {
1090 let key_lang = args.get(key_description + 1).map(|lang| lang.as_str());
1091
1092 CveDisplayType::ByDescription.show(&result, key_lang, None);
1093 cumulative_args += 1;
1094 }
1095
1096 if args.iter().any(|a| a == "--doc" || a == "-K") {
1097 CveDisplayType::ByDocumentations.show(&result, None, None);
1098 cumulative_args += 1;
1099 }
1100
1101 if args.iter().any(|a| a == "--cpe" || a == "-f") {
1102 CveDisplayType::ByCpe.show(&result, None, None);
1103 cumulative_args += 1;
1104 }
1105
1106 if cumulative_args == 0 {
1107 CveDisplayType::All.show(&result, Some("all"), Some(true));
1108 }
1109 }
1110}
1111
1112fn show_schema(result: &Value) {
1113 let cve = create_cve_from(&result["vulnerabilities"][0]);
1114
1115 let mut builder = TreeBuilder::new();
1116 let cve_node = builder.node(section!(cve.reference, Color::Green).to_string());
1117
1118 if let Some(cwe_list) = cve.weaknesses {
1119 let cwe_id = parse_id_to_u32(
1120 cwe_list
1121 .replace("CWE-", "")
1122 .replace(", ", FIELD_SEPARATOR_STRING),
1123 );
1124 cwe_id.iter().for_each(|id| {
1125 if let Some(cwe) = find_cwe_by_id(*id) {
1126 let cwe_node = cve_node.node(
1127 format!("CWE-{} - {}", cwe.id, cwe.name)
1128 .fg(Color::Red)
1129 .to_string(),
1130 );
1131
1132 let capec_id = parse_id_to_u32(
1133 cwe.clone()
1134 .attacks
1135 .replace("CAPEC-", "")
1136 .replace(", ", FIELD_SEPARATOR_STRING),
1137 );
1138 if !capec_id.is_empty() {
1139 capec_id.iter().for_each(|ca_id| {
1140 if let Some(capec) = find_capec_by_id(*ca_id) {
1141 cwe_node.leaf(
1142 format!("CAPEC-{} - {}", capec.id, capec.name)
1143 .fg(Color::DarkCyan)
1144 .to_string(),
1145 );
1146 }
1147 });
1148 }
1149 cwe_node.end();
1150 }
1151 });
1152 } else {
1153 write_error_message_and_exit("No schemas found", None);
1154 }
1155
1156 let tree = builder.build();
1157 println!("{}", tree.render_to_string());
1158}
1159
1160fn show_data(result: &Value, level: &str) {
1161 let cvss_data = match level {
1162 "v3" => get_cvss3_data(result),
1163 "v4" => get_cvss4_data(result),
1164 _ => Null,
1165 };
1166
1167 if !cvss_data.is_null() {
1168 let map = cvss_data.as_object().unwrap();
1169 let filtered = map
1170 .iter()
1171 .filter(|(k, _)| **k != "baseScore" && **k != "baseSeverity" && **k != "version");
1172 filtered.for_each(|(k, v)| {
1173 let str_value = match k.to_string().contains("vectorString") {
1174 false => v.to_string().wording().replace("_", " "),
1175 true => v.to_string(),
1176 };
1177 println!(
1178 "{:>7} {}: {}",
1179 "-",
1180 k.wording(),
1181 String::cleaning(str_value)
1182 );
1183 });
1184 } else {
1185 write_error_message("CVSS data not found", Some(level));
1186 }
1187}
1188
1189pub fn show_languages(result: &Value) {
1223 if let Some(descriptions) = get_descriptions_section(result) {
1224 section_level!(2, "Languages");
1225 for l in descriptions {
1226 print_values(&[l["lang"].clone()]);
1227 }
1228 }
1229}
1230
1231pub fn show_description(result: &Value, key_lang: Option<&String>) {
1270 section_level!(2, "Description");
1271
1272 let description = if let Some(lang) = key_lang {
1273 extract_description(result, Some(lang.as_str()))
1274 } else {
1275 extract_description(result, None)
1276 };
1277 print_values(&[description]);
1278 println!();
1279}
1280
1281fn show_documentations(result: &Value) {
1282 if let Some(documentations) = get_documentation_section(result) {
1283 section_level!(2, "Documentations");
1284 documentations.iter().for_each(|doc| {
1285 print_values(std::slice::from_ref(doc));
1286 if doc != documentations.last().unwrap() {
1287 println!();
1288 }
1289 });
1290 } else {
1291 write_error_message_and_exit("Documentation not found", None);
1292 }
1293}
1294
1295fn extract_description(value: &Value, lang: Option<&str>) -> Value {
1310 if let Some(lang) = lang
1311 && let Some(languages) = get_descriptions_section(value)
1312 {
1313 for l in languages {
1314 if l["lang"].as_str() == Some(lang) {
1315 return l["value"].clone();
1316 }
1317 }
1318 }
1319 extract_default_description(value)
1320}
1321
1322fn extract_default_description(value: &Value) -> Value {
1334 value["vulnerabilities"][0]["cve"]["descriptions"][0]["value"].clone()
1335}
1336
1337fn get_descriptions_section(value: &Value) -> Option<&Vec<Value>> {
1349 value["vulnerabilities"][0]["cve"]["descriptions"].as_array()
1350}
1351
1352fn get_documentation_section(value: &Value) -> Option<&Vec<Value>> {
1361 value["vulnerabilities"][0]["cve"]["references"].as_array()
1362}
1363
1364pub fn run_search_cwe(args: &mut [String], cwe_option: Option<usize>) {
1406 if let Some(cwe_value) = args.get(cwe_option.unwrap() + 1) {
1407 let result = execute_query(build_query(ByCwe, cwe_value.as_str()));
1408 let mut cve_list = standard_or_filtered_list(args, cve_from_cwe(&result));
1409 simplify_cve_list_content(&mut cve_list);
1410 if cve_list.is_empty() {
1411 write_error_message_and_exit("CVE not found", None);
1412 }
1413 show_cves(args, &mut cve_list);
1414 }
1415}
1416
1417enum CveDisplayType {
1418 All,
1419 ByCpe,
1420 ByCwe,
1421 ByData,
1422 ByDescription,
1423 ByDocumentations,
1424 ByLanguages,
1425 ByScores,
1426}
1427
1428impl CveDisplayType {
1429 fn show(&self, value: &Value, argument: Option<&str>, flag: Option<bool>) {
1430 match self {
1431 CveDisplayType::All => {
1432 show_description(value, None);
1433 show_scores(value, argument.unwrap().trim(), flag.unwrap_or(false));
1434 show_documentations(value);
1435 println!();
1436 show_cwe_values(value);
1437 println!();
1438 show_cpe_values(value);
1439 }
1440
1441 CveDisplayType::ByCpe => {
1442 show_cpe_values(value);
1443 }
1444
1445 CveDisplayType::ByCwe => {
1446 show_cwe_values(value);
1447 }
1448
1449 CveDisplayType::ByData => {
1450 show_data(value, argument.unwrap().trim());
1451 }
1452
1453 CveDisplayType::ByDescription => {
1454 show_description(value, None);
1455 }
1456
1457 CveDisplayType::ByDocumentations => {
1458 show_documentations(value);
1459 }
1460
1461 CveDisplayType::ByLanguages => {
1462 show_languages(value);
1463 }
1464
1465 CveDisplayType::ByScores => {
1466 show_scores(value, argument.unwrap().trim(), flag.unwrap_or(false));
1467 }
1468 }
1469 }
1470}
1471
1472fn show_cpe_values(value: &Value) {
1473 section_level!(2, "CPE");
1474 let values = extract_cpe_id_list(value);
1475 if let Some(values) = values {
1476 values.iter().for_each(|v| {
1477 let mut result = v.as_object().unwrap().clone();
1478 result.remove("matchCriteriaId");
1479 print_values(&[Value::Object(result)]);
1480 println!();
1481 })
1482 } else {
1483 println!("None");
1484 }
1485}
1486
1487fn extract_cpe_id_list(value: &Value) -> Option<&Vec<Value>> {
1488 value["vulnerabilities"][0]["cve"]["configurations"][0]["nodes"][0]["cpeMatch"].as_array()
1489}
1490
1491pub fn show_cwe_values(result: &Value) {
1528 let values = extract_cwe(result);
1529 if !values.is_empty() {
1530 let slice_values: Vec<_> = values
1531 .iter()
1532 .map(|v| String::cleaning(v.to_string()))
1533 .collect();
1534 let mut str_values = slice_values.join(FIELD_SEPARATOR_STRING_LONG);
1535 str_values = str_values.replace("CWE-", FIELD_SEPARATOR_STRING_LONG);
1536 show_weaknesses("weaknesses", str_values);
1537 } else {
1538 println!("None");
1539 }
1540}
1541
1542fn extract_cwe(value: &Value) -> Vec<Value> {
1557 let mut result: Vec<Value> = vec![];
1558 let binding = value["vulnerabilities"][0]["cve"]["weaknesses"].clone();
1559 let elements_option = binding.as_array();
1560 if let Some(elements) = elements_option {
1561 elements.iter().for_each(|value| {
1562 let vals = value["description"].as_array().unwrap();
1563 vals.iter()
1564 .for_each(|val| result.push(val["value"].clone()))
1565 });
1566 }
1567
1568 result.dedup();
1569
1570 result
1571}
1572
1573pub fn show_scores(result: &Value, level: &str, include_data: bool) {
1627 if level != "all" {
1628 let cvss_data = match level {
1629 "v3" => get_cvss3_data(result),
1630 "v4" => get_cvss4_data(result),
1631 _ => Null,
1632 };
1633
1634 section_level!(2, "Scores");
1635 if !cvss_data.is_null() {
1636 let score = cvss_data["baseScore"].as_f64().unwrap();
1637 let (color_fg, color_bg) = score.cvss_color();
1638 println!(
1639 "{:>6} Score {}: {:.1} - {}",
1640 "-",
1641 level,
1642 score,
1643 format!(
1644 " {} ",
1645 String::cleaning(cvss_data["baseSeverity"].to_string())
1646 )
1647 .fg(color_fg)
1648 .bg(color_bg),
1649 );
1650 println!();
1651
1652 if include_data {
1653 section_level!(3, "Data");
1654 CveDisplayType::ByData.show(result, Some(level), None);
1655 println!();
1656 }
1657 } else {
1658 write_error_message("Score not found", Some(level));
1659 println!();
1660 }
1661 } else if level == "all" {
1662 show_scores(result, "v3", include_data);
1663 show_scores(result, "v4", include_data);
1664 } else {
1665 write_error_message("Score type not found", Some(level));
1666 }
1667}
1668
1669fn get_cvss4_data(result: &Value) -> Value {
1689 get_cvss_scoring(result, "V40")
1690}
1691
1692fn get_cvss3_data(result: &Value) -> Value {
1733 get_cvss_scoring(result, "V31")
1734}
1735
1736fn get_cvss_scoring(result: &Value, level: &str) -> Value {
1746 result["vulnerabilities"][0]["cve"]["metrics"][format!("cvssMetric{}", level)][0]["cvssData"]
1747 .clone()
1748}
1749
1750pub fn cve_from_new_query(values: &Value) -> Vec<Cve> {
1787 if let Some(vulnerabilities) = values["vulnerabilities"].as_array() {
1788 let mut results: Vec<Cve> = vec![];
1789 vulnerabilities
1790 .iter()
1791 .for_each(|v| results.push(create_cve_from(v)));
1792
1793 results
1794 } else {
1795 vec![]
1796 }
1797}
1798
1799#[cfg(test)]
1800mod tests {
1801 use crate::{extract_cwe, extract_description};
1802 use fenir::cve::{create_cve_from, cve_from_cwe};
1803 use fenir::database::execute_query;
1804 use fenir::query::QueryType::{ByCve, ByCwe};
1805 use fenir::query::build_query;
1806 use serde_json::Value;
1807 use std::thread::sleep;
1808 use std::time::Duration;
1809 use utmt::assert_all;
1810
1811 fn setup() {
1812 let waiting = Duration::from_millis(30);
1813 sleep(waiting);
1814 }
1815
1816 #[test]
1817 fn extract_description_default_lang_valid() {
1818 setup();
1819
1820 let result = execute_query(build_query(ByCve, "CVE-2024-6387"));
1821
1822 assert_default_description(&result, None);
1823 }
1824
1825 fn assert_default_description(result: &Value, option: Option<&str>) {
1826 assert!(
1827 extract_description(&result, option)
1828 .to_string()
1829 .contains("A security regression (CVE-2006-5051) was discovered ")
1830 );
1831 }
1832
1833 #[test]
1834 fn extract_description_invalid_default() {
1835 setup();
1836
1837 let result = execute_query(build_query(ByCve, "CVE-2024-6387"));
1838
1839 assert_default_description(&result, Option::from("ff"));
1840 }
1841
1842 #[test]
1843 fn extract_description_es_lang_valid() {
1844 setup();
1845
1846 let result = execute_query(build_query(ByCve, "CVE-2024-6387"));
1847
1848 assert!(extract_description(&result, Option::from("es")).to_string().contains("Se encontró una condición de ejecución del controlador de señales en el servidor de OpenSSH"));
1849 }
1850
1851 #[test]
1852 fn extract_cwe_valid_cve_non_empty() {
1853 setup();
1854
1855 let result = execute_query(build_query(ByCve, "CVE-2024-6387"));
1856
1857 assert!(!extract_cwe(&result).is_empty());
1858 }
1859
1860 #[test]
1861 fn create_cve_from_value() {
1862 setup();
1863
1864 let cve = create_cve_from(&execute_query(build_query(ByCve, "CVE-2024-6387")));
1865
1866 assert_all!(
1867 cve.cpe_id.is_some(),
1868 cve.description.is_some(),
1869 cve.score_v3.is_some(),
1870 !cve.reference.is_empty(),
1871 cve.weaknesses.is_some()
1872 );
1873 }
1874
1875 #[test]
1876 fn extract_cwe_invalid_cve_empty() {
1877 setup();
1878
1879 let result = execute_query(build_query(ByCve, "CVE-1970-0000"));
1880
1881 assert!(extract_cwe(&result).is_empty());
1882 }
1883
1884 #[test]
1885 fn cve_from_cwe_result_valid_cwe_not_empty() {
1886 setup();
1887
1888 let result = execute_query(build_query(ByCwe, "CWE-287"));
1889
1890 assert!(!cve_from_cwe(&result).is_empty());
1891 }
1892}