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