mirror of
https://github.com/chatmail/core.git
synced 2026-05-05 14:26:30 +03:00
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
This commit is contained in:
@@ -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_MISMATCH 220 // id=contact
|
||||||
#define DC_QR_FPR_WITHOUT_ADDR 230 // test1=formatted fingerprint
|
#define DC_QR_FPR_WITHOUT_ADDR 230 // test1=formatted fingerprint
|
||||||
#define DC_QR_ACCOUNT 250 // text1=domain
|
#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_BACKUP2 252
|
||||||
|
#define DC_QR_BACKUP_TOO_NEW 255
|
||||||
#define DC_QR_WEBRTC_INSTANCE 260 // text1=domain, text2=instance pattern
|
#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_PROXY 271 // text1=address (e.g. "127.0.0.1:9050")
|
||||||
#define DC_QR_ADDR 320 // id=contact
|
#define DC_QR_ADDR 320 // id=contact
|
||||||
|
|||||||
@@ -50,6 +50,7 @@ impl Lot {
|
|||||||
Qr::FprWithoutAddr { fingerprint, .. } => Some(Cow::Borrowed(fingerprint)),
|
Qr::FprWithoutAddr { fingerprint, .. } => Some(Cow::Borrowed(fingerprint)),
|
||||||
Qr::Account { domain } => Some(Cow::Borrowed(domain)),
|
Qr::Account { domain } => Some(Cow::Borrowed(domain)),
|
||||||
Qr::Backup2 { .. } => None,
|
Qr::Backup2 { .. } => None,
|
||||||
|
Qr::BackupTooNew { .. } => None,
|
||||||
Qr::WebrtcInstance { domain, .. } => Some(Cow::Borrowed(domain)),
|
Qr::WebrtcInstance { domain, .. } => Some(Cow::Borrowed(domain)),
|
||||||
Qr::Proxy { host, port, .. } => Some(Cow::Owned(format!("{host}:{port}"))),
|
Qr::Proxy { host, port, .. } => Some(Cow::Owned(format!("{host}:{port}"))),
|
||||||
Qr::Addr { draft, .. } => draft.as_deref().map(Cow::Borrowed),
|
Qr::Addr { draft, .. } => draft.as_deref().map(Cow::Borrowed),
|
||||||
@@ -103,6 +104,7 @@ impl Lot {
|
|||||||
Qr::FprWithoutAddr { .. } => LotState::QrFprWithoutAddr,
|
Qr::FprWithoutAddr { .. } => LotState::QrFprWithoutAddr,
|
||||||
Qr::Account { .. } => LotState::QrAccount,
|
Qr::Account { .. } => LotState::QrAccount,
|
||||||
Qr::Backup2 { .. } => LotState::QrBackup2,
|
Qr::Backup2 { .. } => LotState::QrBackup2,
|
||||||
|
Qr::BackupTooNew { .. } => LotState::QrBackupTooNew,
|
||||||
Qr::WebrtcInstance { .. } => LotState::QrWebrtcInstance,
|
Qr::WebrtcInstance { .. } => LotState::QrWebrtcInstance,
|
||||||
Qr::Proxy { .. } => LotState::QrProxy,
|
Qr::Proxy { .. } => LotState::QrProxy,
|
||||||
Qr::Addr { .. } => LotState::QrAddr,
|
Qr::Addr { .. } => LotState::QrAddr,
|
||||||
@@ -129,6 +131,7 @@ impl Lot {
|
|||||||
Qr::FprWithoutAddr { .. } => Default::default(),
|
Qr::FprWithoutAddr { .. } => Default::default(),
|
||||||
Qr::Account { .. } => Default::default(),
|
Qr::Account { .. } => Default::default(),
|
||||||
Qr::Backup2 { .. } => Default::default(),
|
Qr::Backup2 { .. } => Default::default(),
|
||||||
|
Qr::BackupTooNew { .. } => Default::default(),
|
||||||
Qr::WebrtcInstance { .. } => Default::default(),
|
Qr::WebrtcInstance { .. } => Default::default(),
|
||||||
Qr::Proxy { .. } => Default::default(),
|
Qr::Proxy { .. } => Default::default(),
|
||||||
Qr::Addr { contact_id, .. } => contact_id.to_u32(),
|
Qr::Addr { contact_id, .. } => contact_id.to_u32(),
|
||||||
@@ -178,10 +181,10 @@ pub enum LotState {
|
|||||||
/// text1=domain
|
/// text1=domain
|
||||||
QrAccount = 250,
|
QrAccount = 250,
|
||||||
|
|
||||||
QrBackup = 251,
|
|
||||||
|
|
||||||
QrBackup2 = 252,
|
QrBackup2 = 252,
|
||||||
|
|
||||||
|
QrBackupTooNew = 255,
|
||||||
|
|
||||||
/// text1=domain, text2=instance pattern
|
/// text1=domain, text2=instance pattern
|
||||||
QrWebrtcInstance = 260,
|
QrWebrtcInstance = 260,
|
||||||
|
|
||||||
|
|||||||
@@ -63,6 +63,7 @@ pub enum QrObject {
|
|||||||
/// Iroh node address.
|
/// Iroh node address.
|
||||||
node_addr: String,
|
node_addr: String,
|
||||||
},
|
},
|
||||||
|
BackupTooNew {},
|
||||||
/// Ask the user if they want to use the given service for video chats.
|
/// Ask the user if they want to use the given service for video chats.
|
||||||
WebrtcInstance {
|
WebrtcInstance {
|
||||||
domain: String,
|
domain: String,
|
||||||
@@ -100,11 +101,15 @@ pub enum QrObject {
|
|||||||
/// URL scanned.
|
/// URL scanned.
|
||||||
///
|
///
|
||||||
/// Ask the user if they want to open a browser or copy the URL to clipboard.
|
/// Ask the user if they want to open a browser or copy the URL to clipboard.
|
||||||
Url { url: String },
|
Url {
|
||||||
|
url: String,
|
||||||
|
},
|
||||||
/// Text scanned.
|
/// Text scanned.
|
||||||
///
|
///
|
||||||
/// Ask the user if they want to copy the text to clipboard.
|
/// 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.
|
/// Ask the user if they want to withdraw their own QR code.
|
||||||
WithdrawVerifyContact {
|
WithdrawVerifyContact {
|
||||||
/// Contact ID.
|
/// Contact ID.
|
||||||
@@ -160,7 +165,9 @@ pub enum QrObject {
|
|||||||
/// `dclogin:` scheme parameters.
|
/// `dclogin:` scheme parameters.
|
||||||
///
|
///
|
||||||
/// Ask the user if they want to login with the email address.
|
/// Ask the user if they want to login with the email address.
|
||||||
Login { address: String },
|
Login {
|
||||||
|
address: String,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Qr> for QrObject {
|
impl From<Qr> for QrObject {
|
||||||
@@ -217,6 +224,7 @@ impl From<Qr> for QrObject {
|
|||||||
node_addr: serde_json::to_string(node_addr).unwrap_or_default(),
|
node_addr: serde_json::to_string(node_addr).unwrap_or_default(),
|
||||||
auth_token,
|
auth_token,
|
||||||
},
|
},
|
||||||
|
Qr::BackupTooNew {} => QrObject::BackupTooNew {},
|
||||||
Qr::WebrtcInstance {
|
Qr::WebrtcInstance {
|
||||||
domain,
|
domain,
|
||||||
instance_pattern,
|
instance_pattern,
|
||||||
|
|||||||
17
src/imex.rs
17
src/imex.rs
@@ -23,6 +23,7 @@ use crate::events::EventType;
|
|||||||
use crate::key::{self, DcKey, DcSecretKey, SignedPublicKey, SignedSecretKey};
|
use crate::key::{self, DcKey, DcSecretKey, SignedPublicKey, SignedSecretKey};
|
||||||
use crate::log::LogExt;
|
use crate::log::LogExt;
|
||||||
use crate::pgp;
|
use crate::pgp;
|
||||||
|
use crate::qr::DCBACKUP_VERSION;
|
||||||
use crate::sql;
|
use crate::sql;
|
||||||
use crate::tools::{
|
use crate::tools::{
|
||||||
create_folder, delete_file, get_filesuffix_lc, read_file, time, write_file, TempPathGuard,
|
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>(
|
|||||||
.await
|
.await
|
||||||
.context("cannot import unpacked database");
|
.context("cannot import unpacked database");
|
||||||
}
|
}
|
||||||
|
if res.is_ok() {
|
||||||
|
res = check_backup_version(context).await;
|
||||||
|
}
|
||||||
if res.is_ok() {
|
if res.is_ok() {
|
||||||
res = adjust_bcc_self(context).await;
|
res = adjust_bcc_self(context).await;
|
||||||
}
|
}
|
||||||
@@ -776,6 +780,10 @@ async fn export_database(
|
|||||||
.sql
|
.sql
|
||||||
.set_raw_config_int("backup_time", timestamp)
|
.set_raw_config_int("backup_time", timestamp)
|
||||||
.await?;
|
.await?;
|
||||||
|
context
|
||||||
|
.sql
|
||||||
|
.set_raw_config_int("backup_version", DCBACKUP_VERSION)
|
||||||
|
.await?;
|
||||||
sql::housekeeping(context).await.log_err(context).ok();
|
sql::housekeeping(context).await.log_err(context).ok();
|
||||||
context
|
context
|
||||||
.sql
|
.sql
|
||||||
@@ -813,6 +821,15 @@ async fn adjust_bcc_self(context: &Context) -> Result<()> {
|
|||||||
Ok(())
|
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)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|||||||
31
src/qr.rs
31
src/qr.rs
@@ -40,7 +40,11 @@ const HTTPS_SCHEME: &str = "https://";
|
|||||||
const SHADOWSOCKS_SCHEME: &str = "ss://";
|
const SHADOWSOCKS_SCHEME: &str = "ss://";
|
||||||
|
|
||||||
/// Backup transfer based on iroh-net.
|
/// 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.
|
/// Scanned QR code.
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
@@ -118,6 +122,9 @@ pub enum Qr {
|
|||||||
auth_token: String,
|
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.
|
/// Ask the user if they want to use the given service for video chats.
|
||||||
WebrtcInstance {
|
WebrtcInstance {
|
||||||
/// Server domain name.
|
/// Server domain name.
|
||||||
@@ -296,7 +303,7 @@ pub async fn check_qr(context: &Context, qr: &str) -> Result<Qr> {
|
|||||||
decode_tg_socks_proxy(context, qr)?
|
decode_tg_socks_proxy(context, qr)?
|
||||||
} else if qr.starts_with(SHADOWSOCKS_SCHEME) {
|
} else if qr.starts_with(SHADOWSOCKS_SCHEME) {
|
||||||
decode_shadowsocks_proxy(qr)?
|
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);
|
let qr_fixed = fix_add_second_device_qr(qr);
|
||||||
decode_backup2(&qr_fixed)?
|
decode_backup2(&qr_fixed)?
|
||||||
} else if qr.starts_with(MAILTO_SCHEME) {
|
} else if qr.starts_with(MAILTO_SCHEME) {
|
||||||
@@ -367,7 +374,9 @@ pub fn format_backup(qr: &Qr) -> Result<String> {
|
|||||||
ref auth_token,
|
ref auth_token,
|
||||||
} => {
|
} => {
|
||||||
let node_addr = serde_json::to_string(node_addr)?;
|
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")),
|
_ => Err(anyhow!("Not a backup QR code")),
|
||||||
}
|
}
|
||||||
@@ -643,11 +652,19 @@ fn decode_shadowsocks_proxy(qr: &str) -> Result<Qr> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Decodes a [`DCBACKUP2_SCHEME`] QR code.
|
/// Decodes a `DCBACKUP` QR code.
|
||||||
fn decode_backup2(qr: &str) -> Result<Qr> {
|
fn decode_backup2(qr: &str) -> Result<Qr> {
|
||||||
let payload = qr
|
let version_and_payload = qr
|
||||||
.strip_prefix(DCBACKUP2_SCHEME)
|
.strip_prefix(DCBACKUP_SCHEME_PREFIX)
|
||||||
.ok_or_else(|| anyhow!("Invalid DCBACKUP2 scheme"))?;
|
.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
|
let (auth_token, node_addr) = payload
|
||||||
.split_once('&')
|
.split_once('&')
|
||||||
.context("Backup QR code has no separator")?;
|
.context("Backup QR code has no separator")?;
|
||||||
|
|||||||
@@ -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?;
|
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 { .. }));
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user