SMTP codebase import
This commit is contained in:
@@ -286,6 +286,12 @@ impl Config {
|
||||
.failed(&format!("No 'url' directive found for listener {id:?}"))
|
||||
.to_string()
|
||||
},
|
||||
max_connections: self
|
||||
.property_or_default(
|
||||
("server.listener", id, "max-connections"),
|
||||
"server.max-connections",
|
||||
)?
|
||||
.unwrap_or(8192),
|
||||
protocol,
|
||||
listeners,
|
||||
tls,
|
||||
@@ -356,149 +362,3 @@ impl ParseValue for SupportedCipherSuite {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::{fs, path::PathBuf};
|
||||
|
||||
use tokio::net::TcpSocket;
|
||||
|
||||
use crate::config::{Config, Listener, Server, ServerProtocol};
|
||||
|
||||
fn add_test_certs(config: &str) -> String {
|
||||
let mut cert_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
|
||||
cert_path.push("resources");
|
||||
cert_path.push("tests");
|
||||
cert_path.push("certs");
|
||||
let mut cert = cert_path.clone();
|
||||
cert.push("tls_cert.pem");
|
||||
let mut pk = cert_path.clone();
|
||||
pk.push("tls_privatekey.pem");
|
||||
|
||||
config
|
||||
.replace("{CERT}", cert.as_path().to_str().unwrap())
|
||||
.replace("{PK}", pk.as_path().to_str().unwrap())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_servers() {
|
||||
let mut file = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
|
||||
file.push("resources");
|
||||
file.push("tests");
|
||||
file.push("config");
|
||||
file.push("servers.toml");
|
||||
|
||||
let toml = add_test_certs(&fs::read_to_string(file).unwrap());
|
||||
|
||||
// Parse servers
|
||||
let config = Config::parse(&toml).unwrap();
|
||||
let servers = config.parse_servers().unwrap();
|
||||
let expected_servers = vec![
|
||||
Server {
|
||||
id: "smtp".to_string(),
|
||||
internal_id: 0,
|
||||
hostname: "mx.example.org".to_string(),
|
||||
data: "Stalwart SMTP - hi there!".to_string(),
|
||||
protocol: ServerProtocol::Smtp,
|
||||
listeners: vec![Listener {
|
||||
socket: TcpSocket::new_v4().unwrap(),
|
||||
addr: "127.0.0.1:9925".parse().unwrap(),
|
||||
ttl: 3600.into(),
|
||||
backlog: 1024.into(),
|
||||
}],
|
||||
tls: None,
|
||||
tls_implicit: false,
|
||||
},
|
||||
Server {
|
||||
id: "smtps".to_string(),
|
||||
internal_id: 1,
|
||||
hostname: "mx.example.org".to_string(),
|
||||
data: "Stalwart SMTP - hi there!".to_string(),
|
||||
protocol: ServerProtocol::Smtp,
|
||||
listeners: vec![
|
||||
Listener {
|
||||
socket: TcpSocket::new_v4().unwrap(),
|
||||
addr: "127.0.0.1:9465".parse().unwrap(),
|
||||
ttl: 4096.into(),
|
||||
backlog: 1024.into(),
|
||||
},
|
||||
Listener {
|
||||
socket: TcpSocket::new_v4().unwrap(),
|
||||
addr: "127.0.0.1:9466".parse().unwrap(),
|
||||
ttl: 4096.into(),
|
||||
backlog: 1024.into(),
|
||||
},
|
||||
],
|
||||
tls: None,
|
||||
tls_implicit: true,
|
||||
},
|
||||
Server {
|
||||
id: "submission".to_string(),
|
||||
internal_id: 2,
|
||||
hostname: "submit.example.org".to_string(),
|
||||
data: "Stalwart SMTP submission at your service".to_string(),
|
||||
protocol: ServerProtocol::Smtp,
|
||||
listeners: vec![Listener {
|
||||
socket: TcpSocket::new_v4().unwrap(),
|
||||
addr: "127.0.0.1:9991".parse().unwrap(),
|
||||
ttl: 3600.into(),
|
||||
backlog: 2048.into(),
|
||||
}],
|
||||
tls: None,
|
||||
tls_implicit: true,
|
||||
},
|
||||
];
|
||||
|
||||
for (server, expected_server) in servers.inner.into_iter().zip(expected_servers) {
|
||||
assert_eq!(
|
||||
server.id, expected_server.id,
|
||||
"failed for {}",
|
||||
expected_server.id
|
||||
);
|
||||
assert_eq!(
|
||||
server.internal_id, expected_server.internal_id,
|
||||
"failed for {}",
|
||||
expected_server.id
|
||||
);
|
||||
assert_eq!(
|
||||
server.hostname, expected_server.hostname,
|
||||
"failed for {}",
|
||||
expected_server.id
|
||||
);
|
||||
assert_eq!(
|
||||
server.data, expected_server.data,
|
||||
"failed for {}",
|
||||
expected_server.id
|
||||
);
|
||||
assert_eq!(
|
||||
server.protocol, expected_server.protocol,
|
||||
"failed for {}",
|
||||
expected_server.id
|
||||
);
|
||||
assert_eq!(
|
||||
server.tls_implicit, expected_server.tls_implicit,
|
||||
"failed for {}",
|
||||
expected_server.id
|
||||
);
|
||||
for (listener, expected_listener) in
|
||||
server.listeners.into_iter().zip(expected_server.listeners)
|
||||
{
|
||||
assert_eq!(
|
||||
listener.addr, expected_listener.addr,
|
||||
"failed for {}",
|
||||
expected_server.id
|
||||
);
|
||||
assert_eq!(
|
||||
listener.ttl, expected_listener.ttl,
|
||||
"failed for {}",
|
||||
expected_server.id
|
||||
);
|
||||
assert_eq!(
|
||||
listener.backlog, expected_listener.backlog,
|
||||
"failed for {}",
|
||||
expected_server.id
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ use tokio::net::TcpSocket;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct Config {
|
||||
keys: BTreeMap<String, String>,
|
||||
pub keys: BTreeMap<String, String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
@@ -46,6 +46,7 @@ pub struct Server {
|
||||
pub listeners: Vec<Listener>,
|
||||
pub tls: Option<ServerConfig>,
|
||||
pub tls_implicit: bool,
|
||||
pub max_connections: u64,
|
||||
}
|
||||
|
||||
pub struct Servers {
|
||||
|
||||
@@ -21,7 +21,18 @@
|
||||
* for more details.
|
||||
*/
|
||||
|
||||
use std::{net::IpAddr, time::Duration};
|
||||
use std::{
|
||||
net::{IpAddr, Ipv4Addr, Ipv6Addr},
|
||||
path::PathBuf,
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use mail_auth::{
|
||||
common::crypto::{Algorithm, HashAlgorithm},
|
||||
dkim::Canonicalization,
|
||||
IpLookupStrategy,
|
||||
};
|
||||
use smtp_proto::MtPriority;
|
||||
|
||||
use super::{Config, Rate};
|
||||
|
||||
@@ -342,6 +353,111 @@ impl ParseValue for bool {
|
||||
}
|
||||
}
|
||||
|
||||
impl ParseValue for Ipv4Addr {
|
||||
fn parse_value(key: impl AsKey, value: &str) -> super::Result<Self> {
|
||||
value
|
||||
.parse()
|
||||
.map_err(|_| format!("Invalid IPv4 value {:?} for key {:?}.", value, key.as_key()))
|
||||
}
|
||||
}
|
||||
|
||||
impl ParseValue for Ipv6Addr {
|
||||
fn parse_value(key: impl AsKey, value: &str) -> super::Result<Self> {
|
||||
value
|
||||
.parse()
|
||||
.map_err(|_| format!("Invalid IPv6 value {:?} for key {:?}.", value, key.as_key()))
|
||||
}
|
||||
}
|
||||
|
||||
impl ParseValue for PathBuf {
|
||||
fn parse_value(_key: impl AsKey, value: &str) -> super::Result<Self> {
|
||||
let path = PathBuf::from(value);
|
||||
|
||||
if path.exists() {
|
||||
Ok(path)
|
||||
} else {
|
||||
Err(format!("Directory {} does not exist.", path.display()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ParseValue for MtPriority {
|
||||
fn parse_value(key: impl AsKey, value: &str) -> super::Result<Self> {
|
||||
match value.to_ascii_lowercase().as_str() {
|
||||
"mixer" => Ok(MtPriority::Mixer),
|
||||
"stanag4406" => Ok(MtPriority::Stanag4406),
|
||||
"nsep" => Ok(MtPriority::Nsep),
|
||||
_ => Err(format!(
|
||||
"Invalid priority value {:?} for property {:?}.",
|
||||
value,
|
||||
key.as_key()
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ParseValue for Canonicalization {
|
||||
fn parse_value(key: impl AsKey, value: &str) -> super::Result<Self> {
|
||||
match value {
|
||||
"relaxed" => Ok(Canonicalization::Relaxed),
|
||||
"simple" => Ok(Canonicalization::Simple),
|
||||
_ => Err(format!(
|
||||
"Invalid canonicalization value {:?} for key {:?}.",
|
||||
value,
|
||||
key.as_key()
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ParseValue for IpLookupStrategy {
|
||||
fn parse_value(key: impl AsKey, value: &str) -> super::Result<Self> {
|
||||
Ok(match value.to_lowercase().as_str() {
|
||||
"ipv4-only" => IpLookupStrategy::Ipv4Only,
|
||||
"ipv6-only" => IpLookupStrategy::Ipv6Only,
|
||||
//"ipv4-and-ipv6" => IpLookupStrategy::Ipv4AndIpv6,
|
||||
"ipv6-then-ipv4" => IpLookupStrategy::Ipv6thenIpv4,
|
||||
"ipv4-then-ipv6" => IpLookupStrategy::Ipv4thenIpv6,
|
||||
_ => {
|
||||
return Err(format!(
|
||||
"Invalid IP lookup strategy {:?} for property {:?}.",
|
||||
value,
|
||||
key.as_key()
|
||||
))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl ParseValue for Algorithm {
|
||||
fn parse_value(key: impl AsKey, value: &str) -> super::Result<Self> {
|
||||
match value {
|
||||
"ed25519-sha256" | "ed25519-sha-256" => Ok(Algorithm::Ed25519Sha256),
|
||||
"rsa-sha-256" | "rsa-sha256" => Ok(Algorithm::RsaSha256),
|
||||
"rsa-sha-1" | "rsa-sha1" => Ok(Algorithm::RsaSha1),
|
||||
_ => Err(format!(
|
||||
"Invalid algorithm {:?} for key {:?}.",
|
||||
value,
|
||||
key.as_key()
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ParseValue for HashAlgorithm {
|
||||
fn parse_value(key: impl AsKey, value: &str) -> super::Result<Self> {
|
||||
match value {
|
||||
"sha256" | "sha-256" => Ok(HashAlgorithm::Sha256),
|
||||
"sha-1" | "sha1" => Ok(HashAlgorithm::Sha1),
|
||||
_ => Err(format!(
|
||||
"Invalid hash algorithm {:?} for key {:?}.",
|
||||
value,
|
||||
key.as_key()
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ParseValue for Duration {
|
||||
fn parse_value(key: impl AsKey, value: &str) -> super::Result<Self> {
|
||||
let duration = value.trim_end().to_ascii_lowercase();
|
||||
|
||||
Reference in New Issue
Block a user