Skip to main content

rusmes_cli/commands/
check_config.rs

1//! Validate configuration file
2
3use anyhow::{Context, Result};
4use rusmes_config::ServerConfig;
5use std::path::Path;
6
7/// Check and validate configuration file
8pub fn run(config_path: &str) -> Result<()> {
9    println!("Checking configuration: {}", config_path);
10
11    // Check if file exists
12    let path = Path::new(config_path);
13    if !path.exists() {
14        anyhow::bail!("Configuration file not found: {}", config_path);
15    }
16
17    // Load configuration
18    let config = ServerConfig::from_file(config_path).context("Failed to load configuration")?;
19
20    println!("Configuration loaded successfully");
21    println!();
22
23    // Validate basic settings
24    println!("Basic settings:");
25    println!("  Domain: {}", config.domain);
26    println!("  Postmaster: {}", config.postmaster);
27
28    // Validate postmaster address
29    config
30        .postmaster_address()
31        .context("Invalid postmaster address")?;
32    println!("  Postmaster address: valid");
33
34    // Validate SMTP settings
35    println!();
36    println!("SMTP server:");
37    println!("  Host: {}", config.smtp.host);
38    println!("  Port: {}", config.smtp.port);
39    println!("  Max message size: {}", config.smtp.max_message_size);
40
41    let max_size = config
42        .smtp
43        .max_message_size_bytes()
44        .context("Invalid max_message_size format")?;
45    println!("  Max message size (bytes): {}", max_size);
46
47    if let Some(tls_port) = config.smtp.tls_port {
48        println!("  TLS port: {}", tls_port);
49    }
50
51    if let Some(rate_limit) = &config.smtp.rate_limit {
52        println!(
53            "  Rate limit: {} msgs/hour",
54            rate_limit.max_messages_per_hour
55        );
56        let window = rate_limit
57            .window_duration_seconds()
58            .context("Invalid rate_limit window_duration")?;
59        println!("  Rate limit window: {}s", window);
60    }
61
62    // Validate IMAP settings
63    if let Some(imap) = &config.imap {
64        println!();
65        println!("IMAP server:");
66        println!("  Host: {}", imap.host);
67        println!("  Port: {}", imap.port);
68        if let Some(tls_port) = imap.tls_port {
69            println!("  TLS port: {}", tls_port);
70        }
71    }
72
73    // Validate JMAP settings
74    if let Some(jmap) = &config.jmap {
75        println!();
76        println!("JMAP server:");
77        println!("  Host: {}", jmap.host);
78        println!("  Port: {}", jmap.port);
79        println!("  Base URL: {}", jmap.base_url);
80    }
81
82    // Validate storage settings
83    println!();
84    println!("Storage:");
85    match &config.storage {
86        rusmes_config::StorageConfig::Filesystem { path } => {
87            println!("  Backend: filesystem");
88            println!("  Path: {}", path);
89        }
90        rusmes_config::StorageConfig::Postgres { connection_string } => {
91            println!("  Backend: postgres");
92            println!("  Connection: {}", connection_string);
93        }
94        rusmes_config::StorageConfig::AmateRS {
95            endpoints,
96            replication_factor,
97        } => {
98            println!("  Backend: AmateRS");
99            println!("  Endpoints: {:?}", endpoints);
100            println!("  Replication factor: {}", replication_factor);
101        }
102    }
103
104    // Validate auth settings
105    if let Some(auth) = &config.auth {
106        println!();
107        println!("Authentication:");
108        match auth {
109            rusmes_config::AuthConfig::File {
110                config: file_config,
111            } => {
112                println!("  Backend: file");
113                println!("  Path: {}", file_config.path);
114            }
115            rusmes_config::AuthConfig::Ldap {
116                config: ldap_config,
117            } => {
118                println!("  Backend: LDAP");
119                println!("  URL: {}", ldap_config.url);
120                println!("  Base DN: {}", ldap_config.base_dn);
121            }
122            rusmes_config::AuthConfig::Sql { config: sql_config } => {
123                println!("  Backend: SQL");
124                println!("  Connection: {}", sql_config.connection_string);
125            }
126            rusmes_config::AuthConfig::OAuth2 {
127                config: oauth_config,
128            } => {
129                println!("  Backend: OAuth2");
130                println!("  Token URL: {}", oauth_config.token_url);
131            }
132        }
133    }
134
135    // Validate logging settings
136    if let Some(logging) = &config.logging {
137        println!();
138        println!("Logging:");
139        println!("  Level: {}", logging.level);
140        println!("  Format: {}", logging.format);
141        println!("  Output: {}", logging.output);
142
143        logging.validate_level().context("Invalid log level")?;
144        logging.validate_format().context("Invalid log format")?;
145
146        if let Some(file) = &logging.file {
147            println!("  File path: {}", file.path);
148            println!("  Max size: {}", file.max_size);
149            println!("  Max backups: {}", file.max_backups);
150            println!("  Compress: {}", file.compress);
151
152            let max_size = file
153                .max_size_bytes()
154                .context("Invalid log file max_size format")?;
155            println!("  Max size (bytes): {}", max_size);
156        }
157    }
158
159    // Validate queue settings
160    if let Some(queue) = &config.queue {
161        println!();
162        println!("Queue:");
163        println!("  Initial delay: {}", queue.initial_delay);
164        println!("  Max delay: {}", queue.max_delay);
165        println!("  Backoff multiplier: {}", queue.backoff_multiplier);
166        println!("  Max attempts: {}", queue.max_attempts);
167        println!("  Worker threads: {}", queue.worker_threads);
168        println!("  Batch size: {}", queue.batch_size);
169
170        let initial_delay = queue
171            .initial_delay_seconds()
172            .context("Invalid queue initial_delay format")?;
173        let max_delay = queue
174            .max_delay_seconds()
175            .context("Invalid queue max_delay format")?;
176        println!("  Initial delay (seconds): {}", initial_delay);
177        println!("  Max delay (seconds): {}", max_delay);
178
179        queue
180            .validate_backoff_multiplier()
181            .context("Invalid backoff_multiplier")?;
182        queue
183            .validate_worker_threads()
184            .context("Invalid worker_threads")?;
185    }
186
187    // Validate security settings
188    if let Some(security) = &config.security {
189        println!();
190        println!("Security:");
191        println!("  Relay networks: {:?}", security.relay_networks);
192        println!("  Blocked IPs: {:?}", security.blocked_ips);
193        println!(
194            "  Check recipient exists: {}",
195            security.check_recipient_exists
196        );
197        println!(
198            "  Reject unknown recipients: {}",
199            security.reject_unknown_recipients
200        );
201
202        security
203            .validate_relay_networks()
204            .context("Invalid relay_networks")?;
205        security
206            .validate_blocked_ips()
207            .context("Invalid blocked_ips")?;
208    }
209
210    // Validate domains settings
211    if let Some(domains) = &config.domains {
212        println!();
213        println!("Domains:");
214        println!("  Local domains: {:?}", domains.local_domains);
215
216        domains
217            .validate_local_domains()
218            .context("Invalid local_domains")?;
219
220        if !domains.aliases.is_empty() {
221            println!("  Aliases: {} configured", domains.aliases.len());
222            domains.validate_aliases().context("Invalid aliases")?;
223        }
224    }
225
226    // Validate metrics settings
227    if let Some(metrics) = &config.metrics {
228        println!();
229        println!("Metrics:");
230        println!("  Enabled: {}", metrics.enabled);
231        println!("  Bind address: {}", metrics.bind_address);
232        println!("  Path: {}", metrics.path);
233
234        metrics
235            .validate_bind_address()
236            .context("Invalid metrics bind_address")?;
237        metrics.validate_path().context("Invalid metrics path")?;
238    }
239
240    // Validate processors
241    println!();
242    println!("Processors:");
243    println!("  Total processors: {}", config.processors.len());
244    for processor in &config.processors {
245        println!("  - {} (state: {})", processor.name, processor.state);
246        println!("    Mailets: {}", processor.mailets.len());
247    }
248
249    // All checks passed
250    println!();
251    println!("Configuration is valid!");
252
253    Ok(())
254}
255
256#[cfg(test)]
257mod tests {
258    use super::*;
259    use std::io::Write;
260    use tempfile::Builder;
261
262    #[test]
263    fn test_valid_minimal_config() {
264        let config = r#"
265domain = "example.com"
266postmaster = "postmaster@example.com"
267
268[smtp]
269host = "0.0.0.0"
270port = 25
271max_message_size = "50MB"
272
273[storage]
274backend = "filesystem"
275path = "./data/mailboxes"
276
277[[processors]]
278name = "root"
279state = "root"
280mailets = []
281"#;
282
283        let mut file = Builder::new().suffix(".toml").tempfile().unwrap();
284        file.write_all(config.as_bytes()).unwrap();
285        let path = file.path().to_str().unwrap().to_string();
286
287        let result = run(&path);
288        assert!(result.is_ok());
289    }
290
291    #[test]
292    fn test_missing_config_file() {
293        let result = run("/nonexistent/path/to/config.toml");
294        assert!(result.is_err());
295    }
296
297    #[test]
298    fn test_config_with_all_backends() {
299        let config = r#"
300domain = "example.com"
301postmaster = "postmaster@example.com"
302
303[smtp]
304host = "0.0.0.0"
305port = 25
306max_message_size = "50MB"
307
308[storage]
309backend = "postgres"
310connection_string = "postgresql://localhost/rusmes"
311
312[auth]
313backend = "file"
314path = "./data/users.db"
315
316[[processors]]
317name = "root"
318state = "root"
319mailets = []
320"#;
321
322        let mut file = Builder::new().suffix(".toml").tempfile().unwrap();
323        file.write_all(config.as_bytes()).unwrap();
324        let path = file.path().to_str().unwrap().to_string();
325
326        let result = run(&path);
327        assert!(result.is_ok());
328    }
329}