diff --git a/src/crypto.rs b/src/crypto.rs index 5a6f2a6..2f13194 100644 --- a/src/crypto.rs +++ b/src/crypto.rs @@ -7,9 +7,10 @@ use std::sync::Arc; use anyhow; -use sequoia_openpgp::parse::Parse; +use bytes::Bytes; +use sequoia_openpgp::parse::{PacketParser, PacketParserResult, Parse}; use sequoia_openpgp::policy::StandardPolicy; -use sequoia_openpgp::{Fingerprint, Cert}; +use sequoia_openpgp::{Fingerprint, Cert, Packet}; use sequoia_openpgp::cert::raw::RawCertParser; use sequoia_cert_store::{Store, CertStore, LazyCert}; use sequoia_wot::{self as wot}; @@ -55,7 +56,7 @@ pub fn get_local_trust_root(cert_store: &CertStore) -> anyhow::Result { } -pub fn get_certificates<'hc>(context: &HuskContext<'hc>, email: &str) +pub fn get_certificates<'hc>(context: &HuskContext<'hc>, email: &str) -> Vec>> { let cert_store = &context.cert_store; @@ -125,3 +126,31 @@ pub fn get_introducers<'c>(cert_store: &CertStore<'c>, policy: &StandardPolicy, result } + +pub fn is_encrypted(body: &Bytes) -> bool { + + let mut ppr = match PacketParser::from_bytes(body) { + Ok(pp) => pp, + Err(_) => return false, // no OpenPGP artifacts + }; + + let mut encrypted: bool = false; + + while let PacketParserResult::Some(pp) = ppr { + if let Packet::SEIP(_) = pp.packet { + encrypted = true; + } + + // parse the whole message, don't stop at first SEIP + ppr = match pp.recurse() { + Ok((_, next_ppr)) => next_ppr, + Err(_) => return false, // broken artifact -> not encrypted + } + } + if let PacketParserResult::EOF(eof) = ppr { + if eof.is_message().is_ok() && encrypted { + return true; + } + } + false +} diff --git a/src/daemon/mod.rs b/src/daemon/mod.rs index 90ac1a9..47518b5 100644 --- a/src/daemon/mod.rs +++ b/src/daemon/mod.rs @@ -17,16 +17,18 @@ use indymilter::{ EomContext, Macros, NegotiateContext, - ProtoOpts, + ProtoOpts, SocketInfo, - Status, - Config, + Status, + Config, ContextActions, }; use crate::types::husk_context::HuskContext; use crate::config::{HuskConfig, HuskConfigContainer}; use crate::mail; +use crate::crypto; +use crate::types::mail_context::ProtectionPossibility; pub struct Daemon { } @@ -101,7 +103,7 @@ impl Daemon { log::error!("{}", e); Status::Tempfail } - } + } } async fn handle_mail(cx: &mut Context>, args: Vec) -> Status { @@ -110,7 +112,7 @@ impl Daemon { let mut sender: Option = None; for arg in args { - if let Some(s) = mail::to_email(&arg) { + if let Some(s) = mail::to_email(&arg) { sender = Some(s); break; } @@ -133,18 +135,18 @@ impl Daemon { let mut rcpt: Option = None; for arg in args { - if let Some(s) = mail::to_email(&arg) { + if let Some(s) = mail::to_email(&arg) { rcpt = Some(s); break; } } - if rcpt.is_none() { - Status::Tempfail + if rcpt.is_none() { + Status::Tempfail } else { context.add_recipient(rcpt.unwrap()).unwrap(); Status::Continue } - + } else { Status::Tempfail } @@ -153,7 +155,7 @@ impl Daemon { async fn handle_data(cx: &mut Context>) -> Status { log::debug!("DATA"); - if let Some(ref mut context) = cx.data { + if let Some(ref mut _context) = cx.data { // XXX: check for early accepting // if there are no recipients with authenticated certificates // tell the MTA that this milter is done and the mail can be further @@ -176,10 +178,15 @@ impl Daemon { Status::Continue } - async fn handle_body(_cx: &mut Context>, chunk: Bytes) -> Status { + async fn handle_body(cx: &mut Context>, chunk: Bytes) -> Status { log::debug!("BODY: chunk with {:?} bytes received", &chunk.len()); - Status::Continue + if let Some(ref mut context) = cx.data { + context.mail.add_chunk(chunk); + Status::Continue + } else { + Status::Tempfail + } } async fn handle_eom(cx: &mut EomContext>) -> Status { @@ -188,6 +195,30 @@ impl Daemon { if let Some(ref mut context) = cx.data { log::debug!("Mail from {:?} complete", context.mail.sender); log::debug!("recipients: {:?}", context.mail.recipients); + log::debug!("body size: {}", context.mail.body_size); + + let body = context.mail.collect_body(); + + // check if mail is already encrypted. If it is, keep it as such. + if crypto::is_encrypted(&body) { + log::info!("encrypted mail detected"); + return Status::Accept; + } + + + // possible protection + // none -> accept + // partial -> accept (later: split) + // full -> encrypt + // check mailbody + // is encrypted -> accept + // else -> encrypt + + let protection = ProtectionPossibility::from(&context.mail); + + if protection == ProtectionPossibility::Full { + // encrypt + } } Status::Continue diff --git a/src/types/mail_context.rs b/src/types/mail_context.rs index 877f55a..08c9d2c 100644 --- a/src/types/mail_context.rs +++ b/src/types/mail_context.rs @@ -5,16 +5,25 @@ // use std::sync::Arc; +use bytes::{Bytes, BytesMut}; use sequoia_cert_store::LazyCert; use crate::types::recipient::Recipient; +#[derive(PartialEq)] +pub enum ProtectionPossibility { + NoProtection, + Partial, + Full +} + + pub struct MailContext<'mc> { pub sender: Option, pub recipients: Vec>, // header - // body - // body size + body: Vec, + pub body_size: usize, } impl<'mc> MailContext<'mc> { @@ -23,6 +32,8 @@ impl<'mc> MailContext<'mc> { MailContext { sender: None, recipients: Vec::new(), + body: Vec::new(), + body_size: 0, } } @@ -39,4 +50,39 @@ impl<'mc> MailContext<'mc> { Ok(()) } + pub fn add_chunk(&mut self, chunk: Bytes) { + self.body_size += chunk.len(); + self.body.push(chunk); + } + + pub fn collect_body(&self) -> Bytes { + let mut buffer = BytesMut::with_capacity(self.body_size); + for chunk in &self.body { + buffer.extend_from_slice(&chunk[..]); + } + buffer.into() + } + +} + +impl From<&MailContext<'_>> for ProtectionPossibility { + fn from(mail_context: &MailContext<'_>) -> ProtectionPossibility { + let mut can_encrypt = false; + let mut cannot_encrypt = false; + + for rcpt in mail_context.recipients.iter() { + if rcpt.can_encrypt() { + can_encrypt = true; + } else { + cannot_encrypt = true; + } + } + + match (can_encrypt, cannot_encrypt) { + (true, false) => ProtectionPossibility::Full, + (true, true) => ProtectionPossibility::Partial, + (false, true) => ProtectionPossibility::NoProtection, + (false, false) => ProtectionPossibility::NoProtection, // this should not occure + } + } } diff --git a/src/types/recipient.rs b/src/types/recipient.rs index 0d93a03..0c806b8 100644 --- a/src/types/recipient.rs +++ b/src/types/recipient.rs @@ -36,4 +36,13 @@ impl TryFrom for Recipient<'_> { } } +impl Recipient<'_> { + pub fn can_encrypt(&self) -> bool { + if self.certs.len() == 0 { + return false; + } + true + } +} +