From f67f04129afaacc4cdeb69aa79e5c102ec7331bd Mon Sep 17 00:00:00 2001 From: Quentin Dufour Date: Tue, 23 Jan 2024 16:14:58 +0100 Subject: [PATCH] Add TLS support --- Cargo.lock | 59 +++++++++++++++++++++++++++++++++++++++++++++++-- Cargo.toml | 3 +++ src/config.rs | 14 +++++++++--- src/imap/mod.rs | 40 ++++++++++++++++++++++++++++++--- src/main.rs | 9 ++++---- src/server.rs | 18 +++++++++++---- 6 files changed, 127 insertions(+), 16 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f5b2602..de5bce9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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" diff --git a/Cargo.toml b/Cargo.toml index a865170..2f2ce91 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" diff --git a/src/config.rs b/src/config.rs index b9c1f09..0269773 100644 --- a/src/config.rs +++ b/src/config.rs @@ -9,7 +9,7 @@ use serde::{Deserialize, Serialize}; #[derive(Serialize, Deserialize, Debug, Clone)] pub struct CompanionConfig { pub pid: Option, - 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, - pub imap: ImapConfig, - pub lmtp: LmtpConfig, + pub imap: Option, + pub imap_unsecure: Option, + pub lmtp: Option, 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)] diff --git a/src/imap/mod.rs b/src/imap/mod.rs index 3f685e6..b08a4ff 100644 --- a/src/imap/mod.rs +++ b/src/imap/mod.rs @@ -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, } #[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 { + let loaded_certs = certs(&mut std::io::BufReader::new(std::fs::File::open(config.certs)?)).collect::, _>>()?; + 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); diff --git a/src/main.rs b/src/main.rs index a7462bc..3e3674c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -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 { diff --git a/src/server.rs b/src/server.rs index bd2fd5d..0df1caf 100644 --- a/src/server.rs +++ b/src/server.rs @@ -15,6 +15,7 @@ use crate::login::{demo_provider::*, ldap_provider::*, static_provider::*}; pub struct Server { lmtp_server: Option>, + imap_unsecure_server: Option, imap_server: Option, pid_file: Option, } @@ -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(()),