Files
smartmta/rust/crates/mailer-smtp/src/client/config.rs

171 lines
5.1 KiB
Rust
Raw Normal View History

//! SMTP client configuration types.
use serde::Deserialize;
/// Configuration for connecting to an SMTP server.
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SmtpClientConfig {
/// Target SMTP server hostname.
pub host: String,
/// Target port (25 = SMTP, 465 = implicit TLS, 587 = submission).
pub port: u16,
/// Use implicit TLS (port 465). If false, STARTTLS is attempted.
#[serde(default)]
pub secure: bool,
/// Domain to use in EHLO command. Defaults to "localhost".
#[serde(default = "default_domain")]
pub domain: String,
/// Authentication credentials (optional).
pub auth: Option<SmtpAuthConfig>,
/// Connection timeout in seconds. Default: 30.
#[serde(default = "default_connection_timeout")]
pub connection_timeout_secs: u64,
/// Socket read/write timeout in seconds. Default: 120.
#[serde(default = "default_socket_timeout")]
pub socket_timeout_secs: u64,
/// Pool key override. Defaults to "host:port".
pub pool_key: Option<String>,
/// Maximum connections per pool. Default: 10.
#[serde(default = "default_max_pool_connections")]
pub max_pool_connections: usize,
/// Accept invalid TLS certificates (expired, self-signed, wrong hostname).
/// Standard for MTA-to-MTA opportunistic TLS per RFC 7435.
/// Default: false.
#[serde(default)]
pub tls_opportunistic: bool,
}
/// Authentication configuration.
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SmtpAuthConfig {
/// Username.
pub user: String,
/// Password.
pub pass: String,
/// Method: "PLAIN" or "LOGIN". Default: "PLAIN".
#[serde(default = "default_auth_method")]
pub method: String,
}
/// DKIM signing configuration (applied before sending).
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct DkimSignConfig {
/// Signing domain (e.g. "example.com").
pub domain: String,
/// DKIM selector (e.g. "default" or "mta").
pub selector: String,
/// PEM-encoded private key (RSA or Ed25519 PKCS#8).
pub private_key: String,
/// Key type: "rsa" (default) or "ed25519".
#[serde(default = "default_key_type")]
pub key_type: String,
}
fn default_key_type() -> String {
"rsa".to_string()
}
impl SmtpClientConfig {
/// Get the effective pool key for this config.
pub fn effective_pool_key(&self) -> String {
self.pool_key
.clone()
.unwrap_or_else(|| format!("{}:{}", self.host, self.port))
}
}
fn default_domain() -> String {
"localhost".to_string()
}
fn default_connection_timeout() -> u64 {
30
}
fn default_socket_timeout() -> u64 {
120
}
fn default_max_pool_connections() -> usize {
10
}
fn default_auth_method() -> String {
"PLAIN".to_string()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_deserialize_minimal_config() {
let json = r#"{"host":"mail.example.com","port":25}"#;
let config: SmtpClientConfig = serde_json::from_str(json).unwrap();
assert_eq!(config.host, "mail.example.com");
assert_eq!(config.port, 25);
assert!(!config.secure);
assert_eq!(config.domain, "localhost");
assert!(config.auth.is_none());
assert_eq!(config.connection_timeout_secs, 30);
assert_eq!(config.socket_timeout_secs, 120);
assert_eq!(config.max_pool_connections, 10);
}
#[test]
fn test_deserialize_full_config() {
let json = r#"{
"host": "smtp.gmail.com",
"port": 465,
"secure": true,
"domain": "myserver.com",
"auth": { "user": "u", "pass": "p", "method": "LOGIN" },
"connectionTimeoutSecs": 60,
"socketTimeoutSecs": 300,
"poolKey": "gmail",
"maxPoolConnections": 5
}"#;
let config: SmtpClientConfig = serde_json::from_str(json).unwrap();
assert_eq!(config.host, "smtp.gmail.com");
assert_eq!(config.port, 465);
assert!(config.secure);
assert_eq!(config.domain, "myserver.com");
assert_eq!(config.connection_timeout_secs, 60);
assert_eq!(config.socket_timeout_secs, 300);
assert_eq!(config.effective_pool_key(), "gmail");
assert_eq!(config.max_pool_connections, 5);
let auth = config.auth.unwrap();
assert_eq!(auth.user, "u");
assert_eq!(auth.pass, "p");
assert_eq!(auth.method, "LOGIN");
}
#[test]
fn test_effective_pool_key_default() {
let json = r#"{"host":"mx.example.com","port":587}"#;
let config: SmtpClientConfig = serde_json::from_str(json).unwrap();
assert_eq!(config.effective_pool_key(), "mx.example.com:587");
}
#[test]
fn test_dkim_config_deserialize() {
let json = r#"{"domain":"example.com","selector":"mta","privateKey":"-----BEGIN RSA PRIVATE KEY-----\ntest\n-----END RSA PRIVATE KEY-----"}"#;
let dkim: DkimSignConfig = serde_json::from_str(json).unwrap();
assert_eq!(dkim.domain, "example.com");
assert_eq!(dkim.selector, "mta");
assert!(dkim.private_key.contains("RSA PRIVATE KEY"));
}
}