deltachat/config.rs
1//! # Key-value configuration management.
2
3use std::env;
4use std::path::Path;
5use std::str::FromStr;
6
7use anyhow::{Context as _, Result, bail, ensure};
8use base64::Engine as _;
9use deltachat_contact_tools::{addr_cmp, sanitize_single_line};
10use serde::{Deserialize, Serialize};
11use strum::{EnumProperty, IntoEnumIterator};
12use strum_macros::{AsRefStr, Display, EnumIter, EnumString};
13use tokio::fs;
14
15use crate::blob::BlobObject;
16use crate::context::Context;
17use crate::events::EventType;
18use crate::log::LogExt;
19use crate::mimefactory::RECOMMENDED_FILE_SIZE;
20use crate::provider::Provider;
21use crate::sync::{self, Sync::*, SyncData};
22use crate::tools::{get_abs_path, time};
23use crate::transport::{ConfiguredLoginParam, add_pseudo_transport, send_sync_transports};
24use crate::{constants, stats};
25
26/// The available configuration keys.
27#[derive(
28 Debug,
29 Clone,
30 Copy,
31 PartialEq,
32 Eq,
33 Display,
34 EnumString,
35 AsRefStr,
36 EnumIter,
37 EnumProperty,
38 PartialOrd,
39 Ord,
40 Serialize,
41 Deserialize,
42)]
43#[strum(serialize_all = "snake_case")]
44pub enum Config {
45 /// Email address, used in the `From:` field.
46 Addr,
47
48 /// IMAP server hostname.
49 MailServer,
50
51 /// IMAP server username.
52 MailUser,
53
54 /// IMAP server password.
55 MailPw,
56
57 /// IMAP server port.
58 MailPort,
59
60 /// IMAP server security (e.g. TLS, STARTTLS).
61 MailSecurity,
62
63 /// How to check TLS certificates.
64 ///
65 /// "IMAP" in the name is for compatibility,
66 /// this actually applies to both IMAP and SMTP connections.
67 ImapCertificateChecks,
68
69 /// SMTP server hostname.
70 SendServer,
71
72 /// SMTP server username.
73 SendUser,
74
75 /// SMTP server password.
76 SendPw,
77
78 /// SMTP server port.
79 SendPort,
80
81 /// SMTP server security (e.g. TLS, STARTTLS).
82 SendSecurity,
83
84 /// Deprecated option for backwards compatibility.
85 ///
86 /// Certificate checks for SMTP are actually controlled by `imap_certificate_checks` config.
87 SmtpCertificateChecks,
88
89 /// Whether to use OAuth 2.
90 ///
91 /// Historically contained other bitflags, which are now deprecated.
92 /// Should not be extended in the future, create new config keys instead.
93 ServerFlags,
94
95 /// True if proxy is enabled.
96 ///
97 /// Can be used to disable proxy without erasing known URLs.
98 ProxyEnabled,
99
100 /// Proxy URL.
101 ///
102 /// Supported URLs schemes are `http://` (HTTP), `https://` (HTTPS),
103 /// `socks5://` (SOCKS5) and `ss://` (Shadowsocks).
104 ///
105 /// May contain multiple URLs separated by newline, in which case the first one is used.
106 ProxyUrl,
107
108 /// True if SOCKS5 is enabled.
109 ///
110 /// Can be used to disable SOCKS5 without erasing SOCKS5 configuration.
111 ///
112 /// Deprecated in favor of `ProxyEnabled`.
113 Socks5Enabled,
114
115 /// SOCKS5 proxy server hostname or address.
116 ///
117 /// Deprecated in favor of `ProxyUrl`.
118 Socks5Host,
119
120 /// SOCKS5 proxy server port.
121 ///
122 /// Deprecated in favor of `ProxyUrl`.
123 Socks5Port,
124
125 /// SOCKS5 proxy server username.
126 ///
127 /// Deprecated in favor of `ProxyUrl`.
128 Socks5User,
129
130 /// SOCKS5 proxy server password.
131 ///
132 /// Deprecated in favor of `ProxyUrl`.
133 Socks5Password,
134
135 /// Own name to use in the `From:` field when sending messages.
136 Displayname,
137
138 /// Own status to display, sent in message footer.
139 Selfstatus,
140
141 /// Own avatar filename.
142 Selfavatar,
143
144 /// Send BCC copy to self.
145 ///
146 /// Should be enabled for multi-device setups.
147 ///
148 /// This is automatically enabled when importing/exporting a backup,
149 /// setting up a second device, or receiving a sync message.
150 #[strum(props(default = "0"))]
151 BccSelf,
152
153 /// True if Message Delivery Notifications (read receipts) should
154 /// be sent and requested.
155 #[strum(props(default = "1"))]
156 MdnsEnabled,
157
158 /// Whether to show classic emails or only chat messages.
159 #[strum(props(default = "2"))] // also change ShowEmails.default() on changes
160 ShowEmails,
161
162 /// Quality of the media files to send.
163 #[strum(props(default = "0"))] // also change MediaQuality.default() on changes
164 MediaQuality,
165
166 /// Timer in seconds after which the message is deleted from the
167 /// server.
168 ///
169 /// 0 means messages are never deleted by Delta Chat.
170 ///
171 /// Value 1 is treated as "delete at once": messages are deleted
172 /// immediately, without moving to DeltaChat folder.
173 ///
174 /// Default is 1 for chatmail accounts without `BccSelf`, 0 otherwise.
175 DeleteServerAfter,
176
177 /// Timer in seconds after which the message is deleted from the
178 /// device.
179 ///
180 /// Equals to 0 by default, which means the message is never
181 /// deleted.
182 #[strum(props(default = "0"))]
183 DeleteDeviceAfter,
184
185 /// The primary email address.
186 ConfiguredAddr,
187
188 /// List of configured IMAP servers as a JSON array.
189 ConfiguredImapServers,
190
191 /// Configured IMAP server hostname.
192 ///
193 /// This is replaced by `configured_imap_servers` for new configurations.
194 ConfiguredMailServer,
195
196 /// Configured IMAP server port.
197 ///
198 /// This is replaced by `configured_imap_servers` for new configurations.
199 ConfiguredMailPort,
200
201 /// Configured IMAP server security (e.g. TLS, STARTTLS).
202 ///
203 /// This is replaced by `configured_imap_servers` for new configurations.
204 ConfiguredMailSecurity,
205
206 /// Configured IMAP server username.
207 ///
208 /// This is set if user has configured username manually.
209 ConfiguredMailUser,
210
211 /// Configured IMAP server password.
212 ConfiguredMailPw,
213
214 /// Configured TLS certificate checks.
215 /// This option is saved on successful configuration
216 /// and should not be modified manually.
217 ///
218 /// This actually applies to both IMAP and SMTP connections,
219 /// but has "IMAP" in the name for backwards compatibility.
220 ConfiguredImapCertificateChecks,
221
222 /// List of configured SMTP servers as a JSON array.
223 ConfiguredSmtpServers,
224
225 /// Configured SMTP server hostname.
226 ///
227 /// This is replaced by `configured_smtp_servers` for new configurations.
228 ConfiguredSendServer,
229
230 /// Configured SMTP server port.
231 ///
232 /// This is replaced by `configured_smtp_servers` for new configurations.
233 ConfiguredSendPort,
234
235 /// Configured SMTP server security (e.g. TLS, STARTTLS).
236 ///
237 /// This is replaced by `configured_smtp_servers` for new configurations.
238 ConfiguredSendSecurity,
239
240 /// Configured SMTP server username.
241 ///
242 /// This is set if user has configured username manually.
243 ConfiguredSendUser,
244
245 /// Configured SMTP server password.
246 ConfiguredSendPw,
247
248 /// Deprecated, stored for backwards compatibility.
249 ///
250 /// ConfiguredImapCertificateChecks is actually used.
251 ConfiguredSmtpCertificateChecks,
252
253 /// Whether OAuth 2 is used with configured provider.
254 ConfiguredServerFlags,
255
256 /// Configured folder for incoming messages.
257 ConfiguredInboxFolder,
258
259 /// Unix timestamp of the last successful configuration.
260 ConfiguredTimestamp,
261
262 /// ID of the configured provider from the provider database.
263 ConfiguredProvider,
264
265 /// True if account is configured.
266 Configured,
267
268 /// True if account is a chatmail account.
269 IsChatmail,
270
271 /// True if `IsChatmail` mustn't be autoconfigured. For tests.
272 FixIsChatmail,
273
274 /// True if account is muted.
275 IsMuted,
276
277 /// Optional tag as "Work", "Family".
278 /// Meant to help profile owner to differ between profiles with similar names.
279 PrivateTag,
280
281 /// Read-only core version string.
282 #[strum(serialize = "sys.version")]
283 SysVersion,
284
285 /// Maximal recommended attachment size in bytes.
286 #[strum(serialize = "sys.msgsize_max_recommended")]
287 SysMsgsizeMaxRecommended,
288
289 /// Space separated list of all config keys available.
290 #[strum(serialize = "sys.config_keys")]
291 SysConfigKeys,
292
293 /// True if it is a bot account.
294 Bot,
295
296 /// True when to skip initial start messages in groups.
297 #[strum(props(default = "0"))]
298 SkipStartMessages,
299
300 /// Whether we send a warning if the password is wrong (set to false when we send a warning
301 /// because we do not want to send a second warning)
302 #[strum(props(default = "0"))]
303 NotifyAboutWrongPw,
304
305 /// If a warning about exceeding quota was shown recently,
306 /// this is the percentage of quota at the time the warning was given.
307 /// Unset, when quota falls below minimal warning threshold again.
308 QuotaExceeding,
309
310 /// Timestamp of the last time housekeeping was run
311 LastHousekeeping,
312
313 /// Timestamp of the last `CantDecryptOutgoingMsgs` notification.
314 LastCantDecryptOutgoingMsgs,
315
316 /// Whether to avoid using IMAP IDLE even if the server supports it.
317 ///
318 /// This is a developer option for testing "fake idle".
319 #[strum(props(default = "0"))]
320 DisableIdle,
321
322 /// Timestamp of the next check for donation request need.
323 DonationRequestNextCheck,
324
325 /// Defines the max. size (in bytes) of messages downloaded automatically.
326 ///
327 /// For messages with large attachments, two messages are sent:
328 /// a Pre-Message containing metadata and text and a Post-Message additionally
329 /// containing the attachment. NB: Some "extra" metadata like avatars and gossiped
330 /// encryption keys is stripped from post-messages to save traffic.
331 /// Pre-Messages are shown as placeholder messages. They can be downloaded fully using
332 /// `MsgId::download_full()` later. Post-Messages are automatically downloaded if they are
333 /// smaller than the download_limit. Other messages are always auto-downloaded.
334 ///
335 /// 0 = no limit.
336 /// Changes only affect future messages.
337 #[strum(props(default = "0"))]
338 DownloadLimit,
339
340 /// Enable sending and executing (applying) sync messages. Sending requires `BccSelf` to be set
341 /// and `Bot` unset.
342 ///
343 /// On real devices, this is usually always enabled and `BccSelf` is the only setting
344 /// that controls whether sync messages are sent.
345 ///
346 /// In tests, this is usually disabled.
347 #[strum(props(default = "1"))]
348 SyncMsgs,
349
350 /// Space-separated list of all the authserv-ids which we believe
351 /// may be the one of our email server.
352 ///
353 /// See `crate::authres::update_authservid_candidates`.
354 AuthservIdCandidates,
355
356 /// Make all outgoing messages with Autocrypt header "multipart/signed".
357 SignUnencrypted,
358
359 /// Let the core save all events to the database.
360 /// This value is used internally to remember the MsgId of the logging xdc
361 #[strum(props(default = "0"))]
362 DebugLogging,
363
364 /// Last message processed by the bot.
365 LastMsgId,
366
367 /// How often to gossip Autocrypt keys in chats with multiple recipients, in seconds. 2 days by
368 /// default.
369 ///
370 /// This is not supposed to be changed by UIs and only used for testing.
371 #[strum(props(default = "172800"))]
372 GossipPeriod,
373
374 /// Row ID of the key in the `keypairs` table
375 /// used for signatures, encryption to self and included in `Autocrypt` header.
376 KeyId,
377
378 /// Send statistics to Delta Chat's developers.
379 /// Can be exposed to the user as a setting.
380 StatsSending,
381
382 /// Last time statistics were sent to Delta Chat's developers
383 StatsLastSent,
384
385 /// Last time `update_message_stats()` was called
386 StatsLastUpdate,
387
388 /// This key is sent to the statistics bot so that the bot can recognize the user
389 /// without storing the email address
390 StatsId,
391
392 /// The last contact id that already existed when statistics-sending was enabled for the first time.
393 StatsLastOldContactId,
394
395 /// MsgId of webxdc map integration.
396 WebxdcIntegration,
397
398 /// Enable webxdc realtime features.
399 #[strum(props(default = "1"))]
400 WebxdcRealtimeEnabled,
401
402 /// Last device token stored on the chatmail server.
403 ///
404 /// If it has not changed, we do not store
405 /// the device token again.
406 DeviceToken,
407
408 /// Device token encrypted with OpenPGP.
409 ///
410 /// We store encrypted token next to `device_token`
411 /// to avoid encrypting it differently and
412 /// storing the same token multiple times on the server.
413 EncryptedDeviceToken,
414
415 /// Enables running test hooks, e.g. see `InnerContext::pre_encrypt_mime_hook`.
416 /// This way is better than conditional compilation, i.e. `#[cfg(test)]`, because tests not
417 /// using this still run unmodified code.
418 TestHooks,
419
420 /// Return an error from `receive_imf_inner()`. For tests.
421 SimulateReceiveImfError,
422
423 /// Enable composing emails with Header Protection as defined in
424 /// <https://www.rfc-editor.org/rfc/rfc9788.html> "Header Protection for Cryptographically
425 /// Protected Email".
426 #[strum(props(default = "1"))]
427 StdHeaderProtectionComposing,
428
429 /// Who can call me.
430 ///
431 /// The options are from the `WhoCanCallMe` enum.
432 #[strum(props(default = "1"))]
433 WhoCanCallMe,
434
435 /// Experimental option denoting that the current profile is shared between multiple team members.
436 /// For now, the only effect of this option is that seen flags are not synchronized.
437 TeamProfile,
438}
439
440impl Config {
441 /// Whether the config option is synced across devices.
442 ///
443 /// This must be checked on both sides so that if there are different client versions, the
444 /// synchronisation of a particular option is either done or not done in both directions.
445 /// Moreover, receivers of a config value need to check if a key can be synced because if it is
446 /// a file path, it could otherwise lead to exfiltration of files from a receiver's
447 /// device if we assume an attacker to have control of a device in a multi-device setting or if
448 /// multiple users are sharing an account. Another example is `Self::SyncMsgs` itself which
449 /// mustn't be controlled by other devices.
450 pub(crate) fn is_synced(&self) -> bool {
451 matches!(
452 self,
453 Self::Displayname
454 | Self::MdnsEnabled
455 | Self::ShowEmails
456 | Self::Selfavatar
457 | Self::Selfstatus,
458 )
459 }
460
461 /// Whether the config option needs an IO scheduler restart to take effect.
462 pub(crate) fn needs_io_restart(&self) -> bool {
463 matches!(self, Config::ConfiguredAddr)
464 }
465}
466
467impl Context {
468 /// Returns true if configuration value is set in the db for the given key.
469 ///
470 /// NB: Don't use this to check if the key is configured because this doesn't look into
471 /// environment. The proper use of this function is e.g. checking a key before setting it.
472 pub(crate) async fn config_exists(&self, key: Config) -> Result<bool> {
473 Ok(self.sql.get_raw_config(key.as_ref()).await?.is_some())
474 }
475
476 /// Get a config key value. Returns `None` if no value is set.
477 pub(crate) async fn get_config_opt(&self, key: Config) -> Result<Option<String>> {
478 let env_key = format!("DELTACHAT_{}", key.as_ref().to_uppercase());
479 if let Ok(value) = env::var(env_key) {
480 return Ok(Some(value));
481 }
482
483 let value = match key {
484 Config::Selfavatar => {
485 let rel_path = self.sql.get_raw_config(key.as_ref()).await?;
486 rel_path.map(|p| {
487 get_abs_path(self, Path::new(&p))
488 .to_string_lossy()
489 .into_owned()
490 })
491 }
492 Config::SysVersion => Some(constants::DC_VERSION_STR.to_string()),
493 Config::SysMsgsizeMaxRecommended => Some(format!("{RECOMMENDED_FILE_SIZE}")),
494 Config::SysConfigKeys => Some(get_config_keys_string()),
495 _ => self.sql.get_raw_config(key.as_ref()).await?,
496 };
497 Ok(value)
498 }
499
500 /// Get a config key value if set, or a default value. Returns `None` if no value exists.
501 pub async fn get_config(&self, key: Config) -> Result<Option<String>> {
502 let value = self.get_config_opt(key).await?;
503 if value.is_some() {
504 return Ok(value);
505 }
506
507 // Default values
508 let val = match key {
509 Config::ConfiguredInboxFolder => Some("INBOX".to_string()),
510 Config::DeleteServerAfter => {
511 match !Box::pin(self.get_config_bool(Config::BccSelf)).await?
512 && Box::pin(self.is_chatmail()).await?
513 {
514 true => Some("1".to_string()),
515 false => Some("0".to_string()),
516 }
517 }
518 Config::Addr => self.get_config_opt(Config::ConfiguredAddr).await?,
519 _ => key.get_str("default").map(|s| s.to_string()),
520 };
521 Ok(val)
522 }
523
524 /// Returns Some(T) if a value for the given key is set and was successfully parsed.
525 /// Returns None if could not parse.
526 pub(crate) async fn get_config_opt_parsed<T: FromStr>(&self, key: Config) -> Result<Option<T>> {
527 self.get_config_opt(key)
528 .await
529 .map(|s: Option<String>| s.and_then(|s| s.parse().ok()))
530 }
531
532 /// Returns Some(T) if a value for the given key exists (incl. default value) and was
533 /// successfully parsed.
534 /// Returns None if could not parse.
535 pub async fn get_config_parsed<T: FromStr>(&self, key: Config) -> Result<Option<T>> {
536 self.get_config(key)
537 .await
538 .map(|s: Option<String>| s.and_then(|s| s.parse().ok()))
539 }
540
541 /// Returns 32-bit signed integer configuration value for the given key.
542 pub async fn get_config_int(&self, key: Config) -> Result<i32> {
543 Ok(self.get_config_parsed(key).await?.unwrap_or_default())
544 }
545
546 /// Returns 32-bit unsigned integer configuration value for the given key.
547 pub async fn get_config_u32(&self, key: Config) -> Result<u32> {
548 Ok(self.get_config_parsed(key).await?.unwrap_or_default())
549 }
550
551 /// Returns 64-bit signed integer configuration value for the given key.
552 pub async fn get_config_i64(&self, key: Config) -> Result<i64> {
553 Ok(self.get_config_parsed(key).await?.unwrap_or_default())
554 }
555
556 /// Returns 64-bit unsigned integer configuration value for the given key.
557 pub async fn get_config_u64(&self, key: Config) -> Result<u64> {
558 Ok(self.get_config_parsed(key).await?.unwrap_or_default())
559 }
560
561 /// Returns boolean configuration value (if set) for the given key.
562 pub(crate) async fn get_config_bool_opt(&self, key: Config) -> Result<Option<bool>> {
563 Ok(self
564 .get_config_opt_parsed::<i32>(key)
565 .await?
566 .map(|x| x != 0))
567 }
568
569 /// Returns boolean configuration value for the given key.
570 pub async fn get_config_bool(&self, key: Config) -> Result<bool> {
571 Ok(self
572 .get_config(key)
573 .await?
574 .and_then(|s| s.parse::<i32>().ok())
575 .is_some_and(|x| x != 0))
576 }
577
578 /// Returns true if sync messages should be sent.
579 pub(crate) async fn should_send_sync_msgs(&self) -> Result<bool> {
580 Ok(self.get_config_bool(Config::SyncMsgs).await?
581 && self.get_config_bool(Config::BccSelf).await?
582 && !self.get_config_bool(Config::Bot).await?)
583 }
584
585 /// Returns whether MDNs should be requested.
586 pub(crate) async fn should_request_mdns(&self) -> Result<bool> {
587 match self.get_config_bool_opt(Config::MdnsEnabled).await? {
588 Some(val) => Ok(val),
589 None => Ok(!self.get_config_bool(Config::Bot).await?),
590 }
591 }
592
593 /// Returns whether MDNs should be sent.
594 pub(crate) async fn should_send_mdns(&self) -> Result<bool> {
595 self.get_config_bool(Config::MdnsEnabled).await
596 }
597
598 /// Gets configured "delete_server_after" value.
599 ///
600 /// `None` means never delete the message, `Some(0)` means delete
601 /// at once, `Some(x)` means delete after `x` seconds.
602 pub async fn get_config_delete_server_after(&self) -> Result<Option<i64>> {
603 let val = match self
604 .get_config_parsed::<i64>(Config::DeleteServerAfter)
605 .await?
606 .unwrap_or(0)
607 {
608 0 => None,
609 1 => Some(0),
610 x => Some(x),
611 };
612 Ok(val)
613 }
614
615 /// Gets the configured provider.
616 ///
617 /// The provider is determined by the current primary transport.
618 pub async fn get_configured_provider(&self) -> Result<Option<&'static Provider>> {
619 let provider = ConfiguredLoginParam::load(self)
620 .await?
621 .and_then(|(_transport_id, param)| param.provider);
622 Ok(provider)
623 }
624
625 /// Gets configured "delete_device_after" value.
626 ///
627 /// `None` means never delete the message, `Some(x)` means delete
628 /// after `x` seconds.
629 pub async fn get_config_delete_device_after(&self) -> Result<Option<i64>> {
630 match self.get_config_int(Config::DeleteDeviceAfter).await? {
631 0 => Ok(None),
632 x => Ok(Some(i64::from(x))),
633 }
634 }
635
636 /// Executes [`SyncData::Config`] item sent by other device.
637 pub(crate) async fn sync_config(&self, key: &Config, value: &str) -> Result<()> {
638 let config_value;
639 let value = match key {
640 Config::Selfavatar if value.is_empty() => None,
641 Config::Selfavatar => {
642 config_value = BlobObject::store_from_base64(self, value)?;
643 config_value.as_deref()
644 }
645 _ => Some(value),
646 };
647 match key.is_synced() {
648 true => self.set_config_ex(Nosync, *key, value).await,
649 false => Ok(()),
650 }
651 }
652
653 fn check_config(key: Config, value: Option<&str>) -> Result<()> {
654 match key {
655 Config::Socks5Enabled
656 | Config::ProxyEnabled
657 | Config::BccSelf
658 | Config::MdnsEnabled
659 | Config::Configured
660 | Config::Bot
661 | Config::NotifyAboutWrongPw
662 | Config::SyncMsgs
663 | Config::SignUnencrypted
664 | Config::DisableIdle => {
665 ensure!(
666 matches!(value, None | Some("0") | Some("1")),
667 "Boolean value must be either 0 or 1"
668 );
669 }
670 _ => (),
671 }
672 Ok(())
673 }
674
675 /// Set the given config key and make it effective.
676 /// This may restart the IO scheduler. If `None` is passed as a value the value is cleared and
677 /// set to the default if there is one.
678 pub async fn set_config(&self, key: Config, value: Option<&str>) -> Result<()> {
679 Self::check_config(key, value)?;
680
681 let _pause = match key.needs_io_restart() {
682 true => self.scheduler.pause(self).await?,
683 _ => Default::default(),
684 };
685 if key == Config::StatsSending {
686 let old_value = self.get_config(key).await?;
687 let old_value = bool_from_config(old_value.as_deref());
688 let new_value = bool_from_config(value);
689 stats::pre_sending_config_change(self, old_value, new_value).await?;
690 }
691 self.set_config_internal(key, value).await?;
692 if key == Config::StatsSending {
693 stats::maybe_send_stats(self).await?;
694 }
695 Ok(())
696 }
697
698 pub(crate) async fn set_config_internal(&self, key: Config, value: Option<&str>) -> Result<()> {
699 self.set_config_ex(Sync, key, value).await
700 }
701
702 pub(crate) async fn set_config_ex(
703 &self,
704 sync: sync::Sync,
705 key: Config,
706 mut value: Option<&str>,
707 ) -> Result<()> {
708 Self::check_config(key, value)?;
709 let sync = sync == Sync && key.is_synced() && self.is_configured().await?;
710 let better_value;
711
712 match key {
713 Config::Selfavatar => {
714 self.sql
715 .execute("UPDATE contacts SET selfavatar_sent=0;", ())
716 .await?;
717 match value {
718 Some(path) => {
719 let path = get_abs_path(self, Path::new(path));
720 let mut blob = BlobObject::create_and_deduplicate(self, &path, &path)?;
721 blob.recode_to_avatar_size(self).await?;
722 self.sql
723 .set_raw_config(key.as_ref(), Some(blob.as_name()))
724 .await?;
725 if sync {
726 let buf = fs::read(blob.to_abs_path()).await?;
727 better_value = base64::engine::general_purpose::STANDARD.encode(buf);
728 value = Some(&better_value);
729 }
730 }
731 None => {
732 self.sql.set_raw_config(key.as_ref(), None).await?;
733 if sync {
734 better_value = String::new();
735 value = Some(&better_value);
736 }
737 }
738 }
739 self.emit_event(EventType::SelfavatarChanged);
740 }
741 Config::DeleteDeviceAfter => {
742 let ret = self.sql.set_raw_config(key.as_ref(), value).await;
743 // Interrupt ephemeral loop to delete old messages immediately.
744 self.scheduler.interrupt_ephemeral_task().await;
745 ret?
746 }
747 Config::Displayname => {
748 if let Some(v) = value {
749 better_value = sanitize_single_line(v);
750 value = Some(&better_value);
751 }
752 self.sql.set_raw_config(key.as_ref(), value).await?;
753 }
754 Config::Addr => {
755 self.sql
756 .set_raw_config(key.as_ref(), value.map(|s| s.to_lowercase()).as_deref())
757 .await?;
758 }
759 Config::ConfiguredAddr => {
760 let Some(addr) = value else {
761 bail!("Cannot unset configured_addr");
762 };
763
764 if !self.is_configured().await? {
765 info!(
766 self,
767 "Creating a pseudo configured account which will not be able to send or receive messages. Only meant for tests!"
768 );
769 add_pseudo_transport(self, addr).await?;
770 self.sql
771 .set_raw_config(Config::ConfiguredAddr.as_ref(), Some(addr))
772 .await?;
773 } else {
774 self.sql
775 .transaction(|transaction| {
776 if transaction.query_row(
777 "SELECT COUNT(*) FROM transports WHERE addr=?",
778 (addr,),
779 |row| {
780 let res: i64 = row.get(0)?;
781 Ok(res)
782 },
783 )? == 0
784 {
785 bail!("Address does not belong to any transport.");
786 }
787 transaction.execute(
788 "UPDATE config SET value=? WHERE keyname='configured_addr'",
789 (addr,),
790 )?;
791
792 // Update the timestamp for the primary transport
793 // so it becomes the first in `get_all_self_addrs()` list
794 // and the list of relays distributed in the public key.
795 // This ensures that messages will be sent
796 // to the primary relay by the contacts
797 // and will be fetched in background_fetch()
798 // which only fetches from the primary transport.
799 transaction
800 .execute(
801 "UPDATE transports SET add_timestamp=?, is_published=1 WHERE addr=?",
802 (time(), addr),
803 )
804 .context(
805 "Failed to update add_timestamp for the new primary transport",
806 )?;
807
808 // Clean up SMTP and IMAP APPEND queue.
809 //
810 // The messages in the queue have a different
811 // From address so we cannot send them over
812 // the new SMTP transport.
813 transaction.execute("DELETE FROM smtp", ())?;
814 transaction.execute("DELETE FROM imap_send", ())?;
815
816 Ok(())
817 })
818 .await?;
819 send_sync_transports(self).await?;
820 self.sql.uncache_raw_config("configured_addr").await;
821 }
822 }
823 _ => {
824 self.sql.set_raw_config(key.as_ref(), value).await?;
825 }
826 }
827 if matches!(
828 key,
829 Config::Displayname | Config::Selfavatar | Config::PrivateTag
830 ) {
831 self.emit_event(EventType::AccountsItemChanged);
832 }
833 if key.is_synced() {
834 self.emit_event(EventType::ConfigSynced { key });
835 }
836 if !sync {
837 return Ok(());
838 }
839 let Some(val) = value else {
840 return Ok(());
841 };
842 let val = val.to_string();
843 if self
844 .add_sync_item(SyncData::Config { key, val })
845 .await
846 .log_err(self)
847 .is_err()
848 {
849 return Ok(());
850 }
851 self.scheduler.interrupt_smtp().await;
852 Ok(())
853 }
854
855 /// Set the given config to an unsigned 32-bit integer value.
856 pub async fn set_config_u32(&self, key: Config, value: u32) -> Result<()> {
857 self.set_config(key, Some(&value.to_string())).await?;
858 Ok(())
859 }
860
861 /// Set the given config to a boolean value.
862 pub async fn set_config_bool(&self, key: Config, value: bool) -> Result<()> {
863 self.set_config(key, from_bool(value)).await?;
864 Ok(())
865 }
866
867 /// Sets an ui-specific key-value pair.
868 /// Keys must be prefixed by `ui.`
869 /// and should be followed by the name of the system and maybe subsystem,
870 /// eg. `ui.desktop.linux.foo`, `ui.desktop.macos.bar`, `ui.ios.foobar`.
871 pub async fn set_ui_config(&self, key: &str, value: Option<&str>) -> Result<()> {
872 ensure!(key.starts_with("ui."), "set_ui_config(): prefix missing.");
873 self.sql.set_raw_config(key, value).await
874 }
875
876 /// Gets an ui-specific value set by set_ui_config().
877 pub async fn get_ui_config(&self, key: &str) -> Result<Option<String>> {
878 ensure!(key.starts_with("ui."), "get_ui_config(): prefix missing.");
879 self.sql.get_raw_config(key).await
880 }
881}
882
883/// Returns a value for use in `Context::set_config_*()` for the given `bool`.
884pub(crate) fn from_bool(val: bool) -> Option<&'static str> {
885 Some(if val { "1" } else { "0" })
886}
887
888pub(crate) fn bool_from_config(config: Option<&str>) -> bool {
889 config.is_some_and(|v| v.parse::<i32>().unwrap_or_default() != 0)
890}
891
892// Separate impl block for self address handling
893impl Context {
894 /// Determine whether the specified addr maps to the/a self addr.
895 /// Returns `false` if no addresses are configured.
896 pub(crate) async fn is_self_addr(&self, addr: &str) -> Result<bool> {
897 Ok(self
898 .get_config(Config::ConfiguredAddr)
899 .await?
900 .iter()
901 .any(|a| addr_cmp(addr, a))
902 || self
903 .get_secondary_self_addrs()
904 .await?
905 .iter()
906 .any(|a| addr_cmp(addr, a)))
907 }
908
909 /// Sets `primary_new` as the new primary self address and saves the old
910 /// primary address (if exists) as a secondary address.
911 ///
912 /// This should only be used by test code and during configure.
913 #[cfg(test)] // AEAP is disabled, but there are still tests for it
914 pub(crate) async fn set_primary_self_addr(&self, primary_new: &str) -> Result<()> {
915 self.quota.write().await.clear();
916
917 self.sql
918 .set_raw_config(Config::ConfiguredAddr.as_ref(), Some(primary_new))
919 .await?;
920 self.emit_event(EventType::ConnectivityChanged);
921 Ok(())
922 }
923
924 /// Returns all self addresses, newest first.
925 pub(crate) async fn get_all_self_addrs(&self) -> Result<Vec<String>> {
926 self.sql
927 .query_map_vec(
928 "SELECT addr FROM transports ORDER BY add_timestamp DESC, id DESC",
929 (),
930 |row| {
931 let addr: String = row.get(0)?;
932 Ok(addr)
933 },
934 )
935 .await
936 }
937
938 /// Returns all published self addresses, newest first.
939 /// See `[Context::set_transport_unpublished]`
940 pub(crate) async fn get_published_self_addrs(&self) -> Result<Vec<String>> {
941 self.sql
942 .query_map_vec(
943 "SELECT addr FROM transports WHERE is_published=1 ORDER BY add_timestamp DESC, id DESC",
944 (),
945 |row| {
946 let addr: String = row.get(0)?;
947 Ok(addr)
948 },
949 )
950 .await
951 }
952
953 /// Returns all secondary self addresses.
954 pub(crate) async fn get_secondary_self_addrs(&self) -> Result<Vec<String>> {
955 self.sql.query_map_vec("SELECT addr FROM transports WHERE addr NOT IN (SELECT value FROM config WHERE keyname='configured_addr')", (), |row| {
956 let addr: String = row.get(0)?;
957 Ok(addr)
958 }).await
959 }
960
961 /// Returns all published secondary self addresses.
962 /// See `[Context::set_transport_unpublished]`
963 pub(crate) async fn get_published_secondary_self_addrs(&self) -> Result<Vec<String>> {
964 self.sql
965 .query_map_vec(
966 "SELECT addr FROM transports
967 WHERE is_published
968 AND addr NOT IN (SELECT value FROM config WHERE keyname='configured_addr')
969 ORDER BY add_timestamp DESC, id DESC",
970 (),
971 |row| {
972 let addr: String = row.get(0)?;
973 Ok(addr)
974 },
975 )
976 .await
977 }
978
979 /// Returns the primary self address.
980 /// Returns an error if no self addr is configured.
981 pub async fn get_primary_self_addr(&self) -> Result<String> {
982 self.get_config(Config::ConfiguredAddr)
983 .await?
984 .context("No self addr configured")
985 }
986}
987
988/// Returns all available configuration keys concated together.
989fn get_config_keys_string() -> String {
990 let keys = Config::iter().fold(String::new(), |mut acc, key| {
991 acc += key.as_ref();
992 acc += " ";
993 acc
994 });
995
996 format!(" {keys} ")
997}
998
999/// Returns all `ui.*` config keys that were set by the UI.
1000pub async fn get_all_ui_config_keys(context: &Context) -> Result<Vec<String>> {
1001 let ui_keys = context
1002 .sql
1003 .query_map_vec(
1004 "SELECT keyname FROM config WHERE keyname GLOB 'ui.*' ORDER BY config.id",
1005 (),
1006 |row| Ok(row.get::<_, String>(0)?),
1007 )
1008 .await?;
1009 Ok(ui_keys)
1010}
1011
1012#[cfg(test)]
1013mod config_tests;