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 provider_id = self.provider.map(|provider| provider.id);
813        let configured_addr = context.get_config(Config::ConfiguredAddr).await?;
814        if let Some(configured_addr) = &configured_addr {
815            ensure!(
816                addr_cmp(configured_addr, &addr),
817                "Adding a second transport is not supported right now."
818            );
819        }
820        context
821            .sql
822            .execute(
823                "INSERT INTO transports (addr, entered_param, configured_param)
824                VALUES (?, ?, ?)
825                ON CONFLICT (addr)
826                DO UPDATE SET entered_param=excluded.entered_param, configured_param=excluded.configured_param",
827                (
828                    self.addr.clone(),
829                    serde_json::to_string(entered_param)?,
830                    self.into_json()?,
831                ),
832            )
833            .await?;
834        if configured_addr.is_none() {
835            // If there is no transport yet, set the new transport as the primary one
836            context
837                .sql
838                .set_raw_config(Config::ConfiguredProvider.as_ref(), provider_id)
839                .await?;
840            context
841                .sql
842                .set_raw_config(Config::ConfiguredAddr.as_ref(), Some(&addr))
843                .await?;
844        }
845        Ok(())
846    }
847
848    pub(crate) fn from_json(json: &str) -> Result<Self> {
849        let json: ConfiguredLoginParamJson = serde_json::from_str(json)?;
850
851        let provider = json.provider_id.and_then(|id| get_provider_by_id(&id));
852
853        Ok(ConfiguredLoginParam {
854            addr: json.addr,
855            imap: json.imap,
856            imap_user: json.imap_user,
857            imap_password: json.imap_password,
858            smtp: json.smtp,
859            smtp_user: json.smtp_user,
860            smtp_password: json.smtp_password,
861            provider,
862            certificate_checks: json.certificate_checks,
863            oauth2: json.oauth2,
864        })
865    }
866
867    pub(crate) fn into_json(self) -> Result<String> {
868        let json = ConfiguredLoginParamJson {
869            addr: self.addr,
870            imap: self.imap,
871            imap_user: self.imap_user,
872            imap_password: self.imap_password,
873            smtp: self.smtp,
874            smtp_user: self.smtp_user,
875            smtp_password: self.smtp_password,
876            provider_id: self.provider.map(|p| p.id.to_string()),
877            certificate_checks: self.certificate_checks,
878            oauth2: self.oauth2,
879        };
880        Ok(serde_json::to_string(&json)?)
881    }
882
883    pub(crate) fn strict_tls(&self, connected_through_proxy: bool) -> bool {
884        let provider_strict_tls = self.provider.map(|provider| provider.opt.strict_tls);
885        match self.certificate_checks {
886            ConfiguredCertificateChecks::OldAutomatic => {
887                provider_strict_tls.unwrap_or(connected_through_proxy)
888            }
889            ConfiguredCertificateChecks::Automatic => provider_strict_tls.unwrap_or(true),
890            ConfiguredCertificateChecks::Strict => true,
891            ConfiguredCertificateChecks::AcceptInvalidCertificates
892            | ConfiguredCertificateChecks::AcceptInvalidCertificates2 => false,
893        }
894    }
895}
896
897#[cfg(test)]
898mod tests {
899    use super::*;
900    use crate::log::LogExt as _;
901    use crate::provider::get_provider_by_id;
902    use crate::test_utils::TestContext;
903    use pretty_assertions::assert_eq;
904
905    #[test]
906    fn test_certificate_checks_display() {
907        use std::string::ToString;
908
909        assert_eq!(
910            "accept_invalid_certificates".to_string(),
911            EnteredCertificateChecks::AcceptInvalidCertificates.to_string()
912        );
913
914        assert_eq!(
915            "accept_invalid_certificates".to_string(),
916            ConfiguredCertificateChecks::AcceptInvalidCertificates.to_string()
917        );
918    }
919
920    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
921    async fn test_entered_login_param() -> Result<()> {
922        let t = &TestContext::new().await;
923
924        t.set_config(Config::Addr, Some("alice@example.org"))
925            .await?;
926        t.set_config(Config::MailPw, Some("foobarbaz")).await?;
927
928        let param = EnteredLoginParam::load(t).await?;
929        assert_eq!(param.addr, "alice@example.org");
930        assert_eq!(
931            param.certificate_checks,
932            EnteredCertificateChecks::Automatic
933        );
934
935        t.set_config(Config::ImapCertificateChecks, Some("1"))
936            .await?;
937        let param = EnteredLoginParam::load(t).await?;
938        assert_eq!(param.certificate_checks, EnteredCertificateChecks::Strict);
939
940        // Fail to load invalid settings, but do not panic.
941        t.set_config(Config::ImapCertificateChecks, Some("999"))
942            .await?;
943        assert!(EnteredLoginParam::load(t).await.is_err());
944
945        Ok(())
946    }
947
948    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
949    async fn test_save_entered_login_param() -> Result<()> {
950        let t = TestContext::new().await;
951        let param = EnteredLoginParam {
952            addr: "alice@example.org".to_string(),
953            imap: EnteredServerLoginParam {
954                server: "".to_string(),
955                port: 0,
956                security: Socket::Starttls,
957                user: "".to_string(),
958                password: "foobar".to_string(),
959            },
960            smtp: EnteredServerLoginParam {
961                server: "".to_string(),
962                port: 2947,
963                security: Socket::default(),
964                user: "".to_string(),
965                password: "".to_string(),
966            },
967            certificate_checks: Default::default(),
968            oauth2: false,
969        };
970        param.save(&t).await?;
971        assert_eq!(
972            t.get_config(Config::Addr).await?.unwrap(),
973            "alice@example.org"
974        );
975        assert_eq!(t.get_config(Config::MailPw).await?.unwrap(), "foobar");
976        assert_eq!(t.get_config(Config::SendPw).await?, None);
977        assert_eq!(t.get_config_int(Config::SendPort).await?, 2947);
978
979        assert_eq!(EnteredLoginParam::load(&t).await?, param);
980
981        Ok(())
982    }
983
984    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
985    async fn test_save_load_login_param() -> Result<()> {
986        let t = TestContext::new().await;
987
988        let param = ConfiguredLoginParam {
989            addr: "alice@example.org".to_string(),
990            imap: vec![ConfiguredServerLoginParam {
991                connection: ConnectionCandidate {
992                    host: "imap.example.com".to_string(),
993                    port: 123,
994                    security: ConnectionSecurity::Starttls,
995                },
996                user: "alice".to_string(),
997            }],
998            imap_user: "".to_string(),
999            imap_password: "foo".to_string(),
1000            smtp: vec![ConfiguredServerLoginParam {
1001                connection: ConnectionCandidate {
1002                    host: "smtp.example.com".to_string(),
1003                    port: 456,
1004                    security: ConnectionSecurity::Tls,
1005                },
1006                user: "alice@example.org".to_string(),
1007            }],
1008            smtp_user: "".to_string(),
1009            smtp_password: "bar".to_string(),
1010            provider: None,
1011            certificate_checks: ConfiguredCertificateChecks::Strict,
1012            oauth2: false,
1013        };
1014
1015        param
1016            .clone()
1017            .save_to_transports_table(&t, &EnteredLoginParam::default())
1018            .await?;
1019        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}"#;
1020        assert_eq!(
1021            t.sql
1022                .query_get_value::<String>("SELECT configured_param FROM transports", ())
1023                .await?
1024                .unwrap(),
1025            expected_param
1026        );
1027        assert_eq!(t.is_configured().await?, true);
1028        let loaded = ConfiguredLoginParam::load(&t).await?.unwrap();
1029        assert_eq!(param, loaded);
1030
1031        // Legacy ConfiguredImapCertificateChecks config is ignored
1032        t.set_config(Config::ConfiguredImapCertificateChecks, Some("999"))
1033            .await?;
1034        assert!(ConfiguredLoginParam::load(&t).await.is_ok());
1035
1036        // Test that we don't panic on unknown ConfiguredImapCertificateChecks values.
1037        let wrong_param = expected_param.replace("Strict", "Stricct");
1038        assert_ne!(expected_param, wrong_param);
1039        t.sql
1040            .execute("UPDATE transports SET configured_param=?", (wrong_param,))
1041            .await?;
1042        assert!(ConfiguredLoginParam::load(&t).await.is_err());
1043
1044        Ok(())
1045    }
1046
1047    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
1048    async fn test_posteo_alias() -> Result<()> {
1049        let t = TestContext::new().await;
1050
1051        let user = "alice@posteo.de";
1052
1053        // Alice has old config with "alice@posteo.at" address
1054        // and "alice@posteo.de" username.
1055        t.set_config(Config::Configured, Some("1")).await?;
1056        t.set_config(Config::ConfiguredProvider, Some("posteo"))
1057            .await?;
1058        t.sql
1059            .set_raw_config(Config::ConfiguredAddr.as_ref(), Some("alice@posteo.at"))
1060            .await?;
1061        t.set_config(Config::ConfiguredMailServer, Some("posteo.de"))
1062            .await?;
1063        t.set_config(Config::ConfiguredMailPort, Some("993"))
1064            .await?;
1065        t.set_config(Config::ConfiguredMailSecurity, Some("1"))
1066            .await?; // TLS
1067        t.set_config(Config::ConfiguredMailUser, Some(user)).await?;
1068        t.set_config(Config::ConfiguredMailPw, Some("foobarbaz"))
1069            .await?;
1070        t.set_config(Config::ConfiguredImapCertificateChecks, Some("1"))
1071            .await?; // Strict
1072        t.set_config(Config::ConfiguredSendServer, Some("posteo.de"))
1073            .await?;
1074        t.set_config(Config::ConfiguredSendPort, Some("465"))
1075            .await?;
1076        t.set_config(Config::ConfiguredSendSecurity, Some("1"))
1077            .await?; // TLS
1078        t.set_config(Config::ConfiguredSendUser, Some(user)).await?;
1079        t.set_config(Config::ConfiguredSendPw, Some("foobarbaz"))
1080            .await?;
1081        t.set_config(Config::ConfiguredSmtpCertificateChecks, Some("1"))
1082            .await?; // Strict
1083        t.set_config(Config::ConfiguredServerFlags, Some("0"))
1084            .await?;
1085
1086        let param = ConfiguredLoginParam {
1087            addr: "alice@posteo.at".to_string(),
1088            imap: vec![
1089                ConfiguredServerLoginParam {
1090                    connection: ConnectionCandidate {
1091                        host: "posteo.de".to_string(),
1092                        port: 993,
1093                        security: ConnectionSecurity::Tls,
1094                    },
1095                    user: user.to_string(),
1096                },
1097                ConfiguredServerLoginParam {
1098                    connection: ConnectionCandidate {
1099                        host: "posteo.de".to_string(),
1100                        port: 143,
1101                        security: ConnectionSecurity::Starttls,
1102                    },
1103                    user: user.to_string(),
1104                },
1105            ],
1106            imap_user: "alice@posteo.de".to_string(),
1107            imap_password: "foobarbaz".to_string(),
1108            smtp: vec![
1109                ConfiguredServerLoginParam {
1110                    connection: ConnectionCandidate {
1111                        host: "posteo.de".to_string(),
1112                        port: 465,
1113                        security: ConnectionSecurity::Tls,
1114                    },
1115                    user: user.to_string(),
1116                },
1117                ConfiguredServerLoginParam {
1118                    connection: ConnectionCandidate {
1119                        host: "posteo.de".to_string(),
1120                        port: 587,
1121                        security: ConnectionSecurity::Starttls,
1122                    },
1123                    user: user.to_string(),
1124                },
1125            ],
1126            smtp_user: "alice@posteo.de".to_string(),
1127            smtp_password: "foobarbaz".to_string(),
1128            provider: get_provider_by_id("posteo"),
1129            certificate_checks: ConfiguredCertificateChecks::Strict,
1130            oauth2: false,
1131        };
1132
1133        let loaded = ConfiguredLoginParam::load_legacy(&t).await?.unwrap();
1134        assert_eq!(loaded, param);
1135
1136        migrate_configured_login_param(&t).await;
1137        let loaded = ConfiguredLoginParam::load(&t).await?.unwrap();
1138        assert_eq!(loaded, param);
1139
1140        Ok(())
1141    }
1142
1143    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
1144    async fn test_empty_server_list_legacy() -> Result<()> {
1145        // Find a provider that does not have server list set.
1146        //
1147        // There is at least one such provider in the provider database.
1148        let (domain, provider) = crate::provider::data::PROVIDER_DATA
1149            .iter()
1150            .find(|(_domain, provider)| provider.server.is_empty())
1151            .unwrap();
1152
1153        let t = TestContext::new().await;
1154
1155        let addr = format!("alice@{domain}");
1156
1157        t.set_config(Config::Configured, Some("1")).await?;
1158        t.set_config(Config::ConfiguredProvider, Some(provider.id))
1159            .await?;
1160        t.sql
1161            .set_raw_config(Config::ConfiguredAddr.as_ref(), Some(&addr))
1162            .await?;
1163        t.set_config(Config::ConfiguredMailPw, Some("foobarbaz"))
1164            .await?;
1165        t.set_config(Config::ConfiguredImapCertificateChecks, Some("1"))
1166            .await?; // Strict
1167        t.set_config(Config::ConfiguredSendPw, Some("foobarbaz"))
1168            .await?;
1169        t.set_config(Config::ConfiguredSmtpCertificateChecks, Some("1"))
1170            .await?; // Strict
1171        t.set_config(Config::ConfiguredServerFlags, Some("0"))
1172            .await?;
1173
1174        let loaded = ConfiguredLoginParam::load_legacy(&t).await?.unwrap();
1175        assert_eq!(loaded.provider, Some(*provider));
1176        assert_eq!(loaded.imap.is_empty(), false);
1177        assert_eq!(loaded.smtp.is_empty(), false);
1178
1179        migrate_configured_login_param(&t).await;
1180
1181        let loaded = ConfiguredLoginParam::load(&t).await?.unwrap();
1182        assert_eq!(loaded.provider, Some(*provider));
1183        assert_eq!(loaded.imap.is_empty(), false);
1184        assert_eq!(loaded.smtp.is_empty(), false);
1185
1186        Ok(())
1187    }
1188
1189    async fn migrate_configured_login_param(t: &TestContext) {
1190        t.sql.execute("DROP TABLE transports;", ()).await.unwrap();
1191        t.sql.set_raw_config_int("dbversion", 130).await.unwrap();
1192        t.sql.run_migrations(t).await.log_err(t).ok();
1193    }
1194
1195    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
1196    async fn test_empty_server_list() -> Result<()> {
1197        // Find a provider that does not have server list set.
1198        //
1199        // There is at least one such provider in the provider database.
1200        let (domain, provider) = crate::provider::data::PROVIDER_DATA
1201            .iter()
1202            .find(|(_domain, provider)| provider.server.is_empty())
1203            .unwrap();
1204
1205        let t = TestContext::new().await;
1206
1207        let addr = format!("alice@{domain}");
1208
1209        ConfiguredLoginParam {
1210            addr: addr.clone(),
1211            imap: vec![ConfiguredServerLoginParam {
1212                connection: ConnectionCandidate {
1213                    host: "example.org".to_string(),
1214                    port: 100,
1215                    security: ConnectionSecurity::Tls,
1216                },
1217                user: addr.clone(),
1218            }],
1219            imap_user: addr.clone(),
1220            imap_password: "foobarbaz".to_string(),
1221            smtp: vec![ConfiguredServerLoginParam {
1222                connection: ConnectionCandidate {
1223                    host: "example.org".to_string(),
1224                    port: 100,
1225                    security: ConnectionSecurity::Tls,
1226                },
1227                user: addr.clone(),
1228            }],
1229            smtp_user: addr.clone(),
1230            smtp_password: "foobarbaz".to_string(),
1231            provider: Some(provider),
1232            certificate_checks: ConfiguredCertificateChecks::Automatic,
1233            oauth2: false,
1234        }
1235        .save_to_transports_table(&t, &EnteredLoginParam::default())
1236        .await?;
1237
1238        let loaded = ConfiguredLoginParam::load(&t).await?.unwrap();
1239        assert_eq!(loaded.provider, Some(*provider));
1240        assert_eq!(loaded.imap.is_empty(), false);
1241        assert_eq!(loaded.smtp.is_empty(), false);
1242        assert_eq!(t.get_configured_provider().await?, Some(*provider));
1243
1244        Ok(())
1245    }
1246}