deltachat/
transport.rs

1//! # Message transport.
2//!
3//! A transport represents a single IMAP+SMTP configuration
4//! that is known to work at least once in the past.
5//!
6//! Transports are stored in the `transports` SQL table.
7//! Each transport is uniquely identified by its email address.
8//! The table stores both the login parameters entered by the user
9//! and configured list of connection candidates.
10
11use 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    /// Implicit TLS.
29    Tls,
30
31    // STARTTLS.
32    Starttls,
33
34    /// Plaintext.
35    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/// Values saved into `imap_certificate_checks`.
63#[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    /// Use configuration from the provider database.
70    /// If there is no provider database setting for certificate checks,
71    /// accept invalid certificates.
72    ///
73    /// Must not be saved by new versions.
74    ///
75    /// Previous Delta Chat versions before core 1.133.0
76    /// stored this in `configured_imap_certificate_checks`
77    /// if Automatic configuration
78    /// was selected, configuration with strict TLS checks failed
79    /// and configuration without strict TLS checks succeeded.
80    OldAutomatic = 0,
81
82    /// Ensure that TLS certificate is valid for the server hostname.
83    Strict = 1,
84
85    /// Accept certificates that are expired, self-signed
86    /// or otherwise not valid for the server hostname.
87    AcceptInvalidCertificates = 2,
88
89    /// Accept certificates that are expired, self-signed
90    /// or otherwise not valid for the server hostname.
91    ///
92    /// Alias to `AcceptInvalidCertificates` for compatibility.
93    AcceptInvalidCertificates2 = 3,
94
95    /// Use configuration from the provider database.
96    /// If there is no provider database setting for certificate checks,
97    /// apply strict checks to TLS certificates.
98    Automatic = 4,
99}
100
101#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
102pub(crate) struct ConnectionCandidate {
103    /// Server hostname or IP address.
104    pub host: String,
105
106    /// Server port.
107    pub port: u16,
108
109    /// Transport layer security.
110    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    /// Username.
125    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            &param.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/// Login parameters saved to the database
157/// after successful configuration.
158#[derive(Debug, Clone, PartialEq, Eq)]
159pub(crate) struct ConfiguredLoginParam {
160    /// `From:` address that was used at the time of configuration.
161    pub addr: String,
162
163    pub imap: Vec<ConfiguredServerLoginParam>,
164
165    // Custom IMAP user.
166    //
167    // This overwrites autoconfig from the provider database
168    // if non-empty.
169    pub imap_user: String,
170
171    pub imap_password: String,
172
173    pub smtp: Vec<ConfiguredServerLoginParam>,
174
175    // Custom SMTP user.
176    //
177    // This overwrites autoconfig from the provider database
178    // if non-empty.
179    pub smtp_user: String,
180
181    pub smtp_password: String,
182
183    pub provider: Option<&'static Provider>,
184
185    /// TLS options: whether to allow invalid certificates and/or
186    /// invalid hostnames
187    pub certificate_checks: ConfiguredCertificateChecks,
188
189    /// If true, login via OAUTH2 (not recommended anymore)
190    pub oauth2: bool,
191}
192
193/// The representation of ConfiguredLoginParam in the database,
194/// saved as Json.
195#[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    /// Load configured account settings from the database.
242    ///
243    /// Returns `None` if account is not configured.
244    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    /// Loads legacy configured param. Only used for tests and the migration.
264    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            // This is true for old accounts configured using C core
285            // which did not check TLS certificates.
286            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            // Load legacy settings storing a single IMAP and single SMTP server.
470            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            // If there is no transport yet, set the new transport as the primary one
561            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        // Legacy ConfiguredImapCertificateChecks config is ignored
687        t.set_config(Config::ConfiguredImapCertificateChecks, Some("999"))
688            .await?;
689        assert!(ConfiguredLoginParam::load(&t).await.is_ok());
690
691        // Test that we don't panic on unknown ConfiguredImapCertificateChecks values.
692        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        // Alice has old config with "alice@posteo.at" address
709        // and "alice@posteo.de" username.
710        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?; // TLS
722        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?; // Strict
727        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?; // TLS
733        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?; // Strict
738        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        // Find a provider that does not have server list set.
801        //
802        // There is at least one such provider in the provider database.
803        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?; // Strict
822        t.set_config(Config::ConfiguredSendPw, Some("foobarbaz"))
823            .await?;
824        t.set_config(Config::ConfiguredSmtpCertificateChecks, Some("1"))
825            .await?; // Strict
826        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        // Find a provider that does not have server list set.
853        //
854        // There is at least one such provider in the provider database.
855        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}