deltachat/
login_param.rs

1//! # Login parameters.
2//!
3//! Login parameters are entered by the user
4//! to configure a new transport.
5//! Login parameters may also be entered
6//! implicitly by scanning a QR code
7//! of `dcaccount:` or `dclogin:` scheme.
8
9use std::fmt;
10
11use anyhow::{Context as _, Result};
12use num_traits::ToPrimitive as _;
13use serde::{Deserialize, Serialize};
14
15use crate::config::Config;
16use crate::constants::{DC_LP_AUTH_FLAGS, DC_LP_AUTH_OAUTH2};
17use crate::context::Context;
18pub use crate::net::proxy::ProxyConfig;
19pub use crate::provider::Socket;
20use crate::tools::ToOption;
21
22/// User-entered setting for certificate checks.
23///
24/// Should be saved into `imap_certificate_checks` before running configuration.
25#[derive(
26    Copy,
27    Clone,
28    Debug,
29    Default,
30    Display,
31    FromPrimitive,
32    ToPrimitive,
33    PartialEq,
34    Eq,
35    Serialize,
36    Deserialize,
37)]
38#[repr(u32)]
39#[strum(serialize_all = "snake_case")]
40pub enum EnteredCertificateChecks {
41    /// `Automatic` means that provider database setting should be taken.
42    /// If there is no provider database setting for certificate checks,
43    /// check certificates strictly.
44    #[default]
45    Automatic = 0,
46
47    /// Ensure that TLS certificate is valid for the server hostname.
48    Strict = 1,
49
50    /// Accept certificates that are expired, self-signed
51    /// or otherwise not valid for the server hostname.
52    AcceptInvalidCertificates = 2,
53
54    /// Alias for `AcceptInvalidCertificates`
55    /// for API compatibility.
56    AcceptInvalidCertificates2 = 3,
57}
58
59/// Login parameters for a single server, either IMAP or SMTP
60#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
61pub struct EnteredServerLoginParam {
62    /// Server hostname or IP address.
63    pub server: String,
64
65    /// Server port.
66    ///
67    /// 0 if not specified.
68    pub port: u16,
69
70    /// Socket security.
71    pub security: Socket,
72
73    /// Username.
74    ///
75    /// Empty string if not specified.
76    pub user: String,
77
78    /// Password.
79    pub password: String,
80}
81
82/// A transport, as shown in the "relays" list in the UI.
83#[derive(Debug)]
84pub struct TransportListEntry {
85    /// The login data entered by the user.
86    pub param: EnteredLoginParam,
87    /// Whether this transport is set to 'unpublished'.
88    /// See [`Context::set_transport_unpublished`] for details.
89    pub is_unpublished: bool,
90}
91
92/// Login parameters entered by the user.
93#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
94pub struct EnteredLoginParam {
95    /// Email address.
96    pub addr: String,
97
98    /// IMAP settings.
99    pub imap: EnteredServerLoginParam,
100
101    /// SMTP settings.
102    pub smtp: EnteredServerLoginParam,
103
104    /// TLS options: whether to allow invalid certificates and/or
105    /// invalid hostnames
106    pub certificate_checks: EnteredCertificateChecks,
107
108    /// If true, login via OAUTH2 (not recommended anymore)
109    pub oauth2: bool,
110}
111
112impl EnteredLoginParam {
113    /// Loads entered account settings.
114    pub(crate) async fn load(context: &Context) -> Result<Self> {
115        let addr = context
116            .get_config(Config::Addr)
117            .await?
118            .unwrap_or_default()
119            .trim()
120            .to_string();
121
122        let mail_server = context
123            .get_config(Config::MailServer)
124            .await?
125            .unwrap_or_default();
126        let mail_port = context
127            .get_config_parsed::<u16>(Config::MailPort)
128            .await?
129            .unwrap_or_default();
130        let mail_security = context
131            .get_config_parsed::<i32>(Config::MailSecurity)
132            .await?
133            .and_then(num_traits::FromPrimitive::from_i32)
134            .unwrap_or_default();
135        let mail_user = context
136            .get_config(Config::MailUser)
137            .await?
138            .unwrap_or_default();
139        let mail_pw = context
140            .get_config(Config::MailPw)
141            .await?
142            .unwrap_or_default();
143
144        // The setting is named `imap_certificate_checks`
145        // for backwards compatibility,
146        // but now it is a global setting applied to all protocols,
147        // while `smtp_certificate_checks` is ignored.
148        let certificate_checks = if let Some(certificate_checks) = context
149            .get_config_parsed::<i32>(Config::ImapCertificateChecks)
150            .await?
151        {
152            num_traits::FromPrimitive::from_i32(certificate_checks)
153                .context("Unknown imap_certificate_checks value")?
154        } else {
155            Default::default()
156        };
157
158        let send_server = context
159            .get_config(Config::SendServer)
160            .await?
161            .unwrap_or_default();
162        let send_port = context
163            .get_config_parsed::<u16>(Config::SendPort)
164            .await?
165            .unwrap_or_default();
166        let send_security = context
167            .get_config_parsed::<i32>(Config::SendSecurity)
168            .await?
169            .and_then(num_traits::FromPrimitive::from_i32)
170            .unwrap_or_default();
171        let send_user = context
172            .get_config(Config::SendUser)
173            .await?
174            .unwrap_or_default();
175        let send_pw = context
176            .get_config(Config::SendPw)
177            .await?
178            .unwrap_or_default();
179
180        let server_flags = context
181            .get_config_parsed::<i32>(Config::ServerFlags)
182            .await?
183            .unwrap_or_default();
184        let oauth2 = matches!(server_flags & DC_LP_AUTH_FLAGS, DC_LP_AUTH_OAUTH2);
185
186        Ok(EnteredLoginParam {
187            addr,
188            imap: EnteredServerLoginParam {
189                server: mail_server,
190                port: mail_port,
191                security: mail_security,
192                user: mail_user,
193                password: mail_pw,
194            },
195            smtp: EnteredServerLoginParam {
196                server: send_server,
197                port: send_port,
198                security: send_security,
199                user: send_user,
200                password: send_pw,
201            },
202            certificate_checks,
203            oauth2,
204        })
205    }
206
207    /// Saves entered account settings,
208    /// so that they can be prefilled if the user wants to configure the server again.
209    pub(crate) async fn save(&self, context: &Context) -> Result<()> {
210        context.set_config(Config::Addr, Some(&self.addr)).await?;
211
212        context
213            .set_config(Config::MailServer, self.imap.server.to_option())
214            .await?;
215        context
216            .set_config(Config::MailPort, self.imap.port.to_option().as_deref())
217            .await?;
218        context
219            .set_config(
220                Config::MailSecurity,
221                self.imap.security.to_i32().to_option().as_deref(),
222            )
223            .await?;
224        context
225            .set_config(Config::MailUser, self.imap.user.to_option())
226            .await?;
227        context
228            .set_config(Config::MailPw, self.imap.password.to_option())
229            .await?;
230
231        context
232            .set_config(Config::SendServer, self.smtp.server.to_option())
233            .await?;
234        context
235            .set_config(Config::SendPort, self.smtp.port.to_option().as_deref())
236            .await?;
237        context
238            .set_config(
239                Config::SendSecurity,
240                self.smtp.security.to_i32().to_option().as_deref(),
241            )
242            .await?;
243        context
244            .set_config(Config::SendUser, self.smtp.user.to_option())
245            .await?;
246        context
247            .set_config(Config::SendPw, self.smtp.password.to_option())
248            .await?;
249
250        context
251            .set_config(
252                Config::ImapCertificateChecks,
253                self.certificate_checks.to_i32().to_option().as_deref(),
254            )
255            .await?;
256
257        let server_flags = if self.oauth2 {
258            Some(DC_LP_AUTH_OAUTH2.to_string())
259        } else {
260            None
261        };
262        context
263            .set_config(Config::ServerFlags, server_flags.as_deref())
264            .await?;
265
266        Ok(())
267    }
268}
269
270impl fmt::Display for EnteredLoginParam {
271    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
272        let unset = "0";
273        let pw = "***";
274
275        write!(
276            f,
277            "{} imap:{}:{}:{}:{}:{}:{} smtp:{}:{}:{}:{}:{}:{} cert_{}",
278            unset_empty(&self.addr),
279            unset_empty(&self.imap.user),
280            if !self.imap.password.is_empty() {
281                pw
282            } else {
283                unset
284            },
285            unset_empty(&self.imap.server),
286            self.imap.port,
287            self.imap.security,
288            if self.oauth2 { "OAUTH2" } else { "AUTH_NORMAL" },
289            unset_empty(&self.smtp.user),
290            if !self.smtp.password.is_empty() {
291                pw
292            } else {
293                unset
294            },
295            unset_empty(&self.smtp.server),
296            self.smtp.port,
297            self.smtp.security,
298            if self.oauth2 { "OAUTH2" } else { "AUTH_NORMAL" },
299            self.certificate_checks
300        )
301    }
302}
303
304fn unset_empty(s: &str) -> &str {
305    if s.is_empty() { "unset" } else { s }
306}
307
308#[cfg(test)]
309mod tests {
310    use super::*;
311    use crate::test_utils::TestContext;
312    use pretty_assertions::assert_eq;
313
314    #[test]
315    fn test_entered_certificate_checks_display() {
316        use std::string::ToString;
317
318        assert_eq!(
319            "accept_invalid_certificates".to_string(),
320            EnteredCertificateChecks::AcceptInvalidCertificates.to_string()
321        );
322    }
323
324    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
325    async fn test_entered_login_param() -> Result<()> {
326        let t = &TestContext::new().await;
327
328        t.set_config(Config::Addr, Some("alice@example.org"))
329            .await?;
330        t.set_config(Config::MailPw, Some("foobarbaz")).await?;
331
332        let param = EnteredLoginParam::load(t).await?;
333        assert_eq!(param.addr, "alice@example.org");
334        assert_eq!(
335            param.certificate_checks,
336            EnteredCertificateChecks::Automatic
337        );
338
339        t.set_config(Config::ImapCertificateChecks, Some("1"))
340            .await?;
341        let param = EnteredLoginParam::load(t).await?;
342        assert_eq!(param.certificate_checks, EnteredCertificateChecks::Strict);
343
344        // Fail to load invalid settings, but do not panic.
345        t.set_config(Config::ImapCertificateChecks, Some("999"))
346            .await?;
347        assert!(EnteredLoginParam::load(t).await.is_err());
348
349        Ok(())
350    }
351
352    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
353    async fn test_save_entered_login_param() -> Result<()> {
354        let t = TestContext::new().await;
355        let param = EnteredLoginParam {
356            addr: "alice@example.org".to_string(),
357            imap: EnteredServerLoginParam {
358                server: "".to_string(),
359                port: 0,
360                security: Socket::Starttls,
361                user: "".to_string(),
362                password: "foobar".to_string(),
363            },
364            smtp: EnteredServerLoginParam {
365                server: "".to_string(),
366                port: 2947,
367                security: Socket::default(),
368                user: "".to_string(),
369                password: "".to_string(),
370            },
371            certificate_checks: Default::default(),
372            oauth2: false,
373        };
374        param.save(&t).await?;
375        assert_eq!(
376            t.get_config(Config::Addr).await?.unwrap(),
377            "alice@example.org"
378        );
379        assert_eq!(t.get_config(Config::MailPw).await?.unwrap(), "foobar");
380        assert_eq!(t.get_config(Config::SendPw).await?, None);
381        assert_eq!(t.get_config_int(Config::SendPort).await?, 2947);
382
383        assert_eq!(EnteredLoginParam::load(&t).await?, param);
384
385        Ok(())
386    }
387}