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