Skip to main content

rusmes_config/
listeners.rs

1//! Protocol listener configuration types.
2//!
3//! This module contains the per-protocol server listener configuration structs:
4//! [`SmtpServerConfig`], [`SmtpOutboundConfig`], [`RateLimitConfig`],
5//! [`ImapServerConfig`], [`JmapServerConfig`], [`JmapPushConfig`],
6//! [`Pop3ServerConfig`], and [`RelayConfig`].
7
8use crate::parse::{
9    default_idle_timeout, default_max_connections_per_ip, default_max_total_connections,
10    default_reaper_interval, parse_duration, parse_size,
11};
12use serde::{Deserialize, Serialize};
13
14// --------------------------------------------------------------------------
15// SmtpOutboundConfig
16// --------------------------------------------------------------------------
17
18/// Configuration for outbound SMTP connection pooling.
19///
20/// Controls the pool of reusable outbound SMTP connections maintained by
21/// `OutboundPool`.  Defaults are intentionally conservative; tune for your
22/// deployment's message volume.
23///
24/// ## TOML example
25///
26/// ```toml
27/// [smtp.outbound]
28/// idle_timeout_secs = 30
29/// per_remote_cap    = 8
30/// global_cap        = 256
31/// ```
32#[derive(Debug, Clone, Deserialize, Serialize)]
33pub struct SmtpOutboundConfig {
34    /// Default: `30`. Seconds a connection may sit idle before the background
35    /// reaper closes it.
36    #[serde(default = "default_outbound_idle_timeout")]
37    pub idle_timeout_secs: u64,
38
39    /// Default: `8`. Maximum pooled connections to a single remote address.
40    #[serde(default = "default_outbound_per_remote_cap")]
41    pub per_remote_cap: usize,
42
43    /// Default: `256`. Total pooled connections across all remote addresses.
44    #[serde(default = "default_outbound_global_cap")]
45    pub global_cap: usize,
46}
47
48fn default_outbound_idle_timeout() -> u64 {
49    30
50}
51
52fn default_outbound_per_remote_cap() -> usize {
53    8
54}
55
56fn default_outbound_global_cap() -> usize {
57    256
58}
59
60impl Default for SmtpOutboundConfig {
61    fn default() -> Self {
62        Self {
63            idle_timeout_secs: default_outbound_idle_timeout(),
64            per_remote_cap: default_outbound_per_remote_cap(),
65            global_cap: default_outbound_global_cap(),
66        }
67    }
68}
69
70// --------------------------------------------------------------------------
71// SmtpServerConfig
72// --------------------------------------------------------------------------
73
74/// SMTP server listener configuration.
75#[derive(Debug, Clone, Deserialize, Serialize)]
76pub struct SmtpServerConfig {
77    /// Required. IP address or hostname on which the SMTP listener binds
78    /// (e.g. `"0.0.0.0"` for all interfaces).
79    pub host: String,
80
81    /// Required. SMTP port number (typically `25` for server-to-server or
82    /// `587` for mail submission). Must be in the range `1–65535`.
83    pub port: u16,
84
85    /// Default: `None`. SMTPS (implicit TLS) port number, typically `465`.
86    /// Requires a `[tls]` section to be configured.
87    #[serde(default)]
88    pub tls_port: Option<u16>,
89
90    /// Required. Maximum accepted message size, expressed as a human-readable
91    /// string (e.g. `"50MB"`, `"1GB"`). Parsed by [`SmtpServerConfig::max_message_size_bytes`].
92    pub max_message_size: String,
93
94    /// Default: `false`. When `true`, the server requires SMTP AUTH before
95    /// accepting mail for delivery. Recommended for submission ports.
96    #[serde(default)]
97    pub require_auth: bool,
98
99    /// Default: `false`. When `true`, advertises the STARTTLS extension and
100    /// upgrades connections on demand. Requires a `[tls]` section.
101    #[serde(default)]
102    pub enable_starttls: bool,
103
104    /// Default: `None`. Per-IP and per-hour rate limiting for SMTP connections.
105    /// When absent no rate limiting is applied.
106    #[serde(default)]
107    pub rate_limit: Option<RateLimitConfig>,
108
109    /// Default: `SmtpOutboundConfig::default()`. Outbound connection pool
110    /// parameters.  Omitting the `[smtp.outbound]` subsection uses built-in
111    /// defaults (30 s idle, 8 per-remote, 256 global cap).
112    #[serde(default)]
113    pub outbound: SmtpOutboundConfig,
114}
115
116impl SmtpServerConfig {
117    /// Parse max message size to bytes.
118    pub fn max_message_size_bytes(&self) -> anyhow::Result<usize> {
119        parse_size(&self.max_message_size)
120    }
121}
122
123// --------------------------------------------------------------------------
124// RateLimitConfig
125// --------------------------------------------------------------------------
126
127/// Per-IP SMTP rate limiting parameters.
128#[derive(Debug, Clone, Deserialize, Serialize)]
129pub struct RateLimitConfig {
130    /// Default: `10`. Maximum number of simultaneous SMTP connections allowed
131    /// from a single remote IP address.
132    #[serde(default = "default_max_connections_per_ip")]
133    pub max_connections_per_ip: usize,
134
135    /// Required. Maximum number of accepted messages per `window_duration`
136    /// from a single IP address.
137    pub max_messages_per_hour: u32,
138
139    /// Required. Length of the rate-limiting window as a human-readable
140    /// duration string (e.g. `"1h"`, `"30m"`, `"3600s"`).
141    pub window_duration: String,
142}
143
144impl RateLimitConfig {
145    /// Parse window duration to seconds.
146    pub fn window_duration_seconds(&self) -> anyhow::Result<u64> {
147        parse_duration(&self.window_duration)
148    }
149}
150
151// --------------------------------------------------------------------------
152// ImapServerConfig
153// --------------------------------------------------------------------------
154
155/// IMAP4rev1 server listener configuration.
156#[derive(Debug, Clone, Deserialize, Serialize)]
157pub struct ImapServerConfig {
158    /// Required. IP address or hostname on which the IMAP listener binds
159    /// (e.g. `"0.0.0.0"` for all interfaces).
160    pub host: String,
161
162    /// Required. IMAP port number (typically `143`). Must be in `1–65535`.
163    pub port: u16,
164
165    /// Default: `None`. IMAPS (implicit TLS) port number, typically `993`.
166    /// Requires a `[tls]` section to be configured.
167    #[serde(default)]
168    pub tls_port: Option<u16>,
169}
170
171// --------------------------------------------------------------------------
172// JmapServerConfig / JmapPushConfig
173// --------------------------------------------------------------------------
174
175/// JMAP over HTTP server listener configuration.
176#[derive(Debug, Clone, Deserialize, Serialize)]
177pub struct JmapServerConfig {
178    /// Required. IP address or hostname on which the JMAP HTTP listener binds
179    /// (e.g. `"0.0.0.0"` for all interfaces).
180    pub host: String,
181
182    /// Required. HTTP port number for the JMAP API (typically `8080`).
183    /// Must be in the range `1–65535`.
184    pub port: u16,
185
186    /// Required. Externally reachable base URL returned in JMAP Session
187    /// resources (e.g. `"https://jmap.example.com"`).
188    pub base_url: String,
189
190    /// Optional WebPush / VAPID push delivery configuration.
191    /// When absent, WebPush delivery is disabled.
192    #[serde(default)]
193    pub push: Option<JmapPushConfig>,
194}
195
196/// JMAP WebPush delivery configuration (RFC 8030 + RFC 8444 VAPID).
197#[derive(Debug, Clone, Deserialize, Serialize)]
198pub struct JmapPushConfig {
199    /// Path to the VAPID ES256 private key PEM file.
200    ///
201    /// If the file does not exist when the server starts it will be
202    /// generated and written to this path automatically.  Omit this field
203    /// to use an ephemeral in-memory key (the key changes on every restart,
204    /// which forces all push subscriptions to re-verify).
205    #[serde(default)]
206    pub vapid_key_path: Option<std::path::PathBuf>,
207
208    /// `mailto:` URI or email address used as the `sub` claim in VAPID JWTs.
209    ///
210    /// RFC 8444 requires this to identify a point of contact for push
211    /// endpoint operators.  Defaults to `"mailto:admin@localhost"`.
212    #[serde(default = "default_vapid_admin_email")]
213    pub admin_email: String,
214}
215
216fn default_vapid_admin_email() -> String {
217    "admin@localhost".to_string()
218}
219
220impl Default for JmapPushConfig {
221    fn default() -> Self {
222        Self {
223            vapid_key_path: None,
224            admin_email: default_vapid_admin_email(),
225        }
226    }
227}
228
229// --------------------------------------------------------------------------
230// Pop3ServerConfig
231// --------------------------------------------------------------------------
232
233/// POP3 server listener configuration.
234#[derive(Debug, Clone, Deserialize, Serialize)]
235pub struct Pop3ServerConfig {
236    /// Required. IP address or hostname on which the POP3 listener binds
237    /// (e.g. `"0.0.0.0"` for all interfaces).
238    pub host: String,
239
240    /// Required. POP3 port number (typically `110`). Must be in `1–65535`.
241    pub port: u16,
242
243    /// Default: `None`. POP3S (implicit TLS) port number, typically `995`.
244    /// Requires a `[tls]` section to be configured.
245    #[serde(default)]
246    pub tls_port: Option<u16>,
247
248    /// Default: `600`. Session inactivity timeout in seconds after which an
249    /// idle POP3 connection is dropped.
250    #[serde(default = "default_pop3_timeout")]
251    pub timeout_seconds: u64,
252
253    /// Default: `false`. When `true`, advertises the STLS capability
254    /// (RFC 2595) to upgrade plain POP3 connections to TLS.
255    #[serde(default)]
256    pub enable_stls: bool,
257}
258
259fn default_pop3_timeout() -> u64 {
260    600
261}
262
263// --------------------------------------------------------------------------
264// RelayConfig
265// --------------------------------------------------------------------------
266
267/// Outbound SMTP relay ("smart-host") configuration.
268///
269/// When present, rusmes forwards all outbound mail through the specified relay
270/// instead of delivering directly via DNS MX lookup.
271#[derive(Debug, Clone, Deserialize, Serialize)]
272pub struct RelayConfig {
273    /// Required. Hostname or IP address of the upstream SMTP relay.
274    pub host: String,
275
276    /// Required. TCP port of the upstream SMTP relay (e.g. `587` or `25`).
277    pub port: u16,
278
279    /// Default: `None`. Optional SMTP AUTH username for the relay.
280    #[serde(default)]
281    pub username: Option<String>,
282
283    /// Default: `None`. Optional SMTP AUTH password for the relay.
284    #[serde(default)]
285    pub password: Option<String>,
286
287    /// Default: `true`. When `true`, the relay connection is secured with
288    /// TLS (STARTTLS or implicit TLS depending on the relay port).
289    #[serde(default = "default_use_tls")]
290    pub use_tls: bool,
291}
292
293fn default_use_tls() -> bool {
294    true
295}
296
297// --------------------------------------------------------------------------
298// ConnectionLimitsConfig
299// --------------------------------------------------------------------------
300
301/// Connection limits configuration.
302#[derive(Debug, Clone, Deserialize, Serialize)]
303pub struct ConnectionLimitsConfig {
304    /// Maximum connections per IP address (0 = unlimited).
305    #[serde(default = "default_max_connections_per_ip")]
306    pub max_connections_per_ip: usize,
307    /// Maximum total connections (0 = unlimited).
308    #[serde(default = "default_max_total_connections")]
309    pub max_total_connections: usize,
310    /// Idle timeout for connections (e.g., `"300s"`, `"5m"`).
311    #[serde(default = "default_idle_timeout")]
312    pub idle_timeout: String,
313    /// Reaper interval for cleaning up idle connections (e.g., `"60s"`, `"1m"`).
314    #[serde(default = "default_reaper_interval")]
315    pub reaper_interval: String,
316}
317
318impl ConnectionLimitsConfig {
319    /// Parse idle timeout to seconds.
320    pub fn idle_timeout_seconds(&self) -> anyhow::Result<u64> {
321        parse_duration(&self.idle_timeout)
322    }
323
324    /// Parse reaper interval to seconds.
325    pub fn reaper_interval_seconds(&self) -> anyhow::Result<u64> {
326        parse_duration(&self.reaper_interval)
327    }
328}