Initial commit
- rough filestructure - first simplistic daemon
This commit is contained in:
@@ -0,0 +1 @@
|
||||
/target
|
||||
Generated
+1065
File diff suppressed because it is too large
Load Diff
+21
@@ -0,0 +1,21 @@
|
||||
[package]
|
||||
name = "husk"
|
||||
description="Pass-through solution for automatic OpenPGP encryption"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
repository = "https://gitlab.com/husk-project/husk-milter.git"
|
||||
authors = [ "Malte Meiboom <malte@netropol.de>" ]
|
||||
readme = "README.md"
|
||||
keywords = ["cryptography", "openpgp", "pgp", "encryption", "email"]
|
||||
categories = ["cryptography", "email"]
|
||||
rust-version = "1.79"
|
||||
|
||||
[dependencies]
|
||||
bytes = "1.10.1"
|
||||
indymilter = "0.3.0"
|
||||
log = "0.4.27"
|
||||
log4rs = "1.3.0"
|
||||
serde = "1.0.228"
|
||||
serde_derive = "1.0.228"
|
||||
tokio = { version = "1.47.1", features = [ "tokio-macros", "rt-multi-thread", "signal" ] }
|
||||
toml = "0.9.8"
|
||||
@@ -0,0 +1,5 @@
|
||||
#
|
||||
# Husk config file
|
||||
#
|
||||
|
||||
logfile_config = "./config/log4rs.yml"
|
||||
@@ -0,0 +1,16 @@
|
||||
appenders:
|
||||
stdout_appender:
|
||||
kind: console
|
||||
encoder:
|
||||
pattern: "{h({d(%Y-%m-%d %H:%M:%S)(utc)} - {l}: {m}{n})}"
|
||||
|
||||
file_appender:
|
||||
kind: file
|
||||
path: "/tmp/husk.log"
|
||||
encoder:
|
||||
pattern: "{h({d(%Y-%m-%d %H:%M:%S)(utc)} - {l}: {m}{n})}"
|
||||
|
||||
root:
|
||||
level: debug
|
||||
appenders:
|
||||
- file_appender
|
||||
@@ -0,0 +1,42 @@
|
||||
//
|
||||
// Parsing the config file
|
||||
//
|
||||
|
||||
use std::fs;
|
||||
use serde_derive::Deserialize;
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub struct HuskConfig {
|
||||
logfile_config: Option<String>,
|
||||
}
|
||||
|
||||
impl HuskConfig {
|
||||
|
||||
pub fn load(path: &String) -> Option<Self> {
|
||||
match fs::read_to_string(path) {
|
||||
Ok(data) => {
|
||||
match toml::from_str(data.as_str()) {
|
||||
Ok(configuration) => Some(configuration),
|
||||
Err(e) => {
|
||||
eprintln!("Error while parsing config file: {:?}", e);
|
||||
None
|
||||
}
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
eprintln!("Error while reading config file: {:?}", e);
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn logfile_config(&self) -> String {
|
||||
match &self.logfile_config {
|
||||
Some(c) => c.clone(),
|
||||
None => {
|
||||
eprintln!("Using logging configuration from 'log4rs.yml'");
|
||||
"log4rs.yml".to_string()
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
//
|
||||
// Husk milter
|
||||
//
|
||||
// the actual daemon
|
||||
//
|
||||
|
||||
use bytes::Bytes;
|
||||
use std::ffi::CString;
|
||||
use tokio::signal;
|
||||
use tokio::net::TcpListener;
|
||||
|
||||
use indymilter::{
|
||||
Actions,
|
||||
Callbacks,
|
||||
Context,
|
||||
EomContext,
|
||||
Macros,
|
||||
NegotiateContext,
|
||||
ProtoOpts,
|
||||
SocketInfo,
|
||||
Status,
|
||||
Config,
|
||||
ContextActions,
|
||||
};
|
||||
|
||||
use crate::types::husk_context::HuskContext;
|
||||
|
||||
pub struct Daemon { }
|
||||
|
||||
impl Daemon {
|
||||
|
||||
pub async fn run() -> Result<(), ()> {
|
||||
|
||||
let listener = TcpListener::bind("localhost:3000")
|
||||
.await
|
||||
.expect(" opening milter socket");
|
||||
|
||||
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_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)))
|
||||
.on_header(|cx, name, value| Box::pin(Self::handle_header(cx, name, value)))
|
||||
.on_eoh(|cx| Box::pin(Self::handle_eoh(cx)))
|
||||
.on_body(|cx, chunk| Box::pin(Self::handle_body(cx, chunk)))
|
||||
.on_eom(|cx| Box::pin(Self::handle_eom(cx)))
|
||||
.on_abort(|cx| Box::pin(Self::handle_abort(cx)))
|
||||
.on_close(|cx| Box::pin(Self::handle_close(cx)))
|
||||
.on_unknown(|cx, arg| Box::pin(Self::handle_unknown(cx, arg)));
|
||||
|
||||
let config = Default::default();
|
||||
|
||||
indymilter::run(listener, callbacks, config, signal::ctrl_c())
|
||||
.await
|
||||
.expect("milter execution failed");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn handle_negotiate(
|
||||
_cx: &mut NegotiateContext<HuskContext>,
|
||||
actions: Actions,
|
||||
opts: ProtoOpts,
|
||||
) -> Status {
|
||||
log::debug!("NEGOTIATE: actions: {actions:?} - opts: {opts:?}");
|
||||
|
||||
Status::AllOpts
|
||||
}
|
||||
|
||||
async fn handle_connect(
|
||||
_cx: &mut Context<HuskContext>,
|
||||
hostname: CString,
|
||||
socket_info: SocketInfo,
|
||||
) -> Status {
|
||||
log::debug!("CONNECT: hostname: {hostname:?} - socket_info: {socket_info:?}");
|
||||
//Self::print_macros(&cx.macros);
|
||||
|
||||
Status::Continue
|
||||
}
|
||||
|
||||
async fn handle_helo(_cx: &mut Context<HuskContext>, hostname: CString) -> Status {
|
||||
log::debug!("HELO: hostname: {hostname:?}");
|
||||
|
||||
Status::Continue
|
||||
}
|
||||
|
||||
async fn handle_mail(cx: &mut Context<HuskContext>, args: Vec<CString>) -> Status {
|
||||
log::debug!("MAIL: {args:?}");
|
||||
|
||||
let sender = match args.first() {
|
||||
Some(cs) => {
|
||||
match cs.to_str() {
|
||||
Ok(s) => s.to_string(),
|
||||
_ => "corrupt".to_string()
|
||||
}
|
||||
},
|
||||
None => { "missing".to_string() }
|
||||
};
|
||||
|
||||
// setup Context
|
||||
if let Some(mut context) = HuskContext::new() {
|
||||
context.set_sender(sender);
|
||||
cx.data = Some(context).take();
|
||||
}
|
||||
|
||||
Status::Continue
|
||||
}
|
||||
|
||||
async fn handle_rcpt(_cx: &mut Context<HuskContext>, args: Vec<CString>) -> Status {
|
||||
log::debug!("RCPT: {args:?}");
|
||||
|
||||
Status::Continue
|
||||
}
|
||||
|
||||
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 {
|
||||
log::debug!("HEADER: {name:?} = {value:?}");
|
||||
|
||||
Status::Continue
|
||||
}
|
||||
|
||||
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 {
|
||||
log::debug!("BODY: chunk with {:?} bytes received", &chunk.len());
|
||||
|
||||
Status::Continue
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
Status::Continue
|
||||
}
|
||||
|
||||
async fn handle_abort(_cx: &mut Context<HuskContext>) -> Status {
|
||||
log::debug!("ABORT");
|
||||
|
||||
Status::Continue
|
||||
}
|
||||
|
||||
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 {
|
||||
log::debug!("UNKNOWN: {arg:?}");
|
||||
|
||||
Status::Continue
|
||||
}
|
||||
|
||||
fn print_macros(macros: &Macros) {
|
||||
println!(" macros: {:?}", macros.to_hash_map());
|
||||
}
|
||||
|
||||
}
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
//
|
||||
// Husk milter
|
||||
//
|
||||
|
||||
use std::{
|
||||
env,
|
||||
process,
|
||||
};
|
||||
|
||||
pub mod config;
|
||||
use config::HuskConfig;
|
||||
pub mod types;
|
||||
|
||||
pub mod daemon;
|
||||
use daemon::Daemon;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
|
||||
let config_file = env::args().nth(1).expect("config file missing");
|
||||
let husk_config = HuskConfig::load(&config_file);
|
||||
|
||||
if husk_config.is_none() {
|
||||
eprintln!("Cannot load configuration");
|
||||
process::exit(1);
|
||||
}
|
||||
let husk_config = husk_config.unwrap();
|
||||
|
||||
log4rs::init_file(husk_config.logfile_config(), Default::default()).unwrap();
|
||||
|
||||
log::info!("starting...");
|
||||
|
||||
match Daemon::run().await {
|
||||
Ok(_) => {
|
||||
println!("exiting...");
|
||||
},
|
||||
Err(e) => {
|
||||
println!("exit with {:?}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
//
|
||||
// Husk milter
|
||||
//
|
||||
// Context
|
||||
//
|
||||
|
||||
pub struct HuskContext {
|
||||
pub sender: Option<String>,
|
||||
}
|
||||
|
||||
impl HuskContext {
|
||||
|
||||
/// Create a new HuskContext instance
|
||||
pub fn new() -> Option<HuskContext> {
|
||||
Some(HuskContext {
|
||||
sender: None
|
||||
})
|
||||
}
|
||||
|
||||
pub fn set_sender(&mut self, sender: String) {
|
||||
self.sender = Some(sender);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
pub mod husk_context;
|
||||
Reference in New Issue
Block a user