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