1use fenir::database::{db_mitre_path, text_or_null, MitreData, MitreDatabase, MitreDefinition};
2use fenir::facilities::{ProgressText, Uppercase, Wording};
3use fenir::mitre::MitreRecord;
4use fenir::package::errors::write_error_message;
5use fenir::{db_mitre, flow, header, section, section_level, stamp, value};
6use regex::Regex;
7use rusqlite::fallible_iterator::FallibleIterator;
8use rusqlite::fallible_streaming_iterator::FallibleStreamingIterator;
9use rusqlite::types::Value;
10use rusqlite::Error::QueryReturnedNoRows;
11use rusqlite::{params, Connection, Row};
12use std::borrow::Cow;
13use std::collections::{BTreeSet, HashSet};
14use std::error::Error;
15use std::path::Path;
16use termint::enums::{Color, Modifier};
17use termint::widgets::ToSpan;
18use umya_spreadsheet::{reader, Worksheet};
19
20pub struct MitreEnterprise;
21impl MitreDefinition for MitreEnterprise {
22 fn define() -> MitreData {
23 MitreData {
24 name: "Enterprise",
25 url: "https://attack.mitre.org/docs/attack-excel-files/v18.1/enterprise-attack",
26 files: vec!["enterprise-attack-v18.1.xlsx"],
27 }
28 }
29}
30
31pub struct MitreIcs;
32impl MitreDefinition for MitreIcs {
33 fn define() -> MitreData {
34 MitreData {
35 name: "ICS",
36 url: "https://attack.mitre.org/docs/attack-excel-files/v18.1/ics-attack",
37 files: vec!["ics-attack-v18.1.xlsx"],
38 }
39 }
40}
41
42pub struct MitreMobile;
43impl MitreDefinition for MitreMobile {
44 fn define() -> MitreData {
45 MitreData {
46 name: "Mobile",
47 url: "https://attack.mitre.org/docs/attack-excel-files/v18.1/mobile-attack",
48 files: vec!["mobile-attack-v18.1.xlsx"],
49 }
50 }
51}
52
53macro_rules! body {
54 ($value:expr, $($with:ident),*) => {
55 $($with!($value); println!(); )*
56 };
57 }
58
59macro_rules! citations {
60 ($value:expr) => {{
61 show!(flow, "Citations", $value.citations);
62 }};
63}
64
65macro_rules! contributors {
66 ($value:expr) => {{ contributors!(",", $value) }};
67
68 ($sep:expr, $value:expr) => {{
69 show!(flow, "Contributors", $sep, $value.contributors);
70 }};
71}
72
73#[macro_export]
74macro_rules! create_structure {
75 ($name:ident, $($field:ident),*) => {
76 create_structure!($name, $($field: String),*);
77 };
78
79 ($name:ident, $($field:ident: $type:ty),*) => {
80 #[derive(Clone)]
81 pub struct $name {
82 $($field: $type),*
83 }
84 };
85 }
86
87macro_rules! description {
88 ($value:expr) => {{ show!(value, "Description", $value.description) }};
89}
90
91#[macro_export]
92macro_rules! domains {
93 ($value:expr) => {
94 section_level!(3, "Domains");
95 $value.domain.split(",").for_each(|d| {
96 value!(simplify_domain(d.trim().to_string()));
97 })
98 };
99}
100
101#[macro_export]
102macro_rules! mitre_structure {
103 ($name:ident) => {
104 create_structure!($name, id, name, url, created, modified, domain, version);
105 };
106
107 ($name:ident, $($field:ident),*) => {
108 create_structure!($name, id, name, url, created, modified, domain, version, $($field),*);
109 };
110 }
111
112macro_rules! platforms {
113 ($value:expr) => {{ show!(flow, "Platforms", $value.platforms) }};
114}
115
116macro_rules! prepare_query {
117 ($name:expr) => {
118 prepare_query!($name, $name)
119 };
120
121 ($name:expr, $source_type:expr) => {
122 format!("SELECT * FROM {} WHERE id IN (SELECT source_id FROM relationship WHERE target_id = ? AND source_type = '{}')", $name, $source_type).as_str()
123 };
124 }
125
126macro_rules! sectors {
127 ($value:expr) => {{ show!(flow, "Sectors", $value.sectors) }};
128}
129
130macro_rules! show {
131 ($with:ident, $title:expr, $($value:expr),*) => {
132 {
133 section_level!(3, $title);
134 $with!($($value),*);
135 }
136 };
137 }
138
139macro_rules! summary {
140 ($title:expr, $value:expr) => {{
141 println!(
142 "{:>1} {}",
143 "",
144 section!(format!("{} - {}", $title, $value.id), Color::DarkCyan)
145 );
146
147 stamp!($value.name);
148 stamp!($value.url);
149 stamp!($value.created);
150 stamp!($value.modified);
151 stamp!($value.version)
152 }};
153}
154
155macro_rules! unknown {
156 () => {
157 String::from("Unknown")
158 };
159}
160
161mitre_structure!(Analytic, description);
162
163mitre_structure!(
164 Asset,
165 description,
166 platforms,
167 sectors,
168 related_assets,
169 related_assets_sectors,
170 related_assets_descriptions,
171 citations
172);
173
174mitre_structure!(
175 Campaign,
176 description,
177 associated_campaigns,
178 associated_campaigns_citations,
179 first_seen,
180 first_seen_citations,
181 last_seen,
182 last_seen_citations,
183 contributors,
184 citations
185);
186
187create_structure!(Citation, name, citations, url);
188
189mitre_structure!(DetectionStrategy);
190
191mitre_structure!(
192 Group,
193 description,
194 contributors,
195 associated_groups,
196 associated_groups_citations,
197 citations
198);
199
200mitre_structure!(Mitigation, description, citations);
201
202create_structure!(
203 RelationShip,
204 source_id,
205 source_type,
206 mapping_type,
207 target_id,
208 target_type,
209 description,
210 created,
211 modified
212);
213
214mitre_structure!(
215 Software,
216 description,
217 contributors,
218 platforms,
219 aliases,
220 types,
221 citations
222);
223
224mitre_structure!(Tactic, description);
225
226#[derive(Debug)]
227pub enum ExtendedDisplayType {
228 Analytics,
230
231 Assets,
233
234 Campaigns,
236
237 Citations,
239
240 Groups,
242
243 Detectionstrategy,
245
246 Mitigations,
248
249 Relationship,
251
252 Softwares,
254
255 Tactics,
257}
258
259impl ExtendedDisplayType {
260 pub fn show(&self, mitre_record: &MitreRecord) {
261 println!("{}", header!(format!("MITRE - {}", mitre_record.id)));
262 match self {
263 ExtendedDisplayType::Analytics => {
264 match find_mitre_detectionstrategy(mitre_record.id.as_str()) {
265 Ok(detectionstrategies) => detectionstrategies.iter().for_each(|ds| {
266 let results = find_mitre_analytics(ds.id.as_str());
267 if let Ok(analytics) = results {
268 analytics.iter().for_each(|a| {
269 summary!("Analytics", a);
270
271 println!();
272
273 body!(a, domains, description);
274 })
275 } else {
276 write_error_message(results.err().unwrap().to_string().as_str(), None);
277 }
278 }),
279
280 Err(error) => write_error_message(error.to_string().as_str(), None),
281 }
282 }
283
284 ExtendedDisplayType::Assets => {
285 match find_mitre_assets(mitre_record.id.as_str()) {
286 Ok(rows) => rows.iter().for_each(|a| {
287 summary!("Assets", a);
288
289 println!();
290
291 body!(a, domains, platforms, sectors, description);
292
293 section_level!(3, "Related assets");
294 flow!(";", a.related_assets);
295
296 println!();
297
298 section_level!(3, "Related Assets Sectors");
299 flow!(";", a.related_assets_sectors);
300
301 println!();
302
303 section_level!(3, "Related Assets Descriptions");
304 flow!(a.related_assets_descriptions);
305
306 println!();
307
308 citations!(a);
309
310 println!();
311 }),
312 Err(_) => write_error_message("Assets not found", None),
313 };
314 }
315
316 ExtendedDisplayType::Campaigns => {
317 match find_mitre_campaigns(mitre_record.id.as_str()) {
318 Ok(campaigns) => {
319 campaigns.iter().for_each(|c| {
320 summary!("Campaigns", c);
321
322 println!();
323
324 body!(c, domains, description);
325
326 show!(flow, "Associated campaigns", c.associated_campaigns);
327 show_list(c.associated_campaigns_citations.as_str(), "Citations");
328
329 println!();
330
331 section_level!(3, "First seen");
332 stamp!("Date", c.first_seen);
333 show_list(c.first_seen_citations.as_str(), "Citations");
334
335 println!();
336
337 section_level!(3, "Last seen");
338 stamp!("Date", c.last_seen);
339 show_list(c.last_seen_citations.as_str(), "Citations");
340
341 println!();
342
343 body!(c, contributors, citations);
344 });
345 }
346
347 Err(error) => write_error_message(error.to_string().as_str(), None),
348 };
349 }
350
351 ExtendedDisplayType::Citations => {
352 section_level!("Citations");
353
354 mitre_record.citations.split(",").for_each(|item| {
355 let citation = find_mitre_citation(item);
356 match citation {
357 Ok(citation) => {
358 println!("{:>4} {}:", "-", citation.name);
359
360 stamp!(citation.url);
361 stamp!("List", citation.citations);
362
363 println!();
364 }
365 Err(err) => write_error_message(err.to_string().as_str(), None),
366 }
367 });
368 }
369
370 ExtendedDisplayType::Detectionstrategy => {
371 match find_mitre_detectionstrategy(mitre_record.id.as_str()) {
372 Ok(value) => {
373 value.iter().for_each(|val| {
374 summary!("Detection Strategies", val);
375
376 println!();
377
378 domains!(val);
379
380 println!();
381
382 section_level!(3, "Analytics");
383 let analytics = find_mitre_analytics(val.id.as_str());
384 if let Ok(value) = analytics {
385 value.iter().for_each(|val| {
386 stamp!(val.id, val.url);
387 })
388 }
389 });
390 }
391 Err(error) => write_error_message(error.to_string().as_str(), None),
392 }
393 }
394
395 ExtendedDisplayType::Groups => match find_mitre_groups(mitre_record.id.as_str()) {
396 Ok(results) => {
397 results.iter().for_each(|group| {
398 summary!("Groups", group);
399
400 println!();
401
402 body!(group, domains, description);
403
404 show!(flow, "Associated Groups", group.associated_groups);
405
406 println!();
407
408 show!(
409 flow,
410 "Associated Groups Citations",
411 group.associated_groups_citations
412 );
413
414 println!();
415
416 citations!(group);
417
418 println!();
419
420 contributors!(";", group);
421
422 println!();
423 });
424 }
425 Err(err) => write_error_message(err.to_string().as_str(), None),
426 },
427
428 ExtendedDisplayType::Mitigations => {
429 match find_mitre_mitigations(mitre_record.id.as_str()) {
430 Ok(rows) => {
431 rows.iter().for_each(|row| {
432 summary!("Mitigations", row);
433
434 println!();
435
436 body!(row, domains, description, citations);
437 });
438 }
439 Err(error) => write_error_message(error.to_string().as_str(), None),
440 };
441 }
442
443 ExtendedDisplayType::Relationship => {
444 section_level!("Relationships by target");
445 match find_mitre_relationship(mitre_record.id.as_str(), true) {
446 Ok(rows) => rows.iter().for_each(|row| {
447 section_level!(2, row.source_type);
448
449 stamp!(row.source_id);
450 stamp!(row.mapping_type);
451 stamp!(row.target_type);
452 stamp!(row.created);
453 stamp!(row.modified);
454
455 println!();
456
457 description!(row);
458
459 println!();
460 }),
461
462 Err(_) => write_error_message("No relationship found", None),
463 };
464
465 section_level!("Relationships by source");
466 match find_mitre_relationship(mitre_record.id.as_str(), false) {
467 Ok(rows) => rows.iter().for_each(|row| {
468 section_level!(2, row.target_type);
469
470 stamp!(row.target_id);
471 stamp!(row.mapping_type);
472 stamp!(row.source_type);
473 stamp!(row.created);
474 stamp!(row.modified);
475
476 println!();
477
478 description!(row);
479
480 println!();
481 }),
482
483 Err(_) => write_error_message("No relationship found", None),
484 };
485 }
486
487 ExtendedDisplayType::Softwares => {
488 match find_mitre_softwares(mitre_record.id.as_str()) {
489 Ok(software) => {
490 software.iter().for_each(|soft| {
491 summary!("Softwares", soft);
492 stamp!("Type", soft.types);
493
494 println!();
495
496 body!(soft, domains, description);
497
498 show!(flow, "Platforms", soft.platforms);
499
500 println!();
501
502 show!(flow, "Aliases", soft.aliases);
503
504 println!();
505
506 body!(soft, citations, contributors);
507 });
508 }
509 Err(error) => write_error_message(error.to_string().as_str(), None),
510 };
511 }
512
513 ExtendedDisplayType::Tactics => mitre_record.tactics.split(",").for_each(|item| {
514 match find_mitre_tactics(item, mitre_record.domain.as_str()) {
515 Ok(value) => {
516 summary!("Tactics", value);
517
518 println!();
519
520 body!(value, domains, description);
521 }
522 Err(err) => write_error_message(err.to_string().as_str(), None),
523 };
524 }),
525 };
526 }
527}
528
529fn find_mitre_assets(mitre_id: &str) -> Result<Vec<Asset>, rusqlite::Error> {
530 let connection = db_mitre!()?;
531 let statement = connection.prepare(
532 "SELECT * FROM asset WHERE id IN (SELECT target_id FROM relationship WHERE source_id = ?)",
533 );
534 if let Ok(mut statement) = statement {
535 let results = statement.query(params![mitre_id]);
536 if let Ok(mut results) = results {
537 let mut assets = Vec::new();
538 while let Some(row) = results.next()? {
539 assets.push(Asset {
540 id: row.get(0)?,
541 name: row.get(1)?,
542 description: row.get(2)?,
543 url: row.get(3)?,
544 created: row.get(4)?,
545 modified: row.get(5)?,
546 domain: row.get(6)?,
547 version: row.get(7)?,
548 platforms: row.get(8)?,
549 sectors: row.get(9).unwrap_or(unknown!()),
550 related_assets: row.get(10).unwrap_or(unknown!()),
551 related_assets_sectors: row.get(11).unwrap_or(unknown!()),
552 related_assets_descriptions: row.get(12).unwrap_or(unknown!()),
553 citations: row.get(13).unwrap_or(unknown!()),
554 });
555 }
556
557 Ok(assets)
558 } else {
559 Err(results.err().unwrap())
560 }
561 } else {
562 Err(statement.err().unwrap())
563 }
564}
565
566fn find_mitre_mitigations(mitre_id: &str) -> Result<Vec<Mitigation>, rusqlite::Error> {
567 let connection = db_mitre!()?;
568 let statement = connection.prepare(prepare_query!("mitigation"));
569 if let Ok(mut stat) = statement {
570 let results = stat.query(params![mitre_id]);
571 if let Ok(mut results) = results {
572 let mut mitigations = vec![];
573 while let Some(row) = results.next()? {
574 mitigations.push(Mitigation {
575 id: row.get(0)?,
576 name: row.get(1)?,
577 description: row.get(2)?,
578 url: row.get(3)?,
579 created: row.get(4)?,
580 modified: row.get(5)?,
581 domain: row.get(6)?,
582 version: row.get(7)?,
583 citations: row.get(8).unwrap_or(unknown!()),
584 });
585 }
586
587 Ok(mitigations)
588 } else {
589 Err(QueryReturnedNoRows)
590 }
591 } else {
592 let err_result = statement.unwrap_err();
593 write_error_message(err_result.to_string().as_str(), None);
594 Err(err_result)
595 }
596}
597
598fn find_mitre_campaigns(mitre_id: &str) -> Result<Vec<Campaign>, rusqlite::Error> {
599 let connection = db_mitre!()?;
600 let mut statement = connection.prepare(prepare_query!("campaign"))?;
601 let results = statement.query(params![mitre_id]);
602 if let Ok(mut rows) = results {
603 let mut campaigns = Vec::new();
604 while let Some(row) = rows.next()? {
605 campaigns.push(Campaign {
606 id: row.get(0)?,
607 name: row.get(1)?,
608 description: row.get(2)?,
609 url: row.get(3)?,
610 created: row.get(4)?,
611 modified: row.get(5)?,
612 domain: row.get(6)?,
613 version: row.get(7)?,
614 associated_campaigns: row.get(8).unwrap_or(unknown!()),
615 associated_campaigns_citations: row.get(9).unwrap_or(unknown!()),
616 first_seen: row.get(10)?,
617 first_seen_citations: row.get(11).unwrap_or(unknown!()),
618 last_seen: row.get(12)?,
619 last_seen_citations: row.get(13).unwrap_or(unknown!()),
620 contributors: row.get(14).unwrap_or(unknown!()),
621 citations: row.get(15).unwrap_or(unknown!()),
622 });
623 }
624
625 Ok(campaigns)
626 } else {
627 Err(QueryReturnedNoRows)
628 }
629}
630
631fn find_mitre_softwares(mitre_id: &str) -> Result<Vec<Software>, rusqlite::Error> {
632 let connection = db_mitre!()?;
633 let mut statement = connection.prepare(prepare_query!("software"))?;
634 let result = statement.query(params![mitre_id]);
635 if let Ok(mut rows) = result {
636 let mut softwares = Vec::new();
637
638 while let Some(soft) = rows.next()? {
639 softwares.push(Software {
640 id: soft.get(0)?,
641 name: soft.get(1)?,
642 description: soft.get(2)?,
643 url: soft.get(3)?,
644 created: soft.get(4)?,
645 modified: soft.get(5)?,
646 domain: soft.get(6)?,
647 version: soft.get(7)?,
648 contributors: soft.get(8).unwrap_or(unknown!()),
649 platforms: soft.get(9).unwrap_or(unknown!()),
650 aliases: soft.get(10).unwrap_or(unknown!()),
651 types: soft.get(11)?,
652 citations: soft.get(12).unwrap_or(unknown!()),
653 })
654 }
655 Ok(softwares)
656 } else {
657 Err(QueryReturnedNoRows)
658 }
659}
660
661fn find_mitre_groups(mitre_id: &str) -> Result<Vec<Group>, rusqlite::Error> {
662 let connection = db_mitre!()?;
663 let mut statement = connection.prepare(prepare_query!("groups", "group"))?;
664 let result = statement.query(params![mitre_id]);
665 if let Ok(mut rows) = result {
666 let mut groups = Vec::new();
667 while let Some(group) = rows.next()? {
668 groups.push(Group {
669 id: group.get(0)?,
670 name: group.get(1)?,
671 description: group.get(2)?,
672 url: group.get(3)?,
673 created: group.get(4)?,
674 modified: group.get(5)?,
675 domain: group.get(6)?,
676 version: group.get(7)?,
677 contributors: group.get(8).unwrap_or(unknown!()),
678 associated_groups: group.get(9).unwrap_or(unknown!()),
679 associated_groups_citations: group.get(10).unwrap_or(unknown!()),
680 citations: group.get(11).unwrap_or(unknown!()),
681 })
682 }
683
684 if groups.is_empty() {
685 Err(QueryReturnedNoRows)
686 } else {
687 Ok(groups)
688 }
689 } else {
690 Err(QueryReturnedNoRows)
691 }
692}
693
694fn find_mitre_detectionstrategy(mitre_id: &str) -> Result<Vec<DetectionStrategy>, rusqlite::Error> {
695 let connection = db_mitre!()?;
696 let mut statement = connection.prepare(prepare_query!("detectionstrategy"))?;
697 let results = statement.query(params![mitre_id]);
698 if let Ok(mut rows) = results {
699 let mut detection_strategy = vec![];
700 while let Some(row) = rows.next()? {
701 detection_strategy.push(DetectionStrategy {
702 id: row.get(0)?,
703 name: row.get(1)?,
704 url: row.get(2)?,
705 created: row.get(3)?,
706 modified: row.get(4)?,
707 domain: row.get(5)?,
708 version: row.get(6)?,
709 })
710 }
711
712 if detection_strategy.is_empty() {
713 Err(QueryReturnedNoRows)
714 } else {
715 Ok(detection_strategy)
716 }
717 } else {
718 Err(QueryReturnedNoRows)
719 }
720}
721
722fn find_mitre_tactics(name: &str, domains: &str) -> Result<Tactic, rusqlite::Error> {
723 let connection = db_mitre!()?;
724 connection.query_row(
725 "SELECT * FROM tactic WHERE name = ? and domain = ?",
726 params![name.trim(), domains.to_ascii_lowercase()],
727 |row| {
728 Ok(Tactic {
729 id: row.get(0).unwrap(),
730 name: row.get(1).unwrap(),
731 url: row.get(2).unwrap(),
732 description: row.get(3).unwrap(),
733 created: row.get(4).unwrap(),
734 modified: row.get(5).unwrap(),
735 domain: simplify_domain(row.get(6).unwrap()),
736 version: row.get(7).unwrap(),
737 })
738 },
739 )
740}
741
742fn find_mitre_citation(name: &str) -> Result<Citation, rusqlite::Error> {
743 let connection = db_mitre!()?;
744 connection.query_row(
745 "SELECT * FROM citation WHERE name = ?",
746 params![name],
747 |row| {
748 Ok(Citation {
749 name: row.get(0).unwrap(),
750 citations: row.get(1).unwrap(),
751 url: row.get(2).unwrap(),
752 })
753 },
754 )
755}
756
757fn find_mitre_relationship(
758 mitre_id: &str,
759 by_target_id: bool,
760) -> Result<Vec<RelationShip>, rusqlite::Error> {
761 let connection = db_mitre!()?;
762 let mut query = match by_target_id {
763 true => connection.prepare(
764 "SELECT * FROM relationship WHERE target_id = ? ORDER BY source_type, source_id",
765 )?,
766 false => connection.prepare(
767 "SELECT * FROM relationship WHERE source_id = ? ORDER BY target_type, target_id",
768 )?,
769 };
770
771 let result = query.query(params![mitre_id]);
772 let mut relationships = Vec::new();
773
774 if let Ok(mut rows) = result {
775 while let Some(value) = rows.next()? {
776 relationships.push(RelationShip {
777 source_id: value.get(0)?,
778 source_type: value.get(1)?,
779 mapping_type: value.get(2)?,
780 target_id: value.get(3)?,
781 target_type: value.get(4)?,
782 description: value.get(5).unwrap_or(unknown!()),
783 created: value.get(6)?,
784 modified: value.get(7)?,
785 })
786 }
787 }
788
789 if relationships.is_empty() {
790 Err(QueryReturnedNoRows)
791 } else {
792 Ok(relationships)
793 }
794}
795
796fn find_mitre_analytics(detection_id: &str) -> Result<Vec<Analytic>, rusqlite::Error> {
797 let connection = db_mitre!()?;
798 let mut query = connection.prepare("SELECT * FROM analytic WHERE url LIKE :detection_id")?;
799 let results = query.query(&[(":detection_id", format!("%{}%", detection_id).as_str())])?;
800 let mut analytics = Vec::new();
801 results
802 .for_each(|row: &Row| {
803 analytics.push(Analytic {
804 id: row.get(0).unwrap(),
805 name: row.get(1).unwrap(),
806 description: row.get(2).unwrap(),
807 url: row.get(3).unwrap(),
808 created: row.get(4).unwrap(),
809 modified: row.get(5).unwrap(),
810 domain: simplify_domain(row.get(6).unwrap()),
811 version: row.get(7).unwrap(),
812 })
813 })
814 .expect("Value not found in analytic table");
815
816 if analytics.is_empty() {
817 Err(QueryReturnedNoRows)
818 } else {
819 Ok(analytics)
820 }
821}
822
823pub struct MitreTables;
824
825impl MitreDatabase for MitreTables {
826 fn table_definitions(&self) -> Vec<(&'static str, &'static str)> {
827 vec![
828 (
829 "analytic",
830 "CREATE TABLE IF NOT EXISTS analytic (
831 id TEXT PRIMARY KEY,
832 name TEXT,
833 description TEXT,
834 url TEXT,
835 created TEXT,
836 modified TEXT,
837 domain TEXT,
838 version TEXT)",
839 ),
840 (
841 "asset",
842 "CREATE TABLE IF NOT EXISTS asset (
843 id TEXT PRIMARY KEY,
844 name TEXT,
845 description TEXT,
846 url TEXT,
847 created TEXT,
848 modified TEXT,
849 domain TEXT,
850 version TEXT,
851 platforms TEXT,
852 sectors TEXT,
853 related_assets TEXT,
854 related_assets_sectors TEXT,
855 related_assets_descriptions TEXT,
856 citations TEXT)",
857 ),
858 (
859 "campaign",
860 "CREATE TABLE IF NOT EXISTS campaign (
861 id TEXT PRIMARY KEY,
862 name TEXT,
863 description TEXT,
864 url TEXT,
865 created TEXT,
866 modified TEXT,
867 domain TEXT,
868 version TEXT,
869 associated_campaigns TEXT,
870 associated_campaigns_citations TEXT,
871 first_seen TEXT,
872 first_seen_citations TEXT,
873 last_seen TEXT,
874 last_seen_citations TEXT,
875 contributors TEXT,
876 citations TEXT)",
877 ),
878 (
879 "citation",
880 "CREATE TABLE IF NOT EXISTS citation (
881 name TEXT PRIMARY KEY,
882 citations TEXT,
883 url TEXT)",
884 ),
885 (
886 "datacomponent",
887 "CREATE TABLE IF NOT EXISTS datacomponent (
888 id TEXT PRIMARY KEY,
889 name TEXT,
890 description TEXT,
891 url TEXT,
892 created TEXT,
893 modified TEXT,
894 domain TEXT,
895 version TEXT)",
896 ),
897 (
898 "detectionstrategy",
899 "CREATE TABLE IF NOT EXISTS detectionstrategy (
900 id TEXT PRIMARY KEY,
901 name TEXT,
902 url TEXT,
903 created TEXT,
904 modified TEXT,
905 domain TEXT,
906 version TEXT)",
907 ),
908 (
909 "groups",
910 "CREATE TABLE IF NOT EXISTS groups (
911 id TEXT PRIMARY KEY,
912 name TEXT,
913 description TEXT,
914 url TEXT,
915 created TEXT,
916 modified TEXT,
917 domain TEXT,
918 version TEXT,
919 contributors TEXT,
920 associated_groups TEXT,
921 associated_groups_citations TEXT,
922 citations TEXT)",
923 ),
924 (
925 "mitigation",
926 "CREATE TABLE IF NOT EXISTS mitigation (
927 id TEXT PRIMARY KEY,
928 name TEXT,
929 description TEXT,
930 url TEXT,
931 created TEXT,
932 modified TEXT,
933 domain TEXT,
934 version TEXT,
935 citations TEXT)",
936 ),
937 (
938 "relationship",
939 "CREATE TABLE IF NOT EXISTS relationship (
940 source_id TEXT,
941 source_type TEXT,
942 mapping_type TEXT,
943 target_id TEXT,
944 target_type TEXT,
945 mapping_description TEXT,
946 created TEXT,
947 modified TEXT,
948
949 PRIMARY KEY(source_id, target_id))",
950 ),
951 (
952 "software",
953 "CREATE TABLE IF NOT EXISTS software (
954 id TEXT PRIMARY KEY,
955 name TEXT,
956 description TEXT,
957 url TEXT,
958 created TEXT,
959 modified TEXT,
960 domain TEXT,
961 version TEXT,
962 contributors TEXT,
963 platforms TEXT,
964 aliases TEXT,
965 type TEXT,
966 citations TEXT)",
967 ),
968 (
969 "tactic",
970 "CREATE TABLE IF NOT EXISTS tactic (
971 id TEXT PRIMARY KEY,
972 name TEXT,
973 url TEXT,
974 description TEXT,
975 created TEXT,
976 modified TEXT,
977 domain TEXT,
978 version TEXT)",
979 ),
980 (
981 "technique",
982 "CREATE TABLE IF NOT EXISTS technique (
983 id TEXT PRIMARY KEY,
984 name TEXT,
985 description TEXT,
986 url TEXT,
987 created TEXT,
988 modified TEXT,
989 domain TEXT,
990 version TEXT,
991 tactics TEXT,
992 platforms TEXT,
993 sub_techniques TEXT,
994 contributors TEXT,
995 support_remote TEXT,
996 impact_type TEXT,
997 citations TEXT,
998 tactic_type TEXT,
999 MTC_ID TEXT)",
1000 ),
1001 ]
1002 }
1003
1004 fn index_definitions(&self) -> Vec<(&'static str, &'static str)> {
1005 vec![
1006 (
1007 "asset_citations_idx",
1008 "CREATE INDEX IF NOT EXISTS asset_citations_idx on asset(citations)",
1009 ),
1010 (
1011 "campaign_citations_idx",
1012 "CREATE INDEX IF NOT EXISTS campaing_citations_idx on campaign(citations)",
1013 ),
1014 (
1015 "citation_citations_idx",
1016 "CREATE INDEX IF NOT EXISTS citation_citations_idx on citation(citations)",
1017 ),
1018 (
1019 "groups_citations_idx",
1020 "CREATE INDEX IF NOT EXISTS groups_citations_idx on groups(citations)",
1021 ),
1022 (
1023 "mitigation_citations_idx",
1024 "CREATE INDEX IF NOT EXISTS mitigation_citations_idx on mitigation(citations)",
1025 ),
1026 (
1027 "software_citations_idx",
1028 "CREATE INDEX IF NOT EXISTS software_citations_idx on software(citations)",
1029 ),
1030 (
1031 "technique_citations_idx",
1032 "CREATE INDEX IF NOT EXISTS technique_citations_idx on technique(citations)",
1033 ),
1034 ]
1035 }
1036}
1037
1038type Importer = fn(&Worksheet, &Connection, u32) -> bool;
1039
1040pub fn inject_xlsx_into_database(xlsx_file: &Path) -> Result<(), Box<dyn Error>> {
1041 let file_name = xlsx_file.file_name().unwrap();
1042 if xlsx_file.try_exists().is_ok() && xlsx_file.is_file() {
1043 let mut reader = reader::xlsx::lazy_read(xlsx_file)?;
1044 let max_sheets = reader.read_sheet_collection().get_sheet_count();
1045 let mut sheet_counter = 0;
1046
1047 let connection = db_mitre!()?;
1048 while sheet_counter < max_sheets {
1049 if let Some(sheet) = reader.get_sheet(&sheet_counter) {
1050 match sheet.get_name() {
1051 "analytics" => import_data(sheet, &connection, analytics_data),
1052 "assets" => import_data(sheet, &connection, assets_data),
1053 "campaigns" => {
1054 if !file_name.to_str().unwrap().contains("ics") {
1055 import_data(sheet, &connection, campaigns_data);
1056 } else {
1057 import_data(sheet, &connection, campaigns_data_ics);
1058 }
1059 }
1060 "citations" => import_data(sheet, &connection, citations_data),
1061 "datacomponents" => import_data(sheet, &connection, datacomponents_data),
1062 "detectionstrategies" => {
1063 import_data(sheet, &connection, detectionstrategies_data)
1064 }
1065 "groups" => {
1066 if !file_name.to_str().unwrap().contains("mobile") {
1067 import_data(sheet, &connection, groups_data);
1068 } else {
1069 import_data(sheet, &connection, groups_data_mobile);
1070 }
1071 }
1072 "mitigations" => import_data(sheet, &connection, mitigations_data),
1073 "relationships" => import_data(sheet, &connection, relationships_data),
1074 "software" => {
1075 if !file_name.to_str().unwrap().contains("ics") {
1076 import_data(sheet, &connection, software_data);
1077 } else {
1078 import_data(sheet, &connection, software_data_ics);
1079 }
1080 }
1081 "tactics" => import_data(sheet, &connection, tactics_data),
1082 "techniques" => {
1083 if file_name.to_str().unwrap().contains("enterprise") {
1084 import_data(sheet, &connection, techniques_data_enterprise);
1085 } else if file_name.to_str().unwrap().contains("ics") {
1086 import_data(sheet, &connection, techniques_data_ics);
1087 } else if file_name.to_str().unwrap().contains("mobile") {
1088 import_data(sheet, &connection, techniques_data_mobile);
1089 } else {
1090 write_error_message("No importer for sheet", Some(sheet.get_name()));
1091 }
1092 }
1093 _ => write_error_message("No importer for sheet", Some(sheet.get_name())),
1094 };
1095 }
1096
1097 sheet_counter += 1;
1098 }
1099 }
1100 Ok(())
1101}
1102
1103fn import_data(sheet: &Worksheet, connection: &Connection, importer: Importer) {
1104 let (_, rows) = sheet.get_highest_column_and_row();
1105 let sheet_name = sheet.get_name();
1106 let mut progress_bar = ProgressText::new(2usize, rows as usize, sheet_name.to_string());
1107
1108 while !progress_bar.is_done() {
1109 let _ = importer(sheet, connection, progress_bar.value() as u32);
1110 progress_bar.progress();
1111 }
1112 println!();
1113}
1114
1115fn analytics_data(sheet: &Worksheet, connection: &Connection, row: u32) -> bool {
1116 generic_data_9_columns(sheet, connection, row, "analytic")
1117}
1118
1119fn generic_data_9_columns(
1120 sheet: &Worksheet,
1121 connection: &Connection,
1122 row: u32,
1123 table_name: &str,
1124) -> bool {
1125 connection.execute(
1126 format!("INSERT INTO {table_name} (id, name, description, url, created, modified, domain, version) VALUES (?, ?, ?, ?, ?, ?, ?, ?)").as_str(),
1127 params![
1128 sheet.get_cell_value((1, row)).get_value(),
1129 sheet.get_cell_value((3, row)).get_value(),
1130 remove_citations(sheet.get_cell_value((4, row)).get_value()),
1131 sheet.get_cell_value((5, row)).get_value(),
1132 sheet.get_cell_value((6, row)).get_value(),
1133 sheet.get_cell_value((7, row)).get_value(),
1134 sheet.get_cell_value((8, row)).get_value(),
1135 sheet.get_cell_value((9, row)).get_value(),
1136 ]).is_ok()
1137}
1138
1139fn assets_data(sheet: &Worksheet, connection: &Connection, row: u32) -> bool {
1140 connection.execute(
1141 "INSERT INTO asset (id, name, description, url, created, modified, domain, version, platforms, sectors, related_assets, related_assets_sectors, related_assets_descriptions, citations)
1142 VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
1143 params![
1144 sheet.get_cell_value((1, row)).get_value(),
1145 sheet.get_cell_value((3, row)).get_value(),
1146 remove_citations(sheet.get_cell_value((4, row)).get_value()),
1147 sheet.get_cell_value((5, row)).get_value(),
1148 sheet.get_cell_value((6, row)).get_value(),
1149 sheet.get_cell_value((7, row)).get_value(),
1150 sheet.get_cell_value((8, row)).get_value(),
1151 sheet.get_cell_value((9, row)).get_value(),
1152 simplify_field_content(sheet.get_cell_value((10, row)).get_value()),
1153 cleaning_asset_data(sheet.get_cell_value((11, row)).get_value()),
1154 cleaning_asset_data(sheet.get_cell_value((12, row)).get_value()),
1155 cleaning_asset_data(sheet.get_cell_value((13, row)).get_value()),
1156 cleaning_asset_data(sheet.get_cell_value((14, row)).get_value()),
1157 simplify_field_content(sheet.get_cell_value((15, row)).get_value()),
1158 ],
1159 ).is_ok()
1160}
1161
1162fn cleaning_asset_data(asset_data: Cow<str>) -> Value {
1163 let raw_data = asset_data
1164 .split(";")
1165 .map(|s| s.to_string())
1166 .collect::<HashSet<String>>();
1167 let filtered = raw_data
1168 .iter()
1169 .filter(|&s| !s.trim().is_empty())
1170 .collect::<Vec<_>>();
1171 let mut converted = HashSet::new();
1172
1173 filtered.iter().for_each(|s| {
1174 converted.insert(s.trim().replace(",", "").to_string());
1175 });
1176
1177 text_or_null(converted.into_iter().collect::<Vec<String>>().join(","))
1178}
1179
1180fn campaigns_data(sheet: &Worksheet, connection: &Connection, row: u32) -> bool {
1181 connection.execute(
1182 "INSERT INTO campaign (id, name, description, url, created, modified, domain, version, associated_campaigns, associated_campaigns_citations, first_seen, first_seen_citations, last_seen, last_seen_citations, contributors, citations)\
1183 VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
1184 params![
1185 sheet.get_cell_value((1, row)).get_value(),
1186 sheet.get_cell_value((3, row)).get_value(),
1187 remove_citations(sheet.get_cell_value((4, row)).get_value()),
1188 sheet.get_cell_value((5, row)).get_value(),
1189 sheet.get_cell_value((6, row)).get_value(),
1190 sheet.get_cell_value((7, row)).get_value(),
1191 sheet.get_cell_value((8, row)).get_value(),
1192 sheet.get_cell_value((9, row)).get_value(),
1193 simplify_field_content(sheet.get_cell_value((10, row)).get_value()),
1194 simplify_field_content(sheet.get_cell_value((11, row)).get_value()),
1195 sheet.get_cell_value((12, row)).get_value(),
1196 simplify_field_content(sheet.get_cell_value((13, row)).get_value()),
1197 sheet.get_cell_value((14, row)).get_value(),
1198 simplify_field_content(sheet.get_cell_value((15, row)).get_value()),
1199 simplify_field_content(sheet.get_cell_value((16, row)).get_value()),
1200 simplify_field_content(sheet.get_cell_value((17, row)).get_value()),
1201
1202 ],
1203 ).is_ok()
1204}
1205
1206fn campaigns_data_ics(sheet: &Worksheet, connection: &Connection, row: u32) -> bool {
1207 connection.execute(
1208 "INSERT INTO campaign (id, name, description, url, created, modified, domain, version, associated_campaigns, associated_campaigns_citations, first_seen, first_seen_citations, last_seen, last_seen_citations, citations)\
1209 VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
1210 params![
1211 sheet.get_cell_value((1, row)).get_value(),
1212 sheet.get_cell_value((3, row)).get_value(),
1213 remove_citations(sheet.get_cell_value((4, row)).get_value()),
1214 sheet.get_cell_value((5, row)).get_value(),
1215 sheet.get_cell_value((6, row)).get_value(),
1216 sheet.get_cell_value((7, row)).get_value(),
1217 sheet.get_cell_value((8, row)).get_value(),
1218 sheet.get_cell_value((9, row)).get_value(),
1219 simplify_field_content(sheet.get_cell_value((10, row)).get_value()),
1220 simplify_field_content(sheet.get_cell_value((11, row)).get_value()),
1221 sheet.get_cell_value((12, row)).get_value(),
1222 simplify_field_content(sheet.get_cell_value((13, row)).get_value()),
1223 sheet.get_cell_value((14, row)).get_value(),
1224 simplify_field_content(sheet.get_cell_value((15, row)).get_value()),
1225 simplify_field_content(sheet.get_cell_value((16, row)).get_value()),
1226
1227 ],
1228 ).is_ok()
1229}
1230
1231fn citations_data(sheet: &Worksheet, connection: &Connection, row: u32) -> bool {
1232 connection
1233 .execute(
1234 "INSERT INTO citation (name, citations, url) VALUES (?, ?, ?)",
1235 params![
1236 sheet.get_cell_value((1, row)).get_value(),
1237 sheet.get_cell_value((2, row)).get_value(),
1238 sheet.get_cell_value((3, row)).get_value(),
1239 ],
1240 )
1241 .is_ok()
1242}
1243
1244fn datacomponents_data(sheet: &Worksheet, connection: &Connection, row: u32) -> bool {
1245 generic_data_9_columns(sheet, connection, row, "datacomponent")
1246}
1247
1248fn detectionstrategies_data(sheet: &Worksheet, connection: &Connection, row: u32) -> bool {
1249 connection
1250 .execute(
1251 "INSERT INTO detectionstrategy (id, name, url, created, modified, domain, version)
1252 VALUES(?, ?, ?, ?, ?, ?, ?)",
1253 params![
1254 sheet.get_cell_value((1, row)).get_value(),
1255 sheet.get_cell_value((3, row)).get_value(),
1256 sheet.get_cell_value((4, row)).get_value(),
1257 sheet.get_cell_value((5, row)).get_value(),
1258 sheet.get_cell_value((6, row)).get_value(),
1259 sheet.get_cell_value((7, row)).get_value(),
1260 sheet.get_cell_value((8, row)).get_value(),
1261 ],
1262 )
1263 .is_ok()
1264}
1265
1266fn groups_data(sheet: &Worksheet, connection: &Connection, row: u32) -> bool {
1267 connection.execute(
1268 "INSERT INTO groups (id, name, description, url, created, modified, domain, version, contributors, associated_groups, associated_groups_citations, citations)\
1269 VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
1270 params![
1271 sheet.get_cell_value((1, row)).get_value(),
1272 sheet.get_cell_value((3, row)).get_value(),
1273 remove_citations(sheet.get_cell_value((4, row)).get_value()),
1274 sheet.get_cell_value((5, row)).get_value(),
1275 sheet.get_cell_value((6, row)).get_value(),
1276 sheet.get_cell_value((7, row)).get_value(),
1277 sheet.get_cell_value((8, row)).get_value(),
1278 sheet.get_cell_value((9, row)).get_value(),
1279 simplify_field_content(sheet.get_cell_value((10, row)).get_value()),
1280 simplify_field_content(sheet.get_cell_value((11, row)).get_value()),
1281 simplify_field_content(sheet.get_cell_value((12, row)).get_value()),
1282 simplify_field_content(sheet.get_cell_value((13, row)).get_value()),
1283 ],
1284 ).is_ok()
1285}
1286
1287fn groups_data_mobile(sheet: &Worksheet, connection: &Connection, row: u32) -> bool {
1288 connection.execute(
1289 "INSERT INTO groups (id, name, description, url, created, modified, domain, version, associated_groups, associated_groups_citations, contributors, citations)
1290 VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
1291 params![
1292 sheet.get_cell_value((1, row)).get_value(),
1293 sheet.get_cell_value((3, row)).get_value(),
1294 remove_citations(sheet.get_cell_value((4, row)).get_value()),
1295 sheet.get_cell_value((5, row)).get_value(),
1296 sheet.get_cell_value((6, row)).get_value(),
1297 sheet.get_cell_value((7, row)).get_value(),
1298 sheet.get_cell_value((8, row)).get_value(),
1299 sheet.get_cell_value((9, row)).get_value(),
1300 simplify_field_content(sheet.get_cell_value((11, row)).get_value()),
1301 simplify_field_content(sheet.get_cell_value((12, row)).get_value()),
1302 simplify_field_content(sheet.get_cell_value((10, row)).get_value()),
1303 simplify_field_content(sheet.get_cell_value((13, row)).get_value()),
1304 ],
1305 ).is_ok()
1306}
1307
1308fn mitigations_data(sheet: &Worksheet, connection: &Connection, row: u32) -> bool {
1309 connection.execute(
1310 "INSERT INTO mitigation (id, name, description, url, created, modified, domain, version, citations)\
1311 VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?)",
1312 params![
1313 sheet.get_cell_value((1, row)).get_value(),
1314 sheet.get_cell_value((3, row)).get_value(),
1315 sheet.get_cell_value((4, row)).get_value(),
1316 remove_citations(sheet.get_cell_value((5, row)).get_value()),
1317 sheet.get_cell_value((6, row)).get_value(),
1318 sheet.get_cell_value((7, row)).get_value(),
1319 sheet.get_cell_value((8, row)).get_value(),
1320 sheet.get_cell_value((9, row)).get_value(),
1321 simplify_field_content(sheet.get_cell_value((10, row)).get_value()),
1322 ],
1323 ).is_ok()
1324}
1325
1326fn relationships_data(sheet: &Worksheet, connection: &Connection, row: u32) -> bool {
1327 connection.execute(
1328 "INSERT INTO relationship (source_id, source_type, mapping_type, target_id, target_type, mapping_description, created, modified) \
1329 VALUES(?, ?, ?, ?, ?, ?, ?, ?)",
1330 params![
1331 sheet.get_cell_value((1, row)).get_value(),
1332 sheet.get_cell_value((4, row)).get_value(),
1333 sheet.get_cell_value((5, row)).get_value(),
1334 sheet.get_cell_value((6, row)).get_value(),
1335 sheet.get_cell_value((9, row)).get_value(),
1336 simplify_field_content(sheet.get_cell_value((10, row)).get_value()),
1337 sheet.get_cell_value((12, row)).get_value(),
1338 sheet.get_cell_value((13, row)).get_value(),
1339 ],
1340 ).is_ok()
1341}
1342
1343fn software_data(sheet: &Worksheet, connection: &Connection, row: u32) -> bool {
1344 connection.execute(
1345 "INSERT INTO software (id, name, description, url, created, modified, domain, version, contributors, platforms, aliases, type, citations)
1346 VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
1347 params![
1348 sheet.get_cell_value((1, row)).get_value(),
1349 sheet.get_cell_value((3, row)).get_value(),
1350 remove_citations(sheet.get_cell_value((4, row)).get_value()),
1351 sheet.get_cell_value((5, row)).get_value(),
1352 sheet.get_cell_value((6, row)).get_value(),
1353 sheet.get_cell_value((7, row)).get_value(),
1354 sheet.get_cell_value((8, row)).get_value(),
1355 simplify_field_content(sheet.get_cell_value((9, row)).get_value()),
1356 simplify_field_content(sheet.get_cell_value((10, row)).get_value()),
1357 simplify_field_content(sheet.get_cell_value((11, row)).get_value()),
1358 simplify_field_content(sheet.get_cell_value((12, row)).get_value()),
1359 simplify_field_content(sheet.get_cell_value((13, row)).get_value()),
1360 simplify_field_content(sheet.get_cell_value((14, row)).get_value()),
1361 ],
1362 ).is_ok()
1363}
1364
1365fn software_data_ics(sheet: &Worksheet, connection: &Connection, row: u32) -> bool {
1366 connection.execute(
1367 "INSERT INTO software (id, name, description, url, created, modified, domain, version, platforms, aliases, type, contributors, citations)
1368 VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
1369 params![
1370 sheet.get_cell_value((1, row)).get_value(),
1371 sheet.get_cell_value((3, row)).get_value(),
1372 remove_citations(sheet.get_cell_value((4, row)).get_value()),
1373 sheet.get_cell_value((5, row)).get_value(),
1374 sheet.get_cell_value((6, row)).get_value(),
1375 sheet.get_cell_value((7, row)).get_value(),
1376 sheet.get_cell_value((8, row)).get_value(),
1377 simplify_field_content(sheet.get_cell_value((9, row)).get_value()),
1378 simplify_field_content(sheet.get_cell_value((10, row)).get_value()),
1379 simplify_field_content(sheet.get_cell_value((11, row)).get_value()),
1380 simplify_field_content(sheet.get_cell_value((12, row)).get_value()),
1381 simplify_field_content(sheet.get_cell_value((13, row)).get_value()),
1382 simplify_field_content(sheet.get_cell_value((14, row)).get_value()),
1383 ],
1384 ).is_ok()
1385}
1386
1387fn tactics_data(sheet: &Worksheet, connection: &Connection, row: u32) -> bool {
1388 generic_data_9_columns(sheet, connection, row, "tactic")
1389}
1390
1391fn techniques_data_enterprise(sheet: &Worksheet, connection: &Connection, row: u32) -> bool {
1392 connection.execute(
1393 "INSERT INTO technique (id, name, description, url, created, modified, domain, version, tactics, platforms, sub_techniques, contributors, support_remote, impact_type, citations)
1394 VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", params![
1395 sheet.get_cell_value((1, row)).get_value(),
1396 sheet.get_cell_value((3, row)).get_value(),
1397 remove_citations(sheet.get_cell_value((4, row)).get_value()),
1398 sheet.get_cell_value((5, row)).get_value(),
1399 sheet.get_cell_value((6, row)).get_value(),
1400 sheet.get_cell_value((7, row)).get_value(),
1401 sheet.get_cell_value((8, row)).get_value(),
1402 simplify_field_content(sheet.get_cell_value((9, row)).get_value()),
1403 simplify_field_content(sheet.get_cell_value((10, row)).get_value()),
1404 simplify_field_content(sheet.get_cell_value((12, row)).get_value()),
1405 simplify_field_content(sheet.get_cell_value((14, row)).get_value()),
1406 simplify_field_content(sheet.get_cell_value((15, row)).get_value()),
1407 simplify_field_content(sheet.get_cell_value((16, row)).get_value()),
1408 simplify_field_content(sheet.get_cell_value((17, row)).get_value()),
1409 simplify_field_content(sheet.get_cell_value((18, row)).get_value()),
1410 ],
1411 ).is_ok()
1412}
1413
1414fn techniques_data_ics(sheet: &Worksheet, connection: &Connection, row: u32) -> bool {
1415 connection.execute(
1416 "INSERT INTO technique (id, name, description, url, created, modified, domain, version, tactics, platforms, contributors, citations)
1417 VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", params![
1418 sheet.get_cell_value((1, row)).get_value(),
1419 sheet.get_cell_value((3, row)).get_value(),
1420 remove_citations(sheet.get_cell_value((4, row)).get_value()),
1421 sheet.get_cell_value((5, row)).get_value(),
1422 sheet.get_cell_value((6, row)).get_value(),
1423 sheet.get_cell_value((7, row)).get_value(),
1424 sheet.get_cell_value((8, row)).get_value(),
1425 sheet.get_cell_value((9, row)).get_value(),
1426 simplify_field_content(sheet.get_cell_value((10, row)).get_value()),
1427 simplify_field_content(sheet.get_cell_value((12, row)).get_value()),
1428 simplify_field_content(sheet.get_cell_value((13, row)).get_value()),
1429 simplify_field_content(sheet.get_cell_value((14, row)).get_value()),
1430 ],
1431 ).is_ok()
1432}
1433
1434fn techniques_data_mobile(sheet: &Worksheet, connection: &Connection, row: u32) -> bool {
1435 connection.execute(
1436 "INSERT INTO technique (id, name, description, url, created, modified, domain, version, contributors, tactics, platforms, tactic_type, MTC_ID, citations)
1437 VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
1438 params![
1439 sheet.get_cell_value((1, row)).get_value(),
1440 sheet.get_cell_value((3, row)).get_value(),
1441 remove_citations(sheet.get_cell_value((4, row)).get_value()),
1442 sheet.get_cell_value((5, row)).get_value(),
1443 sheet.get_cell_value((6, row)).get_value(),
1444 sheet.get_cell_value((7, row)).get_value(),
1445 sheet.get_cell_value((8, row)).get_value(),
1446 sheet.get_cell_value((9, row)).get_value(),
1447 simplify_field_content(sheet.get_cell_value((10, row)).get_value()),
1448 simplify_field_content(sheet.get_cell_value((11, row)).get_value()),
1449 simplify_field_content(sheet.get_cell_value((13, row)).get_value()),
1450 simplify_field_content(sheet.get_cell_value((14, row)).get_value()),
1451 simplify_field_content(sheet.get_cell_value((15, row)).get_value()),
1452 simplify_field_content(sheet.get_cell_value((16, row)).get_value()),
1453 ],
1454 ).is_ok()
1455}
1456
1457fn simplify_field_content(raw_value: Cow<str>) -> Value {
1458 let mut field_value = raw_value.to_string();
1459
1460 {
1461 let remove_invalid_entry_1 = Regex::new("^,,|,,$");
1462 field_value = remove_invalid_entry_1
1463 .unwrap()
1464 .replace(&field_value, "")
1465 .to_string();
1466
1467 let remove_invalid_entry_2 = Regex::new("\\),,\\(");
1468 field_value = remove_invalid_entry_2
1469 .unwrap()
1470 .replace(&field_value, "),(")
1471 .to_string();
1472 }
1473
1474 let mut citation_part = BTreeSet::new();
1475
1476 let value = field_value.split(",(").collect::<Vec<&str>>();
1477 value.iter().for_each(|&s| {
1478 citation_part.insert(
1479 s.trim()
1480 .replace("(Citation:", "")
1481 .replace(")", "")
1482 .replace(",,", ",")
1483 .replace("Citation:", "")
1484 .replacen(",", "", citation_part.len()),
1485 );
1486 });
1487
1488 let mut intermediate: Vec<String> = Vec::new();
1489 citation_part
1490 .iter()
1491 .filter(|&s| !s.is_empty())
1492 .for_each(|s| intermediate.push(s.trim().to_string()));
1493
1494 text_or_null(intermediate.join(","))
1495}
1496
1497fn remove_citations(line: Cow<str>) -> Value {
1498 let str_value = line.to_string();
1499
1500 let citations = Regex::new("\\(Citation:.*\\)").unwrap();
1501 let phase_1 = citations.replace_all(&str_value, "").to_string();
1502
1503 let blank_and_point = Regex::new("' '+[.]").unwrap();
1504 text_or_null(blank_and_point.replace_all(&phase_1, ".").to_string())
1505}
1506
1507pub fn domains() -> Vec<String> {
1508 if let Ok(connection) = db_mitre!() {
1509 let mut statement = connection
1510 .prepare("SELECT DISTINCT domain FROM technique ORDER BY domain ASC")
1511 .unwrap();
1512 let rows = statement.query([]).unwrap();
1513 let results = rows.map(|row| row.get(0)).collect::<Vec<String>>();
1514 if let Ok(result) = results {
1515 simplify_domains(result)
1516 } else {
1517 vec![]
1518 }
1519 } else {
1520 vec![]
1521 }
1522}
1523
1524fn simplify_domains(domain_list: Vec<String>) -> Vec<String> {
1525 let mut results: Vec<String> = Vec::new();
1526
1527 domain_list.into_iter().for_each(|domain| {
1528 let value = simplify_domain(domain);
1529 results.push(value);
1530 });
1531
1532 results
1533}
1534
1535fn simplify_domain(domain: String) -> String {
1536 let value = domain.split(&[',', '-']).collect::<Vec<&str>>();
1537 value[0].to_string().first_uppercase()
1538}
1539
1540pub fn sources() -> Vec<String> {
1541 if let Ok(connection) = db_mitre!() {
1542 let mut statement = connection
1543 .prepare("SELECT DISTINCT source_type FROM relationship ORDER BY source_type ASC")
1544 .unwrap();
1545 let rows = statement.query([]).unwrap();
1546 return rows.map(|row| row.get(0)).collect::<Vec<String>>().unwrap();
1547 }
1548
1549 vec![]
1550}
1551
1552pub fn show_mitre(data: &MitreRecord) {
1553 println!("{}", header!(format!("MITRE - {}", data.id)));
1554
1555 summary!("Summary", data);
1556
1557 println!();
1558
1559 show!(flow, "Platforms", data.platforms);
1560
1561 println!();
1562
1563 body!(data, domains, description);
1564
1565 section_level!("Mitre");
1566 stamp!("Sub-techniques", data.sub_techniques);
1567 stamp!(data.impact_type);
1568 stamp!(data.mtc_id);
1569
1570 println!();
1571
1572 show_tactics(data);
1573
1574 println!();
1575
1576 citations!(data);
1577
1578 println!();
1579
1580 resume_relationship(data.id.as_str());
1581
1582 println!();
1583}
1584
1585fn show_tactics(data: &MitreRecord) {
1586 section_level!("Tactics");
1587 stamp!(data.tactic_type);
1588 show_list(&data.tactics, "List");
1589}
1590
1591fn show_list(data: &str, list_label: &str) {
1592 stamp!(list_label, "");
1593 data.split(",")
1594 .for_each(|s| println!("{:>8} {}", "-", s.trim()))
1595}
1596
1597fn resume_relationship(technique_id: &str) {
1598 let connection = db_mitre!().unwrap();
1599 let mut statement = connection
1600 .prepare("SELECT source_id, source_type FROM relationship WHERE target_id = ? order by source_type, source_id")
1601 .unwrap();
1602 if let Ok(mut results) = statement.query(params![technique_id]) {
1603 print!("{:>1} {}", "", section!("Relationship"));
1604 let mut old_value = String::new();
1605 while let Some(row) = results.next().unwrap() {
1606 let section = row.get::<usize, String>(1).unwrap();
1607
1608 if section != old_value {
1609 old_value = section;
1610 print!("\n{:>6} {}: ", "-", old_value.first_uppercase());
1611 } else {
1612 print!(", ")
1613 }
1614
1615 let reference = row.get::<usize, String>(0).unwrap();
1616 print!("{}", reference);
1617 }
1618 }
1619}
1620
1621#[cfg(test)]
1622mod tests {
1623 use crate::{domains, find_mitre_detectionstrategy, sources};
1624
1625 #[test]
1626 fn test_domains() -> Result<(), String> {
1627 let domains = domains();
1628 match domains.len() == 3 {
1629 false => Err("Bad awaiting domain number".to_string()),
1630 true => Ok(()),
1631 }
1632 }
1633
1634 #[test]
1635 fn test_domain_content() -> Result<(), String> {
1636 let domains = domains();
1637 let references = vec!["Ics", "Enterprise", "Mobile"];
1638 let mut counter = 0;
1639 domains.into_iter().for_each(|domain| {
1640 if references.contains(&domain.as_str()) {
1641 counter += 1;
1642 }
1643 });
1644
1645 match counter {
1646 3 => Ok(()),
1647 _ => Err("Bad awaiting domain content".to_string()),
1648 }
1649 }
1650
1651 #[test]
1652 fn test_sources() -> Result<(), String> {
1653 let sources = sources();
1654
1655 match sources.len() {
1656 0 => Err("Bad awaiting mapping number".to_string()),
1657 _ => Ok(()),
1658 }
1659 }
1660
1661 #[test]
1662 fn test_detectionstrategy() -> Result<(), String> {
1663 let result = find_mitre_detectionstrategy("T0857");
1664
1665 match result {
1666 Ok(_) => Ok(()),
1667 Err(e) => Err(e.to_string()),
1668 }
1669 }
1670
1671 #[test]
1672 fn test_detectionstrategy_no_value() -> Result<(), String> {
1673 let result = find_mitre_detectionstrategy("foo");
1674
1675 match result {
1676 Ok(_) => Err("No value awaiting".to_string()),
1677 Err(_e) => Ok(()),
1678 }
1679 }
1680}