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