Skip to content

Commit 483f4ea

Browse files
authored
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
1 parent 8c2207d commit 483f4ea

File tree

6 files changed

+65
-13
lines changed

6 files changed

+65
-13
lines changed

deltachat-ffi/deltachat.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2491,8 +2491,9 @@ void dc_stop_ongoing_process (dc_context_t* context);
24912491
#define DC_QR_FPR_MISMATCH 220 // id=contact
24922492
#define DC_QR_FPR_WITHOUT_ADDR 230 // test1=formatted fingerprint
24932493
#define DC_QR_ACCOUNT 250 // text1=domain
2494-
#define DC_QR_BACKUP 251
2494+
#define DC_QR_BACKUP 251 // deprecated
24952495
#define DC_QR_BACKUP2 252
2496+
#define DC_QR_BACKUP_TOO_NEW 255
24962497
#define DC_QR_WEBRTC_INSTANCE 260 // text1=domain, text2=instance pattern
24972498
#define DC_QR_PROXY 271 // text1=address (e.g. "127.0.0.1:9050")
24982499
#define DC_QR_ADDR 320 // id=contact

deltachat-ffi/src/lot.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ impl Lot {
5050
Qr::FprWithoutAddr { fingerprint, .. } => Some(Cow::Borrowed(fingerprint)),
5151
Qr::Account { domain } => Some(Cow::Borrowed(domain)),
5252
Qr::Backup2 { .. } => None,
53+
Qr::BackupTooNew { .. } => None,
5354
Qr::WebrtcInstance { domain, .. } => Some(Cow::Borrowed(domain)),
5455
Qr::Proxy { host, port, .. } => Some(Cow::Owned(format!("{host}:{port}"))),
5556
Qr::Addr { draft, .. } => draft.as_deref().map(Cow::Borrowed),
@@ -103,6 +104,7 @@ impl Lot {
103104
Qr::FprWithoutAddr { .. } => LotState::QrFprWithoutAddr,
104105
Qr::Account { .. } => LotState::QrAccount,
105106
Qr::Backup2 { .. } => LotState::QrBackup2,
107+
Qr::BackupTooNew { .. } => LotState::QrBackupTooNew,
106108
Qr::WebrtcInstance { .. } => LotState::QrWebrtcInstance,
107109
Qr::Proxy { .. } => LotState::QrProxy,
108110
Qr::Addr { .. } => LotState::QrAddr,
@@ -129,6 +131,7 @@ impl Lot {
129131
Qr::FprWithoutAddr { .. } => Default::default(),
130132
Qr::Account { .. } => Default::default(),
131133
Qr::Backup2 { .. } => Default::default(),
134+
Qr::BackupTooNew { .. } => Default::default(),
132135
Qr::WebrtcInstance { .. } => Default::default(),
133136
Qr::Proxy { .. } => Default::default(),
134137
Qr::Addr { contact_id, .. } => contact_id.to_u32(),
@@ -178,10 +181,10 @@ pub enum LotState {
178181
/// text1=domain
179182
QrAccount = 250,
180183

181-
QrBackup = 251,
182-
183184
QrBackup2 = 252,
184185

186+
QrBackupTooNew = 255,
187+
185188
/// text1=domain, text2=instance pattern
186189
QrWebrtcInstance = 260,
187190

deltachat-jsonrpc/src/api/types/qr.rs

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ pub enum QrObject {
6363
/// Iroh node address.
6464
node_addr: String,
6565
},
66+
BackupTooNew {},
6667
/// Ask the user if they want to use the given service for video chats.
6768
WebrtcInstance {
6869
domain: String,
@@ -100,11 +101,15 @@ pub enum QrObject {
100101
/// URL scanned.
101102
///
102103
/// Ask the user if they want to open a browser or copy the URL to clipboard.
103-
Url { url: String },
104+
Url {
105+
url: String,
106+
},
104107
/// Text scanned.
105108
///
106109
/// Ask the user if they want to copy the text to clipboard.
107-
Text { text: String },
110+
Text {
111+
text: String,
112+
},
108113
/// Ask the user if they want to withdraw their own QR code.
109114
WithdrawVerifyContact {
110115
/// Contact ID.
@@ -160,7 +165,9 @@ pub enum QrObject {
160165
/// `dclogin:` scheme parameters.
161166
///
162167
/// Ask the user if they want to login with the email address.
163-
Login { address: String },
168+
Login {
169+
address: String,
170+
},
164171
}
165172

166173
impl From<Qr> for QrObject {
@@ -217,6 +224,7 @@ impl From<Qr> for QrObject {
217224
node_addr: serde_json::to_string(node_addr).unwrap_or_default(),
218225
auth_token,
219226
},
227+
Qr::BackupTooNew {} => QrObject::BackupTooNew {},
220228
Qr::WebrtcInstance {
221229
domain,
222230
instance_pattern,

src/imex.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ use crate::events::EventType;
2323
use crate::key::{self, DcKey, DcSecretKey, SignedPublicKey, SignedSecretKey};
2424
use crate::log::LogExt;
2525
use crate::pgp;
26+
use crate::qr::DCBACKUP_VERSION;
2627
use crate::sql;
2728
use crate::tools::{
2829
create_folder, delete_file, get_filesuffix_lc, read_file, time, write_file, TempPathGuard,
@@ -392,6 +393,9 @@ async fn import_backup_stream_inner<R: tokio::io::AsyncRead + Unpin>(
392393
.await
393394
.context("cannot import unpacked database");
394395
}
396+
if res.is_ok() {
397+
res = check_backup_version(context).await;
398+
}
395399
if res.is_ok() {
396400
res = adjust_bcc_self(context).await;
397401
}
@@ -776,6 +780,10 @@ async fn export_database(
776780
.sql
777781
.set_raw_config_int("backup_time", timestamp)
778782
.await?;
783+
context
784+
.sql
785+
.set_raw_config_int("backup_version", DCBACKUP_VERSION)
786+
.await?;
779787
sql::housekeeping(context).await.log_err(context).ok();
780788
context
781789
.sql
@@ -813,6 +821,15 @@ async fn adjust_bcc_self(context: &Context) -> Result<()> {
813821
Ok(())
814822
}
815823

824+
async fn check_backup_version(context: &Context) -> Result<()> {
825+
let version = (context.sql.get_raw_config_int("backup_version").await?).unwrap_or(2);
826+
ensure!(
827+
version <= DCBACKUP_VERSION,
828+
"Backup too new, please update Delta Chat"
829+
);
830+
Ok(())
831+
}
832+
816833
#[cfg(test)]
817834
mod tests {
818835
use std::time::Duration;

src/qr.rs

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,11 @@ const HTTPS_SCHEME: &str = "https://";
4040
const SHADOWSOCKS_SCHEME: &str = "ss://";
4141

4242
/// Backup transfer based on iroh-net.
43-
pub(crate) const DCBACKUP2_SCHEME: &str = "DCBACKUP2:";
43+
pub(crate) const DCBACKUP_SCHEME_PREFIX: &str = "DCBACKUP";
44+
45+
/// Version written to Backups and Backup-QR-Codes.
46+
/// Imports will fail when they have a larger version.
47+
pub(crate) const DCBACKUP_VERSION: i32 = 2;
4448

4549
/// Scanned QR code.
4650
#[derive(Debug, Clone, PartialEq, Eq)]
@@ -118,6 +122,9 @@ pub enum Qr {
118122
auth_token: String,
119123
},
120124

125+
/// The QR code is a backup, but it is too new. The user has to update its Delta Chat.
126+
BackupTooNew {},
127+
121128
/// Ask the user if they want to use the given service for video chats.
122129
WebrtcInstance {
123130
/// Server domain name.
@@ -296,7 +303,7 @@ pub async fn check_qr(context: &Context, qr: &str) -> Result<Qr> {
296303
decode_tg_socks_proxy(context, qr)?
297304
} else if qr.starts_with(SHADOWSOCKS_SCHEME) {
298305
decode_shadowsocks_proxy(qr)?
299-
} else if starts_with_ignore_case(qr, DCBACKUP2_SCHEME) {
306+
} else if starts_with_ignore_case(qr, DCBACKUP_SCHEME_PREFIX) {
300307
let qr_fixed = fix_add_second_device_qr(qr);
301308
decode_backup2(&qr_fixed)?
302309
} else if qr.starts_with(MAILTO_SCHEME) {
@@ -367,7 +374,9 @@ pub fn format_backup(qr: &Qr) -> Result<String> {
367374
ref auth_token,
368375
} => {
369376
let node_addr = serde_json::to_string(node_addr)?;
370-
Ok(format!("{DCBACKUP2_SCHEME}{auth_token}&{node_addr}"))
377+
Ok(format!(
378+
"{DCBACKUP_SCHEME_PREFIX}{DCBACKUP_VERSION}:{auth_token}&{node_addr}"
379+
))
371380
}
372381
_ => Err(anyhow!("Not a backup QR code")),
373382
}
@@ -643,11 +652,19 @@ fn decode_shadowsocks_proxy(qr: &str) -> Result<Qr> {
643652
})
644653
}
645654

646-
/// Decodes a [`DCBACKUP2_SCHEME`] QR code.
655+
/// Decodes a `DCBACKUP` QR code.
647656
fn decode_backup2(qr: &str) -> Result<Qr> {
648-
let payload = qr
649-
.strip_prefix(DCBACKUP2_SCHEME)
650-
.ok_or_else(|| anyhow!("Invalid DCBACKUP2 scheme"))?;
657+
let version_and_payload = qr
658+
.strip_prefix(DCBACKUP_SCHEME_PREFIX)
659+
.ok_or_else(|| anyhow!("Invalid DCBACKUP scheme"))?;
660+
let (version, payload) = version_and_payload
661+
.split_once(':')
662+
.context("DCBACKUP scheme separator missing")?;
663+
let version: i32 = version.parse().context("Not a valid number")?;
664+
if version > DCBACKUP_VERSION {
665+
return Ok(Qr::BackupTooNew {});
666+
}
667+
651668
let (auth_token, node_addr) = payload
652669
.split_once('&')
653670
.context("Backup QR code has no separator")?;

src/qr/qr_tests.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -917,5 +917,11 @@ async fn test_decode_backup() -> Result<()> {
917917
let qr = check_qr(&ctx, r#"DCBACKUP2:AIvFjRFBt_aMiisSZ8P33JqY&{"node_id":"buzkyd4x76w66qtanjk5fm6ikeuo4quletajowsl3a3p7l6j23pa","info":{"relay_url":null,"direct_addresses":["192.168.1.5:12345"]}}"#).await?;
918918
assert!(matches!(qr, Qr::Backup2 { .. }));
919919

920+
let qr = check_qr(&ctx, r#"DCBACKUP9:from-the-future"#).await?;
921+
assert!(matches!(qr, Qr::BackupTooNew { .. }));
922+
923+
let qr = check_qr(&ctx, r#"DCBACKUP99:far-from-the-future"#).await?;
924+
assert!(matches!(qr, Qr::BackupTooNew { .. }));
925+
920926
Ok(())
921927
}

0 commit comments

Comments
 (0)