Skip to main content

ploit_malper/
config.rs

1use std::env;
2use std::fs;
3use std::path::PathBuf;
4
5use serde::{Deserialize, Serialize};
6
7use crate::error::Result;
8
9fn home_dir() -> PathBuf {
10    if let Some(home) = env::var_os("HOME") {
11        PathBuf::from(home)
12    } else if let Some(profile) = env::var_os("USERPROFILE") {
13        PathBuf::from(profile)
14    } else {
15        env::current_dir().unwrap_or_else(|_| PathBuf::from("."))
16    }
17}
18
19pub fn config_dir() -> PathBuf {
20    home_dir().join(".config").join("ploit_malper")
21}
22
23pub fn config_file() -> PathBuf {
24    config_dir().join("config.json")
25}
26
27pub fn nvd_cache_file() -> PathBuf {
28    config_dir().join("nvd_cache.json")
29}
30
31pub fn db_schema_file() -> PathBuf {
32    config_dir().join("schema.pbsl")
33}
34
35pub fn default_sqlite_db() -> PathBuf {
36    config_dir().join("ploit_malper.db")
37}
38
39#[derive(Debug, Clone, Serialize, Deserialize)]
40#[serde(default)]
41pub struct MSFRPCConfig {
42    pub host: String,
43    pub port: u16,
44    pub username: String,
45    pub password: String,
46    pub ssl: bool,
47    pub workspace: String,
48}
49
50impl Default for MSFRPCConfig {
51    fn default() -> Self {
52        Self {
53            host: "127.0.0.1".to_string(),
54            port: 55553,
55            username: "msf".to_string(),
56            password: String::new(),
57            ssl: true,
58            workspace: "default".to_string(),
59        }
60    }
61}
62
63#[derive(Debug, Clone, Serialize, Deserialize, Default)]
64#[serde(default)]
65pub struct NVDConfig {
66    pub api_key: String,
67}
68
69#[derive(Debug, Clone, Serialize, Deserialize)]
70#[serde(default)]
71pub struct DatabaseConfig {
72    /// Which storage backend is active: "pocketbase" or "sqlite".
73    pub backend: String,
74    /// PocketBase instance URL (local or remote/shared).
75    pub pocketbase_url: String,
76    /// Auth token used to talk to the PocketBase API. May be empty if only
77    /// admin credentials are stored and a fresh token must be minted.
78    pub pocketbase_token: String,
79    /// Superuser / admin email for authenticating to PocketBase.
80    pub pocketbase_admin_email: String,
81    /// Superuser / admin password for authenticating to PocketBase.
82    pub pocketbase_admin_password: String,
83    /// Local SQLite database file path.
84    pub sqlite_path: String,
85    /// Whether a database has been configured through `db_setup`.
86    pub configured: bool,
87}
88
89impl Default for DatabaseConfig {
90    fn default() -> Self {
91        Self {
92            backend: "pocketbase".to_string(),
93            pocketbase_url: "http://127.0.0.1:8090".to_string(),
94            pocketbase_token: String::new(),
95            pocketbase_admin_email: String::new(),
96            pocketbase_admin_password: String::new(),
97            sqlite_path: default_sqlite_db().to_string_lossy().to_string(),
98            configured: false,
99        }
100    }
101}
102
103#[derive(Debug, Clone, Serialize, Deserialize)]
104#[serde(default)]
105pub struct AppConfig {
106    pub msfrpc: MSFRPCConfig,
107    pub nvd: NVDConfig,
108    pub database: DatabaseConfig,
109    pub report_dir: String,
110    pub last_scan_file: Option<String>,
111}
112
113impl Default for AppConfig {
114    fn default() -> Self {
115        Self {
116            msfrpc: MSFRPCConfig::default(),
117            nvd: NVDConfig::default(),
118            database: DatabaseConfig::default(),
119            report_dir: home_dir()
120                .join("ploit_malper_reports")
121                .to_string_lossy()
122                .to_string(),
123            last_scan_file: None,
124        }
125    }
126}
127
128#[derive(Debug, Clone)]
129pub struct ConfigManager {
130    pub config: AppConfig,
131    loaded: bool,
132}
133
134impl ConfigManager {
135    pub fn new() -> Self {
136        Self {
137            config: AppConfig::default(),
138            loaded: false,
139        }
140    }
141
142    pub fn load(&mut self) -> bool {
143        let path = config_file();
144        if !path.exists() {
145            return false;
146        }
147
148        match fs::read_to_string(&path) {
149            Ok(raw) => match serde_json::from_str::<AppConfig>(&raw) {
150                Ok(config) => {
151                    self.config = config;
152                    self.loaded = true;
153                    true
154                }
155                Err(_) => false,
156            },
157            Err(_) => false,
158        }
159    }
160
161    pub fn save(&mut self) -> Result<()> {
162        let dir = config_dir();
163        fs::create_dir_all(&dir)?;
164        let path = config_file();
165        let data = serde_json::to_string_pretty(&self.config)?;
166        fs::write(path, data)?;
167        self.loaded = true;
168        Ok(())
169    }
170
171    pub fn reset(&mut self) -> Result<()> {
172        let config_path = config_file();
173        if config_path.exists() {
174            fs::remove_file(config_path)?;
175        }
176
177        let cache_path = nvd_cache_file();
178        if cache_path.exists() {
179            fs::remove_file(cache_path)?;
180        }
181
182        self.config = AppConfig::default();
183        self.loaded = false;
184        Ok(())
185    }
186
187    pub fn is_configured(&self) -> bool {
188        self.loaded && !self.config.msfrpc.password.is_empty()
189    }
190
191    pub fn is_nvd_configured(&self) -> bool {
192        !self.config.nvd.api_key.is_empty()
193    }
194
195    pub fn get_msfrpc_config(&self) -> &MSFRPCConfig {
196        &self.config.msfrpc
197    }
198
199    pub fn get_nvd_config(&self) -> &NVDConfig {
200        &self.config.nvd
201    }
202
203    pub fn get_database_config(&self) -> &DatabaseConfig {
204        &self.config.database
205    }
206
207    pub fn database_configured(&self) -> bool {
208        self.config.database.configured
209    }
210}
211
212impl Default for ConfigManager {
213    fn default() -> Self {
214        Self::new()
215    }
216}