Skip to main content

rusmes_smtp/
server.rs

1//! SMTP server implementation
2
3use crate::session::{SmtpConfig, SmtpSessionHandler};
4use rusmes_auth::AuthBackend;
5use rusmes_core::{MailProcessorRouter, RateLimiter};
6use rusmes_storage::StorageBackend;
7use std::sync::Arc;
8use tokio::net::TcpListener;
9
10/// SMTP server
11pub struct SmtpServer {
12    config: SmtpConfig,
13    bind_addr: String,
14    listener: Option<TcpListener>,
15    tls_config: Option<Arc<rustls::ServerConfig>>,
16    processor_router: Arc<MailProcessorRouter>,
17    auth_backend: Arc<dyn AuthBackend>,
18    rate_limiter: Arc<RateLimiter>,
19    storage_backend: Arc<dyn StorageBackend>,
20}
21
22impl SmtpServer {
23    /// Create a new SMTP server
24    #[allow(clippy::too_many_arguments)]
25    pub fn new(
26        config: SmtpConfig,
27        bind_addr: impl Into<String>,
28        processor_router: Arc<MailProcessorRouter>,
29        auth_backend: Arc<dyn AuthBackend>,
30        rate_limiter: Arc<RateLimiter>,
31        storage_backend: Arc<dyn StorageBackend>,
32    ) -> Self {
33        Self {
34            config,
35            bind_addr: bind_addr.into(),
36            listener: None,
37            tls_config: None,
38            processor_router,
39            auth_backend,
40            rate_limiter,
41            storage_backend,
42        }
43    }
44
45    /// Set TLS configuration
46    pub fn with_tls(mut self, tls_config: Arc<rustls::ServerConfig>) -> Self {
47        self.tls_config = Some(tls_config);
48        self
49    }
50
51    /// Bind to the configured address
52    pub async fn bind(&mut self) -> anyhow::Result<()> {
53        let listener = TcpListener::bind(&self.bind_addr).await?;
54        tracing::info!("SMTP server listening on {}", self.bind_addr);
55        self.listener = Some(listener);
56        Ok(())
57    }
58
59    /// Serve incoming connections
60    pub async fn serve(&self) -> anyhow::Result<()> {
61        let listener = self
62            .listener
63            .as_ref()
64            .ok_or_else(|| anyhow::anyhow!("Server not bound - call bind() first"))?;
65
66        loop {
67            let (stream, remote_addr) = listener.accept().await?;
68            tracing::info!("New SMTP connection from {}", remote_addr);
69
70            // Check connection rate limit
71            let ip = remote_addr.ip();
72            if !self.rate_limiter.allow_connection(ip).await {
73                tracing::warn!("Connection rate limit exceeded for {}", ip);
74                // Drop the connection without sending a response
75                drop(stream);
76                continue;
77            }
78
79            let session = SmtpSessionHandler::new(
80                stream,
81                remote_addr,
82                self.config.clone(),
83                self.processor_router.clone(),
84                self.auth_backend.clone(),
85                self.rate_limiter.clone(),
86                self.storage_backend.clone(),
87            );
88
89            let rate_limiter = self.rate_limiter.clone();
90
91            // Spawn a new task for each connection
92            tokio::spawn(async move {
93                if let Err(e) = session.handle().await {
94                    tracing::error!("SMTP session error from {}: {}", remote_addr, e);
95                }
96                // Release the connection when done
97                rate_limiter.release_connection(ip).await;
98            });
99        }
100    }
101
102    /// Run the server (bind and serve)
103    pub async fn run(mut self) -> anyhow::Result<()> {
104        self.bind().await?;
105        self.serve().await
106    }
107}
108
109#[cfg(test)]
110mod tests {
111    use super::*;
112    use rusmes_metrics::MetricsCollector;
113    use rusmes_proto::Username;
114    use rusmes_storage::{MailboxStore, MessageStore, MetadataStore};
115
116    #[allow(dead_code)]
117    struct DummyAuthBackend;
118
119    #[async_trait::async_trait]
120    impl AuthBackend for DummyAuthBackend {
121        async fn authenticate(
122            &self,
123            _username: &rusmes_proto::Username,
124            _password: &str,
125        ) -> anyhow::Result<bool> {
126            Ok(true)
127        }
128
129        async fn verify_identity(
130            &self,
131            _username: &rusmes_proto::Username,
132        ) -> anyhow::Result<bool> {
133            Ok(true)
134        }
135
136        async fn list_users(&self) -> anyhow::Result<Vec<rusmes_proto::Username>> {
137            Ok(Vec::new())
138        }
139
140        async fn create_user(
141            &self,
142            _username: &rusmes_proto::Username,
143            _password: &str,
144        ) -> anyhow::Result<()> {
145            Ok(())
146        }
147
148        async fn delete_user(&self, _username: &rusmes_proto::Username) -> anyhow::Result<()> {
149            Ok(())
150        }
151
152        async fn change_password(
153            &self,
154            _username: &rusmes_proto::Username,
155            _new_password: &str,
156        ) -> anyhow::Result<()> {
157            Ok(())
158        }
159    }
160
161    #[allow(dead_code)]
162    struct DummyMailboxStore;
163
164    #[async_trait::async_trait]
165    impl MailboxStore for DummyMailboxStore {
166        async fn create_mailbox(
167            &self,
168            _path: &rusmes_storage::MailboxPath,
169        ) -> anyhow::Result<rusmes_storage::MailboxId> {
170            Ok(rusmes_storage::MailboxId::new())
171        }
172
173        async fn delete_mailbox(&self, _id: &rusmes_storage::MailboxId) -> anyhow::Result<()> {
174            Ok(())
175        }
176
177        async fn rename_mailbox(
178            &self,
179            _id: &rusmes_storage::MailboxId,
180            _new_path: &rusmes_storage::MailboxPath,
181        ) -> anyhow::Result<()> {
182            Ok(())
183        }
184
185        async fn get_mailbox(
186            &self,
187            _id: &rusmes_storage::MailboxId,
188        ) -> anyhow::Result<Option<rusmes_storage::Mailbox>> {
189            Ok(None)
190        }
191
192        async fn list_mailboxes(
193            &self,
194            _user: &Username,
195        ) -> anyhow::Result<Vec<rusmes_storage::Mailbox>> {
196            Ok(Vec::new())
197        }
198
199        async fn get_user_inbox(
200            &self,
201            _user: &Username,
202        ) -> anyhow::Result<Option<rusmes_storage::MailboxId>> {
203            Ok(None)
204        }
205
206        async fn subscribe_mailbox(
207            &self,
208            _user: &Username,
209            _mailbox_name: String,
210        ) -> anyhow::Result<()> {
211            Ok(())
212        }
213
214        async fn unsubscribe_mailbox(
215            &self,
216            _user: &Username,
217            _mailbox_name: &str,
218        ) -> anyhow::Result<()> {
219            Ok(())
220        }
221
222        async fn list_subscriptions(&self, _user: &Username) -> anyhow::Result<Vec<String>> {
223            Ok(Vec::new())
224        }
225    }
226
227    #[allow(dead_code)]
228    struct DummyMessageStore;
229
230    #[async_trait::async_trait]
231    impl MessageStore for DummyMessageStore {
232        async fn append_message(
233            &self,
234            _mailbox_id: &rusmes_storage::MailboxId,
235            _message: rusmes_proto::Mail,
236        ) -> anyhow::Result<rusmes_storage::MessageMetadata> {
237            Ok(rusmes_storage::MessageMetadata::new(
238                rusmes_proto::MessageId::new(),
239                rusmes_storage::MailboxId::new(),
240                1,
241                rusmes_storage::MessageFlags::new(),
242                0,
243            ))
244        }
245
246        async fn get_message(
247            &self,
248            _message_id: &rusmes_proto::MessageId,
249        ) -> anyhow::Result<Option<rusmes_proto::Mail>> {
250            Ok(None)
251        }
252
253        async fn delete_messages(
254            &self,
255            _message_ids: &[rusmes_proto::MessageId],
256        ) -> anyhow::Result<()> {
257            Ok(())
258        }
259
260        async fn set_flags(
261            &self,
262            _message_ids: &[rusmes_proto::MessageId],
263            _flags: rusmes_storage::MessageFlags,
264        ) -> anyhow::Result<()> {
265            Ok(())
266        }
267
268        async fn search(
269            &self,
270            _mailbox_id: &rusmes_storage::MailboxId,
271            _criteria: rusmes_storage::SearchCriteria,
272        ) -> anyhow::Result<Vec<rusmes_proto::MessageId>> {
273            Ok(Vec::new())
274        }
275
276        async fn copy_messages(
277            &self,
278            _message_ids: &[rusmes_proto::MessageId],
279            _dest_mailbox_id: &rusmes_storage::MailboxId,
280        ) -> anyhow::Result<Vec<rusmes_storage::MessageMetadata>> {
281            Ok(Vec::new())
282        }
283
284        async fn get_mailbox_messages(
285            &self,
286            _mailbox_id: &rusmes_storage::MailboxId,
287        ) -> anyhow::Result<Vec<rusmes_storage::MessageMetadata>> {
288            Ok(Vec::new())
289        }
290    }
291
292    #[allow(dead_code)]
293    struct DummyMetadataStore;
294
295    #[async_trait::async_trait]
296    impl MetadataStore for DummyMetadataStore {
297        async fn get_user_quota(&self, _user: &Username) -> anyhow::Result<rusmes_storage::Quota> {
298            Ok(rusmes_storage::Quota::new(0, 1024 * 1024 * 1024))
299        }
300
301        async fn set_user_quota(
302            &self,
303            _user: &Username,
304            _quota: rusmes_storage::Quota,
305        ) -> anyhow::Result<()> {
306            Ok(())
307        }
308
309        async fn get_mailbox_counters(
310            &self,
311            _mailbox_id: &rusmes_storage::MailboxId,
312        ) -> anyhow::Result<rusmes_storage::MailboxCounters> {
313            Ok(rusmes_storage::MailboxCounters::default())
314        }
315    }
316
317    #[allow(dead_code)]
318    struct DummyStorageBackend {
319        mailbox_store: Arc<dyn MailboxStore>,
320        message_store: Arc<dyn MessageStore>,
321        metadata_store: Arc<dyn MetadataStore>,
322    }
323
324    impl StorageBackend for DummyStorageBackend {
325        fn mailbox_store(&self) -> Arc<dyn MailboxStore> {
326            self.mailbox_store.clone()
327        }
328
329        fn message_store(&self) -> Arc<dyn MessageStore> {
330            self.message_store.clone()
331        }
332
333        fn metadata_store(&self) -> Arc<dyn MetadataStore> {
334            self.metadata_store.clone()
335        }
336    }
337
338    #[test]
339    fn test_server_creation() {
340        let config = SmtpConfig::default();
341        let metrics = Arc::new(MetricsCollector::new());
342        let router = Arc::new(MailProcessorRouter::new(metrics));
343        let auth = Arc::new(DummyAuthBackend);
344        let rate_limiter = Arc::new(rusmes_core::RateLimiter::new(
345            rusmes_core::RateLimitConfig::default(),
346        ));
347        let storage: Arc<dyn StorageBackend> = Arc::new(DummyStorageBackend {
348            mailbox_store: Arc::new(DummyMailboxStore),
349            message_store: Arc::new(DummyMessageStore),
350            metadata_store: Arc::new(DummyMetadataStore),
351        });
352
353        let server = SmtpServer::new(
354            config.clone(),
355            "127.0.0.1:2525",
356            router,
357            auth,
358            rate_limiter,
359            storage,
360        );
361
362        assert_eq!(server.bind_addr, "127.0.0.1:2525");
363        assert_eq!(server.config.hostname, config.hostname);
364    }
365}