SMTP codebase import

This commit is contained in:
Mauro D
2023-05-16 18:25:38 +00:00
parent 4d44e2fa77
commit 77ced9e7fd
169 changed files with 30767 additions and 164 deletions

View File

@@ -11,6 +11,8 @@ tokio = { version = "1.23", features = ["net", "macros"] }
tokio-rustls = { version = "0.24.0"}
serde = { version = "1.0", features = ["derive"]}
tracing = "0.1"
mail-auth = { git = "https://github.com/stalwartlabs/mail-auth" }
smtp-proto = { git = "https://github.com/stalwartlabs/smtp-proto" }
[target.'cfg(unix)'.dependencies]
privdrop = "0.5.3"

View File

@@ -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
);
}
}
}
}

View File

@@ -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 {

View File

@@ -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();

View File

@@ -27,7 +27,7 @@ impl Server {
hostname: self.hostname,
tls_acceptor: self.tls.map(|config| TlsAcceptor::from(Arc::new(config))),
is_tls_implicit: self.tls_implicit,
limiter: ConcurrencyLimiter::new(manager.max_concurrent()),
limiter: ConcurrencyLimiter::new(self.max_connections),
shutdown_rx,
});

View File

@@ -37,5 +37,4 @@ pub struct SessionData<T: AsyncRead + AsyncWrite + Unpin + 'static> {
pub trait SessionManager: Sync + Send + 'static + Clone {
fn spawn(&self, session: SessionData<TcpStream>);
fn max_concurrent(&self) -> u64;
}