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}