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;