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/// Login parameters entered by the user.
83#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
84pub struct EnteredLoginParam {
85    /// Email address.
86    pub addr: String,
87
88    /// IMAP settings.
89    pub imap: EnteredServerLoginParam,
90
91    /// SMTP settings.
92    pub smtp: EnteredServerLoginParam,
93
94    /// TLS options: whether to allow invalid certificates and/or
95    /// invalid hostnames
96    pub certificate_checks: EnteredCertificateChecks,
97
98    /// If true, login via OAUTH2 (not recommended anymore)
99    pub oauth2: bool,
100}
101
102impl EnteredLoginParam {
103    /// Loads entered account settings.
104    pub(crate) async fn load(context: &Context) -> Result<Self> {
105        let addr = context
106            .get_config(Config::Addr)
107            .await?
108            .unwrap_or_default()
109            .trim()
110            .to_string();
111
112        let mail_server = context
113            .get_config(Config::MailServer)
114            .await?
115            .unwrap_or_default();
116        let mail_port = context
117            .get_config_parsed::<u16>(Config::MailPort)
118            .await?
119            .unwrap_or_default();
120        let mail_security = context
121            .get_config_parsed::<i32>(Config::MailSecurity)
122            .await?
123            .and_then(num_traits::FromPrimitive::from_i32)
124            .unwrap_or_default();
125        let mail_user = context
126            .get_config(Config::MailUser)
127            .await?
128            .unwrap_or_default();
129        let mail_pw = context
130            .get_config(Config::MailPw)
131            .await?
132            .unwrap_or_default();
133
134        // The setting is named `imap_certificate_checks`
135        // for backwards compatibility,
136        // but now it is a global setting applied to all protocols,
137        // while `smtp_certificate_checks` is ignored.
138        let certificate_checks = if let Some(certificate_checks) = context
139            .get_config_parsed::<i32>(Config::ImapCertificateChecks)
140            .await?
141        {
142            num_traits::FromPrimitive::from_i32(certificate_checks)
143                .context("Unknown imap_certificate_checks value")?
144        } else {
145            Default::default()
146        };
147
148        let send_server = context
149            .get_config(Config::SendServer)
150            .await?
151            .unwrap_or_default();
152        let send_port = context
153            .get_config_parsed::<u16>(Config::SendPort)
154            .await?
155            .unwrap_or_default();
156        let send_security = context
157            .get_config_parsed::<i32>(Config::SendSecurity)
158            .await?
159            .and_then(num_traits::FromPrimitive::from_i32)
160            .unwrap_or_default();
161        let send_user = context
162            .get_config(Config::SendUser)
163            .await?
164            .unwrap_or_default();
165        let send_pw = context
166            .get_config(Config::SendPw)
167            .await?
168            .unwrap_or_default();
169
170        let server_flags = context
171            .get_config_parsed::<i32>(Config::ServerFlags)
172            .await?
173            .unwrap_or_default();
174        let oauth2 = matches!(server_flags & DC_LP_AUTH_FLAGS, DC_LP_AUTH_OAUTH2);
175
176        Ok(EnteredLoginParam {
177            addr,
178            imap: EnteredServerLoginParam {
179                server: mail_server,
180                port: mail_port,
181                security: mail_security,
182                user: mail_user,
183                password: mail_pw,
184            },
185            smtp: EnteredServerLoginParam {
186                server: send_server,
187                port: send_port,
188                security: send_security,
189                user: send_user,
190                password: send_pw,
191            },
192            certificate_checks,
193            oauth2,
194        })
195    }
196
197    /// Saves entered account settings,
198    /// so that they can be prefilled if the user wants to configure the server again.
199    pub(crate) async fn save(&self, context: &Context) -> Result<()> {
200        context.set_config(Config::Addr, Some(&self.addr)).await?;
201
202        context
203            .set_config(Config::MailServer, self.imap.server.to_option())
204            .await?;
205        context
206            .set_config(Config::MailPort, self.imap.port.to_option().as_deref())
207            .await?;
208        context
209            .set_config(
210                Config::MailSecurity,
211                self.imap.security.to_i32().to_option().as_deref(),
212            )
213            .await?;
214        context
215            .set_config(Config::MailUser, self.imap.user.to_option())
216            .await?;
217        context
218            .set_config(Config::MailPw, self.imap.password.to_option())
219            .await?;
220
221        context
222            .set_config(Config::SendServer, self.smtp.server.to_option())
223            .await?;
224        context
225            .set_config(Config::SendPort, self.smtp.port.to_option().as_deref())
226            .await?;
227        context
228            .set_config(
229                Config::SendSecurity,
230                self.smtp.security.to_i32().to_option().as_deref(),
231            )
232            .await?;
233        context
234            .set_config(Config::SendUser, self.smtp.user.to_option())
235            .await?;
236        context
237            .set_config(Config::SendPw, self.smtp.password.to_option())
238            .await?;
239
240        context
241            .set_config(
242                Config::ImapCertificateChecks,
243                self.certificate_checks.to_i32().to_option().as_deref(),
244            )
245            .await?;
246
247        let server_flags = if self.oauth2 {
248            Some(DC_LP_AUTH_OAUTH2.to_string())
249        } else {
250            None
251        };
252        context
253            .set_config(Config::ServerFlags, server_flags.as_deref())
254            .await?;
255
256        Ok(())
257    }
258}
259
260impl fmt::Display for EnteredLoginParam {
261    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
262        let unset = "0";
263        let pw = "***";
264
265        write!(
266            f,
267            "{} imap:{}:{}:{}:{}:{}:{} smtp:{}:{}:{}:{}:{}:{} cert_{}",
268            unset_empty(&self.addr),
269            unset_empty(&self.imap.user),
270            if !self.imap.password.is_empty() {
271                pw
272            } else {
273                unset
274            },
275            unset_empty(&self.imap.server),
276            self.imap.port,
277            self.imap.security,
278            if self.oauth2 { "OAUTH2" } else { "AUTH_NORMAL" },
279            unset_empty(&self.smtp.user),
280            if !self.smtp.password.is_empty() {
281                pw
282            } else {
283                unset
284            },
285            unset_empty(&self.smtp.server),
286            self.smtp.port,
287            self.smtp.security,
288            if self.oauth2 { "OAUTH2" } else { "AUTH_NORMAL" },
289            self.certificate_checks
290        )
291    }
292}
293
294fn unset_empty(s: &str) -> &str {
295    if s.is_empty() { "unset" } else { s }
296}
297
298#[cfg(test)]
299mod tests {
300    use super::*;
301    use crate::test_utils::TestContext;
302    use pretty_assertions::assert_eq;
303
304    #[test]
305    fn test_entered_certificate_checks_display() {
306        use std::string::ToString;
307
308        assert_eq!(
309            "accept_invalid_certificates".to_string(),
310            EnteredCertificateChecks::AcceptInvalidCertificates.to_string()
311        );
312    }
313
314    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
315    async fn test_entered_login_param() -> Result<()> {
316        let t = &TestContext::new().await;
317
318        t.set_config(Config::Addr, Some("alice@example.org"))
319            .await?;
320        t.set_config(Config::MailPw, Some("foobarbaz")).await?;
321
322        let param = EnteredLoginParam::load(t).await?;
323        assert_eq!(param.addr, "alice@example.org");
324        assert_eq!(
325            param.certificate_checks,
326            EnteredCertificateChecks::Automatic
327        );
328
329        t.set_config(Config::ImapCertificateChecks, Some("1"))
330            .await?;
331        let param = EnteredLoginParam::load(t).await?;
332        assert_eq!(param.certificate_checks, EnteredCertificateChecks::Strict);
333
334        // Fail to load invalid settings, but do not panic.
335        t.set_config(Config::ImapCertificateChecks, Some("999"))
336            .await?;
337        assert!(EnteredLoginParam::load(t).await.is_err());
338
339        Ok(())
340    }
341
342    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
343    async fn test_save_entered_login_param() -> Result<()> {
344        let t = TestContext::new().await;
345        let param = EnteredLoginParam {
346            addr: "alice@example.org".to_string(),
347            imap: EnteredServerLoginParam {
348                server: "".to_string(),
349                port: 0,
350                security: Socket::Starttls,
351                user: "".to_string(),
352                password: "foobar".to_string(),
353            },
354            smtp: EnteredServerLoginParam {
355                server: "".to_string(),
356                port: 2947,
357                security: Socket::default(),
358                user: "".to_string(),
359                password: "".to_string(),
360            },
361            certificate_checks: Default::default(),
362            oauth2: false,
363        };
364        param.save(&t).await?;
365        assert_eq!(
366            t.get_config(Config::Addr).await?.unwrap(),
367            "alice@example.org"
368        );
369        assert_eq!(t.get_config(Config::MailPw).await?.unwrap(), "foobar");
370        assert_eq!(t.get_config(Config::SendPw).await?, None);
371        assert_eq!(t.get_config_int(Config::SendPort).await?, 2947);
372
373        assert_eq!(EnteredLoginParam::load(&t).await?, param);
374
375        Ok(())
376    }
377}