Steps towards encryption
- Added certstore and retrieval of the local trust root. - Major work on keeping the context between calls to the milter. - Added a context for an email in processing - Added Husk specific errors
This commit is contained in:
@@ -16,7 +16,11 @@ bytes = "1.10.1"
|
||||
indymilter = "0.3.0"
|
||||
log = "0.4.27"
|
||||
log4rs = "1.3.0"
|
||||
sequoia-cert-store = "0.7.1"
|
||||
sequoia-directories = "0.1.0"
|
||||
sequoia-openpgp = "2.1.0"
|
||||
serde = "1.0.228"
|
||||
serde_derive = "1.0.228"
|
||||
thiserror = "2.0.17"
|
||||
tokio = { version = "1.47.1", features = [ "tokio-macros", "rt-multi-thread", "signal" ] }
|
||||
toml = "0.9.8"
|
||||
|
||||
@@ -3,4 +3,6 @@
|
||||
#
|
||||
connection = "localhost:3000"
|
||||
|
||||
sequoia_home = "/tmp/sq_home"
|
||||
|
||||
logfile_config = "./config/log4rs.yml"
|
||||
|
||||
+13
-1
@@ -3,11 +3,13 @@
|
||||
//
|
||||
|
||||
use std::fs;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use serde_derive::Deserialize;
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
#[derive(Deserialize, Debug, Clone)]
|
||||
pub struct HuskConfig {
|
||||
pub connection: String,
|
||||
pub sequoia_home: String,
|
||||
logfile_config: Option<String>,
|
||||
}
|
||||
|
||||
@@ -41,3 +43,13 @@ impl HuskConfig {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub type HuskConfigContainer = Arc<Mutex<HuskConfig>>;
|
||||
|
||||
impl From<HuskConfigContainer> for HuskConfig {
|
||||
fn from(container: HuskConfigContainer) -> Self {
|
||||
let config = container.lock().unwrap();
|
||||
config.clone()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
//
|
||||
// Husk milter
|
||||
//
|
||||
// cryptographic functions
|
||||
//
|
||||
|
||||
use anyhow;
|
||||
use sequoia_openpgp::parse::Parse;
|
||||
use sequoia_openpgp::{Fingerprint, Cert};
|
||||
use sequoia_openpgp::cert::raw::RawCertParser;
|
||||
use sequoia_cert_store::CertStore;
|
||||
use crate::types::errors::HuskError;
|
||||
|
||||
pub fn get_local_trust_root(cert_store: &CertStore) -> anyhow::Result<Cert> {
|
||||
|
||||
let root = cert_store.certd()
|
||||
.and_then(|certd| {
|
||||
match certd.certd().get(sequoia_cert_store::store::openpgp_cert_d::TRUST_ROOT) {
|
||||
Ok(Some((_tag, bytes))) => Some(bytes),
|
||||
Ok(None) => None,
|
||||
Err(_) => None
|
||||
}
|
||||
})
|
||||
.and_then(|bytes| {
|
||||
match RawCertParser::from_bytes(&bytes[..]) {
|
||||
Ok(mut parser) => {
|
||||
match parser.next() {
|
||||
Some(Ok(cert)) => {
|
||||
match Cert::from_bytes(cert.as_bytes()) {
|
||||
Ok(c) => Some(c),
|
||||
Err(_) => None,
|
||||
}
|
||||
},
|
||||
Some(Err(_))
|
||||
| None => None
|
||||
}
|
||||
},
|
||||
Err(_) => None,
|
||||
}
|
||||
});
|
||||
|
||||
match root {
|
||||
Some(r) => Ok(r),
|
||||
None => Err(HuskError::NoLocalTrustRoot.into())
|
||||
}
|
||||
|
||||
}
|
||||
+42
-24
@@ -25,13 +25,15 @@ use indymilter::{
|
||||
};
|
||||
|
||||
use crate::types::husk_context::HuskContext;
|
||||
use crate::config::HuskConfig;
|
||||
use crate::config::{HuskConfig, HuskConfigContainer};
|
||||
|
||||
pub struct Daemon { }
|
||||
|
||||
impl Daemon {
|
||||
|
||||
pub async fn run(config: &HuskConfig) -> anyhow::Result<()> {
|
||||
pub async fn run(config_container: HuskConfigContainer) -> anyhow::Result<()> {
|
||||
|
||||
let config: HuskConfig = config_container.clone().into();
|
||||
|
||||
let listener = TcpListener::bind(&config.connection)
|
||||
.await
|
||||
@@ -40,7 +42,7 @@ impl Daemon {
|
||||
let callbacks = Callbacks::new()
|
||||
.on_negotiate(|cx, actions, opts| Box::pin(Self::handle_negotiate(cx, actions, opts)))
|
||||
.on_connect(|cx, hostname, socket_info| Box::pin(Self::handle_connect(cx, hostname, socket_info)))
|
||||
.on_helo(|cx, hostname| Box::pin(Self::handle_helo(cx, hostname)))
|
||||
.on_helo(move |cx, hostname| Box::pin(Self::handle_helo(cx, config_container.clone(), hostname)))
|
||||
.on_mail(|cx, args| Box::pin(Self::handle_mail(cx, args)))
|
||||
.on_rcpt(|cx, args| Box::pin(Self::handle_rcpt(cx, args)))
|
||||
.on_data(|cx| Box::pin(Self::handle_data(cx)))
|
||||
@@ -62,7 +64,7 @@ impl Daemon {
|
||||
}
|
||||
|
||||
async fn handle_negotiate(
|
||||
_cx: &mut NegotiateContext<HuskContext>,
|
||||
_cx: &mut NegotiateContext<HuskContext<'_>>,
|
||||
actions: Actions,
|
||||
opts: ProtoOpts,
|
||||
) -> Status {
|
||||
@@ -72,7 +74,7 @@ impl Daemon {
|
||||
}
|
||||
|
||||
async fn handle_connect(
|
||||
_cx: &mut Context<HuskContext>,
|
||||
_cx: &mut Context<HuskContext<'_>>,
|
||||
hostname: CString,
|
||||
socket_info: SocketInfo,
|
||||
) -> Status {
|
||||
@@ -82,15 +84,28 @@ impl Daemon {
|
||||
Status::Continue
|
||||
}
|
||||
|
||||
async fn handle_helo(_cx: &mut Context<HuskContext>, hostname: CString) -> Status {
|
||||
async fn handle_helo(cx: &mut Context<HuskContext<'_>>, config_container: HuskConfigContainer, hostname: CString) -> Status {
|
||||
log::debug!("HELO: hostname: {hostname:?}");
|
||||
|
||||
Status::Continue
|
||||
let config: HuskConfig = config_container.into();
|
||||
|
||||
// setup Context
|
||||
match HuskContext::new(&config) {
|
||||
Ok(context) => {
|
||||
cx.data = Some(context).take();
|
||||
Status::Continue
|
||||
},
|
||||
Err(e) => {
|
||||
log::error!("{}", e);
|
||||
Status::Tempfail
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn handle_mail(cx: &mut Context<HuskContext>, args: Vec<CString>) -> Status {
|
||||
async fn handle_mail(cx: &mut Context<HuskContext<'_>>, args: Vec<CString>) -> Status {
|
||||
log::debug!("MAIL: {args:?}");
|
||||
|
||||
// XXX
|
||||
let sender = match args.first() {
|
||||
Some(cs) => {
|
||||
match cs.to_str() {
|
||||
@@ -101,68 +116,71 @@ impl Daemon {
|
||||
None => { "missing".to_string() }
|
||||
};
|
||||
|
||||
// setup Context
|
||||
if let Some(mut context) = HuskContext::new() {
|
||||
if let Some(ref mut context) = cx.data {
|
||||
context.set_sender(sender);
|
||||
cx.data = Some(context).take();
|
||||
Status::Continue
|
||||
} else {
|
||||
Status::Tempfail
|
||||
}
|
||||
|
||||
Status::Continue
|
||||
}
|
||||
|
||||
async fn handle_rcpt(_cx: &mut Context<HuskContext>, args: Vec<CString>) -> Status {
|
||||
async fn handle_rcpt(cx: &mut Context<HuskContext<'_>>, args: Vec<CString>) -> Status {
|
||||
log::debug!("RCPT: {args:?}");
|
||||
|
||||
if let Some(ref mut context) = cx.data {
|
||||
|
||||
}
|
||||
|
||||
Status::Continue
|
||||
}
|
||||
|
||||
async fn handle_data(_cx: &mut Context<HuskContext>) -> Status {
|
||||
async fn handle_data(_cx: &mut Context<HuskContext<'_>>) -> Status {
|
||||
log::debug!("DATA");
|
||||
|
||||
Status::Continue
|
||||
}
|
||||
|
||||
async fn handle_header(_cx: &mut Context<HuskContext>, name: CString, value: CString) -> Status {
|
||||
async fn handle_header(_cx: &mut Context<HuskContext<'_>>, name: CString, value: CString) -> Status {
|
||||
log::debug!("HEADER: {name:?} = {value:?}");
|
||||
|
||||
Status::Continue
|
||||
}
|
||||
|
||||
async fn handle_eoh(_cx: &mut Context<HuskContext>) -> Status {
|
||||
async fn handle_eoh(_cx: &mut Context<HuskContext<'_>>) -> Status {
|
||||
log::debug!("EOH");
|
||||
|
||||
Status::Continue
|
||||
}
|
||||
|
||||
async fn handle_body(_cx: &mut Context<HuskContext>, chunk: Bytes) -> Status {
|
||||
async fn handle_body(_cx: &mut Context<HuskContext<'_>>, chunk: Bytes) -> Status {
|
||||
log::debug!("BODY: chunk with {:?} bytes received", &chunk.len());
|
||||
|
||||
Status::Continue
|
||||
}
|
||||
|
||||
async fn handle_eom(cx: &mut EomContext<HuskContext>) -> Status {
|
||||
async fn handle_eom(cx: &mut EomContext<HuskContext<'_>>) -> Status {
|
||||
log::debug!("EOM");
|
||||
|
||||
if let Some(ref mut context_data) = cx.data {
|
||||
log::debug!("Mail from {:?} complete", context_data.sender);
|
||||
if let Some(ref mut context) = cx.data {
|
||||
log::debug!("Mail from {:?} complete", context.mail.sender);
|
||||
}
|
||||
|
||||
Status::Continue
|
||||
}
|
||||
|
||||
async fn handle_abort(_cx: &mut Context<HuskContext>) -> Status {
|
||||
async fn handle_abort(_cx: &mut Context<HuskContext<'_>>) -> Status {
|
||||
log::debug!("ABORT");
|
||||
|
||||
Status::Continue
|
||||
}
|
||||
|
||||
async fn handle_close(_cx: &mut Context<HuskContext>) -> Status {
|
||||
async fn handle_close(_cx: &mut Context<HuskContext<'_>>) -> Status {
|
||||
log::debug!("CLOSE");
|
||||
|
||||
Status::Continue
|
||||
}
|
||||
|
||||
async fn handle_unknown(_cx: &mut Context<HuskContext>, arg: CString) -> Status {
|
||||
async fn handle_unknown(_cx: &mut Context<HuskContext<'_>>, arg: CString) -> Status {
|
||||
log::debug!("UNKNOWN: {arg:?}");
|
||||
|
||||
Status::Continue
|
||||
|
||||
+7
-7
@@ -2,15 +2,13 @@
|
||||
// Husk milter
|
||||
//
|
||||
|
||||
use std::{
|
||||
env,
|
||||
process,
|
||||
};
|
||||
use std::{env, process};
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
pub mod config;
|
||||
use config::HuskConfig;
|
||||
use config::{HuskConfig, HuskConfigContainer};
|
||||
pub mod types;
|
||||
|
||||
pub mod crypto;
|
||||
pub mod daemon;
|
||||
use daemon::Daemon;
|
||||
|
||||
@@ -30,7 +28,9 @@ async fn main() {
|
||||
|
||||
log::info!("starting...");
|
||||
|
||||
match Daemon::run(&husk_config).await {
|
||||
let config_container: HuskConfigContainer = Arc::new(Mutex::new(husk_config));
|
||||
|
||||
match Daemon::run(config_container).await {
|
||||
Ok(_) => {
|
||||
println!("exiting...");
|
||||
},
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
//
|
||||
// Husk milter
|
||||
//
|
||||
// Error typ
|
||||
//
|
||||
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum HuskError {
|
||||
#[error("Cannot find local trust root")]
|
||||
NoLocalTrustRoot,
|
||||
#[error("Cannot access cert store")]
|
||||
NoCertStore
|
||||
}
|
||||
@@ -4,21 +4,41 @@
|
||||
// Context
|
||||
//
|
||||
|
||||
pub struct HuskContext {
|
||||
pub sender: Option<String>,
|
||||
use anyhow;
|
||||
use std::path::PathBuf;
|
||||
use sequoia_openpgp::Cert;
|
||||
use sequoia_cert_store::CertStore;
|
||||
use sequoia_directories::Home;
|
||||
|
||||
use crate::{config::HuskConfig, crypto};
|
||||
use crate::types::mail_context::MailContext;
|
||||
|
||||
pub struct HuskContext<'hc> {
|
||||
pub cert_store: CertStore<'hc>,
|
||||
pub local_trust_root: Cert,
|
||||
pub mail: MailContext<'hc>,
|
||||
}
|
||||
|
||||
impl HuskContext {
|
||||
|
||||
impl<'hc> HuskContext<'hc> {
|
||||
|
||||
/// Create a new HuskContext instance
|
||||
pub fn new() -> Option<HuskContext> {
|
||||
Some(HuskContext {
|
||||
sender: None
|
||||
pub fn new(config: &HuskConfig) -> anyhow::Result<HuskContext<'hc>> {
|
||||
|
||||
let sequoia_home = Home::new(PathBuf::from(&config.sequoia_home))?;
|
||||
let cert_store_base = sequoia_home.data_dir(sequoia_directories::Component::CertD);
|
||||
let cert_store = CertStore::open(cert_store_base)?;
|
||||
|
||||
let local_trust_root = crypto::get_local_trust_root(&cert_store)?;
|
||||
|
||||
Ok(HuskContext {
|
||||
cert_store,
|
||||
local_trust_root,
|
||||
mail: MailContext::new(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn set_sender(&mut self, sender: String) {
|
||||
self.sender = Some(sender);
|
||||
self.mail.set_sender(sender);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
//
|
||||
// Husk milter
|
||||
//
|
||||
// Mail Context
|
||||
//
|
||||
|
||||
use std::ffi::CString;
|
||||
|
||||
use sequoia_openpgp::cert::ValidCert;
|
||||
|
||||
pub struct Recipient<'r> {
|
||||
pub email: CString,
|
||||
pub cert: Option<ValidCert<'r>>,
|
||||
}
|
||||
|
||||
pub struct MailContext<'c> {
|
||||
pub sender: Option<String>,
|
||||
pub recipients: Vec<Recipient<'c>>,
|
||||
// header
|
||||
// body
|
||||
// body size
|
||||
}
|
||||
|
||||
impl MailContext<'_> {
|
||||
|
||||
pub fn new() -> Self {
|
||||
MailContext {
|
||||
sender: None,
|
||||
recipients: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_sender(&mut self, sender: String) {
|
||||
self.sender = Some(sender);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1 +1,3 @@
|
||||
pub mod husk_context;
|
||||
pub mod mail_context;
|
||||
pub mod errors;
|
||||
|
||||
Reference in New Issue
Block a user