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