deltachat/configure/
server_params.rs

1//! Variable server parameters lists
2
3use crate::provider::{Protocol, Socket};
4
5/// Set of variable parameters to try during configuration.
6///
7/// Can be loaded from offline provider database, online configuration
8/// or derived from user entered parameters.
9#[derive(Debug, Clone, PartialEq)]
10pub(crate) struct ServerParams {
11    /// Protocol, such as IMAP or SMTP.
12    pub protocol: Protocol,
13
14    /// Server hostname, empty if unknown.
15    pub hostname: String,
16
17    /// Server port, zero if unknown.
18    pub port: u16,
19
20    /// Socket security, such as TLS or STARTTLS, Socket::Automatic if unknown.
21    pub socket: Socket,
22
23    /// Username, empty if unknown.
24    pub username: String,
25}
26
27impl ServerParams {
28    fn expand_usernames(self, addr: &str) -> Vec<ServerParams> {
29        if self.username.is_empty() {
30            vec![Self {
31                username: addr.to_string(),
32                ..self.clone()
33            }]
34        } else {
35            vec![self]
36        }
37    }
38
39    fn expand_hostnames(self, param_domain: &str) -> Vec<ServerParams> {
40        if self.hostname.is_empty() {
41            vec![
42                // Try "imap.ex.org"/"smtp.ex.org" and "mail.ex.org" first because if a server exists
43                // under this address, it's likely the correct one.
44                Self {
45                    hostname: match self.protocol {
46                        Protocol::Imap => "imap.".to_string() + param_domain,
47                        Protocol::Smtp => "smtp.".to_string() + param_domain,
48                    },
49                    ..self.clone()
50                },
51                Self {
52                    hostname: "mail.".to_string() + param_domain,
53                    ..self.clone()
54                },
55                // Try "ex.org" last because if it's wrong and the server is configured to
56                // not answer at all, configuration may be stuck for several minutes.
57                Self {
58                    hostname: param_domain.to_string(),
59                    ..self
60                },
61            ]
62        } else {
63            vec![self]
64        }
65    }
66
67    fn expand_ports(mut self) -> Vec<ServerParams> {
68        // Try to infer port from socket security.
69        if self.port == 0 {
70            self.port = match self.socket {
71                Socket::Ssl => match self.protocol {
72                    Protocol::Imap => 993,
73                    Protocol::Smtp => 465,
74                },
75                Socket::Starttls | Socket::Plain => match self.protocol {
76                    Protocol::Imap => 143,
77                    Protocol::Smtp => 587,
78                },
79                Socket::Automatic => 0,
80            }
81        }
82
83        if self.port == 0 {
84            // Neither port nor security is set.
85            //
86            // Try common secure combinations.
87
88            vec![
89                // Try TLS
90                Self {
91                    socket: Socket::Ssl,
92                    port: match self.protocol {
93                        Protocol::Imap => 993,
94                        Protocol::Smtp => 465,
95                    },
96                    ..self.clone()
97                },
98                // Try STARTTLS
99                Self {
100                    socket: Socket::Starttls,
101                    port: match self.protocol {
102                        Protocol::Imap => 143,
103                        Protocol::Smtp => 587,
104                    },
105                    ..self
106                },
107            ]
108        } else if self.socket == Socket::Automatic {
109            vec![
110                // Try TLS over user-provided port.
111                Self {
112                    socket: Socket::Ssl,
113                    ..self.clone()
114                },
115                // Try STARTTLS over user-provided port.
116                Self {
117                    socket: Socket::Starttls,
118                    ..self
119                },
120            ]
121        } else {
122            vec![self]
123        }
124    }
125}
126
127/// Expands vector of `ServerParams`, replacing placeholders with
128/// variants to try.
129pub(crate) fn expand_param_vector(
130    v: Vec<ServerParams>,
131    addr: &str,
132    domain: &str,
133) -> Vec<ServerParams> {
134    v.into_iter()
135        // The order of expansion is important.
136        //
137        // Ports are expanded the last, so they are changed the first.
138        .flat_map(|params| params.expand_usernames(addr).into_iter())
139        .flat_map(|params| params.expand_hostnames(domain).into_iter())
140        .flat_map(|params| params.expand_ports().into_iter())
141        .collect()
142}
143
144#[cfg(test)]
145mod tests {
146    use super::*;
147
148    #[test]
149    fn test_expand_param_vector() {
150        let v = expand_param_vector(
151            vec![ServerParams {
152                protocol: Protocol::Imap,
153                hostname: "example.net".to_string(),
154                port: 0,
155                socket: Socket::Ssl,
156                username: "foobar".to_string(),
157            }],
158            "foobar@example.net",
159            "example.net",
160        );
161
162        assert_eq!(
163            v,
164            vec![ServerParams {
165                protocol: Protocol::Imap,
166                hostname: "example.net".to_string(),
167                port: 993,
168                socket: Socket::Ssl,
169                username: "foobar".to_string(),
170            }],
171        );
172
173        let v = expand_param_vector(
174            vec![ServerParams {
175                protocol: Protocol::Smtp,
176                hostname: "example.net".to_string(),
177                port: 123,
178                socket: Socket::Automatic,
179                username: "foobar".to_string(),
180            }],
181            "foobar@example.net",
182            "example.net",
183        );
184
185        assert_eq!(
186            v,
187            vec![
188                ServerParams {
189                    protocol: Protocol::Smtp,
190                    hostname: "example.net".to_string(),
191                    port: 123,
192                    socket: Socket::Ssl,
193                    username: "foobar".to_string(),
194                },
195                ServerParams {
196                    protocol: Protocol::Smtp,
197                    hostname: "example.net".to_string(),
198                    port: 123,
199                    socket: Socket::Starttls,
200                    username: "foobar".to_string(),
201                },
202            ],
203        );
204
205        let v = expand_param_vector(
206            vec![ServerParams {
207                protocol: Protocol::Smtp,
208                hostname: "example.net".to_string(),
209                port: 123,
210                socket: Socket::Plain,
211                username: "foobar".to_string(),
212            }],
213            "foobar@example.net",
214            "example.net",
215        );
216        assert_eq!(
217            v,
218            vec![ServerParams {
219                protocol: Protocol::Smtp,
220                hostname: "example.net".to_string(),
221                port: 123,
222                socket: Socket::Plain,
223                username: "foobar".to_string(),
224            }],
225        );
226
227        // Test that "example.net" is tried after "*.example.net".
228        let v = expand_param_vector(
229            vec![ServerParams {
230                protocol: Protocol::Imap,
231                hostname: "".to_string(),
232                port: 10480,
233                socket: Socket::Ssl,
234                username: "foobar".to_string(),
235            }],
236            "foobar@example.net",
237            "example.net",
238        );
239        assert_eq!(
240            v,
241            vec![
242                ServerParams {
243                    protocol: Protocol::Imap,
244                    hostname: "imap.example.net".to_string(),
245                    port: 10480,
246                    socket: Socket::Ssl,
247                    username: "foobar".to_string(),
248                },
249                ServerParams {
250                    protocol: Protocol::Imap,
251                    hostname: "mail.example.net".to_string(),
252                    port: 10480,
253                    socket: Socket::Ssl,
254                    username: "foobar".to_string(),
255                },
256                ServerParams {
257                    protocol: Protocol::Imap,
258                    hostname: "example.net".to_string(),
259                    port: 10480,
260                    socket: Socket::Ssl,
261                    username: "foobar".to_string(),
262                }
263            ],
264        );
265
266        // Test that TLS is preferred to STARTTLS
267        // when the port and security are not set.
268        let v = expand_param_vector(
269            vec![ServerParams {
270                protocol: Protocol::Smtp,
271                hostname: "example.net".to_string(),
272                port: 0,
273                socket: Socket::Automatic,
274                username: "foobar".to_string(),
275            }],
276            "foobar@example.net",
277            "example.net",
278        );
279        assert_eq!(
280            v,
281            vec![
282                ServerParams {
283                    protocol: Protocol::Smtp,
284                    hostname: "example.net".to_string(),
285                    port: 465,
286                    socket: Socket::Ssl,
287                    username: "foobar".to_string(),
288                },
289                ServerParams {
290                    protocol: Protocol::Smtp,
291                    hostname: "example.net".to_string(),
292                    port: 587,
293                    socket: Socket::Starttls,
294                    username: "foobar".to_string(),
295                },
296            ],
297        );
298
299        // Test that email address is used as the default username.
300        // We do not try other usernames
301        // such as the local part of the address
302        // as this is very uncommon configuration
303        // and not worth doubling the number of candidates to try.
304        // If such configuration is used, email provider
305        // should provide XML autoconfig or
306        // be added to the provider database as an exception.
307        let v = expand_param_vector(
308            vec![ServerParams {
309                protocol: Protocol::Imap,
310                hostname: "example.net".to_string(),
311                port: 0,
312                socket: Socket::Automatic,
313                username: "".to_string(),
314            }],
315            "foobar@example.net",
316            "example.net",
317        );
318        assert_eq!(
319            v,
320            vec![
321                ServerParams {
322                    protocol: Protocol::Imap,
323                    hostname: "example.net".to_string(),
324                    port: 993,
325                    socket: Socket::Ssl,
326                    username: "foobar@example.net".to_string(),
327                },
328                ServerParams {
329                    protocol: Protocol::Imap,
330                    hostname: "example.net".to_string(),
331                    port: 143,
332                    socket: Socket::Starttls,
333                    username: "foobar@example.net".to_string(),
334                },
335            ],
336        );
337    }
338}