generate one autoconfig per email address

This commit is contained in:
Clément DOUIN 2024-01-15 15:27:14 +01:00
parent 1246be8a5b
commit 7eba3a5186
No known key found for this signature in database
GPG key ID: 353E4A18EE0FAB72
6 changed files with 56 additions and 39 deletions

16
Cargo.lock generated
View file

@ -1462,7 +1462,9 @@ dependencies = [
[[package]] [[package]]
name = "email-lib" name = "email-lib"
version = "0.20.0" version = "0.20.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d79298206bcb8ada88ed67be329711502351ad55bf143025b310139701dd01a0"
dependencies = [ dependencies = [
"advisory-lock", "advisory-lock",
"anyhow", "anyhow",
@ -2957,7 +2959,9 @@ dependencies = [
[[package]] [[package]]
name = "mml-lib" name = "mml-lib"
version = "1.0.6" version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8ffcc68cd2f48395ee07fe85ec580154c6f8f22ff89972b4a1dd2452890d614"
dependencies = [ dependencies = [
"async-recursion", "async-recursion",
"chumsky", "chumsky",
@ -3709,7 +3713,9 @@ dependencies = [
[[package]] [[package]]
name = "process-lib" name = "process-lib"
version = "0.3.0" version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e83db4af201454004f9cdc5fb343031f6d84bddf8a0d41348bc9e82fab1f1ee"
dependencies = [ dependencies = [
"log", "log",
"once_cell", "once_cell",
@ -4245,7 +4251,9 @@ dependencies = [
[[package]] [[package]]
name = "secret-lib" name = "secret-lib"
version = "0.3.2" version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af8fa450b77b5d8e0ac1cf7c741e912e54b568e8dcf888d80f5502c23899aeae"
dependencies = [ dependencies = [
"keyring-lib", "keyring-lib",
"process-lib", "process-lib",

View file

@ -5,6 +5,7 @@ use dialoguer::Input;
#[cfg(feature = "account-sync")] #[cfg(feature = "account-sync")]
use email::account::sync::config::SyncConfig; use email::account::sync::config::SyncConfig;
use email_address::EmailAddress; use email_address::EmailAddress;
use log::{debug, trace, warn};
#[allow(unused)] #[allow(unused)]
use crate::backend::{self, config::BackendConfig, BackendKind}; use crate::backend::{self, config::BackendConfig, BackendKind};
@ -35,6 +36,8 @@ pub(crate) async fn configure() -> Result<Option<(String, TomlAccountConfig)>> {
}) })
.interact()?; .interact()?;
let email = &config.email;
config.display_name = Some( config.display_name = Some(
Input::with_theme(&*THEME) Input::with_theme(&*THEME)
.with_prompt("Full display name") .with_prompt("Full display name")
@ -49,7 +52,22 @@ pub(crate) async fn configure() -> Result<Option<(String, TomlAccountConfig)>> {
.into(), .into(),
); );
match backend::wizard::configure(&account_name, &config.email).await? { let autoconfig = match autoconfig::from_addr(email).await {
Ok(autoconfig) => {
println!("An automatic configuration has been found for {email},");
println!("it will be used by default for the rest of the configuration.\n");
trace!("{autoconfig:#?}");
Some(autoconfig)
}
Err(err) => {
warn!("cannot discover configuration from {email}: {err}");
debug!("{err:?}");
None
}
};
let autoconfig = autoconfig.as_ref();
match backend::wizard::configure(&account_name, email, autoconfig).await? {
#[cfg(feature = "imap")] #[cfg(feature = "imap")]
Some(BackendConfig::Imap(imap_config)) => { Some(BackendConfig::Imap(imap_config)) => {
config.imap = Some(imap_config); config.imap = Some(imap_config);
@ -68,7 +86,7 @@ pub(crate) async fn configure() -> Result<Option<(String, TomlAccountConfig)>> {
_ => (), _ => (),
}; };
match backend::wizard::configure_sender(&account_name, &config.email).await? { match backend::wizard::configure_sender(&account_name, email, autoconfig).await? {
#[cfg(feature = "smtp")] #[cfg(feature = "smtp")]
Some(BackendConfig::Smtp(smtp_config)) => { Some(BackendConfig::Smtp(smtp_config)) => {
config.smtp = Some(smtp_config); config.smtp = Some(smtp_config);

View file

@ -1,8 +1,6 @@
use anyhow::Result; use anyhow::Result;
use autoconfig::config::Config as AutoConfig; use autoconfig::config::Config as AutoConfig;
use dialoguer::Select; use dialoguer::Select;
use log::{debug, warn};
use std::sync::OnceLock;
#[cfg(feature = "imap")] #[cfg(feature = "imap")]
use crate::imap; use crate::imap;
@ -34,26 +32,10 @@ const SEND_MESSAGE_BACKEND_KINDS: &[BackendKind] = &[
BackendKind::Sendmail, BackendKind::Sendmail,
]; ];
static AUTOCONFIG: OnceLock<AutoConfig> = OnceLock::new();
#[cfg(any(feature = "imap", feature = "smtp"))]
pub(crate) async fn get_or_init_autoconfig(email: &str) -> Option<&AutoConfig> {
match AUTOCONFIG.get() {
Some(autoconfig) => Some(autoconfig),
None => match autoconfig::from_addr(email).await {
Ok(autoconfig) => Some(AUTOCONFIG.get_or_init(|| autoconfig)),
Err(err) => {
warn!("cannot discover SMTP configuration from {email}: {err}");
debug!("{err:?}");
None
}
},
}
}
pub(crate) async fn configure( pub(crate) async fn configure(
#[allow(unused)] account_name: &str, #[allow(unused)] account_name: &str,
#[allow(unused)] email: &str, #[allow(unused)] email: &str,
autoconfig: Option<&AutoConfig>,
) -> Result<Option<BackendConfig>> { ) -> Result<Option<BackendConfig>> {
let kind = Select::with_theme(&*THEME) let kind = Select::with_theme(&*THEME)
.with_prompt("Default email backend") .with_prompt("Default email backend")
@ -65,7 +47,7 @@ pub(crate) async fn configure(
let config = match kind { let config = match kind {
#[cfg(feature = "imap")] #[cfg(feature = "imap")]
Some(kind) if kind == BackendKind::Imap => { Some(kind) if kind == BackendKind::Imap => {
Some(imap::wizard::configure(account_name, email).await?) Some(imap::wizard::configure(account_name, email, autoconfig).await?)
} }
#[cfg(feature = "maildir")] #[cfg(feature = "maildir")]
Some(kind) if kind == BackendKind::Maildir => Some(maildir::wizard::configure()?), Some(kind) if kind == BackendKind::Maildir => Some(maildir::wizard::configure()?),
@ -80,6 +62,7 @@ pub(crate) async fn configure(
pub(crate) async fn configure_sender( pub(crate) async fn configure_sender(
#[allow(unused)] account_name: &str, #[allow(unused)] account_name: &str,
#[allow(unused)] email: &str, #[allow(unused)] email: &str,
autoconfig: Option<&AutoConfig>,
) -> Result<Option<BackendConfig>> { ) -> Result<Option<BackendConfig>> {
let kind = Select::with_theme(&*THEME) let kind = Select::with_theme(&*THEME)
.with_prompt("Backend for sending messages") .with_prompt("Backend for sending messages")
@ -91,7 +74,7 @@ pub(crate) async fn configure_sender(
let config = match kind { let config = match kind {
#[cfg(feature = "smtp")] #[cfg(feature = "smtp")]
Some(kind) if kind == BackendKind::Smtp => { Some(kind) if kind == BackendKind::Smtp => {
Some(smtp::wizard::configure(account_name, email).await?) Some(smtp::wizard::configure(account_name, email, autoconfig).await?)
} }
#[cfg(feature = "sendmail")] #[cfg(feature = "sendmail")]
Some(kind) if kind == BackendKind::Sendmail => Some(sendmail::wizard::configure()?), Some(kind) if kind == BackendKind::Sendmail => Some(sendmail::wizard::configure()?),

View file

@ -191,13 +191,15 @@ mod test {
use crate::{account::config::TomlAccountConfig, config::TomlConfig}; use crate::{account::config::TomlAccountConfig, config::TomlConfig};
use super::pretty_serialize;
fn assert_eq(config: TomlAccountConfig, expected_toml: &str) { fn assert_eq(config: TomlAccountConfig, expected_toml: &str) {
let config = TomlConfig { let config = TomlConfig {
accounts: HashMap::from_iter([("test".into(), config)]), accounts: HashMap::from_iter([("test".into(), config)]),
..Default::default() ..Default::default()
}; };
let toml = super::pretty_serialize(&config).expect("serialize error"); let toml = pretty_serialize(&config).expect("serialize error");
assert_eq!(toml, expected_toml); assert_eq!(toml, expected_toml);
let expected_config = toml::from_str(&toml).expect("deserialize error"); let expected_config = toml::from_str(&toml).expect("deserialize error");

View file

@ -1,5 +1,5 @@
use anyhow::Result; use anyhow::Result;
use autoconfig::config::{AuthenticationType, SecurityType, ServerType}; use autoconfig::config::{AuthenticationType, Config as AutoConfig, SecurityType, ServerType};
use dialoguer::{Confirm, Input, Password, Select}; use dialoguer::{Confirm, Input, Password, Select};
use email::{ use email::{
account::config::{ account::config::{
@ -12,7 +12,7 @@ use oauth::v2_0::{AuthorizationCodeGrant, Client};
use secret::Secret; use secret::Secret;
use crate::{ use crate::{
backend::{config::BackendConfig, wizard::get_or_init_autoconfig}, backend::config::BackendConfig,
ui::{prompt, THEME}, ui::{prompt, THEME},
wizard_log, wizard_prompt, wizard_log, wizard_prompt,
}; };
@ -32,8 +32,11 @@ const KEYRING: &str = "Ask my password, then save it in my system's global keyri
const RAW: &str = "Ask my password, then save it in the configuration file (not safe)"; const RAW: &str = "Ask my password, then save it in the configuration file (not safe)";
const CMD: &str = "Ask me a shell command that exposes my password"; const CMD: &str = "Ask me a shell command that exposes my password";
pub(crate) async fn configure(account_name: &str, email: &str) -> Result<BackendConfig> { pub(crate) async fn configure(
let autoconfig = get_or_init_autoconfig(email).await; account_name: &str,
email: &str,
autoconfig: Option<&AutoConfig>,
) -> Result<BackendConfig> {
let autoconfig_oauth2 = autoconfig.and_then(|c| c.oauth2()); let autoconfig_oauth2 = autoconfig.and_then(|c| c.oauth2());
let autoconfig_server = autoconfig.and_then(|c| { let autoconfig_server = autoconfig.and_then(|c| {
c.email_provider() c.email_provider()

View file

@ -1,5 +1,5 @@
use anyhow::Result; use anyhow::Result;
use autoconfig::config::{AuthenticationType, SecurityType, ServerType}; use autoconfig::config::{AuthenticationType, Config as AutoConfig, SecurityType, ServerType};
use dialoguer::{Confirm, Input, Password, Select}; use dialoguer::{Confirm, Input, Password, Select};
use email::{ use email::{
account::config::{ account::config::{
@ -12,7 +12,7 @@ use oauth::v2_0::{AuthorizationCodeGrant, Client};
use secret::Secret; use secret::Secret;
use crate::{ use crate::{
backend::{config::BackendConfig, wizard::get_or_init_autoconfig}, backend::config::BackendConfig,
ui::{prompt, THEME}, ui::{prompt, THEME},
wizard_log, wizard_prompt, wizard_log, wizard_prompt,
}; };
@ -32,8 +32,11 @@ const KEYRING: &str = "Ask my password, then save it in my system's global keyri
const RAW: &str = "Ask my password, then save it in the configuration file (not safe)"; const RAW: &str = "Ask my password, then save it in the configuration file (not safe)";
const CMD: &str = "Ask me a shell command that exposes my password"; const CMD: &str = "Ask me a shell command that exposes my password";
pub(crate) async fn configure(account_name: &str, email: &str) -> Result<BackendConfig> { pub(crate) async fn configure(
let autoconfig = get_or_init_autoconfig(email).await; account_name: &str,
email: &str,
autoconfig: Option<&AutoConfig>,
) -> Result<BackendConfig> {
let autoconfig_oauth2 = autoconfig.and_then(|c| c.oauth2()); let autoconfig_oauth2 = autoconfig.and_then(|c| c.oauth2());
let autoconfig_server = autoconfig.and_then(|c| { let autoconfig_server = autoconfig.and_then(|c| {
c.email_provider() c.email_provider()
@ -80,9 +83,9 @@ pub(crate) async fn configure(account_name: &str, email: &str) -> Result<Backend
.and_then(|s| s.port()) .and_then(|s| s.port())
.map(ToOwned::to_owned) .map(ToOwned::to_owned)
.unwrap_or_else(|| match &autoconfig_encryption { .unwrap_or_else(|| match &autoconfig_encryption {
SmtpEncryptionKind::Tls => 993, SmtpEncryptionKind::Tls => 465,
SmtpEncryptionKind::StartTls => 143, SmtpEncryptionKind::StartTls => 587,
SmtpEncryptionKind::None => 143, SmtpEncryptionKind::None => 25,
}); });
let (encryption, default_port) = match encryption_idx { let (encryption, default_port) = match encryption_idx {