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