summaryrefslogtreecommitdiffstats
path: root/ui
diff options
context:
space:
mode:
authorManos Pitsidianakis <el13635@mail.ntua.gr>2019-09-28 10:46:49 +0300
committerManos Pitsidianakis <el13635@mail.ntua.gr>2019-09-28 12:19:22 +0300
commite35a93336a2c0ce081cd05f64986c6f6cdc59077 (patch)
treee36c7e99b24f7826980d5eec590551c68713d734 /ui
parent963fdd157503966d4ec16a9b175be6b2caa9fba6 (diff)
Add GPG signing and sig verifying
Diffstat (limited to 'ui')
-rw-r--r--ui/src/components/mail.rs2
-rw-r--r--ui/src/components/mail/compose.rs114
-rw-r--r--ui/src/components/mail/pgp.rs130
-rw-r--r--ui/src/components/mail/view.rs54
-rw-r--r--ui/src/conf.rs12
-rw-r--r--ui/src/execute.rs13
-rw-r--r--ui/src/execute/actions.rs1
7 files changed, 265 insertions, 61 deletions
diff --git a/ui/src/components/mail.rs b/ui/src/components/mail.rs
index a4b215e2..657ae664 100644
--- a/ui/src/components/mail.rs
+++ b/ui/src/components/mail.rs
@@ -33,6 +33,8 @@ pub use crate::view::*;
mod compose;
pub use self::compose::*;
+pub mod pgp;
+
mod accounts;
pub use self::accounts::*;
diff --git a/ui/src/components/mail/compose.rs b/ui/src/components/mail/compose.rs
index b2b13972..ca3cbd9c 100644
--- a/ui/src/components/mail/compose.rs
+++ b/ui/src/components/mail/compose.rs
@@ -44,6 +44,7 @@ pub struct Composer {
form: FormWidget,
mode: ViewMode,
+ sign_mail: ToggleFlag,
dirty: bool,
initialized: bool,
id: ComponentId,
@@ -62,6 +63,7 @@ impl Default for Composer {
form: FormWidget::default(),
mode: ViewMode::Edit,
+ sign_mail: ToggleFlag::Unset,
dirty: true,
initialized: false,
id: ComponentId::new_v4(),
@@ -217,8 +219,38 @@ impl Composer {
}
}
- fn draw_attachments(&self, grid: &mut CellBuffer, area: Area, _context: &mut Context) {
+ fn draw_attachments(&self, grid: &mut CellBuffer, area: Area, context: &Context) {
let attachments_no = self.draft.attachments().len();
+ if self.sign_mail.is_true() {
+ write_string_to_grid(
+ &format!(
+ "☑ sign with {}",
+ context
+ .settings
+ .pgp
+ .key
+ .as_ref()
+ .map(String::as_str)
+ .unwrap_or("default key")
+ ),
+ grid,
+ Color::Default,
+ Color::Default,
+ Attr::Default,
+ (pos_inc(upper_left!(area), (0, 1)), bottom_right!(area)),
+ false,
+ );
+ } else {
+ write_string_to_grid(
+ "☐ don't sign",
+ grid,
+ Color::Default,
+ Color::Default,
+ Attr::Default,
+ (pos_inc(upper_left!(area), (0, 1)), bottom_right!(area)),
+ false,
+ );
+ }
if attachments_no == 0 {
write_string_to_grid(
"no attachments",
@@ -226,7 +258,7 @@ impl Composer {
Color::Default,
Color::Default,
Attr::Default,
- (pos_inc(upper_left!(area), (0, 1)), bottom_right!(area)),
+ (pos_inc(upper_left!(area), (0, 2)), bottom_right!(area)),
false,
);
} else {
@@ -236,7 +268,7 @@ impl Composer {
Color::Default,
Color::Default,
Attr::Default,
- (pos_inc(upper_left!(area), (0, 1)), bottom_right!(area)),
+ (pos_inc(upper_left!(area), (0, 2)), bottom_right!(area)),
false,
);
for (i, a) in self.draft.attachments().iter().enumerate() {
@@ -253,7 +285,7 @@ impl Composer {
Color::Default,
Color::Default,
Attr::Default,
- (pos_inc(upper_left!(area), (0, 2 + i)), bottom_right!(area)),
+ (pos_inc(upper_left!(area), (0, 3 + i)), bottom_right!(area)),
false,
);
} else {
@@ -263,7 +295,7 @@ impl Composer {
Color::Default,
Color::Default,
Attr::Default,
- (pos_inc(upper_left!(area), (0, 2 + i)), bottom_right!(area)),
+ (pos_inc(upper_left!(area), (0, 3 + i)), bottom_right!(area)),
false,
);
}
@@ -291,6 +323,9 @@ impl Component for Composer {
};
if !self.initialized {
+ if self.sign_mail.is_unset() {
+ self.sign_mail = ToggleFlag::InternalVal(context.settings.pgp.auto_sign);
+ }
if !self.draft.headers().contains_key("From") || self.draft.headers()["From"].is_empty()
{
self.draft.headers_mut().insert(
@@ -632,7 +667,12 @@ impl Component for Composer {
}
UIEvent::Input(Key::Char('s')) => {
self.update_draft();
- if send_draft(context, self.account_cursor, self.draft.clone()) {
+ if send_draft(
+ self.sign_mail,
+ context,
+ self.account_cursor,
+ self.draft.clone(),
+ ) {
context
.replies
.push_back(UIEvent::Action(Tab(Kill(self.id))));
@@ -743,6 +783,12 @@ impl Component for Composer {
self.dirty = true;
return true;
}
+ Action::Compose(ComposeAction::ToggleSign) => {
+ let is_true = self.sign_mail.is_true();
+ self.sign_mail = ToggleFlag::from(!is_true);
+ self.dirty = true;
+ return true;
+ }
_ => {}
}
}
@@ -815,7 +861,12 @@ impl Component for Composer {
}
}
-pub fn send_draft(context: &mut Context, account_cursor: usize, draft: Draft) -> bool {
+pub fn send_draft(
+ sign_mail: ToggleFlag,
+ context: &mut Context,
+ account_cursor: usize,
+ mut draft: Draft,
+) -> bool {
use std::io::Write;
use std::process::{Command, Stdio};
let mut failure = true;
@@ -830,6 +881,55 @@ pub fn send_draft(context: &mut Context, account_cursor: usize, draft: Draft) ->
.expect("Failed to start mailer command");
{
let stdin = msmtp.stdin.as_mut().expect("failed to open stdin");
+ if sign_mail.is_true() {
+ let mut body: AttachmentBuilder = Attachment::new(
+ Default::default(),
+ Default::default(),
+ std::mem::replace(&mut draft.body, String::new()).into_bytes(),
+ )
+ .into();
+ if !draft.attachments.is_empty() {
+ let mut parts = std::mem::replace(&mut draft.attachments, Vec::new());
+ parts.insert(0, body);
+ let boundary = ContentType::make_boundary(&parts);
+ body = Attachment::new(
+ ContentType::Multipart {
+ boundary: boundary.into_bytes(),
+ kind: MultipartType::Mixed,
+ parts: parts.into_iter().map(|a| a.into()).collect::<Vec<_>>(),
+ },
+ Default::default(),
+ Vec::new(),
+ )
+ .into();
+ }
+ let output = crate::components::mail::pgp::sign(
+ body.into(),
+ context.settings.pgp.gpg_binary.as_ref().map(String::as_str),
+ context.settings.pgp.key.as_ref().map(String::as_str),
+ );
+ if let Err(e) = &output {
+ debug!("{:?} could not sign draft msg", e);
+ log(
+ format!(
+ "Could not sign draft in account `{}`: {}.",
+ context.accounts[account_cursor].name(),
+ e.to_string()
+ ),
+ ERROR,
+ );
+ context.replies.push_back(UIEvent::Notification(
+ Some(format!(
+ "Could not sign draft in account `{}`.",
+ context.accounts[account_cursor].name()
+ )),
+ e.to_string(),
+ Some(NotificationType::ERROR),
+ ));
+ return false;
+ }
+ draft.attachments.push(output.unwrap());
+ }
let draft = draft.finalise().unwrap();
stdin
.write_all(draft.as_bytes())
diff --git a/ui/src/components/mail/pgp.rs b/ui/src/components/mail/pgp.rs
new file mode 100644
index 00000000..233c633b
--- /dev/null
+++ b/ui/src/components/mail/pgp.rs
@@ -0,0 +1,130 @@
+/*
+ * meli - ui crate.
+ *
+ * Copyright 2019 Manos Pitsidianakis
+ *
+ * This file is part of meli.
+ *
+ * meli is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * meli is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with meli. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+use super::*;
+use std::io::Write;
+use std::process::{Command, Stdio};
+
+pub fn verify_signature(a: &Attachment, context: &mut Context) -> Vec<u8> {
+ match melib::signatures::verify_signature(a) {
+ Ok((bytes, sig)) => {
+ let bytes_file = create_temp_file(&bytes, None, None, true);
+ let signature_file = create_temp_file(sig, None, None, true);
+ if let Ok(gpg) = Command::new(
+ context
+ .settings
+ .pgp
+ .gpg_binary
+ .as_ref()
+ .map(String::as_str)
+ .unwrap_or("gpg2"),
+ )
+ .args(&[
+ "--output",
+ "-",
+ "--verify",
+ signature_file.path.to_str().unwrap(),
+ bytes_file.path.to_str().unwrap(),
+ ])
+ .stdin(Stdio::piped())
+ .stderr(Stdio::piped())
+ .spawn()
+ {
+ return gpg.wait_with_output().unwrap().stderr;
+ } else {
+ context.replies.push_back(UIEvent::Notification(
+ Some(format!(
+ "Failed to launch {} to verify PGP signature",
+ context
+ .settings
+ .pgp
+ .gpg_binary
+ .as_ref()
+ .map(String::as_str)
+ .unwrap_or("gpg2"),
+ )),
+ "see meli.conf(5) for configuration setting pgp.gpg_binary".to_string(),
+ Some(NotificationType::ERROR),
+ ));
+ }
+ }
+ Err(e) => {
+ context.replies.push_back(UIEvent::Notification(
+ Some(e.to_string()),
+ String::new(),
+ Some(NotificationType::ERROR),
+ ));
+ }
+ }
+ Vec::new()
+}
+
+/// Returns multipart/signed
+pub fn sign(
+ a: AttachmentBuilder,
+ gpg_binary: Option<&str>,
+ pgp_key: Option<&str>,
+) -> Result<AttachmentBuilder> {
+ let mut command = Command::new(gpg_binary.unwrap_or("gpg2"));
+ command.args(&[
+ "--digest-algo",
+ "sha512",
+ "--output",
+ "-",
+ "--detach-sig",
+ "--armor",
+ ]);
+ if let Some(key) = pgp_key {
+ command.args(&["--local-user", key]);
+ }
+ let a: Attachment = a.into();
+ let mut gpg = command
+ .stdin(Stdio::piped())
+ .stdout(Stdio::piped())
+ .stderr(Stdio::null())
+ .spawn()?;
+
+ let sig_attachment = {
+ gpg.stdin
+ .as_mut()
+ .unwrap()
+ .write_all(&melib::signatures::convert_attachment_to_rfc_spec(
+ a.into_raw().as_bytes(),
+ ))
+ .unwrap();
+ let gpg = gpg.wait_with_output().unwrap();
+ Attachment::new(ContentType::PGPSignature, Default::default(), gpg.stdout)
+ };
+
+ let a: AttachmentBuilder = a.into();
+ let parts = vec![a, sig_attachment.into()];
+ let boundary = ContentType::make_boundary(&parts);
+ Ok(Attachment::new(
+ ContentType::Multipart {
+ boundary: boundary.into_bytes(),
+ kind: MultipartType::Signed,
+ parts: parts.into_iter().map(|a| a.into()).collect::<Vec<_>>(),
+ },
+ Default::default(),
+ Vec::new(),
+ )
+ .into())
+}
diff --git a/ui/src/components/mail/view.rs b/ui/src/components/mail/view.rs
index 8a8e7c5f..a10d6cae 100644
--- a/ui/src/components/mail/view.rs
+++ b/ui/src/components/mail/view.rs
@@ -196,58 +196,7 @@ impl MailView {
} else if a.is_signed() {
v.clear();
if context.settings.pgp.auto_verify_signatures {
- match melib::signatures::verify_signature(a) {
- Ok((bytes, sig)) => {
- let bytes_file = create_temp_file(&bytes, None, None, true);
- let signature_file = create_temp_file(sig, None, None, true);
- if let Ok(gpg) = Command::new(
- context
- .settings
- .pgp
- .gpg_binary
- .as_ref()
- .map(String::as_str)
- .unwrap_or("gpg2"),
- )
- .args(&[
- "--output",
- "-",
- "--verify",
- signature_file.path.to_str().unwrap(),
- bytes_file.path.to_str().unwrap(),
- ])
- .stdin(Stdio::piped())
- .stderr(Stdio::piped())
- .spawn()
- {
- v.extend(gpg.wait_with_output().unwrap().stderr);
- } else {
- context.replies.push_back(UIEvent::Notification(
- Some(format!(
- "Failed to launch {} to verify PGP signature",
- context
- .settings
- .pgp
- .gpg_binary
- .as_ref()
- .map(String::as_str)
- .unwrap_or("gpg2"),
- )),
- "see meli.conf(5) for configuration setting pgp.gpg_binary"
- .to_string(),
- Some(NotificationType::ERROR),
- ));
- return;
- }
- }
- Err(e) => {
- context.replies.push_back(UIEvent::Notification(
- Some(e.to_string()),
- String::new(),
- Some(NotificationType::ERROR),
- ));
- }
- }
+ v.extend(crate::mail::pgp::verify_signature(a, context).into_iter());
}
}
})),
@@ -1041,6 +990,7 @@ impl Component for MailView {
),
);
if super::compose::send_draft(
+ ToggleFlag::False,
/* FIXME: refactor to avoid unsafe.
*
* actions contains byte slices from the envelope's
diff --git a/ui/src/conf.rs b/ui/src/conf.rs
index dc6cc169..33af0ced 100644
--- a/ui/src/conf.rs
+++ b/ui/src/conf.rs
@@ -58,7 +58,7 @@ macro_rules! split_command {
}};
}
-#[derive(Debug, Clone, PartialEq)]
+#[derive(Copy, Debug, Clone, PartialEq)]
pub enum ToggleFlag {
Unset,
InternalVal(bool),
@@ -66,6 +66,16 @@ pub enum ToggleFlag {
True,
}
+impl From<bool> for ToggleFlag {
+ fn from(val: bool) -> Self {
+ if val {
+ ToggleFlag::True
+ } else {
+ ToggleFlag::False
+ }
+ }
+}
+
impl Default for ToggleFlag {
fn default() -> Self {
ToggleFlag::Unset
diff --git a/ui/src/execute.rs b/ui/src/execute.rs
index 18e3a47e..6393c05f 100644
--- a/ui/src/execute.rs
+++ b/ui/src/execute.rs
@@ -214,6 +214,17 @@ define_commands!([
);
)
},
+ { tags: ["toggle sign "],
+ desc: "switch between sign/unsign for this draft",
+ parser:(
+ named!( toggle_sign<Action>,
+ do_parse!(
+ ws!(tag!("toggle sign"))
+ >> (Compose(ToggleSign))
+ )
+ );
+ )
+ },
{ tags: ["create-folder "],
desc: "create-folder ACCOUNT FOLDER_PATH",
parser:(
@@ -350,7 +361,7 @@ named!(
named!(
compose_action<Action>,
- alt_complete!(add_attachment | remove_attachment)
+ alt_complete!(add_attachment | remove_attachment | toggle_sign)
);
named!(pub parse_command<Action>,
diff --git a/ui/src/execute/actions.rs b/ui/src/execute/actions.rs
index 23605402..4357e26a 100644
--- a/ui/src/execute/actions.rs
+++ b/ui/src/execute/actions.rs
@@ -71,6 +71,7 @@ pub enum PagerAction {
pub enum ComposeAction {
AddAttachment(String),
RemoveAttachment(usize),
+ ToggleSign,
}
#[derive(Debug)]