add pgp support

This commit is contained in:
Clément DOUIN 2023-08-02 18:03:47 +02:00
parent 183aa2f306
commit 99ec7c6d97
No known key found for this signature in database
GPG key ID: 353E4A18EE0FAB72
9 changed files with 141 additions and 98 deletions

103
Cargo.lock generated
View file

@ -1096,9 +1096,9 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
[[package]] [[package]]
name = "form_urlencoded" name = "form_urlencoded"
version = "1.1.0" version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8" checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652"
dependencies = [ dependencies = [
"percent-encoding", "percent-encoding",
] ]
@ -1348,7 +1348,7 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
[[package]] [[package]]
name = "himalaya" name = "himalaya"
version = "0.8.4-beta" version = "0.9.0-beta"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"async-trait", "async-trait",
@ -1517,6 +1517,22 @@ dependencies = [
"tokio-rustls 0.23.4", "tokio-rustls 0.23.4",
] ]
[[package]]
name = "hyper-rustls"
version = "0.24.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8d78e1e73ec14cf7375674f74d7dde185c8206fd9dea6fb6295e8a98098aaa97"
dependencies = [
"futures-util",
"http",
"hyper",
"log",
"rustls 0.21.1",
"rustls-native-certs",
"tokio",
"tokio-rustls 0.24.0",
]
[[package]] [[package]]
name = "iana-time-zone" name = "iana-time-zone"
version = "0.1.53" version = "0.1.53"
@ -1569,9 +1585,9 @@ dependencies = [
[[package]] [[package]]
name = "idna" name = "idna"
version = "0.3.0" version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6" checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c"
dependencies = [ dependencies = [
"unicode-bidi", "unicode-bidi",
"unicode-normalization", "unicode-normalization",
@ -2291,9 +2307,9 @@ dependencies = [
[[package]] [[package]]
name = "percent-encoding" name = "percent-encoding"
version = "2.2.0" version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94"
[[package]] [[package]]
name = "petgraph" name = "petgraph"
@ -2397,7 +2413,7 @@ dependencies = [
[[package]] [[package]]
name = "pimalaya-email" name = "pimalaya-email"
version = "0.14.1-beta" version = "0.14.1-beta"
source = "git+https://git.sr.ht/~soywod/pimalaya#a912fa5d3babfdaa8d240581f9a831d7118cb729" source = "git+https://git.sr.ht/~soywod/pimalaya#da0eb6f289cf16971b62347ac00a799c2fa05a99"
dependencies = [ dependencies = [
"advisory-lock", "advisory-lock",
"ammonia", "ammonia",
@ -2418,13 +2434,12 @@ dependencies = [
"notmuch", "notmuch",
"once_cell", "once_cell",
"ouroboros", "ouroboros",
"pgp",
"pimalaya-email-tpl", "pimalaya-email-tpl",
"pimalaya-keyring", "pimalaya-keyring",
"pimalaya-oauth2", "pimalaya-oauth2",
"pimalaya-pgp",
"pimalaya-process", "pimalaya-process",
"pimalaya-secret", "pimalaya-secret",
"rand",
"rayon", "rayon",
"regex", "regex",
"rfc2047-decoder", "rfc2047-decoder",
@ -2432,7 +2447,6 @@ dependencies = [
"rustls 0.21.1", "rustls 0.21.1",
"rustls-native-certs", "rustls-native-certs",
"shellexpand", "shellexpand",
"smallvec",
"thiserror", "thiserror",
"tokio", "tokio",
"tokio-rustls 0.24.0", "tokio-rustls 0.24.0",
@ -2445,9 +2459,8 @@ dependencies = [
[[package]] [[package]]
name = "pimalaya-email-tpl" name = "pimalaya-email-tpl"
version = "0.3.1" version = "0.3.2-beta"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "git+https://git.sr.ht/~soywod/pimalaya#da0eb6f289cf16971b62347ac00a799c2fa05a99"
checksum = "b2a8b37bec4947fb310d807e92e2970d088da3416bc2c7d897814187c639f50a"
dependencies = [ dependencies = [
"async-recursion", "async-recursion",
"chumsky 0.9.0", "chumsky 0.9.0",
@ -2455,7 +2468,9 @@ dependencies = [
"mail-builder", "mail-builder",
"mail-parser", "mail-parser",
"nanohtml2text", "nanohtml2text",
"pimalaya-process", "pimalaya-keyring",
"pimalaya-pgp",
"pimalaya-secret",
"shellexpand", "shellexpand",
"thiserror", "thiserror",
"tree_magic", "tree_magic",
@ -2463,9 +2478,8 @@ dependencies = [
[[package]] [[package]]
name = "pimalaya-keyring" name = "pimalaya-keyring"
version = "0.0.5" version = "0.0.6-beta"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "git+https://git.sr.ht/~soywod/pimalaya#da0eb6f289cf16971b62347ac00a799c2fa05a99"
checksum = "ac7b7da6f9ce8a647ab83dda8cfd657dc0acb62e6ac1edd95f3b434341fadb03"
dependencies = [ dependencies = [
"keyring", "keyring",
"log", "log",
@ -2474,9 +2488,8 @@ dependencies = [
[[package]] [[package]]
name = "pimalaya-oauth2" name = "pimalaya-oauth2"
version = "0.0.4" version = "0.0.5-beta"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "git+https://git.sr.ht/~soywod/pimalaya#da0eb6f289cf16971b62347ac00a799c2fa05a99"
checksum = "c47b1326c05b2f2ed610faf2eb03aecd5caaf4b3a78db34cfa97fc102c9b72ca"
dependencies = [ dependencies = [
"log", "log",
"oauth2", "oauth2",
@ -2486,11 +2499,30 @@ dependencies = [
"url", "url",
] ]
[[package]]
name = "pimalaya-pgp"
version = "0.0.1"
source = "git+https://git.sr.ht/~soywod/pimalaya#da0eb6f289cf16971b62347ac00a799c2fa05a99"
dependencies = [
"async-recursion",
"futures",
"hyper",
"hyper-rustls 0.24.1",
"log",
"pgp",
"rand",
"sha1",
"smallvec",
"thiserror",
"tokio",
"url",
"z-base-32",
]
[[package]] [[package]]
name = "pimalaya-process" name = "pimalaya-process"
version = "0.0.5" version = "0.0.6-beta"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "git+https://git.sr.ht/~soywod/pimalaya#da0eb6f289cf16971b62347ac00a799c2fa05a99"
checksum = "1a9d1e1ab6334d4e4d06613cd65f3fed34e5d2d7b1488f3d1a0d2fdffdba5d1c"
dependencies = [ dependencies = [
"log", "log",
"thiserror", "thiserror",
@ -2499,9 +2531,8 @@ dependencies = [
[[package]] [[package]]
name = "pimalaya-secret" name = "pimalaya-secret"
version = "0.0.5" version = "0.0.6-beta"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "git+https://git.sr.ht/~soywod/pimalaya#da0eb6f289cf16971b62347ac00a799c2fa05a99"
checksum = "966d3dd6e8d9dd39ac80c20597d68b47eb5170c968fbe3ebbb425fca530037f6"
dependencies = [ dependencies = [
"log", "log",
"pimalaya-keyring", "pimalaya-keyring",
@ -2787,7 +2818,7 @@ dependencies = [
"http", "http",
"http-body", "http-body",
"hyper", "hyper",
"hyper-rustls", "hyper-rustls 0.23.2",
"ipnet", "ipnet",
"js-sys", "js-sys",
"log", "log",
@ -3681,9 +3712,9 @@ checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba"
[[package]] [[package]]
name = "unicode-bidi" name = "unicode-bidi"
version = "0.3.8" version = "0.3.13"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460"
[[package]] [[package]]
name = "unicode-ident" name = "unicode-ident"
@ -3714,12 +3745,12 @@ checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a"
[[package]] [[package]]
name = "url" name = "url"
version = "2.3.1" version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643" checksum = "50bff7831e19200a85b17131d085c25d7811bc4e186efdaf54bbd132994a88cb"
dependencies = [ dependencies = [
"form_urlencoded", "form_urlencoded",
"idna 0.3.0", "idna 0.4.0",
"percent-encoding", "percent-encoding",
"serde", "serde",
] ]
@ -4087,6 +4118,12 @@ dependencies = [
"zeroize", "zeroize",
] ]
[[package]]
name = "z-base-32"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e897c250a7dfbb7002a324817f2540ab789a8f32d505032ca623a4109245e87"
[[package]] [[package]]
name = "zeroize" name = "zeroize"
version = "1.5.7" version = "1.5.7"

View file

@ -1,7 +1,7 @@
[package] [package]
name = "himalaya" name = "himalaya"
description = "CLI to manage your emails." description = "CLI to manage your emails."
version = "0.8.4-beta" version = "0.9.0-beta"
authors = ["soywod <clement.douin@posteo.net>"] authors = ["soywod <clement.douin@posteo.net>"]
edition = "2021" edition = "2021"
license = "MIT" license = "MIT"
@ -46,10 +46,10 @@ log = "0.4"
md5 = "0.7.0" md5 = "0.7.0"
once_cell = "1.16.0" once_cell = "1.16.0"
pimalaya-email = { git = "https://git.sr.ht/~soywod/pimalaya", default-features = false } pimalaya-email = { git = "https://git.sr.ht/~soywod/pimalaya", default-features = false }
pimalaya-keyring = "=0.0.5" pimalaya-keyring = { git = "https://git.sr.ht/~soywod/pimalaya" }
pimalaya-oauth2 = "=0.0.4" pimalaya-oauth2 = { git = "https://git.sr.ht/~soywod/pimalaya" }
pimalaya-process = "=0.0.5" pimalaya-process = { git = "https://git.sr.ht/~soywod/pimalaya" }
pimalaya-secret = "=0.0.5" pimalaya-secret = { git = "https://git.sr.ht/~soywod/pimalaya" }
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0" serde_json = "1.0"
shellexpand = "2.1" shellexpand = "2.1"

View file

@ -5,7 +5,9 @@ use pimalaya_email::backend::{ImapAuthConfig, ImapConfig};
#[cfg(feature = "smtp-sender")] #[cfg(feature = "smtp-sender")]
use pimalaya_email::sender::{SmtpAuthConfig, SmtpConfig}; use pimalaya_email::sender::{SmtpAuthConfig, SmtpConfig};
use pimalaya_email::{ use pimalaya_email::{
account::{OAuth2Config, OAuth2Method, OAuth2Scopes, PasswdConfig}, account::{
OAuth2Config, OAuth2Method, OAuth2Scopes, PasswdConfig, PgpConfig, PgpKey, PgpNativeConfig,
},
backend::{BackendConfig, MaildirConfig}, backend::{BackendConfig, MaildirConfig},
email::{EmailHooks, EmailTextPlainFormat}, email::{EmailHooks, EmailTextPlainFormat},
folder::sync::FolderSyncStrategy, folder::sync::FolderSyncStrategy,
@ -387,3 +389,37 @@ pub enum FolderSyncStrategyDef {
#[serde(alias = "ignore")] #[serde(alias = "ignore")]
Exclude(HashSet<String>), Exclude(HashSet<String>),
} }
#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
#[serde(remote = "PgpConfig", tag = "backend", rename_all = "kebab-case")]
pub enum PgpConfigDef {
#[default]
None,
#[serde(with = "PgpNativeConfigDef")]
Native(PgpNativeConfig),
}
#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
#[serde(remote = "PgpNativeConfig", rename_all = "kebab-case")]
pub struct PgpNativeConfigDef {
#[serde(default, with = "PgpKeyDef")]
secret_key: PgpKey,
#[serde(default, with = "SecretDef")]
secret_key_passwd: Secret,
#[serde(default, with = "PgpKeyDef")]
public_key: PgpKey,
#[serde(default = "PgpNativeConfig::default_wkd")]
wkd: bool,
#[serde(default = "PgpNativeConfig::default_key_servers")]
key_servers: Vec<String>,
}
#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
#[serde(remote = "PgpKey", rename_all = "kebab-case")]
pub enum PgpKeyDef {
#[default]
None,
Path(PathBuf),
#[serde(with = "EntryDef")]
Keyring(Entry),
}

View file

@ -8,7 +8,7 @@ use pimalaya_email::backend::ImapAuthConfig;
#[cfg(feature = "smtp-sender")] #[cfg(feature = "smtp-sender")]
use pimalaya_email::sender::SmtpAuthConfig; use pimalaya_email::sender::SmtpAuthConfig;
use pimalaya_email::{ use pimalaya_email::{
account::AccountConfig, account::{AccountConfig, PgpConfig},
backend::BackendConfig, backend::BackendConfig,
email::{EmailHooks, EmailTextPlainFormat}, email::{EmailHooks, EmailTextPlainFormat},
folder::sync::FolderSyncStrategy, folder::sync::FolderSyncStrategy,
@ -90,6 +90,9 @@ pub struct DeserializedAccountConfig {
pub backend: BackendConfig, pub backend: BackendConfig,
#[serde(flatten, with = "SenderConfigDef")] #[serde(flatten, with = "SenderConfigDef")]
pub sender: SenderConfig, pub sender: SenderConfig,
#[serde(default, with = "PgpConfigDef")]
pub pgp: PgpConfig,
} }
impl DeserializedAccountConfig { impl DeserializedAccountConfig {
@ -155,46 +158,6 @@ impl DeserializedAccountConfig {
.map(ToOwned::to_owned) .map(ToOwned::to_owned)
.or_else(|| config.email_reading_headers.as_ref().map(ToOwned::to_owned)), .or_else(|| config.email_reading_headers.as_ref().map(ToOwned::to_owned)),
email_reading_format: self.email_reading_format.clone(), email_reading_format: self.email_reading_format.clone(),
email_reading_verify_cmd: self
.email_reading_verify_cmd
.as_ref()
.map(ToOwned::to_owned)
.or_else(|| {
config
.email_reading_verify_cmd
.as_ref()
.map(ToOwned::to_owned)
}),
email_reading_decrypt_cmd: self
.email_reading_decrypt_cmd
.as_ref()
.map(ToOwned::to_owned)
.or_else(|| {
config
.email_reading_decrypt_cmd
.as_ref()
.map(ToOwned::to_owned)
}),
email_writing_sign_cmd: self
.email_writing_sign_cmd
.as_ref()
.map(ToOwned::to_owned)
.or_else(|| {
config
.email_writing_sign_cmd
.as_ref()
.map(ToOwned::to_owned)
}),
email_writing_encrypt_cmd: self
.email_writing_encrypt_cmd
.as_ref()
.map(ToOwned::to_owned)
.or_else(|| {
config
.email_writing_encrypt_cmd
.as_ref()
.map(ToOwned::to_owned)
}),
email_writing_headers: self email_writing_headers: self
.email_writing_headers .email_writing_headers
.as_ref() .as_ref()
@ -258,7 +221,7 @@ impl DeserializedAccountConfig {
sender sender
}, },
pgp: Default::default(), pgp: self.pgp.clone(),
} }
} }
} }

View file

@ -72,6 +72,8 @@ pub async fn configure(config: &AccountConfig, reset: bool) -> Result<()> {
warn!("{err}"); warn!("{err}");
} }
} }
config.pgp.reset().await?;
} }
#[cfg(feature = "imap-backend")] #[cfg(feature = "imap-backend")]
@ -102,6 +104,11 @@ pub async fn configure(config: &AccountConfig, reset: bool) -> Result<()> {
}?; }?;
} }
config
.pgp
.configure(&config.email, || prompt_passwd("PGP secret key password"))
.await?;
println!( println!(
"Account successfully {}configured!", "Account successfully {}configured!",
if reset { "re" } else { "" } if reset { "re" } else { "" }

View file

@ -185,7 +185,7 @@ pub async fn mailto<P: Printer>(
let tpl = config let tpl = config
.generate_tpl_interpreter() .generate_tpl_interpreter()
.show_only_headers(config.email_writing_headers()) .with_show_only_headers(config.email_writing_headers())
.interpret_msg_builder(builder) .interpret_msg_builder(builder)
.await?; .await?;
@ -235,9 +235,9 @@ pub async fn read<P: Printer>(
let tpl: String = email let tpl: String = email
.to_read_tpl(&config, |tpl| match text_mime { .to_read_tpl(&config, |tpl| match text_mime {
"html" => tpl "html" => tpl
.hide_all_headers() .with_hide_all_headers()
.filter_parts(FilterParts::Only("text/html".into())), .with_filter_parts(FilterParts::Only("text/html".into())),
_ => tpl.show_additional_headers(&headers), _ => tpl.with_show_additional_headers(&headers),
}) })
.await? .await?
.into(); .into();

View file

@ -3,7 +3,7 @@ use atty::Stream;
use pimalaya_email::{ use pimalaya_email::{
account::AccountConfig, account::AccountConfig,
backend::Backend, backend::Backend,
email::{Flags, Message, Tpl}, email::{Flag, Flags, Message, Tpl},
sender::Sender, sender::Sender,
}; };
use std::io::{stdin, BufRead}; use std::io::{stdin, BufRead};
@ -86,8 +86,8 @@ pub async fn save<P: Printer>(
.collect::<Vec<String>>() .collect::<Vec<String>>()
.join("\n") .join("\n")
}) })
.some_pgp_sign_cmd(config.email_writing_sign_cmd.clone()) .with_pgp_encrypt(config.pgp.clone())
.some_pgp_encrypt_cmd(config.email_writing_encrypt_cmd.clone()) .with_pgp_sign(config.pgp.clone())
.compile() .compile()
.await? .await?
.write_to_vec()?; .write_to_vec()?;
@ -103,9 +103,9 @@ pub async fn send<P: Printer>(
printer: &mut P, printer: &mut P,
backend: &mut dyn Backend, backend: &mut dyn Backend,
sender: &mut dyn Sender, sender: &mut dyn Sender,
folder: &str,
tpl: String, tpl: String,
) -> Result<()> { ) -> Result<()> {
let folder = config.sent_folder_alias()?;
let email = Tpl::from(if atty::is(Stream::Stdin) || printer.is_json() { let email = Tpl::from(if atty::is(Stream::Stdin) || printer.is_json() {
tpl.replace("\r", "") tpl.replace("\r", "")
} else { } else {
@ -116,8 +116,8 @@ pub async fn send<P: Printer>(
.collect::<Vec<String>>() .collect::<Vec<String>>()
.join("\n") .join("\n")
}) })
.some_pgp_sign_cmd(config.email_writing_sign_cmd.clone()) .with_pgp_encrypt(config.pgp.clone())
.some_pgp_encrypt_cmd(config.email_writing_encrypt_cmd.clone()) .with_pgp_sign(config.pgp.clone())
.compile() .compile()
.await? .await?
.write_to_vec()?; .write_to_vec()?;
@ -125,7 +125,9 @@ pub async fn send<P: Printer>(
sender.send(&email).await?; sender.send(&email).await?;
if config.email_sending_save_copy { if config.email_sending_save_copy {
backend.add_email(folder, &email, &Flags::default()).await?; backend
.add_email(&folder, &email, &Flags::from_iter([Flag::Seen]))
.await?;
} }
printer.print("Template successfully sent!")?; printer.print("Template successfully sent!")?;

View file

@ -513,7 +513,6 @@ async fn main() -> Result<()> {
return Ok(()); return Ok(());
} }
Some(tpl::args::Cmd::Send(tpl)) => { Some(tpl::args::Cmd::Send(tpl)) => {
let folder = folder.unwrap_or(DEFAULT_INBOX_FOLDER);
let mut backend = backend_builder.clone().into_build().await?; let mut backend = backend_builder.clone().into_build().await?;
let mut sender = sender_builder.build().await?; let mut sender = sender_builder.build().await?;
tpl::handlers::send( tpl::handlers::send(
@ -521,7 +520,6 @@ async fn main() -> Result<()> {
&mut printer, &mut printer,
backend.as_mut(), backend.as_mut(),
sender.as_mut(), sender.as_mut(),
&folder,
tpl, tpl,
) )
.await?; .await?;

View file

@ -76,8 +76,8 @@ pub async fn edit_tpl_with_editor<P: Printer>(
Ok(PostEditChoice::Send) => { Ok(PostEditChoice::Send) => {
printer.print_log("Sending email…")?; printer.print_log("Sending email…")?;
let email = tpl let email = tpl
.some_pgp_sign_cmd(config.email_writing_sign_cmd.clone()) .with_pgp_encrypt(config.pgp.clone())
.some_pgp_encrypt_cmd(config.email_writing_encrypt_cmd.clone()) .with_pgp_sign(config.pgp.clone())
.compile() .compile()
.await? .await?
.write_to_vec()?; .write_to_vec()?;
@ -103,8 +103,8 @@ pub async fn edit_tpl_with_editor<P: Printer>(
} }
Ok(PostEditChoice::RemoteDraft) => { Ok(PostEditChoice::RemoteDraft) => {
let email = tpl let email = tpl
.some_pgp_sign_cmd(config.email_writing_sign_cmd.clone()) .with_pgp_encrypt(config.pgp.clone())
.some_pgp_encrypt_cmd(config.email_writing_encrypt_cmd.clone()) .with_pgp_sign(config.pgp.clone())
.compile() .compile()
.await? .await?
.write_to_vec()?; .write_to_vec()?;