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