Add TLS support

This commit is contained in:
Quentin 2024-01-23 16:14:58 +01:00
parent 1f449dc7e9
commit f67f04129a
Signed by: quentin
GPG key ID: E9602264D639FF68
6 changed files with 127 additions and 16 deletions

59
Cargo.lock generated
View file

@ -56,12 +56,15 @@ dependencies = [
"rand",
"rmp-serde",
"rpassword",
"rustls 0.22.2",
"rustls-pemfile 2.0.0",
"serde",
"smtp-message",
"smtp-server",
"sodiumoxide",
"thiserror",
"tokio",
"tokio-rustls 0.25.0",
"tokio-util",
"toml",
"tracing",
@ -2662,10 +2665,24 @@ checksum = "f9d5a6813c0759e4609cd494e8e725babae6a2ca7b62a5536a13daaec6fcb7ba"
dependencies = [
"log",
"ring 0.17.7",
"rustls-webpki",
"rustls-webpki 0.101.7",
"sct",
]
[[package]]
name = "rustls"
version = "0.22.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e87c9956bd9807afa1f77e0f7594af32566e830e088a5576d27c5b6f30f49d41"
dependencies = [
"log",
"ring 0.17.7",
"rustls-pki-types",
"rustls-webpki 0.102.1",
"subtle",
"zeroize",
]
[[package]]
name = "rustls-native-certs"
version = "0.6.3"
@ -2673,7 +2690,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a9aace74cb666635c918e9c12bc0d348266037aa8eb599b5cba565709a8dff00"
dependencies = [
"openssl-probe",
"rustls-pemfile",
"rustls-pemfile 1.0.4",
"schannel",
"security-framework",
]
@ -2687,6 +2704,22 @@ dependencies = [
"base64 0.21.7",
]
[[package]]
name = "rustls-pemfile"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "35e4980fa29e4c4b212ffb3db068a564cbf560e51d3944b7c88bd8bf5bec64f4"
dependencies = [
"base64 0.21.7",
"rustls-pki-types",
]
[[package]]
name = "rustls-pki-types"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e9d979b3ce68192e42760c7810125eb6cf2ea10efae545a156063e61f314e2a"
[[package]]
name = "rustls-webpki"
version = "0.101.7"
@ -2697,6 +2730,17 @@ dependencies = [
"untrusted 0.9.0",
]
[[package]]
name = "rustls-webpki"
version = "0.102.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef4ca26037c909dedb327b48c3327d0ba91d3dd3c4e05dad328f210ffb68e95b"
dependencies = [
"ring 0.17.7",
"rustls-pki-types",
"untrusted 0.9.0",
]
[[package]]
name = "ryu"
version = "1.0.16"
@ -3186,6 +3230,17 @@ dependencies = [
"tokio",
]
[[package]]
name = "tokio-rustls"
version = "0.25.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "775e0c0f0adb3a2f22a00c4745d728b479985fc15ee7ca6a2608388c5569860f"
dependencies = [
"rustls 0.22.2",
"rustls-pki-types",
"tokio",
]
[[package]]
name = "tokio-stream"
version = "0.1.14"

View file

@ -43,6 +43,9 @@ zstd = { version = "0.9", default-features = false }
sodiumoxide = "0.2"
argon2 = "0.5"
rand = "0.8.5"
rustls = "0.22"
rustls-pemfile = "2.0"
tokio-rustls = "0.25"
hyper-rustls = { version = "0.24", features = ["http2"] }
rpassword = "7.0"

View file

@ -9,7 +9,7 @@ use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct CompanionConfig {
pub pid: Option<PathBuf>,
pub imap: ImapConfig,
pub imap: ImapUnsecureConfig,
#[serde(flatten)]
pub users: LoginStaticConfig,
@ -18,8 +18,9 @@ pub struct CompanionConfig {
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct ProviderConfig {
pub pid: Option<PathBuf>,
pub imap: ImapConfig,
pub lmtp: LmtpConfig,
pub imap: Option<ImapConfig>,
pub imap_unsecure: Option<ImapUnsecureConfig>,
pub lmtp: Option<LmtpConfig>,
pub users: UserManagement,
}
@ -40,6 +41,13 @@ pub struct LmtpConfig {
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct ImapConfig {
pub bind_addr: SocketAddr,
pub certs: PathBuf,
pub key: PathBuf,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct ImapUnsecureConfig {
pub bind_addr: SocketAddr,
}
#[derive(Serialize, Deserialize, Debug, Clone)]

View file

@ -26,8 +26,10 @@ use imap_codec::imap_types::response::{Code, CommandContinuationRequest, Respons
use imap_codec::imap_types::{core::Text, response::Greeting};
use imap_flow::server::{ServerFlow, ServerFlowEvent, ServerFlowOptions};
use imap_flow::stream::AnyStream;
use tokio_rustls::TlsAcceptor;
use rustls_pemfile::{certs, private_key};
use crate::config::ImapConfig;
use crate::config::{ImapConfig, ImapUnsecureConfig};
use crate::imap::capability::ServerCapability;
use crate::imap::request::Request;
use crate::imap::response::{Body, ResponseOrIdle};
@ -39,6 +41,7 @@ pub struct Server {
bind_addr: SocketAddr,
login_provider: ArcLoginProvider,
capabilities: ServerCapability,
tls: Option<TlsAcceptor>,
}
#[derive(Clone)]
@ -49,11 +52,29 @@ struct ClientContext {
server_capabilities: ServerCapability,
}
pub fn new(config: ImapConfig, login: ArcLoginProvider) -> Server {
pub fn new(config: ImapConfig, login: ArcLoginProvider) -> Result<Server> {
let loaded_certs = certs(&mut std::io::BufReader::new(std::fs::File::open(config.certs)?)).collect::<Result<Vec<_>, _>>()?;
let loaded_key = private_key(&mut std::io::BufReader::new(std::fs::File::open(config.key)?))?.unwrap();
let tls_config = rustls::ServerConfig::builder()
.with_no_client_auth()
.with_single_cert(loaded_certs, loaded_key)?;
let acceptor = TlsAcceptor::from(Arc::new(tls_config));
Ok(Server {
bind_addr: config.bind_addr,
login_provider: login,
capabilities: ServerCapability::default(),
tls: Some(acceptor),
})
}
pub fn new_unsecure(config: ImapUnsecureConfig, login: ArcLoginProvider) -> Server {
Server {
bind_addr: config.bind_addr,
login_provider: login,
capabilities: ServerCapability::default(),
tls: None,
}
}
@ -78,6 +99,19 @@ impl Server {
_ = must_exit.changed() => continue,
};
tracing::info!("IMAP: accepted connection from {}", remote_addr);
let stream = match self.tls.clone() {
Some(acceptor) => {
let stream = match acceptor.accept(socket).await {
Ok(v) => v,
Err(e) => {
tracing::error!(err=?e, "TLS negociation failed");
continue;
}
};
AnyStream::new(stream)
},
None => AnyStream::new(socket),
};
let client = ClientContext {
addr: remote_addr.clone(),
@ -85,7 +119,7 @@ impl Server {
must_exit: must_exit.clone(),
server_capabilities: self.capabilities.clone(),
};
let conn = tokio::spawn(NetLoop::handler(client, AnyStream::new(socket)));
let conn = tokio::spawn(NetLoop::handler(client, stream));
connections.push(conn);
}
drop(tcp);

View file

@ -167,13 +167,14 @@ async fn main() -> Result<()> {
use std::net::*;
AnyConfig::Provider(ProviderConfig {
pid: None,
imap: ImapConfig {
imap: None,
imap_unsecure: Some(ImapUnsecureConfig {
bind_addr: SocketAddr::new(IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1)), 1143),
},
lmtp: LmtpConfig {
}),
lmtp: Some(LmtpConfig {
bind_addr: SocketAddr::new(IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1)), 1025),
hostname: "example.tld".to_string(),
},
}),
users: UserManagement::Demo,
})
} else {

View file

@ -15,6 +15,7 @@ use crate::login::{demo_provider::*, ldap_provider::*, static_provider::*};
pub struct Server {
lmtp_server: Option<Arc<LmtpServer>>,
imap_unsecure_server: Option<imap::Server>,
imap_server: Option<imap::Server>,
pid_file: Option<PathBuf>,
}
@ -25,10 +26,11 @@ impl Server {
let login = Arc::new(StaticLoginProvider::new(config.users).await?);
let lmtp_server = None;
let imap_server = Some(imap::new(config.imap, login.clone()));
let imap_unsecure_server = Some(imap::new_unsecure(config.imap, login.clone()));
Ok(Self {
lmtp_server,
imap_server,
imap_unsecure_server,
imap_server: None,
pid_file: config.pid,
})
}
@ -41,11 +43,13 @@ impl Server {
UserManagement::Ldap(x) => Arc::new(LdapLoginProvider::new(x)?),
};
let lmtp_server = Some(LmtpServer::new(config.lmtp, login.clone()));
let imap_server = Some(imap::new(config.imap, login.clone()));
let lmtp_server = config.lmtp.map(|lmtp| LmtpServer::new(lmtp, login.clone()));
let imap_unsecure_server = config.imap_unsecure.map(|imap| imap::new_unsecure(imap, login.clone()));
let imap_server = config.imap.map(|imap| imap::new(imap, login.clone())).transpose()?;
Ok(Self {
lmtp_server,
imap_unsecure_server,
imap_server,
pid_file: config.pid,
})
@ -79,6 +83,12 @@ impl Server {
Some(s) => s.run(exit_signal.clone()).await,
}
},
async {
match self.imap_unsecure_server {
None => Ok(()),
Some(s) => s.run(exit_signal.clone()).await,
}
},
async {
match self.imap_server {
None => Ok(()),