From c6da79cdb06c32e4adf0e3cd4818c3950eef288f Mon Sep 17 00:00:00 2001 From: rustmailer Date: Wed, 15 Jul 2026 09:40:44 +0800 Subject: [PATCH] feat(config): add built-in IMAP server config with shared SMTP/IMAP TLS paths --- crates/core/src/settings/cli.rs | 52 ++++++++++++++++++++++++++++----- crates/core/src/settings/mod.rs | 4 +-- crates/smtp/src/server.rs | 12 ++++---- crates/smtp/src/tls.rs | 4 +-- 4 files changed, 54 insertions(+), 18 deletions(-) diff --git a/crates/core/src/settings/cli.rs b/crates/core/src/settings/cli.rs index 1e6c0e7..ea4914e 100644 --- a/crates/core/src/settings/cli.rs +++ b/crates/core/src/settings/cli.rs @@ -265,7 +265,7 @@ pub struct Settings { Ok(s.to_string()) }) )] - pub bichon_smtp_tls_key_path: Option, + pub bichon_tls_key_path: Option, #[clap( long, @@ -282,7 +282,7 @@ pub struct Settings { Ok(s.to_string()) }) )] - pub bichon_smtp_tls_cert_path: Option, + pub bichon_tls_cert_path: Option, #[clap( long, @@ -299,7 +299,7 @@ pub struct Settings { default_value = "starttls", help = "Set the encryption mode for SMTP: 'none', 'starttls', or 'tls'" )] - pub bichon_smtp_encryption: SmtpEncryptionMode, + pub bichon_smtp_encryption: EncryptionMode, #[clap( long, @@ -309,6 +309,42 @@ pub struct Settings { )] pub bichon_smtp_auth_required: bool, + /// Enable the built-in IMAP server for read-only email access via standard + /// email clients (Thunderbird, Outlook, Apple Mail, etc.). + #[clap( + long, + default_value = "false", + env, + help = "Enable the embedded IMAP server" + )] + pub bichon_enable_imap: bool, + + #[clap( + long, + default_value = "10143", + env, + help = "Set the IMAP port (STARTTLS or plaintext)", + value_parser = clap::value_parser!(u16).range(1..) + )] + pub bichon_imap_port: u16, + + #[clap( + long, + default_value = "10993", + env, + help = "Set the IMAPS port (implicit TLS)", + value_parser = clap::value_parser!(u16).range(1..) + )] + pub bichon_imaps_port: u16, + + #[clap( + long, + env, + default_value = "none", + help = "Set the encryption mode for IMAP: 'none', 'starttls', or 'tls'" + )] + pub bichon_imap_encryption: EncryptionMode, + /// Enable OIDC-based Single Sign-On (Pro/Enterprise feature). #[clap(long, default_value = "false", env, help = "Enable OpenID Connect SSO")] pub bichon_oidc_enabled: bool, @@ -417,7 +453,7 @@ impl fmt::Display for CompressionAlgorithm { } #[derive(Clone, Copy, Debug, PartialEq, ValueEnum)] -pub enum SmtpEncryptionMode { +pub enum EncryptionMode { #[clap(name = "none")] None, #[clap(name = "starttls")] @@ -426,12 +462,12 @@ pub enum SmtpEncryptionMode { Tls, } -impl fmt::Display for SmtpEncryptionMode { +impl fmt::Display for EncryptionMode { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - SmtpEncryptionMode::None => write!(f, "none"), - SmtpEncryptionMode::Starttls => write!(f, "starttls"), - SmtpEncryptionMode::Tls => write!(f, "tls"), + EncryptionMode::None => write!(f, "none"), + EncryptionMode::Starttls => write!(f, "starttls"), + EncryptionMode::Tls => write!(f, "tls"), } } } diff --git a/crates/core/src/settings/mod.rs b/crates/core/src/settings/mod.rs index 6e843e4..79c2db5 100644 --- a/crates/core/src/settings/mod.rs +++ b/crates/core/src/settings/mod.rs @@ -103,8 +103,8 @@ impl From<&Settings> for SystemConfigurations { bichon_smtp_port: s.bichon_smtp_port, bichon_smtp_encryption: s.bichon_smtp_encryption.to_string(), bichon_smtp_auth_required: s.bichon_smtp_auth_required, - bichon_smtp_tls_key_path: s.bichon_smtp_tls_key_path.clone(), - bichon_smtp_tls_cert_path: s.bichon_smtp_tls_cert_path.clone(), + bichon_smtp_tls_key_path: s.bichon_tls_key_path.clone(), + bichon_smtp_tls_cert_path: s.bichon_tls_cert_path.clone(), bichon_oidc_enabled: s.bichon_oidc_enabled, bichon_oidc_issuer_url: s.bichon_oidc_issuer_url.clone(), bichon_oidc_client_id: s.bichon_oidc_client_id.clone(), diff --git a/crates/smtp/src/server.rs b/crates/smtp/src/server.rs index 1f8d12b..ad38ff4 100644 --- a/crates/smtp/src/server.rs +++ b/crates/smtp/src/server.rs @@ -26,7 +26,7 @@ use bichon_core::cache::imap::mailbox::{Attribute, AttributeEnum}; use bichon_core::common::signal::SIGNAL_MANAGER; use bichon_core::envelope::extractor::extract_envelope_from_smtp; use bichon_core::error::BichonResult; -use bichon_core::settings::cli::{SmtpEncryptionMode, SETTINGS}; +use bichon_core::settings::cli::{EncryptionMode, SETTINGS}; use bichon_core::utils::create_hash; use bichon_core::{ account::migration::AccountModel, @@ -698,8 +698,8 @@ pub async fn start_smtp_server() -> std::io::Result { let smtp_port = SETTINGS.bichon_smtp_port; let tls_acceptor: Option = match SETTINGS.bichon_smtp_encryption { - SmtpEncryptionMode::None => None, - SmtpEncryptionMode::Starttls | SmtpEncryptionMode::Tls => Some(create_acceptor().await?), + EncryptionMode::None => None, + EncryptionMode::Starttls | EncryptionMode::Tls => Some(create_acceptor().await?), }; let smtp_listener = TcpListener::bind(( @@ -721,15 +721,15 @@ pub async fn start_smtp_server() -> std::io::Result { let smtp_config = SmtpConfig { whitelist: None, tls_acceptor: match SETTINGS.bichon_smtp_encryption { - SmtpEncryptionMode::None | SmtpEncryptionMode::Tls => None, - SmtpEncryptionMode::Starttls => tls_acceptor.clone(), + EncryptionMode::None | EncryptionMode::Tls => None, + EncryptionMode::Starttls => tls_acceptor.clone(), }, auth_required: SETTINGS.bichon_smtp_auth_required, }; let smtp_shutdown = SIGNAL_MANAGER.subscribe(); - let smtp_handle = if matches!(SETTINGS.bichon_smtp_encryption, SmtpEncryptionMode::Tls) { + let smtp_handle = if matches!(SETTINGS.bichon_smtp_encryption, EncryptionMode::Tls) { let acceptor = tls_acceptor .clone() .expect("TLS acceptor required when tls=true"); diff --git a/crates/smtp/src/tls.rs b/crates/smtp/src/tls.rs index 76f0bd4..077cf58 100644 --- a/crates/smtp/src/tls.rs +++ b/crates/smtp/src/tls.rs @@ -29,8 +29,8 @@ use bichon_core::settings::cli::SETTINGS; pub async fn create_acceptor() -> io::Result { let (certs, key) = if let (Some(key_path), Some(cert_path)) = ( - &SETTINGS.bichon_smtp_tls_key_path, - &SETTINGS.bichon_smtp_tls_cert_path, + &SETTINGS.bichon_tls_key_path, + &SETTINGS.bichon_tls_cert_path, ) { load_certs_from_files(key_path, cert_path).await? } else {