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"
|
indymilter = "0.3.0"
|
||||||
log = "0.4.27"
|
log = "0.4.27"
|
||||||
log4rs = "1.3.0"
|
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 = "1.0.228"
|
||||||
serde_derive = "1.0.228"
|
serde_derive = "1.0.228"
|
||||||
|
thiserror = "2.0.17"
|
||||||
tokio = { version = "1.47.1", features = [ "tokio-macros", "rt-multi-thread", "signal" ] }
|
tokio = { version = "1.47.1", features = [ "tokio-macros", "rt-multi-thread", "signal" ] }
|
||||||
toml = "0.9.8"
|
toml = "0.9.8"
|
||||||
|
|||||||
@@ -3,4 +3,6 @@
|
|||||||
#
|
#
|
||||||
connection = "localhost:3000"
|
connection = "localhost:3000"
|
||||||
|
|
||||||
|
sequoia_home = "/tmp/sq_home"
|
||||||
|
|
||||||
logfile_config = "./config/log4rs.yml"
|
logfile_config = "./config/log4rs.yml"
|
||||||
|
|||||||
+13
-1
@@ -3,11 +3,13 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
use std::fs;
|
use std::fs;
|
||||||
|
use std::sync::{Arc, Mutex};
|
||||||
use serde_derive::Deserialize;
|
use serde_derive::Deserialize;
|
||||||
|
|
||||||
#[derive(Deserialize, Debug)]
|
#[derive(Deserialize, Debug, Clone)]
|
||||||
pub struct HuskConfig {
|
pub struct HuskConfig {
|
||||||
pub connection: String,
|
pub connection: String,
|
||||||
|
pub sequoia_home: String,
|
||||||
logfile_config: Option<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())
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+45
-27
@@ -25,13 +25,15 @@ use indymilter::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
use crate::types::husk_context::HuskContext;
|
use crate::types::husk_context::HuskContext;
|
||||||
use crate::config::HuskConfig;
|
use crate::config::{HuskConfig, HuskConfigContainer};
|
||||||
|
|
||||||
pub struct Daemon { }
|
pub struct Daemon { }
|
||||||
|
|
||||||
impl 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)
|
let listener = TcpListener::bind(&config.connection)
|
||||||
.await
|
.await
|
||||||
@@ -40,7 +42,7 @@ impl Daemon {
|
|||||||
let callbacks = Callbacks::new()
|
let callbacks = Callbacks::new()
|
||||||
.on_negotiate(|cx, actions, opts| Box::pin(Self::handle_negotiate(cx, actions, opts)))
|
.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_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_mail(|cx, args| Box::pin(Self::handle_mail(cx, args)))
|
||||||
.on_rcpt(|cx, args| Box::pin(Self::handle_rcpt(cx, args)))
|
.on_rcpt(|cx, args| Box::pin(Self::handle_rcpt(cx, args)))
|
||||||
.on_data(|cx| Box::pin(Self::handle_data(cx)))
|
.on_data(|cx| Box::pin(Self::handle_data(cx)))
|
||||||
@@ -62,7 +64,7 @@ impl Daemon {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn handle_negotiate(
|
async fn handle_negotiate(
|
||||||
_cx: &mut NegotiateContext<HuskContext>,
|
_cx: &mut NegotiateContext<HuskContext<'_>>,
|
||||||
actions: Actions,
|
actions: Actions,
|
||||||
opts: ProtoOpts,
|
opts: ProtoOpts,
|
||||||
) -> Status {
|
) -> Status {
|
||||||
@@ -72,7 +74,7 @@ impl Daemon {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn handle_connect(
|
async fn handle_connect(
|
||||||
_cx: &mut Context<HuskContext>,
|
_cx: &mut Context<HuskContext<'_>>,
|
||||||
hostname: CString,
|
hostname: CString,
|
||||||
socket_info: SocketInfo,
|
socket_info: SocketInfo,
|
||||||
) -> Status {
|
) -> Status {
|
||||||
@@ -82,15 +84,28 @@ impl Daemon {
|
|||||||
Status::Continue
|
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:?}");
|
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:?}");
|
log::debug!("MAIL: {args:?}");
|
||||||
|
|
||||||
|
// XXX
|
||||||
let sender = match args.first() {
|
let sender = match args.first() {
|
||||||
Some(cs) => {
|
Some(cs) => {
|
||||||
match cs.to_str() {
|
match cs.to_str() {
|
||||||
@@ -101,68 +116,71 @@ impl Daemon {
|
|||||||
None => { "missing".to_string() }
|
None => { "missing".to_string() }
|
||||||
};
|
};
|
||||||
|
|
||||||
// setup Context
|
if let Some(ref mut context) = cx.data {
|
||||||
if let Some(mut context) = HuskContext::new() {
|
|
||||||
context.set_sender(sender);
|
context.set_sender(sender);
|
||||||
cx.data = Some(context).take();
|
Status::Continue
|
||||||
|
} else {
|
||||||
|
Status::Tempfail
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
Status::Continue
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn handle_rcpt(_cx: &mut Context<HuskContext>, args: Vec<CString>) -> Status {
|
async fn handle_data(_cx: &mut Context<HuskContext<'_>>) -> Status {
|
||||||
log::debug!("RCPT: {args:?}");
|
|
||||||
|
|
||||||
Status::Continue
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn handle_data(_cx: &mut Context<HuskContext>) -> Status {
|
|
||||||
log::debug!("DATA");
|
log::debug!("DATA");
|
||||||
|
|
||||||
Status::Continue
|
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:?}");
|
log::debug!("HEADER: {name:?} = {value:?}");
|
||||||
|
|
||||||
Status::Continue
|
Status::Continue
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn handle_eoh(_cx: &mut Context<HuskContext>) -> Status {
|
async fn handle_eoh(_cx: &mut Context<HuskContext<'_>>) -> Status {
|
||||||
log::debug!("EOH");
|
log::debug!("EOH");
|
||||||
|
|
||||||
Status::Continue
|
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());
|
log::debug!("BODY: chunk with {:?} bytes received", &chunk.len());
|
||||||
|
|
||||||
Status::Continue
|
Status::Continue
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn handle_eom(cx: &mut EomContext<HuskContext>) -> Status {
|
async fn handle_eom(cx: &mut EomContext<HuskContext<'_>>) -> Status {
|
||||||
log::debug!("EOM");
|
log::debug!("EOM");
|
||||||
|
|
||||||
if let Some(ref mut context_data) = cx.data {
|
if let Some(ref mut context) = cx.data {
|
||||||
log::debug!("Mail from {:?} complete", context_data.sender);
|
log::debug!("Mail from {:?} complete", context.mail.sender);
|
||||||
}
|
}
|
||||||
|
|
||||||
Status::Continue
|
Status::Continue
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn handle_abort(_cx: &mut Context<HuskContext>) -> Status {
|
async fn handle_abort(_cx: &mut Context<HuskContext<'_>>) -> Status {
|
||||||
log::debug!("ABORT");
|
log::debug!("ABORT");
|
||||||
|
|
||||||
Status::Continue
|
Status::Continue
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn handle_close(_cx: &mut Context<HuskContext>) -> Status {
|
async fn handle_close(_cx: &mut Context<HuskContext<'_>>) -> Status {
|
||||||
log::debug!("CLOSE");
|
log::debug!("CLOSE");
|
||||||
|
|
||||||
Status::Continue
|
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:?}");
|
log::debug!("UNKNOWN: {arg:?}");
|
||||||
|
|
||||||
Status::Continue
|
Status::Continue
|
||||||
|
|||||||
+7
-7
@@ -2,15 +2,13 @@
|
|||||||
// Husk milter
|
// Husk milter
|
||||||
//
|
//
|
||||||
|
|
||||||
use std::{
|
use std::{env, process};
|
||||||
env,
|
use std::sync::{Arc, Mutex};
|
||||||
process,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub mod config;
|
pub mod config;
|
||||||
use config::HuskConfig;
|
use config::{HuskConfig, HuskConfigContainer};
|
||||||
pub mod types;
|
pub mod types;
|
||||||
|
pub mod crypto;
|
||||||
pub mod daemon;
|
pub mod daemon;
|
||||||
use daemon::Daemon;
|
use daemon::Daemon;
|
||||||
|
|
||||||
@@ -30,7 +28,9 @@ async fn main() {
|
|||||||
|
|
||||||
log::info!("starting...");
|
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(_) => {
|
Ok(_) => {
|
||||||
println!("exiting...");
|
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
|
// Context
|
||||||
//
|
//
|
||||||
|
|
||||||
pub struct HuskContext {
|
use anyhow;
|
||||||
pub sender: Option<String>,
|
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
|
/// Create a new HuskContext instance
|
||||||
pub fn new() -> Option<HuskContext> {
|
pub fn new(config: &HuskConfig) -> anyhow::Result<HuskContext<'hc>> {
|
||||||
Some(HuskContext {
|
|
||||||
sender: None
|
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) {
|
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 husk_context;
|
||||||
|
pub mod mail_context;
|
||||||
|
pub mod errors;
|
||||||
|
|||||||
Reference in New Issue
Block a user