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