//! Variable server parameters lists use crate::provider::{Protocol, Socket}; /// Set of variable parameters to try during configuration. /// /// Can be loaded from offline provider database, online configuration /// or derived from user entered parameters. #[derive(Debug, Clone, PartialEq)] pub(crate) struct ServerParams { /// Protocol, such as IMAP or SMTP. pub protocol: Protocol, /// Server hostname, empty if unknown. pub hostname: String, /// Server port, zero if unknown. pub port: u16, /// Socket security, such as TLS or STARTTLS, Socket::Automatic if unknown. pub socket: Socket, /// Username, empty if unknown. pub username: String, } impl ServerParams { fn expand_usernames(self, addr: &str) -> Vec { if self.username.is_empty() { vec![Self { username: addr.to_string(), ..self.clone() }] } else { vec![self] } } fn expand_hostnames(self, param_domain: &str) -> Vec { if self.hostname.is_empty() { vec![ // Try "imap.ex.org"/"smtp.ex.org" and "mail.ex.org" first because if a server exists // under this address, it's likely the correct one. Self { hostname: match self.protocol { Protocol::Imap => "imap.".to_string() + param_domain, Protocol::Smtp => "smtp.".to_string() + param_domain, }, ..self.clone() }, Self { hostname: "mail.".to_string() + param_domain, ..self.clone() }, // Try "ex.org" last because if it's wrong and the server is configured to // not answer at all, configuration may be stuck for several minutes. Self { hostname: param_domain.to_string(), ..self }, ] } else { vec![self] } } fn expand_ports(mut self) -> Vec { // Try to infer port from socket security. if self.port == 0 { self.port = match self.socket { Socket::Ssl => match self.protocol { Protocol::Imap => 993, Protocol::Smtp => 465, }, Socket::Starttls | Socket::Plain => match self.protocol { Protocol::Imap => 143, Protocol::Smtp => 587, }, Socket::Automatic => 0, } } if self.port == 0 { // Neither port nor security is set. // // Try common secure combinations. vec![ // Try TLS Self { socket: Socket::Ssl, port: match self.protocol { Protocol::Imap => 993, Protocol::Smtp => 465, }, ..self.clone() }, // Try STARTTLS Self { socket: Socket::Starttls, port: match self.protocol { Protocol::Imap => 143, Protocol::Smtp => 587, }, ..self }, ] } else if self.socket == Socket::Automatic { vec![ // Try TLS over user-provided port. Self { socket: Socket::Ssl, ..self.clone() }, // Try STARTTLS over user-provided port. Self { socket: Socket::Starttls, ..self }, ] } else { vec![self] } } } /// Expands vector of `ServerParams`, replacing placeholders with /// variants to try. pub(crate) fn expand_param_vector( v: Vec, addr: &str, domain: &str, ) -> Vec { v.into_iter() // The order of expansion is important. // // Ports are expanded the last, so they are changed the first. .flat_map(|params| params.expand_usernames(addr).into_iter()) .flat_map(|params| params.expand_hostnames(domain).into_iter()) .flat_map(|params| params.expand_ports().into_iter()) .collect() } #[cfg(test)] mod tests { use super::*; #[test] fn test_expand_param_vector() { let v = expand_param_vector( vec![ServerParams { protocol: Protocol::Imap, hostname: "example.net".to_string(), port: 0, socket: Socket::Ssl, username: "foobar".to_string(), }], "foobar@example.net", "example.net", ); assert_eq!( v, vec![ServerParams { protocol: Protocol::Imap, hostname: "example.net".to_string(), port: 993, socket: Socket::Ssl, username: "foobar".to_string(), }], ); let v = expand_param_vector( vec![ServerParams { protocol: Protocol::Smtp, hostname: "example.net".to_string(), port: 123, socket: Socket::Automatic, username: "foobar".to_string(), }], "foobar@example.net", "example.net", ); assert_eq!( v, vec![ ServerParams { protocol: Protocol::Smtp, hostname: "example.net".to_string(), port: 123, socket: Socket::Ssl, username: "foobar".to_string(), }, ServerParams { protocol: Protocol::Smtp, hostname: "example.net".to_string(), port: 123, socket: Socket::Starttls, username: "foobar".to_string(), }, ], ); let v = expand_param_vector( vec![ServerParams { protocol: Protocol::Smtp, hostname: "example.net".to_string(), port: 123, socket: Socket::Plain, username: "foobar".to_string(), }], "foobar@example.net", "example.net", ); assert_eq!( v, vec![ServerParams { protocol: Protocol::Smtp, hostname: "example.net".to_string(), port: 123, socket: Socket::Plain, username: "foobar".to_string(), }], ); // Test that "example.net" is tried after "*.example.net". let v = expand_param_vector( vec![ServerParams { protocol: Protocol::Imap, hostname: "".to_string(), port: 10480, socket: Socket::Ssl, username: "foobar".to_string(), }], "foobar@example.net", "example.net", ); assert_eq!( v, vec![ ServerParams { protocol: Protocol::Imap, hostname: "imap.example.net".to_string(), port: 10480, socket: Socket::Ssl, username: "foobar".to_string(), }, ServerParams { protocol: Protocol::Imap, hostname: "mail.example.net".to_string(), port: 10480, socket: Socket::Ssl, username: "foobar".to_string(), }, ServerParams { protocol: Protocol::Imap, hostname: "example.net".to_string(), port: 10480, socket: Socket::Ssl, username: "foobar".to_string(), } ], ); // Test that TLS is preferred to STARTTLS // when the port and security are not set. let v = expand_param_vector( vec![ServerParams { protocol: Protocol::Smtp, hostname: "example.net".to_string(), port: 0, socket: Socket::Automatic, username: "foobar".to_string(), }], "foobar@example.net", "example.net", ); assert_eq!( v, vec![ ServerParams { protocol: Protocol::Smtp, hostname: "example.net".to_string(), port: 465, socket: Socket::Ssl, username: "foobar".to_string(), }, ServerParams { protocol: Protocol::Smtp, hostname: "example.net".to_string(), port: 587, socket: Socket::Starttls, username: "foobar".to_string(), }, ], ); // Test that email address is used as the default username. // We do not try other usernames // such as the local part of the address // as this is very uncommon configuration // and not worth doubling the number of candidates to try. // If such configuration is used, email provider // should provide XML autoconfig or // be added to the provider database as an exception. let v = expand_param_vector( vec![ServerParams { protocol: Protocol::Imap, hostname: "example.net".to_string(), port: 0, socket: Socket::Automatic, username: "".to_string(), }], "foobar@example.net", "example.net", ); assert_eq!( v, vec![ ServerParams { protocol: Protocol::Imap, hostname: "example.net".to_string(), port: 993, socket: Socket::Ssl, username: "foobar@example.net".to_string(), }, ServerParams { protocol: Protocol::Imap, hostname: "example.net".to_string(), port: 143, socket: Socket::Starttls, username: "foobar@example.net".to_string(), }, ], ); } }