Add GPG signing and sig verifying
parent
963fdd1575
commit
e35a93336a
2
meli.1
2
meli.1
|
@ -180,6 +180,8 @@ in composer, add
|
||||||
as an attachment
|
as an attachment
|
||||||
.It Ic remove-attachment Ar INDEX
|
.It Ic remove-attachment Ar INDEX
|
||||||
remove attachment with given index
|
remove attachment with given index
|
||||||
|
.It Ic toggle sign
|
||||||
|
toggle between signing and not signing this message. If the gpg invocation fails then the mail won't be sent.
|
||||||
.El
|
.El
|
||||||
.Pp
|
.Pp
|
||||||
generic commands:
|
generic commands:
|
||||||
|
|
|
@ -74,6 +74,27 @@ impl<'a> From<&'a [u8]> for Charset {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Display for Charset {
|
||||||
|
fn fmt(&self, f: &mut Formatter) -> FmtResult {
|
||||||
|
match self {
|
||||||
|
Charset::Ascii => write!(f, "us-ascii"),
|
||||||
|
Charset::UTF8 => write!(f, "utf-8"),
|
||||||
|
Charset::UTF16 => write!(f, "utf-16"),
|
||||||
|
Charset::ISO8859_1 => write!(f, "iso-8859-1"),
|
||||||
|
Charset::ISO8859_2 => write!(f, "iso-8859-2"),
|
||||||
|
Charset::ISO8859_7 => write!(f, "iso-8859-7"),
|
||||||
|
Charset::ISO8859_15 => write!(f, "iso-8859-15"),
|
||||||
|
Charset::Windows1251 => write!(f, "windows-1251"),
|
||||||
|
Charset::Windows1252 => write!(f, "windows-1252"),
|
||||||
|
Charset::Windows1253 => write!(f, "windows-1253"),
|
||||||
|
Charset::GBK => write!(f, "GBK"),
|
||||||
|
Charset::GB2312 => write!(f, "gb2312"),
|
||||||
|
Charset::BIG5 => write!(f, "BIG5"),
|
||||||
|
Charset::ISO2022JP => write!(f, "ISO-2022-JP"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||||
pub enum MultipartType {
|
pub enum MultipartType {
|
||||||
Mixed,
|
Mixed,
|
||||||
|
@ -264,7 +285,7 @@ pub enum ContentTransferEncoding {
|
||||||
|
|
||||||
impl Default for ContentTransferEncoding {
|
impl Default for ContentTransferEncoding {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
ContentTransferEncoding::_7Bit
|
ContentTransferEncoding::_8Bit
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -273,6 +273,23 @@ impl From<Attachment> for AttachmentBuilder {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<AttachmentBuilder> for Attachment {
|
||||||
|
fn from(val: AttachmentBuilder) -> Self {
|
||||||
|
let AttachmentBuilder {
|
||||||
|
content_type,
|
||||||
|
content_transfer_encoding,
|
||||||
|
raw,
|
||||||
|
body,
|
||||||
|
} = val;
|
||||||
|
Attachment {
|
||||||
|
content_type,
|
||||||
|
content_transfer_encoding,
|
||||||
|
raw,
|
||||||
|
body,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Immutable attachment type.
|
/// Immutable attachment type.
|
||||||
#[derive(Clone, Serialize, Deserialize, PartialEq)]
|
#[derive(Clone, Serialize, Deserialize, PartialEq)]
|
||||||
pub struct Attachment {
|
pub struct Attachment {
|
||||||
|
@ -546,6 +563,73 @@ impl Attachment {
|
||||||
_ => false,
|
_ => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn into_raw(&self) -> String {
|
||||||
|
let mut ret = String::with_capacity(2 * self.raw.len());
|
||||||
|
fn into_raw_helper(a: &Attachment, ret: &mut String) {
|
||||||
|
ret.extend(
|
||||||
|
format!(
|
||||||
|
"Content-Transfer-Encoding: {}\n",
|
||||||
|
a.content_transfer_encoding
|
||||||
|
)
|
||||||
|
.chars(),
|
||||||
|
);
|
||||||
|
match &a.content_type {
|
||||||
|
ContentType::Text { kind: _, charset } => {
|
||||||
|
ret.extend(
|
||||||
|
format!("Content-Type: {}; charset={}\n\n", a.content_type, charset)
|
||||||
|
.chars(),
|
||||||
|
);
|
||||||
|
ret.extend(String::from_utf8_lossy(a.body()).chars());
|
||||||
|
}
|
||||||
|
ContentType::Multipart {
|
||||||
|
boundary,
|
||||||
|
kind,
|
||||||
|
parts,
|
||||||
|
} => {
|
||||||
|
let boundary = String::from_utf8_lossy(boundary);
|
||||||
|
ret.extend(format!("Content-Type: {}; boundary={}", kind, boundary).chars());
|
||||||
|
if *kind == MultipartType::Signed {
|
||||||
|
ret.extend(
|
||||||
|
"; micalg=pgp-sha512; protocol=\"application/pgp-signature\"".chars(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
ret.push('\n');
|
||||||
|
|
||||||
|
let boundary_start = format!("\n--{}\n", boundary);
|
||||||
|
for p in parts {
|
||||||
|
ret.extend(boundary_start.chars());
|
||||||
|
into_raw_helper(p, ret);
|
||||||
|
}
|
||||||
|
ret.extend(format!("--{}--\n\n", boundary).chars());
|
||||||
|
}
|
||||||
|
ContentType::MessageRfc822 => {
|
||||||
|
ret.extend(format!("Content-Type: {}\n\n", a.content_type).chars());
|
||||||
|
ret.extend(String::from_utf8_lossy(a.body()).chars());
|
||||||
|
}
|
||||||
|
ContentType::PGPSignature => {
|
||||||
|
ret.extend(format!("Content-Type: {}\n\n", a.content_type).chars());
|
||||||
|
ret.extend(String::from_utf8_lossy(a.body()).chars());
|
||||||
|
}
|
||||||
|
ContentType::OctetStream { ref name } => {
|
||||||
|
if let Some(name) = name {
|
||||||
|
ret.extend(
|
||||||
|
format!("Content-Type: {}; name={}\n\n", a.content_type, name).chars(),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
ret.extend(format!("Content-Type: {}\n\n", a.content_type).chars());
|
||||||
|
}
|
||||||
|
ret.push_str(&BASE64_MIME.encode(a.body()).trim());
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
ret.extend(format!("Content-Type: {}\n\n", a.content_type).chars());
|
||||||
|
ret.extend(String::from_utf8_lossy(a.body()).chars());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
into_raw_helper(self, &mut ret);
|
||||||
|
ret
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn interpret_format_flowed(_t: &str) -> String {
|
pub fn interpret_format_flowed(_t: &str) -> String {
|
||||||
|
|
|
@ -18,11 +18,11 @@ use fnv::FnvHashMap;
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Clone)]
|
#[derive(Debug, PartialEq, Clone)]
|
||||||
pub struct Draft {
|
pub struct Draft {
|
||||||
headers: FnvHashMap<String, String>,
|
pub headers: FnvHashMap<String, String>,
|
||||||
header_order: Vec<String>,
|
pub header_order: Vec<String>,
|
||||||
body: String,
|
pub body: String,
|
||||||
|
|
||||||
attachments: Vec<AttachmentBuilder>,
|
pub attachments: Vec<AttachmentBuilder>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Draft {
|
impl Default for Draft {
|
||||||
|
@ -259,7 +259,19 @@ impl Draft {
|
||||||
}
|
}
|
||||||
ret.push_str("MIME-Version: 1.0\n");
|
ret.push_str("MIME-Version: 1.0\n");
|
||||||
|
|
||||||
if !self.attachments.is_empty() {
|
if self.attachments.is_empty() {
|
||||||
|
let content_type: ContentType = Default::default();
|
||||||
|
let content_transfer_encoding: ContentTransferEncoding = ContentTransferEncoding::_8Bit;
|
||||||
|
ret.extend(format!("Content-Type: {}; charset=\"utf-8\"\n", content_type).chars());
|
||||||
|
ret.extend(
|
||||||
|
format!("Content-Transfer-Encoding: {}\n", content_transfer_encoding).chars(),
|
||||||
|
);
|
||||||
|
ret.push('\n');
|
||||||
|
ret.push_str(&self.body);
|
||||||
|
} else if self.attachments.len() == 1 && self.body.is_empty() {
|
||||||
|
let attachment: Attachment = self.attachments.remove(0).into();
|
||||||
|
ret.extend(attachment.into_raw().chars());
|
||||||
|
} else {
|
||||||
let mut parts = Vec::with_capacity(self.attachments.len() + 1);
|
let mut parts = Vec::with_capacity(self.attachments.len() + 1);
|
||||||
let attachments = std::mem::replace(&mut self.attachments, Vec::new());
|
let attachments = std::mem::replace(&mut self.attachments, Vec::new());
|
||||||
let mut body_attachment = AttachmentBuilder::default();
|
let mut body_attachment = AttachmentBuilder::default();
|
||||||
|
@ -267,24 +279,6 @@ impl Draft {
|
||||||
parts.push(body_attachment);
|
parts.push(body_attachment);
|
||||||
parts.extend(attachments.into_iter());
|
parts.extend(attachments.into_iter());
|
||||||
build_multipart(&mut ret, MultipartType::Mixed, parts);
|
build_multipart(&mut ret, MultipartType::Mixed, parts);
|
||||||
} else {
|
|
||||||
if self.body.is_ascii() {
|
|
||||||
ret.push('\n');
|
|
||||||
ret.push_str(&self.body);
|
|
||||||
} else {
|
|
||||||
let content_type: ContentType = Default::default();
|
|
||||||
let content_transfer_encoding: ContentTransferEncoding =
|
|
||||||
ContentTransferEncoding::Base64;
|
|
||||||
|
|
||||||
ret.extend(format!("Content-Type: {}; charset=\"utf-8\"\n", content_type).chars());
|
|
||||||
ret.extend(
|
|
||||||
format!("Content-Transfer-Encoding: {}\n", content_transfer_encoding).chars(),
|
|
||||||
);
|
|
||||||
ret.push('\n');
|
|
||||||
|
|
||||||
ret.push_str(&BASE64_MIME.encode(&self.body.as_bytes()).trim());
|
|
||||||
ret.push('\n');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(ret)
|
Ok(ret)
|
||||||
|
|
|
@ -33,6 +33,8 @@ pub use crate::view::*;
|
||||||
mod compose;
|
mod compose;
|
||||||
pub use self::compose::*;
|
pub use self::compose::*;
|
||||||
|
|
||||||
|
pub mod pgp;
|
||||||
|
|
||||||
mod accounts;
|
mod accounts;
|
||||||
pub use self::accounts::*;
|
pub use self::accounts::*;
|
||||||
|
|
||||||
|
|
|
@ -44,6 +44,7 @@ pub struct Composer {
|
||||||
form: FormWidget,
|
form: FormWidget,
|
||||||
|
|
||||||
mode: ViewMode,
|
mode: ViewMode,
|
||||||
|
sign_mail: ToggleFlag,
|
||||||
dirty: bool,
|
dirty: bool,
|
||||||
initialized: bool,
|
initialized: bool,
|
||||||
id: ComponentId,
|
id: ComponentId,
|
||||||
|
@ -62,6 +63,7 @@ impl Default for Composer {
|
||||||
form: FormWidget::default(),
|
form: FormWidget::default(),
|
||||||
|
|
||||||
mode: ViewMode::Edit,
|
mode: ViewMode::Edit,
|
||||||
|
sign_mail: ToggleFlag::Unset,
|
||||||
dirty: true,
|
dirty: true,
|
||||||
initialized: false,
|
initialized: false,
|
||||||
id: ComponentId::new_v4(),
|
id: ComponentId::new_v4(),
|
||||||
|
@ -217,11 +219,20 @@ 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();
|
let attachments_no = self.draft.attachments().len();
|
||||||
if attachments_no == 0 {
|
if self.sign_mail.is_true() {
|
||||||
write_string_to_grid(
|
write_string_to_grid(
|
||||||
"no attachments",
|
&format!(
|
||||||
|
"☑ sign with {}",
|
||||||
|
context
|
||||||
|
.settings
|
||||||
|
.pgp
|
||||||
|
.key
|
||||||
|
.as_ref()
|
||||||
|
.map(String::as_str)
|
||||||
|
.unwrap_or("default key")
|
||||||
|
),
|
||||||
grid,
|
grid,
|
||||||
Color::Default,
|
Color::Default,
|
||||||
Color::Default,
|
Color::Default,
|
||||||
|
@ -231,7 +242,7 @@ impl Composer {
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
write_string_to_grid(
|
write_string_to_grid(
|
||||||
&format!("{} attachments ", attachments_no),
|
"☐ don't sign",
|
||||||
grid,
|
grid,
|
||||||
Color::Default,
|
Color::Default,
|
||||||
Color::Default,
|
Color::Default,
|
||||||
|
@ -239,6 +250,27 @@ impl Composer {
|
||||||
(pos_inc(upper_left!(area), (0, 1)), bottom_right!(area)),
|
(pos_inc(upper_left!(area), (0, 1)), bottom_right!(area)),
|
||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
if attachments_no == 0 {
|
||||||
|
write_string_to_grid(
|
||||||
|
"no attachments",
|
||||||
|
grid,
|
||||||
|
Color::Default,
|
||||||
|
Color::Default,
|
||||||
|
Attr::Default,
|
||||||
|
(pos_inc(upper_left!(area), (0, 2)), bottom_right!(area)),
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
write_string_to_grid(
|
||||||
|
&format!("{} attachments ", attachments_no),
|
||||||
|
grid,
|
||||||
|
Color::Default,
|
||||||
|
Color::Default,
|
||||||
|
Attr::Default,
|
||||||
|
(pos_inc(upper_left!(area), (0, 2)), bottom_right!(area)),
|
||||||
|
false,
|
||||||
|
);
|
||||||
for (i, a) in self.draft.attachments().iter().enumerate() {
|
for (i, a) in self.draft.attachments().iter().enumerate() {
|
||||||
if let Some(name) = a.content_type().name() {
|
if let Some(name) = a.content_type().name() {
|
||||||
write_string_to_grid(
|
write_string_to_grid(
|
||||||
|
@ -253,7 +285,7 @@ impl Composer {
|
||||||
Color::Default,
|
Color::Default,
|
||||||
Color::Default,
|
Color::Default,
|
||||||
Attr::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,
|
false,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
|
@ -263,7 +295,7 @@ impl Composer {
|
||||||
Color::Default,
|
Color::Default,
|
||||||
Color::Default,
|
Color::Default,
|
||||||
Attr::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,
|
false,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -291,6 +323,9 @@ impl Component for Composer {
|
||||||
};
|
};
|
||||||
|
|
||||||
if !self.initialized {
|
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()
|
if !self.draft.headers().contains_key("From") || self.draft.headers()["From"].is_empty()
|
||||||
{
|
{
|
||||||
self.draft.headers_mut().insert(
|
self.draft.headers_mut().insert(
|
||||||
|
@ -632,7 +667,12 @@ impl Component for Composer {
|
||||||
}
|
}
|
||||||
UIEvent::Input(Key::Char('s')) => {
|
UIEvent::Input(Key::Char('s')) => {
|
||||||
self.update_draft();
|
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
|
context
|
||||||
.replies
|
.replies
|
||||||
.push_back(UIEvent::Action(Tab(Kill(self.id))));
|
.push_back(UIEvent::Action(Tab(Kill(self.id))));
|
||||||
|
@ -743,6 +783,12 @@ impl Component for Composer {
|
||||||
self.dirty = true;
|
self.dirty = true;
|
||||||
return 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::io::Write;
|
||||||
use std::process::{Command, Stdio};
|
use std::process::{Command, Stdio};
|
||||||
let mut failure = true;
|
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");
|
.expect("Failed to start mailer command");
|
||||||
{
|
{
|
||||||
let stdin = msmtp.stdin.as_mut().expect("failed to open stdin");
|
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();
|
let draft = draft.finalise().unwrap();
|
||||||
stdin
|
stdin
|
||||||
.write_all(draft.as_bytes())
|
.write_all(draft.as_bytes())
|
||||||
|
|
|
@ -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())
|
||||||
|
}
|
|
@ -196,58 +196,7 @@ impl MailView {
|
||||||
} else if a.is_signed() {
|
} else if a.is_signed() {
|
||||||
v.clear();
|
v.clear();
|
||||||
if context.settings.pgp.auto_verify_signatures {
|
if context.settings.pgp.auto_verify_signatures {
|
||||||
match melib::signatures::verify_signature(a) {
|
v.extend(crate::mail::pgp::verify_signature(a, context).into_iter());
|
||||||
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),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})),
|
})),
|
||||||
|
@ -1041,6 +990,7 @@ impl Component for MailView {
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
if super::compose::send_draft(
|
if super::compose::send_draft(
|
||||||
|
ToggleFlag::False,
|
||||||
/* FIXME: refactor to avoid unsafe.
|
/* FIXME: refactor to avoid unsafe.
|
||||||
*
|
*
|
||||||
* actions contains byte slices from the envelope's
|
* actions contains byte slices from the envelope's
|
||||||
|
|
|
@ -58,7 +58,7 @@ macro_rules! split_command {
|
||||||
}};
|
}};
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Copy, Debug, Clone, PartialEq)]
|
||||||
pub enum ToggleFlag {
|
pub enum ToggleFlag {
|
||||||
Unset,
|
Unset,
|
||||||
InternalVal(bool),
|
InternalVal(bool),
|
||||||
|
@ -66,6 +66,16 @@ pub enum ToggleFlag {
|
||||||
True,
|
True,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<bool> for ToggleFlag {
|
||||||
|
fn from(val: bool) -> Self {
|
||||||
|
if val {
|
||||||
|
ToggleFlag::True
|
||||||
|
} else {
|
||||||
|
ToggleFlag::False
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Default for ToggleFlag {
|
impl Default for ToggleFlag {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
ToggleFlag::Unset
|
ToggleFlag::Unset
|
||||||
|
|
|
@ -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 "],
|
{ tags: ["create-folder "],
|
||||||
desc: "create-folder ACCOUNT FOLDER_PATH",
|
desc: "create-folder ACCOUNT FOLDER_PATH",
|
||||||
parser:(
|
parser:(
|
||||||
|
@ -350,7 +361,7 @@ named!(
|
||||||
|
|
||||||
named!(
|
named!(
|
||||||
compose_action<Action>,
|
compose_action<Action>,
|
||||||
alt_complete!(add_attachment | remove_attachment)
|
alt_complete!(add_attachment | remove_attachment | toggle_sign)
|
||||||
);
|
);
|
||||||
|
|
||||||
named!(pub parse_command<Action>,
|
named!(pub parse_command<Action>,
|
||||||
|
|
|
@ -71,6 +71,7 @@ pub enum PagerAction {
|
||||||
pub enum ComposeAction {
|
pub enum ComposeAction {
|
||||||
AddAttachment(String),
|
AddAttachment(String),
|
||||||
RemoveAttachment(usize),
|
RemoveAttachment(usize),
|
||||||
|
ToggleSign,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
|
Loading…
Reference in New Issue