mirror of
https://github.com/rustmailer/bichon.git
synced 2026-08-03 07:48:34 +02:00
feat: add option to trust any TLS certificate for IMAP connections
This commit is contained in:
Generated
+1
-1
@@ -448,7 +448,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "bichon"
|
||||
version = "0.0.4"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
"async-imap",
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "bichon"
|
||||
version = "0.0.4"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[[bin]]
|
||||
|
||||
@@ -55,7 +55,7 @@ use crate::modules::rest::response::DataPage;
|
||||
use crate::modules::token::AccessToken;
|
||||
use crate::raise_error;
|
||||
|
||||
pub type AccountModel = AccountV1;
|
||||
pub type AccountModel = AccountV2;
|
||||
|
||||
#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize, Enum)]
|
||||
pub enum AccountType {
|
||||
@@ -86,11 +86,41 @@ pub struct AccountV1 {
|
||||
pub updated_at: i64,
|
||||
pub use_proxy: Option<u64>,
|
||||
}
|
||||
|
||||
impl AccountV1 {
|
||||
fn pk(&self) -> String {
|
||||
format!("{}_{}", self.created_at, self.id)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize, Object)]
|
||||
#[native_model(id = 4, version = 2, from = AccountV1)]
|
||||
#[native_db(primary_key(pk -> String))]
|
||||
pub struct AccountV2 {
|
||||
#[secondary_key(unique)]
|
||||
pub id: u64,
|
||||
pub imap: Option<ImapConfig>,
|
||||
pub enabled: bool,
|
||||
#[oai(validator(custom = "crate::modules::common::validator::EmailValidator"))]
|
||||
pub email: String,
|
||||
pub name: Option<String>,
|
||||
pub capabilities: Option<Vec<String>>,
|
||||
pub date_since: Option<DateSince>,
|
||||
pub folder_limit: Option<u32>,
|
||||
pub sync_folders: Option<Vec<String>>,
|
||||
pub account_type: AccountType,
|
||||
pub sync_interval_min: Option<i64>,
|
||||
pub known_folders: Option<BTreeSet<String>>,
|
||||
pub created_at: i64,
|
||||
pub updated_at: i64,
|
||||
pub use_proxy: Option<u64>,
|
||||
pub use_dangerous: bool,
|
||||
pub pgp_key: Option<String>,
|
||||
}
|
||||
|
||||
impl AccountV2 {
|
||||
fn pk(&self) -> String {
|
||||
format!("{}_{}", self.created_at, self.id)
|
||||
}
|
||||
|
||||
pub fn new(request: AccountCreateRequest) -> BichonResult<Self> {
|
||||
Ok(Self {
|
||||
@@ -109,12 +139,14 @@ impl AccountV1 {
|
||||
updated_at: utc_now!(),
|
||||
use_proxy: request.use_proxy,
|
||||
folder_limit: request.folder_limit,
|
||||
use_dangerous: request.use_dangerous,
|
||||
pgp_key: request.pgp_key,
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn check_account_exists(account_id: u64) -> BichonResult<AccountModel> {
|
||||
let account =
|
||||
secondary_find_impl::<AccountModel>(DB_MANAGER.meta_db(), AccountV1Key::id, account_id)
|
||||
secondary_find_impl::<AccountModel>(DB_MANAGER.meta_db(), AccountV2Key::id, account_id)
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
raise_error!(
|
||||
@@ -144,7 +176,7 @@ impl AccountV1 {
|
||||
}
|
||||
|
||||
pub async fn find(account_id: u64) -> BichonResult<Option<AccountModel>> {
|
||||
secondary_find_impl::<AccountModel>(DB_MANAGER.meta_db(), AccountV1Key::id, account_id)
|
||||
secondary_find_impl::<AccountModel>(DB_MANAGER.meta_db(), AccountV2Key::id, account_id)
|
||||
.await
|
||||
}
|
||||
|
||||
@@ -198,7 +230,7 @@ impl AccountV1 {
|
||||
|
||||
async fn delete_account(account_id: u64) -> BichonResult<()> {
|
||||
delete_impl(DB_MANAGER.meta_db(), move|rw|{
|
||||
rw.get().secondary::<AccountModel>(AccountV1Key::id, account_id).map_err(|e| raise_error!(format!("{:#?}", e), ErrorCode::InternalError))?
|
||||
rw.get().secondary::<AccountModel>(AccountV2Key::id, account_id).map_err(|e| raise_error!(format!("{:#?}", e), ErrorCode::InternalError))?
|
||||
.ok_or_else(||raise_error!(format!("The account entity with id={account_id} that you want to delete was not found."), ErrorCode::ResourceNotFound))
|
||||
}).await
|
||||
}
|
||||
@@ -228,7 +260,7 @@ impl AccountV1 {
|
||||
sync_folders: Vec<String>,
|
||||
) -> BichonResult<()> {
|
||||
update_impl(DB_MANAGER.meta_db(), move |rw| {
|
||||
rw.get().secondary::<AccountModel>(AccountV1Key::id, account_id).map_err(|e| raise_error!(format!("{:#?}", e), ErrorCode::InternalError))?
|
||||
rw.get().secondary::<AccountModel>(AccountV2Key::id, account_id).map_err(|e| raise_error!(format!("{:#?}", e), ErrorCode::InternalError))?
|
||||
.ok_or_else(|| raise_error!(format!("When trying to update account sync_folders, the corresponding record was not found. account_id={}", account_id), ErrorCode::ResourceNotFound))
|
||||
}, |current|{
|
||||
let mut updated = current.clone();
|
||||
@@ -243,7 +275,7 @@ impl AccountV1 {
|
||||
known_folders: BTreeSet<String>,
|
||||
) -> BichonResult<()> {
|
||||
update_impl(DB_MANAGER.meta_db(), move |rw| {
|
||||
rw.get().secondary::<AccountModel>(AccountV1Key::id, account_id).map_err(|e| raise_error!(format!("{:#?}", e), ErrorCode::InternalError))?
|
||||
rw.get().secondary::<AccountModel>(AccountV2Key::id, account_id).map_err(|e| raise_error!(format!("{:#?}", e), ErrorCode::InternalError))?
|
||||
.ok_or_else(|| raise_error!(format!("When trying to update account known_folders, the corresponding record was not found. account_id={}", account_id), ErrorCode::ResourceNotFound))
|
||||
}, |current|{
|
||||
let mut updated = current.clone();
|
||||
@@ -258,7 +290,7 @@ impl AccountV1 {
|
||||
capabilities: Vec<String>,
|
||||
) -> BichonResult<()> {
|
||||
update_impl(DB_MANAGER.meta_db(), move |rw| {
|
||||
rw.get().secondary::<AccountModel>(AccountV1Key::id, account_id).map_err(|e| raise_error!(format!("{:#?}", e), ErrorCode::InternalError))?
|
||||
rw.get().secondary::<AccountModel>(AccountV2Key::id, account_id).map_err(|e| raise_error!(format!("{:#?}", e), ErrorCode::InternalError))?
|
||||
.ok_or_else(|| raise_error!(format!("When trying to update account capabilities, the corresponding record was not found. account_id={}", account_id), ErrorCode::ResourceNotFound))
|
||||
}, |current|{
|
||||
let mut updated = current.clone();
|
||||
@@ -287,7 +319,7 @@ impl AccountV1 {
|
||||
}
|
||||
|
||||
pub async fn count() -> BichonResult<usize> {
|
||||
count_by_unique_secondary_key_impl::<AccountModel>(DB_MANAGER.meta_db(), AccountV1Key::id)
|
||||
count_by_unique_secondary_key_impl::<AccountModel>(DB_MANAGER.meta_db(), AccountV2Key::id)
|
||||
.await
|
||||
}
|
||||
|
||||
@@ -355,7 +387,62 @@ impl AccountV1 {
|
||||
if let Some(enabled) = request.enabled {
|
||||
new.enabled = enabled;
|
||||
}
|
||||
|
||||
if let Some(use_dangerous) = request.use_dangerous {
|
||||
new.use_dangerous = use_dangerous;
|
||||
}
|
||||
|
||||
if let Some(pgp_key) = request.pgp_key {
|
||||
new.pgp_key = Some(pgp_key);
|
||||
}
|
||||
|
||||
new.updated_at = utc_now!();
|
||||
Ok(new)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<AccountV1> for AccountV2 {
|
||||
fn from(value: AccountV1) -> Self {
|
||||
Self {
|
||||
id: value.id,
|
||||
imap: value.imap,
|
||||
enabled: value.enabled,
|
||||
email: value.email,
|
||||
name: value.name,
|
||||
capabilities: value.capabilities,
|
||||
date_since: value.date_since,
|
||||
folder_limit: value.folder_limit,
|
||||
sync_folders: value.sync_folders,
|
||||
account_type: value.account_type,
|
||||
sync_interval_min: value.sync_interval_min,
|
||||
known_folders: value.known_folders,
|
||||
created_at: value.created_at,
|
||||
updated_at: value.updated_at,
|
||||
use_proxy: value.use_proxy,
|
||||
use_dangerous: false,
|
||||
pgp_key: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<AccountV2> for AccountV1 {
|
||||
fn from(value: AccountV2) -> Self {
|
||||
Self {
|
||||
id: value.id,
|
||||
imap: value.imap,
|
||||
enabled: value.enabled,
|
||||
email: value.email,
|
||||
name: value.name,
|
||||
capabilities: value.capabilities,
|
||||
date_since: value.date_since,
|
||||
folder_limit: value.folder_limit,
|
||||
sync_folders: value.sync_folders,
|
||||
account_type: value.account_type,
|
||||
sync_interval_min: value.sync_interval_min,
|
||||
known_folders: value.known_folders,
|
||||
created_at: value.created_at,
|
||||
updated_at: value.updated_at,
|
||||
use_proxy: value.use_proxy,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
use crate::modules::account::entity::ImapConfig;
|
||||
@@ -43,6 +42,8 @@ pub struct AccountCreateRequest {
|
||||
#[oai(validator(minimum(value = "10"), maximum(value = "480")))]
|
||||
pub sync_interval_min: Option<i64>,
|
||||
pub use_proxy: Option<u64>,
|
||||
pub use_dangerous: bool,
|
||||
pub pgp_key: Option<String>,
|
||||
}
|
||||
|
||||
impl AccountCreateRequest {
|
||||
@@ -132,6 +133,10 @@ pub struct AccountUpdateRequest {
|
||||
/// - If `None` or not provided, the client will connect directly to the API server.
|
||||
/// - If `Some(proxy_id)`, the client will use the pre-configured proxy with the given ID for API requests.
|
||||
pub use_proxy: Option<u64>,
|
||||
|
||||
pub use_dangerous: Option<bool>,
|
||||
|
||||
pub pgp_key: Option<String>,
|
||||
}
|
||||
|
||||
impl AccountUpdateRequest {
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
use crate::modules::account::migration::AccountModel;
|
||||
use crate::modules::cache::imap::MAILBOX_MODELS;
|
||||
use crate::modules::error::{code::ErrorCode, BichonError};
|
||||
use crate::modules::settings::cli::SETTINGS;
|
||||
@@ -71,6 +71,8 @@ impl DatabaseManager {
|
||||
let rw = database
|
||||
.rw_transaction()
|
||||
.map_err(|e| raise_error!(format!("{:#?}", e), ErrorCode::InternalError))?;
|
||||
rw.migrate::<AccountModel>()
|
||||
.map_err(|e| raise_error!(format!("{:#?}", e), ErrorCode::InternalError))?;
|
||||
rw.commit()
|
||||
.map_err(|e| raise_error!(format!("{:#?}", e), ErrorCode::InternalError))?;
|
||||
|
||||
|
||||
@@ -16,8 +16,7 @@
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
use crate::modules::account::migration::AccountV1;
|
||||
use crate::modules::account::migration::{AccountV1, AccountV2};
|
||||
use crate::modules::autoconfig::CachedMailSettings;
|
||||
use crate::modules::error::code::ErrorCode;
|
||||
use crate::modules::error::BichonResult;
|
||||
@@ -63,6 +62,7 @@ impl ModelsAdapter {
|
||||
self.register_model::<SystemSetting>();
|
||||
self.register_model::<CachedMailSettings>();
|
||||
self.register_model::<AccountV1>();
|
||||
self.register_model::<AccountV2>();
|
||||
self.register_model::<OAuth2>();
|
||||
self.register_model::<OAuth2PendingEntity>();
|
||||
self.register_model::<OAuth2AccessToken>();
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
use crate::modules::account::entity::Encryption;
|
||||
use crate::modules::error::code::ErrorCode;
|
||||
use crate::modules::error::BichonResult;
|
||||
@@ -100,15 +99,17 @@ impl Client {
|
||||
encryption: &Encryption,
|
||||
port: u16,
|
||||
use_proxy: Option<u64>,
|
||||
dangerous: bool,
|
||||
) -> BichonResult<Self> {
|
||||
let resolved_addr = Self::resolve_to_socket_addr(domain, port)?;
|
||||
debug!("Attempting IMAP connection to {domain} ({resolved_addr}).");
|
||||
match encryption {
|
||||
Encryption::Ssl => {
|
||||
Self::establish_secure_connection(resolved_addr, domain, use_proxy).await
|
||||
Self::establish_secure_connection(resolved_addr, domain, use_proxy, dangerous).await
|
||||
}
|
||||
Encryption::StartTls => {
|
||||
Self::establish_starttls_connection(resolved_addr, domain, use_proxy).await
|
||||
Self::establish_starttls_connection(resolved_addr, domain, use_proxy, dangerous)
|
||||
.await
|
||||
}
|
||||
Encryption::None => Self::establish_insecure_connection(resolved_addr, use_proxy).await,
|
||||
}
|
||||
@@ -118,11 +119,17 @@ impl Client {
|
||||
address: SocketAddr,
|
||||
server_hostname: &str,
|
||||
use_proxy: Option<u64>,
|
||||
dangerous: bool,
|
||||
) -> BichonResult<Self> {
|
||||
// Establish the TLS connection with the specified parameters
|
||||
let tls_stream =
|
||||
establish_tls_connection(address, server_hostname, alpn(address.port()), use_proxy)
|
||||
.await?;
|
||||
let tls_stream = establish_tls_connection(
|
||||
address,
|
||||
server_hostname,
|
||||
alpn(address.port()),
|
||||
use_proxy,
|
||||
dangerous,
|
||||
)
|
||||
.await?;
|
||||
let stats_stream = StatsWrapper::new(tls_stream);
|
||||
// Wrap the TLS stream in a buffered writer for efficient IO
|
||||
let buffered_stream = BufWriter::new(stats_stream);
|
||||
@@ -180,6 +187,7 @@ impl Client {
|
||||
address: SocketAddr,
|
||||
server_hostname: &str,
|
||||
use_proxy: Option<u64>,
|
||||
dangerous: bool,
|
||||
) -> BichonResult<Self> {
|
||||
// Establish the initial TCP connection
|
||||
let tcp_stream = establish_tcp_connection_with_timeout(address, use_proxy).await?;
|
||||
@@ -217,7 +225,7 @@ impl Client {
|
||||
let buffered_tcp_stream = client.into_inner();
|
||||
let tcp_stream = buffered_tcp_stream.into_inner();
|
||||
// Wrap the TCP stream in TLS encryption
|
||||
let tls_stream = establish_tls_stream(server_hostname, &[], tcp_stream).await?;
|
||||
let tls_stream = establish_tls_stream(server_hostname, &[], tcp_stream, dangerous).await?;
|
||||
// Wrap the TLS stream in a buffered writer
|
||||
let buffered_stream = BufWriter::new(tls_stream);
|
||||
// Create a SessionStream trait object for further communication
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
use crate::modules::account::dispatcher::STATUS_DISPATCHER;
|
||||
use crate::modules::account::entity::AuthType;
|
||||
use crate::modules::account::migration::{AccountModel, AccountType};
|
||||
@@ -51,7 +50,14 @@ impl ImapConnectionManager {
|
||||
async fn create_client(&self, account: &AccountModel) -> BichonResult<Client> {
|
||||
assert_eq!(account.account_type, AccountType::IMAP);
|
||||
let imap = account.imap.as_ref().unwrap();
|
||||
Client::connection(&imap.host, &imap.encryption, imap.port, imap.use_proxy).await
|
||||
Client::connection(
|
||||
&imap.host,
|
||||
&imap.encryption,
|
||||
imap.port,
|
||||
imap.use_proxy,
|
||||
account.use_dangerous,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn authenticate(
|
||||
|
||||
@@ -16,19 +16,21 @@
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
use mail_parser::MessageParser;
|
||||
|
||||
use crate::{base64_encode_url_safe, modules::{account::entity::Encryption, imap::client::Client}};
|
||||
use crate::{
|
||||
base64_encode_url_safe,
|
||||
modules::{account::entity::Encryption, imap::client::Client},
|
||||
};
|
||||
|
||||
#[tokio::test]
|
||||
async fn testxx() {
|
||||
rustls::crypto::CryptoProvider::install_default(rustls::crypto::ring::default_provider())
|
||||
.unwrap();
|
||||
let client = Client::connection("imap.zoho.com".into(), &Encryption::Ssl, 993, None)
|
||||
let client = Client::connection("imap.zoho.com".into(), &Encryption::Ssl, 993, None, false)
|
||||
.await
|
||||
.unwrap();
|
||||
let mut session = client.login("pollybase@zohomail.com", "xxx").await.unwrap();
|
||||
let mut session = client.login("xx@zohomail.com", "xxx").await.unwrap();
|
||||
session.select("INBOX").await.unwrap();
|
||||
let result = session.uid_search("LARGER 1024").await.unwrap();
|
||||
println!("{:#?}", result);
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
use crate::modules::error::code::ErrorCode;
|
||||
use crate::modules::settings::proxy::Proxy;
|
||||
use crate::modules::utils::tls::establish_tls_stream;
|
||||
@@ -56,17 +55,19 @@ pub(crate) async fn establish_tcp_connection_with_timeout(
|
||||
Ok(Box::pin(timeout_stream))
|
||||
}
|
||||
|
||||
pub(crate) async fn establish_tls_connection(
|
||||
pub async fn establish_tls_connection(
|
||||
address: SocketAddr,
|
||||
server_hostname: &str,
|
||||
alpn_protocols: &[&str],
|
||||
use_proxy: Option<u64>,
|
||||
dangerous: bool,
|
||||
) -> BichonResult<impl SessionStream> {
|
||||
// Establish the TCP connection with timeout
|
||||
let tcp_stream = establish_tcp_connection_with_timeout(address, use_proxy).await?;
|
||||
|
||||
// Wrap the TCP stream with TLS encryption
|
||||
let tls_stream = establish_tls_stream(server_hostname, alpn_protocols, tcp_stream).await?;
|
||||
let tls_stream =
|
||||
establish_tls_stream(server_hostname, alpn_protocols, tcp_stream, dangerous).await?;
|
||||
|
||||
// Return the TLS stream wrapped in a SessionStream
|
||||
Ok(tls_stream)
|
||||
|
||||
+72
-14
@@ -16,7 +16,6 @@
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
use crate::{
|
||||
modules::{
|
||||
error::{code::ErrorCode, BichonResult},
|
||||
@@ -24,38 +23,44 @@ use crate::{
|
||||
},
|
||||
raise_error,
|
||||
};
|
||||
use rustls::RootCertStore;
|
||||
use rustls::{
|
||||
client::danger::{ServerCertVerified, ServerCertVerifier},
|
||||
RootCertStore,
|
||||
};
|
||||
use std::sync::Arc;
|
||||
|
||||
pub async fn establish_tls_stream(
|
||||
server_hostname: &str,
|
||||
alpn_protocols: &[&str],
|
||||
stream: impl SessionStream + 'static,
|
||||
dangerous: bool,
|
||||
) -> BichonResult<impl SessionStream> {
|
||||
let tls_stream = establish_rustls_stream(server_hostname, alpn_protocols, stream).await?;
|
||||
let tls_stream =
|
||||
establish_rustls_stream(server_hostname, alpn_protocols, stream, dangerous).await?;
|
||||
let boxed_stream: Box<dyn SessionStream> = Box::new(tls_stream);
|
||||
Ok(boxed_stream)
|
||||
}
|
||||
|
||||
pub async fn establish_rustls_stream(
|
||||
async fn establish_rustls_stream(
|
||||
server_hostname: &str,
|
||||
alpn_protocols: &[&str],
|
||||
stream: impl SessionStream,
|
||||
dangerous: bool,
|
||||
) -> BichonResult<impl SessionStream> {
|
||||
// Create a root certificate store and add default trusted roots
|
||||
let root_store = RootCertStore {
|
||||
roots: webpki_roots::TLS_SERVER_ROOTS.into(),
|
||||
};
|
||||
|
||||
// Configure the Rustls client with the root certs and no client authentication
|
||||
let mut config = rustls::ClientConfig::builder()
|
||||
//builder_with_provider(
|
||||
// rustls::crypto::ring::default_provider().into(),
|
||||
// )
|
||||
// .with_protocol_versions(&[&rustls::version::TLS13])
|
||||
// .unwrap()
|
||||
.with_root_certificates(root_store)
|
||||
.with_no_client_auth();
|
||||
let mut config = if dangerous {
|
||||
rustls::ClientConfig::builder()
|
||||
.dangerous()
|
||||
.with_custom_certificate_verifier(Arc::new(SkipCertVerification))
|
||||
.with_no_client_auth()
|
||||
} else {
|
||||
rustls::ClientConfig::builder()
|
||||
.with_root_certificates(root_store)
|
||||
.with_no_client_auth()
|
||||
};
|
||||
|
||||
// Set the ALPN protocols
|
||||
config.alpn_protocols = alpn_protocols
|
||||
@@ -76,3 +81,56 @@ pub async fn establish_rustls_stream(
|
||||
|
||||
Ok(tls_stream)
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct SkipCertVerification;
|
||||
|
||||
impl ServerCertVerifier for SkipCertVerification {
|
||||
fn verify_server_cert(
|
||||
&self,
|
||||
_end_entity: &rustls::pki_types::CertificateDer<'_>,
|
||||
_intermediates: &[rustls::pki_types::CertificateDer<'_>],
|
||||
_server_name: &rustls::pki_types::ServerName<'_>,
|
||||
_ocsp_response: &[u8],
|
||||
_now: rustls::pki_types::UnixTime,
|
||||
) -> Result<ServerCertVerified, rustls::Error> {
|
||||
// Always return a valid certificate verification result
|
||||
Ok(ServerCertVerified::assertion())
|
||||
}
|
||||
|
||||
fn verify_tls12_signature(
|
||||
&self,
|
||||
_message: &[u8],
|
||||
_cert: &rustls::pki_types::CertificateDer<'_>,
|
||||
_dss: &rustls::DigitallySignedStruct,
|
||||
) -> Result<rustls::client::danger::HandshakeSignatureValid, rustls::Error> {
|
||||
Ok(rustls::client::danger::HandshakeSignatureValid::assertion())
|
||||
}
|
||||
|
||||
fn verify_tls13_signature(
|
||||
&self,
|
||||
_message: &[u8],
|
||||
_cert: &rustls::pki_types::CertificateDer<'_>,
|
||||
_dss: &rustls::DigitallySignedStruct,
|
||||
) -> Result<rustls::client::danger::HandshakeSignatureValid, rustls::Error> {
|
||||
Ok(rustls::client::danger::HandshakeSignatureValid::assertion())
|
||||
}
|
||||
|
||||
fn supported_verify_schemes(&self) -> Vec<rustls::SignatureScheme> {
|
||||
vec![
|
||||
rustls::SignatureScheme::RSA_PKCS1_SHA1,
|
||||
rustls::SignatureScheme::ECDSA_SHA1_Legacy,
|
||||
rustls::SignatureScheme::RSA_PKCS1_SHA256,
|
||||
rustls::SignatureScheme::ECDSA_NISTP256_SHA256,
|
||||
rustls::SignatureScheme::RSA_PKCS1_SHA384,
|
||||
rustls::SignatureScheme::ECDSA_NISTP384_SHA384,
|
||||
rustls::SignatureScheme::RSA_PKCS1_SHA512,
|
||||
rustls::SignatureScheme::ECDSA_NISTP521_SHA512,
|
||||
rustls::SignatureScheme::RSA_PSS_SHA256,
|
||||
rustls::SignatureScheme::RSA_PSS_SHA384,
|
||||
rustls::SignatureScheme::RSA_PSS_SHA512,
|
||||
rustls::SignatureScheme::ED25519,
|
||||
rustls::SignatureScheme::ED448,
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -99,6 +99,7 @@ export type Account = {
|
||||
use_proxy?: number;
|
||||
};
|
||||
enabled: boolean;
|
||||
use_dangerous: boolean;
|
||||
date_since?: {
|
||||
fixed?: string;
|
||||
relative?: {
|
||||
@@ -116,6 +117,7 @@ const getAccountSchema = (isEdit: boolean, t: (key: string) => string) =>
|
||||
email: z.string({ required_error: t('validation.emailRequired') }).email({ message: t('validation.invalidEmail') }),
|
||||
imap: getImapConfigSchema(isEdit, t),
|
||||
enabled: z.boolean(),
|
||||
use_dangerous: z.boolean(),
|
||||
date_since: getDateSelectionSchema(t).optional(),
|
||||
folder_limit: z
|
||||
.number({ invalid_type_error: t('validation.folderLimitMustBeNumber') })
|
||||
@@ -137,7 +139,7 @@ export type Steps = [
|
||||
|
||||
const getSteps = (t: (key: string) => string): Steps => [
|
||||
{ id: "step-1", name: t('accounts.steps.emailAddress'), fields: ["email"] },
|
||||
{ id: "step-2", name: t('accounts.steps.imap'), fields: ["imap"] },
|
||||
{ id: "step-2", name: t('accounts.steps.imap'), fields: ["imap", "use_dangerous"] },
|
||||
{ id: "step-3", name: t('accounts.steps.syncPreferences'), fields: ["enabled", "date_since", "folder_limit", "sync_interval_min"] },
|
||||
{ id: "step-4", name: t('accounts.steps.summary'), fields: [] },
|
||||
];
|
||||
@@ -164,6 +166,7 @@ const defaultValues: Account = {
|
||||
use_proxy: undefined
|
||||
},
|
||||
enabled: true,
|
||||
use_dangerous: false,
|
||||
date_since: undefined,
|
||||
folder_limit: undefined,
|
||||
sync_interval_min: 10,
|
||||
@@ -189,6 +192,7 @@ const mapCurrentRowToFormValues = (currentRow: AccountModel): Account => {
|
||||
email: currentRow.email,
|
||||
imap,
|
||||
enabled: currentRow.enabled,
|
||||
use_dangerous: currentRow.use_dangerous,
|
||||
date_since: currentRow.date_since ?? undefined,
|
||||
folder_limit: currentRow.folder_limit ?? undefined,
|
||||
sync_interval_min: currentRow.sync_interval_min ?? 10,
|
||||
@@ -266,6 +270,7 @@ export function AccountActionDialog({ currentRow, open, onOpenChange }: Props) {
|
||||
},
|
||||
},
|
||||
enabled: data.enabled,
|
||||
use_dangerous: data.use_dangerous,
|
||||
date_since: data.date_since,
|
||||
folder_limit: data.folder_limit,
|
||||
sync_interval_min: data.sync_interval_min,
|
||||
|
||||
@@ -147,7 +147,7 @@ export function RunningStateDialog({ currentRow, open, onOpenChange }: Props) {
|
||||
</span>
|
||||
) : state.initial_sync_start_time ? (
|
||||
<span className="flex items-center gap-1 text-blue-600">
|
||||
{t('runningState.inProgress')}
|
||||
{t('accounts.runningState.inProgress')}
|
||||
</span>
|
||||
) : (
|
||||
<span className="text-yellow-600">{t('accounts.runningState.notStarted')}</span>
|
||||
|
||||
@@ -38,6 +38,7 @@ import { Account } from "./action-dialog";
|
||||
import { PasswordInput } from "@/components/password-input";
|
||||
import useProxyList from "@/hooks/use-proxy";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Checkbox } from "@/components/ui/checkbox";
|
||||
|
||||
interface StepProps {
|
||||
isEdit: boolean;
|
||||
@@ -110,6 +111,23 @@ export default function Step2({ isEdit }: StepProps) {
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={control}
|
||||
name="use_dangerous"
|
||||
render={({ field }) => (
|
||||
<FormItem className="flex flex-col items-start gap-y-1">
|
||||
<FormLabel>{t('accounts.useDangerous')}:</FormLabel>
|
||||
<FormControl>
|
||||
<Checkbox
|
||||
className="mt-2"
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormDescription>{t('accounts.useDangerousDescription')}</FormDescription>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={control}
|
||||
name="imap.auth.auth_type"
|
||||
|
||||
@@ -93,7 +93,6 @@ export default function Step3() {
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormLabel className="flex items-center justify-between">{t('accounts.dateSince')}:</FormLabel>
|
||||
<RadioGroup
|
||||
defaultValue={rangeType}
|
||||
|
||||
@@ -60,4 +60,5 @@ export interface AccountModel {
|
||||
created_at: number;
|
||||
updated_at: number;
|
||||
use_proxy?: number
|
||||
use_dangerous: boolean
|
||||
}
|
||||
@@ -298,7 +298,9 @@
|
||||
"enable": "تفعيل",
|
||||
"disable": "تعطيل",
|
||||
"thisWillPreventTheAccountFromBeingUsed": "لن يقوم هذا الحساب بمزامنة البيانات من خادم IMAP بعد الآن، ولكن لا تزال البيانات الموجودة متاحة للعرض.",
|
||||
"disabled": "معطل"
|
||||
"disabled": "معطل",
|
||||
"useDangerous": "الثقة بأي شهادة TLS",
|
||||
"useDangerousDescription": "فعّل هذا الخيار فقط إذا كنت تتصل بخادم IMAP يستخدم شهادة TLS صادرة عن CA عام أو شهادة موقعة ذاتياً قد لا يتعرف عليها نظامك. هذا الإعداد يتجاوز عملية التحقق الاعتيادية من الشهادة، وقد يعرض الاتصال لهجمات “رجل في الوسط” — فعّله فقط إذا كنت تدرك المخاطر."
|
||||
},
|
||||
"mailbox": {
|
||||
"title": "صندوق البريد",
|
||||
|
||||
@@ -298,7 +298,9 @@
|
||||
"enable": "Aktivér",
|
||||
"disable": "Deaktivér",
|
||||
"thisWillPreventTheAccountFromBeingUsed": "Denne konto vil ikke længere synkronisere data fra IMAP-serveren, men eksisterende data vil stadig være tilgængelige.",
|
||||
"disabled": "Deaktiveret"
|
||||
"disabled": "Deaktiveret",
|
||||
"useDangerous": "Stol på ethvert TLS‑certifikat",
|
||||
"useDangerousDescription": "Aktivér denne indstilling kun hvis du opretter forbindelse til en IMAP‑server, der bruger et offentligt CA‑certifikat eller et selvsigneret certifikat, som dit system måske ikke genkender. Denne indstilling omgår standard certificeringsvalidering og kan gøre dig sårbar over for man‑in‑the‑middle‑angreb — aktiver kun hvis du forstår risikoen."
|
||||
},
|
||||
"mailbox": {
|
||||
"title": "Mailboks",
|
||||
|
||||
@@ -298,7 +298,9 @@
|
||||
"enable": "Aktivieren",
|
||||
"disable": "Deaktivieren",
|
||||
"thisWillPreventTheAccountFromBeingUsed": "Dieses Konto synchronisiert keine Daten mehr vom IMAP-Server, aber vorhandene Daten bleiben weiterhin zugänglich.",
|
||||
"disabled": "Deaktiviert"
|
||||
"disabled": "Deaktiviert",
|
||||
"useDangerous": "Jedem TLS‑Zertifikat vertrauen",
|
||||
"useDangerousDescription": "Aktivieren Sie diese Option nur, wenn Sie sich mit einem IMAP‑Server verbinden, der ein öffentliches CA‑Zertifikat oder ein selbstsigniertes Zertifikat verwendet, das Ihr System möglicherweise nicht erkennt. Diese Einstellung umgeht die Standard‑Zertifikatsprüfung und kann Sie für Man‑in‑the‑Middle‑Angriffe anfällig machen – aktivieren Sie nur, wenn Sie die Risiken verstehen."
|
||||
},
|
||||
"mailbox": {
|
||||
"title": "Postfach",
|
||||
|
||||
@@ -298,7 +298,9 @@
|
||||
"enable": "Enable",
|
||||
"disable": "Disable",
|
||||
"thisWillPreventTheAccountFromBeingUsed": "This account will no longer sync data from the IMAP server, but existing data will still be accessible.",
|
||||
"disabled": "Disabled"
|
||||
"disabled": "Disabled",
|
||||
"useDangerous": "Trust Any TLS Certificate",
|
||||
"useDangerousDescription": "Enable this option only if you are connecting to an IMAP server with a public or self-signed certificate that may not be recognized by your system. Using this setting bypasses standard certificate validation, which can expose you to man-in-the-middle attacks. Only enable if you understand the risks."
|
||||
},
|
||||
"mailbox": {
|
||||
"title": "Mailbox",
|
||||
|
||||
@@ -298,7 +298,9 @@
|
||||
"enable": "Activar",
|
||||
"disable": "Desactivar",
|
||||
"thisWillPreventTheAccountFromBeingUsed": "Esta cuenta ya no sincronizará datos desde el servidor IMAP, pero los datos existentes seguirán siendo accesibles.",
|
||||
"disabled": "Deshabilitado"
|
||||
"disabled": "Deshabilitado",
|
||||
"useDangerous": "Confiar en cualquier certificado TLS",
|
||||
"useDangerousDescription": "Activa esta opción solo si te estás conectando a un servidor IMAP que utiliza un certificado público o auto‑firmado, el cual puede no ser reconocido por tu sistema. Esta opción omite la validación estándar del certificado y puede exponerte a ataques de tipo “man‑in‑the‑middle” — actívala solo si entiendes los riesgos."
|
||||
},
|
||||
"mailbox": {
|
||||
"title": "Buzón",
|
||||
|
||||
@@ -298,7 +298,9 @@
|
||||
"enable": "Ota käyttöön",
|
||||
"disable": "Poista käytöstä",
|
||||
"thisWillPreventTheAccountFromBeingUsed": "Tili ei enää synkronoi tietoja IMAP-palvelimelta, mutta olemassa olevat tiedot ovat edelleen käytettävissä.",
|
||||
"disabled": "Poistettu käytöstä"
|
||||
"disabled": "Poistettu käytöstä",
|
||||
"useDangerous": "Luota mihin tahansa TLS-sertifikaattiin",
|
||||
"useDangerousDescription": "Ota tämä vaihtoehto käyttöön vain, jos IMAP-palvelin käyttää julkista CA:ta tai itse allekirjoitettua sertifikaattia, jonka järjestelmä ei tunnista. Tämä ohittaa normaalin sertifikaattitarkistuksen, ja voi altistaa Man-in-the-Middle -hyökkäyksille. Käytä vain, jos ymmärrät riskin."
|
||||
},
|
||||
"mailbox": {
|
||||
"title": "Sähköposti",
|
||||
|
||||
@@ -298,7 +298,9 @@
|
||||
"enable": "Activer",
|
||||
"disable": "Désactiver",
|
||||
"thisWillPreventTheAccountFromBeingUsed": "Ce compte ne synchronisera plus les données depuis le serveur IMAP, mais les données existantes resteront accessibles.",
|
||||
"disabled": "Désactivé"
|
||||
"disabled": "Désactivé",
|
||||
"useDangerous": "Faire confiance à n’importe quel certificat TLS",
|
||||
"useDangerousDescription": "Activez cette option seulement si vous vous connectez à un serveur IMAP utilisant un certificat public ou auto‑signé que votre système pourrait ne pas reconnaître. Cette option contourne la vérification standard des certificats, ce qui peut vous exposer à des attaques de type homme‑du‑milieu — n’activez que si vous comprenez les risques."
|
||||
},
|
||||
"mailbox": {
|
||||
"title": "Boîte aux lettres",
|
||||
|
||||
@@ -298,7 +298,9 @@
|
||||
"enable": "Attiva",
|
||||
"disable": "Disattiva",
|
||||
"thisWillPreventTheAccountFromBeingUsed": "Questo account non sincronizzerà più i dati dal server IMAP, ma i dati esistenti resteranno accessibili.",
|
||||
"disabled": "Disabilitato"
|
||||
"disabled": "Disabilitato",
|
||||
"useDangerous": "Accetta qualsiasi certificato TLS",
|
||||
"useDangerousDescription": "Abilita questa opzione solo se ti connetti a un server IMAP che utilizza un certificato pubblico o auto‑firmato che il tuo sistema potrebbe non riconoscere. Questa impostazione salta la verifica standard del certificato e può esporre la connessione ad attacchi “man‑in‑the‑middle” — attivala solo se comprendi i rischi."
|
||||
},
|
||||
"mailbox": {
|
||||
"title": "Posta in arrivo",
|
||||
|
||||
@@ -298,7 +298,9 @@
|
||||
"enable": "有効化",
|
||||
"disable": "無効化",
|
||||
"thisWillPreventTheAccountFromBeingUsed": "このアカウントはIMAPサーバーからのデータ同期を行わなくなりますが、既存のデータは引き続き参照可能です。",
|
||||
"disabled": "無効"
|
||||
"disabled": "無効",
|
||||
"useDangerous": "任意の TLS 証明書を信頼する",
|
||||
"useDangerousDescription": "このオプションを有効にすると、公開 CA または自己署名証明書を使用している IMAP サーバーに対して、システムが証明書を認識していない場合でも接続できます。ただし、標準の証明書検証をバイパスするため、中間者攻撃 (MITM) のリスクがあり — リスクを理解した上でのみ有効にしてください。"
|
||||
},
|
||||
"mailbox": {
|
||||
"title": "メールボックス",
|
||||
|
||||
@@ -298,7 +298,9 @@
|
||||
"enable": "활성화",
|
||||
"disable": "비활성화",
|
||||
"thisWillPreventTheAccountFromBeingUsed": "이 계정은 더 이상 IMAP 서버에서 데이터를 동기화하지 않지만, 기존 데이터는 계속 조회할 수 있습니다.",
|
||||
"disabled": "비활성화"
|
||||
"disabled": "비활성화",
|
||||
"useDangerous": "모든 TLS 인증서를 신뢰",
|
||||
"useDangerousDescription": "공개 CA 인증서 또는 자체 서명 인증서를 사용하는 IMAP 서버에 연결할 때, 시스템에서 인증서를 신뢰하지 않아도 이 옵션을 켜면 무시할 수 있습니다. 하지만 표준 인증서 검증을 무시하기 때문에 중간자 공격에 노출될 수 있습니다 — 위험을 이해한 경우에만 사용하세요"
|
||||
},
|
||||
"mailbox": {
|
||||
"title": "받은 편지함",
|
||||
|
||||
@@ -298,7 +298,9 @@
|
||||
"enable": "Inschakelen",
|
||||
"disable": "Uitschakelen",
|
||||
"thisWillPreventTheAccountFromBeingUsed": "Dit account zal geen gegevens meer synchroniseren van de IMAP-server, maar bestaande gegevens blijven toegankelijk.",
|
||||
"disabled": "Uitgeschakeld"
|
||||
"disabled": "Uitgeschakeld",
|
||||
"useDangerous": "Vertrouw elk TLS‑certificaat",
|
||||
"useDangerousDescription": "Schakel deze optie alleen in als je verbinding maakt met een IMAP‑server die een openbaar CA‑ of een self‑signed certificaat gebruikt dat door je systeem mogelijk niet wordt vertrouwd. Deze instelling omzeilt standaard certificaatverificatie en kan je blootstellen aan man‑in‑the‑middle‑aanvallen — activeer alleen als je de risico’s begrijpt."
|
||||
},
|
||||
"mailbox": {
|
||||
"title": "Postvak In",
|
||||
|
||||
@@ -298,7 +298,9 @@
|
||||
"enable": "Aktiver",
|
||||
"disable": "Deaktiver",
|
||||
"thisWillPreventTheAccountFromBeingUsed": "Denne kontoen vil ikke lenger synkronisere data fra IMAP-serveren, men eksisterende data vil fortsatt være tilgjengelige.",
|
||||
"disabled": "Deaktivert"
|
||||
"disabled": "Deaktivert",
|
||||
"useDangerous": "Stol på hvilken som helst TLS‑sertifikat",
|
||||
"useDangerousDescription": "Aktiver dette alternativet kun hvis IMAP‑serveren bruker et offentlig CA‑sertifikat eller et selvsignert sertifikat som systemet ditt ikke gjenkjenner. Innstillingen hopper over vanlig sertifikatvalidering, noe som kan utsette deg for man‑in‑the‑middle‑angrep – bruk kun hvis du forstår risikoen."
|
||||
},
|
||||
"mailbox": {
|
||||
"title": "Postkasse",
|
||||
|
||||
@@ -298,7 +298,9 @@
|
||||
"enable": "Ativar",
|
||||
"disable": "Desativar",
|
||||
"thisWillPreventTheAccountFromBeingUsed": "Esta conta não sincronizará mais dados do servidor IMAP, mas os dados existentes ainda estarão acessíveis.",
|
||||
"disabled": "Desativado"
|
||||
"disabled": "Desativado",
|
||||
"useDangerous": "Confiar em qualquer certificado TLS",
|
||||
"useDangerousDescription": "Ative esta opção apenas se estiver a conectar a um servidor IMAP que use um certificado público ou autoassinado que o seu sistema possa não reconhecer. Esta opção ignora a verificação normal de certificados e pode expor a ligação a ataques man‑in‑the‑middle — ative apenas se compreender os riscos."
|
||||
},
|
||||
"mailbox": {
|
||||
"title": "Caixa de Entrada",
|
||||
|
||||
@@ -298,7 +298,9 @@
|
||||
"enable": "Включить",
|
||||
"disable": "Отключить",
|
||||
"thisWillPreventTheAccountFromBeingUsed": "Этот аккаунт больше не будет синхронизировать данные с IMAP-сервера, но существующие данные останутся доступными.",
|
||||
"disabled": "Отключено"
|
||||
"disabled": "Отключено",
|
||||
"useDangerous": "Доверять любому TLS‑сертификату",
|
||||
"useDangerousDescription": "Включите этот параметр только если IMAP‑сервер использует публичный CA‑сертификат или самоподписанный сертификат, который система может не распознавать. Это отключает стандартную проверку сертификатов и может подвергнуть соединение атакам «человек‑посередине» — активируйте только если вы понимаете риски."
|
||||
},
|
||||
"mailbox": {
|
||||
"title": "Почтовый ящик",
|
||||
|
||||
@@ -298,7 +298,9 @@
|
||||
"enable": "Aktivera",
|
||||
"disable": "Inaktivera",
|
||||
"thisWillPreventTheAccountFromBeingUsed": "Det här kontot kommer inte längre att synkronisera data från IMAP-servern, men befintlig data kommer fortfarande att vara åtkomlig.",
|
||||
"disabled": "Inaktiverad"
|
||||
"disabled": "Inaktiverad",
|
||||
"useDangerous": "Lita på vilken TLS‑certifikat som helst",
|
||||
"useDangerousDescription": "Aktivera detta alternativ endast om IMAP‑servern använder ett offentligt eller självsignerat certifikat som inte är betrott av ditt system. Denna inställning kringgår standardverifiering av certifikat, vilket kan utsätta dig för man‑i‑mitten‑attacker — slå på endast om du förstår riskerna."
|
||||
},
|
||||
"mailbox": {
|
||||
"title": "Brevlåda",
|
||||
|
||||
@@ -298,7 +298,9 @@
|
||||
"enable": "啟用",
|
||||
"disable": "停用",
|
||||
"thisWillPreventTheAccountFromBeingUsed": "該帳戶將不會繼續從 IMAP 伺服器同步資料,但已有資料仍然可以查詢。",
|
||||
"disabled": "已停用"
|
||||
"disabled": "已停用",
|
||||
"useDangerous": "信任任意 TLS 憑證",
|
||||
"useDangerousDescription": "僅當你連線的 IMAP 伺服器使用公開根憑證或自簽憑證,且系統無法驗證該憑證時才啟用此選項。本選項會略過標準憑證驗證流程,可能導致中間人攻擊等安全風險 — 請確認你了解並接受這些風險後再啟用。"
|
||||
},
|
||||
"mailbox": {
|
||||
"title": "信箱",
|
||||
|
||||
@@ -298,7 +298,9 @@
|
||||
"enable": "启用",
|
||||
"disable": "禁用",
|
||||
"thisWillPreventTheAccountFromBeingUsed": "该账户将不会继续从 IMAP 服务器同步数据,但已有数据仍然可以查询。",
|
||||
"disabled": "已禁用"
|
||||
"disabled": "已禁用",
|
||||
"useDangerous": "信任任意 TLS 证书",
|
||||
"useDangerousDescription": "如果你连接的 IMAP 服务器使用公开 CA 或者自签证书,而系统不认可该证书时,启用此选项可绕过标准证书校验。但请注意,这样可能使你的连接容易受到中间人攻击 —— 仅当你完全理解风险时才启用。"
|
||||
},
|
||||
"mailbox": {
|
||||
"title": "邮箱",
|
||||
|
||||
Reference in New Issue
Block a user