1#[cfg(any(test, feature = "admin"))]
10pub mod admin;
11mod pg;
13
14#[cfg(any(test, feature = "sqlite"))]
16mod sqlite;
17
18#[cfg(any(test, feature = "sqlite"))]
20mod sqlite_functions;
21
22pub mod types;
24
25#[cfg(any(test, feature = "admin"))]
26use crate::crypto::EncryptionOption;
27use crate::crypto::FileEncryption;
28use crate::db::pg::Postgres;
29#[cfg(any(test, feature = "sqlite"))]
30use crate::db::sqlite::Sqlite;
31use crate::db::types::{FileMetadata, FileType};
32use malwaredb_api::{
33 GetUserInfoResponse, Labels, SearchRequest, SearchResponse, Sources, digest::HashType,
34};
35use malwaredb_types::KnownType;
36
37use std::collections::HashMap;
38use std::path::PathBuf;
39
40use anyhow::{Result, bail, ensure};
41use argon2::password_hash::{SaltString, rand_core::OsRng};
42use argon2::{Argon2, PasswordHasher};
43#[cfg(any(test, feature = "admin"))]
44use chrono::Local;
45#[cfg(feature = "vt")]
46use malwaredb_virustotal::filereport::ScanResultAttributes;
47
48pub const PARTIAL_SEARCH_LIMIT: u32 = 100;
50
51#[derive(Copy, Clone)]
53pub enum Migration {
54 Check,
56
57 #[cfg(any(test, feature = "admin"))]
59 Migrate,
60}
61
62#[derive(Debug)]
64pub enum DatabaseType {
65 Postgres(Postgres),
67
68 #[cfg(any(test, feature = "sqlite"))]
70 SQLite(Sqlite),
71}
72
73#[derive(Debug)]
75pub struct DatabaseInformation {
76 pub version: String,
78
79 pub size: String,
81
82 pub num_files: u64,
84
85 pub num_users: u32,
87
88 pub num_groups: u32,
90
91 pub num_sources: u32,
93}
94
95pub struct FileAddedResult {
97 pub file_id: u64,
99
100 pub is_new: bool,
103}
104
105#[derive(Debug)]
107pub struct MDBConfig {
108 pub name: String,
110
111 pub compression: bool,
113
114 pub send_samples_to_vt: bool,
116
117 pub keep_unknown_files: bool,
119
120 pub(crate) default_key: Option<u32>,
122}
123
124#[cfg_attr(docsrs, doc(cfg(feature = "vt")))]
126#[cfg(feature = "vt")]
127#[derive(Debug, Clone, Copy)]
128pub struct VtStats {
129 pub clean_records: u32,
131
132 pub hits_records: u32,
134
135 pub files_without_records: u32,
137}
138
139impl DatabaseType {
140 pub async fn from_string(arg: &str, server_ca: Option<PathBuf>) -> Result<Self> {
149 let db = Self::init_from_string(arg, server_ca).await?;
150 db.migrate_check(Migration::Check).await?;
151 Ok(db)
152 }
153
154 #[cfg(feature = "admin")]
163 pub async fn migrate(arg: &str, server_ca: Option<PathBuf>) -> Result<Self> {
164 let db = Self::init_from_string(arg, server_ca).await?;
165 db.migrate_check(Migration::Migrate).await?;
166 Ok(db)
167 }
168
169 async fn init_from_string(arg: &str, server_ca: Option<PathBuf>) -> Result<Self> {
170 #[cfg(any(test, feature = "sqlite"))]
171 if arg.starts_with("file:") {
172 let new_conn_str = arg.trim_start_matches("file:");
173 let db = DatabaseType::SQLite(Sqlite::new(new_conn_str)?);
174 return Ok(db);
175 }
176
177 if arg.starts_with("postgres") {
178 let new_conn_str = arg.trim_start_matches("postgres");
179 let db = DatabaseType::Postgres(Postgres::new(new_conn_str, server_ca).await?);
180
181 return Ok(db);
182 }
183
184 bail!("unknown database type `{arg}`")
185 }
186
187 #[cfg_attr(docsrs, doc(cfg(feature = "vt")))]
193 #[cfg(feature = "vt")]
194 pub async fn enable_vt_upload(&self) -> Result<()> {
195 match self {
196 DatabaseType::Postgres(pg) => pg.enable_vt_upload().await,
197 #[cfg(any(test, feature = "sqlite"))]
198 DatabaseType::SQLite(sl) => sl.enable_vt_upload(),
199 }
200 }
201
202 #[cfg_attr(docsrs, doc(cfg(feature = "vt")))]
208 #[cfg(feature = "vt")]
209 pub async fn disable_vt_upload(&self) -> Result<()> {
210 match self {
211 DatabaseType::Postgres(pg) => pg.disable_vt_upload().await,
212 #[cfg(any(test, feature = "sqlite"))]
213 DatabaseType::SQLite(sl) => sl.disable_vt_upload(),
214 }
215 }
216
217 #[cfg_attr(docsrs, doc(cfg(feature = "vt")))]
223 #[cfg(feature = "vt")]
224 pub async fn files_without_vt_records(&self, limit: u32) -> Result<Vec<String>> {
225 match self {
226 DatabaseType::Postgres(pg) => pg.files_without_vt_records(limit).await,
227 #[cfg(any(test, feature = "sqlite"))]
228 DatabaseType::SQLite(sl) => sl.files_without_vt_records(limit),
229 }
230 }
231
232 #[cfg_attr(docsrs, doc(cfg(feature = "vt")))]
238 #[cfg(feature = "vt")]
239 pub async fn store_vt_record(&self, results: &ScanResultAttributes) -> Result<()> {
240 match self {
241 DatabaseType::Postgres(pg) => pg.store_vt_record(results).await,
242 #[cfg(any(test, feature = "sqlite"))]
243 DatabaseType::SQLite(sl) => sl.store_vt_record(results),
244 }
245 }
246
247 #[cfg_attr(docsrs, doc(cfg(feature = "vt")))]
253 #[cfg(feature = "vt")]
254 pub async fn get_vt_stats(&self) -> Result<VtStats> {
255 match self {
256 DatabaseType::Postgres(pg) => pg.get_vt_stats().await,
257 #[cfg(any(test, feature = "sqlite"))]
258 DatabaseType::SQLite(sl) => sl.get_vt_stats(),
259 }
260 }
261
262 #[cfg_attr(docsrs, doc(cfg(feature = "yara")))]
268 #[cfg(feature = "yara")]
269 pub async fn add_yara_search(
270 &self,
271 uid: u32,
272 yara_string: &str,
273 yara_bytes: &[u8],
274 ) -> Result<uuid::Uuid> {
275 match self {
276 DatabaseType::Postgres(pg) => pg.add_yara_search(uid, yara_string, yara_bytes).await,
277 #[cfg(any(test, feature = "sqlite"))]
278 DatabaseType::SQLite(sl) => sl.add_yara_search(uid, yara_string, yara_bytes),
279 }
280 }
281
282 #[cfg_attr(docsrs, doc(cfg(feature = "yara")))]
288 #[cfg(feature = "yara")]
289 pub async fn get_unfinished_yara_tasks(&self) -> Result<Vec<crate::yara::YaraTask>> {
290 match self {
291 DatabaseType::Postgres(pg) => pg.get_unfinished_yara_tasks().await,
292 #[cfg(any(test, feature = "sqlite"))]
293 DatabaseType::SQLite(sl) => sl.get_unfinished_yara_tasks(),
294 }
295 }
296
297 #[cfg_attr(docsrs, doc(cfg(feature = "yara")))]
303 #[cfg(feature = "yara")]
304 pub async fn add_yara_match(
305 &self,
306 id: uuid::Uuid,
307 rule_name: &str,
308 file_sha256: &str,
309 ) -> Result<()> {
310 match self {
311 DatabaseType::Postgres(pg) => pg.add_yara_match(id, rule_name, file_sha256).await,
312 #[cfg(any(test, feature = "sqlite"))]
313 DatabaseType::SQLite(sl) => sl.add_yara_match(id, rule_name, file_sha256),
314 }
315 }
316
317 #[cfg_attr(docsrs, doc(cfg(feature = "yara")))]
323 #[cfg(feature = "yara")]
324 pub async fn mark_yara_task_as_finished(&self, id: uuid::Uuid) -> Result<()> {
325 match self {
326 DatabaseType::Postgres(pg) => pg.mark_yara_task_as_finished(id).await,
327 #[cfg(any(test, feature = "sqlite"))]
328 DatabaseType::SQLite(sl) => sl.mark_yara_task_as_finished(id),
329 }
330 }
331
332 #[cfg_attr(docsrs, doc(cfg(feature = "yara")))]
338 #[cfg(feature = "yara")]
339 pub async fn yara_add_next_file_id(&self, id: uuid::Uuid, file_id: u64) -> Result<()> {
340 match self {
341 DatabaseType::Postgres(pg) => pg.yara_add_next_file_id(id, file_id).await,
342 #[cfg(any(test, feature = "sqlite"))]
343 DatabaseType::SQLite(sl) => sl.yara_add_next_file_id(id, file_id),
344 }
345 }
346
347 #[cfg_attr(docsrs, doc(cfg(feature = "yara")))]
353 #[cfg(feature = "yara")]
354 pub async fn get_yara_results(
355 &self,
356 id: uuid::Uuid,
357 user_id: u32,
358 ) -> Result<malwaredb_api::YaraSearchResponse> {
359 match self {
360 DatabaseType::Postgres(pg) => pg.get_yara_results(id, user_id).await,
361 #[cfg(any(test, feature = "sqlite"))]
362 DatabaseType::SQLite(sl) => sl.get_yara_results(id, user_id),
363 }
364 }
365
366 pub async fn get_config(&self) -> Result<MDBConfig> {
372 match self {
373 DatabaseType::Postgres(pg) => pg.get_config().await,
374 #[cfg(any(test, feature = "sqlite"))]
375 DatabaseType::SQLite(sl) => sl.get_config(),
376 }
377 }
378
379 pub async fn authenticate(&self, uname: &str, password: &str) -> Result<String> {
386 match self {
387 DatabaseType::Postgres(pg) => pg.authenticate(uname, password).await,
388 #[cfg(any(test, feature = "sqlite"))]
389 DatabaseType::SQLite(sl) => sl.authenticate(uname, password),
390 }
391 }
392
393 pub async fn get_uid(&self, apikey: &str) -> Result<u32> {
400 ensure!(!apikey.is_empty(), "API key was empty");
401 match self {
402 DatabaseType::Postgres(pg) => pg.get_uid(apikey).await,
403 #[cfg(any(test, feature = "sqlite"))]
404 DatabaseType::SQLite(sl) => sl.get_uid(apikey),
405 }
406 }
407
408 pub async fn db_info(&self) -> Result<DatabaseInformation> {
414 match self {
415 DatabaseType::Postgres(pg) => pg.db_info().await,
416 #[cfg(any(test, feature = "sqlite"))]
417 DatabaseType::SQLite(sl) => sl.db_info(),
418 }
419 }
420
421 pub async fn get_user_info(&self, uid: u32) -> Result<GetUserInfoResponse> {
428 match self {
429 DatabaseType::Postgres(pg) => pg.get_user_info(uid).await,
430 #[cfg(any(test, feature = "sqlite"))]
431 DatabaseType::SQLite(sl) => sl.get_user_info(uid),
432 }
433 }
434
435 pub async fn get_user_sources(&self, uid: u32) -> Result<Sources> {
442 match self {
443 DatabaseType::Postgres(pg) => pg.get_user_sources(uid).await,
444 #[cfg(any(test, feature = "sqlite"))]
445 DatabaseType::SQLite(sl) => sl.get_user_sources(uid),
446 }
447 }
448
449 pub async fn reset_own_api_key(&self, uid: u32) -> Result<()> {
456 match self {
457 DatabaseType::Postgres(pg) => pg.reset_own_api_key(uid).await,
458 #[cfg(any(test, feature = "sqlite"))]
459 DatabaseType::SQLite(sl) => sl.reset_own_api_key(uid),
460 }
461 }
462
463 pub async fn get_known_data_types(&self) -> Result<Vec<FileType>> {
469 match self {
470 DatabaseType::Postgres(pg) => pg.get_known_data_types().await,
471 #[cfg(any(test, feature = "sqlite"))]
472 DatabaseType::SQLite(sl) => sl.get_known_data_types(),
473 }
474 }
475
476 pub async fn get_labels(&self) -> Result<Labels> {
482 match self {
483 DatabaseType::Postgres(pg) => pg.get_labels().await,
484 #[cfg(any(test, feature = "sqlite"))]
485 DatabaseType::SQLite(sl) => sl.get_labels(),
486 }
487 }
488
489 pub async fn get_type_id_for_bytes(&self, data: &[u8]) -> Result<u32> {
495 match self {
496 DatabaseType::Postgres(pg) => pg.get_type_id_for_bytes(data).await,
497 #[cfg(any(test, feature = "sqlite"))]
498 DatabaseType::SQLite(sl) => sl.get_type_id_for_bytes(data),
499 }
500 }
501
502 pub async fn allowed_user_source(&self, uid: u32, sid: u32) -> Result<bool> {
509 match self {
510 DatabaseType::Postgres(pg) => pg.allowed_user_source(uid, sid).await,
511 #[cfg(any(test, feature = "sqlite"))]
512 DatabaseType::SQLite(sl) => sl.allowed_user_source(uid, sid),
513 }
514 }
515
516 pub async fn user_is_admin(&self, uid: u32) -> Result<bool> {
524 match self {
525 DatabaseType::Postgres(pg) => pg.user_is_admin(uid).await,
526 #[cfg(any(test, feature = "sqlite"))]
527 DatabaseType::SQLite(sl) => sl.user_is_admin(uid),
528 }
529 }
530
531 pub async fn add_file(
538 &self,
539 meta: &FileMetadata,
540 known_type: KnownType<'_>,
541 uid: u32,
542 sid: u32,
543 ftype: u32,
544 parent: Option<u64>,
545 ) -> Result<FileAddedResult> {
546 match self {
547 DatabaseType::Postgres(pg) => {
548 pg.add_file(meta, known_type, uid, sid, ftype, parent).await
549 }
550 #[cfg(any(test, feature = "sqlite"))]
551 DatabaseType::SQLite(sl) => sl.add_file(meta, &known_type, uid, sid, ftype, parent),
552 }
553 }
554
555 pub async fn partial_search(&self, uid: u32, search: SearchRequest) -> Result<SearchResponse> {
561 match self {
562 DatabaseType::Postgres(pg) => pg.partial_search(uid, search).await,
563 #[cfg(any(test, feature = "sqlite"))]
564 DatabaseType::SQLite(sl) => sl.partial_search(uid, search),
565 }
566 }
567
568 pub async fn cleanup(&self) -> Result<u64> {
574 match self {
575 DatabaseType::Postgres(pg) => pg.cleanup().await,
576 #[cfg(any(test, feature = "sqlite"))]
577 DatabaseType::SQLite(sl) => sl.cleanup(),
578 }
579 }
580
581 pub async fn retrieve_sample(&self, uid: u32, hash: &HashType) -> Result<String> {
589 match self {
590 DatabaseType::Postgres(pg) => pg.retrieve_sample(uid, hash).await,
591 #[cfg(any(test, feature = "sqlite"))]
592 DatabaseType::SQLite(sl) => sl.retrieve_sample(uid, hash),
593 }
594 }
595
596 pub async fn get_sample_report(
603 &self,
604 uid: u32,
605 hash: &HashType,
606 ) -> Result<malwaredb_api::Report> {
607 match self {
608 DatabaseType::Postgres(pg) => pg.get_sample_report(uid, hash).await,
609 #[cfg(any(test, feature = "sqlite"))]
610 DatabaseType::SQLite(sl) => sl.get_sample_report(uid, hash),
611 }
612 }
613
614 pub async fn find_similar_samples(
620 &self,
621 uid: u32,
622 sim: &[(malwaredb_api::SimilarityHashType, String)],
623 ) -> Result<Vec<malwaredb_api::SimilarSample>> {
624 match self {
625 DatabaseType::Postgres(pg) => pg.find_similar_samples(uid, sim).await,
626 #[cfg(any(test, feature = "sqlite"))]
627 DatabaseType::SQLite(sl) => sl.find_similar_samples(uid, sim),
628 }
629 }
630
631 pub async fn user_allowed_files_by_sha256(
638 &self,
639 uid: u32,
640 next: Option<u64>,
641 ) -> Result<(Vec<String>, u64)> {
642 match self {
643 DatabaseType::Postgres(pg) => pg.user_allowed_files_by_sha256(uid, next).await,
644 #[cfg(any(test, feature = "sqlite"))]
645 DatabaseType::SQLite(sl) => sl.user_allowed_files_by_sha256(uid, next),
646 }
647 }
648
649 pub(crate) async fn get_encryption_keys(&self) -> Result<HashMap<u32, FileEncryption>> {
653 match self {
654 DatabaseType::Postgres(pg) => pg.get_encryption_keys().await,
655 #[cfg(any(test, feature = "sqlite"))]
656 DatabaseType::SQLite(sl) => sl.get_encryption_keys(),
657 }
658 }
659
660 pub(crate) async fn get_file_encryption_key_id(
662 &self,
663 hash: &str,
664 ) -> Result<(Option<u32>, Option<Vec<u8>>)> {
665 match self {
666 DatabaseType::Postgres(pg) => pg.get_file_encryption_key_id(hash).await,
667 #[cfg(any(test, feature = "sqlite"))]
668 DatabaseType::SQLite(sl) => sl.get_file_encryption_key_id(hash),
669 }
670 }
671
672 pub(crate) async fn set_file_nonce(&self, hash: &str, nonce: Option<&[u8]>) -> Result<()> {
674 match self {
675 DatabaseType::Postgres(pg) => pg.set_file_nonce(hash, nonce).await,
676 #[cfg(any(test, feature = "sqlite"))]
677 DatabaseType::SQLite(sl) => sl.set_file_nonce(hash, nonce),
678 }
679 }
680
681 pub(crate) async fn clear_file_crypto(&self, hash: &str) -> Result<()> {
684 match self {
685 DatabaseType::Postgres(pg) => pg.clear_file_crypto(hash).await,
686 #[cfg(any(test, feature = "sqlite"))]
687 DatabaseType::SQLite(sl) => sl.clear_file_crypto(hash),
688 }
689 }
690
691 pub async fn migrate_check(&self, action: Migration) -> Result<()> {
698 match self {
699 DatabaseType::Postgres(pg) => pg.migrate(action).await,
700 #[cfg(any(test, feature = "sqlite"))]
701 DatabaseType::SQLite(sl) => sl.migrate(action),
702 }
703 }
704
705 #[cfg(any(test, feature = "admin"))]
713 pub async fn set_name(&self, name: &str) -> Result<()> {
714 match self {
715 DatabaseType::Postgres(pg) => pg.set_name(name).await,
716 #[cfg(any(test, feature = "sqlite"))]
717 DatabaseType::SQLite(sl) => sl.set_name(name),
718 }
719 }
720
721 #[cfg(any(test, feature = "admin"))]
727 pub async fn enable_compression(&self) -> Result<()> {
728 match self {
729 DatabaseType::Postgres(pg) => pg.enable_compression().await,
730 #[cfg(any(test, feature = "sqlite"))]
731 DatabaseType::SQLite(sl) => sl.enable_compression(),
732 }
733 }
734
735 #[cfg(any(test, feature = "admin"))]
741 pub async fn disable_compression(&self) -> Result<()> {
742 match self {
743 DatabaseType::Postgres(pg) => pg.disable_compression().await,
744 #[cfg(any(test, feature = "sqlite"))]
745 DatabaseType::SQLite(sl) => sl.disable_compression(),
746 }
747 }
748
749 #[cfg(any(test, feature = "admin"))]
755 pub async fn enable_keep_unknown_files(&self) -> Result<()> {
756 match self {
757 DatabaseType::Postgres(pg) => pg.enable_keep_unknown_files().await,
758 #[cfg(any(test, feature = "sqlite"))]
759 DatabaseType::SQLite(sl) => sl.enable_keep_unknown_files(),
760 }
761 }
762
763 #[cfg(any(test, feature = "admin"))]
769 pub async fn disable_keep_unknown_files(&self) -> Result<()> {
770 match self {
771 DatabaseType::Postgres(pg) => pg.disable_keep_unknown_files().await,
772 #[cfg(any(test, feature = "sqlite"))]
773 DatabaseType::SQLite(sl) => sl.disable_keep_unknown_files(),
774 }
775 }
776
777 #[cfg(any(test, feature = "admin"))]
783 pub async fn add_file_encryption_key(&self, key: &FileEncryption) -> Result<u32> {
784 match self {
785 DatabaseType::Postgres(pg) => pg.add_file_encryption_key(key).await,
786 #[cfg(any(test, feature = "sqlite"))]
787 DatabaseType::SQLite(sl) => sl.add_file_encryption_key(key),
788 }
789 }
790
791 #[cfg(any(test, feature = "admin"))]
797 pub async fn get_encryption_key_names_ids(&self) -> Result<Vec<(u32, EncryptionOption)>> {
798 match self {
799 DatabaseType::Postgres(pg) => pg.get_encryption_key_names_ids().await,
800 #[cfg(any(test, feature = "sqlite"))]
801 DatabaseType::SQLite(sl) => sl.get_encryption_key_names_ids(),
802 }
803 }
804
805 #[allow(clippy::too_many_arguments)]
811 #[cfg(any(test, feature = "admin"))]
812 pub async fn create_user(
813 &self,
814 uname: &str,
815 fname: &str,
816 lname: &str,
817 email: &str,
818 password: Option<String>,
819 organisation: Option<&String>,
820 readonly: bool,
821 ) -> Result<u32> {
822 match self {
823 DatabaseType::Postgres(pg) => {
824 pg.create_user(uname, fname, lname, email, password, organisation, readonly)
825 .await
826 }
827 #[cfg(any(test, feature = "sqlite"))]
828 DatabaseType::SQLite(sl) => {
829 sl.create_user(uname, fname, lname, email, password, organisation, readonly)
830 }
831 }
832 }
833
834 #[cfg(any(test, feature = "admin"))]
840 pub async fn reset_api_keys(&self) -> Result<u64> {
841 match self {
842 DatabaseType::Postgres(pg) => pg.reset_api_keys().await,
843 #[cfg(any(test, feature = "sqlite"))]
844 DatabaseType::SQLite(sl) => sl.reset_api_keys(),
845 }
846 }
847
848 #[cfg(any(test, feature = "admin"))]
855 pub async fn set_password(&self, uname: &str, password: &str) -> Result<()> {
856 match self {
857 DatabaseType::Postgres(pg) => pg.set_password(uname, password).await,
858 #[cfg(any(test, feature = "sqlite"))]
859 DatabaseType::SQLite(sl) => sl.set_password(uname, password),
860 }
861 }
862
863 #[cfg(any(test, feature = "admin"))]
869 pub async fn list_users(&self) -> Result<Vec<admin::User>> {
870 match self {
871 DatabaseType::Postgres(pg) => pg.list_users().await,
872 #[cfg(any(test, feature = "sqlite"))]
873 DatabaseType::SQLite(sl) => sl.list_users(),
874 }
875 }
876
877 #[cfg(any(test, feature = "admin"))]
884 pub async fn group_id_from_name(&self, name: &str) -> Result<i32> {
885 match self {
886 DatabaseType::Postgres(pg) => pg.group_id_from_name(name).await,
887 #[cfg(any(test, feature = "sqlite"))]
888 DatabaseType::SQLite(sl) => sl.group_id_from_name(name),
889 }
890 }
891
892 #[cfg(any(test, feature = "admin"))]
899 pub async fn edit_group(
900 &self,
901 gid: u32,
902 name: &str,
903 desc: &str,
904 parent: Option<u32>,
905 ) -> Result<()> {
906 match self {
907 DatabaseType::Postgres(pg) => pg.edit_group(gid, name, desc, parent).await,
908 #[cfg(any(test, feature = "sqlite"))]
909 DatabaseType::SQLite(sl) => sl.edit_group(gid, name, desc, parent),
910 }
911 }
912
913 #[cfg(any(test, feature = "admin"))]
919 pub async fn list_groups(&self) -> Result<Vec<admin::Group>> {
920 match self {
921 DatabaseType::Postgres(pg) => pg.list_groups().await,
922 #[cfg(any(test, feature = "sqlite"))]
923 DatabaseType::SQLite(sl) => sl.list_groups(),
924 }
925 }
926
927 #[cfg(any(test, feature = "admin"))]
934 pub async fn add_user_to_group(&self, uid: u32, gid: u32) -> Result<()> {
935 match self {
936 DatabaseType::Postgres(pg) => pg.add_user_to_group(uid, gid).await,
937 #[cfg(any(test, feature = "sqlite"))]
938 DatabaseType::SQLite(sl) => sl.add_user_to_group(uid, gid),
939 }
940 }
941
942 #[cfg(any(test, feature = "admin"))]
949 pub async fn add_group_to_source(&self, gid: u32, sid: u32) -> Result<()> {
950 match self {
951 DatabaseType::Postgres(pg) => pg.add_group_to_source(gid, sid).await,
952 #[cfg(any(test, feature = "sqlite"))]
953 DatabaseType::SQLite(sl) => sl.add_group_to_source(gid, sid),
954 }
955 }
956
957 #[cfg(any(test, feature = "admin"))]
964 pub async fn create_group(
965 &self,
966 name: &str,
967 description: &str,
968 parent: Option<u32>,
969 ) -> Result<u32> {
970 match self {
971 DatabaseType::Postgres(pg) => pg.create_group(name, description, parent).await,
972 #[cfg(any(test, feature = "sqlite"))]
973 DatabaseType::SQLite(sl) => sl.create_group(name, description, parent),
974 }
975 }
976
977 #[cfg(any(test, feature = "admin"))]
983 pub async fn list_sources(&self) -> Result<Vec<admin::Source>> {
984 match self {
985 DatabaseType::Postgres(pg) => pg.list_sources().await,
986 #[cfg(any(test, feature = "sqlite"))]
987 DatabaseType::SQLite(sl) => sl.list_sources(),
988 }
989 }
990
991 #[cfg(any(test, feature = "admin"))]
998 pub async fn create_source(
999 &self,
1000 name: &str,
1001 description: Option<&str>,
1002 url: Option<&str>,
1003 date: chrono::DateTime<Local>,
1004 releasable: bool,
1005 malicious: Option<bool>,
1006 ) -> Result<u32> {
1007 match self {
1008 DatabaseType::Postgres(pg) => {
1009 pg.create_source(name, description, url, date, releasable, malicious)
1010 .await
1011 }
1012 #[cfg(any(test, feature = "sqlite"))]
1013 DatabaseType::SQLite(sl) => {
1014 sl.create_source(name, description, url, date, releasable, malicious)
1015 }
1016 }
1017 }
1018
1019 #[cfg(any(test, feature = "admin"))]
1026 pub async fn edit_user(
1027 &self,
1028 uid: u32,
1029 uname: &str,
1030 fname: &str,
1031 lname: &str,
1032 email: &str,
1033 readonly: bool,
1034 ) -> Result<()> {
1035 match self {
1036 DatabaseType::Postgres(pg) => {
1037 pg.edit_user(uid, uname, fname, lname, email, readonly)
1038 .await
1039 }
1040 #[cfg(any(test, feature = "sqlite"))]
1041 DatabaseType::SQLite(sl) => sl.edit_user(uid, uname, fname, lname, email, readonly),
1042 }
1043 }
1044
1045 #[cfg(any(test, feature = "admin"))]
1052 pub async fn deactivate_user(&self, uid: u32) -> Result<()> {
1053 match self {
1054 DatabaseType::Postgres(pg) => pg.deactivate_user(uid).await,
1055 #[cfg(any(test, feature = "sqlite"))]
1056 DatabaseType::SQLite(sl) => sl.deactivate_user(uid),
1057 }
1058 }
1059
1060 #[cfg(any(test, feature = "admin"))]
1066 pub async fn file_types_counts(&self) -> Result<HashMap<String, u32>> {
1067 match self {
1068 DatabaseType::Postgres(pg) => pg.file_types_counts().await,
1069 #[cfg(any(test, feature = "sqlite"))]
1070 DatabaseType::SQLite(sl) => sl.file_types_counts(),
1071 }
1072 }
1073
1074 #[cfg(any(test, feature = "admin"))]
1082 pub async fn create_label(&self, name: &str, parent: Option<u64>) -> Result<u64> {
1083 match self {
1084 DatabaseType::Postgres(pg) => pg.create_label(name, parent).await,
1085 #[cfg(any(test, feature = "sqlite"))]
1086 DatabaseType::SQLite(sl) => sl.create_label(name, parent),
1087 }
1088 }
1089
1090 #[cfg(any(test, feature = "admin"))]
1098 pub async fn edit_label(&self, id: u64, name: &str, parent: Option<u64>) -> Result<()> {
1099 match self {
1100 DatabaseType::Postgres(pg) => pg.edit_label(id, name, parent).await,
1101 #[cfg(any(test, feature = "sqlite"))]
1102 DatabaseType::SQLite(sl) => sl.edit_label(id, name, parent),
1103 }
1104 }
1105
1106 #[cfg(any(test, feature = "admin"))]
1113 pub async fn label_id_from_name(&self, name: &str) -> Result<u64> {
1114 match self {
1115 DatabaseType::Postgres(pg) => pg.label_id_from_name(name).await,
1116 #[cfg(any(test, feature = "sqlite"))]
1117 DatabaseType::SQLite(sl) => sl.label_id_from_name(name),
1118 }
1119 }
1120
1121 #[cfg(any(test, feature = "admin"))]
1129 pub async fn label_file(&self, file_id: u64, label_id: u64) -> Result<()> {
1130 match self {
1131 DatabaseType::Postgres(pg) => pg.label_file(file_id, label_id).await,
1132 #[cfg(any(test, feature = "sqlite"))]
1133 DatabaseType::SQLite(sl) => sl.label_file(file_id, label_id),
1134 }
1135 }
1136}
1137
1138pub fn hash_password(password: &str) -> Result<String> {
1144 let salt = SaltString::generate(&mut OsRng);
1145 let argon2 = Argon2::default();
1146 Ok(argon2
1147 .hash_password(password.as_bytes(), &salt)?
1148 .to_string())
1149}
1150
1151#[must_use]
1153pub fn random_bytes_api_key() -> String {
1154 let key1 = uuid::Uuid::new_v4();
1155 let key2 = uuid::Uuid::new_v4();
1156 let key1 = key1.to_string().replace('-', "");
1157 let key2 = key2.to_string().replace('-', "");
1158 format!("{key1}{key2}")
1159}
1160
1161#[cfg(test)]
1162mod tests {
1163 use super::*;
1164 #[cfg(feature = "vt")]
1165 use crate::vt::VtUpdater;
1166
1167 use std::fs;
1168 #[cfg(feature = "vt")]
1169 use std::sync::Arc;
1170 #[cfg(feature = "vt")]
1171 use std::time::SystemTime;
1172
1173 use anyhow::Context;
1174 use fuzzyhash::FuzzyHash;
1175 use malwaredb_api::{PartialHashSearchType, SearchRequestParameters, SearchType};
1176 use malwaredb_lzjd::{LZDict, Murmur3HashState};
1177 use tlsh_fixed::TlshBuilder;
1178 use uuid::Uuid;
1179
1180 const MALWARE_LABEL: &str = "malware";
1181 const RANSOMWARE_LABEL: &str = "ransomware";
1182
1183 fn generate_similarity_request(data: &[u8]) -> malwaredb_api::SimilarSamplesRequest {
1184 let mut hashes = vec![];
1185
1186 hashes.push((malwaredb_api::SimilarityHashType::SSDeep, FuzzyHash::new(data).to_string()));
1187
1188 let mut builder = TlshBuilder::new(
1189 tlsh_fixed::BucketKind::Bucket256,
1190 tlsh_fixed::ChecksumKind::ThreeByte,
1191 tlsh_fixed::Version::Version4,
1192 );
1193
1194 builder.update(data);
1195
1196 if let Ok(hasher) = builder.build() {
1197 hashes.push((malwaredb_api::SimilarityHashType::TLSH, hasher.hash()));
1198 }
1199
1200 let build_hasher = Murmur3HashState::default();
1201 let lzjd_str = LZDict::from_bytes_stream(data.iter().copied(), &build_hasher).to_string();
1202 hashes.push((malwaredb_api::SimilarityHashType::LZJD, lzjd_str));
1203
1204 malwaredb_api::SimilarSamplesRequest { hashes }
1205 }
1206
1207 async fn pg_config() -> Postgres {
1208 const CONNECTION_STRING: &str = "user=malwaredbtesting password=malwaredbtesting dbname=malwaredbtesting host=localhost sslmode=disable";
1211
1212 if let Ok(pg_port) = std::env::var("PG_PORT") {
1213 let conn_string = format!("{CONNECTION_STRING} port={pg_port}");
1215 Postgres::new(&conn_string, None)
1216 .await
1217 .context(format!("failed to connect to postgres with specified port {pg_port}"))
1218 .unwrap()
1219 } else {
1220 Postgres::new(CONNECTION_STRING, None).await.unwrap()
1221 }
1222 }
1223
1224 #[tokio::test]
1225 #[ignore = "don't run this in CI"]
1226 async fn pg() {
1227 let psql = pg_config().await;
1228 psql.delete().await.unwrap();
1229
1230 let psql = pg_config().await;
1231 let db = DatabaseType::Postgres(psql);
1232 everything(&db).await.unwrap();
1233
1234 #[cfg(feature = "vt")]
1235 {
1236 let db_config = db.get_config().await.unwrap();
1237 let state = crate::State {
1238 port: 8080,
1239 directory: None,
1240 max_upload: 10 * 1024 * 1024,
1241 ip: "127.0.0.1".parse().unwrap(),
1242 db_type: Arc::new(db),
1243 db_config,
1244 keys: HashMap::new(),
1245 started: SystemTime::now(),
1246 vt_client: std::env::var("VT_API_KEY")
1247 .map_or(None, |e| Some(malwaredb_virustotal::VirusTotalClient::new(e))),
1248 tls_config: None,
1249 mdns: None,
1250 };
1251
1252 let vt: VtUpdater = state.try_into().expect("failed to create VtUpdater");
1253
1254 vt.updater().await.unwrap();
1255 println!("PG: Did VT ops!");
1256
1257 let psql = pg_config().await;
1258
1259 let vt_stats = psql
1260 .get_vt_stats()
1261 .await
1262 .context("failed to get Postgres VT Stats")
1263 .unwrap();
1264 println!("{vt_stats:?}");
1265 assert!(
1266 vt_stats.files_without_records + vt_stats.clean_records + vt_stats.hits_records > 2
1267 );
1268 }
1269
1270 let psql = pg_config().await;
1272 psql.delete().await.unwrap();
1273 }
1274
1275 #[tokio::test]
1276 async fn sqlite() {
1277 const DB_FILE: &str = "testing_sqlite.db";
1278 if std::path::Path::new(DB_FILE).exists() {
1279 fs::remove_file(DB_FILE)
1280 .context(format!("failed to delete old SQLite file {DB_FILE}"))
1281 .unwrap();
1282 }
1283
1284 let sqlite = Sqlite::new(DB_FILE)
1285 .context(format!("failed to create SQLite instance for {DB_FILE}"))
1286 .unwrap();
1287
1288 let db = DatabaseType::SQLite(sqlite);
1289 everything(&db).await.unwrap();
1290
1291 #[cfg(feature = "vt")]
1292 {
1293 let db_config = db.get_config().await.unwrap();
1294 let state = crate::State {
1295 port: 8080,
1296 directory: None,
1297 max_upload: 10 * 1024 * 1024,
1298 ip: "127.0.0.1".parse().unwrap(),
1299 db_type: Arc::new(db),
1300 db_config,
1301 keys: HashMap::new(),
1302 started: SystemTime::now(),
1303 vt_client: std::env::var("VT_API_KEY")
1304 .map_or(None, |e| Some(malwaredb_virustotal::VirusTotalClient::new(e))),
1305 tls_config: None,
1306 mdns: None,
1307 };
1308
1309 let sqlite_second = Sqlite::new(DB_FILE)
1310 .context(format!("failed to create SQLite instance for {DB_FILE}"))
1311 .unwrap();
1312
1313 let vt: VtUpdater = state.try_into().expect("failed to create VtUpdater");
1314
1315 vt.updater().await.unwrap();
1316 println!("Sqlite: Did VT ops!");
1317 let vt_stats = sqlite_second
1318 .get_vt_stats()
1319 .context("failed to get Sqlite VT Stats")
1320 .unwrap();
1321 println!("{vt_stats:?}");
1322 assert!(
1323 vt_stats.files_without_records + vt_stats.clean_records + vt_stats.hits_records > 2
1324 );
1325 }
1326
1327 fs::remove_file(DB_FILE)
1328 .context(format!("failed to delete SQLite file {DB_FILE}"))
1329 .unwrap();
1330 }
1331
1332 #[allow(clippy::too_many_lines)]
1333 async fn everything(db: &DatabaseType) -> Result<()> {
1334 const ADMIN_UNAME: &str = "admin";
1335 const ADMIN_PASSWORD: &str = "super_secure_password_dont_tell_anyone!";
1336
1337 db.set_name("Testing Database")
1338 .await
1339 .context("setting instance name failed")?;
1340
1341 assert!(
1342 db.authenticate(ADMIN_UNAME, ADMIN_PASSWORD).await.is_err(),
1343 "Authentication without password should have failed."
1344 );
1345
1346 db.set_password(ADMIN_UNAME, ADMIN_PASSWORD)
1347 .await
1348 .context("failed to set admin password")?;
1349
1350 let admin_api_key = db
1351 .authenticate(ADMIN_UNAME, ADMIN_PASSWORD)
1352 .await
1353 .context("unable to get api key for admin")?;
1354 println!("API key: {admin_api_key}");
1355 assert_eq!(admin_api_key.len(), 64);
1356
1357 assert_eq!(db.get_uid(&admin_api_key).await?, 0, "Unable to get UID given the API key");
1358
1359 let admin_api_key_again = db
1360 .authenticate(ADMIN_UNAME, ADMIN_PASSWORD)
1361 .await
1362 .context("unable to get api key a second time for admin")?;
1363
1364 assert_eq!(admin_api_key, admin_api_key_again, "API keys didn't match the second time.");
1365
1366 let bad_password = "this_is_totally_not_my_password!!";
1367 eprintln!("Testing API login with incorrect password.");
1368 assert!(
1369 db.authenticate(ADMIN_UNAME, bad_password).await.is_err(),
1370 "Authenticating as admin with a bad password should have failed."
1371 );
1372
1373 let admin_is_admin = db
1374 .user_is_admin(0)
1375 .await
1376 .context("unable to see if admin (uid 0) is an admin")?;
1377 assert!(admin_is_admin);
1378
1379 let new_user_uname = "testuser";
1380 let new_user_email = "test@example.com";
1381 let new_user_password = "some_awesome_password_++";
1382 let new_id = db
1383 .create_user(
1384 new_user_uname,
1385 new_user_uname,
1386 new_user_uname,
1387 new_user_email,
1388 Some(new_user_password.into()),
1389 None,
1390 false,
1391 )
1392 .await
1393 .context(format!("failed to create user {new_user_uname}"))?;
1394
1395 let passwordless_user_id = db
1396 .create_user(
1397 "passwordless_user",
1398 "passwordless_user",
1399 "passwordless_user",
1400 "passwordless_user@example.com",
1401 None,
1402 None,
1403 false,
1404 )
1405 .await
1406 .context("failed to create passwordless_user")?;
1407
1408 for user in &db.list_users().await.context("failed to list users")? {
1409 if user.id == passwordless_user_id {
1410 assert_eq!(user.uname, "passwordless_user");
1411 }
1412 }
1413
1414 db.edit_user(
1415 passwordless_user_id,
1416 "passwordless_user_2",
1417 "passwordless_user_2",
1418 "passwordless_user_2",
1419 "passwordless_user_2@something.com",
1420 false,
1421 )
1422 .await
1423 .context(format!("failed to alter 'passwordless' user, id {passwordless_user_id}"))?;
1424
1425 for user in &db.list_users().await.context("failed to list users")? {
1426 if user.id == passwordless_user_id {
1427 assert_eq!(user.uname, "passwordless_user_2");
1428 }
1429 }
1430
1431 assert!(new_id > 0, "Weird UID created for user {new_user_uname}: {new_id}");
1432
1433 assert!(
1434 db.create_user(
1435 new_user_uname,
1436 new_user_uname,
1437 new_user_uname,
1438 new_user_email,
1439 Some(new_user_password.into()),
1440 None,
1441 false
1442 )
1443 .await
1444 .is_err(),
1445 "Creating a new user with the same user name should fail"
1446 );
1447
1448 let ro_user_name = "ro_user";
1449 let ro_user_password = "ro_user_password";
1450 db.create_user(
1451 ro_user_name,
1452 "ro_user",
1453 "ro_user",
1454 "ro@example.com",
1455 Some(ro_user_password.into()),
1456 None,
1457 true,
1458 )
1459 .await
1460 .context("failed to create read-only user")?;
1461
1462 let ro_user_api_key = db
1463 .authenticate(ro_user_name, ro_user_password)
1464 .await
1465 .context("unable to get api key for read-only user")?;
1466
1467 let new_user_password_change = "some_new_awesomer_password!_++";
1468 db.set_password(new_user_uname, new_user_password_change)
1469 .await
1470 .context("failed to change the password for testuser")?;
1471
1472 let new_user_api_key = db
1473 .authenticate(new_user_uname, new_user_password_change)
1474 .await
1475 .context("unable to get api key for testuser")?;
1476 eprintln!("{new_user_uname} got API key {new_user_api_key}");
1477
1478 assert_eq!(admin_api_key.len(), new_user_api_key.len());
1479
1480 let users = db.list_users().await.context("failed to list users")?;
1481 assert_eq!(users.len(), 4, "Four users were created, yet there are {} users", users.len());
1482 eprintln!("DB has {} users:", users.len());
1483 let mut passwordless_user_found = false;
1484 for user in users {
1485 println!("{user}");
1486 if user.uname == "passwordless_user_2" {
1487 assert!(!user.has_api_key);
1488 assert!(!user.has_password);
1489 passwordless_user_found = true;
1490 } else {
1491 assert!(user.has_api_key);
1492 assert!(user.has_password);
1493 }
1494 }
1495 assert!(passwordless_user_found);
1496
1497 let new_group_name = "some_new_group";
1498 let new_group_desc = "some_new_group_description";
1499 let new_group_id = 1;
1500 assert_eq!(
1501 db.create_group(new_group_name, new_group_desc, None)
1502 .await
1503 .context("failed to create group")?,
1504 new_group_id,
1505 "New group didn't have the expected ID, expected {new_group_id}"
1506 );
1507
1508 assert!(
1509 db.create_group(new_group_name, new_group_desc, None)
1510 .await
1511 .is_err(),
1512 "Duplicate group name should have failed"
1513 );
1514
1515 db.add_user_to_group(1, 1)
1516 .await
1517 .context("Unable to add uid 1 to gid 1")?;
1518
1519 let ro_user_uid = db
1520 .get_uid(&ro_user_api_key)
1521 .await
1522 .context("Unable to get UID for read-only user")?;
1523 db.add_user_to_group(ro_user_uid, 1)
1524 .await
1525 .context("Unable to add uid 2 to gid 1")?;
1526
1527 let new_admin_group_name = "admin_subgroup";
1528 let new_admin_group_desc = "admin_subgroup_description";
1529 let new_admin_group_id = 2;
1530 assert!(
1532 db.create_group(new_admin_group_name, new_admin_group_desc, Some(0))
1533 .await
1534 .context("failed to create admin sub-group")?
1535 >= new_admin_group_id,
1536 "New group didn't have the expected ID, expected >= {new_admin_group_id}"
1537 );
1538
1539 let groups = db.list_groups().await.context("failed to list groups")?;
1540 assert_eq!(
1541 groups.len(),
1542 3,
1543 "Three groups were created, yet there are {} groups",
1544 groups.len()
1545 );
1546 eprintln!("DB has {} groups:", groups.len());
1547 for group in groups {
1548 println!("{group}");
1549 if group.id == new_admin_group_id {
1550 assert_eq!(group.parent, Some("admin".to_string()));
1551 }
1552 if group.id == 1 {
1553 let test_user_str = String::from(new_user_uname);
1554 let mut found = false;
1555 for member in group.members {
1556 if member.uname == test_user_str {
1557 found = true;
1558 break;
1559 }
1560 }
1561 assert!(found, "new user {test_user_str} wasn't in the group");
1562 }
1563 }
1564
1565 let default_source_name = "default_source".to_string();
1566 let default_source_id = db
1567 .create_source(
1568 &default_source_name,
1569 Some("desc_default_source"),
1570 None,
1571 Local::now(),
1572 true,
1573 Some(false),
1574 )
1575 .await
1576 .context("failed to create source `default_source`")?;
1577
1578 db.add_group_to_source(1, default_source_id)
1579 .await
1580 .context("failed to add group 1 to source 1")?;
1581
1582 let another_source_name = "another_source".to_string();
1583 let another_source_id = db
1584 .create_source(
1585 &another_source_name,
1586 Some("yet another file source"),
1587 None,
1588 Local::now(),
1589 true,
1590 Some(false),
1591 )
1592 .await
1593 .context("failed to create source `another_source`")?;
1594
1595 let empty_source_name = "empty_source".to_string();
1596 db.create_source(
1597 &empty_source_name,
1598 Some("empty and unused file source"),
1599 None,
1600 Local::now(),
1601 true,
1602 Some(false),
1603 )
1604 .await
1605 .context("failed to create source `another_source`")?;
1606
1607 db.add_group_to_source(1, another_source_id)
1608 .await
1609 .context("failed to add group 1 to source 1")?;
1610
1611 let sources = db.list_sources().await.context("failed to list sources")?;
1612 eprintln!("DB has {} sources:", sources.len());
1613 for source in sources {
1614 println!("{source}");
1615 assert_eq!(source.files, 0);
1616 if source.id == default_source_id || source.id == another_source_id {
1617 assert_eq!(
1618 source.groups, 1,
1619 "default source {default_source_name} should have 1 group"
1620 );
1621 } else {
1622 assert_eq!(source.groups, 0, "groups should zero (empty)");
1623 }
1624 }
1625
1626 let uid = db
1627 .get_uid(&new_user_api_key)
1628 .await
1629 .context("failed to user uid from apikey")?;
1630 let user_info = db
1631 .get_user_info(uid)
1632 .await
1633 .context("failed to get user's available groups and sources")?;
1634 assert!(user_info.sources.contains(&default_source_name));
1635 assert!(!user_info.is_admin);
1636 println!("UserInfoResponse: {user_info:?}");
1637
1638 assert!(
1639 db.allowed_user_source(1, default_source_id)
1640 .await
1641 .context(format!(
1642 "failed to check that user 1 has access to source {default_source_id}"
1643 ))?,
1644 "User 1 should should have had access to source {default_source_id}"
1645 );
1646
1647 assert!(
1648 !db.allowed_user_source(1, 5)
1649 .await
1650 .context("failed to check that user 1 has access to source 5")?,
1651 "User 1 should should not have had access to source 5"
1652 );
1653
1654 let test_label_id = db
1655 .create_label("TestLabel", None)
1656 .await
1657 .context("failed to create test label")?;
1658 let test_elf_label_id = db
1659 .create_label("TestELF", Some(test_label_id))
1660 .await
1661 .context("failed to create test label")?;
1662
1663 let test_elf = include_bytes!("../../../types/testdata/elf/elf_linux_ppc64le").to_vec();
1664 let test_elf_meta = FileMetadata::new(&test_elf, Some("elf_linux_ppc64le"));
1665 let elf_type = db.get_type_id_for_bytes(&test_elf).await.unwrap();
1666
1667 let known_type =
1668 KnownType::new(&test_elf).context("failed to parse elf from test crate's test data")?;
1669 assert!(known_type.is_exec(), "ELF should be executable");
1670 eprintln!("ELF type ID: {elf_type}");
1671
1672 let file_addition = db
1673 .add_file(&test_elf_meta, known_type.clone(), 1, default_source_id, elf_type, None)
1674 .await
1675 .context("failed to insert a test elf")?;
1676 assert!(file_addition.is_new, "File should have been added");
1677 eprintln!("Added ELF to the DB");
1678 db.label_file(file_addition.file_id, test_elf_label_id)
1679 .await
1680 .context("failed to label file")?;
1681
1682 let partial_search = SearchRequest {
1684 search: SearchType::Search(SearchRequestParameters {
1685 partial_hash: Some((PartialHashSearchType::SHA1, "fe7d0186".into())),
1686 labels: Some(vec![String::from("TestELF")]),
1687 file_type: Some(String::from("ELF")),
1688 magic: Some(String::from("ELF")),
1689 ..Default::default()
1690 }),
1691 };
1692 assert!(partial_search.is_valid());
1693 let partial_search_response = db.partial_search(1, partial_search).await?;
1694 assert_eq!(partial_search_response.hashes.len(), 1);
1695 assert_eq!(
1696 partial_search_response.hashes[0],
1697 "897541f9f3c673b3ecc7004ff52c70c0b0440e804c7c3eb4854d72d94c317868"
1698 );
1699
1700 let partial_search = SearchRequest {
1702 search: SearchType::Search(SearchRequestParameters {
1703 partial_hash: None,
1704 labels: None,
1705 file_type: None,
1706 magic: Some(String::from("ELF")),
1707 ..Default::default()
1708 }),
1709 };
1710 assert!(partial_search.is_valid());
1711 let partial_search_response = db.partial_search(1, partial_search).await?;
1712 assert_eq!(partial_search_response.hashes.len(), 1);
1713 assert_eq!(
1714 partial_search_response.hashes[0],
1715 "897541f9f3c673b3ecc7004ff52c70c0b0440e804c7c3eb4854d72d94c317868"
1716 );
1717
1718 let partial_search = SearchRequest {
1720 search: SearchType::Search(SearchRequestParameters {
1721 partial_hash: Some((PartialHashSearchType::SHA1, "fe7d0186".into())),
1722 file_type: Some(String::from("PE32")),
1723 ..Default::default()
1724 }),
1725 };
1726 assert!(partial_search.is_valid());
1727 let partial_search_response = db.partial_search(1, partial_search).await?;
1728 assert_eq!(partial_search_response.hashes.len(), 0);
1729
1730 let partial_search = SearchRequest {
1731 search: SearchType::Search(SearchRequestParameters {
1732 file_name: Some("ppc64".into()),
1733 ..Default::default()
1734 }),
1735 };
1736 assert!(partial_search.is_valid());
1737 let partial_search_response = db.partial_search(1, partial_search).await?;
1738 assert_eq!(partial_search_response.hashes.len(), 1);
1739
1740 let partial_search = SearchRequest {
1742 search: SearchType::Search(SearchRequestParameters::default()),
1743 };
1744 assert!(!partial_search.is_valid());
1745 let partial_search_response = db.partial_search(1, partial_search).await?;
1746 assert!(partial_search_response.hashes.is_empty());
1747
1748 let partial_search = SearchRequest {
1750 search: SearchType::Continuation(Uuid::default()),
1751 };
1752 assert!(partial_search.is_valid());
1753 let partial_search_response = db.partial_search(1, partial_search).await?;
1754 assert!(partial_search_response.hashes.is_empty());
1755
1756 assert!(
1758 db.get_type_id_for_bytes(include_bytes!("../../../../MDB_Logo.ico"))
1759 .await
1760 .is_err()
1761 );
1762
1763 assert!(
1764 db.add_file(
1765 &test_elf_meta,
1766 known_type.clone(),
1767 ro_user_uid,
1768 default_source_id,
1769 elf_type,
1770 None
1771 )
1772 .await
1773 .is_err(),
1774 "Read-only user should not be able to add a file"
1775 );
1776
1777 let mut test_elf_meta_different_name = test_elf_meta.clone();
1778 test_elf_meta_different_name.name = Some("completely_different_name.bin".into());
1779
1780 assert!(
1781 !db.add_file(
1782 &test_elf_meta_different_name,
1783 known_type,
1784 1,
1785 another_source_id,
1786 elf_type,
1787 None
1788 )
1789 .await
1790 .context("failed to insert a test elf again for a different source")?
1791 .is_new
1792 );
1793
1794 let sources = db
1795 .list_sources()
1796 .await
1797 .context("failed to re-list sources")?;
1798 eprintln!("DB has {} sources, and a file was added twice:", sources.len());
1799 println!("We should have two sources with one file each, yet only one ELF.");
1800 for source in sources {
1801 println!("{source}");
1802 if source.id == default_source_id || source.id == another_source_id {
1803 assert_eq!(source.files, 1);
1804 } else {
1805 assert_eq!(source.files, 0, "groups should zero (empty)");
1806 }
1807 }
1808
1809 assert!(
1810 !db.get_user_sources(1)
1811 .await
1812 .expect("failed to get user 1's sources")
1813 .sources
1814 .is_empty()
1815 );
1816
1817 let file_types_counts = db
1818 .file_types_counts()
1819 .await
1820 .context("failed to get file types and counts")?;
1821 for (name, count) in file_types_counts {
1822 println!("{name}: {count}");
1823 assert_eq!(name, "ELF");
1824 assert_eq!(count, 1);
1825 }
1826
1827 let mut test_elf_modified = test_elf.clone();
1828 let random_bytes = Uuid::new_v4();
1829 let mut random_bytes = random_bytes.into_bytes().to_vec();
1830 test_elf_modified.append(&mut random_bytes);
1831 let similarity_request = generate_similarity_request(&test_elf_modified);
1832 let similarity_response = db
1833 .find_similar_samples(1, &similarity_request.hashes)
1834 .await
1835 .context("failed to get similarity response")?;
1836 eprintln!("Similarity response: {similarity_response:?}");
1837 let similarity_response = similarity_response.first().unwrap();
1838 assert_eq!(
1839 similarity_response.sha256,
1840 hex::encode(&test_elf_meta.sha256),
1841 "Similarity response should have had the hash of the original ELF"
1842 );
1843 for (algo, sim) in &similarity_response.algorithms {
1844 match algo {
1845 malwaredb_api::SimilarityHashType::LZJD => {
1846 assert!(*sim > 0.0f32);
1847 }
1848 malwaredb_api::SimilarityHashType::SSDeep => {
1849 assert!(*sim > 80.0f32);
1850 }
1851 malwaredb_api::SimilarityHashType::TLSH => {
1852 assert!(*sim <= 20f32);
1853 }
1854 _ => {}
1855 }
1856 }
1857
1858 let test_elf_hashtype = HashType::try_from(test_elf_meta.sha1.as_slice())
1859 .context("failed to get `HashType::SHA1` from string")?;
1860 let response_sha256 = db
1861 .retrieve_sample(1, &test_elf_hashtype)
1862 .await
1863 .context("could not get SHA-256 hash from test sample")
1864 .unwrap();
1865 assert_eq!(response_sha256, hex::encode(&test_elf_meta.sha256));
1866
1867 let test_bogus_hash =
1868 HashType::try_from("d154b8420fc56a629df2e6d918be53310d8ac39a926aa5f60ae59a66298969a0")
1869 .context("failed to get `HashType` from static string")?;
1870 assert!(
1871 db.retrieve_sample(1, &test_bogus_hash).await.is_err(),
1872 "Getting a file with a bogus hash should have failed."
1873 );
1874
1875 let test_pdf = include_bytes!("../../../types/testdata/pdf/test.pdf").to_vec();
1876 let test_pdf_meta = FileMetadata::new(&test_pdf, Some("test.pdf"));
1877 let pdf_type = db.get_type_id_for_bytes(&test_pdf).await.unwrap();
1878
1879 let known_type =
1880 KnownType::new(&test_pdf).context("failed to parse pdf from test crate's test data")?;
1881
1882 assert!(
1883 db.add_file(&test_pdf_meta, known_type, 1, default_source_id, pdf_type, None)
1884 .await
1885 .context("failed to insert a test pdf")?
1886 .is_new
1887 );
1888 eprintln!("Added PDF to the DB");
1889
1890 let test_rtf = include_bytes!("../../../types/testdata/rtf/hello.rtf").to_vec();
1891 let test_rtf_meta = FileMetadata::new(&test_rtf, Some("test.rtf"));
1892 let rtf_type = db
1893 .get_type_id_for_bytes(&test_rtf)
1894 .await
1895 .context("failed to get file type id for rtf")?;
1896
1897 let known_type =
1898 KnownType::new(&test_rtf).context("failed to parse pdf from test crate's test data")?;
1899
1900 assert!(
1901 db.add_file(&test_rtf_meta, known_type, 1, default_source_id, rtf_type, None)
1902 .await
1903 .context("failed to insert a test rtf")?
1904 .is_new
1905 );
1906 eprintln!("Added RTF to the DB");
1907
1908 let report = db
1909 .get_sample_report(1, &HashType::try_from(test_rtf_meta.sha256.as_slice()).unwrap())
1910 .await
1911 .context("failed to get report for test rtf")?;
1912 assert!(
1913 report
1914 .clone()
1915 .filecommand
1916 .unwrap()
1917 .contains("Rich Text Format")
1918 );
1919 println!("Report: {report}");
1920
1921 assert!(
1922 db.get_sample_report(
1923 999,
1924 &HashType::try_from(test_rtf_meta.sha256.as_slice()).unwrap()
1925 )
1926 .await
1927 .is_err()
1928 );
1929
1930 #[cfg(feature = "vt")]
1931 {
1932 assert!(report.vt.is_some());
1933 let files_needing_vt = db
1934 .files_without_vt_records(10)
1935 .await
1936 .context("failed to get files without VT records")?;
1937 assert!(files_needing_vt.len() > 2);
1938 println!("{} files needing VT data: {files_needing_vt:?}", files_needing_vt.len());
1939 }
1940
1941 #[cfg(not(feature = "vt"))]
1942 {
1943 assert!(report.vt.is_none());
1944 }
1945
1946 let reset = db
1947 .reset_api_keys()
1948 .await
1949 .context("failed to reset all API keys")?;
1950 eprintln!("Cleared {reset} api keys.");
1951
1952 let db_info = db.db_info().await.context("failed to get database info")?;
1953 eprintln!("DB Info: {db_info:?}");
1954
1955 let data_types = db
1956 .get_known_data_types()
1957 .await
1958 .context("failed to get data types")?;
1959 for data_type in data_types {
1960 println!("{data_type:?}");
1961 }
1962
1963 let sources = db
1964 .list_sources()
1965 .await
1966 .context("failed to list sources second time")?;
1967 eprintln!("DB has {} sources:", sources.len());
1968 for source in sources {
1969 println!("{source}");
1970 }
1971
1972 let file_types_counts = db
1973 .file_types_counts()
1974 .await
1975 .context("failed to get file types and counts")?;
1976 for (name, count) in file_types_counts {
1977 println!("{name}: {count}");
1978 assert_ne!(name, "Mach-O", "No Mach-O files have been inserted yet!");
1979 }
1980
1981 let fatmacho =
1982 include_bytes!("../../../types/testdata/macho/macho_fat_arm64_ppc_ppc64_x86_64")
1983 .to_vec();
1984 let fatmacho_meta = FileMetadata::new(&fatmacho, Some("macho_fat_arm64_ppc_ppc64_x86_64"));
1985 let fatmacho_type = db
1986 .get_type_id_for_bytes(&fatmacho)
1987 .await
1988 .context("failed to get file type for Fat Mach-O")?;
1989 let known_type = KnownType::new(&fatmacho)
1990 .context("failed to parse Fat Mach-O from type crate's test data")?;
1991
1992 assert!(
1993 db.add_file(&fatmacho_meta, known_type, 1, default_source_id, fatmacho_type, None)
1994 .await
1995 .context("failed to insert a test Fat Mach-O")?
1996 .is_new
1997 );
1998 eprintln!("Added Fat Mach-O to the DB");
1999
2000 let file_types_counts = db
2001 .file_types_counts()
2002 .await
2003 .context("failed to get file types and counts")?;
2004 for (name, count) in &file_types_counts {
2005 println!("{name}: {count}");
2006 }
2007
2008 assert_eq!(
2009 *file_types_counts.get("Mach-O").unwrap(),
2010 4,
2011 "Expected 4 Mach-O files, got {:?}",
2012 file_types_counts.get("Mach-O")
2013 );
2014
2015 let allowed_files = db
2016 .user_allowed_files_by_sha256(1, None)
2017 .await
2018 .context("failed to get allowed files")?;
2019 assert_eq!(allowed_files.0.len(), 8);
2020
2021 let allowed_files = db
2022 .user_allowed_files_by_sha256(1, Some(allowed_files.1))
2023 .await
2024 .context("failed to get allowed files")?;
2025 assert!(allowed_files.0.is_empty());
2026
2027 let malware_label_id = db
2028 .create_label(MALWARE_LABEL, None)
2029 .await
2030 .context("failed to create first label")?;
2031 let ransomware_label_id = db
2032 .create_label(RANSOMWARE_LABEL, Some(malware_label_id))
2033 .await
2034 .context("failed to create malware sub-label")?;
2035 let labels = db.get_labels().await.context("failed to get labels")?;
2036
2037 assert_eq!(labels.len(), 4, "Expected 4 labels, got {labels}");
2038 for label in labels.0 {
2039 if label.name == RANSOMWARE_LABEL {
2040 assert_eq!(label.id, ransomware_label_id);
2041 assert_eq!(label.parent.unwrap(), MALWARE_LABEL);
2042 }
2043 }
2044
2045 let source_code = include_bytes!("mod.rs");
2047 let source_meta = FileMetadata::new(source_code, Some("mod.rs"));
2048 let known_type =
2049 KnownType::new(source_code).context("failed to source code to get `Unknown` type")?;
2050
2051 assert!(matches!(known_type, KnownType::Unknown(_)));
2052
2053 let unknown_type: Vec<FileType> = db
2054 .get_known_data_types()
2055 .await?
2056 .into_iter()
2057 .filter(|t| t.name.eq_ignore_ascii_case("unknown"))
2058 .collect();
2059 let unknown_type_id = unknown_type.first().unwrap().id;
2060 assert!(db.get_type_id_for_bytes(source_code).await.is_err());
2061 db.enable_keep_unknown_files()
2062 .await
2063 .context("failed to enable keeping of unknown files")?;
2064 let source_type = db
2065 .get_type_id_for_bytes(source_code)
2066 .await
2067 .context("failed to type id for source code unknown type example")?;
2068 assert_eq!(source_type, unknown_type_id);
2069 eprintln!("Unknown file type ID: {source_type}");
2070 assert!(
2071 db.add_file(&source_meta, known_type, 1, default_source_id, unknown_type_id, None)
2072 .await
2073 .context("failed to add Rust source code file")?
2074 .is_new
2075 );
2076 eprintln!("Added Rust source code to the DB");
2077
2078 #[cfg(feature = "yara")]
2079 assert!(db.get_unfinished_yara_tasks().await?.is_empty());
2080
2081 db.reset_own_api_key(0)
2082 .await
2083 .context("failed to clear own API key uid 0")?;
2084
2085 db.deactivate_user(0)
2086 .await
2087 .context("failed to clear password and API key for uid 0")?;
2088
2089 Ok(())
2090 }
2091}