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