deltachat/
login_param.rs

1//! # Login parameters.
2
3use std::fmt;
4
5use anyhow::{Context as _, Result, bail, ensure, format_err};
6use deltachat_contact_tools::{EmailAddress, addr_cmp, addr_normalize};
7use num_traits::ToPrimitive as _;
8use serde::{Deserialize, Serialize};
9
10use crate::config::Config;
11use crate::configure::server_params::{ServerParams, expand_param_vector};
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::{Protocol, Provider, UsernamePattern, get_provider_by_id};
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() { "unset" } else { s }
334}
335
336#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
337pub(crate) struct ConnectionCandidate {
338    /// Server hostname or IP address.
339    pub host: String,
340
341    /// Server port.
342    pub port: u16,
343
344    /// Transport layer security.
345    pub security: ConnectionSecurity,
346}
347
348impl fmt::Display for ConnectionCandidate {
349    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
350        write!(f, "{}:{}:{}", &self.host, self.port, self.security)?;
351        Ok(())
352    }
353}
354
355#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
356pub(crate) enum ConnectionSecurity {
357    /// Implicit TLS.
358    Tls,
359
360    // STARTTLS.
361    Starttls,
362
363    /// Plaintext.
364    Plain,
365}
366
367impl fmt::Display for ConnectionSecurity {
368    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
369        match self {
370            Self::Tls => write!(f, "tls")?,
371            Self::Starttls => write!(f, "starttls")?,
372            Self::Plain => write!(f, "plain")?,
373        }
374        Ok(())
375    }
376}
377
378impl TryFrom<Socket> for ConnectionSecurity {
379    type Error = anyhow::Error;
380
381    fn try_from(socket: Socket) -> Result<Self> {
382        match socket {
383            Socket::Automatic => Err(format_err!("Socket security is not configured")),
384            Socket::Ssl => Ok(Self::Tls),
385            Socket::Starttls => Ok(Self::Starttls),
386            Socket::Plain => Ok(Self::Plain),
387        }
388    }
389}
390
391#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
392pub(crate) struct ConfiguredServerLoginParam {
393    pub connection: ConnectionCandidate,
394
395    /// Username.
396    pub user: String,
397}
398
399impl fmt::Display for ConfiguredServerLoginParam {
400    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
401        write!(f, "{}:{}", self.connection, &self.user)?;
402        Ok(())
403    }
404}
405
406pub(crate) async fn prioritize_server_login_params(
407    sql: &Sql,
408    params: &[ConfiguredServerLoginParam],
409    alpn: &str,
410) -> Result<Vec<ConfiguredServerLoginParam>> {
411    let mut res: Vec<(Option<i64>, ConfiguredServerLoginParam)> = Vec::with_capacity(params.len());
412    for param in params {
413        let timestamp = load_connection_timestamp(
414            sql,
415            alpn,
416            &param.connection.host,
417            param.connection.port,
418            None,
419        )
420        .await?;
421        res.push((timestamp, param.clone()));
422    }
423    res.sort_by_key(|(ts, _param)| std::cmp::Reverse(*ts));
424    Ok(res.into_iter().map(|(_ts, param)| param).collect())
425}
426
427/// Login parameters saved to the database
428/// after successful configuration.
429#[derive(Debug, Clone, PartialEq, Eq)]
430pub(crate) struct ConfiguredLoginParam {
431    /// `From:` address that was used at the time of configuration.
432    pub addr: String,
433
434    pub imap: Vec<ConfiguredServerLoginParam>,
435
436    // Custom IMAP user.
437    //
438    // This overwrites autoconfig from the provider database
439    // if non-empty.
440    pub imap_user: String,
441
442    pub imap_password: String,
443
444    pub smtp: Vec<ConfiguredServerLoginParam>,
445
446    // Custom SMTP user.
447    //
448    // This overwrites autoconfig from the provider database
449    // if non-empty.
450    pub smtp_user: String,
451
452    pub smtp_password: String,
453
454    pub provider: Option<&'static Provider>,
455
456    /// TLS options: whether to allow invalid certificates and/or
457    /// invalid hostnames
458    pub certificate_checks: ConfiguredCertificateChecks,
459
460    /// If true, login via OAUTH2 (not recommended anymore)
461    pub oauth2: bool,
462}
463
464/// The representation of ConfiguredLoginParam in the database,
465/// saved as Json.
466#[derive(Debug, Serialize, Deserialize)]
467struct ConfiguredLoginParamJson {
468    pub addr: String,
469    pub imap: Vec<ConfiguredServerLoginParam>,
470    pub imap_user: String,
471    pub imap_password: String,
472    pub smtp: Vec<ConfiguredServerLoginParam>,
473    pub smtp_user: String,
474    pub smtp_password: String,
475    pub provider_id: Option<String>,
476    pub certificate_checks: ConfiguredCertificateChecks,
477    pub oauth2: bool,
478}
479
480impl fmt::Display for ConfiguredLoginParam {
481    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
482        let addr = &self.addr;
483        let provider_id = match self.provider {
484            Some(provider) => provider.id,
485            None => "none",
486        };
487        let certificate_checks = self.certificate_checks;
488        write!(f, "{addr} imap:[")?;
489        let mut first = true;
490        for imap in &self.imap {
491            if !first {
492                write!(f, ", ")?;
493            }
494            write!(f, "{imap}")?;
495            first = false;
496        }
497        write!(f, "] smtp:[")?;
498        let mut first = true;
499        for smtp in &self.smtp {
500            if !first {
501                write!(f, ", ")?;
502            }
503            write!(f, "{smtp}")?;
504            first = false;
505        }
506        write!(f, "] provider:{provider_id} cert_{certificate_checks}")?;
507        Ok(())
508    }
509}
510
511impl ConfiguredLoginParam {
512    /// Load configured account settings from the database.
513    ///
514    /// Returns `None` if account is not configured.
515    pub(crate) async fn load(context: &Context) -> Result<Option<Self>> {
516        let Some(self_addr) = context.get_config(Config::ConfiguredAddr).await? else {
517            return Ok(None);
518        };
519
520        let json: Option<String> = context
521            .sql
522            .query_get_value(
523                "SELECT configured_param FROM transports WHERE addr=?",
524                (&self_addr,),
525            )
526            .await?;
527        if let Some(json) = json {
528            Ok(Some(Self::from_json(&json)?))
529        } else {
530            bail!("Self address {self_addr} doesn't have a corresponding transport");
531        }
532    }
533
534    /// Loads legacy configured param. Only used for tests and the migration.
535    pub(crate) async fn load_legacy(context: &Context) -> Result<Option<Self>> {
536        if !context.get_config_bool(Config::Configured).await? {
537            return Ok(None);
538        }
539
540        let addr = context
541            .get_config(Config::ConfiguredAddr)
542            .await?
543            .unwrap_or_default()
544            .trim()
545            .to_string();
546
547        let certificate_checks: ConfiguredCertificateChecks = if let Some(certificate_checks) =
548            context
549                .get_config_parsed::<i32>(Config::ConfiguredImapCertificateChecks)
550                .await?
551        {
552            num_traits::FromPrimitive::from_i32(certificate_checks)
553                .context("Invalid configured_imap_certificate_checks value")?
554        } else {
555            // This is true for old accounts configured using C core
556            // which did not check TLS certificates.
557            ConfiguredCertificateChecks::OldAutomatic
558        };
559
560        let send_pw = context
561            .get_config(Config::ConfiguredSendPw)
562            .await?
563            .context("SMTP password is not configured")?;
564        let mail_pw = context
565            .get_config(Config::ConfiguredMailPw)
566            .await?
567            .context("IMAP password is not configured")?;
568
569        let server_flags = context
570            .get_config_parsed::<i32>(Config::ConfiguredServerFlags)
571            .await?
572            .unwrap_or_default();
573        let oauth2 = matches!(server_flags & DC_LP_AUTH_FLAGS, DC_LP_AUTH_OAUTH2);
574
575        let provider = context.get_configured_provider().await?;
576
577        let imap;
578        let smtp;
579
580        let mail_user = context
581            .get_config(Config::ConfiguredMailUser)
582            .await?
583            .unwrap_or_default();
584        let send_user = context
585            .get_config(Config::ConfiguredSendUser)
586            .await?
587            .unwrap_or_default();
588
589        if let Some(provider) = provider {
590            let parsed_addr = EmailAddress::new(&addr).context("Bad email-address")?;
591            let addr_localpart = parsed_addr.local;
592
593            if provider.server.is_empty() {
594                let servers = vec![
595                    ServerParams {
596                        protocol: Protocol::Imap,
597                        hostname: context
598                            .get_config(Config::ConfiguredMailServer)
599                            .await?
600                            .unwrap_or_default(),
601                        port: context
602                            .get_config_parsed::<u16>(Config::ConfiguredMailPort)
603                            .await?
604                            .unwrap_or_default(),
605                        socket: context
606                            .get_config_parsed::<i32>(Config::ConfiguredMailSecurity)
607                            .await?
608                            .and_then(num_traits::FromPrimitive::from_i32)
609                            .unwrap_or_default(),
610                        username: mail_user.clone(),
611                    },
612                    ServerParams {
613                        protocol: Protocol::Smtp,
614                        hostname: context
615                            .get_config(Config::ConfiguredSendServer)
616                            .await?
617                            .unwrap_or_default(),
618                        port: context
619                            .get_config_parsed::<u16>(Config::ConfiguredSendPort)
620                            .await?
621                            .unwrap_or_default(),
622                        socket: context
623                            .get_config_parsed::<i32>(Config::ConfiguredSendSecurity)
624                            .await?
625                            .and_then(num_traits::FromPrimitive::from_i32)
626                            .unwrap_or_default(),
627                        username: send_user.clone(),
628                    },
629                ];
630                let servers = expand_param_vector(servers, &addr, &parsed_addr.domain);
631                imap = servers
632                    .iter()
633                    .filter_map(|params| {
634                        let Ok(security) = params.socket.try_into() else {
635                            return None;
636                        };
637                        if params.protocol == Protocol::Imap {
638                            Some(ConfiguredServerLoginParam {
639                                connection: ConnectionCandidate {
640                                    host: params.hostname.clone(),
641                                    port: params.port,
642                                    security,
643                                },
644                                user: params.username.clone(),
645                            })
646                        } else {
647                            None
648                        }
649                    })
650                    .collect();
651                smtp = servers
652                    .iter()
653                    .filter_map(|params| {
654                        let Ok(security) = params.socket.try_into() else {
655                            return None;
656                        };
657                        if params.protocol == Protocol::Smtp {
658                            Some(ConfiguredServerLoginParam {
659                                connection: ConnectionCandidate {
660                                    host: params.hostname.clone(),
661                                    port: params.port,
662                                    security,
663                                },
664                                user: params.username.clone(),
665                            })
666                        } else {
667                            None
668                        }
669                    })
670                    .collect();
671            } else {
672                imap = provider
673                    .server
674                    .iter()
675                    .filter_map(|server| {
676                        if server.protocol != Protocol::Imap {
677                            return None;
678                        }
679
680                        let Ok(security) = server.socket.try_into() else {
681                            return None;
682                        };
683
684                        Some(ConfiguredServerLoginParam {
685                            connection: ConnectionCandidate {
686                                host: server.hostname.to_string(),
687                                port: server.port,
688                                security,
689                            },
690                            user: if !mail_user.is_empty() {
691                                mail_user.clone()
692                            } else {
693                                match server.username_pattern {
694                                    UsernamePattern::Email => addr.to_string(),
695                                    UsernamePattern::Emaillocalpart => addr_localpart.clone(),
696                                }
697                            },
698                        })
699                    })
700                    .collect();
701                smtp = provider
702                    .server
703                    .iter()
704                    .filter_map(|server| {
705                        if server.protocol != Protocol::Smtp {
706                            return None;
707                        }
708
709                        let Ok(security) = server.socket.try_into() else {
710                            return None;
711                        };
712
713                        Some(ConfiguredServerLoginParam {
714                            connection: ConnectionCandidate {
715                                host: server.hostname.to_string(),
716                                port: server.port,
717                                security,
718                            },
719                            user: if !send_user.is_empty() {
720                                send_user.clone()
721                            } else {
722                                match server.username_pattern {
723                                    UsernamePattern::Email => addr.to_string(),
724                                    UsernamePattern::Emaillocalpart => addr_localpart.clone(),
725                                }
726                            },
727                        })
728                    })
729                    .collect();
730            }
731        } else if let (Some(configured_mail_servers), Some(configured_send_servers)) = (
732            context.get_config(Config::ConfiguredImapServers).await?,
733            context.get_config(Config::ConfiguredSmtpServers).await?,
734        ) {
735            imap = serde_json::from_str(&configured_mail_servers)
736                .context("Failed to parse configured IMAP servers")?;
737            smtp = serde_json::from_str(&configured_send_servers)
738                .context("Failed to parse configured SMTP servers")?;
739        } else {
740            // Load legacy settings storing a single IMAP and single SMTP server.
741            let mail_server = context
742                .get_config(Config::ConfiguredMailServer)
743                .await?
744                .unwrap_or_default();
745            let mail_port = context
746                .get_config_parsed::<u16>(Config::ConfiguredMailPort)
747                .await?
748                .unwrap_or_default();
749
750            let mail_security: Socket = context
751                .get_config_parsed::<i32>(Config::ConfiguredMailSecurity)
752                .await?
753                .and_then(num_traits::FromPrimitive::from_i32)
754                .unwrap_or_default();
755
756            let send_server = context
757                .get_config(Config::ConfiguredSendServer)
758                .await?
759                .context("SMTP server is not configured")?;
760            let send_port = context
761                .get_config_parsed::<u16>(Config::ConfiguredSendPort)
762                .await?
763                .unwrap_or_default();
764            let send_security: Socket = context
765                .get_config_parsed::<i32>(Config::ConfiguredSendSecurity)
766                .await?
767                .and_then(num_traits::FromPrimitive::from_i32)
768                .unwrap_or_default();
769
770            imap = vec![ConfiguredServerLoginParam {
771                connection: ConnectionCandidate {
772                    host: mail_server,
773                    port: mail_port,
774                    security: mail_security.try_into()?,
775                },
776                user: mail_user.clone(),
777            }];
778            smtp = vec![ConfiguredServerLoginParam {
779                connection: ConnectionCandidate {
780                    host: send_server,
781                    port: send_port,
782                    security: send_security.try_into()?,
783                },
784                user: send_user.clone(),
785            }];
786        }
787
788        Ok(Some(ConfiguredLoginParam {
789            addr,
790            imap,
791            imap_user: mail_user,
792            imap_password: mail_pw,
793            smtp,
794            smtp_user: send_user,
795            smtp_password: send_pw,
796            certificate_checks,
797            provider,
798            oauth2,
799        }))
800    }
801
802    pub(crate) async fn save_to_transports_table(
803        self,
804        context: &Context,
805        entered_param: &EnteredLoginParam,
806    ) -> Result<()> {
807        let addr = addr_normalize(&self.addr);
808        let provider_id = self.provider.map(|provider| provider.id);
809        let configured_addr = context.get_config(Config::ConfiguredAddr).await?;
810        if let Some(configured_addr) = &configured_addr {
811            ensure!(
812                addr_cmp(configured_addr, &addr),
813                "Adding a second transport is not supported right now."
814            );
815        }
816        context
817            .sql
818            .execute(
819                "INSERT INTO transports (addr, entered_param, configured_param)
820                VALUES (?, ?, ?)
821                ON CONFLICT (addr)
822                DO UPDATE SET entered_param=excluded.entered_param, configured_param=excluded.configured_param",
823                (
824                    self.addr.clone(),
825                    serde_json::to_string(entered_param)?,
826                    self.into_json()?,
827                ),
828            )
829            .await?;
830        if configured_addr.is_none() {
831            // If there is no transport yet, set the new transport as the primary one
832            context
833                .sql
834                .set_raw_config(Config::ConfiguredProvider.as_ref(), provider_id)
835                .await?;
836            context
837                .sql
838                .set_raw_config(Config::ConfiguredAddr.as_ref(), Some(&addr))
839                .await?;
840        }
841        Ok(())
842    }
843
844    pub(crate) fn from_json(json: &str) -> Result<Self> {
845        let json: ConfiguredLoginParamJson = serde_json::from_str(json)?;
846
847        let provider = json.provider_id.and_then(|id| get_provider_by_id(&id));
848
849        Ok(ConfiguredLoginParam {
850            addr: json.addr,
851            imap: json.imap,
852            imap_user: json.imap_user,
853            imap_password: json.imap_password,
854            smtp: json.smtp,
855            smtp_user: json.smtp_user,
856            smtp_password: json.smtp_password,
857            provider,
858            certificate_checks: json.certificate_checks,
859            oauth2: json.oauth2,
860        })
861    }
862
863    pub(crate) fn into_json(self) -> Result<String> {
864        let json = ConfiguredLoginParamJson {
865            addr: self.addr,
866            imap: self.imap,
867            imap_user: self.imap_user,
868            imap_password: self.imap_password,
869            smtp: self.smtp,
870            smtp_user: self.smtp_user,
871            smtp_password: self.smtp_password,
872            provider_id: self.provider.map(|p| p.id.to_string()),
873            certificate_checks: self.certificate_checks,
874            oauth2: self.oauth2,
875        };
876        Ok(serde_json::to_string(&json)?)
877    }
878
879    pub(crate) fn strict_tls(&self, connected_through_proxy: bool) -> bool {
880        let provider_strict_tls = self.provider.map(|provider| provider.opt.strict_tls);
881        match self.certificate_checks {
882            ConfiguredCertificateChecks::OldAutomatic => {
883                provider_strict_tls.unwrap_or(connected_through_proxy)
884            }
885            ConfiguredCertificateChecks::Automatic => provider_strict_tls.unwrap_or(true),
886            ConfiguredCertificateChecks::Strict => true,
887            ConfiguredCertificateChecks::AcceptInvalidCertificates
888            | ConfiguredCertificateChecks::AcceptInvalidCertificates2 => false,
889        }
890    }
891}
892
893#[cfg(test)]
894mod tests {
895    use super::*;
896    use crate::log::LogExt as _;
897    use crate::provider::get_provider_by_id;
898    use crate::test_utils::TestContext;
899    use pretty_assertions::assert_eq;
900
901    #[test]
902    fn test_certificate_checks_display() {
903        use std::string::ToString;
904
905        assert_eq!(
906            "accept_invalid_certificates".to_string(),
907            EnteredCertificateChecks::AcceptInvalidCertificates.to_string()
908        );
909
910        assert_eq!(
911            "accept_invalid_certificates".to_string(),
912            ConfiguredCertificateChecks::AcceptInvalidCertificates.to_string()
913        );
914    }
915
916    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
917    async fn test_entered_login_param() -> Result<()> {
918        let t = &TestContext::new().await;
919
920        t.set_config(Config::Addr, Some("alice@example.org"))
921            .await?;
922        t.set_config(Config::MailPw, Some("foobarbaz")).await?;
923
924        let param = EnteredLoginParam::load(t).await?;
925        assert_eq!(param.addr, "alice@example.org");
926        assert_eq!(
927            param.certificate_checks,
928            EnteredCertificateChecks::Automatic
929        );
930
931        t.set_config(Config::ImapCertificateChecks, Some("1"))
932            .await?;
933        let param = EnteredLoginParam::load(t).await?;
934        assert_eq!(param.certificate_checks, EnteredCertificateChecks::Strict);
935
936        // Fail to load invalid settings, but do not panic.
937        t.set_config(Config::ImapCertificateChecks, Some("999"))
938            .await?;
939        assert!(EnteredLoginParam::load(t).await.is_err());
940
941        Ok(())
942    }
943
944    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
945    async fn test_save_entered_login_param() -> Result<()> {
946        let t = TestContext::new().await;
947        let param = EnteredLoginParam {
948            addr: "alice@example.org".to_string(),
949            imap: EnteredServerLoginParam {
950                server: "".to_string(),
951                port: 0,
952                security: Socket::Starttls,
953                user: "".to_string(),
954                password: "foobar".to_string(),
955            },
956            smtp: EnteredServerLoginParam {
957                server: "".to_string(),
958                port: 2947,
959                security: Socket::default(),
960                user: "".to_string(),
961                password: "".to_string(),
962            },
963            certificate_checks: Default::default(),
964            oauth2: false,
965        };
966        param.save(&t).await?;
967        assert_eq!(
968            t.get_config(Config::Addr).await?.unwrap(),
969            "alice@example.org"
970        );
971        assert_eq!(t.get_config(Config::MailPw).await?.unwrap(), "foobar");
972        assert_eq!(t.get_config(Config::SendPw).await?, None);
973        assert_eq!(t.get_config_int(Config::SendPort).await?, 2947);
974
975        assert_eq!(EnteredLoginParam::load(&t).await?, param);
976
977        Ok(())
978    }
979
980    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
981    async fn test_save_load_login_param() -> Result<()> {
982        let t = TestContext::new().await;
983
984        let param = ConfiguredLoginParam {
985            addr: "alice@example.org".to_string(),
986            imap: vec![ConfiguredServerLoginParam {
987                connection: ConnectionCandidate {
988                    host: "imap.example.com".to_string(),
989                    port: 123,
990                    security: ConnectionSecurity::Starttls,
991                },
992                user: "alice".to_string(),
993            }],
994            imap_user: "".to_string(),
995            imap_password: "foo".to_string(),
996            smtp: vec![ConfiguredServerLoginParam {
997                connection: ConnectionCandidate {
998                    host: "smtp.example.com".to_string(),
999                    port: 456,
1000                    security: ConnectionSecurity::Tls,
1001                },
1002                user: "alice@example.org".to_string(),
1003            }],
1004            smtp_user: "".to_string(),
1005            smtp_password: "bar".to_string(),
1006            provider: None,
1007            certificate_checks: ConfiguredCertificateChecks::Strict,
1008            oauth2: false,
1009        };
1010
1011        param
1012            .clone()
1013            .save_to_transports_table(&t, &EnteredLoginParam::default())
1014            .await?;
1015        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}"#;
1016        assert_eq!(
1017            t.sql
1018                .query_get_value::<String>("SELECT configured_param FROM transports", ())
1019                .await?
1020                .unwrap(),
1021            expected_param
1022        );
1023        assert_eq!(t.is_configured().await?, true);
1024        let loaded = ConfiguredLoginParam::load(&t).await?.unwrap();
1025        assert_eq!(param, loaded);
1026
1027        // Legacy ConfiguredImapCertificateChecks config is ignored
1028        t.set_config(Config::ConfiguredImapCertificateChecks, Some("999"))
1029            .await?;
1030        assert!(ConfiguredLoginParam::load(&t).await.is_ok());
1031
1032        // Test that we don't panic on unknown ConfiguredImapCertificateChecks values.
1033        let wrong_param = expected_param.replace("Strict", "Stricct");
1034        assert_ne!(expected_param, wrong_param);
1035        t.sql
1036            .execute("UPDATE transports SET configured_param=?", (wrong_param,))
1037            .await?;
1038        assert!(ConfiguredLoginParam::load(&t).await.is_err());
1039
1040        Ok(())
1041    }
1042
1043    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
1044    async fn test_posteo_alias() -> Result<()> {
1045        let t = TestContext::new().await;
1046
1047        let user = "alice@posteo.de";
1048
1049        // Alice has old config with "alice@posteo.at" address
1050        // and "alice@posteo.de" username.
1051        t.set_config(Config::Configured, Some("1")).await?;
1052        t.set_config(Config::ConfiguredProvider, Some("posteo"))
1053            .await?;
1054        t.sql
1055            .set_raw_config(Config::ConfiguredAddr.as_ref(), Some("alice@posteo.at"))
1056            .await?;
1057        t.set_config(Config::ConfiguredMailServer, Some("posteo.de"))
1058            .await?;
1059        t.set_config(Config::ConfiguredMailPort, Some("993"))
1060            .await?;
1061        t.set_config(Config::ConfiguredMailSecurity, Some("1"))
1062            .await?; // TLS
1063        t.set_config(Config::ConfiguredMailUser, Some(user)).await?;
1064        t.set_config(Config::ConfiguredMailPw, Some("foobarbaz"))
1065            .await?;
1066        t.set_config(Config::ConfiguredImapCertificateChecks, Some("1"))
1067            .await?; // Strict
1068        t.set_config(Config::ConfiguredSendServer, Some("posteo.de"))
1069            .await?;
1070        t.set_config(Config::ConfiguredSendPort, Some("465"))
1071            .await?;
1072        t.set_config(Config::ConfiguredSendSecurity, Some("1"))
1073            .await?; // TLS
1074        t.set_config(Config::ConfiguredSendUser, Some(user)).await?;
1075        t.set_config(Config::ConfiguredSendPw, Some("foobarbaz"))
1076            .await?;
1077        t.set_config(Config::ConfiguredSmtpCertificateChecks, Some("1"))
1078            .await?; // Strict
1079        t.set_config(Config::ConfiguredServerFlags, Some("0"))
1080            .await?;
1081
1082        let param = ConfiguredLoginParam {
1083            addr: "alice@posteo.at".to_string(),
1084            imap: vec![
1085                ConfiguredServerLoginParam {
1086                    connection: ConnectionCandidate {
1087                        host: "posteo.de".to_string(),
1088                        port: 993,
1089                        security: ConnectionSecurity::Tls,
1090                    },
1091                    user: user.to_string(),
1092                },
1093                ConfiguredServerLoginParam {
1094                    connection: ConnectionCandidate {
1095                        host: "posteo.de".to_string(),
1096                        port: 143,
1097                        security: ConnectionSecurity::Starttls,
1098                    },
1099                    user: user.to_string(),
1100                },
1101            ],
1102            imap_user: "alice@posteo.de".to_string(),
1103            imap_password: "foobarbaz".to_string(),
1104            smtp: vec![
1105                ConfiguredServerLoginParam {
1106                    connection: ConnectionCandidate {
1107                        host: "posteo.de".to_string(),
1108                        port: 465,
1109                        security: ConnectionSecurity::Tls,
1110                    },
1111                    user: user.to_string(),
1112                },
1113                ConfiguredServerLoginParam {
1114                    connection: ConnectionCandidate {
1115                        host: "posteo.de".to_string(),
1116                        port: 587,
1117                        security: ConnectionSecurity::Starttls,
1118                    },
1119                    user: user.to_string(),
1120                },
1121            ],
1122            smtp_user: "alice@posteo.de".to_string(),
1123            smtp_password: "foobarbaz".to_string(),
1124            provider: get_provider_by_id("posteo"),
1125            certificate_checks: ConfiguredCertificateChecks::Strict,
1126            oauth2: false,
1127        };
1128
1129        let loaded = ConfiguredLoginParam::load_legacy(&t).await?.unwrap();
1130        assert_eq!(loaded, param);
1131
1132        migrate_configured_login_param(&t).await;
1133        let loaded = ConfiguredLoginParam::load(&t).await?.unwrap();
1134        assert_eq!(loaded, param);
1135
1136        Ok(())
1137    }
1138
1139    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
1140    async fn test_empty_server_list_legacy() -> Result<()> {
1141        // Find a provider that does not have server list set.
1142        //
1143        // There is at least one such provider in the provider database.
1144        let (domain, provider) = crate::provider::data::PROVIDER_DATA
1145            .iter()
1146            .find(|(_domain, provider)| provider.server.is_empty())
1147            .unwrap();
1148
1149        let t = TestContext::new().await;
1150
1151        let addr = format!("alice@{domain}");
1152
1153        t.set_config(Config::Configured, Some("1")).await?;
1154        t.set_config(Config::ConfiguredProvider, Some(provider.id))
1155            .await?;
1156        t.sql
1157            .set_raw_config(Config::ConfiguredAddr.as_ref(), Some(&addr))
1158            .await?;
1159        t.set_config(Config::ConfiguredMailPw, Some("foobarbaz"))
1160            .await?;
1161        t.set_config(Config::ConfiguredImapCertificateChecks, Some("1"))
1162            .await?; // Strict
1163        t.set_config(Config::ConfiguredSendPw, Some("foobarbaz"))
1164            .await?;
1165        t.set_config(Config::ConfiguredSmtpCertificateChecks, Some("1"))
1166            .await?; // Strict
1167        t.set_config(Config::ConfiguredServerFlags, Some("0"))
1168            .await?;
1169
1170        let loaded = ConfiguredLoginParam::load_legacy(&t).await?.unwrap();
1171        assert_eq!(loaded.provider, Some(*provider));
1172        assert_eq!(loaded.imap.is_empty(), false);
1173        assert_eq!(loaded.smtp.is_empty(), false);
1174
1175        migrate_configured_login_param(&t).await;
1176
1177        let loaded = ConfiguredLoginParam::load(&t).await?.unwrap();
1178        assert_eq!(loaded.provider, Some(*provider));
1179        assert_eq!(loaded.imap.is_empty(), false);
1180        assert_eq!(loaded.smtp.is_empty(), false);
1181
1182        Ok(())
1183    }
1184
1185    async fn migrate_configured_login_param(t: &TestContext) {
1186        t.sql.execute("DROP TABLE transports;", ()).await.unwrap();
1187        t.sql.set_raw_config_int("dbversion", 130).await.unwrap();
1188        t.sql.run_migrations(t).await.log_err(t).ok();
1189    }
1190
1191    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
1192    async fn test_empty_server_list() -> Result<()> {
1193        // Find a provider that does not have server list set.
1194        //
1195        // There is at least one such provider in the provider database.
1196        let (domain, provider) = crate::provider::data::PROVIDER_DATA
1197            .iter()
1198            .find(|(_domain, provider)| provider.server.is_empty())
1199            .unwrap();
1200
1201        let t = TestContext::new().await;
1202
1203        let addr = format!("alice@{domain}");
1204
1205        ConfiguredLoginParam {
1206            addr: addr.clone(),
1207            imap: vec![ConfiguredServerLoginParam {
1208                connection: ConnectionCandidate {
1209                    host: "example.org".to_string(),
1210                    port: 100,
1211                    security: ConnectionSecurity::Tls,
1212                },
1213                user: addr.clone(),
1214            }],
1215            imap_user: addr.clone(),
1216            imap_password: "foobarbaz".to_string(),
1217            smtp: vec![ConfiguredServerLoginParam {
1218                connection: ConnectionCandidate {
1219                    host: "example.org".to_string(),
1220                    port: 100,
1221                    security: ConnectionSecurity::Tls,
1222                },
1223                user: addr.clone(),
1224            }],
1225            smtp_user: addr.clone(),
1226            smtp_password: "foobarbaz".to_string(),
1227            provider: Some(provider),
1228            certificate_checks: ConfiguredCertificateChecks::Automatic,
1229            oauth2: false,
1230        }
1231        .save_to_transports_table(&t, &EnteredLoginParam::default())
1232        .await?;
1233
1234        let loaded = ConfiguredLoginParam::load(&t).await?.unwrap();
1235        assert_eq!(loaded.provider, Some(*provider));
1236        assert_eq!(loaded.imap.is_empty(), false);
1237        assert_eq!(loaded.smtp.is_empty(), false);
1238        assert_eq!(t.get_configured_provider().await?, Some(*provider));
1239
1240        Ok(())
1241    }
1242}