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, &self.user)?;
135 Ok(())
136 }
137}
138
139pub(crate) async fn prioritize_server_login_params(
140 sql: &Sql,
141 params: &[ConfiguredServerLoginParam],
142 alpn: &str,
143) -> Result<Vec<ConfiguredServerLoginParam>> {
144 let mut res: Vec<(Option<i64>, ConfiguredServerLoginParam)> = Vec::with_capacity(params.len());
145 for param in params {
146 let timestamp = load_connection_timestamp(
147 sql,
148 alpn,
149 ¶m.connection.host,
150 param.connection.port,
151 None,
152 )
153 .await?;
154 res.push((timestamp, param.clone()));
155 }
156 res.sort_by_key(|(ts, _param)| std::cmp::Reverse(*ts));
157 Ok(res.into_iter().map(|(_ts, param)| param).collect())
158}
159
160#[derive(Debug, Clone, PartialEq, Eq)]
163pub(crate) struct ConfiguredLoginParam {
164 pub addr: String,
166
167 pub imap: Vec<ConfiguredServerLoginParam>,
169
170 pub imap_user: String,
175
176 pub imap_password: String,
177
178 pub imap_folder: Option<String>,
183
184 pub smtp: Vec<ConfiguredServerLoginParam>,
186
187 pub smtp_user: String,
192
193 pub smtp_password: String,
194
195 pub provider: Option<&'static Provider>,
196
197 pub certificate_checks: ConfiguredCertificateChecks,
200
201 pub oauth2: bool,
203}
204
205#[derive(Debug, Serialize, Deserialize)]
208pub(crate) struct ConfiguredLoginParamJson {
209 pub addr: String,
210 pub imap: Vec<ConfiguredServerLoginParam>,
211
212 #[serde(skip_serializing_if = "Option::is_none")]
216 pub imap_folder: Option<String>,
217
218 pub imap_user: String,
219 pub imap_password: String,
220 pub smtp: Vec<ConfiguredServerLoginParam>,
221 pub smtp_user: String,
222 pub smtp_password: String,
223 pub provider_id: Option<String>,
224 pub certificate_checks: ConfiguredCertificateChecks,
225 pub oauth2: bool,
226}
227
228impl fmt::Display for ConfiguredLoginParam {
229 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
230 let addr = &self.addr;
231 let provider_id = match self.provider {
232 Some(provider) => provider.id,
233 None => "none",
234 };
235 let certificate_checks = self.certificate_checks;
236 write!(f, "{addr} imap:[")?;
237 let mut first = true;
238 for imap in &self.imap {
239 if !first {
240 write!(f, ", ")?;
241 }
242 write!(f, "{imap}")?;
243 first = false;
244 }
245 write!(f, "] smtp:[")?;
246 let mut first = true;
247 for smtp in &self.smtp {
248 if !first {
249 write!(f, ", ")?;
250 }
251 write!(f, "{smtp}")?;
252 first = false;
253 }
254 write!(f, "] provider:{provider_id} cert_{certificate_checks}")?;
255 Ok(())
256 }
257}
258
259impl ConfiguredLoginParam {
260 pub(crate) async fn load(context: &Context) -> Result<Option<(u32, Self)>> {
266 let Some(self_addr) = context.get_config(Config::ConfiguredAddr).await? else {
267 return Ok(None);
268 };
269
270 let Some((id, json)) = context
271 .sql
272 .query_row_optional(
273 "SELECT id, configured_param FROM transports WHERE addr=?",
274 (&self_addr,),
275 |row| {
276 let id: u32 = row.get(0)?;
277 let json: String = row.get(1)?;
278 Ok((id, json))
279 },
280 )
281 .await?
282 else {
283 bail!("Self address {self_addr} doesn't have a corresponding transport");
284 };
285 Ok(Some((id, Self::from_json(&json)?)))
286 }
287
288 pub(crate) async fn load_all(context: &Context) -> Result<Vec<(u32, Self)>> {
293 context
294 .sql
295 .query_map_vec("SELECT id, configured_param FROM transports", (), |row| {
296 let id: u32 = row.get(0)?;
297 let json: String = row.get(1)?;
298 let param = Self::from_json(&json)?;
299 Ok((id, param))
300 })
301 .await
302 }
303
304 pub(crate) async fn load_legacy(context: &Context) -> Result<Option<Self>> {
306 if !context.get_config_bool(Config::Configured).await? {
307 return Ok(None);
308 }
309
310 let addr = context
311 .get_config(Config::ConfiguredAddr)
312 .await?
313 .unwrap_or_default()
314 .trim()
315 .to_string();
316
317 let certificate_checks: ConfiguredCertificateChecks = if let Some(certificate_checks) =
318 context
319 .get_config_parsed::<i32>(Config::ConfiguredImapCertificateChecks)
320 .await?
321 {
322 num_traits::FromPrimitive::from_i32(certificate_checks)
323 .context("Invalid configured_imap_certificate_checks value")?
324 } else {
325 ConfiguredCertificateChecks::OldAutomatic
328 };
329
330 let send_pw = context
331 .get_config(Config::ConfiguredSendPw)
332 .await?
333 .context("SMTP password is not configured")?;
334 let mail_pw = context
335 .get_config(Config::ConfiguredMailPw)
336 .await?
337 .context("IMAP password is not configured")?;
338
339 let server_flags = context
340 .get_config_parsed::<i32>(Config::ConfiguredServerFlags)
341 .await?
342 .unwrap_or_default();
343 let oauth2 = matches!(server_flags & DC_LP_AUTH_FLAGS, DC_LP_AUTH_OAUTH2);
344
345 let provider = context
346 .get_config(Config::ConfiguredProvider)
347 .await?
348 .and_then(|cfg| get_provider_by_id(&cfg));
349
350 let imap;
351 let smtp;
352
353 let mail_user = context
354 .get_config(Config::ConfiguredMailUser)
355 .await?
356 .unwrap_or_default();
357 let send_user = context
358 .get_config(Config::ConfiguredSendUser)
359 .await?
360 .unwrap_or_default();
361
362 if let Some(provider) = provider {
363 let parsed_addr = EmailAddress::new(&addr).context("Bad email-address")?;
364 let addr_localpart = parsed_addr.local;
365
366 if provider.server.is_empty() {
367 let servers = vec![
368 ServerParams {
369 protocol: Protocol::Imap,
370 hostname: context
371 .get_config(Config::ConfiguredMailServer)
372 .await?
373 .unwrap_or_default(),
374 port: context
375 .get_config_parsed::<u16>(Config::ConfiguredMailPort)
376 .await?
377 .unwrap_or_default(),
378 socket: context
379 .get_config_parsed::<i32>(Config::ConfiguredMailSecurity)
380 .await?
381 .and_then(num_traits::FromPrimitive::from_i32)
382 .unwrap_or_default(),
383 username: mail_user.clone(),
384 },
385 ServerParams {
386 protocol: Protocol::Smtp,
387 hostname: context
388 .get_config(Config::ConfiguredSendServer)
389 .await?
390 .unwrap_or_default(),
391 port: context
392 .get_config_parsed::<u16>(Config::ConfiguredSendPort)
393 .await?
394 .unwrap_or_default(),
395 socket: context
396 .get_config_parsed::<i32>(Config::ConfiguredSendSecurity)
397 .await?
398 .and_then(num_traits::FromPrimitive::from_i32)
399 .unwrap_or_default(),
400 username: send_user.clone(),
401 },
402 ];
403 let servers = expand_param_vector(servers, &addr, &parsed_addr.domain);
404 imap = servers
405 .iter()
406 .filter_map(|params| {
407 let Ok(security) = params.socket.try_into() else {
408 return None;
409 };
410 if params.protocol == Protocol::Imap {
411 Some(ConfiguredServerLoginParam {
412 connection: ConnectionCandidate {
413 host: params.hostname.clone(),
414 port: params.port,
415 security,
416 },
417 user: params.username.clone(),
418 })
419 } else {
420 None
421 }
422 })
423 .collect();
424 smtp = servers
425 .iter()
426 .filter_map(|params| {
427 let Ok(security) = params.socket.try_into() else {
428 return None;
429 };
430 if params.protocol == Protocol::Smtp {
431 Some(ConfiguredServerLoginParam {
432 connection: ConnectionCandidate {
433 host: params.hostname.clone(),
434 port: params.port,
435 security,
436 },
437 user: params.username.clone(),
438 })
439 } else {
440 None
441 }
442 })
443 .collect();
444 } else {
445 imap = provider
446 .server
447 .iter()
448 .filter_map(|server| {
449 if server.protocol != Protocol::Imap {
450 return None;
451 }
452
453 let Ok(security) = server.socket.try_into() else {
454 return None;
455 };
456
457 Some(ConfiguredServerLoginParam {
458 connection: ConnectionCandidate {
459 host: server.hostname.to_string(),
460 port: server.port,
461 security,
462 },
463 user: if !mail_user.is_empty() {
464 mail_user.clone()
465 } else {
466 match server.username_pattern {
467 UsernamePattern::Email => addr.to_string(),
468 UsernamePattern::Emaillocalpart => addr_localpart.clone(),
469 }
470 },
471 })
472 })
473 .collect();
474 smtp = provider
475 .server
476 .iter()
477 .filter_map(|server| {
478 if server.protocol != Protocol::Smtp {
479 return None;
480 }
481
482 let Ok(security) = server.socket.try_into() else {
483 return None;
484 };
485
486 Some(ConfiguredServerLoginParam {
487 connection: ConnectionCandidate {
488 host: server.hostname.to_string(),
489 port: server.port,
490 security,
491 },
492 user: if !send_user.is_empty() {
493 send_user.clone()
494 } else {
495 match server.username_pattern {
496 UsernamePattern::Email => addr.to_string(),
497 UsernamePattern::Emaillocalpart => addr_localpart.clone(),
498 }
499 },
500 })
501 })
502 .collect();
503 }
504 } else if let (Some(configured_mail_servers), Some(configured_send_servers)) = (
505 context.get_config(Config::ConfiguredImapServers).await?,
506 context.get_config(Config::ConfiguredSmtpServers).await?,
507 ) {
508 imap = serde_json::from_str(&configured_mail_servers)
509 .context("Failed to parse configured IMAP servers")?;
510 smtp = serde_json::from_str(&configured_send_servers)
511 .context("Failed to parse configured SMTP servers")?;
512 } else {
513 let mail_server = context
515 .get_config(Config::ConfiguredMailServer)
516 .await?
517 .unwrap_or_default();
518 let mail_port = context
519 .get_config_parsed::<u16>(Config::ConfiguredMailPort)
520 .await?
521 .unwrap_or_default();
522
523 let mail_security: Socket = context
524 .get_config_parsed::<i32>(Config::ConfiguredMailSecurity)
525 .await?
526 .and_then(num_traits::FromPrimitive::from_i32)
527 .unwrap_or_default();
528
529 let send_server = context
530 .get_config(Config::ConfiguredSendServer)
531 .await?
532 .context("SMTP server is not configured")?;
533 let send_port = context
534 .get_config_parsed::<u16>(Config::ConfiguredSendPort)
535 .await?
536 .unwrap_or_default();
537 let send_security: Socket = context
538 .get_config_parsed::<i32>(Config::ConfiguredSendSecurity)
539 .await?
540 .and_then(num_traits::FromPrimitive::from_i32)
541 .unwrap_or_default();
542
543 imap = vec![ConfiguredServerLoginParam {
544 connection: ConnectionCandidate {
545 host: mail_server,
546 port: mail_port,
547 security: mail_security.try_into()?,
548 },
549 user: mail_user.clone(),
550 }];
551 smtp = vec![ConfiguredServerLoginParam {
552 connection: ConnectionCandidate {
553 host: send_server,
554 port: send_port,
555 security: send_security.try_into()?,
556 },
557 user: send_user.clone(),
558 }];
559 }
560
561 Ok(Some(ConfiguredLoginParam {
562 addr,
563 imap,
564 imap_folder: None,
565 imap_user: mail_user,
566 imap_password: mail_pw,
567 smtp,
568 smtp_user: send_user,
569 smtp_password: send_pw,
570 certificate_checks,
571 provider,
572 oauth2,
573 }))
574 }
575
576 pub(crate) async fn save_to_transports_table(
577 self,
578 context: &Context,
579 entered_param: &EnteredLoginParam,
580 timestamp: i64,
581 ) -> Result<()> {
582 let is_published = true;
583 save_transport(
584 context,
585 entered_param,
586 &self.into(),
587 timestamp,
588 is_published,
589 )
590 .await?;
591 Ok(())
592 }
593
594 pub(crate) fn from_json(json: &str) -> Result<Self> {
595 let json: ConfiguredLoginParamJson = serde_json::from_str(json)?;
596
597 ensure_and_debug_assert!(
598 json.imap_folder
599 .as_ref()
600 .is_none_or(|folder| !folder.is_empty()),
601 "Configured watched folder name cannot be empty"
602 );
603 let provider = json.provider_id.and_then(|id| get_provider_by_id(&id));
604
605 Ok(ConfiguredLoginParam {
606 addr: json.addr,
607 imap: json.imap,
608 imap_folder: json.imap_folder,
609 imap_user: json.imap_user,
610 imap_password: json.imap_password,
611 smtp: json.smtp,
612 smtp_user: json.smtp_user,
613 smtp_password: json.smtp_password,
614 provider,
615 certificate_checks: json.certificate_checks,
616 oauth2: json.oauth2,
617 })
618 }
619
620 pub(crate) fn into_json(self) -> Result<String> {
621 let json: ConfiguredLoginParamJson = self.into();
622 Ok(serde_json::to_string(&json)?)
623 }
624
625 pub(crate) fn strict_tls(&self, connected_through_proxy: bool) -> bool {
626 let provider_strict_tls = self.provider.map(|provider| provider.opt.strict_tls);
627 match self.certificate_checks {
628 ConfiguredCertificateChecks::OldAutomatic => {
629 provider_strict_tls.unwrap_or(connected_through_proxy)
630 }
631 ConfiguredCertificateChecks::Automatic => provider_strict_tls.unwrap_or(true),
632 ConfiguredCertificateChecks::Strict => true,
633 ConfiguredCertificateChecks::AcceptInvalidCertificates
634 | ConfiguredCertificateChecks::AcceptInvalidCertificates2 => false,
635 }
636 }
637}
638
639impl From<ConfiguredLoginParam> for ConfiguredLoginParamJson {
640 fn from(configured_login_param: ConfiguredLoginParam) -> Self {
641 Self {
642 addr: configured_login_param.addr,
643 imap: configured_login_param.imap,
644 imap_user: configured_login_param.imap_user,
645 imap_password: configured_login_param.imap_password,
646 imap_folder: configured_login_param.imap_folder,
647 smtp: configured_login_param.smtp,
648 smtp_user: configured_login_param.smtp_user,
649 smtp_password: configured_login_param.smtp_password,
650 provider_id: configured_login_param.provider.map(|p| p.id.to_string()),
651 certificate_checks: configured_login_param.certificate_checks,
652 oauth2: configured_login_param.oauth2,
653 }
654 }
655}
656
657pub(crate) async fn save_transport(
660 context: &Context,
661 entered_param: &EnteredLoginParam,
662 configured: &ConfiguredLoginParamJson,
663 add_timestamp: i64,
664 is_published: bool,
665) -> Result<bool> {
666 ensure_and_debug_assert!(
667 configured
668 .imap_folder
669 .as_ref()
670 .is_none_or(|folder| !folder.is_empty()),
671 "Configured watched folder name cannot be empty"
672 );
673
674 let addr = addr_normalize(&configured.addr);
675 let configured_addr = context.get_config(Config::ConfiguredAddr).await?;
676 let mut modified = context
677 .sql
678 .execute(
679 "INSERT INTO transports (addr, entered_param, configured_param, add_timestamp, is_published)
680 VALUES (?, ?, ?, ?, ?)
681 ON CONFLICT (addr)
682 DO UPDATE SET entered_param=excluded.entered_param,
683 configured_param=excluded.configured_param,
684 add_timestamp=excluded.add_timestamp,
685 is_published=excluded.is_published
686 WHERE entered_param != excluded.entered_param
687 OR configured_param != excluded.configured_param
688 OR add_timestamp < excluded.add_timestamp
689 OR is_published != excluded.is_published",
690 (
691 &addr,
692 serde_json::to_string(entered_param)?,
693 serde_json::to_string(configured)?,
694 add_timestamp,
695 is_published,
696 ),
697 )
698 .await?
699 > 0;
700
701 if configured_addr.is_none() {
702 context
704 .sql
705 .set_raw_config(Config::ConfiguredAddr.as_ref(), Some(&addr))
706 .await?;
707 modified = true;
708 }
709 Ok(modified)
710}
711
712pub(crate) async fn send_sync_transports(context: &Context) -> Result<()> {
714 info!(context, "Sending transport synchronization message.");
715
716 context.self_public_key.lock().await.take();
718
719 let transports = context
730 .sql
731 .query_map_vec(
732 "SELECT entered_param, configured_param, add_timestamp, is_published
733 FROM transports WHERE id>1",
734 (),
735 |row| {
736 let entered_json: String = row.get(0)?;
737 let entered: EnteredLoginParam = serde_json::from_str(&entered_json)?;
738 let configured_json: String = row.get(1)?;
739 let configured: ConfiguredLoginParamJson = serde_json::from_str(&configured_json)?;
740 let timestamp: i64 = row.get(2)?;
741 let is_published: bool = row.get(3)?;
742 Ok(TransportData {
743 configured,
744 entered,
745 timestamp,
746 is_published,
747 })
748 },
749 )
750 .await?;
751 let removed_transports = context
752 .sql
753 .query_map_vec(
754 "SELECT addr, remove_timestamp FROM removed_transports",
755 (),
756 |row| {
757 let addr: String = row.get(0)?;
758 let timestamp: i64 = row.get(1)?;
759 Ok(RemovedTransportData { addr, timestamp })
760 },
761 )
762 .await?;
763 context
764 .add_sync_item(SyncData::Transports {
765 transports,
766 removed_transports,
767 })
768 .await?;
769 context.scheduler.interrupt_smtp().await;
770
771 Ok(())
772}
773
774pub(crate) async fn sync_transports(
776 context: &Context,
777 transports: &[TransportData],
778 removed_transports: &[RemovedTransportData],
779) -> Result<()> {
780 let mut modified = false;
781 for TransportData {
782 configured,
783 entered,
784 timestamp,
785 is_published,
786 } in transports
787 {
788 modified |= save_transport(context, entered, configured, *timestamp, *is_published).await?;
789 }
790
791 context
792 .sql
793 .transaction(|transaction| {
794 for RemovedTransportData { addr, timestamp } in removed_transports {
795 modified |= transaction.execute(
796 "DELETE FROM transports
797 WHERE addr=? AND add_timestamp<=?",
798 (addr, timestamp),
799 )? > 0;
800 transaction.execute(
801 "INSERT INTO removed_transports (addr, remove_timestamp)
802 VALUES (?, ?)
803 ON CONFLICT (addr) DO
804 UPDATE SET remove_timestamp = excluded.remove_timestamp
805 WHERE excluded.remove_timestamp > remove_timestamp",
806 (addr, timestamp),
807 )?;
808 }
809 Ok(())
810 })
811 .await?;
812
813 if modified {
814 context.self_public_key.lock().await.take();
815 tokio::task::spawn(restart_io_if_running_boxed(context.clone()));
816 context.emit_event(EventType::TransportsModified);
817 }
818 Ok(())
819}
820
821fn restart_io_if_running_boxed(context: Context) -> Pin<Box<dyn Future<Output = ()> + Send>> {
824 Box::pin(async move { context.restart_io_if_running().await })
825}
826
827pub(crate) async fn add_pseudo_transport(context: &Context, addr: &str) -> Result<()> {
829 context.sql
830 .execute(
831 "INSERT INTO transports (addr, entered_param, configured_param) VALUES (?, ?, ?)",
832 (
833 addr,
834 serde_json::to_string(&EnteredLoginParam{addr: addr.to_string(), ..Default::default()})?,
835 format!(r#"{{"addr":"{addr}","imap":[],"imap_user":"","imap_password":"","smtp":[],"smtp_user":"","smtp_password":"","certificate_checks":"Automatic","oauth2":false}}"#)
836 ),
837 )
838 .await?;
839 Ok(())
840}
841
842#[cfg(test)]
843mod transport_tests;