Skip to main content

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
59impl EnteredCertificateChecks {
60    pub(crate) fn accept_invalid_certificates(self) -> bool {
61        matches!(
62            self,
63            Self::AcceptInvalidCertificates | Self::AcceptInvalidCertificates2
64        )
65    }
66}
67
68/// Login parameters for a single IMAP server.
69#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
70pub struct EnteredImapLoginParam {
71    /// Server hostname or IP address.
72    pub server: String,
73
74    /// Server port.
75    ///
76    /// 0 if not specified.
77    pub port: u16,
78
79    /// Folder to watch.
80    ///
81    /// If empty, user has not entered anything and it shuold expand to "INBOX" later.
82    #[serde(default)]
83    pub folder: String,
84
85    /// Socket security.
86    pub security: Socket,
87
88    /// Username.
89    ///
90    /// Empty string if not specified.
91    pub user: String,
92
93    /// Password.
94    pub password: String,
95}
96
97/// Login parameters for a single SMTP server.
98#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
99pub struct EnteredSmtpLoginParam {
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/// A transport, as shown in the "relays" list in the UI.
121#[derive(Debug)]
122pub struct TransportListEntry {
123    /// The login data entered by the user.
124    pub param: EnteredLoginParam,
125    /// Whether this transport is set to 'unpublished'.
126    /// See [`Context::set_transport_unpublished`] for details.
127    pub is_unpublished: bool,
128}
129
130/// Login parameters entered by the user.
131#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
132pub struct EnteredLoginParam {
133    /// Email address.
134    pub addr: String,
135
136    /// IMAP settings.
137    pub imap: EnteredImapLoginParam,
138
139    /// SMTP settings.
140    pub smtp: EnteredSmtpLoginParam,
141
142    /// TLS options: whether to allow invalid certificates and/or
143    /// invalid hostnames
144    pub certificate_checks: EnteredCertificateChecks,
145
146    /// If true, login via OAUTH2 (not recommended anymore)
147    pub oauth2: bool,
148}
149
150impl EnteredLoginParam {
151    /// Loads entered account settings
152    /// that were set by the deprecated `configured_*` configs.
153    ///
154    /// This is only needed by tests and clients using the old CFFI API.
155    pub(crate) async fn load_legacy(context: &Context) -> Result<Self> {
156        let addr = context
157            .get_config(Config::Addr)
158            .await?
159            .unwrap_or_default()
160            .trim()
161            .to_string();
162
163        let mail_server = context
164            .get_config(Config::MailServer)
165            .await?
166            .unwrap_or_default();
167        let mail_port = context
168            .get_config_parsed::<u16>(Config::MailPort)
169            .await?
170            .unwrap_or_default();
171
172        // There is no way to set custom folder with this legacy API.
173        let mail_folder = String::new();
174
175        let mail_security = context
176            .get_config_parsed::<i32>(Config::MailSecurity)
177            .await?
178            .and_then(num_traits::FromPrimitive::from_i32)
179            .unwrap_or_default();
180        let mail_user = context
181            .get_config(Config::MailUser)
182            .await?
183            .unwrap_or_default();
184        let mail_pw = context
185            .get_config(Config::MailPw)
186            .await?
187            .unwrap_or_default();
188
189        // The setting is named `imap_certificate_checks`
190        // for backwards compatibility,
191        // but now it is a global setting applied to all protocols,
192        // while `smtp_certificate_checks` has been removed.
193        let certificate_checks = if let Some(certificate_checks) = context
194            .get_config_parsed::<i32>(Config::ImapCertificateChecks)
195            .await?
196        {
197            num_traits::FromPrimitive::from_i32(certificate_checks)
198                .context("Unknown imap_certificate_checks value")?
199        } else {
200            Default::default()
201        };
202
203        let send_server = context
204            .get_config(Config::SendServer)
205            .await?
206            .unwrap_or_default();
207        let send_port = context
208            .get_config_parsed::<u16>(Config::SendPort)
209            .await?
210            .unwrap_or_default();
211        let send_security = context
212            .get_config_parsed::<i32>(Config::SendSecurity)
213            .await?
214            .and_then(num_traits::FromPrimitive::from_i32)
215            .unwrap_or_default();
216        let send_user = context
217            .get_config(Config::SendUser)
218            .await?
219            .unwrap_or_default();
220        let send_pw = context
221            .get_config(Config::SendPw)
222            .await?
223            .unwrap_or_default();
224
225        let server_flags = context
226            .get_config_parsed::<i32>(Config::ServerFlags)
227            .await?
228            .unwrap_or_default();
229        let oauth2 = matches!(server_flags & DC_LP_AUTH_FLAGS, DC_LP_AUTH_OAUTH2);
230
231        Ok(EnteredLoginParam {
232            addr,
233            imap: EnteredImapLoginParam {
234                server: mail_server,
235                port: mail_port,
236                folder: mail_folder,
237                security: mail_security,
238                user: mail_user,
239                password: mail_pw,
240            },
241            smtp: EnteredSmtpLoginParam {
242                server: send_server,
243                port: send_port,
244                security: send_security,
245                user: send_user,
246                password: send_pw,
247            },
248            certificate_checks,
249            oauth2,
250        })
251    }
252
253    /// Saves entered account settings,
254    /// so that they can be prefilled if the user wants to configure the server again.
255    ///
256    /// This is needed in case a UI is not yet updated, and still uses `get_config("mail_pw")` etc.
257    /// in order to prefill the entered account settings.
258    pub(crate) async fn save_legacy(&self, context: &Context) -> Result<()> {
259        context.set_config(Config::Addr, Some(&self.addr)).await?;
260
261        context
262            .set_config(Config::MailServer, self.imap.server.to_option())
263            .await?;
264        context
265            .set_config(Config::MailPort, self.imap.port.to_option().as_deref())
266            .await?;
267        context
268            .set_config(
269                Config::MailSecurity,
270                self.imap.security.to_i32().to_option().as_deref(),
271            )
272            .await?;
273        context
274            .set_config(Config::MailUser, self.imap.user.to_option())
275            .await?;
276        context
277            .set_config(Config::MailPw, self.imap.password.to_option())
278            .await?;
279
280        context
281            .set_config(Config::SendServer, self.smtp.server.to_option())
282            .await?;
283        context
284            .set_config(Config::SendPort, self.smtp.port.to_option().as_deref())
285            .await?;
286        context
287            .set_config(
288                Config::SendSecurity,
289                self.smtp.security.to_i32().to_option().as_deref(),
290            )
291            .await?;
292        context
293            .set_config(Config::SendUser, self.smtp.user.to_option())
294            .await?;
295        context
296            .set_config(Config::SendPw, self.smtp.password.to_option())
297            .await?;
298
299        context
300            .set_config(
301                Config::ImapCertificateChecks,
302                self.certificate_checks.to_i32().to_option().as_deref(),
303            )
304            .await?;
305
306        let server_flags = if self.oauth2 {
307            Some(DC_LP_AUTH_OAUTH2.to_string())
308        } else {
309            None
310        };
311        context
312            .set_config(Config::ServerFlags, server_flags.as_deref())
313            .await?;
314
315        Ok(())
316    }
317}
318
319impl fmt::Display for EnteredLoginParam {
320    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
321        let unset = "0";
322        let pw = "***";
323
324        write!(
325            f,
326            "{} imap:{}:{}:{}:{}:{}:{} smtp:{}:{}:{}:{}:{}:{} cert_{}",
327            unset_empty(&self.addr),
328            unset_empty(&self.imap.user),
329            if !self.imap.password.is_empty() {
330                pw
331            } else {
332                unset
333            },
334            unset_empty(&self.imap.server),
335            self.imap.port,
336            self.imap.security,
337            if self.oauth2 { "OAUTH2" } else { "AUTH_NORMAL" },
338            unset_empty(&self.smtp.user),
339            if !self.smtp.password.is_empty() {
340                pw
341            } else {
342                unset
343            },
344            unset_empty(&self.smtp.server),
345            self.smtp.port,
346            self.smtp.security,
347            if self.oauth2 { "OAUTH2" } else { "AUTH_NORMAL" },
348            self.certificate_checks
349        )
350    }
351}
352
353fn unset_empty(s: &str) -> &str {
354    if s.is_empty() { "unset" } else { s }
355}
356
357#[cfg(test)]
358mod tests {
359    use super::*;
360    use crate::test_utils::TestContext;
361    use pretty_assertions::assert_eq;
362
363    #[test]
364    fn test_entered_certificate_checks_display() {
365        use std::string::ToString;
366
367        assert_eq!(
368            "accept_invalid_certificates".to_string(),
369            EnteredCertificateChecks::AcceptInvalidCertificates.to_string()
370        );
371    }
372
373    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
374    async fn test_entered_login_param() -> Result<()> {
375        let t = &TestContext::new().await;
376
377        t.set_config(Config::Addr, Some("alice@example.org"))
378            .await?;
379        t.set_config(Config::MailPw, Some("foobarbaz")).await?;
380
381        let param = EnteredLoginParam::load_legacy(t).await?;
382        assert_eq!(param.addr, "alice@example.org");
383        assert_eq!(
384            param.certificate_checks,
385            EnteredCertificateChecks::Automatic
386        );
387
388        t.set_config(Config::ImapCertificateChecks, Some("1"))
389            .await?;
390        let param = EnteredLoginParam::load_legacy(t).await?;
391        assert_eq!(param.certificate_checks, EnteredCertificateChecks::Strict);
392
393        // Fail to load invalid settings, but do not panic.
394        t.set_config(Config::ImapCertificateChecks, Some("999"))
395            .await?;
396        assert!(EnteredLoginParam::load_legacy(t).await.is_err());
397
398        Ok(())
399    }
400
401    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
402    async fn test_save_entered_login_param() -> Result<()> {
403        let t = TestContext::new().await;
404        let param = EnteredLoginParam {
405            addr: "alice@example.org".to_string(),
406            imap: EnteredImapLoginParam {
407                server: "".to_string(),
408                port: 0,
409                folder: "".to_string(),
410                security: Socket::Starttls,
411                user: "".to_string(),
412                password: "foobar".to_string(),
413            },
414            smtp: EnteredSmtpLoginParam {
415                server: "".to_string(),
416                port: 2947,
417                security: Socket::default(),
418                user: "".to_string(),
419                password: "".to_string(),
420            },
421            certificate_checks: Default::default(),
422            oauth2: false,
423        };
424        param.save_legacy(&t).await?;
425        assert_eq!(
426            t.get_config(Config::Addr).await?.unwrap(),
427            "alice@example.org"
428        );
429        assert_eq!(t.get_config(Config::MailPw).await?.unwrap(), "foobar");
430        assert_eq!(t.get_config(Config::SendPw).await?, None);
431        assert_eq!(t.get_config_int(Config::SendPort).await?, 2947);
432
433        assert_eq!(EnteredLoginParam::load_legacy(&t).await?, param);
434
435        Ok(())
436    }
437}