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