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