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