deltachat/
login_param.rs

1//! # Login parameters.
2
3use std::fmt;
4
5use anyhow::{bail, ensure, format_err, Context as _, Result};
6use deltachat_contact_tools::{addr_cmp, addr_normalize, EmailAddress};
7use num_traits::ToPrimitive as _;
8use serde::{Deserialize, Serialize};
9
10use crate::config::Config;
11use crate::configure::server_params::{expand_param_vector, ServerParams};
12use crate::constants::{DC_LP_AUTH_FLAGS, DC_LP_AUTH_OAUTH2};
13use crate::context::Context;
14use crate::net::load_connection_timestamp;
15pub use crate::net::proxy::ProxyConfig;
16pub use crate::provider::Socket;
17use crate::provider::{get_provider_by_id, Protocol, Provider, UsernamePattern};
18use crate::sql::Sql;
19use crate::tools::ToOption;
20
21/// User-entered setting for certificate checks.
22///
23/// Should be saved into `imap_certificate_checks` before running configuration.
24#[derive(
25    Copy,
26    Clone,
27    Debug,
28    Default,
29    Display,
30    FromPrimitive,
31    ToPrimitive,
32    PartialEq,
33    Eq,
34    Serialize,
35    Deserialize,
36)]
37#[repr(u32)]
38#[strum(serialize_all = "snake_case")]
39pub enum EnteredCertificateChecks {
40    /// `Automatic` means that provider database setting should be taken.
41    /// If there is no provider database setting for certificate checks,
42    /// check certificates strictly.
43    #[default]
44    Automatic = 0,
45
46    /// Ensure that TLS certificate is valid for the server hostname.
47    Strict = 1,
48
49    /// Accept certificates that are expired, self-signed
50    /// or otherwise not valid for the server hostname.
51    AcceptInvalidCertificates = 2,
52
53    /// Alias for `AcceptInvalidCertificates`
54    /// for API compatibility.
55    AcceptInvalidCertificates2 = 3,
56}
57
58/// Values saved into `imap_certificate_checks`.
59#[derive(
60    Copy, Clone, Debug, Display, FromPrimitive, ToPrimitive, PartialEq, Eq, Serialize, Deserialize,
61)]
62#[repr(u32)]
63#[strum(serialize_all = "snake_case")]
64pub(crate) enum ConfiguredCertificateChecks {
65    /// Use configuration from the provider database.
66    /// If there is no provider database setting for certificate checks,
67    /// accept invalid certificates.
68    ///
69    /// Must not be saved by new versions.
70    ///
71    /// Previous Delta Chat versions before core 1.133.0
72    /// stored this in `configured_imap_certificate_checks`
73    /// if Automatic configuration
74    /// was selected, configuration with strict TLS checks failed
75    /// and configuration without strict TLS checks succeeded.
76    OldAutomatic = 0,
77
78    /// Ensure that TLS certificate is valid for the server hostname.
79    Strict = 1,
80
81    /// Accept certificates that are expired, self-signed
82    /// or otherwise not valid for the server hostname.
83    AcceptInvalidCertificates = 2,
84
85    /// Accept certificates that are expired, self-signed
86    /// or otherwise not valid for the server hostname.
87    ///
88    /// Alias to `AcceptInvalidCertificates` for compatibility.
89    AcceptInvalidCertificates2 = 3,
90
91    /// Use configuration from the provider database.
92    /// If there is no provider database setting for certificate checks,
93    /// apply strict checks to TLS certificates.
94    Automatic = 4,
95}
96
97/// Login parameters for a single server, either IMAP or SMTP
98#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
99pub struct EnteredServerLoginParam {
100    /// Server hostname or IP address.
101    pub server: String,
102
103    /// Server port.
104    ///
105    /// 0 if not specified.
106    pub port: u16,
107
108    /// Socket security.
109    pub security: Socket,
110
111    /// Username.
112    ///
113    /// Empty string if not specified.
114    pub user: String,
115
116    /// Password.
117    pub password: String,
118}
119
120/// Login parameters entered by the user.
121#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
122pub struct EnteredLoginParam {
123    /// Email address.
124    pub addr: String,
125
126    /// IMAP settings.
127    pub imap: EnteredServerLoginParam,
128
129    /// SMTP settings.
130    pub smtp: EnteredServerLoginParam,
131
132    /// TLS options: whether to allow invalid certificates and/or
133    /// invalid hostnames
134    pub certificate_checks: EnteredCertificateChecks,
135
136    /// If true, login via OAUTH2 (not recommended anymore)
137    pub oauth2: bool,
138}
139
140impl EnteredLoginParam {
141    /// Loads entered account settings.
142    pub(crate) async fn load(context: &Context) -> Result<Self> {
143        let addr = context
144            .get_config(Config::Addr)
145            .await?
146            .unwrap_or_default()
147            .trim()
148            .to_string();
149
150        let mail_server = context
151            .get_config(Config::MailServer)
152            .await?
153            .unwrap_or_default();
154        let mail_port = context
155            .get_config_parsed::<u16>(Config::MailPort)
156            .await?
157            .unwrap_or_default();
158        let mail_security = context
159            .get_config_parsed::<i32>(Config::MailSecurity)
160            .await?
161            .and_then(num_traits::FromPrimitive::from_i32)
162            .unwrap_or_default();
163        let mail_user = context
164            .get_config(Config::MailUser)
165            .await?
166            .unwrap_or_default();
167        let mail_pw = context
168            .get_config(Config::MailPw)
169            .await?
170            .unwrap_or_default();
171
172        // The setting is named `imap_certificate_checks`
173        // for backwards compatibility,
174        // but now it is a global setting applied to all protocols,
175        // while `smtp_certificate_checks` is ignored.
176        let certificate_checks = if let Some(certificate_checks) = context
177            .get_config_parsed::<i32>(Config::ImapCertificateChecks)
178            .await?
179        {
180            num_traits::FromPrimitive::from_i32(certificate_checks)
181                .context("Unknown imap_certificate_checks value")?
182        } else {
183            Default::default()
184        };
185
186        let send_server = context
187            .get_config(Config::SendServer)
188            .await?
189            .unwrap_or_default();
190        let send_port = context
191            .get_config_parsed::<u16>(Config::SendPort)
192            .await?
193            .unwrap_or_default();
194        let send_security = context
195            .get_config_parsed::<i32>(Config::SendSecurity)
196            .await?
197            .and_then(num_traits::FromPrimitive::from_i32)
198            .unwrap_or_default();
199        let send_user = context
200            .get_config(Config::SendUser)
201            .await?
202            .unwrap_or_default();
203        let send_pw = context
204            .get_config(Config::SendPw)
205            .await?
206            .unwrap_or_default();
207
208        let server_flags = context
209            .get_config_parsed::<i32>(Config::ServerFlags)
210            .await?
211            .unwrap_or_default();
212        let oauth2 = matches!(server_flags & DC_LP_AUTH_FLAGS, DC_LP_AUTH_OAUTH2);
213
214        Ok(EnteredLoginParam {
215            addr,
216            imap: EnteredServerLoginParam {
217                server: mail_server,
218                port: mail_port,
219                security: mail_security,
220                user: mail_user,
221                password: mail_pw,
222            },
223            smtp: EnteredServerLoginParam {
224                server: send_server,
225                port: send_port,
226                security: send_security,
227                user: send_user,
228                password: send_pw,
229            },
230            certificate_checks,
231            oauth2,
232        })
233    }
234
235    /// Saves entered account settings,
236    /// so that they can be prefilled if the user wants to configure the server again.
237    pub(crate) async fn save(&self, context: &Context) -> Result<()> {
238        context.set_config(Config::Addr, Some(&self.addr)).await?;
239
240        context
241            .set_config(Config::MailServer, self.imap.server.to_option())
242            .await?;
243        context
244            .set_config(Config::MailPort, self.imap.port.to_option().as_deref())
245            .await?;
246        context
247            .set_config(
248                Config::MailSecurity,
249                self.imap.security.to_i32().to_option().as_deref(),
250            )
251            .await?;
252        context
253            .set_config(Config::MailUser, self.imap.user.to_option())
254            .await?;
255        context
256            .set_config(Config::MailPw, self.imap.password.to_option())
257            .await?;
258
259        context
260            .set_config(Config::SendServer, self.smtp.server.to_option())
261            .await?;
262        context
263            .set_config(Config::SendPort, self.smtp.port.to_option().as_deref())
264            .await?;
265        context
266            .set_config(
267                Config::SendSecurity,
268                self.smtp.security.to_i32().to_option().as_deref(),
269            )
270            .await?;
271        context
272            .set_config(Config::SendUser, self.smtp.user.to_option())
273            .await?;
274        context
275            .set_config(Config::SendPw, self.smtp.password.to_option())
276            .await?;
277
278        context
279            .set_config(
280                Config::ImapCertificateChecks,
281                self.certificate_checks.to_i32().to_option().as_deref(),
282            )
283            .await?;
284
285        let server_flags = if self.oauth2 {
286            Some(DC_LP_AUTH_OAUTH2.to_string())
287        } else {
288            None
289        };
290        context
291            .set_config(Config::ServerFlags, server_flags.as_deref())
292            .await?;
293
294        Ok(())
295    }
296}
297
298impl fmt::Display for EnteredLoginParam {
299    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
300        let unset = "0";
301        let pw = "***";
302
303        write!(
304            f,
305            "{} imap:{}:{}:{}:{}:{}:{} smtp:{}:{}:{}:{}:{}:{} cert_{}",
306            unset_empty(&self.addr),
307            unset_empty(&self.imap.user),
308            if !self.imap.password.is_empty() {
309                pw
310            } else {
311                unset
312            },
313            unset_empty(&self.imap.server),
314            self.imap.port,
315            self.imap.security,
316            if self.oauth2 { "OAUTH2" } else { "AUTH_NORMAL" },
317            unset_empty(&self.smtp.user),
318            if !self.smtp.password.is_empty() {
319                pw
320            } else {
321                unset
322            },
323            unset_empty(&self.smtp.server),
324            self.smtp.port,
325            self.smtp.security,
326            if self.oauth2 { "OAUTH2" } else { "AUTH_NORMAL" },
327            self.certificate_checks
328        )
329    }
330}
331
332fn unset_empty(s: &str) -> &str {
333    if s.is_empty() {
334        "unset"
335    } else {
336        s
337    }
338}
339
340#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
341pub(crate) struct ConnectionCandidate {
342    /// Server hostname or IP address.
343    pub host: String,
344
345    /// Server port.
346    pub port: u16,
347
348    /// Transport layer security.
349    pub security: ConnectionSecurity,
350}
351
352impl fmt::Display for ConnectionCandidate {
353    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
354        write!(f, "{}:{}:{}", &self.host, self.port, self.security)?;
355        Ok(())
356    }
357}
358
359#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
360pub(crate) enum ConnectionSecurity {
361    /// Implicit TLS.
362    Tls,
363
364    // STARTTLS.
365    Starttls,
366
367    /// Plaintext.
368    Plain,
369}
370
371impl fmt::Display for ConnectionSecurity {
372    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
373        match self {
374            Self::Tls => write!(f, "tls")?,
375            Self::Starttls => write!(f, "starttls")?,
376            Self::Plain => write!(f, "plain")?,
377        }
378        Ok(())
379    }
380}
381
382impl TryFrom<Socket> for ConnectionSecurity {
383    type Error = anyhow::Error;
384
385    fn try_from(socket: Socket) -> Result<Self> {
386        match socket {
387            Socket::Automatic => Err(format_err!("Socket security is not configured")),
388            Socket::Ssl => Ok(Self::Tls),
389            Socket::Starttls => Ok(Self::Starttls),
390            Socket::Plain => Ok(Self::Plain),
391        }
392    }
393}
394
395#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
396pub(crate) struct ConfiguredServerLoginParam {
397    pub connection: ConnectionCandidate,
398
399    /// Username.
400    pub user: String,
401}
402
403impl fmt::Display for ConfiguredServerLoginParam {
404    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
405        write!(f, "{}:{}", self.connection, &self.user)?;
406        Ok(())
407    }
408}
409
410pub(crate) async fn prioritize_server_login_params(
411    sql: &Sql,
412    params: &[ConfiguredServerLoginParam],
413    alpn: &str,
414) -> Result<Vec<ConfiguredServerLoginParam>> {
415    let mut res: Vec<(Option<i64>, ConfiguredServerLoginParam)> = Vec::with_capacity(params.len());
416    for param in params {
417        let timestamp = load_connection_timestamp(
418            sql,
419            alpn,
420            &param.connection.host,
421            param.connection.port,
422            None,
423        )
424        .await?;
425        res.push((timestamp, param.clone()));
426    }
427    res.sort_by_key(|(ts, _param)| std::cmp::Reverse(*ts));
428    Ok(res.into_iter().map(|(_ts, param)| param).collect())
429}
430
431/// Login parameters saved to the database
432/// after successful configuration.
433#[derive(Debug, Clone, PartialEq, Eq)]
434pub(crate) struct ConfiguredLoginParam {
435    /// `From:` address that was used at the time of configuration.
436    pub addr: String,
437
438    pub imap: Vec<ConfiguredServerLoginParam>,
439
440    // Custom IMAP user.
441    //
442    // This overwrites autoconfig from the provider database
443    // if non-empty.
444    pub imap_user: String,
445
446    pub imap_password: String,
447
448    pub smtp: Vec<ConfiguredServerLoginParam>,
449
450    // Custom SMTP user.
451    //
452    // This overwrites autoconfig from the provider database
453    // if non-empty.
454    pub smtp_user: String,
455
456    pub smtp_password: String,
457
458    pub provider: Option<&'static Provider>,
459
460    /// TLS options: whether to allow invalid certificates and/or
461    /// invalid hostnames
462    pub certificate_checks: ConfiguredCertificateChecks,
463
464    /// If true, login via OAUTH2 (not recommended anymore)
465    pub oauth2: bool,
466}
467
468/// The representation of ConfiguredLoginParam in the database,
469/// saved as Json.
470#[derive(Debug, Serialize, Deserialize)]
471struct ConfiguredLoginParamJson {
472    pub addr: String,
473    pub imap: Vec<ConfiguredServerLoginParam>,
474    pub imap_user: String,
475    pub imap_password: String,
476    pub smtp: Vec<ConfiguredServerLoginParam>,
477    pub smtp_user: String,
478    pub smtp_password: String,
479    pub provider_id: Option<String>,
480    pub certificate_checks: ConfiguredCertificateChecks,
481    pub oauth2: bool,
482}
483
484impl fmt::Display for ConfiguredLoginParam {
485    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
486        let addr = &self.addr;
487        let provider_id = match self.provider {
488            Some(provider) => provider.id,
489            None => "none",
490        };
491        let certificate_checks = self.certificate_checks;
492        write!(f, "{addr} imap:[")?;
493        let mut first = true;
494        for imap in &self.imap {
495            if !first {
496                write!(f, ", ")?;
497            }
498            write!(f, "{imap}")?;
499            first = false;
500        }
501        write!(f, "] smtp:[")?;
502        let mut first = true;
503        for smtp in &self.smtp {
504            if !first {
505                write!(f, ", ")?;
506            }
507            write!(f, "{smtp}")?;
508            first = false;
509        }
510        write!(f, "] provider:{provider_id} cert_{certificate_checks}")?;
511        Ok(())
512    }
513}
514
515impl ConfiguredLoginParam {
516    /// Load configured account settings from the database.
517    ///
518    /// Returns `None` if account is not configured.
519    pub(crate) async fn load(context: &Context) -> Result<Option<Self>> {
520        let Some(self_addr) = context.get_config(Config::ConfiguredAddr).await? else {
521            return Ok(None);
522        };
523
524        let json: Option<String> = context
525            .sql
526            .query_get_value(
527                "SELECT configured_param FROM transports WHERE addr=?",
528                (&self_addr,),
529            )
530            .await?;
531        if let Some(json) = json {
532            Ok(Some(Self::from_json(&json)?))
533        } else {
534            bail!("Self address {self_addr} doesn't have a corresponding transport");
535        }
536    }
537
538    /// Loads legacy configured param. Only used for tests and the migration.
539    pub(crate) async fn load_legacy(context: &Context) -> Result<Option<Self>> {
540        if !context.get_config_bool(Config::Configured).await? {
541            return Ok(None);
542        }
543
544        let addr = context
545            .get_config(Config::ConfiguredAddr)
546            .await?
547            .unwrap_or_default()
548            .trim()
549            .to_string();
550
551        let certificate_checks: ConfiguredCertificateChecks = if let Some(certificate_checks) =
552            context
553                .get_config_parsed::<i32>(Config::ConfiguredImapCertificateChecks)
554                .await?
555        {
556            num_traits::FromPrimitive::from_i32(certificate_checks)
557                .context("Invalid configured_imap_certificate_checks value")?
558        } else {
559            // This is true for old accounts configured using C core
560            // which did not check TLS certificates.
561            ConfiguredCertificateChecks::OldAutomatic
562        };
563
564        let send_pw = context
565            .get_config(Config::ConfiguredSendPw)
566            .await?
567            .context("SMTP password is not configured")?;
568        let mail_pw = context
569            .get_config(Config::ConfiguredMailPw)
570            .await?
571            .context("IMAP password is not configured")?;
572
573        let server_flags = context
574            .get_config_parsed::<i32>(Config::ConfiguredServerFlags)
575            .await?
576            .unwrap_or_default();
577        let oauth2 = matches!(server_flags & DC_LP_AUTH_FLAGS, DC_LP_AUTH_OAUTH2);
578
579        let provider = context.get_configured_provider().await?;
580
581        let imap;
582        let smtp;
583
584        let mail_user = context
585            .get_config(Config::ConfiguredMailUser)
586            .await?
587            .unwrap_or_default();
588        let send_user = context
589            .get_config(Config::ConfiguredSendUser)
590            .await?
591            .unwrap_or_default();
592
593        if let Some(provider) = provider {
594            let parsed_addr = EmailAddress::new(&addr).context("Bad email-address")?;
595            let addr_localpart = parsed_addr.local;
596
597            if provider.server.is_empty() {
598                let servers = vec![
599                    ServerParams {
600                        protocol: Protocol::Imap,
601                        hostname: context
602                            .get_config(Config::ConfiguredMailServer)
603                            .await?
604                            .unwrap_or_default(),
605                        port: context
606                            .get_config_parsed::<u16>(Config::ConfiguredMailPort)
607                            .await?
608                            .unwrap_or_default(),
609                        socket: context
610                            .get_config_parsed::<i32>(Config::ConfiguredMailSecurity)
611                            .await?
612                            .and_then(num_traits::FromPrimitive::from_i32)
613                            .unwrap_or_default(),
614                        username: mail_user.clone(),
615                    },
616                    ServerParams {
617                        protocol: Protocol::Smtp,
618                        hostname: context
619                            .get_config(Config::ConfiguredSendServer)
620                            .await?
621                            .unwrap_or_default(),
622                        port: context
623                            .get_config_parsed::<u16>(Config::ConfiguredSendPort)
624                            .await?
625                            .unwrap_or_default(),
626                        socket: context
627                            .get_config_parsed::<i32>(Config::ConfiguredSendSecurity)
628                            .await?
629                            .and_then(num_traits::FromPrimitive::from_i32)
630                            .unwrap_or_default(),
631                        username: send_user.clone(),
632                    },
633                ];
634                let servers = expand_param_vector(servers, &addr, &parsed_addr.domain);
635                imap = servers
636                    .iter()
637                    .filter_map(|params| {
638                        let Ok(security) = params.socket.try_into() else {
639                            return None;
640                        };
641                        if params.protocol == Protocol::Imap {
642                            Some(ConfiguredServerLoginParam {
643                                connection: ConnectionCandidate {
644                                    host: params.hostname.clone(),
645                                    port: params.port,
646                                    security,
647                                },
648                                user: params.username.clone(),
649                            })
650                        } else {
651                            None
652                        }
653                    })
654                    .collect();
655                smtp = servers
656                    .iter()
657                    .filter_map(|params| {
658                        let Ok(security) = params.socket.try_into() else {
659                            return None;
660                        };
661                        if params.protocol == Protocol::Smtp {
662                            Some(ConfiguredServerLoginParam {
663                                connection: ConnectionCandidate {
664                                    host: params.hostname.clone(),
665                                    port: params.port,
666                                    security,
667                                },
668                                user: params.username.clone(),
669                            })
670                        } else {
671                            None
672                        }
673                    })
674                    .collect();
675            } else {
676                imap = provider
677                    .server
678                    .iter()
679                    .filter_map(|server| {
680                        if server.protocol != Protocol::Imap {
681                            return None;
682                        }
683
684                        let Ok(security) = server.socket.try_into() else {
685                            return None;
686                        };
687
688                        Some(ConfiguredServerLoginParam {
689                            connection: ConnectionCandidate {
690                                host: server.hostname.to_string(),
691                                port: server.port,
692                                security,
693                            },
694                            user: if !mail_user.is_empty() {
695                                mail_user.clone()
696                            } else {
697                                match server.username_pattern {
698                                    UsernamePattern::Email => addr.to_string(),
699                                    UsernamePattern::Emaillocalpart => addr_localpart.clone(),
700                                }
701                            },
702                        })
703                    })
704                    .collect();
705                smtp = provider
706                    .server
707                    .iter()
708                    .filter_map(|server| {
709                        if server.protocol != Protocol::Smtp {
710                            return None;
711                        }
712
713                        let Ok(security) = server.socket.try_into() else {
714                            return None;
715                        };
716
717                        Some(ConfiguredServerLoginParam {
718                            connection: ConnectionCandidate {
719                                host: server.hostname.to_string(),
720                                port: server.port,
721                                security,
722                            },
723                            user: if !send_user.is_empty() {
724                                send_user.clone()
725                            } else {
726                                match server.username_pattern {
727                                    UsernamePattern::Email => addr.to_string(),
728                                    UsernamePattern::Emaillocalpart => addr_localpart.clone(),
729                                }
730                            },
731                        })
732                    })
733                    .collect();
734            }
735        } else if let (Some(configured_mail_servers), Some(configured_send_servers)) = (
736            context.get_config(Config::ConfiguredImapServers).await?,
737            context.get_config(Config::ConfiguredSmtpServers).await?,
738        ) {
739            imap = serde_json::from_str(&configured_mail_servers)
740                .context("Failed to parse configured IMAP servers")?;
741            smtp = serde_json::from_str(&configured_send_servers)
742                .context("Failed to parse configured SMTP servers")?;
743        } else {
744            // Load legacy settings storing a single IMAP and single SMTP server.
745            let mail_server = context
746                .get_config(Config::ConfiguredMailServer)
747                .await?
748                .unwrap_or_default();
749            let mail_port = context
750                .get_config_parsed::<u16>(Config::ConfiguredMailPort)
751                .await?
752                .unwrap_or_default();
753
754            let mail_security: Socket = context
755                .get_config_parsed::<i32>(Config::ConfiguredMailSecurity)
756                .await?
757                .and_then(num_traits::FromPrimitive::from_i32)
758                .unwrap_or_default();
759
760            let send_server = context
761                .get_config(Config::ConfiguredSendServer)
762                .await?
763                .context("SMTP server is not configured")?;
764            let send_port = context
765                .get_config_parsed::<u16>(Config::ConfiguredSendPort)
766                .await?
767                .unwrap_or_default();
768            let send_security: Socket = context
769                .get_config_parsed::<i32>(Config::ConfiguredSendSecurity)
770                .await?
771                .and_then(num_traits::FromPrimitive::from_i32)
772                .unwrap_or_default();
773
774            imap = vec![ConfiguredServerLoginParam {
775                connection: ConnectionCandidate {
776                    host: mail_server,
777                    port: mail_port,
778                    security: mail_security.try_into()?,
779                },
780                user: mail_user.clone(),
781            }];
782            smtp = vec![ConfiguredServerLoginParam {
783                connection: ConnectionCandidate {
784                    host: send_server,
785                    port: send_port,
786                    security: send_security.try_into()?,
787                },
788                user: send_user.clone(),
789            }];
790        }
791
792        Ok(Some(ConfiguredLoginParam {
793            addr,
794            imap,
795            imap_user: mail_user,
796            imap_password: mail_pw,
797            smtp,
798            smtp_user: send_user,
799            smtp_password: send_pw,
800            certificate_checks,
801            provider,
802            oauth2,
803        }))
804    }
805
806    pub(crate) async fn save_to_transports_table(
807        self,
808        context: &Context,
809        entered_param: &EnteredLoginParam,
810    ) -> Result<()> {
811        let addr = addr_normalize(&self.addr);
812        let configured_addr = context.get_config(Config::ConfiguredAddr).await?;
813        if let Some(configured_addr) = configured_addr {
814            ensure!(
815                addr_cmp(&configured_addr, &addr,),
816                "Adding a second transport is not supported right now."
817            );
818        }
819        context
820            .sql
821            .set_raw_config(
822                Config::ConfiguredProvider.as_ref(),
823                self.provider.map(|provider| provider.id),
824            )
825            .await?;
826        context
827            .sql
828            .execute(
829                "INSERT INTO transports (addr, entered_param, configured_param)
830                VALUES (?, ?, ?)
831                ON CONFLICT (addr)
832                DO UPDATE SET entered_param=excluded.entered_param, configured_param=excluded.configured_param",
833                (
834                    self.addr.clone(),
835                    serde_json::to_string(entered_param)?,
836                    self.into_json()?,
837                ),
838            )
839            .await?;
840        context
841            .sql
842            .set_raw_config(Config::ConfiguredAddr.as_ref(), Some(&addr))
843            .await?;
844        Ok(())
845    }
846
847    pub(crate) fn from_json(json: &str) -> Result<Self> {
848        let json: ConfiguredLoginParamJson = serde_json::from_str(json)?;
849
850        let provider = json.provider_id.and_then(|id| get_provider_by_id(&id));
851
852        Ok(ConfiguredLoginParam {
853            addr: json.addr,
854            imap: json.imap,
855            imap_user: json.imap_user,
856            imap_password: json.imap_password,
857            smtp: json.smtp,
858            smtp_user: json.smtp_user,
859            smtp_password: json.smtp_password,
860            provider,
861            certificate_checks: json.certificate_checks,
862            oauth2: json.oauth2,
863        })
864    }
865
866    pub(crate) fn into_json(self) -> Result<String> {
867        let json = ConfiguredLoginParamJson {
868            addr: self.addr,
869            imap: self.imap,
870            imap_user: self.imap_user,
871            imap_password: self.imap_password,
872            smtp: self.smtp,
873            smtp_user: self.smtp_user,
874            smtp_password: self.smtp_password,
875            provider_id: self.provider.map(|p| p.id.to_string()),
876            certificate_checks: self.certificate_checks,
877            oauth2: self.oauth2,
878        };
879        Ok(serde_json::to_string(&json)?)
880    }
881
882    pub(crate) fn strict_tls(&self, connected_through_proxy: bool) -> bool {
883        let provider_strict_tls = self.provider.map(|provider| provider.opt.strict_tls);
884        match self.certificate_checks {
885            ConfiguredCertificateChecks::OldAutomatic => {
886                provider_strict_tls.unwrap_or(connected_through_proxy)
887            }
888            ConfiguredCertificateChecks::Automatic => provider_strict_tls.unwrap_or(true),
889            ConfiguredCertificateChecks::Strict => true,
890            ConfiguredCertificateChecks::AcceptInvalidCertificates
891            | ConfiguredCertificateChecks::AcceptInvalidCertificates2 => false,
892        }
893    }
894}
895
896#[cfg(test)]
897mod tests {
898    use super::*;
899    use crate::log::LogExt as _;
900    use crate::provider::get_provider_by_id;
901    use crate::test_utils::TestContext;
902    use pretty_assertions::assert_eq;
903
904    #[test]
905    fn test_certificate_checks_display() {
906        use std::string::ToString;
907
908        assert_eq!(
909            "accept_invalid_certificates".to_string(),
910            EnteredCertificateChecks::AcceptInvalidCertificates.to_string()
911        );
912
913        assert_eq!(
914            "accept_invalid_certificates".to_string(),
915            ConfiguredCertificateChecks::AcceptInvalidCertificates.to_string()
916        );
917    }
918
919    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
920    async fn test_entered_login_param() -> Result<()> {
921        let t = &TestContext::new().await;
922
923        t.set_config(Config::Addr, Some("alice@example.org"))
924            .await?;
925        t.set_config(Config::MailPw, Some("foobarbaz")).await?;
926
927        let param = EnteredLoginParam::load(t).await?;
928        assert_eq!(param.addr, "alice@example.org");
929        assert_eq!(
930            param.certificate_checks,
931            EnteredCertificateChecks::Automatic
932        );
933
934        t.set_config(Config::ImapCertificateChecks, Some("1"))
935            .await?;
936        let param = EnteredLoginParam::load(t).await?;
937        assert_eq!(param.certificate_checks, EnteredCertificateChecks::Strict);
938
939        // Fail to load invalid settings, but do not panic.
940        t.set_config(Config::ImapCertificateChecks, Some("999"))
941            .await?;
942        assert!(EnteredLoginParam::load(t).await.is_err());
943
944        Ok(())
945    }
946
947    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
948    async fn test_save_entered_login_param() -> Result<()> {
949        let t = TestContext::new().await;
950        let param = EnteredLoginParam {
951            addr: "alice@example.org".to_string(),
952            imap: EnteredServerLoginParam {
953                server: "".to_string(),
954                port: 0,
955                security: Socket::Starttls,
956                user: "".to_string(),
957                password: "foobar".to_string(),
958            },
959            smtp: EnteredServerLoginParam {
960                server: "".to_string(),
961                port: 2947,
962                security: Socket::default(),
963                user: "".to_string(),
964                password: "".to_string(),
965            },
966            certificate_checks: Default::default(),
967            oauth2: false,
968        };
969        param.save(&t).await?;
970        assert_eq!(
971            t.get_config(Config::Addr).await?.unwrap(),
972            "alice@example.org"
973        );
974        assert_eq!(t.get_config(Config::MailPw).await?.unwrap(), "foobar");
975        assert_eq!(t.get_config(Config::SendPw).await?, None);
976        assert_eq!(t.get_config_int(Config::SendPort).await?, 2947);
977
978        assert_eq!(EnteredLoginParam::load(&t).await?, param);
979
980        Ok(())
981    }
982
983    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
984    async fn test_save_load_login_param() -> Result<()> {
985        let t = TestContext::new().await;
986
987        let param = ConfiguredLoginParam {
988            addr: "alice@example.org".to_string(),
989            imap: vec![ConfiguredServerLoginParam {
990                connection: ConnectionCandidate {
991                    host: "imap.example.com".to_string(),
992                    port: 123,
993                    security: ConnectionSecurity::Starttls,
994                },
995                user: "alice".to_string(),
996            }],
997            imap_user: "".to_string(),
998            imap_password: "foo".to_string(),
999            smtp: vec![ConfiguredServerLoginParam {
1000                connection: ConnectionCandidate {
1001                    host: "smtp.example.com".to_string(),
1002                    port: 456,
1003                    security: ConnectionSecurity::Tls,
1004                },
1005                user: "alice@example.org".to_string(),
1006            }],
1007            smtp_user: "".to_string(),
1008            smtp_password: "bar".to_string(),
1009            provider: None,
1010            certificate_checks: ConfiguredCertificateChecks::Strict,
1011            oauth2: false,
1012        };
1013
1014        param
1015            .clone()
1016            .save_to_transports_table(&t, &EnteredLoginParam::default())
1017            .await?;
1018        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}"#;
1019        assert_eq!(
1020            t.sql
1021                .query_get_value::<String>("SELECT configured_param FROM transports", ())
1022                .await?
1023                .unwrap(),
1024            expected_param
1025        );
1026        assert_eq!(t.is_configured().await?, true);
1027        let loaded = ConfiguredLoginParam::load(&t).await?.unwrap();
1028        assert_eq!(param, loaded);
1029
1030        // Legacy ConfiguredImapCertificateChecks config is ignored
1031        t.set_config(Config::ConfiguredImapCertificateChecks, Some("999"))
1032            .await?;
1033        assert!(ConfiguredLoginParam::load(&t).await.is_ok());
1034
1035        // Test that we don't panic on unknown ConfiguredImapCertificateChecks values.
1036        let wrong_param = expected_param.replace("Strict", "Stricct");
1037        assert_ne!(expected_param, wrong_param);
1038        t.sql
1039            .execute("UPDATE transports SET configured_param=?", (wrong_param,))
1040            .await?;
1041        assert!(ConfiguredLoginParam::load(&t).await.is_err());
1042
1043        Ok(())
1044    }
1045
1046    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
1047    async fn test_posteo_alias() -> Result<()> {
1048        let t = TestContext::new().await;
1049
1050        let user = "alice@posteo.de";
1051
1052        // Alice has old config with "alice@posteo.at" address
1053        // and "alice@posteo.de" username.
1054        t.set_config(Config::Configured, Some("1")).await?;
1055        t.set_config(Config::ConfiguredProvider, Some("posteo"))
1056            .await?;
1057        t.sql
1058            .set_raw_config(Config::ConfiguredAddr.as_ref(), Some("alice@posteo.at"))
1059            .await?;
1060        t.set_config(Config::ConfiguredMailServer, Some("posteo.de"))
1061            .await?;
1062        t.set_config(Config::ConfiguredMailPort, Some("993"))
1063            .await?;
1064        t.set_config(Config::ConfiguredMailSecurity, Some("1"))
1065            .await?; // TLS
1066        t.set_config(Config::ConfiguredMailUser, Some(user)).await?;
1067        t.set_config(Config::ConfiguredMailPw, Some("foobarbaz"))
1068            .await?;
1069        t.set_config(Config::ConfiguredImapCertificateChecks, Some("1"))
1070            .await?; // Strict
1071        t.set_config(Config::ConfiguredSendServer, Some("posteo.de"))
1072            .await?;
1073        t.set_config(Config::ConfiguredSendPort, Some("465"))
1074            .await?;
1075        t.set_config(Config::ConfiguredSendSecurity, Some("1"))
1076            .await?; // TLS
1077        t.set_config(Config::ConfiguredSendUser, Some(user)).await?;
1078        t.set_config(Config::ConfiguredSendPw, Some("foobarbaz"))
1079            .await?;
1080        t.set_config(Config::ConfiguredSmtpCertificateChecks, Some("1"))
1081            .await?; // Strict
1082        t.set_config(Config::ConfiguredServerFlags, Some("0"))
1083            .await?;
1084
1085        let param = ConfiguredLoginParam {
1086            addr: "alice@posteo.at".to_string(),
1087            imap: vec![
1088                ConfiguredServerLoginParam {
1089                    connection: ConnectionCandidate {
1090                        host: "posteo.de".to_string(),
1091                        port: 993,
1092                        security: ConnectionSecurity::Tls,
1093                    },
1094                    user: user.to_string(),
1095                },
1096                ConfiguredServerLoginParam {
1097                    connection: ConnectionCandidate {
1098                        host: "posteo.de".to_string(),
1099                        port: 143,
1100                        security: ConnectionSecurity::Starttls,
1101                    },
1102                    user: user.to_string(),
1103                },
1104            ],
1105            imap_user: "alice@posteo.de".to_string(),
1106            imap_password: "foobarbaz".to_string(),
1107            smtp: vec![
1108                ConfiguredServerLoginParam {
1109                    connection: ConnectionCandidate {
1110                        host: "posteo.de".to_string(),
1111                        port: 465,
1112                        security: ConnectionSecurity::Tls,
1113                    },
1114                    user: user.to_string(),
1115                },
1116                ConfiguredServerLoginParam {
1117                    connection: ConnectionCandidate {
1118                        host: "posteo.de".to_string(),
1119                        port: 587,
1120                        security: ConnectionSecurity::Starttls,
1121                    },
1122                    user: user.to_string(),
1123                },
1124            ],
1125            smtp_user: "alice@posteo.de".to_string(),
1126            smtp_password: "foobarbaz".to_string(),
1127            provider: get_provider_by_id("posteo"),
1128            certificate_checks: ConfiguredCertificateChecks::Strict,
1129            oauth2: false,
1130        };
1131
1132        let loaded = ConfiguredLoginParam::load_legacy(&t).await?.unwrap();
1133        assert_eq!(loaded, param);
1134
1135        migrate_configured_login_param(&t).await;
1136        let loaded = ConfiguredLoginParam::load(&t).await?.unwrap();
1137        assert_eq!(loaded, param);
1138
1139        Ok(())
1140    }
1141
1142    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
1143    async fn test_empty_server_list_legacy() -> Result<()> {
1144        // Find a provider that does not have server list set.
1145        //
1146        // There is at least one such provider in the provider database.
1147        let (domain, provider) = crate::provider::data::PROVIDER_DATA
1148            .iter()
1149            .find(|(_domain, provider)| provider.server.is_empty())
1150            .unwrap();
1151
1152        let t = TestContext::new().await;
1153
1154        let addr = format!("alice@{domain}");
1155
1156        t.set_config(Config::Configured, Some("1")).await?;
1157        t.set_config(Config::ConfiguredProvider, Some(provider.id))
1158            .await?;
1159        t.sql
1160            .set_raw_config(Config::ConfiguredAddr.as_ref(), Some(&addr))
1161            .await?;
1162        t.set_config(Config::ConfiguredMailPw, Some("foobarbaz"))
1163            .await?;
1164        t.set_config(Config::ConfiguredImapCertificateChecks, Some("1"))
1165            .await?; // Strict
1166        t.set_config(Config::ConfiguredSendPw, Some("foobarbaz"))
1167            .await?;
1168        t.set_config(Config::ConfiguredSmtpCertificateChecks, Some("1"))
1169            .await?; // Strict
1170        t.set_config(Config::ConfiguredServerFlags, Some("0"))
1171            .await?;
1172
1173        let loaded = ConfiguredLoginParam::load_legacy(&t).await?.unwrap();
1174        assert_eq!(loaded.provider, Some(*provider));
1175        assert_eq!(loaded.imap.is_empty(), false);
1176        assert_eq!(loaded.smtp.is_empty(), false);
1177
1178        migrate_configured_login_param(&t).await;
1179
1180        let loaded = ConfiguredLoginParam::load(&t).await?.unwrap();
1181        assert_eq!(loaded.provider, Some(*provider));
1182        assert_eq!(loaded.imap.is_empty(), false);
1183        assert_eq!(loaded.smtp.is_empty(), false);
1184
1185        Ok(())
1186    }
1187
1188    async fn migrate_configured_login_param(t: &TestContext) {
1189        t.sql.execute("DROP TABLE transports;", ()).await.unwrap();
1190        t.sql.set_raw_config_int("dbversion", 130).await.unwrap();
1191        t.sql.run_migrations(t).await.log_err(t).ok();
1192    }
1193
1194    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
1195    async fn test_empty_server_list() -> Result<()> {
1196        // Find a provider that does not have server list set.
1197        //
1198        // There is at least one such provider in the provider database.
1199        let (domain, provider) = crate::provider::data::PROVIDER_DATA
1200            .iter()
1201            .find(|(_domain, provider)| provider.server.is_empty())
1202            .unwrap();
1203
1204        let t = TestContext::new().await;
1205
1206        let addr = format!("alice@{domain}");
1207
1208        ConfiguredLoginParam {
1209            addr: addr.clone(),
1210            imap: vec![ConfiguredServerLoginParam {
1211                connection: ConnectionCandidate {
1212                    host: "example.org".to_string(),
1213                    port: 100,
1214                    security: ConnectionSecurity::Tls,
1215                },
1216                user: addr.clone(),
1217            }],
1218            imap_user: addr.clone(),
1219            imap_password: "foobarbaz".to_string(),
1220            smtp: vec![ConfiguredServerLoginParam {
1221                connection: ConnectionCandidate {
1222                    host: "example.org".to_string(),
1223                    port: 100,
1224                    security: ConnectionSecurity::Tls,
1225                },
1226                user: addr.clone(),
1227            }],
1228            smtp_user: addr.clone(),
1229            smtp_password: "foobarbaz".to_string(),
1230            provider: Some(provider),
1231            certificate_checks: ConfiguredCertificateChecks::Automatic,
1232            oauth2: false,
1233        }
1234        .save_to_transports_table(&t, &EnteredLoginParam::default())
1235        .await?;
1236
1237        let loaded = ConfiguredLoginParam::load(&t).await?.unwrap();
1238        assert_eq!(loaded.provider, Some(*provider));
1239        assert_eq!(loaded.imap.is_empty(), false);
1240        assert_eq!(loaded.smtp.is_empty(), false);
1241        assert_eq!(t.get_configured_provider().await?, Some(*provider));
1242
1243        Ok(())
1244    }
1245}