1use std::fmt;
12
13use anyhow::{Context as _, Result, bail, ensure, format_err};
14use deltachat_contact_tools::{EmailAddress, addr_cmp, addr_normalize};
15use serde::{Deserialize, Serialize};
16
17use crate::config::Config;
18use crate::configure::server_params::{ServerParams, expand_param_vector};
19use crate::constants::{DC_LP_AUTH_FLAGS, DC_LP_AUTH_OAUTH2};
20use crate::context::Context;
21use crate::login_param::EnteredLoginParam;
22use crate::net::load_connection_timestamp;
23use crate::provider::{Protocol, Provider, Socket, UsernamePattern, get_provider_by_id};
24use crate::sql::Sql;
25
26#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
27pub(crate) enum ConnectionSecurity {
28 Tls,
30
31 Starttls,
33
34 Plain,
36}
37
38impl fmt::Display for ConnectionSecurity {
39 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
40 match self {
41 Self::Tls => write!(f, "tls")?,
42 Self::Starttls => write!(f, "starttls")?,
43 Self::Plain => write!(f, "plain")?,
44 }
45 Ok(())
46 }
47}
48
49impl TryFrom<Socket> for ConnectionSecurity {
50 type Error = anyhow::Error;
51
52 fn try_from(socket: Socket) -> Result<Self> {
53 match socket {
54 Socket::Automatic => Err(format_err!("Socket security is not configured")),
55 Socket::Ssl => Ok(Self::Tls),
56 Socket::Starttls => Ok(Self::Starttls),
57 Socket::Plain => Ok(Self::Plain),
58 }
59 }
60}
61
62#[derive(
64 Copy, Clone, Debug, Display, FromPrimitive, ToPrimitive, PartialEq, Eq, Serialize, Deserialize,
65)]
66#[repr(u32)]
67#[strum(serialize_all = "snake_case")]
68pub(crate) enum ConfiguredCertificateChecks {
69 OldAutomatic = 0,
81
82 Strict = 1,
84
85 AcceptInvalidCertificates = 2,
88
89 AcceptInvalidCertificates2 = 3,
94
95 Automatic = 4,
99}
100
101#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
102pub(crate) struct ConnectionCandidate {
103 pub host: String,
105
106 pub port: u16,
108
109 pub security: ConnectionSecurity,
111}
112
113impl fmt::Display for ConnectionCandidate {
114 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
115 write!(f, "{}:{}:{}", &self.host, self.port, self.security)?;
116 Ok(())
117 }
118}
119
120#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
121pub(crate) struct ConfiguredServerLoginParam {
122 pub connection: ConnectionCandidate,
123
124 pub user: String,
126}
127
128impl fmt::Display for ConfiguredServerLoginParam {
129 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
130 write!(f, "{}:{}", self.connection, &self.user)?;
131 Ok(())
132 }
133}
134
135pub(crate) async fn prioritize_server_login_params(
136 sql: &Sql,
137 params: &[ConfiguredServerLoginParam],
138 alpn: &str,
139) -> Result<Vec<ConfiguredServerLoginParam>> {
140 let mut res: Vec<(Option<i64>, ConfiguredServerLoginParam)> = Vec::with_capacity(params.len());
141 for param in params {
142 let timestamp = load_connection_timestamp(
143 sql,
144 alpn,
145 ¶m.connection.host,
146 param.connection.port,
147 None,
148 )
149 .await?;
150 res.push((timestamp, param.clone()));
151 }
152 res.sort_by_key(|(ts, _param)| std::cmp::Reverse(*ts));
153 Ok(res.into_iter().map(|(_ts, param)| param).collect())
154}
155
156#[derive(Debug, Clone, PartialEq, Eq)]
159pub(crate) struct ConfiguredLoginParam {
160 pub addr: String,
162
163 pub imap: Vec<ConfiguredServerLoginParam>,
164
165 pub imap_user: String,
170
171 pub imap_password: String,
172
173 pub smtp: Vec<ConfiguredServerLoginParam>,
174
175 pub smtp_user: String,
180
181 pub smtp_password: String,
182
183 pub provider: Option<&'static Provider>,
184
185 pub certificate_checks: ConfiguredCertificateChecks,
188
189 pub oauth2: bool,
191}
192
193#[derive(Debug, Serialize, Deserialize)]
196struct ConfiguredLoginParamJson {
197 pub addr: String,
198 pub imap: Vec<ConfiguredServerLoginParam>,
199 pub imap_user: String,
200 pub imap_password: String,
201 pub smtp: Vec<ConfiguredServerLoginParam>,
202 pub smtp_user: String,
203 pub smtp_password: String,
204 pub provider_id: Option<String>,
205 pub certificate_checks: ConfiguredCertificateChecks,
206 pub oauth2: bool,
207}
208
209impl fmt::Display for ConfiguredLoginParam {
210 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
211 let addr = &self.addr;
212 let provider_id = match self.provider {
213 Some(provider) => provider.id,
214 None => "none",
215 };
216 let certificate_checks = self.certificate_checks;
217 write!(f, "{addr} imap:[")?;
218 let mut first = true;
219 for imap in &self.imap {
220 if !first {
221 write!(f, ", ")?;
222 }
223 write!(f, "{imap}")?;
224 first = false;
225 }
226 write!(f, "] smtp:[")?;
227 let mut first = true;
228 for smtp in &self.smtp {
229 if !first {
230 write!(f, ", ")?;
231 }
232 write!(f, "{smtp}")?;
233 first = false;
234 }
235 write!(f, "] provider:{provider_id} cert_{certificate_checks}")?;
236 Ok(())
237 }
238}
239
240impl ConfiguredLoginParam {
241 pub(crate) async fn load(context: &Context) -> Result<Option<Self>> {
245 let Some(self_addr) = context.get_config(Config::ConfiguredAddr).await? else {
246 return Ok(None);
247 };
248
249 let json: Option<String> = context
250 .sql
251 .query_get_value(
252 "SELECT configured_param FROM transports WHERE addr=?",
253 (&self_addr,),
254 )
255 .await?;
256 if let Some(json) = json {
257 Ok(Some(Self::from_json(&json)?))
258 } else {
259 bail!("Self address {self_addr} doesn't have a corresponding transport");
260 }
261 }
262
263 pub(crate) async fn load_legacy(context: &Context) -> Result<Option<Self>> {
265 if !context.get_config_bool(Config::Configured).await? {
266 return Ok(None);
267 }
268
269 let addr = context
270 .get_config(Config::ConfiguredAddr)
271 .await?
272 .unwrap_or_default()
273 .trim()
274 .to_string();
275
276 let certificate_checks: ConfiguredCertificateChecks = if let Some(certificate_checks) =
277 context
278 .get_config_parsed::<i32>(Config::ConfiguredImapCertificateChecks)
279 .await?
280 {
281 num_traits::FromPrimitive::from_i32(certificate_checks)
282 .context("Invalid configured_imap_certificate_checks value")?
283 } else {
284 ConfiguredCertificateChecks::OldAutomatic
287 };
288
289 let send_pw = context
290 .get_config(Config::ConfiguredSendPw)
291 .await?
292 .context("SMTP password is not configured")?;
293 let mail_pw = context
294 .get_config(Config::ConfiguredMailPw)
295 .await?
296 .context("IMAP password is not configured")?;
297
298 let server_flags = context
299 .get_config_parsed::<i32>(Config::ConfiguredServerFlags)
300 .await?
301 .unwrap_or_default();
302 let oauth2 = matches!(server_flags & DC_LP_AUTH_FLAGS, DC_LP_AUTH_OAUTH2);
303
304 let provider = context.get_configured_provider().await?;
305
306 let imap;
307 let smtp;
308
309 let mail_user = context
310 .get_config(Config::ConfiguredMailUser)
311 .await?
312 .unwrap_or_default();
313 let send_user = context
314 .get_config(Config::ConfiguredSendUser)
315 .await?
316 .unwrap_or_default();
317
318 if let Some(provider) = provider {
319 let parsed_addr = EmailAddress::new(&addr).context("Bad email-address")?;
320 let addr_localpart = parsed_addr.local;
321
322 if provider.server.is_empty() {
323 let servers = vec![
324 ServerParams {
325 protocol: Protocol::Imap,
326 hostname: context
327 .get_config(Config::ConfiguredMailServer)
328 .await?
329 .unwrap_or_default(),
330 port: context
331 .get_config_parsed::<u16>(Config::ConfiguredMailPort)
332 .await?
333 .unwrap_or_default(),
334 socket: context
335 .get_config_parsed::<i32>(Config::ConfiguredMailSecurity)
336 .await?
337 .and_then(num_traits::FromPrimitive::from_i32)
338 .unwrap_or_default(),
339 username: mail_user.clone(),
340 },
341 ServerParams {
342 protocol: Protocol::Smtp,
343 hostname: context
344 .get_config(Config::ConfiguredSendServer)
345 .await?
346 .unwrap_or_default(),
347 port: context
348 .get_config_parsed::<u16>(Config::ConfiguredSendPort)
349 .await?
350 .unwrap_or_default(),
351 socket: context
352 .get_config_parsed::<i32>(Config::ConfiguredSendSecurity)
353 .await?
354 .and_then(num_traits::FromPrimitive::from_i32)
355 .unwrap_or_default(),
356 username: send_user.clone(),
357 },
358 ];
359 let servers = expand_param_vector(servers, &addr, &parsed_addr.domain);
360 imap = servers
361 .iter()
362 .filter_map(|params| {
363 let Ok(security) = params.socket.try_into() else {
364 return None;
365 };
366 if params.protocol == Protocol::Imap {
367 Some(ConfiguredServerLoginParam {
368 connection: ConnectionCandidate {
369 host: params.hostname.clone(),
370 port: params.port,
371 security,
372 },
373 user: params.username.clone(),
374 })
375 } else {
376 None
377 }
378 })
379 .collect();
380 smtp = servers
381 .iter()
382 .filter_map(|params| {
383 let Ok(security) = params.socket.try_into() else {
384 return None;
385 };
386 if params.protocol == Protocol::Smtp {
387 Some(ConfiguredServerLoginParam {
388 connection: ConnectionCandidate {
389 host: params.hostname.clone(),
390 port: params.port,
391 security,
392 },
393 user: params.username.clone(),
394 })
395 } else {
396 None
397 }
398 })
399 .collect();
400 } else {
401 imap = provider
402 .server
403 .iter()
404 .filter_map(|server| {
405 if server.protocol != Protocol::Imap {
406 return None;
407 }
408
409 let Ok(security) = server.socket.try_into() else {
410 return None;
411 };
412
413 Some(ConfiguredServerLoginParam {
414 connection: ConnectionCandidate {
415 host: server.hostname.to_string(),
416 port: server.port,
417 security,
418 },
419 user: if !mail_user.is_empty() {
420 mail_user.clone()
421 } else {
422 match server.username_pattern {
423 UsernamePattern::Email => addr.to_string(),
424 UsernamePattern::Emaillocalpart => addr_localpart.clone(),
425 }
426 },
427 })
428 })
429 .collect();
430 smtp = provider
431 .server
432 .iter()
433 .filter_map(|server| {
434 if server.protocol != Protocol::Smtp {
435 return None;
436 }
437
438 let Ok(security) = server.socket.try_into() else {
439 return None;
440 };
441
442 Some(ConfiguredServerLoginParam {
443 connection: ConnectionCandidate {
444 host: server.hostname.to_string(),
445 port: server.port,
446 security,
447 },
448 user: if !send_user.is_empty() {
449 send_user.clone()
450 } else {
451 match server.username_pattern {
452 UsernamePattern::Email => addr.to_string(),
453 UsernamePattern::Emaillocalpart => addr_localpart.clone(),
454 }
455 },
456 })
457 })
458 .collect();
459 }
460 } else if let (Some(configured_mail_servers), Some(configured_send_servers)) = (
461 context.get_config(Config::ConfiguredImapServers).await?,
462 context.get_config(Config::ConfiguredSmtpServers).await?,
463 ) {
464 imap = serde_json::from_str(&configured_mail_servers)
465 .context("Failed to parse configured IMAP servers")?;
466 smtp = serde_json::from_str(&configured_send_servers)
467 .context("Failed to parse configured SMTP servers")?;
468 } else {
469 let mail_server = context
471 .get_config(Config::ConfiguredMailServer)
472 .await?
473 .unwrap_or_default();
474 let mail_port = context
475 .get_config_parsed::<u16>(Config::ConfiguredMailPort)
476 .await?
477 .unwrap_or_default();
478
479 let mail_security: Socket = context
480 .get_config_parsed::<i32>(Config::ConfiguredMailSecurity)
481 .await?
482 .and_then(num_traits::FromPrimitive::from_i32)
483 .unwrap_or_default();
484
485 let send_server = context
486 .get_config(Config::ConfiguredSendServer)
487 .await?
488 .context("SMTP server is not configured")?;
489 let send_port = context
490 .get_config_parsed::<u16>(Config::ConfiguredSendPort)
491 .await?
492 .unwrap_or_default();
493 let send_security: Socket = context
494 .get_config_parsed::<i32>(Config::ConfiguredSendSecurity)
495 .await?
496 .and_then(num_traits::FromPrimitive::from_i32)
497 .unwrap_or_default();
498
499 imap = vec![ConfiguredServerLoginParam {
500 connection: ConnectionCandidate {
501 host: mail_server,
502 port: mail_port,
503 security: mail_security.try_into()?,
504 },
505 user: mail_user.clone(),
506 }];
507 smtp = vec![ConfiguredServerLoginParam {
508 connection: ConnectionCandidate {
509 host: send_server,
510 port: send_port,
511 security: send_security.try_into()?,
512 },
513 user: send_user.clone(),
514 }];
515 }
516
517 Ok(Some(ConfiguredLoginParam {
518 addr,
519 imap,
520 imap_user: mail_user,
521 imap_password: mail_pw,
522 smtp,
523 smtp_user: send_user,
524 smtp_password: send_pw,
525 certificate_checks,
526 provider,
527 oauth2,
528 }))
529 }
530
531 pub(crate) async fn save_to_transports_table(
532 self,
533 context: &Context,
534 entered_param: &EnteredLoginParam,
535 ) -> Result<()> {
536 let addr = addr_normalize(&self.addr);
537 let provider_id = self.provider.map(|provider| provider.id);
538 let configured_addr = context.get_config(Config::ConfiguredAddr).await?;
539 if let Some(configured_addr) = &configured_addr {
540 ensure!(
541 addr_cmp(configured_addr, &addr),
542 "Adding a second transport is not supported right now."
543 );
544 }
545 context
546 .sql
547 .execute(
548 "INSERT INTO transports (addr, entered_param, configured_param)
549 VALUES (?, ?, ?)
550 ON CONFLICT (addr)
551 DO UPDATE SET entered_param=excluded.entered_param, configured_param=excluded.configured_param",
552 (
553 self.addr.clone(),
554 serde_json::to_string(entered_param)?,
555 self.into_json()?,
556 ),
557 )
558 .await?;
559 if configured_addr.is_none() {
560 context
562 .sql
563 .set_raw_config(Config::ConfiguredProvider.as_ref(), provider_id)
564 .await?;
565 context
566 .sql
567 .set_raw_config(Config::ConfiguredAddr.as_ref(), Some(&addr))
568 .await?;
569 }
570 Ok(())
571 }
572
573 pub(crate) fn from_json(json: &str) -> Result<Self> {
574 let json: ConfiguredLoginParamJson = serde_json::from_str(json)?;
575
576 let provider = json.provider_id.and_then(|id| get_provider_by_id(&id));
577
578 Ok(ConfiguredLoginParam {
579 addr: json.addr,
580 imap: json.imap,
581 imap_user: json.imap_user,
582 imap_password: json.imap_password,
583 smtp: json.smtp,
584 smtp_user: json.smtp_user,
585 smtp_password: json.smtp_password,
586 provider,
587 certificate_checks: json.certificate_checks,
588 oauth2: json.oauth2,
589 })
590 }
591
592 pub(crate) fn into_json(self) -> Result<String> {
593 let json = ConfiguredLoginParamJson {
594 addr: self.addr,
595 imap: self.imap,
596 imap_user: self.imap_user,
597 imap_password: self.imap_password,
598 smtp: self.smtp,
599 smtp_user: self.smtp_user,
600 smtp_password: self.smtp_password,
601 provider_id: self.provider.map(|p| p.id.to_string()),
602 certificate_checks: self.certificate_checks,
603 oauth2: self.oauth2,
604 };
605 Ok(serde_json::to_string(&json)?)
606 }
607
608 pub(crate) fn strict_tls(&self, connected_through_proxy: bool) -> bool {
609 let provider_strict_tls = self.provider.map(|provider| provider.opt.strict_tls);
610 match self.certificate_checks {
611 ConfiguredCertificateChecks::OldAutomatic => {
612 provider_strict_tls.unwrap_or(connected_through_proxy)
613 }
614 ConfiguredCertificateChecks::Automatic => provider_strict_tls.unwrap_or(true),
615 ConfiguredCertificateChecks::Strict => true,
616 ConfiguredCertificateChecks::AcceptInvalidCertificates
617 | ConfiguredCertificateChecks::AcceptInvalidCertificates2 => false,
618 }
619 }
620}
621
622#[cfg(test)]
623mod tests {
624 use super::*;
625 use crate::log::LogExt as _;
626 use crate::provider::get_provider_by_id;
627 use crate::test_utils::TestContext;
628
629 #[test]
630 fn test_configured_certificate_checks_display() {
631 use std::string::ToString;
632
633 assert_eq!(
634 "accept_invalid_certificates".to_string(),
635 ConfiguredCertificateChecks::AcceptInvalidCertificates.to_string()
636 );
637 }
638
639 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
640 async fn test_save_load_login_param() -> Result<()> {
641 let t = TestContext::new().await;
642
643 let param = ConfiguredLoginParam {
644 addr: "alice@example.org".to_string(),
645 imap: vec![ConfiguredServerLoginParam {
646 connection: ConnectionCandidate {
647 host: "imap.example.com".to_string(),
648 port: 123,
649 security: ConnectionSecurity::Starttls,
650 },
651 user: "alice".to_string(),
652 }],
653 imap_user: "".to_string(),
654 imap_password: "foo".to_string(),
655 smtp: vec![ConfiguredServerLoginParam {
656 connection: ConnectionCandidate {
657 host: "smtp.example.com".to_string(),
658 port: 456,
659 security: ConnectionSecurity::Tls,
660 },
661 user: "alice@example.org".to_string(),
662 }],
663 smtp_user: "".to_string(),
664 smtp_password: "bar".to_string(),
665 provider: None,
666 certificate_checks: ConfiguredCertificateChecks::Strict,
667 oauth2: false,
668 };
669
670 param
671 .clone()
672 .save_to_transports_table(&t, &EnteredLoginParam::default())
673 .await?;
674 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}"#;
675 assert_eq!(
676 t.sql
677 .query_get_value::<String>("SELECT configured_param FROM transports", ())
678 .await?
679 .unwrap(),
680 expected_param
681 );
682 assert_eq!(t.is_configured().await?, true);
683 let loaded = ConfiguredLoginParam::load(&t).await?.unwrap();
684 assert_eq!(param, loaded);
685
686 t.set_config(Config::ConfiguredImapCertificateChecks, Some("999"))
688 .await?;
689 assert!(ConfiguredLoginParam::load(&t).await.is_ok());
690
691 let wrong_param = expected_param.replace("Strict", "Stricct");
693 assert_ne!(expected_param, wrong_param);
694 t.sql
695 .execute("UPDATE transports SET configured_param=?", (wrong_param,))
696 .await?;
697 assert!(ConfiguredLoginParam::load(&t).await.is_err());
698
699 Ok(())
700 }
701
702 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
703 async fn test_posteo_alias() -> Result<()> {
704 let t = TestContext::new().await;
705
706 let user = "alice@posteo.de";
707
708 t.set_config(Config::Configured, Some("1")).await?;
711 t.set_config(Config::ConfiguredProvider, Some("posteo"))
712 .await?;
713 t.sql
714 .set_raw_config(Config::ConfiguredAddr.as_ref(), Some("alice@posteo.at"))
715 .await?;
716 t.set_config(Config::ConfiguredMailServer, Some("posteo.de"))
717 .await?;
718 t.set_config(Config::ConfiguredMailPort, Some("993"))
719 .await?;
720 t.set_config(Config::ConfiguredMailSecurity, Some("1"))
721 .await?; t.set_config(Config::ConfiguredMailUser, Some(user)).await?;
723 t.set_config(Config::ConfiguredMailPw, Some("foobarbaz"))
724 .await?;
725 t.set_config(Config::ConfiguredImapCertificateChecks, Some("1"))
726 .await?; t.set_config(Config::ConfiguredSendServer, Some("posteo.de"))
728 .await?;
729 t.set_config(Config::ConfiguredSendPort, Some("465"))
730 .await?;
731 t.set_config(Config::ConfiguredSendSecurity, Some("1"))
732 .await?; t.set_config(Config::ConfiguredSendUser, Some(user)).await?;
734 t.set_config(Config::ConfiguredSendPw, Some("foobarbaz"))
735 .await?;
736 t.set_config(Config::ConfiguredSmtpCertificateChecks, Some("1"))
737 .await?; t.set_config(Config::ConfiguredServerFlags, Some("0"))
739 .await?;
740
741 let param = ConfiguredLoginParam {
742 addr: "alice@posteo.at".to_string(),
743 imap: vec![
744 ConfiguredServerLoginParam {
745 connection: ConnectionCandidate {
746 host: "posteo.de".to_string(),
747 port: 993,
748 security: ConnectionSecurity::Tls,
749 },
750 user: user.to_string(),
751 },
752 ConfiguredServerLoginParam {
753 connection: ConnectionCandidate {
754 host: "posteo.de".to_string(),
755 port: 143,
756 security: ConnectionSecurity::Starttls,
757 },
758 user: user.to_string(),
759 },
760 ],
761 imap_user: "alice@posteo.de".to_string(),
762 imap_password: "foobarbaz".to_string(),
763 smtp: vec![
764 ConfiguredServerLoginParam {
765 connection: ConnectionCandidate {
766 host: "posteo.de".to_string(),
767 port: 465,
768 security: ConnectionSecurity::Tls,
769 },
770 user: user.to_string(),
771 },
772 ConfiguredServerLoginParam {
773 connection: ConnectionCandidate {
774 host: "posteo.de".to_string(),
775 port: 587,
776 security: ConnectionSecurity::Starttls,
777 },
778 user: user.to_string(),
779 },
780 ],
781 smtp_user: "alice@posteo.de".to_string(),
782 smtp_password: "foobarbaz".to_string(),
783 provider: get_provider_by_id("posteo"),
784 certificate_checks: ConfiguredCertificateChecks::Strict,
785 oauth2: false,
786 };
787
788 let loaded = ConfiguredLoginParam::load_legacy(&t).await?.unwrap();
789 assert_eq!(loaded, param);
790
791 migrate_configured_login_param(&t).await;
792 let loaded = ConfiguredLoginParam::load(&t).await?.unwrap();
793 assert_eq!(loaded, param);
794
795 Ok(())
796 }
797
798 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
799 async fn test_empty_server_list_legacy() -> Result<()> {
800 let (domain, provider) = crate::provider::data::PROVIDER_DATA
804 .iter()
805 .find(|(_domain, provider)| provider.server.is_empty())
806 .unwrap();
807
808 let t = TestContext::new().await;
809
810 let addr = format!("alice@{domain}");
811
812 t.set_config(Config::Configured, Some("1")).await?;
813 t.set_config(Config::ConfiguredProvider, Some(provider.id))
814 .await?;
815 t.sql
816 .set_raw_config(Config::ConfiguredAddr.as_ref(), Some(&addr))
817 .await?;
818 t.set_config(Config::ConfiguredMailPw, Some("foobarbaz"))
819 .await?;
820 t.set_config(Config::ConfiguredImapCertificateChecks, Some("1"))
821 .await?; t.set_config(Config::ConfiguredSendPw, Some("foobarbaz"))
823 .await?;
824 t.set_config(Config::ConfiguredSmtpCertificateChecks, Some("1"))
825 .await?; t.set_config(Config::ConfiguredServerFlags, Some("0"))
827 .await?;
828
829 let loaded = ConfiguredLoginParam::load_legacy(&t).await?.unwrap();
830 assert_eq!(loaded.provider, Some(*provider));
831 assert_eq!(loaded.imap.is_empty(), false);
832 assert_eq!(loaded.smtp.is_empty(), false);
833
834 migrate_configured_login_param(&t).await;
835
836 let loaded = ConfiguredLoginParam::load(&t).await?.unwrap();
837 assert_eq!(loaded.provider, Some(*provider));
838 assert_eq!(loaded.imap.is_empty(), false);
839 assert_eq!(loaded.smtp.is_empty(), false);
840
841 Ok(())
842 }
843
844 async fn migrate_configured_login_param(t: &TestContext) {
845 t.sql.execute("DROP TABLE transports;", ()).await.unwrap();
846 t.sql.set_raw_config_int("dbversion", 130).await.unwrap();
847 t.sql.run_migrations(t).await.log_err(t).ok();
848 }
849
850 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
851 async fn test_empty_server_list() -> Result<()> {
852 let (domain, provider) = crate::provider::data::PROVIDER_DATA
856 .iter()
857 .find(|(_domain, provider)| provider.server.is_empty())
858 .unwrap();
859
860 let t = TestContext::new().await;
861
862 let addr = format!("alice@{domain}");
863
864 ConfiguredLoginParam {
865 addr: addr.clone(),
866 imap: vec![ConfiguredServerLoginParam {
867 connection: ConnectionCandidate {
868 host: "example.org".to_string(),
869 port: 100,
870 security: ConnectionSecurity::Tls,
871 },
872 user: addr.clone(),
873 }],
874 imap_user: addr.clone(),
875 imap_password: "foobarbaz".to_string(),
876 smtp: vec![ConfiguredServerLoginParam {
877 connection: ConnectionCandidate {
878 host: "example.org".to_string(),
879 port: 100,
880 security: ConnectionSecurity::Tls,
881 },
882 user: addr.clone(),
883 }],
884 smtp_user: addr.clone(),
885 smtp_password: "foobarbaz".to_string(),
886 provider: Some(provider),
887 certificate_checks: ConfiguredCertificateChecks::Automatic,
888 oauth2: false,
889 }
890 .save_to_transports_table(&t, &EnteredLoginParam::default())
891 .await?;
892
893 let loaded = ConfiguredLoginParam::load(&t).await?.unwrap();
894 assert_eq!(loaded.provider, Some(*provider));
895 assert_eq!(loaded.imap.is_empty(), false);
896 assert_eq!(loaded.smtp.is_empty(), false);
897 assert_eq!(t.get_configured_provider().await?, Some(*provider));
898
899 Ok(())
900 }
901}