171 lines
5.1 KiB
Rust
171 lines
5.1 KiB
Rust
//! 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"));
|
|
}
|
|
}
|