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::events::EventType;
23use crate::login_param::EnteredLoginParam;
24use crate::net::load_connection_timestamp;
25use crate::provider::{Protocol, Provider, Socket, UsernamePattern, get_provider_by_id};
26use crate::sql::Sql;
27use crate::sync::{RemovedTransportData, SyncData, TransportData};
28
29#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
30pub(crate) enum ConnectionSecurity {
31 Tls,
33
34 Starttls,
36
37 Plain,
39}
40
41impl fmt::Display for ConnectionSecurity {
42 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
43 match self {
44 Self::Tls => write!(f, "tls")?,
45 Self::Starttls => write!(f, "starttls")?,
46 Self::Plain => write!(f, "plain")?,
47 }
48 Ok(())
49 }
50}
51
52impl TryFrom<Socket> for ConnectionSecurity {
53 type Error = anyhow::Error;
54
55 fn try_from(socket: Socket) -> Result<Self> {
56 match socket {
57 Socket::Automatic => Err(format_err!("Socket security is not configured")),
58 Socket::Ssl => Ok(Self::Tls),
59 Socket::Starttls => Ok(Self::Starttls),
60 Socket::Plain => Ok(Self::Plain),
61 }
62 }
63}
64
65#[derive(
67 Copy, Clone, Debug, Display, FromPrimitive, ToPrimitive, PartialEq, Eq, Serialize, Deserialize,
68)]
69#[repr(u32)]
70#[strum(serialize_all = "snake_case")]
71pub(crate) enum ConfiguredCertificateChecks {
72 OldAutomatic = 0,
84
85 Strict = 1,
87
88 AcceptInvalidCertificates = 2,
91
92 AcceptInvalidCertificates2 = 3,
97
98 Automatic = 4,
102}
103
104#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
105pub(crate) struct ConnectionCandidate {
106 pub host: String,
108
109 pub port: u16,
111
112 pub security: ConnectionSecurity,
114}
115
116impl fmt::Display for ConnectionCandidate {
117 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
118 write!(f, "{}:{}:{}", &self.host, self.port, self.security)?;
119 Ok(())
120 }
121}
122
123#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
124pub(crate) struct ConfiguredServerLoginParam {
125 pub connection: ConnectionCandidate,
126
127 pub user: String,
129}
130
131impl fmt::Display for ConfiguredServerLoginParam {
132 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
133 write!(f, "{}:{}", self.connection, &self.user)?;
134 Ok(())
135 }
136}
137
138pub(crate) async fn prioritize_server_login_params(
139 sql: &Sql,
140 params: &[ConfiguredServerLoginParam],
141 alpn: &str,
142) -> Result<Vec<ConfiguredServerLoginParam>> {
143 let mut res: Vec<(Option<i64>, ConfiguredServerLoginParam)> = Vec::with_capacity(params.len());
144 for param in params {
145 let timestamp = load_connection_timestamp(
146 sql,
147 alpn,
148 ¶m.connection.host,
149 param.connection.port,
150 None,
151 )
152 .await?;
153 res.push((timestamp, param.clone()));
154 }
155 res.sort_by_key(|(ts, _param)| std::cmp::Reverse(*ts));
156 Ok(res.into_iter().map(|(_ts, param)| param).collect())
157}
158
159#[derive(Debug, Clone, PartialEq, Eq)]
162pub(crate) struct ConfiguredLoginParam {
163 pub addr: String,
165
166 pub imap: Vec<ConfiguredServerLoginParam>,
167
168 pub imap_user: String,
173
174 pub imap_password: String,
175
176 pub smtp: Vec<ConfiguredServerLoginParam>,
177
178 pub smtp_user: String,
183
184 pub smtp_password: String,
185
186 pub provider: Option<&'static Provider>,
187
188 pub certificate_checks: ConfiguredCertificateChecks,
191
192 pub oauth2: bool,
194}
195
196#[derive(Debug, Serialize, Deserialize)]
199pub(crate) struct ConfiguredLoginParamJson {
200 pub addr: String,
201 pub imap: Vec<ConfiguredServerLoginParam>,
202 pub imap_user: String,
203 pub imap_password: String,
204 pub smtp: Vec<ConfiguredServerLoginParam>,
205 pub smtp_user: String,
206 pub smtp_password: String,
207 pub provider_id: Option<String>,
208 pub certificate_checks: ConfiguredCertificateChecks,
209 pub oauth2: bool,
210}
211
212impl fmt::Display for ConfiguredLoginParam {
213 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
214 let addr = &self.addr;
215 let provider_id = match self.provider {
216 Some(provider) => provider.id,
217 None => "none",
218 };
219 let certificate_checks = self.certificate_checks;
220 write!(f, "{addr} imap:[")?;
221 let mut first = true;
222 for imap in &self.imap {
223 if !first {
224 write!(f, ", ")?;
225 }
226 write!(f, "{imap}")?;
227 first = false;
228 }
229 write!(f, "] smtp:[")?;
230 let mut first = true;
231 for smtp in &self.smtp {
232 if !first {
233 write!(f, ", ")?;
234 }
235 write!(f, "{smtp}")?;
236 first = false;
237 }
238 write!(f, "] provider:{provider_id} cert_{certificate_checks}")?;
239 Ok(())
240 }
241}
242
243impl ConfiguredLoginParam {
244 pub(crate) async fn load(context: &Context) -> Result<Option<(u32, Self)>> {
250 let Some(self_addr) = context.get_config(Config::ConfiguredAddr).await? else {
251 return Ok(None);
252 };
253
254 let Some((id, json)) = context
255 .sql
256 .query_row_optional(
257 "SELECT id, configured_param FROM transports WHERE addr=?",
258 (&self_addr,),
259 |row| {
260 let id: u32 = row.get(0)?;
261 let json: String = row.get(1)?;
262 Ok((id, json))
263 },
264 )
265 .await?
266 else {
267 bail!("Self address {self_addr} doesn't have a corresponding transport");
268 };
269 Ok(Some((id, Self::from_json(&json)?)))
270 }
271
272 pub(crate) async fn load_all(context: &Context) -> Result<Vec<(u32, Self)>> {
277 context
278 .sql
279 .query_map_vec("SELECT id, configured_param FROM transports", (), |row| {
280 let id: u32 = row.get(0)?;
281 let json: String = row.get(1)?;
282 let param = Self::from_json(&json)?;
283 Ok((id, param))
284 })
285 .await
286 }
287
288 pub(crate) async fn load_legacy(context: &Context) -> Result<Option<Self>> {
290 if !context.get_config_bool(Config::Configured).await? {
291 return Ok(None);
292 }
293
294 let addr = context
295 .get_config(Config::ConfiguredAddr)
296 .await?
297 .unwrap_or_default()
298 .trim()
299 .to_string();
300
301 let certificate_checks: ConfiguredCertificateChecks = if let Some(certificate_checks) =
302 context
303 .get_config_parsed::<i32>(Config::ConfiguredImapCertificateChecks)
304 .await?
305 {
306 num_traits::FromPrimitive::from_i32(certificate_checks)
307 .context("Invalid configured_imap_certificate_checks value")?
308 } else {
309 ConfiguredCertificateChecks::OldAutomatic
312 };
313
314 let send_pw = context
315 .get_config(Config::ConfiguredSendPw)
316 .await?
317 .context("SMTP password is not configured")?;
318 let mail_pw = context
319 .get_config(Config::ConfiguredMailPw)
320 .await?
321 .context("IMAP password is not configured")?;
322
323 let server_flags = context
324 .get_config_parsed::<i32>(Config::ConfiguredServerFlags)
325 .await?
326 .unwrap_or_default();
327 let oauth2 = matches!(server_flags & DC_LP_AUTH_FLAGS, DC_LP_AUTH_OAUTH2);
328
329 let provider = context
330 .get_config(Config::ConfiguredProvider)
331 .await?
332 .and_then(|cfg| get_provider_by_id(&cfg));
333
334 let imap;
335 let smtp;
336
337 let mail_user = context
338 .get_config(Config::ConfiguredMailUser)
339 .await?
340 .unwrap_or_default();
341 let send_user = context
342 .get_config(Config::ConfiguredSendUser)
343 .await?
344 .unwrap_or_default();
345
346 if let Some(provider) = provider {
347 let parsed_addr = EmailAddress::new(&addr).context("Bad email-address")?;
348 let addr_localpart = parsed_addr.local;
349
350 if provider.server.is_empty() {
351 let servers = vec![
352 ServerParams {
353 protocol: Protocol::Imap,
354 hostname: context
355 .get_config(Config::ConfiguredMailServer)
356 .await?
357 .unwrap_or_default(),
358 port: context
359 .get_config_parsed::<u16>(Config::ConfiguredMailPort)
360 .await?
361 .unwrap_or_default(),
362 socket: context
363 .get_config_parsed::<i32>(Config::ConfiguredMailSecurity)
364 .await?
365 .and_then(num_traits::FromPrimitive::from_i32)
366 .unwrap_or_default(),
367 username: mail_user.clone(),
368 },
369 ServerParams {
370 protocol: Protocol::Smtp,
371 hostname: context
372 .get_config(Config::ConfiguredSendServer)
373 .await?
374 .unwrap_or_default(),
375 port: context
376 .get_config_parsed::<u16>(Config::ConfiguredSendPort)
377 .await?
378 .unwrap_or_default(),
379 socket: context
380 .get_config_parsed::<i32>(Config::ConfiguredSendSecurity)
381 .await?
382 .and_then(num_traits::FromPrimitive::from_i32)
383 .unwrap_or_default(),
384 username: send_user.clone(),
385 },
386 ];
387 let servers = expand_param_vector(servers, &addr, &parsed_addr.domain);
388 imap = servers
389 .iter()
390 .filter_map(|params| {
391 let Ok(security) = params.socket.try_into() else {
392 return None;
393 };
394 if params.protocol == Protocol::Imap {
395 Some(ConfiguredServerLoginParam {
396 connection: ConnectionCandidate {
397 host: params.hostname.clone(),
398 port: params.port,
399 security,
400 },
401 user: params.username.clone(),
402 })
403 } else {
404 None
405 }
406 })
407 .collect();
408 smtp = servers
409 .iter()
410 .filter_map(|params| {
411 let Ok(security) = params.socket.try_into() else {
412 return None;
413 };
414 if params.protocol == Protocol::Smtp {
415 Some(ConfiguredServerLoginParam {
416 connection: ConnectionCandidate {
417 host: params.hostname.clone(),
418 port: params.port,
419 security,
420 },
421 user: params.username.clone(),
422 })
423 } else {
424 None
425 }
426 })
427 .collect();
428 } else {
429 imap = provider
430 .server
431 .iter()
432 .filter_map(|server| {
433 if server.protocol != Protocol::Imap {
434 return None;
435 }
436
437 let Ok(security) = server.socket.try_into() else {
438 return None;
439 };
440
441 Some(ConfiguredServerLoginParam {
442 connection: ConnectionCandidate {
443 host: server.hostname.to_string(),
444 port: server.port,
445 security,
446 },
447 user: if !mail_user.is_empty() {
448 mail_user.clone()
449 } else {
450 match server.username_pattern {
451 UsernamePattern::Email => addr.to_string(),
452 UsernamePattern::Emaillocalpart => addr_localpart.clone(),
453 }
454 },
455 })
456 })
457 .collect();
458 smtp = provider
459 .server
460 .iter()
461 .filter_map(|server| {
462 if server.protocol != Protocol::Smtp {
463 return None;
464 }
465
466 let Ok(security) = server.socket.try_into() else {
467 return None;
468 };
469
470 Some(ConfiguredServerLoginParam {
471 connection: ConnectionCandidate {
472 host: server.hostname.to_string(),
473 port: server.port,
474 security,
475 },
476 user: if !send_user.is_empty() {
477 send_user.clone()
478 } else {
479 match server.username_pattern {
480 UsernamePattern::Email => addr.to_string(),
481 UsernamePattern::Emaillocalpart => addr_localpart.clone(),
482 }
483 },
484 })
485 })
486 .collect();
487 }
488 } else if let (Some(configured_mail_servers), Some(configured_send_servers)) = (
489 context.get_config(Config::ConfiguredImapServers).await?,
490 context.get_config(Config::ConfiguredSmtpServers).await?,
491 ) {
492 imap = serde_json::from_str(&configured_mail_servers)
493 .context("Failed to parse configured IMAP servers")?;
494 smtp = serde_json::from_str(&configured_send_servers)
495 .context("Failed to parse configured SMTP servers")?;
496 } else {
497 let mail_server = context
499 .get_config(Config::ConfiguredMailServer)
500 .await?
501 .unwrap_or_default();
502 let mail_port = context
503 .get_config_parsed::<u16>(Config::ConfiguredMailPort)
504 .await?
505 .unwrap_or_default();
506
507 let mail_security: Socket = context
508 .get_config_parsed::<i32>(Config::ConfiguredMailSecurity)
509 .await?
510 .and_then(num_traits::FromPrimitive::from_i32)
511 .unwrap_or_default();
512
513 let send_server = context
514 .get_config(Config::ConfiguredSendServer)
515 .await?
516 .context("SMTP server is not configured")?;
517 let send_port = context
518 .get_config_parsed::<u16>(Config::ConfiguredSendPort)
519 .await?
520 .unwrap_or_default();
521 let send_security: Socket = context
522 .get_config_parsed::<i32>(Config::ConfiguredSendSecurity)
523 .await?
524 .and_then(num_traits::FromPrimitive::from_i32)
525 .unwrap_or_default();
526
527 imap = vec![ConfiguredServerLoginParam {
528 connection: ConnectionCandidate {
529 host: mail_server,
530 port: mail_port,
531 security: mail_security.try_into()?,
532 },
533 user: mail_user.clone(),
534 }];
535 smtp = vec![ConfiguredServerLoginParam {
536 connection: ConnectionCandidate {
537 host: send_server,
538 port: send_port,
539 security: send_security.try_into()?,
540 },
541 user: send_user.clone(),
542 }];
543 }
544
545 Ok(Some(ConfiguredLoginParam {
546 addr,
547 imap,
548 imap_user: mail_user,
549 imap_password: mail_pw,
550 smtp,
551 smtp_user: send_user,
552 smtp_password: send_pw,
553 certificate_checks,
554 provider,
555 oauth2,
556 }))
557 }
558
559 pub(crate) async fn save_to_transports_table(
560 self,
561 context: &Context,
562 entered_param: &EnteredLoginParam,
563 timestamp: i64,
564 ) -> Result<()> {
565 save_transport(context, entered_param, &self.into(), timestamp).await?;
566 Ok(())
567 }
568
569 pub(crate) fn from_json(json: &str) -> Result<Self> {
570 let json: ConfiguredLoginParamJson = serde_json::from_str(json)?;
571
572 let provider = json.provider_id.and_then(|id| get_provider_by_id(&id));
573
574 Ok(ConfiguredLoginParam {
575 addr: json.addr,
576 imap: json.imap,
577 imap_user: json.imap_user,
578 imap_password: json.imap_password,
579 smtp: json.smtp,
580 smtp_user: json.smtp_user,
581 smtp_password: json.smtp_password,
582 provider,
583 certificate_checks: json.certificate_checks,
584 oauth2: json.oauth2,
585 })
586 }
587
588 pub(crate) fn into_json(self) -> Result<String> {
589 let json: ConfiguredLoginParamJson = self.into();
590 Ok(serde_json::to_string(&json)?)
591 }
592
593 pub(crate) fn strict_tls(&self, connected_through_proxy: bool) -> bool {
594 let provider_strict_tls = self.provider.map(|provider| provider.opt.strict_tls);
595 match self.certificate_checks {
596 ConfiguredCertificateChecks::OldAutomatic => {
597 provider_strict_tls.unwrap_or(connected_through_proxy)
598 }
599 ConfiguredCertificateChecks::Automatic => provider_strict_tls.unwrap_or(true),
600 ConfiguredCertificateChecks::Strict => true,
601 ConfiguredCertificateChecks::AcceptInvalidCertificates
602 | ConfiguredCertificateChecks::AcceptInvalidCertificates2 => false,
603 }
604 }
605}
606
607impl From<ConfiguredLoginParam> for ConfiguredLoginParamJson {
608 fn from(configured_login_param: ConfiguredLoginParam) -> Self {
609 Self {
610 addr: configured_login_param.addr,
611 imap: configured_login_param.imap,
612 imap_user: configured_login_param.imap_user,
613 imap_password: configured_login_param.imap_password,
614 smtp: configured_login_param.smtp,
615 smtp_user: configured_login_param.smtp_user,
616 smtp_password: configured_login_param.smtp_password,
617 provider_id: configured_login_param.provider.map(|p| p.id.to_string()),
618 certificate_checks: configured_login_param.certificate_checks,
619 oauth2: configured_login_param.oauth2,
620 }
621 }
622}
623
624pub(crate) async fn save_transport(
627 context: &Context,
628 entered_param: &EnteredLoginParam,
629 configured: &ConfiguredLoginParamJson,
630 add_timestamp: i64,
631) -> Result<bool> {
632 let addr = addr_normalize(&configured.addr);
633 let configured_addr = context.get_config(Config::ConfiguredAddr).await?;
634
635 let mut modified = context
636 .sql
637 .execute(
638 "INSERT INTO transports (addr, entered_param, configured_param, add_timestamp)
639 VALUES (?, ?, ?, ?)
640 ON CONFLICT (addr)
641 DO UPDATE SET entered_param=excluded.entered_param,
642 configured_param=excluded.configured_param,
643 add_timestamp=excluded.add_timestamp
644 WHERE entered_param != excluded.entered_param
645 OR configured_param != excluded.configured_param
646 OR add_timestamp < excluded.add_timestamp",
647 (
648 &addr,
649 serde_json::to_string(entered_param)?,
650 serde_json::to_string(configured)?,
651 add_timestamp,
652 ),
653 )
654 .await?
655 > 0;
656
657 if configured_addr.is_none() {
658 context
660 .sql
661 .set_raw_config(Config::ConfiguredAddr.as_ref(), Some(&addr))
662 .await?;
663 modified = true;
664 }
665 Ok(modified)
666}
667
668pub(crate) async fn send_sync_transports(context: &Context) -> Result<()> {
670 info!(context, "Sending transport synchronization message.");
671
672 let transports = context
683 .sql
684 .query_map_vec(
685 "SELECT entered_param, configured_param, add_timestamp
686 FROM transports WHERE id>1",
687 (),
688 |row| {
689 let entered_json: String = row.get(0)?;
690 let entered: EnteredLoginParam = serde_json::from_str(&entered_json)?;
691 let configured_json: String = row.get(1)?;
692 let configured: ConfiguredLoginParamJson = serde_json::from_str(&configured_json)?;
693 let timestamp: i64 = row.get(2)?;
694 Ok(TransportData {
695 configured,
696 entered,
697 timestamp,
698 })
699 },
700 )
701 .await?;
702 let removed_transports = context
703 .sql
704 .query_map_vec(
705 "SELECT addr, remove_timestamp FROM removed_transports",
706 (),
707 |row| {
708 let addr: String = row.get(0)?;
709 let timestamp: i64 = row.get(1)?;
710 Ok(RemovedTransportData { addr, timestamp })
711 },
712 )
713 .await?;
714 context
715 .add_sync_item(SyncData::Transports {
716 transports,
717 removed_transports,
718 })
719 .await?;
720 context.scheduler.interrupt_smtp().await;
721
722 Ok(())
723}
724
725pub(crate) async fn sync_transports(
727 context: &Context,
728 transports: &[TransportData],
729 removed_transports: &[RemovedTransportData],
730) -> Result<()> {
731 let mut modified = false;
732 for TransportData {
733 configured,
734 entered,
735 timestamp,
736 } in transports
737 {
738 modified |= save_transport(context, entered, configured, *timestamp).await?;
739 }
740
741 context
742 .sql
743 .transaction(|transaction| {
744 for RemovedTransportData { addr, timestamp } in removed_transports {
745 modified |= transaction.execute(
746 "DELETE FROM transports
747 WHERE addr=? AND add_timestamp<=?",
748 (addr, timestamp),
749 )? > 0;
750 transaction.execute(
751 "INSERT INTO removed_transports (addr, remove_timestamp)
752 VALUES (?, ?)
753 ON CONFLICT (addr) DO
754 UPDATE SET remove_timestamp = excluded.remove_timestamp
755 WHERE excluded.remove_timestamp > remove_timestamp",
756 (addr, timestamp),
757 )?;
758 }
759 Ok(())
760 })
761 .await?;
762
763 if modified {
764 tokio::task::spawn(restart_io_if_running_boxed(context.clone()));
765 context.emit_event(EventType::TransportsModified);
766 }
767 Ok(())
768}
769
770fn restart_io_if_running_boxed(context: Context) -> Pin<Box<dyn Future<Output = ()> + Send>> {
773 Box::pin(async move { context.restart_io_if_running().await })
774}
775
776pub(crate) async fn add_pseudo_transport(context: &Context, addr: &str) -> Result<()> {
778 context.sql
779 .execute(
780 "INSERT INTO transports (addr, entered_param, configured_param) VALUES (?, ?, ?)",
781 (
782 addr,
783 serde_json::to_string(&EnteredLoginParam::default())?,
784 format!(r#"{{"addr":"{addr}","imap":[],"imap_user":"","imap_password":"","smtp":[],"smtp_user":"","smtp_password":"","certificate_checks":"Automatic","oauth2":false}}"#)
785 ),
786 )
787 .await?;
788 Ok(())
789}
790
791#[cfg(test)]
792mod tests {
793 use super::*;
794 use crate::log::LogExt as _;
795 use crate::provider::get_provider_by_id;
796 use crate::test_utils::TestContext;
797 use crate::tools::time;
798
799 #[test]
800 fn test_configured_certificate_checks_display() {
801 use std::string::ToString;
802
803 assert_eq!(
804 "accept_invalid_certificates".to_string(),
805 ConfiguredCertificateChecks::AcceptInvalidCertificates.to_string()
806 );
807 }
808
809 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
810 async fn test_save_load_login_param() -> Result<()> {
811 let t = TestContext::new().await;
812
813 let param = ConfiguredLoginParam {
814 addr: "alice@example.org".to_string(),
815 imap: vec![ConfiguredServerLoginParam {
816 connection: ConnectionCandidate {
817 host: "imap.example.com".to_string(),
818 port: 123,
819 security: ConnectionSecurity::Starttls,
820 },
821 user: "alice".to_string(),
822 }],
823 imap_user: "".to_string(),
824 imap_password: "foo".to_string(),
825 smtp: vec![ConfiguredServerLoginParam {
826 connection: ConnectionCandidate {
827 host: "smtp.example.com".to_string(),
828 port: 456,
829 security: ConnectionSecurity::Tls,
830 },
831 user: "alice@example.org".to_string(),
832 }],
833 smtp_user: "".to_string(),
834 smtp_password: "bar".to_string(),
835 provider: None,
836 certificate_checks: ConfiguredCertificateChecks::Strict,
837 oauth2: false,
838 };
839
840 param
841 .clone()
842 .save_to_transports_table(&t, &EnteredLoginParam::default(), time())
843 .await?;
844 let expected_param = r#"{"addr":"alice@example.org","imap":[{"connection":{"host":"imap.example.com","port":123,"security":"Starttls"},"user":"alice"}],"imap_user":"","imap_password":"foo","smtp":[{"connection":{"host":"smtp.example.com","port":456,"security":"Tls"},"user":"alice@example.org"}],"smtp_user":"","smtp_password":"bar","provider_id":null,"certificate_checks":"Strict","oauth2":false}"#;
845 assert_eq!(
846 t.sql
847 .query_get_value::<String>("SELECT configured_param FROM transports", ())
848 .await?
849 .unwrap(),
850 expected_param
851 );
852 assert_eq!(t.is_configured().await?, true);
853 let (_transport_id, loaded) = ConfiguredLoginParam::load(&t).await?.unwrap();
854 assert_eq!(param, loaded);
855
856 t.set_config(Config::ConfiguredImapCertificateChecks, Some("999"))
858 .await?;
859 assert!(ConfiguredLoginParam::load(&t).await.is_ok());
860
861 let wrong_param = expected_param.replace("Strict", "Stricct");
863 assert_ne!(expected_param, wrong_param);
864 t.sql
865 .execute("UPDATE transports SET configured_param=?", (wrong_param,))
866 .await?;
867 assert!(ConfiguredLoginParam::load(&t).await.is_err());
868
869 Ok(())
870 }
871
872 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
873 async fn test_posteo_alias() -> Result<()> {
874 let t = TestContext::new().await;
875
876 let user = "alice@posteo.de";
877
878 t.set_config(Config::Configured, Some("1")).await?;
881 t.set_config(Config::ConfiguredProvider, Some("posteo"))
882 .await?;
883 t.sql
884 .set_raw_config(Config::ConfiguredAddr.as_ref(), Some("alice@posteo.at"))
885 .await?;
886 t.set_config(Config::ConfiguredMailServer, Some("posteo.de"))
887 .await?;
888 t.set_config(Config::ConfiguredMailPort, Some("993"))
889 .await?;
890 t.set_config(Config::ConfiguredMailSecurity, Some("1"))
891 .await?; t.set_config(Config::ConfiguredMailUser, Some(user)).await?;
893 t.set_config(Config::ConfiguredMailPw, Some("foobarbaz"))
894 .await?;
895 t.set_config(Config::ConfiguredImapCertificateChecks, Some("1"))
896 .await?; t.set_config(Config::ConfiguredSendServer, Some("posteo.de"))
898 .await?;
899 t.set_config(Config::ConfiguredSendPort, Some("465"))
900 .await?;
901 t.set_config(Config::ConfiguredSendSecurity, Some("1"))
902 .await?; t.set_config(Config::ConfiguredSendUser, Some(user)).await?;
904 t.set_config(Config::ConfiguredSendPw, Some("foobarbaz"))
905 .await?;
906 t.set_config(Config::ConfiguredSmtpCertificateChecks, Some("1"))
907 .await?; t.set_config(Config::ConfiguredServerFlags, Some("0"))
909 .await?;
910
911 let param = ConfiguredLoginParam {
912 addr: "alice@posteo.at".to_string(),
913 imap: vec![
914 ConfiguredServerLoginParam {
915 connection: ConnectionCandidate {
916 host: "posteo.de".to_string(),
917 port: 993,
918 security: ConnectionSecurity::Tls,
919 },
920 user: user.to_string(),
921 },
922 ConfiguredServerLoginParam {
923 connection: ConnectionCandidate {
924 host: "posteo.de".to_string(),
925 port: 143,
926 security: ConnectionSecurity::Starttls,
927 },
928 user: user.to_string(),
929 },
930 ],
931 imap_user: "alice@posteo.de".to_string(),
932 imap_password: "foobarbaz".to_string(),
933 smtp: vec![
934 ConfiguredServerLoginParam {
935 connection: ConnectionCandidate {
936 host: "posteo.de".to_string(),
937 port: 465,
938 security: ConnectionSecurity::Tls,
939 },
940 user: user.to_string(),
941 },
942 ConfiguredServerLoginParam {
943 connection: ConnectionCandidate {
944 host: "posteo.de".to_string(),
945 port: 587,
946 security: ConnectionSecurity::Starttls,
947 },
948 user: user.to_string(),
949 },
950 ],
951 smtp_user: "alice@posteo.de".to_string(),
952 smtp_password: "foobarbaz".to_string(),
953 provider: get_provider_by_id("posteo"),
954 certificate_checks: ConfiguredCertificateChecks::Strict,
955 oauth2: false,
956 };
957
958 let loaded = ConfiguredLoginParam::load_legacy(&t).await?.unwrap();
959 assert_eq!(loaded, param);
960
961 migrate_configured_login_param(&t).await;
962 let (_transport_id, loaded) = ConfiguredLoginParam::load(&t).await?.unwrap();
963 assert_eq!(loaded, param);
964
965 Ok(())
966 }
967
968 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
969 async fn test_empty_server_list_legacy() -> Result<()> {
970 let (domain, provider) = crate::provider::data::PROVIDER_DATA
974 .iter()
975 .find(|(_domain, provider)| provider.server.is_empty())
976 .unwrap();
977
978 let t = TestContext::new().await;
979
980 let addr = format!("alice@{domain}");
981
982 t.set_config(Config::Configured, Some("1")).await?;
983 t.set_config(Config::ConfiguredProvider, Some(provider.id))
984 .await?;
985 t.sql
986 .set_raw_config(Config::ConfiguredAddr.as_ref(), Some(&addr))
987 .await?;
988 t.set_config(Config::ConfiguredMailPw, Some("foobarbaz"))
989 .await?;
990 t.set_config(Config::ConfiguredImapCertificateChecks, Some("1"))
991 .await?; t.set_config(Config::ConfiguredSendPw, Some("foobarbaz"))
993 .await?;
994 t.set_config(Config::ConfiguredSmtpCertificateChecks, Some("1"))
995 .await?; t.set_config(Config::ConfiguredServerFlags, Some("0"))
997 .await?;
998
999 let loaded = ConfiguredLoginParam::load_legacy(&t).await?.unwrap();
1000 assert_eq!(loaded.provider, Some(*provider));
1001 assert_eq!(loaded.imap.is_empty(), false);
1002 assert_eq!(loaded.smtp.is_empty(), false);
1003
1004 migrate_configured_login_param(&t).await;
1005
1006 let (_transport_id, loaded) = ConfiguredLoginParam::load(&t).await?.unwrap();
1007 assert_eq!(loaded.provider, Some(*provider));
1008 assert_eq!(loaded.imap.is_empty(), false);
1009 assert_eq!(loaded.smtp.is_empty(), false);
1010
1011 Ok(())
1012 }
1013
1014 async fn migrate_configured_login_param(t: &TestContext) {
1015 t.sql.execute("DROP TABLE transports;", ()).await.unwrap();
1016 t.sql.set_raw_config_int("dbversion", 130).await.unwrap();
1017 t.sql.run_migrations(t).await.log_err(t).ok();
1018 }
1019
1020 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
1021 async fn test_empty_server_list() -> Result<()> {
1022 let (domain, provider) = crate::provider::data::PROVIDER_DATA
1026 .iter()
1027 .find(|(_domain, provider)| provider.server.is_empty())
1028 .unwrap();
1029
1030 let t = TestContext::new().await;
1031
1032 let addr = format!("alice@{domain}");
1033
1034 ConfiguredLoginParam {
1035 addr: addr.clone(),
1036 imap: vec![ConfiguredServerLoginParam {
1037 connection: ConnectionCandidate {
1038 host: "example.org".to_string(),
1039 port: 100,
1040 security: ConnectionSecurity::Tls,
1041 },
1042 user: addr.clone(),
1043 }],
1044 imap_user: addr.clone(),
1045 imap_password: "foobarbaz".to_string(),
1046 smtp: vec![ConfiguredServerLoginParam {
1047 connection: ConnectionCandidate {
1048 host: "example.org".to_string(),
1049 port: 100,
1050 security: ConnectionSecurity::Tls,
1051 },
1052 user: addr.clone(),
1053 }],
1054 smtp_user: addr.clone(),
1055 smtp_password: "foobarbaz".to_string(),
1056 provider: Some(provider),
1057 certificate_checks: ConfiguredCertificateChecks::Automatic,
1058 oauth2: false,
1059 }
1060 .save_to_transports_table(&t, &EnteredLoginParam::default(), time())
1061 .await?;
1062
1063 let (_transport_id, loaded) = ConfiguredLoginParam::load(&t).await?.unwrap();
1064 assert_eq!(loaded.provider, Some(*provider));
1065 assert_eq!(loaded.imap.is_empty(), false);
1066 assert_eq!(loaded.smtp.is_empty(), false);
1067 assert_eq!(t.get_configured_provider().await?, Some(*provider));
1068
1069 Ok(())
1070 }
1071}