From e1239fea58b55a0915ac5f6b6fdffb53fe8174c9 Mon Sep 17 00:00:00 2001 From: Nathan Fisher Date: Thu, 22 Jun 2023 19:04:51 -0400 Subject: [PATCH] Partially implement `ClientCertificateStore` for `Filesystem` --- Cargo.toml | 1 + src/mailstore/filesystem.rs | 125 +++++++++++++++++++++++++++++++----- 2 files changed, 111 insertions(+), 15 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index c91c583..ccb1196 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,6 +5,7 @@ edition = "2021" [dependencies] digest = "0.10" +rustls-pemfile = "1.0" sha2 = "0.10" time = "0.3" x509-parser = "0.15" diff --git a/src/mailstore/filesystem.rs b/src/mailstore/filesystem.rs index 6638dad..f671572 100644 --- a/src/mailstore/filesystem.rs +++ b/src/mailstore/filesystem.rs @@ -1,10 +1,19 @@ +use std::io::BufReader; + +use crate::prelude::Certificate; + use { super::*, - crate::{message::Parser as MessageParser, prelude::ParseMailboxError}, + crate::{ + message::Parser as MessageParser, + prelude::{ClientCertificateStore, ParseMailboxError}, + }, + rustls_pemfile::{read_one, Item}, std::{ ffi::OsString, fs::{self, File}, io::{self, BufWriter, Write}, + iter, os::unix::fs::DirBuilderExt, path::{Path, PathBuf}, }, @@ -141,11 +150,8 @@ impl MailStore for Filesystem { } fn get_message(&self, user: &str, folder: &str, id: &str) -> Option { - self.get_folder(user, folder).and_then(|f| { - f.messages - .get(id) - .cloned() - }) + self.get_folder(user, folder) + .and_then(|f| f.messages.get(id).cloned()) } fn add_message( @@ -288,6 +294,77 @@ impl MultiDomain for Filesystem { } } +impl ClientCertificateStore for Filesystem { + fn get_certificate(&self, user: &Mailuser) -> Option { + let mut path = self.path.clone(); + path.push(&user.host.to_string()); + path.push(&user.username); + path.push("certificate.pem"); + if path.exists() { + let Ok(fd) = File::open(&path) else { + return None; + }; + let mut reader = BufReader::new(fd); + let mut certificate = Certificate { + der: vec![], + key: vec![], + }; + for item in iter::from_fn(|| read_one(&mut reader).transpose()) { + match item { + Ok(Item::X509Certificate(cert)) => { + if certificate.der.is_empty() { + certificate.der = cert; + } else { + return None; + } + } + Ok(Item::RSAKey(key)) => { + if certificate.key.is_empty() { + certificate.key = key; + } else { + return None; + } + } + Ok(Item::PKCS8Key(key)) => { + if certificate.key.is_empty() { + certificate.key = key; + } else { + return None; + } + } + Ok(Item::ECKey(key)) => { + if certificate.key.is_empty() { + certificate.key = key; + } else { + return None; + } + } + _ => {} + } + } + if certificate.der.is_empty() || certificate.key.is_empty() { + None + } else { + Some(certificate) + } + } else { + None + } + } + + fn insert_certificate( + &mut self, + user: &Mailuser, + certificate: Certificate, + ) -> Option { + todo!() + } + + fn contains_certificate(&self, user: &Mailuser) -> bool { + self.get_certificate(user).is_some() + } +} + #[cfg(test)] mod tests { use super::*; @@ -299,10 +376,18 @@ mod tests { #[test] fn users() { let users = store().users(); - assert!(users.iter().any(|u| u.host.to_string() == "mail.gmi.org" && u.username == "jane" )); - assert!(users.iter().any(|u| u.host.to_string() == "mail.gmi.org" && u.username == "dick" )); - assert!(users.iter().any(|u| u.host.to_string() == "misfin.example.org" && u.username == "jane" )); - assert!(users.iter().any(|u| u.host.to_string() == "misfin.example.org" && u.username == "dick" )); + assert!(users + .iter() + .any(|u| u.host.to_string() == "mail.gmi.org" && u.username == "jane")); + assert!(users + .iter() + .any(|u| u.host.to_string() == "mail.gmi.org" && u.username == "dick")); + assert!(users + .iter() + .any(|u| u.host.to_string() == "misfin.example.org" && u.username == "jane")); + assert!(users + .iter() + .any(|u| u.host.to_string() == "misfin.example.org" && u.username == "dick")); assert!(users.len() >= 4 && users.len() < 6); } @@ -336,7 +421,9 @@ mod tests { #[test] fn get_message() { - let msg = store().get_message("jane@mail.gmi.org", "Inbox", "867017").unwrap(); + let msg = store() + .get_message("jane@mail.gmi.org", "Inbox", "867017") + .unwrap(); assert_eq!(msg.from.username, "april"); assert_eq!(msg.from.host.subdomain.unwrap(), "we.wear"); } @@ -352,18 +439,26 @@ mod tests { title: Some(String::from("Get a look at 'is 'orse")), body: String::from("Gorgeous, innit?"), }; - store().add_message("dick@mail.gmi.org", "Inbox", msg).unwrap(); + store() + .add_message("dick@mail.gmi.org", "Inbox", msg) + .unwrap(); assert!(PathBuf::from("test/mailstore/mail.gmi.org/dick/Inbox/1687407844.gmi").exists()); - store().move_message("dick@mail.gmi.org", "1687407844", "Inbox", "Lists").unwrap(); + store() + .move_message("dick@mail.gmi.org", "1687407844", "Inbox", "Lists") + .unwrap(); assert!(!PathBuf::from("test/mailstore/mail.gmi.org/dick/Inbox/1687407844.gmi").exists()); assert!(PathBuf::from("test/mailstore/mail.gmi.org/dick/Lists/1687407844.gmi").exists()); - store().delete_message("dick@mail.gmi.org", "Lists", "1687407844").unwrap(); + store() + .delete_message("dick@mail.gmi.org", "Lists", "1687407844") + .unwrap(); assert!(!PathBuf::from("test/mailstore/mail.gmi.org/dick/Lists/1687407844.gmi").exists()); } #[test] fn add_remove_user() { - store().add_user("rob@misfin.example.org Rob Zombie").unwrap(); + store() + .add_user("rob@misfin.example.org Rob Zombie") + .unwrap(); assert!(store().has_mailuser("rob@misfin.example.org")); assert!(store().remove_user("rob@misfin.example.org")); assert!(!store().has_mailuser("rob@misfin.example.org"));