Skip to main content

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;