From 483f4eaa17f8ef4fe66d5fd01ab46834dd0f8645 Mon Sep 17 00:00:00 2001 From: bjoern Date: Wed, 26 Feb 2025 22:03:08 +0100 Subject: [PATCH] feat: fail on too new backups (#6580) this PR checks the number from `DCBACKUP?:` and also adds it to the backup file and checks it there closes #2294 if we would reopen it --- deltachat-ffi/deltachat.h | 3 ++- deltachat-ffi/src/lot.rs | 7 ++++-- deltachat-jsonrpc/src/api/types/qr.rs | 14 +++++++++--- src/imex.rs | 17 +++++++++++++++ src/qr.rs | 31 +++++++++++++++++++++------ src/qr/qr_tests.rs | 6 ++++++ 6 files changed, 65 insertions(+), 13 deletions(-) diff --git a/deltachat-ffi/deltachat.h b/deltachat-ffi/deltachat.h index 6e943aef21..c18539961a 100644 --- a/deltachat-ffi/deltachat.h +++ b/deltachat-ffi/deltachat.h @@ -2491,8 +2491,9 @@ void dc_stop_ongoing_process (dc_context_t* context); #define DC_QR_FPR_MISMATCH 220 // id=contact #define DC_QR_FPR_WITHOUT_ADDR 230 // test1=formatted fingerprint #define DC_QR_ACCOUNT 250 // text1=domain -#define DC_QR_BACKUP 251 +#define DC_QR_BACKUP 251 // deprecated #define DC_QR_BACKUP2 252 +#define DC_QR_BACKUP_TOO_NEW 255 #define DC_QR_WEBRTC_INSTANCE 260 // text1=domain, text2=instance pattern #define DC_QR_PROXY 271 // text1=address (e.g. "127.0.0.1:9050") #define DC_QR_ADDR 320 // id=contact diff --git a/deltachat-ffi/src/lot.rs b/deltachat-ffi/src/lot.rs index 6c283404b6..0649b4ec21 100644 --- a/deltachat-ffi/src/lot.rs +++ b/deltachat-ffi/src/lot.rs @@ -50,6 +50,7 @@ impl Lot { Qr::FprWithoutAddr { fingerprint, .. } => Some(Cow::Borrowed(fingerprint)), Qr::Account { domain } => Some(Cow::Borrowed(domain)), Qr::Backup2 { .. } => None, + Qr::BackupTooNew { .. } => None, Qr::WebrtcInstance { domain, .. } => Some(Cow::Borrowed(domain)), Qr::Proxy { host, port, .. } => Some(Cow::Owned(format!("{host}:{port}"))), Qr::Addr { draft, .. } => draft.as_deref().map(Cow::Borrowed), @@ -103,6 +104,7 @@ impl Lot { Qr::FprWithoutAddr { .. } => LotState::QrFprWithoutAddr, Qr::Account { .. } => LotState::QrAccount, Qr::Backup2 { .. } => LotState::QrBackup2, + Qr::BackupTooNew { .. } => LotState::QrBackupTooNew, Qr::WebrtcInstance { .. } => LotState::QrWebrtcInstance, Qr::Proxy { .. } => LotState::QrProxy, Qr::Addr { .. } => LotState::QrAddr, @@ -129,6 +131,7 @@ impl Lot { Qr::FprWithoutAddr { .. } => Default::default(), Qr::Account { .. } => Default::default(), Qr::Backup2 { .. } => Default::default(), + Qr::BackupTooNew { .. } => Default::default(), Qr::WebrtcInstance { .. } => Default::default(), Qr::Proxy { .. } => Default::default(), Qr::Addr { contact_id, .. } => contact_id.to_u32(), @@ -178,10 +181,10 @@ pub enum LotState { /// text1=domain QrAccount = 250, - QrBackup = 251, - QrBackup2 = 252, + QrBackupTooNew = 255, + /// text1=domain, text2=instance pattern QrWebrtcInstance = 260, diff --git a/deltachat-jsonrpc/src/api/types/qr.rs b/deltachat-jsonrpc/src/api/types/qr.rs index c2588ee484..61d8141f76 100644 --- a/deltachat-jsonrpc/src/api/types/qr.rs +++ b/deltachat-jsonrpc/src/api/types/qr.rs @@ -63,6 +63,7 @@ pub enum QrObject { /// Iroh node address. node_addr: String, }, + BackupTooNew {}, /// Ask the user if they want to use the given service for video chats. WebrtcInstance { domain: String, @@ -100,11 +101,15 @@ pub enum QrObject { /// URL scanned. /// /// Ask the user if they want to open a browser or copy the URL to clipboard. - Url { url: String }, + Url { + url: String, + }, /// Text scanned. /// /// Ask the user if they want to copy the text to clipboard. - Text { text: String }, + Text { + text: String, + }, /// Ask the user if they want to withdraw their own QR code. WithdrawVerifyContact { /// Contact ID. @@ -160,7 +165,9 @@ pub enum QrObject { /// `dclogin:` scheme parameters. /// /// Ask the user if they want to login with the email address. - Login { address: String }, + Login { + address: String, + }, } impl From for QrObject { @@ -217,6 +224,7 @@ impl From for QrObject { node_addr: serde_json::to_string(node_addr).unwrap_or_default(), auth_token, }, + Qr::BackupTooNew {} => QrObject::BackupTooNew {}, Qr::WebrtcInstance { domain, instance_pattern, diff --git a/src/imex.rs b/src/imex.rs index 4ef08cb10d..dd8a589029 100644 --- a/src/imex.rs +++ b/src/imex.rs @@ -23,6 +23,7 @@ use crate::events::EventType; use crate::key::{self, DcKey, DcSecretKey, SignedPublicKey, SignedSecretKey}; use crate::log::LogExt; use crate::pgp; +use crate::qr::DCBACKUP_VERSION; use crate::sql; use crate::tools::{ create_folder, delete_file, get_filesuffix_lc, read_file, time, write_file, TempPathGuard, @@ -392,6 +393,9 @@ async fn import_backup_stream_inner( .await .context("cannot import unpacked database"); } + if res.is_ok() { + res = check_backup_version(context).await; + } if res.is_ok() { res = adjust_bcc_self(context).await; } @@ -776,6 +780,10 @@ async fn export_database( .sql .set_raw_config_int("backup_time", timestamp) .await?; + context + .sql + .set_raw_config_int("backup_version", DCBACKUP_VERSION) + .await?; sql::housekeeping(context).await.log_err(context).ok(); context .sql @@ -813,6 +821,15 @@ async fn adjust_bcc_self(context: &Context) -> Result<()> { Ok(()) } +async fn check_backup_version(context: &Context) -> Result<()> { + let version = (context.sql.get_raw_config_int("backup_version").await?).unwrap_or(2); + ensure!( + version <= DCBACKUP_VERSION, + "Backup too new, please update Delta Chat" + ); + Ok(()) +} + #[cfg(test)] mod tests { use std::time::Duration; diff --git a/src/qr.rs b/src/qr.rs index afc6013c77..4d91df894c 100644 --- a/src/qr.rs +++ b/src/qr.rs @@ -40,7 +40,11 @@ const HTTPS_SCHEME: &str = "https://"; const SHADOWSOCKS_SCHEME: &str = "ss://"; /// Backup transfer based on iroh-net. -pub(crate) const DCBACKUP2_SCHEME: &str = "DCBACKUP2:"; +pub(crate) const DCBACKUP_SCHEME_PREFIX: &str = "DCBACKUP"; + +/// Version written to Backups and Backup-QR-Codes. +/// Imports will fail when they have a larger version. +pub(crate) const DCBACKUP_VERSION: i32 = 2; /// Scanned QR code. #[derive(Debug, Clone, PartialEq, Eq)] @@ -118,6 +122,9 @@ pub enum Qr { auth_token: String, }, + /// The QR code is a backup, but it is too new. The user has to update its Delta Chat. + BackupTooNew {}, + /// Ask the user if they want to use the given service for video chats. WebrtcInstance { /// Server domain name. @@ -296,7 +303,7 @@ pub async fn check_qr(context: &Context, qr: &str) -> Result { decode_tg_socks_proxy(context, qr)? } else if qr.starts_with(SHADOWSOCKS_SCHEME) { decode_shadowsocks_proxy(qr)? - } else if starts_with_ignore_case(qr, DCBACKUP2_SCHEME) { + } else if starts_with_ignore_case(qr, DCBACKUP_SCHEME_PREFIX) { let qr_fixed = fix_add_second_device_qr(qr); decode_backup2(&qr_fixed)? } else if qr.starts_with(MAILTO_SCHEME) { @@ -367,7 +374,9 @@ pub fn format_backup(qr: &Qr) -> Result { ref auth_token, } => { let node_addr = serde_json::to_string(node_addr)?; - Ok(format!("{DCBACKUP2_SCHEME}{auth_token}&{node_addr}")) + Ok(format!( + "{DCBACKUP_SCHEME_PREFIX}{DCBACKUP_VERSION}:{auth_token}&{node_addr}" + )) } _ => Err(anyhow!("Not a backup QR code")), } @@ -643,11 +652,19 @@ fn decode_shadowsocks_proxy(qr: &str) -> Result { }) } -/// Decodes a [`DCBACKUP2_SCHEME`] QR code. +/// Decodes a `DCBACKUP` QR code. fn decode_backup2(qr: &str) -> Result { - let payload = qr - .strip_prefix(DCBACKUP2_SCHEME) - .ok_or_else(|| anyhow!("Invalid DCBACKUP2 scheme"))?; + let version_and_payload = qr + .strip_prefix(DCBACKUP_SCHEME_PREFIX) + .ok_or_else(|| anyhow!("Invalid DCBACKUP scheme"))?; + let (version, payload) = version_and_payload + .split_once(':') + .context("DCBACKUP scheme separator missing")?; + let version: i32 = version.parse().context("Not a valid number")?; + if version > DCBACKUP_VERSION { + return Ok(Qr::BackupTooNew {}); + } + let (auth_token, node_addr) = payload .split_once('&') .context("Backup QR code has no separator")?; diff --git a/src/qr/qr_tests.rs b/src/qr/qr_tests.rs index a7f6cbedf9..fa507b1bdc 100644 --- a/src/qr/qr_tests.rs +++ b/src/qr/qr_tests.rs @@ -917,5 +917,11 @@ async fn test_decode_backup() -> Result<()> { let qr = check_qr(&ctx, r#"DCBACKUP2:AIvFjRFBt_aMiisSZ8P33JqY&{"node_id":"buzkyd4x76w66qtanjk5fm6ikeuo4quletajowsl3a3p7l6j23pa","info":{"relay_url":null,"direct_addresses":["192.168.1.5:12345"]}}"#).await?; assert!(matches!(qr, Qr::Backup2 { .. })); + let qr = check_qr(&ctx, r#"DCBACKUP9:from-the-future"#).await?; + assert!(matches!(qr, Qr::BackupTooNew { .. })); + + let qr = check_qr(&ctx, r#"DCBACKUP99:far-from-the-future"#).await?; + assert!(matches!(qr, Qr::BackupTooNew { .. })); + Ok(()) }