refactor: extract shared pieces for non-chat messages

No functional changes:
Add a relay_addrs helper, share the protected headers and self-key rendering
of non-chat messages, and move insert_into_smtp from securejoin to smtp.
This commit is contained in:
holger krekel
2026-08-20 22:49:03 +02:00
parent 4d5ebe9ff4
commit 5176a9c633
5 changed files with 81 additions and 90 deletions

View File

@@ -10,6 +10,8 @@ use deltachat_contact_tools::sanitize_bidi_characters;
use iroh_gossip::proto::TopicId;
use mail_builder::headers::HeaderType;
use mail_builder::headers::address::Address;
use mail_builder::headers::raw::Raw;
use mail_builder::headers::text::Text;
use mail_builder::mime::MimePart;
use tokio::fs;
@@ -32,7 +34,7 @@ use crate::message::{Message, MsgId, Viewtype};
use crate::mimeparser::SystemMessage;
use crate::param::Param;
use crate::peer_channels::{create_iroh_header, get_iroh_topic_for_msg};
use crate::pgp::{SeipdVersion, addresses_from_public_key, pubkey_supports_seipdv2};
use crate::pgp::{SeipdVersion, addresses_from_public_key, pubkey_supports_seipdv2, relay_addrs};
use crate::simplify::escape_message_footer_marks;
use crate::stock_str;
use crate::tools::{IsNoneOrEmpty, create_outgoing_rfc724_mid, remove_subject_prefix, time};
@@ -578,9 +580,7 @@ impl MimeFactory {
let public_key = SignedPublicKey::from_slice(&public_key_bytes)?;
let relays =
addresses_from_public_key(&public_key).unwrap_or_else(|| vec![addr.clone()]);
recipients.extend(relays);
recipients.extend(relay_addrs(&public_key, &addr));
to.push((authname, addr.clone()));
Encryption::Asymmetric {
@@ -897,7 +897,7 @@ impl MimeFactory {
}
} else if contact.is_key_contact() {
let encryption_pubkeys = if let Some(key) = contact.public_key(context).await? {
recipients = addresses_from_public_key(&key).unwrap_or_else(|| vec![addr.clone()]);
recipients = relay_addrs(&key, &addr);
vec![(addr.clone(), key)]
} else {
Vec::new()
@@ -2439,6 +2439,42 @@ fn b_encode(value: &str) -> String {
)
}
/// Returns the headers to place into the encrypted part
/// of messages that are not part of a chat.
async fn non_chat_protected_headers(
context: &Context,
subject: &str,
) -> Result<Vec<(&'static str, HeaderType<'static>)>> {
let date = chrono::DateTime::<chrono::Utc>::from_timestamp(time(), 0)
.unwrap()
.to_rfc2822();
let mut headers = vec![
("To", Address::new_list(vec![hidden_recipients()]).into()),
("Date", Raw::new(date).into()),
("Subject", Text::new(subject.to_string()).into()),
];
// Automatic Response headers <https://www.rfc-editor.org/rfc/rfc3834>
if context.get_config_bool(Config::Bot).await? {
headers.push(("Auto-Submitted", Raw::new("auto-generated").into()));
}
Ok(headers)
}
/// Renders `queued_mail` for SMTP with the own key pair and primary address.
async fn render_with_self_key(context: &Context, queued_mail: QueuedMail) -> Result<String> {
let public_key = key::load_self_public_key(context).await?;
let secret_key = key::load_self_secret_key(context).await?;
let from_addr = context.get_primary_self_addr().await?;
let rendered_mail = render_queued_mail(
queued_mail,
&public_key,
&secret_key,
from_addr,
RenderSideEffects::default(),
)?;
Ok(rendered_mail.message)
}
pub(crate) async fn render_symm_encrypted_securejoin_message(
context: &Context,
step: &str,
@@ -2451,81 +2487,29 @@ pub(crate) async fn render_symm_encrypted_securejoin_message(
let message: MimePart<'static> = MimePart::new("text/plain", "Secure-Join");
let mut headers = Vec::<(&'static str, HeaderType<'static>)>::new();
let to: Vec<Address<'static>> = vec![hidden_recipients()];
headers.push((
"To",
mail_builder::headers::address::Address::new_list(to.clone()).into(),
));
let timestamp = time();
let date = chrono::DateTime::<chrono::Utc>::from_timestamp(timestamp, 0)
.unwrap()
.to_rfc2822();
headers.push(("Date", mail_builder::headers::raw::Raw::new(date).into()));
headers.push((
"Subject",
mail_builder::headers::text::Text::new("Secure-Join".to_string()).into(),
));
// Automatic Response headers <https://www.rfc-editor.org/rfc/rfc3834>
if context.get_config_bool(Config::Bot).await? {
headers.push((
"Auto-Submitted",
mail_builder::headers::raw::Raw::new("auto-generated".to_string()).into(),
));
}
headers.push((
"Secure-Join",
mail_builder::headers::raw::Raw::new(step.to_string()).into(),
));
headers.push((
"Secure-Join-Auth",
mail_builder::headers::text::Text::new(auth.to_string()).into(),
));
let mut headers = non_chat_protected_headers(context, "Secure-Join").await?;
headers.push(("Secure-Join", Raw::new(step.to_string()).into()));
headers.push(("Secure-Join-Auth", Text::new(auth.to_string()).into()));
let message = add_headers_to_encrypted_part(message, headers);
// Disable compression for SecureJoin to ensure
// there are no compression side channels
// leaking information about the tokens.
let should_compress = false;
// Only sign the message if we attach the pubkey.
let should_sign = should_attach_pubkey;
let raw_message = part_to_bytes(message);
let queued_mail = QueuedMail {
raw_message,
raw_message: part_to_bytes(message),
display_name: String::new(),
rfc724_mid: rfc724_mid.to_string(),
encryption: Encryption::Symmetric {
shared_secret: shared_secret.to_string(),
},
should_attach_pubkey,
should_sign,
should_compress,
// Only sign the message if we attach the pubkey.
should_sign: should_attach_pubkey,
// Disable compression for SecureJoin to ensure
// there are no compression side channels
// leaking information about the tokens.
should_compress: false,
};
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)
render_with_self_key(context, queued_mail).await
}
/// Renders MIME part into a vector of bytes.

View File

@@ -452,6 +452,12 @@ pub(crate) fn addresses_from_public_key(public_key: &SignedPublicKey) -> Option<
None
}
/// Returns the addresses to reach the owner of `public_key`,
/// falling back to `addr` if the key carries no relay list.
pub(crate) fn relay_addrs(public_key: &SignedPublicKey, addr: &str) -> Vec<String> {
addresses_from_public_key(public_key).unwrap_or_else(|| vec![addr.to_string()])
}
/// Returns true if public key advertises SEIPDv2 feature.
pub(crate) fn pubkey_supports_seipdv2(public_key: &SignedPublicKey) -> bool {
// If any Direct Key Signature or any User ID signature has SEIPDv2 feature,

View File

@@ -16,11 +16,12 @@ use crate::key;
use crate::key::{DcKey, Fingerprint, load_self_public_key, self_fingerprint};
use crate::log::LogExt as _;
use crate::log::warn;
use crate::message::{self, Message, MsgId, Viewtype};
use crate::message::{self, Message, Viewtype};
use crate::mimeparser::{MimeMessage, SystemMessage};
use crate::param::Param;
use crate::qr::check_qr;
use crate::securejoin::bob::JoinerProgress;
use crate::smtp::insert_into_smtp;
use crate::sync::Sync::*;
use crate::tools::{create_id, create_outgoing_rfc724_mid, time};
use crate::{SecurejoinSource, mimefactory, stats};
@@ -743,24 +744,6 @@ pub(crate) async fn handle_securejoin_handshake(
}
}
async fn insert_into_smtp(
context: &Context,
rfc724_mid: &str,
recipients: &str,
rendered_message: String,
msg_id: MsgId,
) -> Result<(), Error> {
context
.sql
.execute(
"INSERT INTO smtp (rfc724_mid, recipients, mime, msg_id)
VALUES (?1, ?2, ?3, ?4)",
(&rfc724_mid, &recipients, &rendered_message, msg_id),
)
.await?;
Ok(())
}
/// Observe self-sent Securejoin message.
///
/// In a multi-device-setup, there may be other devices that "see" the handshake messages.

View File

@@ -16,9 +16,8 @@ use crate::message::{self, Message, MsgId, Viewtype};
use crate::mimeparser::{MimeMessage, SystemMessage};
use crate::param::{Param, Params};
use crate::pgp::addresses_from_public_key;
use crate::securejoin::{
ContactId, encrypted_and_signed, insert_into_smtp, verify_sender_by_fingerprint,
};
use crate::securejoin::{ContactId, encrypted_and_signed, verify_sender_by_fingerprint};
use crate::smtp::insert_into_smtp;
use crate::stock_str;
use crate::sync::Sync::*;
use crate::tools::{create_outgoing_rfc724_mid, time};

View File

@@ -327,6 +327,25 @@ pub(crate) async fn smtp_send(
status
}
/// Inserts a rendered message into the `smtp` table for sending.
pub(crate) async fn insert_into_smtp(
context: &Context,
rfc724_mid: &str,
recipients: &str,
rendered_message: String,
msg_id: MsgId,
) -> Result<(), Error> {
context
.sql
.execute(
"INSERT INTO smtp (rfc724_mid, recipients, mime, msg_id)
VALUES (?1, ?2, ?3, ?4)",
(&rfc724_mid, &recipients, &rendered_message, msg_id),
)
.await?;
Ok(())
}
/// Sends message identified by `smtp` table rowid over SMTP connection.
///
/// Removes row if the message should not be retried, otherwise increments retry count.