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