1use std::collections::BTreeMap;
4use std::collections::BTreeSet;
5use std::time::Instant;
6
7use anyhow::{ensure, Context as _, Result};
8use deltachat_contact_tools::addr_cmp;
9use deltachat_contact_tools::EmailAddress;
10use pgp::composed::SignedPublicKey;
11use rusqlite::OptionalExtension;
12
13use crate::config::Config;
14use crate::configure::EnteredLoginParam;
15use crate::constants::ShowEmails;
16use crate::context::Context;
17use crate::imap;
18use crate::key::DcKey;
19use crate::log::{info, warn};
20use crate::login_param::ConfiguredLoginParam;
21use crate::message::MsgId;
22use crate::provider::get_provider_by_domain;
23use crate::sql::Sql;
24use crate::tools::inc_and_check;
25
26const DBVERSION: i32 = 68;
27const VERSION_CFG: &str = "dbversion";
28const TABLES: &str = include_str!("./tables.sql");
29
30#[cfg(test)]
31tokio::task_local! {
32 static STOP_MIGRATIONS_AT: i32;
33}
34
35pub async fn run(context: &Context, sql: &Sql) -> Result<(bool, bool, bool)> {
36 let mut exists_before_update = false;
37 let mut dbversion_before_update = DBVERSION;
38
39 if !sql
40 .table_exists("config")
41 .await
42 .context("failed to check if config table exists")?
43 {
44 sql.transaction(move |transaction| {
45 transaction.execute_batch(TABLES)?;
46
47 transaction.execute(
49 "INSERT INTO config (keyname, value) VALUES (?, ?);",
50 (VERSION_CFG, format!("{dbversion_before_update}")),
51 )?;
52 Ok(())
53 })
54 .await
55 .context("Creating tables failed")?;
56
57 let mut lock = context.sql.config_cache.write().await;
58 lock.insert(
59 VERSION_CFG.to_string(),
60 Some(format!("{dbversion_before_update}")),
61 );
62 drop(lock);
63 } else {
64 exists_before_update = true;
65 dbversion_before_update = sql
66 .get_raw_config_int(VERSION_CFG)
67 .await?
68 .unwrap_or_default();
69 }
70
71 let dbversion = dbversion_before_update;
72 let mut update_icons = !exists_before_update;
73 let mut disable_server_delete = false;
74 let mut recode_avatar = false;
75
76 if dbversion < 1 {
77 sql.execute_migration(
78 r#"
79CREATE TABLE leftgrps ( id INTEGER PRIMARY KEY, grpid TEXT DEFAULT '');
80CREATE INDEX leftgrps_index1 ON leftgrps (grpid);"#,
81 1,
82 )
83 .await?;
84 }
85 if dbversion < 2 {
86 sql.execute_migration(
87 "ALTER TABLE contacts ADD COLUMN authname TEXT DEFAULT '';",
88 2,
89 )
90 .await?;
91 }
92 if dbversion < 7 {
93 sql.execute_migration(
94 "CREATE TABLE keypairs (\
95 id INTEGER PRIMARY KEY, \
96 addr TEXT DEFAULT '' COLLATE NOCASE, \
97 is_default INTEGER DEFAULT 0, \
98 private_key, \
99 public_key, \
100 created INTEGER DEFAULT 0);",
101 7,
102 )
103 .await?;
104 }
105 if dbversion < 10 {
106 sql.execute_migration(
107 "CREATE TABLE acpeerstates (\
108 id INTEGER PRIMARY KEY, \
109 addr TEXT DEFAULT '' COLLATE NOCASE, \
110 last_seen INTEGER DEFAULT 0, \
111 last_seen_autocrypt INTEGER DEFAULT 0, \
112 public_key, \
113 prefer_encrypted INTEGER DEFAULT 0); \
114 CREATE INDEX acpeerstates_index1 ON acpeerstates (addr);",
115 10,
116 )
117 .await?;
118 }
119 if dbversion < 12 {
120 sql.execute_migration(
121 r#"
122CREATE TABLE msgs_mdns ( msg_id INTEGER, contact_id INTEGER);
123CREATE INDEX msgs_mdns_index1 ON msgs_mdns (msg_id);"#,
124 12,
125 )
126 .await?;
127 }
128 if dbversion < 17 {
129 sql.execute_migration(
130 r#"
131ALTER TABLE chats ADD COLUMN archived INTEGER DEFAULT 0;
132CREATE INDEX chats_index2 ON chats (archived);
133ALTER TABLE msgs ADD COLUMN starred INTEGER DEFAULT 0;
134CREATE INDEX msgs_index5 ON msgs (starred);"#,
135 17,
136 )
137 .await?;
138 }
139 if dbversion < 18 {
140 sql.execute_migration(
141 r#"
142ALTER TABLE acpeerstates ADD COLUMN gossip_timestamp INTEGER DEFAULT 0;
143ALTER TABLE acpeerstates ADD COLUMN gossip_key;"#,
144 18,
145 )
146 .await?;
147 }
148 if dbversion < 27 {
149 sql.execute_migration(
152 r#"
153DELETE FROM msgs WHERE chat_id=1 OR chat_id=2;
154CREATE INDEX chats_contacts_index2 ON chats_contacts (contact_id);
155ALTER TABLE msgs ADD COLUMN timestamp_sent INTEGER DEFAULT 0;
156ALTER TABLE msgs ADD COLUMN timestamp_rcvd INTEGER DEFAULT 0;"#,
157 27,
158 )
159 .await?;
160 }
161 if dbversion < 34 {
162 sql.execute_migration(
163 r#"
164ALTER TABLE msgs ADD COLUMN hidden INTEGER DEFAULT 0;
165ALTER TABLE msgs_mdns ADD COLUMN timestamp_sent INTEGER DEFAULT 0;
166ALTER TABLE acpeerstates ADD COLUMN public_key_fingerprint TEXT DEFAULT '';
167ALTER TABLE acpeerstates ADD COLUMN gossip_key_fingerprint TEXT DEFAULT '';
168CREATE INDEX acpeerstates_index3 ON acpeerstates (public_key_fingerprint);
169CREATE INDEX acpeerstates_index4 ON acpeerstates (gossip_key_fingerprint);"#,
170 34,
171 )
172 .await?;
173 }
174 if dbversion < 39 {
175 sql.execute_migration(
176 r#"
177CREATE TABLE tokens (
178 id INTEGER PRIMARY KEY,
179 namespc INTEGER DEFAULT 0,
180 foreign_id INTEGER DEFAULT 0,
181 token TEXT DEFAULT '',
182 timestamp INTEGER DEFAULT 0
183);
184ALTER TABLE acpeerstates ADD COLUMN verified_key;
185ALTER TABLE acpeerstates ADD COLUMN verified_key_fingerprint TEXT DEFAULT '';
186CREATE INDEX acpeerstates_index5 ON acpeerstates (verified_key_fingerprint);"#,
187 39,
188 )
189 .await?;
190 }
191 if dbversion < 40 {
192 sql.execute_migration("ALTER TABLE jobs ADD COLUMN thread INTEGER DEFAULT 0;", 40)
193 .await?;
194 }
195 if dbversion < 44 {
196 sql.execute_migration("ALTER TABLE msgs ADD COLUMN mime_headers TEXT;", 44)
197 .await?;
198 }
199 if dbversion < 46 {
200 sql.execute_migration(
201 r#"
202ALTER TABLE msgs ADD COLUMN mime_in_reply_to TEXT;
203ALTER TABLE msgs ADD COLUMN mime_references TEXT;"#,
204 46,
205 )
206 .await?;
207 }
208 if dbversion < 47 {
209 sql.execute_migration("ALTER TABLE jobs ADD COLUMN tries INTEGER DEFAULT 0;", 47)
210 .await?;
211 }
212 if dbversion < 48 {
213 sql.execute_migration(
215 "ALTER TABLE msgs ADD COLUMN move_state INTEGER DEFAULT 1;",
216 48,
217 )
218 .await?;
219 }
220 if dbversion < 49 {
221 sql.execute_migration(
222 "ALTER TABLE chats ADD COLUMN gossiped_timestamp INTEGER DEFAULT 0;",
223 49,
224 )
225 .await?;
226 }
227 if dbversion < 50 {
228 if exists_before_update {
232 sql.set_raw_config_int("show_emails", ShowEmails::All as i32)
233 .await?;
234 }
235 sql.set_db_version(50).await?;
236 }
237 if dbversion < 53 {
238 sql.execute_migration(
241 r#"
242CREATE TABLE locations (
243 id INTEGER PRIMARY KEY AUTOINCREMENT,
244 latitude REAL DEFAULT 0.0,
245 longitude REAL DEFAULT 0.0,
246 accuracy REAL DEFAULT 0.0,
247 timestamp INTEGER DEFAULT 0,
248 chat_id INTEGER DEFAULT 0,
249 from_id INTEGER DEFAULT 0
250);"
251CREATE INDEX locations_index1 ON locations (from_id);
252CREATE INDEX locations_index2 ON locations (timestamp);
253ALTER TABLE chats ADD COLUMN locations_send_begin INTEGER DEFAULT 0;
254ALTER TABLE chats ADD COLUMN locations_send_until INTEGER DEFAULT 0;
255ALTER TABLE chats ADD COLUMN locations_last_sent INTEGER DEFAULT 0;
256CREATE INDEX chats_index3 ON chats (locations_send_until);"#,
257 53,
258 )
259 .await?;
260 }
261 if dbversion < 54 {
262 sql.execute_migration(
263 r#"
264ALTER TABLE msgs ADD COLUMN location_id INTEGER DEFAULT 0;
265CREATE INDEX msgs_index6 ON msgs (location_id);"#,
266 54,
267 )
268 .await?;
269 }
270 if dbversion < 55 {
271 sql.execute_migration(
272 "ALTER TABLE locations ADD COLUMN independent INTEGER DEFAULT 0;",
273 55,
274 )
275 .await?;
276 }
277 if dbversion < 59 {
278 sql.execute_migration(
281 r#"
282CREATE TABLE devmsglabels (id INTEGER PRIMARY KEY AUTOINCREMENT, label TEXT, msg_id INTEGER DEFAULT 0);
283CREATE INDEX devmsglabels_index1 ON devmsglabels (label);"#, 59)
284 .await?;
285 if exists_before_update && sql.get_raw_config_int("bcc_self").await?.is_none() {
286 sql.set_raw_config_int("bcc_self", 1).await?;
287 }
288 }
289
290 if dbversion < 60 {
291 sql.execute_migration(
292 "ALTER TABLE chats ADD COLUMN created_timestamp INTEGER DEFAULT 0;",
293 60,
294 )
295 .await?;
296 }
297 if dbversion < 61 {
298 sql.execute_migration(
299 "ALTER TABLE contacts ADD COLUMN selfavatar_sent INTEGER DEFAULT 0;",
300 61,
301 )
302 .await?;
303 update_icons = true;
304 }
305 if dbversion < 62 {
306 sql.execute_migration(
307 "ALTER TABLE chats ADD COLUMN muted_until INTEGER DEFAULT 0;",
308 62,
309 )
310 .await?;
311 }
312 if dbversion < 63 {
313 sql.execute_migration("UPDATE chats SET grpid='' WHERE type=100", 63)
314 .await?;
315 }
316 if dbversion < 64 {
317 sql.execute_migration("ALTER TABLE msgs ADD COLUMN error TEXT DEFAULT '';", 64)
318 .await?;
319 }
320 if dbversion < 65 {
321 sql.execute_migration(
322 r#"
323ALTER TABLE chats ADD COLUMN ephemeral_timer INTEGER;
324ALTER TABLE msgs ADD COLUMN ephemeral_timer INTEGER DEFAULT 0;
325ALTER TABLE msgs ADD COLUMN ephemeral_timestamp INTEGER DEFAULT 0;"#,
326 65,
327 )
328 .await?;
329 }
330 if dbversion < 66 {
331 update_icons = true;
332 sql.set_db_version(66).await?;
333 }
334 if dbversion < 67 {
335 for prefix in &["", "configured_"] {
336 if let Some(server_flags) = sql
337 .get_raw_config_int(&format!("{prefix}server_flags"))
338 .await?
339 {
340 let imap_socket_flags = server_flags & 0x700;
341 let key = &format!("{prefix}mail_security");
342 match imap_socket_flags {
343 0x100 => sql.set_raw_config_int(key, 2).await?, 0x200 => sql.set_raw_config_int(key, 1).await?, 0x400 => sql.set_raw_config_int(key, 3).await?, _ => sql.set_raw_config_int(key, 0).await?,
347 }
348 let smtp_socket_flags = server_flags & 0x70000;
349 let key = &format!("{prefix}send_security");
350 match smtp_socket_flags {
351 0x10000 => sql.set_raw_config_int(key, 2).await?, 0x20000 => sql.set_raw_config_int(key, 1).await?, 0x40000 => sql.set_raw_config_int(key, 3).await?, _ => sql.set_raw_config_int(key, 0).await?,
355 }
356 }
357 }
358 sql.set_db_version(67).await?;
359 }
360 if dbversion < 68 {
361 sql.execute_migration(
363 "CREATE INDEX IF NOT EXISTS msgs_index7 ON msgs (state, hidden, chat_id);",
364 68,
365 )
366 .await?;
367 }
368 if dbversion < 69 {
369 sql.execute_migration(
370 r#"
371ALTER TABLE chats ADD COLUMN protected INTEGER DEFAULT 0;
372-- 120=group, 130=old verified group
373UPDATE chats SET protected=1, type=120 WHERE type=130;"#,
374 69,
375 )
376 .await?;
377 }
378
379 if dbversion < 71 {
380 if let Ok(addr) = context.get_primary_self_addr().await {
381 if let Ok(domain) = EmailAddress::new(&addr).map(|email| email.domain) {
382 context
383 .set_config_internal(
384 Config::ConfiguredProvider,
385 get_provider_by_domain(&domain).map(|provider| provider.id),
386 )
387 .await?;
388 } else {
389 warn!(context, "Can't parse configured address: {:?}", addr);
390 }
391 }
392
393 sql.set_db_version(71).await?;
394 }
395 if dbversion < 72 && !sql.col_exists("msgs", "mime_modified").await? {
396 sql.execute_migration(
397 r#"
398 ALTER TABLE msgs ADD COLUMN mime_modified INTEGER DEFAULT 0;"#,
399 72,
400 )
401 .await?;
402 }
403 if dbversion < 73 {
404 use Config::*;
405 sql.execute(
406 r#"
407CREATE TABLE imap_sync (folder TEXT PRIMARY KEY, uidvalidity INTEGER DEFAULT 0, uid_next INTEGER DEFAULT 0);"#,
408()
409 )
410 .await?;
411 for c in &[
412 ConfiguredInboxFolder,
413 ConfiguredSentboxFolder,
414 ConfiguredMvboxFolder,
415 ] {
416 if let Some(folder) = context.get_config(*c).await? {
417 let (uid_validity, last_seen_uid) =
418 imap::get_config_last_seen_uid(context, &folder).await?;
419 if last_seen_uid > 0 {
420 imap::set_uid_next(context, &folder, last_seen_uid + 1).await?;
421 imap::set_uidvalidity(context, &folder, uid_validity).await?;
422 }
423 }
424 }
425 if exists_before_update {
426 disable_server_delete = true;
427
428 if let Some(provider) = context.get_configured_provider().await? {
430 if let Some(defaults) = &provider.config_defaults {
431 if defaults.iter().any(|d| d.key == Config::DeleteServerAfter) {
432 disable_server_delete = false;
433 }
434 }
435 }
436 }
437 sql.set_db_version(73).await?;
438 }
439 if dbversion < 74 {
440 sql.execute_migration("UPDATE contacts SET name='' WHERE name=authname", 74)
441 .await?;
442 }
443 if dbversion < 75 {
444 sql.execute_migration(
445 "ALTER TABLE contacts ADD COLUMN status TEXT DEFAULT '';",
446 75,
447 )
448 .await?;
449 }
450 if dbversion < 76 {
451 sql.execute_migration("ALTER TABLE msgs ADD COLUMN subject TEXT DEFAULT '';", 76)
452 .await?;
453 }
454 if dbversion < 77 {
455 recode_avatar = true;
456 sql.set_db_version(77).await?;
457 }
458 if dbversion < 78 {
459 sql.execute_migration("UPDATE chats SET archived=1 WHERE blocked=2;", 78)
462 .await?;
463 }
464 if dbversion < 79 {
465 sql.execute_migration(
466 r#"
467 ALTER TABLE msgs ADD COLUMN download_state INTEGER DEFAULT 0;
468 "#,
469 79,
470 )
471 .await?;
472 }
473 if dbversion < 80 {
474 sql.execute_migration(
475 r#"CREATE TABLE multi_device_sync (
476id INTEGER PRIMARY KEY AUTOINCREMENT,
477item TEXT DEFAULT '');"#,
478 80,
479 )
480 .await?;
481 }
482 if dbversion < 81 {
483 sql.execute_migration("ALTER TABLE msgs ADD COLUMN hop_info TEXT;", 81)
484 .await?;
485 }
486 if dbversion < 82 {
487 sql.execute_migration(
488 r#"CREATE TABLE imap (
489id INTEGER PRIMARY KEY AUTOINCREMENT,
490rfc724_mid TEXT DEFAULT '', -- Message-ID header
491folder TEXT DEFAULT '', -- IMAP folder
492target TEXT DEFAULT '', -- Destination folder, empty to delete.
493uid INTEGER DEFAULT 0, -- UID
494uidvalidity INTEGER DEFAULT 0,
495UNIQUE (folder, uid, uidvalidity)
496);
497CREATE INDEX imap_folder ON imap(folder);
498CREATE INDEX imap_messageid ON imap(rfc724_mid);
499
500INSERT INTO imap
501(rfc724_mid, folder, target, uid, uidvalidity)
502SELECT
503rfc724_mid,
504server_folder AS folder,
505server_folder AS target,
506server_uid AS uid,
507(SELECT uidvalidity FROM imap_sync WHERE folder=server_folder) AS uidvalidity
508FROM msgs
509WHERE server_uid>0
510ON CONFLICT (folder, uid, uidvalidity)
511DO UPDATE SET rfc724_mid=excluded.rfc724_mid,
512 target=excluded.target;
513"#,
514 82,
515 )
516 .await?;
517 }
518 if dbversion < 83 {
519 sql.execute_migration(
520 "ALTER TABLE imap_sync
521 ADD COLUMN modseq -- Highest modification sequence
522 INTEGER DEFAULT 0",
523 83,
524 )
525 .await?;
526 }
527 if dbversion < 84 {
528 sql.execute_migration(
529 r#"CREATE TABLE msgs_status_updates (
530id INTEGER PRIMARY KEY AUTOINCREMENT,
531msg_id INTEGER,
532update_item TEXT DEFAULT '',
533update_item_read INTEGER DEFAULT 0 -- XXX unused
534);
535CREATE INDEX msgs_status_updates_index1 ON msgs_status_updates (msg_id);"#,
536 84,
537 )
538 .await?;
539 }
540 if dbversion < 85 {
541 sql.execute_migration(
542 r#"CREATE TABLE smtp (
543id INTEGER PRIMARY KEY,
544rfc724_mid TEXT NOT NULL, -- Message-ID
545mime TEXT NOT NULL, -- SMTP payload
546msg_id INTEGER NOT NULL, -- ID of the message in `msgs` table
547recipients TEXT NOT NULL, -- List of recipients separated by space
548retries INTEGER NOT NULL DEFAULT 0 -- Number of failed attempts to send the message
549);
550CREATE INDEX smtp_messageid ON imap(rfc724_mid);
551"#,
552 85,
553 )
554 .await?;
555 }
556 if dbversion < 86 {
557 sql.execute_migration(
558 r#"CREATE TABLE bobstate (
559 id INTEGER PRIMARY KEY AUTOINCREMENT,
560 invite TEXT NOT NULL,
561 next_step INTEGER NOT NULL,
562 chat_id INTEGER NOT NULL
563 );"#,
564 86,
565 )
566 .await?;
567 }
568 if dbversion < 87 {
569 sql.execute_migration(
571 "CREATE INDEX IF NOT EXISTS msgs_index8 ON msgs (ephemeral_timestamp);",
572 87,
573 )
574 .await?;
575 }
576 if dbversion < 88 {
577 sql.execute_migration("DROP TABLE IF EXISTS backup_blobs;", 88)
578 .await?;
579 }
580 if dbversion < 89 {
581 sql.execute_migration(
582 r#"CREATE TABLE imap_markseen (
583 id INTEGER,
584 FOREIGN KEY(id) REFERENCES imap(id) ON DELETE CASCADE
585 );"#,
586 89,
587 )
588 .await?;
589 }
590 if dbversion < 90 {
591 sql.execute_migration(
592 r#"CREATE TABLE smtp_mdns (
593 msg_id INTEGER NOT NULL, -- id of the message in msgs table which requested MDN (DEPRECATED 2024-06-21)
594 from_id INTEGER NOT NULL, -- id of the contact that sent the message, MDN destination
595 rfc724_mid TEXT NOT NULL, -- Message-ID header
596 retries INTEGER NOT NULL DEFAULT 0 -- Number of failed attempts to send MDN
597 );"#,
598 90,
599 )
600 .await?;
601 }
602 if dbversion < 91 {
603 sql.execute_migration(
604 r#"CREATE TABLE smtp_status_updates (
605 msg_id INTEGER NOT NULL UNIQUE, -- msg_id of the webxdc instance with pending updates
606 first_serial INTEGER NOT NULL, -- id in msgs_status_updates
607 last_serial INTEGER NOT NULL, -- id in msgs_status_updates
608 descr TEXT NOT NULL -- text to send along with the updates
609 );"#,
610 91,
611 )
612 .await?;
613 }
614 if dbversion < 92 {
615 sql.execute_migration(
616 r#"CREATE TABLE reactions (
617 msg_id INTEGER NOT NULL, -- id of the message reacted to
618 contact_id INTEGER NOT NULL, -- id of the contact reacting to the message
619 reaction TEXT DEFAULT '' NOT NULL, -- a sequence of emojis separated by spaces
620 PRIMARY KEY(msg_id, contact_id),
621 FOREIGN KEY(msg_id) REFERENCES msgs(id) ON DELETE CASCADE -- delete reactions when message is deleted
622 FOREIGN KEY(contact_id) REFERENCES contacts(id) ON DELETE CASCADE -- delete reactions when contact is deleted
623 )"#,
624 92
625 ).await?;
626 }
627 if dbversion < 93 {
628 sql.execute_migration(
630 "CREATE TABLE sending_domains(domain TEXT PRIMARY KEY, dkim_works INTEGER DEFAULT 0);",
631 93,
632 )
633 .await?;
634 }
635 if dbversion < 94 {
636 sql.execute_migration(
637 "CREATE TABLE new_acpeerstates (
642 id INTEGER PRIMARY KEY,
643 addr TEXT DEFAULT '' COLLATE NOCASE,
644 last_seen INTEGER DEFAULT 0,
645 last_seen_autocrypt INTEGER DEFAULT 0,
646 public_key,
647 prefer_encrypted INTEGER DEFAULT 0,
648 gossip_timestamp INTEGER DEFAULT 0,
649 gossip_key,
650 public_key_fingerprint TEXT DEFAULT '',
651 gossip_key_fingerprint TEXT DEFAULT '',
652 verified_key,
653 verified_key_fingerprint TEXT DEFAULT '',
654 UNIQUE (addr) -- Only one peerstate per address
655 );
656 INSERT OR IGNORE INTO new_acpeerstates SELECT
657 id, addr, last_seen, last_seen_autocrypt, public_key, prefer_encrypted,
658 gossip_timestamp, gossip_key, public_key_fingerprint,
659 gossip_key_fingerprint, verified_key, verified_key_fingerprint
660 FROM acpeerstates;
661 DROP TABLE acpeerstates;
662 ALTER TABLE new_acpeerstates RENAME TO acpeerstates;
663 CREATE INDEX acpeerstates_index1 ON acpeerstates (addr);
664 CREATE INDEX acpeerstates_index3 ON acpeerstates (public_key_fingerprint);
665 CREATE INDEX acpeerstates_index4 ON acpeerstates (gossip_key_fingerprint);
666 CREATE INDEX acpeerstates_index5 ON acpeerstates (verified_key_fingerprint);
667 ",
668 94,
669 )
670 .await?;
671 }
672 if dbversion < 95 {
673 sql.execute_migration(
674 "CREATE TABLE new_chats_contacts (chat_id INTEGER, contact_id INTEGER, UNIQUE(chat_id, contact_id));\
675 INSERT OR IGNORE INTO new_chats_contacts SELECT chat_id, contact_id FROM chats_contacts;\
676 DROP TABLE chats_contacts;\
677 ALTER TABLE new_chats_contacts RENAME TO chats_contacts;\
678 CREATE INDEX chats_contacts_index1 ON chats_contacts (chat_id);\
679 CREATE INDEX chats_contacts_index2 ON chats_contacts (contact_id);",
680 95
681 ).await?;
682 }
683 if dbversion < 96 {
684 sql.execute_migration(
685 "ALTER TABLE acpeerstates ADD COLUMN verifier TEXT DEFAULT '';",
686 96,
687 )
688 .await?;
689 }
690 if dbversion < 97 {
691 sql.execute_migration(
692 "CREATE TABLE dns_cache (
693 hostname TEXT NOT NULL,
694 address TEXT NOT NULL, -- IPv4 or IPv6 address
695 timestamp INTEGER NOT NULL,
696 UNIQUE (hostname, address)
697 )",
698 97,
699 )
700 .await?;
701 }
702 if dbversion < 98 {
703 if exists_before_update && sql.get_raw_config_int("show_emails").await?.is_none() {
704 sql.set_raw_config_int("show_emails", ShowEmails::Off as i32)
705 .await?;
706 }
707 sql.set_db_version(98).await?;
708 }
709 if dbversion < 99 {
710 sql.set_db_version(99).await?;
722 }
723 if dbversion < 100 {
724 sql.execute_migration(
725 "ALTER TABLE msgs ADD COLUMN mime_compressed INTEGER NOT NULL DEFAULT 0",
726 100,
727 )
728 .await?;
729 }
730 if dbversion < 101 {
731 sql.execute_migration(
734 "DROP TABLE smtp;
735 CREATE TABLE smtp (
736 id INTEGER PRIMARY KEY AUTOINCREMENT,
737 rfc724_mid TEXT NOT NULL, -- Message-ID
738 mime TEXT NOT NULL, -- SMTP payload
739 msg_id INTEGER NOT NULL, -- ID of the message in `msgs` table
740 recipients TEXT NOT NULL, -- List of recipients separated by space
741 retries INTEGER NOT NULL DEFAULT 0 -- Number of failed attempts to send the message
742 );
743 ",
744 101,
745 )
746 .await?;
747 }
748
749 if dbversion < 102 {
750 sql.execute_migration(
751 "CREATE TABLE download (
752 msg_id INTEGER NOT NULL -- id of the message stub in msgs table
753 )",
754 102,
755 )
756 .await?;
757 }
758
759 if dbversion < 103 {
761 sql.execute_migration(
762 "ALTER TABLE contacts ADD COLUMN is_bot INTEGER NOT NULL DEFAULT 0",
763 103,
764 )
765 .await?;
766 }
767
768 if dbversion < 104 {
769 sql.execute_migration(
770 "ALTER TABLE acpeerstates
771 ADD COLUMN secondary_verified_key;
772 ALTER TABLE acpeerstates
773 ADD COLUMN secondary_verified_key_fingerprint TEXT DEFAULT '';
774 ALTER TABLE acpeerstates
775 ADD COLUMN secondary_verifier TEXT DEFAULT ''",
776 104,
777 )
778 .await?;
779 }
780
781 if dbversion < 105 {
782 sql.execute_migration(
784 r#"CREATE TABLE new_msgs_status_updates (
785id INTEGER PRIMARY KEY AUTOINCREMENT,
786msg_id INTEGER,
787update_item TEXT DEFAULT '',
788uid TEXT UNIQUE
789);
790INSERT OR IGNORE INTO new_msgs_status_updates SELECT
791 id, msg_id, update_item, NULL
792FROM msgs_status_updates;
793DROP TABLE msgs_status_updates;
794ALTER TABLE new_msgs_status_updates RENAME TO msgs_status_updates;
795CREATE INDEX msgs_status_updates_index1 ON msgs_status_updates (msg_id);
796CREATE INDEX msgs_status_updates_index2 ON msgs_status_updates (uid);
797"#,
798 105,
799 )
800 .await?;
801 }
802
803 if dbversion < 106 {
804 sql.execute_migration(
806 "CREATE TABLE new_config (
807 id INTEGER PRIMARY KEY,
808 keyname TEXT UNIQUE,
809 value TEXT NOT NULL
810 );
811 INSERT OR IGNORE INTO new_config SELECT
812 id, keyname, value
813 FROM config;
814 DROP TABLE config;
815 ALTER TABLE new_config RENAME TO config;
816 CREATE INDEX config_index1 ON config (keyname);",
817 106,
818 )
819 .await?;
820 }
821
822 if dbversion < 107 {
823 sql.execute_migration(
824 "CREATE TABLE new_keypairs (
825 id INTEGER PRIMARY KEY AUTOINCREMENT,
826 private_key UNIQUE NOT NULL,
827 public_key UNIQUE NOT NULL
828 );
829 INSERT OR IGNORE INTO new_keypairs SELECT id, private_key, public_key FROM keypairs;
830
831 INSERT OR IGNORE
832 INTO config (keyname, value)
833 VALUES
834 ('key_id', (SELECT id FROM new_keypairs
835 WHERE private_key=
836 (SELECT private_key FROM keypairs
837 WHERE addr=(SELECT value FROM config WHERE keyname='configured_addr')
838 AND is_default=1)));
839
840 -- We do not drop the old `keypairs` table for now,
841 -- but move it to `old_keypairs`. We can remove it later
842 -- in next migrations. This may be needed for recovery
843 -- in case something is wrong with the migration.
844 ALTER TABLE keypairs RENAME TO old_keypairs;
845 ALTER TABLE new_keypairs RENAME TO keypairs;
846 ",
847 107,
848 )
849 .await?;
850 }
851
852 if dbversion < 108 {
853 let version = 108;
854 let chunk_size = context.get_max_smtp_rcpt_to().await?;
855 sql.transaction(move |trans| {
856 Sql::set_db_version_trans(trans, version)?;
857 let id_max =
858 trans.query_row("SELECT IFNULL((SELECT MAX(id) FROM smtp), 0)", (), |row| {
859 let id_max: i64 = row.get(0)?;
860 Ok(id_max)
861 })?;
862 while let Some((id, rfc724_mid, mime, msg_id, recipients, retries)) = trans
863 .query_row(
864 "SELECT id, rfc724_mid, mime, msg_id, recipients, retries FROM smtp \
865 WHERE id<=? LIMIT 1",
866 (id_max,),
867 |row| {
868 let id: i64 = row.get(0)?;
869 let rfc724_mid: String = row.get(1)?;
870 let mime: String = row.get(2)?;
871 let msg_id: MsgId = row.get(3)?;
872 let recipients: String = row.get(4)?;
873 let retries: i64 = row.get(5)?;
874 Ok((id, rfc724_mid, mime, msg_id, recipients, retries))
875 },
876 )
877 .optional()?
878 {
879 trans.execute("DELETE FROM smtp WHERE id=?", (id,))?;
880 let recipients = recipients.split(' ').collect::<Vec<_>>();
881 for recipients in recipients.chunks(chunk_size) {
882 let recipients = recipients.join(" ");
883 trans.execute(
884 "INSERT INTO smtp (rfc724_mid, mime, msg_id, recipients, retries) \
885 VALUES (?, ?, ?, ?, ?)",
886 (&rfc724_mid, &mime, msg_id, recipients, retries),
887 )?;
888 }
889 }
890 Ok(())
891 })
892 .await
893 .with_context(|| format!("migration failed for version {version}"))?;
894
895 sql.set_db_version_in_cache(version).await?;
896 }
897
898 if dbversion < 109 {
899 sql.execute_migration(
900 r#"ALTER TABLE acpeerstates
901 ADD COLUMN backward_verified_key_id -- What we think the contact has as our verified key
902 INTEGER;
903 UPDATE acpeerstates
904 SET backward_verified_key_id=(SELECT value FROM config WHERE keyname='key_id')
905 WHERE verified_key IS NOT NULL
906 "#,
907 109,
908 )
909 .await?;
910 }
911
912 if dbversion < 110 {
913 sql.execute_migration(
914 "ALTER TABLE keypairs ADD COLUMN addr TEXT DEFAULT '' COLLATE NOCASE;
915 ALTER TABLE keypairs ADD COLUMN is_default INTEGER DEFAULT 0;
916 ALTER TABLE keypairs ADD COLUMN created INTEGER DEFAULT 0;
917 UPDATE keypairs SET addr=(SELECT value FROM config WHERE keyname='configured_addr'), is_default=1;",
918 110,
919 )
920 .await?;
921 }
922
923 if dbversion < 111 {
924 sql.execute_migration(
925 "CREATE TABLE iroh_gossip_peers (msg_id TEXT not NULL, topic TEXT NOT NULL, public_key TEXT NOT NULL)",
926 111,
927 )
928 .await?;
929 }
930
931 if dbversion < 112 {
932 sql.execute_migration(
933 "DROP TABLE iroh_gossip_peers; CREATE TABLE iroh_gossip_peers (msg_id INTEGER not NULL, topic BLOB NOT NULL, public_key BLOB NOT NULL, relay_server TEXT, UNIQUE (public_key, topic)) STRICT",
934 112,
935 )
936 .await?;
937 }
938
939 if dbversion < 113 {
940 sql.execute_migration(
941 "DROP TABLE iroh_gossip_peers; CREATE TABLE iroh_gossip_peers (msg_id INTEGER not NULL, topic BLOB NOT NULL, public_key BLOB NOT NULL, relay_server TEXT, UNIQUE (topic, public_key), PRIMARY KEY(topic, public_key)) STRICT",
942 113,
943 )
944 .await?;
945 }
946
947 if dbversion < 114 {
948 sql.execute_migration("CREATE INDEX reactions_index1 ON reactions (msg_id)", 114)
949 .await?;
950 }
951
952 if dbversion < 115 {
953 sql.execute_migration("ALTER TABLE msgs ADD COLUMN txt_normalized TEXT", 115)
954 .await?;
955 }
956 let mut migration_version: i32 = 115;
957
958 inc_and_check(&mut migration_version, 116)?;
959 if dbversion < migration_version {
960 sql.execute_migration(
963 "ALTER TABLE msgs ADD COLUMN deleted INTEGER NOT NULL DEFAULT 0",
964 migration_version,
965 )
966 .await?;
967 }
968
969 inc_and_check(&mut migration_version, 117)?;
970 if dbversion < migration_version {
971 sql.execute_migration(
972 "CREATE TABLE connection_history (
973 host TEXT NOT NULL, -- server hostname
974 port INTEGER NOT NULL, -- server port
975 alpn TEXT NOT NULL, -- ALPN such as smtp or imap
976 addr TEXT NOT NULL, -- IP address
977 timestamp INTEGER NOT NULL, -- timestamp of the most recent successful connection
978 UNIQUE (host, port, alpn, addr)
979 ) STRICT",
980 migration_version,
981 )
982 .await?;
983 }
984
985 inc_and_check(&mut migration_version, 118)?;
986 if dbversion < migration_version {
987 sql.execute_migration(
988 "CREATE TABLE tokens_new (
989 id INTEGER PRIMARY KEY,
990 namespc INTEGER DEFAULT 0,
991 foreign_key TEXT DEFAULT '',
992 token TEXT DEFAULT '',
993 timestamp INTEGER DEFAULT 0
994 ) STRICT;
995 INSERT INTO tokens_new
996 SELECT t.id, t.namespc, IFNULL(c.grpid, ''), t.token, t.timestamp
997 FROM tokens t LEFT JOIN chats c ON t.foreign_id=c.id;
998 DROP TABLE tokens;
999 ALTER TABLE tokens_new RENAME TO tokens;",
1000 migration_version,
1001 )
1002 .await?;
1003 }
1004
1005 inc_and_check(&mut migration_version, 119)?;
1006 if dbversion < migration_version {
1007 sql.execute_migration(
1008 "CREATE TABLE imap_send (
1009 id INTEGER PRIMARY KEY AUTOINCREMENT,
1010 mime TEXT NOT NULL, -- Message content
1011 msg_id INTEGER NOT NULL, -- ID of the message in the `msgs` table
1012 attempts INTEGER NOT NULL DEFAULT 0 -- Number of failed attempts to send the message
1013 )",
1014 migration_version,
1015 )
1016 .await?;
1017 }
1018
1019 inc_and_check(&mut migration_version, 120)?;
1020 if dbversion < migration_version {
1021 sql.execute_migration(
1033 "INSERT OR IGNORE INTO config (keyname, value)
1034 SELECT 'delete_server_after', '0'
1035 FROM config WHERE keyname='configured'
1036 ",
1037 migration_version,
1038 )
1039 .await?;
1040 }
1041
1042 inc_and_check(&mut migration_version, 121)?;
1043 if dbversion < migration_version {
1044 sql.execute_migration(
1045 "CREATE INDEX chats_index4 ON chats (name)",
1046 migration_version,
1047 )
1048 .await?;
1049 }
1050
1051 inc_and_check(&mut migration_version, 122)?;
1052 if dbversion < migration_version {
1053 sql.execute_migration(
1054 "ALTER TABLE tokens ADD COLUMN foreign_id INTEGER NOT NULL DEFAULT 0",
1055 migration_version,
1056 )
1057 .await?;
1058 }
1059
1060 inc_and_check(&mut migration_version, 123)?;
1061 if dbversion < migration_version {
1062 sql.execute_migration(
1064 "CREATE TABLE new_msgs_status_updates (
1065 id INTEGER PRIMARY KEY AUTOINCREMENT,
1066 msg_id INTEGER,
1067 update_item TEXT DEFAULT '',
1068 uid TEXT UNIQUE,
1069 FOREIGN KEY(msg_id) REFERENCES msgs(id) ON DELETE CASCADE
1070 );
1071 INSERT OR IGNORE INTO new_msgs_status_updates SELECT
1072 id, msg_id, update_item, uid
1073 FROM msgs_status_updates;
1074 DROP TABLE msgs_status_updates;
1075 ALTER TABLE new_msgs_status_updates RENAME TO msgs_status_updates;
1076 CREATE INDEX msgs_status_updates_index1 ON msgs_status_updates (msg_id);
1077 CREATE INDEX msgs_status_updates_index2 ON msgs_status_updates (uid);
1078 ",
1079 migration_version,
1080 )
1081 .await?;
1082 }
1083
1084 inc_and_check(&mut migration_version, 124)?;
1085 if dbversion < migration_version {
1086 sql.execute_migration(
1088 "UPDATE chats
1089 SET protected=1 -- ProtectionStatus::Protected
1090 WHERE type==100 -- Chattype::Single
1091 AND EXISTS (
1092 SELECT 1 FROM chats_contacts cc
1093 WHERE cc.chat_id==chats.id
1094 AND cc.contact_id=1
1095 )
1096 ",
1097 migration_version,
1098 )
1099 .await?;
1100 }
1101
1102 inc_and_check(&mut migration_version, 125)?;
1103 if dbversion < migration_version {
1104 sql.execute_migration(
1105 "CREATE TABLE http_cache (
1106 url TEXT PRIMARY KEY,
1107 expires INTEGER NOT NULL, -- When the cache entry is considered expired, timestamp in seconds.
1108 blobname TEXT NOT NULL,
1109 mimetype TEXT NOT NULL DEFAULT '', -- MIME type extracted from Content-Type header.
1110 encoding TEXT NOT NULL DEFAULT '' -- Encoding from Content-Type header.
1111 ) STRICT",
1112 migration_version,
1113 )
1114 .await?;
1115 }
1116
1117 inc_and_check(&mut migration_version, 126)?;
1118 if dbversion < migration_version {
1119 sql.execute_migration(
1121 "DROP TABLE http_cache;
1122 CREATE TABLE http_cache (
1123 url TEXT PRIMARY KEY,
1124 expires INTEGER NOT NULL, -- When the cache entry is considered expired, timestamp in seconds.
1125 stale INTEGER NOT NULL, -- When the cache entry is considered stale, timestamp in seconds.
1126 blobname TEXT NOT NULL,
1127 mimetype TEXT NOT NULL DEFAULT '', -- MIME type extracted from Content-Type header.
1128 encoding TEXT NOT NULL DEFAULT '' -- Encoding from Content-Type header.
1129 ) STRICT",
1130 migration_version,
1131 )
1132 .await?;
1133 }
1134
1135 inc_and_check(&mut migration_version, 127)?;
1136 if dbversion < migration_version {
1137 sql.execute_migration(
1139 "INSERT OR IGNORE INTO config (keyname, value)
1140 SELECT 'bcc_self', '1'
1141 FROM config WHERE keyname='delete_server_after' AND value='0'
1142 ",
1143 migration_version,
1144 )
1145 .await?;
1146 }
1147
1148 inc_and_check(&mut migration_version, 128)?;
1149 if dbversion < migration_version {
1150 sql.execute_migration(
1156 "ALTER TABLE chats_contacts
1157 ADD COLUMN add_timestamp NOT NULL DEFAULT 0;
1158 ALTER TABLE chats_contacts
1159 ADD COLUMN remove_timestamp NOT NULL DEFAULT 0;
1160 ",
1161 migration_version,
1162 )
1163 .await?;
1164 }
1165
1166 inc_and_check(&mut migration_version, 129)?;
1167 if dbversion < migration_version {
1168 sql.execute_migration(
1176 "INSERT OR IGNORE INTO config (keyname, value)
1177 SELECT 'bcc_self', '1'
1178 FROM config WHERE keyname='delete_server_after' AND value!='1'
1179 ",
1180 migration_version,
1181 )
1182 .await?;
1183 }
1184
1185 inc_and_check(&mut migration_version, 130)?;
1186 if dbversion < migration_version {
1187 sql.execute_migration(
1188 "
1189CREATE TABLE gossip_timestamp (
1190 chat_id INTEGER NOT NULL,
1191 fingerprint TEXT NOT NULL, -- Upper-case fingerprint of the key.
1192 timestamp INTEGER NOT NULL,
1193 UNIQUE (chat_id, fingerprint)
1194) STRICT;
1195CREATE INDEX gossip_timestamp_index ON gossip_timestamp (chat_id, fingerprint);
1196",
1197 migration_version,
1198 )
1199 .await?;
1200 }
1201
1202 inc_and_check(&mut migration_version, 131)?;
1203 if dbversion < migration_version {
1204 let entered_param = EnteredLoginParam::load(context).await?;
1205 let configured_param = ConfiguredLoginParam::load_legacy(context).await?;
1206
1207 sql.execute_migration_transaction(
1208 |transaction| {
1209 transaction.execute(
1210 "CREATE TABLE transports (
1211 id INTEGER PRIMARY KEY AUTOINCREMENT,
1212 addr TEXT NOT NULL,
1213 entered_param TEXT NOT NULL,
1214 configured_param TEXT NOT NULL,
1215 UNIQUE(addr)
1216 )",
1217 (),
1218 )?;
1219 if let Some(configured_param) = configured_param {
1220 transaction.execute(
1221 "INSERT INTO transports (addr, entered_param, configured_param)
1222 VALUES (?, ?, ?)",
1223 (
1224 configured_param.addr.clone(),
1225 serde_json::to_string(&entered_param)?,
1226 configured_param.into_json()?,
1227 ),
1228 )?;
1229 }
1230
1231 Ok(())
1232 },
1233 migration_version,
1234 )
1235 .await?;
1236 }
1237
1238 inc_and_check(&mut migration_version, 132)?;
1239 if dbversion < migration_version {
1240 let start = Instant::now();
1241 sql.execute_migration_transaction(|t| migrate_key_contacts(context, t), migration_version)
1242 .await?;
1243 info!(
1244 context,
1245 "key-contacts migration took {:?} in total.",
1246 start.elapsed()
1247 );
1248 }
1249
1250 let new_version = sql
1251 .get_raw_config_int(VERSION_CFG)
1252 .await?
1253 .unwrap_or_default();
1254 if new_version != dbversion || !exists_before_update {
1255 let created_db = if exists_before_update {
1256 ""
1257 } else {
1258 "Created new database. "
1259 };
1260 info!(context, "{}Migration done from v{}.", created_db, dbversion);
1261 }
1262 info!(context, "Database version: v{new_version}.");
1263
1264 Ok((update_icons, disable_server_delete, recode_avatar))
1265}
1266
1267fn migrate_key_contacts(
1268 context: &Context,
1269 transaction: &mut rusqlite::Transaction<'_>,
1270) -> std::result::Result<(), anyhow::Error> {
1271 info!(context, "Starting key-contact transition.");
1272
1273 transaction.execute_batch(
1276 "ALTER TABLE contacts ADD COLUMN fingerprint TEXT NOT NULL DEFAULT '';
1277
1278 -- Verifier is an ID of the verifier contact.
1279 -- 0 if the contact is not verified.
1280 ALTER TABLE contacts ADD COLUMN verifier INTEGER NOT NULL DEFAULT 0;
1281
1282 CREATE INDEX contacts_fingerprint_index ON contacts (fingerprint);
1283
1284 CREATE TABLE public_keys (
1285 id INTEGER PRIMARY KEY AUTOINCREMENT,
1286 fingerprint TEXT NOT NULL UNIQUE, -- Upper-case fingerprint of the key.
1287 public_key BLOB NOT NULL -- Binary key, not ASCII-armored
1288 ) STRICT;
1289 CREATE INDEX public_key_index ON public_keys (fingerprint);
1290
1291 INSERT OR IGNORE INTO public_keys (fingerprint, public_key)
1292 SELECT public_key_fingerprint, public_key FROM acpeerstates
1293 WHERE public_key_fingerprint IS NOT NULL AND public_key IS NOT NULL;
1294
1295 INSERT OR IGNORE INTO public_keys (fingerprint, public_key)
1296 SELECT gossip_key_fingerprint, gossip_key FROM acpeerstates
1297 WHERE gossip_key_fingerprint IS NOT NULL AND gossip_key IS NOT NULL;
1298
1299 INSERT OR IGNORE INTO public_keys (fingerprint, public_key)
1300 SELECT verified_key_fingerprint, verified_key FROM acpeerstates
1301 WHERE verified_key_fingerprint IS NOT NULL AND verified_key IS NOT NULL;
1302
1303 INSERT OR IGNORE INTO public_keys (fingerprint, public_key)
1304 SELECT secondary_verified_key_fingerprint, secondary_verified_key FROM acpeerstates
1305 WHERE secondary_verified_key_fingerprint IS NOT NULL AND secondary_verified_key IS NOT NULL;",
1306 )
1307 .context("Creating key-contact tables")?;
1308
1309 let Some(self_addr): Option<String> = transaction
1310 .query_row(
1311 "SELECT value FROM config WHERE keyname='configured_addr'",
1312 (),
1313 |row| row.get(0),
1314 )
1315 .optional()
1316 .context("Step 0")?
1317 else {
1318 info!(
1319 context,
1320 "Not yet configured, no need to migrate key-contacts"
1321 );
1322 return Ok(());
1323 };
1324
1325 let mut autocrypt_key_contacts: BTreeMap<u32, u32> = BTreeMap::new();
1332 let mut autocrypt_key_contacts_with_reset_peerstate: BTreeMap<u32, u32> = BTreeMap::new();
1333 let mut verified_key_contacts: BTreeMap<u32, u32> = BTreeMap::new();
1334 {
1335 let mut verifications: BTreeMap<u32, u32> = BTreeMap::new();
1339
1340 let mut load_contacts_stmt = transaction
1341 .prepare(
1342 "SELECT c.id, c.name, c.addr, c.origin, c.blocked, c.last_seen,
1343 c.authname, c.param, c.status, c.is_bot, c.selfavatar_sent,
1344 IFNULL(p.public_key, p.gossip_key),
1345 p.verified_key, IFNULL(p.verifier, ''),
1346 p.secondary_verified_key, p.secondary_verifier, p.prefer_encrypted
1347 FROM contacts c
1348 INNER JOIN acpeerstates p ON c.addr=p.addr
1349 WHERE c.id > 9
1350 ORDER BY p.last_seen DESC",
1351 )
1352 .context("Step 2")?;
1353
1354 let all_address_contacts: rusqlite::Result<Vec<_>> = load_contacts_stmt
1355 .query_map((), |row| {
1356 let id: i64 = row.get(0)?;
1357 let name: String = row.get(1)?;
1358 let addr: String = row.get(2)?;
1359 let origin: i64 = row.get(3)?;
1360 let blocked: Option<bool> = row.get(4)?;
1361 let last_seen: i64 = row.get(5)?;
1362 let authname: String = row.get(6)?;
1363 let param: String = row.get(7)?;
1364 let status: Option<String> = row.get(8)?;
1365 let is_bot: bool = row.get(9)?;
1366 let selfavatar_sent: i64 = row.get(10)?;
1367 let autocrypt_key = row
1368 .get(11)
1369 .ok()
1370 .and_then(|blob: Vec<u8>| SignedPublicKey::from_slice(&blob).ok());
1371 let verified_key = row
1372 .get(12)
1373 .ok()
1374 .and_then(|blob: Vec<u8>| SignedPublicKey::from_slice(&blob).ok());
1375 let verifier: String = row.get(13)?;
1376 let secondary_verified_key = row
1377 .get(12)
1378 .ok()
1379 .and_then(|blob: Vec<u8>| SignedPublicKey::from_slice(&blob).ok());
1380 let secondary_verifier: String = row.get(15)?;
1381 let prefer_encrypt: u8 = row.get(16)?;
1382 Ok((
1383 id,
1384 name,
1385 addr,
1386 origin,
1387 blocked,
1388 last_seen,
1389 authname,
1390 param,
1391 status,
1392 is_bot,
1393 selfavatar_sent,
1394 autocrypt_key,
1395 verified_key,
1396 verifier,
1397 secondary_verified_key,
1398 secondary_verifier,
1399 prefer_encrypt,
1400 ))
1401 })
1402 .context("Step 3")?
1403 .collect();
1404
1405 let mut insert_contact_stmt = transaction
1406 .prepare(
1407 "INSERT INTO contacts (name, addr, origin, blocked, last_seen,
1408 authname, param, status, is_bot, selfavatar_sent, fingerprint)
1409 VALUES(?,?,?,?,?,?,?,?,?,?,?)",
1410 )
1411 .context("Step 4")?;
1412 let mut fingerprint_to_id_stmt = transaction
1413 .prepare("SELECT id FROM contacts WHERE fingerprint=? AND id>9")
1414 .context("Step 5")?;
1415 let mut original_contact_id_from_addr_stmt = transaction
1416 .prepare("SELECT id FROM contacts WHERE addr=? AND fingerprint='' AND id>9")
1417 .context("Step 6")?;
1418
1419 for row in all_address_contacts? {
1420 let (
1421 original_id,
1422 name,
1423 addr,
1424 origin,
1425 blocked,
1426 last_seen,
1427 authname,
1428 param,
1429 status,
1430 is_bot,
1431 selfavatar_sent,
1432 autocrypt_key,
1433 verified_key,
1434 verifier,
1435 secondary_verified_key,
1436 secondary_verifier,
1437 prefer_encrypt,
1438 ) = row;
1439 let mut insert_contact = |key: SignedPublicKey| -> Result<u32> {
1440 let fingerprint = key.dc_fingerprint().hex();
1441 let existing_contact_id: Option<u32> = fingerprint_to_id_stmt
1442 .query_row((&fingerprint,), |row| row.get(0))
1443 .optional()
1444 .context("Step 7")?;
1445 if let Some(existing_contact_id) = existing_contact_id {
1446 return Ok(existing_contact_id);
1447 }
1448 insert_contact_stmt
1449 .execute((
1450 &name,
1451 &addr,
1452 origin,
1453 blocked,
1454 last_seen,
1455 &authname,
1456 ¶m,
1457 &status,
1458 is_bot,
1459 selfavatar_sent,
1460 fingerprint.clone(),
1461 ))
1462 .context("Step 8")?;
1463 let id = transaction
1464 .last_insert_rowid()
1465 .try_into()
1466 .context("Step 9")?;
1467 info!(
1468 context,
1469 "Inserted new contact id={id} name='{name}' addr='{addr}' fingerprint={fingerprint}"
1470 );
1471 Ok(id)
1472 };
1473 let mut original_contact_id_from_addr = |addr: &str, default: u32| -> Result<u32> {
1474 if addr_cmp(addr, &self_addr) {
1475 Ok(1) } else if addr.is_empty() {
1477 Ok(default)
1478 } else {
1479 original_contact_id_from_addr_stmt
1480 .query_row((addr,), |row| row.get(0))
1481 .with_context(|| format!("Original contact '{addr}' not found"))
1482 }
1483 };
1484
1485 let Some(autocrypt_key) = autocrypt_key else {
1486 continue;
1487 };
1488 let new_id = insert_contact(autocrypt_key).context("Step 10")?;
1489
1490 if prefer_encrypt != 20 {
1493 autocrypt_key_contacts.insert(original_id.try_into().context("Step 11")?, new_id);
1494 } else {
1495 autocrypt_key_contacts_with_reset_peerstate
1496 .insert(original_id.try_into().context("Step 12")?, new_id);
1497 }
1498
1499 let Some(verified_key) = verified_key else {
1500 continue;
1501 };
1502 let new_id = insert_contact(verified_key).context("Step 13")?;
1503 verified_key_contacts.insert(original_id.try_into().context("Step 14")?, new_id);
1504 let verifier_id =
1508 original_contact_id_from_addr(&verifier, new_id).context("Step 15")?;
1509 verifications.insert(new_id, verifier_id);
1510
1511 let Some(secondary_verified_key) = secondary_verified_key else {
1512 continue;
1513 };
1514 let new_id = insert_contact(secondary_verified_key).context("Step 16")?;
1515 let verifier_id: u32 =
1516 original_contact_id_from_addr(&secondary_verifier, new_id).context("Step 17")?;
1517 verifications.entry(new_id).or_insert(verifier_id);
1519 }
1520 info!(
1521 context,
1522 "Created key-contacts identified by autocrypt key: {autocrypt_key_contacts:?}"
1523 );
1524 info!(context, "Created key-contacts with 'reset' peerstate identified by autocrypt key: {autocrypt_key_contacts_with_reset_peerstate:?}");
1525 info!(
1526 context,
1527 "Created key-contacts identified by verified key: {verified_key_contacts:?}"
1528 );
1529
1530 for (&new_contact, &verifier_original_contact) in &verifications {
1531 let verifier = if verifier_original_contact == 1 {
1532 1 } else if verifier_original_contact == new_contact {
1534 new_contact } else {
1536 match verified_key_contacts.get(&verifier_original_contact) {
1539 Some(v) => *v,
1540 None => {
1541 warn!(context, "Couldn't find key-contact for {verifier_original_contact} who verified {new_contact}");
1542 continue;
1543 }
1544 }
1545 };
1546 transaction
1547 .execute(
1548 "UPDATE contacts SET verifier=? WHERE id=?",
1549 (verifier, new_contact),
1550 )
1551 .context("Step 18")?;
1552 }
1553 info!(context, "Migrated verifications: {verifications:?}");
1554 }
1555
1556 let mut orphaned_contacts: BTreeSet<u32> = transaction
1561 .prepare("SELECT id FROM contacts WHERE id>9")
1562 .context("Step 19")?
1563 .query_map((), |row| row.get::<usize, u32>(0))
1564 .context("Step 20")?
1565 .collect::<Result<BTreeSet<u32>, rusqlite::Error>>()
1566 .context("Step 21")?;
1567
1568 {
1569 let mut stmt = transaction
1570 .prepare(
1571 "SELECT c.id, c.type, c.grpid, c.protected
1572 FROM chats c
1573 WHERE id>9",
1574 )
1575 .context("Step 22")?;
1576 let all_chats = stmt
1577 .query_map((), |row| {
1578 let id: u32 = row.get(0)?;
1579 let typ: u32 = row.get(1)?;
1580 let grpid: String = row.get(2)?;
1581 let protected: u32 = row.get(3)?;
1582 Ok((id, typ, grpid, protected))
1583 })
1584 .context("Step 23")?;
1585 let mut load_chat_contacts_stmt = transaction
1586 .prepare("SELECT contact_id FROM chats_contacts WHERE chat_id=? AND contact_id>9")?;
1587 let is_chatmail: Option<String> = transaction
1588 .query_row(
1589 "SELECT value FROM config WHERE keyname='is_chatmail'",
1590 (),
1591 |row| row.get(0),
1592 )
1593 .optional()
1594 .context("Step 23.1")?;
1595 let is_chatmail = is_chatmail
1596 .and_then(|s| s.parse::<i32>().ok())
1597 .unwrap_or_default()
1598 != 0;
1599 let map_to_key_contact = |old_member: &u32| {
1600 (
1601 *old_member,
1602 autocrypt_key_contacts
1603 .get(old_member)
1604 .or_else(|| {
1605 if is_chatmail {
1610 autocrypt_key_contacts_with_reset_peerstate.get(old_member)
1611 } else {
1612 None
1613 }
1614 })
1615 .copied(),
1616 )
1617 };
1618
1619 let mut update_member_stmt = transaction
1620 .prepare("UPDATE chats_contacts SET contact_id=? WHERE contact_id=? AND chat_id=?")?;
1621 let mut addr_cmp_stmt = transaction
1622 .prepare("SELECT c.addr=d.addr FROM contacts c, contacts d WHERE c.id=? AND d.id=?")?;
1623 for chat in all_chats {
1624 let (chat_id, typ, grpid, protected) = chat.context("Step 24")?;
1625 let old_members: Vec<u32> = load_chat_contacts_stmt
1627 .query_map((chat_id,), |row| row.get::<_, u32>(0))
1628 .context("Step 25")?
1629 .collect::<Result<Vec<u32>, rusqlite::Error>>()
1630 .context("Step 26")?;
1631
1632 let mut keep_address_contacts = |reason: &str| {
1633 info!(context, "Chat {chat_id} will be an unencrypted chat with contacts identified by email address: {reason}");
1634 for m in &old_members {
1635 orphaned_contacts.remove(m);
1636 }
1637 };
1638 let old_and_new_members: Vec<(u32, Option<u32>)> = match typ {
1639 100 => {
1647 let Some(old_member) = old_members.first() else {
1648 info!(context, "1:1 chat {chat_id} doesn't contain contact, probably it's self or device chat");
1649 continue;
1650 };
1651
1652 let (_, Some(new_contact)) = map_to_key_contact(old_member) else {
1653 keep_address_contacts("No peerstate, or peerstate in 'reset' state");
1654 continue;
1655 };
1656 if !addr_cmp_stmt
1657 .query_row((old_member, new_contact), |row| row.get::<_, bool>(0))?
1658 {
1659 transaction
1663 .execute("UPDATE chats SET protected=0 WHERE id=?", (chat_id,))?;
1664
1665 keep_address_contacts("key contact has different email");
1666 continue;
1667 }
1668 vec![(*old_member, Some(new_contact))]
1669 }
1670
1671 120 => {
1673 if grpid.is_empty() {
1674 keep_address_contacts("Empty chat-Group-ID");
1677 continue;
1678 } else if protected == 1 {
1679 old_members
1680 .iter()
1681 .map(|old_member| {
1682 (*old_member, verified_key_contacts.get(old_member).copied())
1683 })
1684 .collect()
1685 } else {
1686 old_members
1687 .iter()
1688 .map(map_to_key_contact)
1689 .collect::<Vec<(u32, Option<u32>)>>()
1690 }
1691 }
1692
1693 140 => {
1695 keep_address_contacts("Mailinglist");
1696 continue;
1697 }
1698
1699 160 => old_members
1701 .iter()
1702 .map(|original| {
1703 (
1704 *original,
1705 autocrypt_key_contacts
1706 .get(original)
1707 .or_else(|| {
1711 autocrypt_key_contacts_with_reset_peerstate.get(original)
1712 })
1713 .copied(),
1714 )
1715 })
1716 .collect::<Vec<(u32, Option<u32>)>>(),
1717 _ => {
1718 warn!(context, "Invalid chat type {typ}");
1719 continue;
1720 }
1721 };
1722
1723 if typ == 120 && old_and_new_members.iter().any(|(_old, new)| new.is_none()) {
1726 transaction
1727 .execute("UPDATE chats SET grpid='' WHERE id=?", (chat_id,))
1728 .context("Step 26.1")?;
1729 keep_address_contacts("Group contains contact without peerstate");
1730 continue;
1731 }
1732
1733 let human_readable_transitions = old_and_new_members
1734 .iter()
1735 .map(|(old, new)| format!("{old}->{}", new.unwrap_or_default()))
1736 .collect::<Vec<String>>()
1737 .join(" ");
1738 info!(
1739 context,
1740 "Migrating chat {chat_id} to key-contacts: {human_readable_transitions}"
1741 );
1742
1743 for (old_member, new_member) in old_and_new_members {
1744 if let Some(new_member) = new_member {
1745 orphaned_contacts.remove(&new_member);
1746 let res = update_member_stmt.execute((new_member, old_member, chat_id));
1747 if res.is_err() {
1748 let member_to_delete: u32 = transaction
1755 .query_row(
1756 "SELECT contact_id
1757 FROM chats_contacts
1758 WHERE chat_id=? AND contact_id IN (?,?)
1759 ORDER BY add_timestamp>=remove_timestamp, add_timestamp LIMIT 1",
1760 (chat_id, new_member, old_member),
1761 |row| row.get(0),
1762 )
1763 .context("Step 27")?;
1764 info!(
1765 context,
1766 "Chat partner is in the chat {chat_id} multiple times. \
1767 Deleting {member_to_delete}, then trying to update \
1768 {old_member}->{new_member} again"
1769 );
1770 transaction
1771 .execute(
1772 "DELETE FROM chats_contacts WHERE chat_id=? AND contact_id=?",
1773 (chat_id, member_to_delete),
1774 )
1775 .context("Step 28")?;
1776 update_member_stmt.execute((new_member, old_member, chat_id))?;
1779 }
1780 } else {
1781 info!(context, "Old member {old_member} in chat {chat_id} can't be upgraded to key-contact, removing them");
1782 transaction
1783 .execute(
1784 "DELETE FROM chats_contacts WHERE contact_id=? AND chat_id=?",
1785 (old_member, chat_id),
1786 )
1787 .context("Step 29")?;
1788 }
1789 }
1790 }
1791 }
1792
1793 {
1795 info!(
1796 context,
1797 "Marking contacts which remained in no chat at all as hidden: {orphaned_contacts:?}"
1798 );
1799 let mut mark_as_hidden_stmt = transaction
1800 .prepare("UPDATE contacts SET origin=? WHERE id=?")
1801 .context("Step 30")?;
1802 for contact in orphaned_contacts {
1803 mark_as_hidden_stmt
1804 .execute((0x8, contact))
1805 .context("Step 31")?;
1806 }
1807 }
1808
1809 {
1812 let start = Instant::now();
1813
1814 let mut encrypted_msgs_stmt = transaction
1815 .prepare(
1816 "SELECT id, from_id, to_id
1817 FROM msgs
1818 WHERE id>9
1819 AND (param LIKE '%\nc=1%' OR param LIKE 'c=1%')
1820 AND chat_id>9
1821 ORDER BY id DESC LIMIT 10000",
1822 )
1823 .context("Step 32")?;
1824 let mut rewrite_msg_stmt = transaction
1825 .prepare("UPDATE msgs SET from_id=?, to_id=? WHERE id=?")
1826 .context("Step 32.1")?;
1827
1828 struct LoadedMsg {
1829 id: u32,
1830 from_id: u32,
1831 to_id: u32,
1832 }
1833
1834 let encrypted_msgs = encrypted_msgs_stmt
1835 .query_map((), |row| {
1836 let id: u32 = row.get(0)?;
1837 let from_id: u32 = row.get(1)?;
1838 let to_id: u32 = row.get(2)?;
1839 Ok(LoadedMsg { id, from_id, to_id })
1840 })
1841 .context("Step 33")?;
1842
1843 for msg in encrypted_msgs {
1844 let msg = msg.context("Step 34")?;
1845
1846 let new_from_id = *autocrypt_key_contacts
1847 .get(&msg.from_id)
1848 .or_else(|| autocrypt_key_contacts_with_reset_peerstate.get(&msg.from_id))
1849 .unwrap_or(&msg.from_id);
1850
1851 let new_to_id = *autocrypt_key_contacts
1852 .get(&msg.to_id)
1853 .or_else(|| autocrypt_key_contacts_with_reset_peerstate.get(&msg.to_id))
1854 .unwrap_or(&msg.to_id);
1855
1856 rewrite_msg_stmt
1857 .execute((new_from_id, new_to_id, msg.id))
1858 .context("Step 35")?;
1859 }
1860 info!(
1861 context,
1862 "Rewriting msgs to key-contacts took {:?}.",
1863 start.elapsed()
1864 );
1865 }
1866
1867 Ok(())
1868}
1869
1870impl Sql {
1871 async fn set_db_version(&self, version: i32) -> Result<()> {
1872 self.set_raw_config_int(VERSION_CFG, version).await?;
1873 Ok(())
1874 }
1875
1876 fn set_db_version_trans(transaction: &mut rusqlite::Transaction, version: i32) -> Result<()> {
1878 transaction.execute(
1879 "UPDATE config SET value=? WHERE keyname=?;",
1880 (format!("{version}"), VERSION_CFG),
1881 )?;
1882 Ok(())
1883 }
1884
1885 async fn set_db_version_in_cache(&self, version: i32) -> Result<()> {
1886 let mut lock = self.config_cache.write().await;
1887 lock.insert(VERSION_CFG.to_string(), Some(format!("{version}")));
1888 Ok(())
1889 }
1890
1891 async fn execute_migration(&self, query: &str, version: i32) -> Result<()> {
1892 self.execute_migration_transaction(
1893 |transaction| {
1894 transaction.execute_batch(query)?;
1895 Ok(())
1896 },
1897 version,
1898 )
1899 .await
1900 }
1901
1902 async fn execute_migration_transaction(
1903 &self,
1904 migration: impl Send + FnOnce(&mut rusqlite::Transaction) -> Result<()>,
1905 version: i32,
1906 ) -> Result<()> {
1907 #[cfg(test)]
1908 if STOP_MIGRATIONS_AT.try_with(|stop_migrations_at| version > *stop_migrations_at)
1909 == Ok(true)
1910 {
1911 println!("Not running migration {version}, because STOP_MIGRATIONS_AT is set");
1912 return Ok(());
1913 }
1914
1915 self.transaction(move |transaction| {
1916 let curr_version: String = transaction.query_row(
1917 "SELECT IFNULL(value, ?) FROM config WHERE keyname=?;",
1918 ("0", VERSION_CFG),
1919 |row| row.get(0),
1920 )?;
1921 let curr_version: i32 = curr_version.parse()?;
1922 ensure!(curr_version < version, "Db version must be increased");
1923 Self::set_db_version_trans(transaction, version)?;
1924 migration(transaction)?;
1925
1926 Ok(())
1927 })
1928 .await
1929 .with_context(|| format!("execute_migration failed for version {version}"))?;
1930
1931 self.config_cache.write().await.clear();
1932
1933 Ok(())
1934 }
1935}
1936
1937#[cfg(test)]
1938mod migrations_tests;