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