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, "] smtp:[")?;
256 let mut first = true;
257 for smtp in &self.smtp {
258 if !first {
259 write!(f, ", ")?;
260 }
261 write!(f, "{smtp}")?;
262 first = false;
263 }
264 write!(f, "] provider:{provider_id} cert_{certificate_checks}")?;
265 Ok(())
266 }
267}
268
269impl ConfiguredLoginParam {
270 pub(crate) async fn load(context: &Context) -> Result<Option<(u32, Self)>> {
276 let Some(self_addr) = context.get_config(Config::ConfiguredAddr).await? else {
277 return Ok(None);
278 };
279
280 let Some((id, json)) = context
281 .sql
282 .query_row_optional(
283 "SELECT id, configured_param FROM transports WHERE addr=?",
284 (&self_addr,),
285 |row| {
286 let id: u32 = row.get(0)?;
287 let json: String = row.get(1)?;
288 Ok((id, json))
289 },
290 )
291 .await?
292 else {
293 bail!("Self address {self_addr} doesn't have a corresponding transport");
294 };
295 Ok(Some((id, Self::from_json(&json)?)))
296 }
297
298 pub(crate) async fn load_all(context: &Context) -> Result<Vec<(u32, Self)>> {
303 context
304 .sql
305 .query_map_vec("SELECT id, configured_param FROM transports", (), |row| {
306 let id: u32 = row.get(0)?;
307 let json: String = row.get(1)?;
308 let param = Self::from_json(&json)?;
309 Ok((id, param))
310 })
311 .await
312 }
313
314 pub(crate) async fn load_legacy(context: &Context) -> Result<Option<Self>> {
316 if !context.get_config_bool(Config::Configured).await? {
317 return Ok(None);
318 }
319
320 let addr = context
321 .get_config(Config::ConfiguredAddr)
322 .await?
323 .unwrap_or_default()
324 .trim()
325 .to_string();
326
327 let certificate_checks: ConfiguredCertificateChecks = if let Some(certificate_checks) =
328 context
329 .get_config_parsed::<i32>(Config::ConfiguredImapCertificateChecks)
330 .await?
331 {
332 num_traits::FromPrimitive::from_i32(certificate_checks)
333 .context("Invalid configured_imap_certificate_checks value")?
334 } else {
335 ConfiguredCertificateChecks::OldAutomatic
338 };
339
340 let send_pw = context
341 .get_config(Config::ConfiguredSendPw)
342 .await?
343 .context("SMTP password is not configured")?;
344 let mail_pw = context
345 .get_config(Config::ConfiguredMailPw)
346 .await?
347 .context("IMAP password is not configured")?;
348
349 let server_flags = context
350 .get_config_parsed::<i32>(Config::ConfiguredServerFlags)
351 .await?
352 .unwrap_or_default();
353 let oauth2 = matches!(server_flags & DC_LP_AUTH_FLAGS, DC_LP_AUTH_OAUTH2);
354
355 let provider = context
356 .get_config(Config::ConfiguredProvider)
357 .await?
358 .and_then(|cfg| get_provider_by_id(&cfg));
359
360 let imap;
361 let smtp;
362
363 let mail_user = context
364 .get_config(Config::ConfiguredMailUser)
365 .await?
366 .unwrap_or_default();
367 let send_user = context
368 .get_config(Config::ConfiguredSendUser)
369 .await?
370 .unwrap_or_default();
371
372 if let Some(provider) = provider {
373 let parsed_addr = EmailAddress::new(&addr).context("Bad email-address")?;
374 let addr_localpart = parsed_addr.local;
375
376 if provider.server.is_empty() {
377 let servers = vec![
378 ServerParams {
379 protocol: Protocol::Imap,
380 hostname: context
381 .get_config(Config::ConfiguredMailServer)
382 .await?
383 .unwrap_or_default(),
384 port: context
385 .get_config_parsed::<u16>(Config::ConfiguredMailPort)
386 .await?
387 .unwrap_or_default(),
388 socket: context
389 .get_config_parsed::<i32>(Config::ConfiguredMailSecurity)
390 .await?
391 .and_then(num_traits::FromPrimitive::from_i32)
392 .unwrap_or_default(),
393 username: mail_user.clone(),
394 },
395 ServerParams {
396 protocol: Protocol::Smtp,
397 hostname: context
398 .get_config(Config::ConfiguredSendServer)
399 .await?
400 .unwrap_or_default(),
401 port: context
402 .get_config_parsed::<u16>(Config::ConfiguredSendPort)
403 .await?
404 .unwrap_or_default(),
405 socket: context
406 .get_config_parsed::<i32>(Config::ConfiguredSendSecurity)
407 .await?
408 .and_then(num_traits::FromPrimitive::from_i32)
409 .unwrap_or_default(),
410 username: send_user.clone(),
411 },
412 ];
413 let servers = expand_param_vector(servers, &addr, &parsed_addr.domain);
414 imap = servers
415 .iter()
416 .filter_map(|params| {
417 let Ok(security) = params.socket.try_into() else {
418 return None;
419 };
420 if params.protocol == Protocol::Imap {
421 Some(ConfiguredServerLoginParam {
422 connection: ConnectionCandidate {
423 host: params.hostname.clone(),
424 port: params.port,
425 security,
426 },
427 user: params.username.clone(),
428 })
429 } else {
430 None
431 }
432 })
433 .collect();
434 smtp = servers
435 .iter()
436 .filter_map(|params| {
437 let Ok(security) = params.socket.try_into() else {
438 return None;
439 };
440 if params.protocol == Protocol::Smtp {
441 Some(ConfiguredServerLoginParam {
442 connection: ConnectionCandidate {
443 host: params.hostname.clone(),
444 port: params.port,
445 security,
446 },
447 user: params.username.clone(),
448 })
449 } else {
450 None
451 }
452 })
453 .collect();
454 } else {
455 imap = provider
456 .server
457 .iter()
458 .filter_map(|server| {
459 if server.protocol != Protocol::Imap {
460 return None;
461 }
462
463 let Ok(security) = server.socket.try_into() else {
464 return None;
465 };
466
467 Some(ConfiguredServerLoginParam {
468 connection: ConnectionCandidate {
469 host: server.hostname.to_string(),
470 port: server.port,
471 security,
472 },
473 user: if !mail_user.is_empty() {
474 mail_user.clone()
475 } else {
476 match server.username_pattern {
477 UsernamePattern::Email => addr.to_string(),
478 UsernamePattern::Emaillocalpart => addr_localpart.clone(),
479 }
480 },
481 })
482 })
483 .collect();
484 smtp = provider
485 .server
486 .iter()
487 .filter_map(|server| {
488 if server.protocol != Protocol::Smtp {
489 return None;
490 }
491
492 let Ok(security) = server.socket.try_into() else {
493 return None;
494 };
495
496 Some(ConfiguredServerLoginParam {
497 connection: ConnectionCandidate {
498 host: server.hostname.to_string(),
499 port: server.port,
500 security,
501 },
502 user: if !send_user.is_empty() {
503 send_user.clone()
504 } else {
505 match server.username_pattern {
506 UsernamePattern::Email => addr.to_string(),
507 UsernamePattern::Emaillocalpart => addr_localpart.clone(),
508 }
509 },
510 })
511 })
512 .collect();
513 }
514 } else if let (Some(configured_mail_servers), Some(configured_send_servers)) = (
515 context.get_config(Config::ConfiguredImapServers).await?,
516 context.get_config(Config::ConfiguredSmtpServers).await?,
517 ) {
518 imap = serde_json::from_str(&configured_mail_servers)
519 .context("Failed to parse configured IMAP servers")?;
520 smtp = serde_json::from_str(&configured_send_servers)
521 .context("Failed to parse configured SMTP servers")?;
522 } else {
523 let mail_server = context
525 .get_config(Config::ConfiguredMailServer)
526 .await?
527 .unwrap_or_default();
528 let mail_port = context
529 .get_config_parsed::<u16>(Config::ConfiguredMailPort)
530 .await?
531 .unwrap_or_default();
532
533 let mail_security: Socket = context
534 .get_config_parsed::<i32>(Config::ConfiguredMailSecurity)
535 .await?
536 .and_then(num_traits::FromPrimitive::from_i32)
537 .unwrap_or_default();
538
539 let send_server = context
540 .get_config(Config::ConfiguredSendServer)
541 .await?
542 .context("SMTP server is not configured")?;
543 let send_port = context
544 .get_config_parsed::<u16>(Config::ConfiguredSendPort)
545 .await?
546 .unwrap_or_default();
547 let send_security: Socket = context
548 .get_config_parsed::<i32>(Config::ConfiguredSendSecurity)
549 .await?
550 .and_then(num_traits::FromPrimitive::from_i32)
551 .unwrap_or_default();
552
553 imap = vec![ConfiguredServerLoginParam {
554 connection: ConnectionCandidate {
555 host: mail_server,
556 port: mail_port,
557 security: mail_security.try_into()?,
558 },
559 user: mail_user.clone(),
560 }];
561 smtp = vec![ConfiguredServerLoginParam {
562 connection: ConnectionCandidate {
563 host: send_server,
564 port: send_port,
565 security: send_security.try_into()?,
566 },
567 user: send_user.clone(),
568 }];
569 }
570
571 Ok(Some(ConfiguredLoginParam {
572 addr,
573 imap,
574 imap_folder: None,
575 imap_user: mail_user,
576 imap_password: mail_pw,
577 smtp,
578 smtp_user: send_user,
579 smtp_password: send_pw,
580 certificate_checks,
581 provider,
582 oauth2,
583 }))
584 }
585
586 pub(crate) async fn save_to_transports_table(
587 self,
588 context: &Context,
589 entered_param: &EnteredLoginParam,
590 timestamp: i64,
591 ) -> Result<()> {
592 let is_published = true;
593 save_transport(
594 context,
595 entered_param,
596 &self.into(),
597 timestamp,
598 is_published,
599 )
600 .await?;
601 Ok(())
602 }
603
604 pub(crate) fn from_json(json: &str) -> Result<Self> {
605 let json: ConfiguredLoginParamJson = serde_json::from_str(json)?;
606
607 ensure_and_debug_assert!(
608 json.imap_folder
609 .as_ref()
610 .is_none_or(|folder| !folder.is_empty()),
611 "Configured watched folder name cannot be empty"
612 );
613 let provider = json.provider_id.and_then(|id| get_provider_by_id(&id));
614
615 Ok(ConfiguredLoginParam {
616 addr: json.addr,
617 imap: json.imap,
618 imap_folder: json.imap_folder,
619 imap_user: json.imap_user,
620 imap_password: json.imap_password,
621 smtp: json.smtp,
622 smtp_user: json.smtp_user,
623 smtp_password: json.smtp_password,
624 provider,
625 certificate_checks: json.certificate_checks,
626 oauth2: json.oauth2,
627 })
628 }
629
630 pub(crate) fn into_json(self) -> Result<String> {
631 let json: ConfiguredLoginParamJson = self.into();
632 Ok(serde_json::to_string(&json)?)
633 }
634
635 pub(crate) fn strict_tls(&self, connected_through_proxy: bool) -> bool {
636 let provider_strict_tls = self.provider.map(|provider| provider.opt.strict_tls);
637 match self.certificate_checks {
638 ConfiguredCertificateChecks::OldAutomatic => {
639 provider_strict_tls.unwrap_or(connected_through_proxy)
640 }
641 ConfiguredCertificateChecks::Automatic => provider_strict_tls.unwrap_or(true),
642 ConfiguredCertificateChecks::Strict => true,
643 ConfiguredCertificateChecks::AcceptInvalidCertificates
644 | ConfiguredCertificateChecks::AcceptInvalidCertificates2 => false,
645 }
646 }
647}
648
649impl From<ConfiguredLoginParam> for ConfiguredLoginParamJson {
650 fn from(configured_login_param: ConfiguredLoginParam) -> Self {
651 Self {
652 addr: configured_login_param.addr,
653 imap: configured_login_param.imap,
654 imap_user: configured_login_param.imap_user,
655 imap_password: configured_login_param.imap_password,
656 imap_folder: configured_login_param.imap_folder,
657 smtp: configured_login_param.smtp,
658 smtp_user: configured_login_param.smtp_user,
659 smtp_password: configured_login_param.smtp_password,
660 provider_id: configured_login_param.provider.map(|p| p.id.to_string()),
661 certificate_checks: configured_login_param.certificate_checks,
662 oauth2: configured_login_param.oauth2,
663 }
664 }
665}
666
667pub(crate) async fn save_transport(
670 context: &Context,
671 entered_param: &EnteredLoginParam,
672 configured: &ConfiguredLoginParamJson,
673 add_timestamp: i64,
674 is_published: bool,
675) -> Result<bool> {
676 ensure_and_debug_assert!(
677 configured
678 .imap_folder
679 .as_ref()
680 .is_none_or(|folder| !folder.is_empty()),
681 "Configured watched folder name cannot be empty"
682 );
683
684 let addr = addr_normalize(&configured.addr);
685 let configured_addr = context.get_config(Config::ConfiguredAddr).await?;
686 let mut modified = context
687 .sql
688 .execute(
689 "INSERT INTO transports (addr, entered_param, configured_param, add_timestamp, is_published)
690 VALUES (?, ?, ?, ?, ?)
691 ON CONFLICT (addr)
692 DO UPDATE SET entered_param=excluded.entered_param,
693 configured_param=excluded.configured_param,
694 add_timestamp=excluded.add_timestamp,
695 is_published=excluded.is_published
696 WHERE entered_param != excluded.entered_param
697 OR configured_param != excluded.configured_param
698 OR add_timestamp < excluded.add_timestamp
699 OR is_published != excluded.is_published",
700 (
701 &addr,
702 serde_json::to_string(entered_param)?,
703 serde_json::to_string(configured)?,
704 add_timestamp,
705 is_published,
706 ),
707 )
708 .await?
709 > 0;
710
711 if configured_addr.is_none() {
712 context
714 .sql
715 .set_raw_config(Config::ConfiguredAddr.as_ref(), Some(&addr))
716 .await?;
717 modified = true;
718 }
719 Ok(modified)
720}
721
722pub(crate) async fn send_sync_transports(context: &Context) -> Result<()> {
724 info!(context, "Sending transport synchronization message.");
725
726 context.self_public_key.lock().await.take();
728
729 let transports = context
740 .sql
741 .query_map_vec(
742 "SELECT entered_param, configured_param, add_timestamp, is_published
743 FROM transports WHERE id>1",
744 (),
745 |row| {
746 let entered_json: String = row.get(0)?;
747 let entered: EnteredLoginParam = serde_json::from_str(&entered_json)?;
748 let configured_json: String = row.get(1)?;
749 let configured: ConfiguredLoginParamJson = serde_json::from_str(&configured_json)?;
750 let timestamp: i64 = row.get(2)?;
751 let is_published: bool = row.get(3)?;
752 Ok(TransportData {
753 configured,
754 entered,
755 timestamp,
756 is_published,
757 })
758 },
759 )
760 .await?;
761 let removed_transports = context
762 .sql
763 .query_map_vec(
764 "SELECT addr, remove_timestamp FROM removed_transports",
765 (),
766 |row| {
767 let addr: String = row.get(0)?;
768 let timestamp: i64 = row.get(1)?;
769 Ok(RemovedTransportData { addr, timestamp })
770 },
771 )
772 .await?;
773 context
774 .add_sync_item(SyncData::Transports {
775 transports,
776 removed_transports,
777 })
778 .await?;
779 context.scheduler.interrupt_smtp().await;
780
781 Ok(())
782}
783
784pub(crate) async fn sync_transports(
786 context: &Context,
787 transports: &[TransportData],
788 removed_transports: &[RemovedTransportData],
789) -> Result<()> {
790 let mut modified = false;
791 for TransportData {
792 configured,
793 entered,
794 timestamp,
795 is_published,
796 } in transports
797 {
798 modified |= save_transport(context, entered, configured, *timestamp, *is_published).await?;
799 }
800
801 context
802 .sql
803 .transaction(|transaction| {
804 let configured_addr = transaction.query_row(
805 "SELECT value FROM config WHERE keyname='configured_addr'",
806 (),
807 |row| {
808 let addr: String = row.get(0)?;
809 Ok(addr)
810 },
811 )?;
812 for RemovedTransportData { addr, timestamp } in removed_transports {
813 if *addr == configured_addr {
814 continue;
815 }
816 modified |= transaction.execute(
817 "DELETE FROM transports
818 WHERE addr=? AND add_timestamp<=?",
819 (addr, timestamp),
820 )? > 0;
821 transaction.execute(
822 "INSERT INTO removed_transports (addr, remove_timestamp)
823 VALUES (?, ?)
824 ON CONFLICT (addr) DO
825 UPDATE SET remove_timestamp = excluded.remove_timestamp
826 WHERE excluded.remove_timestamp > remove_timestamp",
827 (addr, timestamp),
828 )?;
829 }
830 Ok(())
831 })
832 .await?;
833
834 if modified {
835 context.self_public_key.lock().await.take();
836 tokio::task::spawn(restart_io_if_running_boxed(context.clone()));
837 context.emit_event(EventType::TransportsModified);
838 }
839 Ok(())
840}
841
842fn restart_io_if_running_boxed(context: Context) -> Pin<Box<dyn Future<Output = ()> + Send>> {
845 Box::pin(async move { context.restart_io_if_running().await })
846}
847
848pub(crate) async fn add_pseudo_transport(context: &Context, addr: &str) -> Result<()> {
850 context.sql
851 .execute(
852 "INSERT INTO transports (addr, entered_param, configured_param) VALUES (?, ?, ?)",
853 (
854 addr,
855 serde_json::to_string(&EnteredLoginParam{addr: addr.to_string(), ..Default::default()})?,
856 format!(r#"{{"addr":"{addr}","imap":[],"imap_user":"","imap_password":"","smtp":[],"smtp_user":"","smtp_password":"","certificate_checks":"Automatic","oauth2":false}}"#)
857 ),
858 )
859 .await?;
860 Ok(())
861}
862
863#[cfg(test)]
864mod transport_tests;