From f83712537f71a51090fc44f02518beeee556b30a Mon Sep 17 00:00:00 2001 From: Hocuri Date: Fri, 14 Aug 2026 14:35:08 +0200 Subject: [PATCH 1/2] --wip-- [skip ci] --- src/imap.rs | 24 ++++++++++++++++++++---- src/imap/imap_tests.rs | 10 ++++++++++ 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/src/imap.rs b/src/imap.rs index c211b8b3d6..fa7b6e7236 100644 --- a/src/imap.rs +++ b/src/imap.rs @@ -146,6 +146,18 @@ pub(crate) struct ServerMetadata { pub app_versions: Option, } +/// Selects the most actionable error from failed IMAP connection candidates. +/// +/// A login error proves that a connection succeeded, so it takes precedence over network errors. +fn select_connect_error( + first_connection_error: Option, + first_login_error: Option, +) -> anyhow::Error { + first_login_error + .or(first_connection_error) + .unwrap_or_else(|| format_err!("No IMAP connection candidates provided")) +} + struct UidGrouper> { inner: Peekable, } @@ -314,7 +326,8 @@ impl Imap { self.conn_backoff_ms = max(BACKOFF_MIN_MS, self.conn_backoff_ms); let login_params = prioritize_server_login_params(&context.sql, &self.lp, "imap").await?; - let mut first_error = None; + let mut first_connection_error = None; + let mut first_login_error = None; 'candidate: for lp in login_params { info!(context, "IMAP trying to connect to {}.", lp.connection); let connection_candidate = lp.connection.clone(); @@ -330,7 +343,7 @@ impl Imap { Ok(client) => client, Err(err) => { warn!(context, "{err:#}."); - first_error.get_or_insert(err); + first_connection_error.get_or_insert(err); continue 'candidate; } }; @@ -410,7 +423,7 @@ impl Imap { let message = stock_str::cannot_login(context, &imap_user); warn!(context, "IMAP failed to login: {err:#}."); - first_error.get_or_insert(format_err!("{message} ({err:#})")); + first_login_error.get_or_insert(format_err!("{message} ({err:#})")); // If it looks like the password is wrong, send a notification: let _lock = context.wrong_pw_warning_mutex.lock().await; @@ -446,7 +459,10 @@ impl Imap { } } - Err(first_error.unwrap_or_else(|| format_err!("No IMAP connection candidates provided"))) + Err(select_connect_error( + first_connection_error, + first_login_error, + )) } /// Prepare a new IMAP session. diff --git a/src/imap/imap_tests.rs b/src/imap/imap_tests.rs index bb4c22fc70..b6b0ca3cc5 100644 --- a/src/imap/imap_tests.rs +++ b/src/imap/imap_tests.rs @@ -1,6 +1,16 @@ use super::*; use crate::test_utils::TestContext; +#[test] +fn test_connect_prefers_login_error() { + let connection_error = format_err!("All connection attempts failed"); + let login_error = format_err!("Cannot login, please check the password"); + + let error = select_connect_error(Some(connection_error), Some(login_error)); + + assert_eq!(error.to_string(), "Cannot login, please check the password"); +} + #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn test_set_uid_next_validity() { let t = TestContext::new_alice().await; From 4866fa18bd533621d5bf028cc1d6930a7e643ccc Mon Sep 17 00:00:00 2001 From: Hocuri Date: Fri, 14 Aug 2026 13:59:08 +0200 Subject: [PATCH 2/2] --wip-- [skip ci] --- src/automatic_relay_management.rs | 12 ++++++ src/mimefactory.rs | 65 +++++++++++++++++++++++++++++++ 2 files changed, 77 insertions(+) diff --git a/src/automatic_relay_management.rs b/src/automatic_relay_management.rs index 542a4da77d..d34c306621 100644 --- a/src/automatic_relay_management.rs +++ b/src/automatic_relay_management.rs @@ -174,5 +174,17 @@ pub(crate) fn login_param_from_host(host: &str) -> EnteredLoginParam { } } +pub(crate) async fn send_key_update_messages(context: &Context) -> Result<()> { + let contacts_to_update: Vec = contacts_to_update(context).await?; + + let chunks = contacts_to_update.chunks(20); + + for chunk in chunks { + send_key_update_message(context, chunk).await?; + } + + Ok(()) +} + #[cfg(test)] mod automatic_relay_management_tests; diff --git a/src/mimefactory.rs b/src/mimefactory.rs index 3f987ee98b..e9b413e131 100644 --- a/src/mimefactory.rs +++ b/src/mimefactory.rs @@ -2511,6 +2511,71 @@ pub(crate) async fn render_symm_encrypted_securejoin_message( Ok(rendered_mail.message) } +pub(crate) async fn render_key_update_message( + context: &Context, + rfc724_mid: &str, +) -> Result { + let message: MimePart<'static> = MimePart::new("text/plain", "Secure-Join"); + + let mut headers = Vec::<(&'static str, HeaderType<'static>)>::new(); + + let to: Vec> = vec![hidden_recipients()]; + headers.push(( + "To", + mail_builder::headers::address::Address::new_list(to.clone()).into(), + )); + + let timestamp = time(); + let date = chrono::DateTime::::from_timestamp(timestamp, 0) + .unwrap() + .to_rfc2822(); + headers.push(("Date", mail_builder::headers::raw::Raw::new(date).into())); + + // Automatic Response headers + if context.get_config_bool(Config::Bot).await? { + headers.push(( + "Auto-Submitted", + mail_builder::headers::raw::Raw::new("auto-generated".to_string()).into(), + )); + } + + let message = add_headers_to_encrypted_part(message, headers); + + // No need to sign; key updates are applied from whatever message. + let should_sign = false; + let should_attach_pubkey = true; + let should_compress = true; + + let raw_message = part_to_bytes(message); + + let encryption = todo!(); + + let queued_mail = QueuedMail { + raw_message, + display_name: String::new(), + rfc724_mid: rfc724_mid.to_string(), + encryption, + should_attach_pubkey, + should_sign, + should_compress, + }; + + let public_key = key::load_self_public_key(context).await?; + let secret_key = key::load_self_secret_key(context).await?; + let side_effects = RenderSideEffects::default(); + + let from_addr = context.get_primary_self_addr().await?; + let rendered_mail = render_queued_mail( + queued_mail, + &public_key, + &secret_key, + from_addr, + side_effects, + )?; + + Ok(rendered_mail.message) +} + /// Renders MIME part into a vector of bytes. pub(crate) fn part_to_bytes(message: MimePart<'static>) -> Vec { let mut raw_message = Vec::new();