1use std::fmt;
12use std::pin::Pin;
13
14use anyhow::{Context as _, Result, bail, format_err};
15use deltachat_contact_tools::{EmailAddress, addr_normalize};
16use serde::{Deserialize, Serialize};
17
18use crate::config::Config;
19use crate::configure::server_params::{ServerParams, expand_param_vector};
20use crate::constants::{DC_LP_AUTH_FLAGS, DC_LP_AUTH_OAUTH2};
21use crate::context::Context;
22use crate::ensure_and_debug_assert;
23use crate::events::EventType;
24use crate::login_param::EnteredLoginParam;
25use crate::net::load_connection_timestamp;
26use crate::provider::{Protocol, Provider, Socket, UsernamePattern, get_provider_by_id};
27use crate::sql::Sql;
28use crate::sync::{RemovedTransportData, SyncData, TransportData};
29
30#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
31pub(crate) enum ConnectionSecurity {
32 Tls,
34
35 Starttls,
37
38 Plain,
40}
41
42impl fmt::Display for ConnectionSecurity {
43 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
44 match self {
45 Self::Tls => write!(f, "tls")?,
46 Self::Starttls => write!(f, "starttls")?,
47 Self::Plain => write!(f, "plain")?,
48 }
49 Ok(())
50 }
51}
52
53impl TryFrom<Socket> for ConnectionSecurity {
54 type Error = anyhow::Error;
55
56 fn try_from(socket: Socket) -> Result<Self> {
57 match socket {
58 Socket::Automatic => Err(format_err!("Socket security is not configured")),
59 Socket::Ssl => Ok(Self::Tls),
60 Socket::Starttls => Ok(Self::Starttls),
61 Socket::Plain => Ok(Self::Plain),
62 }
63 }
64}
65
66#[derive(
68 Copy, Clone, Debug, Display, FromPrimitive, ToPrimitive, PartialEq, Eq, Serialize, Deserialize,
69)]
70#[repr(u32)]
71#[strum(serialize_all = "snake_case")]
72pub(crate) enum ConfiguredCertificateChecks {
73 OldAutomatic = 0,
85
86 Strict = 1,
88
89 AcceptInvalidCertificates = 2,
92
93 AcceptInvalidCertificates2 = 3,
98
99 Automatic = 4,
103}
104
105#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
106pub(crate) struct ConnectionCandidate {
107 pub host: String,
109
110 pub port: u16,
112
113 pub security: ConnectionSecurity,
115}
116
117impl fmt::Display for ConnectionCandidate {
118 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
119 write!(f, "{}:{}:{}", self.host, self.port, self.security)?;
120 Ok(())
121 }
122}
123
124#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
125pub(crate) struct ConfiguredServerLoginParam {
126 pub connection: ConnectionCandidate,
127
128 pub user: String,
130}
131
132impl fmt::Display for ConfiguredServerLoginParam {
133 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
134 write!(f, "{}", self.connection)?;
137 Ok(())
138 }
139}
140
141pub(crate) async fn prioritize_server_login_params(
142 sql: &Sql,
143 params: &[ConfiguredServerLoginParam],
144 alpn: &str,
145) -> Result<Vec<ConfiguredServerLoginParam>> {
146 let mut res: Vec<(Option<i64>, ConfiguredServerLoginParam)> = Vec::with_capacity(params.len());
147 for param in params {
148 let timestamp = load_connection_timestamp(
149 sql,
150 alpn,
151 ¶m.connection.host,
152 param.connection.port,
153 None,
154 )
155 .await?;
156 res.push((timestamp, param.clone()));
157 }
158 res.sort_by_key(|(ts, _param)| std::cmp::Reverse(*ts));
159 Ok(res.into_iter().map(|(_ts, param)| param).collect())
160}
161
162#[derive(Debug, Clone, PartialEq, Eq)]
165pub(crate) struct ConfiguredLoginParam {
166 pub addr: String,
168
169 pub imap: Vec<ConfiguredServerLoginParam>,
171
172 pub imap_user: String,
177
178 pub imap_password: String,
179
180 pub imap_folder: Option<String>,
185
186 pub smtp: Vec<ConfiguredServerLoginParam>,
188
189 pub smtp_user: String,
194
195 pub smtp_password: String,
196
197 pub provider: Option<&'static Provider>,
198
199 pub certificate_checks: ConfiguredCertificateChecks,
202
203 pub oauth2: bool,
205}
206
207#[derive(Debug, Serialize, Deserialize)]
210pub(crate) struct ConfiguredLoginParamJson {
211 pub addr: String,
212 pub imap: Vec<ConfiguredServerLoginParam>,
213
214 #[serde(skip_serializing_if = "Option::is_none")]
218 pub imap_folder: Option<String>,
219
220 pub imap_user: String,
221 pub imap_password: String,
222 pub smtp: Vec<ConfiguredServerLoginParam>,
223 pub smtp_user: String,
224 pub smtp_password: String,
225 pub provider_id: Option<String>,
226 pub certificate_checks: ConfiguredCertificateChecks,
227 pub oauth2: bool,
228}
229
230impl fmt::Display for ConfiguredLoginParam {
231 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
232 let provider_id = match self.provider {
233 Some(provider) => provider.id,
234 None => "none",
235 };
236 let certificate_checks = self.certificate_checks;
237 if let Ok(parsed_addr) = EmailAddress::new(&self.addr) {
238 write!(f, "***@{}", parsed_addr.domain)?;
240 } else {
241 write!(f, "{}", self.addr)?;
245 };
246 write!(f, " imap:[")?;
247 let mut first = true;
248 for imap in &self.imap {
249 if !first {
250 write!(f, ", ")?;
251 }
252 write!(f, "{imap}")?;
253 first = false;
254 }
255 write!(f, "]")?;
256 if let Some(folder) = &self.imap_folder {
257 write!(f, " folder:{folder:?}")?;
258 }
259 write!(f, " smtp:[")?;
260 let mut first = true;
261 for smtp in &self.smtp {
262 if !first {
263 write!(f, ", ")?;
264 }
265 write!(f, "{smtp}")?;
266 first = false;
267 }
268 write!(f, "] provider:{provider_id} cert_{certificate_checks}")?;
269 Ok(())
270 }
271}
272
273impl ConfiguredLoginParam {
274 pub(crate) async fn load(context: &Context) -> Result<Option<(u32, Self)>> {
280 let Some(self_addr) = context.get_config(Config::ConfiguredAddr).await? else {
281 return Ok(None);
282 };
283
284 let Some((id, json)) = context
285 .sql
286 .query_row_optional(
287 "SELECT id, configured_param FROM transports WHERE addr=?",
288 (&self_addr,),
289 |row| {
290 let id: u32 = row.get(0)?;
291 let json: String = row.get(1)?;
292 Ok((id, json))
293 },
294 )
295 .await?
296 else {
297 bail!("Self address {self_addr} doesn't have a corresponding transport");
298 };
299 Ok(Some((id, Self::from_json(&json)?)))
300 }
301
302 pub(crate) async fn load_all(context: &Context) -> Result<Vec<(u32, Self)>> {
307 context
308 .sql
309 .query_map_vec("SELECT id, configured_param FROM transports", (), |row| {
310 let id: u32 = row.get(0)?;
311 let json: String = row.get(1)?;
312 let param = Self::from_json(&json)?;
313 Ok((id, param))
314 })
315 .await
316 }
317
318 pub(crate) async fn load_legacy(context: &Context) -> Result<Option<Self>> {
320 if !context.get_config_bool(Config::Configured).await? {
321 return Ok(None);
322 }
323
324 let addr = context
325 .get_config(Config::ConfiguredAddr)
326 .await?
327 .unwrap_or_default()
328 .trim()
329 .to_string();
330
331 let certificate_checks: ConfiguredCertificateChecks = if let Some(certificate_checks) =
332 context
333 .get_config_parsed::<i32>(Config::ConfiguredImapCertificateChecks)
334 .await?
335 {
336 num_traits::FromPrimitive::from_i32(certificate_checks)
337 .context("Invalid configured_imap_certificate_checks value")?
338 } else {
339 ConfiguredCertificateChecks::OldAutomatic
342 };
343
344 let send_pw = context
345 .get_config(Config::ConfiguredSendPw)
346 .await?
347 .context("SMTP password is not configured")?;
348 let mail_pw = context
349 .get_config(Config::ConfiguredMailPw)
350 .await?
351 .context("IMAP password is not configured")?;
352
353 let server_flags = context
354 .get_config_parsed::<i32>(Config::ConfiguredServerFlags)
355 .await?
356 .unwrap_or_default();
357 let oauth2 = matches!(server_flags & DC_LP_AUTH_FLAGS, DC_LP_AUTH_OAUTH2);
358
359 let provider = context
360 .get_config(Config::ConfiguredProvider)
361 .await?
362 .and_then(|cfg| get_provider_by_id(&cfg));
363
364 let imap;
365 let smtp;
366
367 let mail_user = context
368 .get_config(Config::ConfiguredMailUser)
369 .await?
370 .unwrap_or_default();
371 let send_user = context
372 .get_config(Config::ConfiguredSendUser)
373 .await?
374 .unwrap_or_default();
375
376 if let Some(provider) = provider {
377 let parsed_addr = EmailAddress::new(&addr).context("Bad email-address")?;
378 let addr_localpart = parsed_addr.local;
379
380 if provider.server.is_empty() {
381 let servers = vec![
382 ServerParams {
383 protocol: Protocol::Imap,
384 hostname: context
385 .get_config(Config::ConfiguredMailServer)
386 .await?
387 .unwrap_or_default(),
388 port: context
389 .get_config_parsed::<u16>(Config::ConfiguredMailPort)
390 .await?
391 .unwrap_or_default(),
392 socket: context
393 .get_config_parsed::<i32>(Config::ConfiguredMailSecurity)
394 .await?
395 .and_then(num_traits::FromPrimitive::from_i32)
396 .unwrap_or_default(),
397 username: mail_user.clone(),
398 },
399 ServerParams {
400 protocol: Protocol::Smtp,
401 hostname: context
402 .get_config(Config::ConfiguredSendServer)
403 .await?
404 .unwrap_or_default(),
405 port: context
406 .get_config_parsed::<u16>(Config::ConfiguredSendPort)
407 .await?
408 .unwrap_or_default(),
409 socket: context
410 .get_config_parsed::<i32>(Config::ConfiguredSendSecurity)
411 .await?
412 .and_then(num_traits::FromPrimitive::from_i32)
413 .unwrap_or_default(),
414 username: send_user.clone(),
415 },
416 ];
417 let servers = expand_param_vector(servers, &addr, &parsed_addr.domain);
418 imap = servers
419 .iter()
420 .filter_map(|params| {
421 let Ok(security) = params.socket.try_into() else {
422 return None;
423 };
424 if params.protocol == Protocol::Imap {
425 Some(ConfiguredServerLoginParam {
426 connection: ConnectionCandidate {
427 host: params.hostname.clone(),
428 port: params.port,
429 security,
430 },
431 user: params.username.clone(),
432 })
433 } else {
434 None
435 }
436 })
437 .collect();
438 smtp = servers
439 .iter()
440 .filter_map(|params| {
441 let Ok(security) = params.socket.try_into() else {
442 return None;
443 };
444 if params.protocol == Protocol::Smtp {
445 Some(ConfiguredServerLoginParam {
446 connection: ConnectionCandidate {
447 host: params.hostname.clone(),
448 port: params.port,
449 security,
450 },
451 user: params.username.clone(),
452 })
453 } else {
454 None
455 }
456 })
457 .collect();
458 } else {
459 imap = provider
460 .server
461 .iter()
462 .filter_map(|server| {
463 if server.protocol != Protocol::Imap {
464 return None;
465 }
466
467 let Ok(security) = server.socket.try_into() else {
468 return None;
469 };
470
471 Some(ConfiguredServerLoginParam {
472 connection: ConnectionCandidate {
473 host: server.hostname.to_string(),
474 port: server.port,
475 security,
476 },
477 user: if !mail_user.is_empty() {
478 mail_user.clone()
479 } else {
480 match server.username_pattern {
481 UsernamePattern::Email => addr.to_string(),
482 UsernamePattern::Emaillocalpart => addr_localpart.clone(),
483 }
484 },
485 })
486 })
487 .collect();
488 smtp = provider
489 .server
490 .iter()
491 .filter_map(|server| {
492 if server.protocol != Protocol::Smtp {
493 return None;
494 }
495
496 let Ok(security) = server.socket.try_into() else {
497 return None;
498 };
499
500 Some(ConfiguredServerLoginParam {
501 connection: ConnectionCandidate {
502 host: server.hostname.to_string(),
503 port: server.port,
504 security,
505 },
506 user: if !send_user.is_empty() {
507 send_user.clone()
508 } else {
509 match server.username_pattern {
510 UsernamePattern::Email => addr.to_string(),
511 UsernamePattern::Emaillocalpart => addr_localpart.clone(),
512 }
513 },
514 })
515 })
516 .collect();
517 }
518 } else if let (Some(configured_mail_servers), Some(configured_send_servers)) = (
519 context.get_config(Config::ConfiguredImapServers).await?,
520 context.get_config(Config::ConfiguredSmtpServers).await?,
521 ) {
522 imap = serde_json::from_str(&configured_mail_servers)
523 .context("Failed to parse configured IMAP servers")?;
524 smtp = serde_json::from_str(&configured_send_servers)
525 .context("Failed to parse configured SMTP servers")?;
526 } else {
527 let mail_server = context
529 .get_config(Config::ConfiguredMailServer)
530 .await?
531 .unwrap_or_default();
532 let mail_port = context
533 .get_config_parsed::<u16>(Config::ConfiguredMailPort)
534 .await?
535 .unwrap_or_default();
536
537 let mail_security: Socket = context
538 .get_config_parsed::<i32>(Config::ConfiguredMailSecurity)
539 .await?
540 .and_then(num_traits::FromPrimitive::from_i32)
541 .unwrap_or_default();
542
543 let send_server = context
544 .get_config(Config::ConfiguredSendServer)
545 .await?
546 .context("SMTP server is not configured")?;
547 let send_port = context
548 .get_config_parsed::<u16>(Config::ConfiguredSendPort)
549 .await?
550 .unwrap_or_default();
551 let send_security: Socket = context
552 .get_config_parsed::<i32>(Config::ConfiguredSendSecurity)
553 .await?
554 .and_then(num_traits::FromPrimitive::from_i32)
555 .unwrap_or_default();
556
557 imap = vec![ConfiguredServerLoginParam {
558 connection: ConnectionCandidate {
559 host: mail_server,
560 port: mail_port,
561 security: mail_security.try_into()?,
562 },
563 user: mail_user.clone(),
564 }];
565 smtp = vec![ConfiguredServerLoginParam {
566 connection: ConnectionCandidate {
567 host: send_server,
568 port: send_port,
569 security: send_security.try_into()?,
570 },
571 user: send_user.clone(),
572 }];
573 }
574
575 Ok(Some(ConfiguredLoginParam {
576 addr,
577 imap,
578 imap_folder: None,
579 imap_user: mail_user,
580 imap_password: mail_pw,
581 smtp,
582 smtp_user: send_user,
583 smtp_password: send_pw,
584 certificate_checks,
585 provider,
586 oauth2,
587 }))
588 }
589
590 pub(crate) async fn save_to_transports_table(
591 self,
592 context: &Context,
593 entered_param: &EnteredLoginParam,
594 timestamp: i64,
595 ) -> Result<()> {
596 let is_published = true;
597 save_transport(
598 context,
599 entered_param,
600 &self.into(),
601 timestamp,
602 is_published,
603 )
604 .await?;
605 Ok(())
606 }
607
608 pub(crate) fn from_json(json: &str) -> Result<Self> {
609 let json: ConfiguredLoginParamJson = serde_json::from_str(json)?;
610
611 ensure_and_debug_assert!(
612 json.imap_folder
613 .as_ref()
614 .is_none_or(|folder| !folder.is_empty()),
615 "Configured watched folder name cannot be empty"
616 );
617 let provider = json.provider_id.and_then(|id| get_provider_by_id(&id));
618
619 Ok(ConfiguredLoginParam {
620 addr: json.addr,
621 imap: json.imap,
622 imap_folder: json.imap_folder,
623 imap_user: json.imap_user,
624 imap_password: json.imap_password,
625 smtp: json.smtp,
626 smtp_user: json.smtp_user,
627 smtp_password: json.smtp_password,
628 provider,
629 certificate_checks: json.certificate_checks,
630 oauth2: json.oauth2,
631 })
632 }
633
634 pub(crate) fn into_json(self) -> Result<String> {
635 let json: ConfiguredLoginParamJson = self.into();
636 Ok(serde_json::to_string(&json)?)
637 }
638
639 pub(crate) fn strict_tls(&self, connected_through_proxy: bool) -> bool {
640 let provider_strict_tls = self.provider.map(|provider| provider.opt.strict_tls);
641 match self.certificate_checks {
642 ConfiguredCertificateChecks::OldAutomatic => {
643 provider_strict_tls.unwrap_or(connected_through_proxy)
644 }
645 ConfiguredCertificateChecks::Automatic => provider_strict_tls.unwrap_or(true),
646 ConfiguredCertificateChecks::Strict => true,
647 ConfiguredCertificateChecks::AcceptInvalidCertificates
648 | ConfiguredCertificateChecks::AcceptInvalidCertificates2 => false,
649 }
650 }
651}
652
653impl From<ConfiguredLoginParam> for ConfiguredLoginParamJson {
654 fn from(configured_login_param: ConfiguredLoginParam) -> Self {
655 Self {
656 addr: configured_login_param.addr,
657 imap: configured_login_param.imap,
658 imap_user: configured_login_param.imap_user,
659 imap_password: configured_login_param.imap_password,
660 imap_folder: configured_login_param.imap_folder,
661 smtp: configured_login_param.smtp,
662 smtp_user: configured_login_param.smtp_user,
663 smtp_password: configured_login_param.smtp_password,
664 provider_id: configured_login_param.provider.map(|p| p.id.to_string()),
665 certificate_checks: configured_login_param.certificate_checks,
666 oauth2: configured_login_param.oauth2,
667 }
668 }
669}
670
671pub(crate) async fn save_transport(
674 context: &Context,
675 entered_param: &EnteredLoginParam,
676 configured: &ConfiguredLoginParamJson,
677 add_timestamp: i64,
678 is_published: bool,
679) -> Result<bool> {
680 ensure_and_debug_assert!(
681 configured
682 .imap_folder
683 .as_ref()
684 .is_none_or(|folder| !folder.is_empty()),
685 "Configured watched folder name cannot be empty"
686 );
687
688 let addr = addr_normalize(&configured.addr);
689 let configured_addr = context.get_config(Config::ConfiguredAddr).await?;
690 let mut modified = context
691 .sql
692 .execute(
693 "INSERT INTO transports (addr, entered_param, configured_param, add_timestamp, is_published)
694 VALUES (?, ?, ?, ?, ?)
695 ON CONFLICT (addr)
696 DO UPDATE SET entered_param=excluded.entered_param,
697 configured_param=excluded.configured_param,
698 add_timestamp=excluded.add_timestamp,
699 is_published=excluded.is_published
700 WHERE entered_param != excluded.entered_param
701 OR configured_param != excluded.configured_param
702 OR add_timestamp < excluded.add_timestamp
703 OR is_published != excluded.is_published",
704 (
705 &addr,
706 serde_json::to_string(entered_param)?,
707 serde_json::to_string(configured)?,
708 add_timestamp,
709 is_published,
710 ),
711 )
712 .await?
713 > 0;
714
715 if configured_addr.is_none() {
716 context
718 .sql
719 .set_raw_config(Config::ConfiguredAddr.as_ref(), Some(&addr))
720 .await?;
721 modified = true;
722 }
723 Ok(modified)
724}
725
726pub(crate) async fn send_sync_transports(context: &Context) -> Result<()> {
728 info!(context, "Sending transport synchronization message.");
729
730 context.self_public_key.lock().await.take();
732
733 let transports = context
744 .sql
745 .query_map_vec(
746 "SELECT entered_param, configured_param, add_timestamp, is_published
747 FROM transports WHERE id>1",
748 (),
749 |row| {
750 let entered_json: String = row.get(0)?;
751 let entered: EnteredLoginParam = serde_json::from_str(&entered_json)?;
752 let configured_json: String = row.get(1)?;
753 let configured: ConfiguredLoginParamJson = serde_json::from_str(&configured_json)?;
754 let timestamp: i64 = row.get(2)?;
755 let is_published: bool = row.get(3)?;
756 Ok(TransportData {
757 configured,
758 entered,
759 timestamp,
760 is_published,
761 })
762 },
763 )
764 .await?;
765 let removed_transports = context
766 .sql
767 .query_map_vec(
768 "SELECT addr, remove_timestamp FROM removed_transports",
769 (),
770 |row| {
771 let addr: String = row.get(0)?;
772 let timestamp: i64 = row.get(1)?;
773 Ok(RemovedTransportData { addr, timestamp })
774 },
775 )
776 .await?;
777 context
778 .add_sync_item(SyncData::Transports {
779 transports,
780 removed_transports,
781 })
782 .await?;
783 context.scheduler.interrupt_smtp().await;
784
785 Ok(())
786}
787
788pub(crate) async fn sync_transports(
790 context: &Context,
791 transports: &[TransportData],
792 removed_transports: &[RemovedTransportData],
793) -> Result<()> {
794 let mut modified = false;
795 for TransportData {
796 configured,
797 entered,
798 timestamp,
799 is_published,
800 } in transports
801 {
802 modified |= save_transport(context, entered, configured, *timestamp, *is_published).await?;
803 }
804
805 context
806 .sql
807 .transaction(|transaction| {
808 let configured_addr = transaction.query_row(
809 "SELECT value FROM config WHERE keyname='configured_addr'",
810 (),
811 |row| {
812 let addr: String = row.get(0)?;
813 Ok(addr)
814 },
815 )?;
816 for RemovedTransportData { addr, timestamp } in removed_transports {
817 if *addr == configured_addr {
818 continue;
819 }
820 modified |= transaction.execute(
821 "DELETE FROM transports
822 WHERE addr=? AND add_timestamp<=?",
823 (addr, timestamp),
824 )? > 0;
825 transaction.execute(
826 "INSERT INTO removed_transports (addr, remove_timestamp)
827 VALUES (?, ?)
828 ON CONFLICT (addr) DO
829 UPDATE SET remove_timestamp = excluded.remove_timestamp
830 WHERE excluded.remove_timestamp > remove_timestamp",
831 (addr, timestamp),
832 )?;
833 }
834 Ok(())
835 })
836 .await?;
837
838 if modified {
839 context.self_public_key.lock().await.take();
840 tokio::task::spawn(restart_io_if_running_boxed(context.clone()));
841 context.emit_event(EventType::TransportsModified);
842 }
843 Ok(())
844}
845
846fn restart_io_if_running_boxed(context: Context) -> Pin<Box<dyn Future<Output = ()> + Send>> {
849 Box::pin(async move { context.restart_io_if_running().await })
850}
851
852pub(crate) async fn add_pseudo_transport(context: &Context, addr: &str) -> Result<()> {
854 context.sql
855 .execute(
856 "INSERT INTO transports (addr, entered_param, configured_param) VALUES (?, ?, ?)",
857 (
858 addr,
859 serde_json::to_string(&EnteredLoginParam{addr: addr.to_string(), ..Default::default()})?,
860 format!(r#"{{"addr":"{addr}","imap":[],"imap_user":"","imap_password":"","smtp":[],"smtp_user":"","smtp_password":"","certificate_checks":"Automatic","oauth2":false}}"#)
861 ),
862 )
863 .await?;
864 Ok(())
865}
866
867#[cfg(test)]
868mod transport_tests;