diff --git a/Cargo.toml b/Cargo.toml index b41579c..0ae7a32 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,6 +9,7 @@ libc = "0.2.146" rustls-pemfile = "1.0" sha2 = "0.10" time = "0.3" +tinyrand = "0.5" x509-parser = "0.15" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/src/connection/error.rs b/src/connection/error.rs index 8680f09..a2c389b 100644 --- a/src/connection/error.rs +++ b/src/connection/error.rs @@ -1,2 +1,69 @@ +use { + crate::prelude::ParseRequestError, + std::{fmt, io, string::FromUtf8Error, time::SystemTimeError}, +}; + #[derive(Debug)] -pub struct Error; +pub enum Error { + Io(io::Error), + Request(ParseRequestError), + Storage(String), + Rustls(rustls::Error), + Time(SystemTimeError), + Utf8, +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Io(e) => write!(f, "{e}"), + Self::Storage(e) => write!(f, "{e}"), + Self::Request(e) => write!(f, "{e}"), + Self::Rustls(e) => write!(f, "{e}"), + Self::Time(e) => write!(f, "{e}"), + Self::Utf8 => write!(f, "Utf8 error"), + } + } +} + +impl std::error::Error for Error { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match self { + Self::Io(e) => Some(e), + Self::Request(e) => Some(e), + Self::Rustls(e) => Some(e), + Self::Time(e) => Some(e), + _ => None, + } + } +} + +impl From for Error { + fn from(value: io::Error) -> Self { + Self::Io(value) + } +} + +impl From for Error { + fn from(value: ParseRequestError) -> Self { + Self::Request(value) + } +} + +impl From for Error { + fn from(value: rustls::Error) -> Self { + Self::Rustls(value) + } +} + +impl From for Error { + fn from(value: SystemTimeError) -> Self { + Self::Time(value) + } +} + +impl From for Error { + fn from(_value: FromUtf8Error) -> Self { + Self::Utf8 + } +} diff --git a/src/connection/mod.rs b/src/connection/mod.rs index 69f2106..c6194e7 100644 --- a/src/connection/mod.rs +++ b/src/connection/mod.rs @@ -3,6 +3,19 @@ pub mod builder; pub mod error; pub mod verifier; +use { + crate::{ + prelude::{ClientCertificateStore, MailStore}, + request::Request, + response::Response, + }, + std::{ + io::Read, + sync::{Arc, Mutex}, + time::{SystemTime, UNIX_EPOCH}, + }, +}; + pub use self::{ builder::Builder, error::Error, @@ -14,4 +27,28 @@ pub struct Connection { pub inner: rustls::ServerConnection, } -impl Connection {} +impl Connection { + pub fn handle_request( + &mut self, + verifier: Verifier, + mailstore: Arc>, + ) -> Result { + let mut buf: Vec = Vec::with_capacity(2048); + self.inner.reader().read_to_end(&mut buf)?; + let raw = String::from_utf8(buf)?; + let request: Request = raw.parse()?; + let res = if let Ok(store) = mailstore.lock() { + if store.has_mailuser(&request.recipient.to_string()) { + let time = SystemTime::now().duration_since(UNIX_EPOCH)?.as_secs(); + //store.add_message(&request.recipient.to_string(), "Inbox", request.message) + // .map_err(|e| Error::Storage(e.to_string()))?; + true + } else { + false + } + } else { + false + }; + todo!() + } +} diff --git a/src/connection/verifier.rs b/src/connection/verifier.rs index 3e40f3e..bfd7833 100644 --- a/src/connection/verifier.rs +++ b/src/connection/verifier.rs @@ -1,5 +1,5 @@ use crate::mailuser::Mailuser; -use rustls::server::ClientCertVerifier; +use rustls::server::{ClientCertVerified, ClientCertVerifier}; use std::sync::Mutex; #[derive(Debug)] @@ -23,7 +23,7 @@ impl ClientCertVerifier for Verifier { end_entity: &rustls::Certificate, intermediates: &[rustls::Certificate], now: std::time::SystemTime, - ) -> Result { + ) -> Result { todo!() } } diff --git a/src/gemtext/parser.rs b/src/gemtext/parser.rs index a80fa40..101da04 100644 --- a/src/gemtext/parser.rs +++ b/src/gemtext/parser.rs @@ -1,4 +1,4 @@ -use crate::prelude::{DateTime, Link, Mailbox, Recipients}; +use crate::prelude::{DateTime, Id, Link, Mailbox, Recipients}; use std::fmt; #[derive(Debug)] @@ -30,6 +30,12 @@ pub enum GemtextNode { Link(Link), } +impl From for GemtextNode { + fn from(value: Id) -> Self { + Self::Timestamp(value.into()) + } +} + impl fmt::Display for GemtextNode { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { diff --git a/src/mailstore/filesystem.rs b/src/mailstore/filesystem.rs index 3e65ab4..69198da 100644 --- a/src/mailstore/filesystem.rs +++ b/src/mailstore/filesystem.rs @@ -264,9 +264,7 @@ impl MailStore for Filesystem { let mut path = self.path.clone(); path.push(&mb.host.to_string()); path.push(&mb.username); - fs::DirBuilder::new() - .recursive(true) - .create(&path)?; + fs::DirBuilder::new().recursive(true).create(&path)?; let p = CString::new(path.to_str().ok_or(Error::Utf8)?.to_string())?; if let Some(pw) = pw::Passwd::getpw()? { let groups = pw.groups()?; diff --git a/src/mailstore/mod.rs b/src/mailstore/mod.rs index 70f0448..aa69ff5 100644 --- a/src/mailstore/mod.rs +++ b/src/mailstore/mod.rs @@ -10,7 +10,7 @@ mod filesystem; pub use filesystem::{Error as FilesystemError, Filesystem, MultiDomain}; pub trait MailStore { - type Error; + type Error: std::error::Error; /// Retreives a list of all valid mailusers on this server fn users(&self) -> Vec; diff --git a/src/message/id.rs b/src/message/id.rs new file mode 100644 index 0000000..8077d46 --- /dev/null +++ b/src/message/id.rs @@ -0,0 +1,62 @@ +use { + std::{ + error::Error, + fmt, + fs::File, + io::{self, Read}, + time::{SystemTime, UNIX_EPOCH}, + }, + tinyrand::{RandRange, Seeded, StdRand}, +}; + +const ALPHABET: [char; 67] = [ + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', + 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', + 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '0', '1', '2', '3', '4', + '5', '6', '7', '8', '9', '@', '%', '&', '=', '+', +]; + +#[derive(Clone, Debug)] +pub struct Id { + pub time: u64, + pub id: [char; 8], +} + +impl fmt::Display for Id { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}.", self.time)?; + self.id.iter().try_for_each(|c| write!(f, "{c}")) + } +} + +impl Id { + pub fn new() -> Result> { + let time = SystemTime::now().duration_since(UNIX_EPOCH)?.as_secs(); + let mut rand = StdRand::seed(random_seed()?); + let mut id = ['0', '0', '0', '0', '0', '0', '0', '0']; + for item in &mut id { + if let Some(c) = ALPHABET.get(rand.next_range(0..68)) { + *item = *c; + } + } + Ok(Self { time, id }) + } +} + +fn u64_from_bytes(b: [u8; 8]) -> u64 { + u64::from(b[0]) + | (u64::from(b[1]) << 8) + | (u64::from(b[2]) << 16) + | (u64::from(b[3]) << 24) + | (u64::from(b[4]) << 32) + | (u64::from(b[5]) << 40) + | (u64::from(b[6]) << 48) + | (u64::from(b[7]) << 56) +} + +fn random_seed() -> io::Result { + let mut buf = [0, 0, 0, 0, 0, 0, 0, 0]; + let mut fd = File::open("/dev/urandom")?; + fd.read_exact(&mut buf)?; + Ok(u64_from_bytes(buf)) +} diff --git a/src/message/mod.rs b/src/message/mod.rs index 4cb74eb..6e037d2 100644 --- a/src/message/mod.rs +++ b/src/message/mod.rs @@ -6,9 +6,10 @@ use std::{fmt, str::FromStr}; use serde::{Deserialize, Serialize}; mod error; +mod id; mod link; mod parser; -pub use {error::Error, link::Link, parser::Parser}; +pub use {error::Error, id::Id, link::Link, parser::Parser}; #[derive(Clone, Debug, Default, PartialEq)] pub struct Recipients { diff --git a/src/message/parser.rs b/src/message/parser.rs index e808f60..9a14af4 100644 --- a/src/message/parser.rs +++ b/src/message/parser.rs @@ -22,6 +22,7 @@ impl Parser { } } + #[allow(clippy::missing_panics_doc)] pub fn parse(mut self, content: &str) -> Result { let lines = content.lines(); for l in lines { diff --git a/src/prelude.rs b/src/prelude.rs index 48ff12d..b38ffb8 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -6,7 +6,7 @@ pub use super::{ mailbox::{Error as ParseMailboxError, Mailbox}, mailstore::{Account, Domain, Filesystem, FilesystemError, Folder, MailStore}, mailuser::Mailuser, - message::{Error as ParseMessageError, Link, Message, Recipients}, + message::{Error as ParseMessageError, Id, Link, Message, Recipients}, //receiver, request::{Error as ParseRequestError, Request}, response::{Error as ParseResponseError, Response}, diff --git a/src/time/mod.rs b/src/time/mod.rs index 3edd7c3..fdcf30e 100644 --- a/src/time/mod.rs +++ b/src/time/mod.rs @@ -1,4 +1,6 @@ //! A container representing a moment in ISO-8601 format Time +#![allow(clippy::module_name_repetitions)] +use crate::prelude::Id; use std::{fmt, str::FromStr}; mod error; @@ -8,13 +10,13 @@ pub use {error::Error, parser::Parser}; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; -const SECS_PER_DAY: i64 = 86400; +const SECS_PER_DAY: u64 = 86400; -fn days_since_epoch(ts: i64) -> i64 { +fn days_since_epoch(ts: u64) -> u64 { ts / SECS_PER_DAY } -fn is_leap(year: i64) -> bool { +fn is_leap(year: u32) -> bool { year % 4 == 0 && (year % 100 != 0 || year % 16 == 0) } @@ -22,7 +24,7 @@ fn is_leap(year: i64) -> bool { /// given month is February, then we also need to know /// whether it is a leap year or not, hence this function /// takes the year as an argument as well. -pub fn days_in_month(month: u8, year: i64) -> i64 { +pub fn days_in_month(month: u8, year: u32) -> u8 { assert!(month < 13); match month { 1 | 3 | 5 | 7 | 10 | 12 => 31, @@ -142,8 +144,8 @@ impl fmt::Display for DateTime { impl PartialOrd for DateTime { fn partial_cmp(&self, other: &Self) -> Option { - let a: i64 = (*self).into(); - let b: i64 = (*other).into(); + let a: u64 = (*self).into(); + let b: u64 = (*other).into(); a.partial_cmp(&b) } } @@ -156,12 +158,8 @@ impl FromStr for DateTime { } } -impl From for DateTime { - fn from(ts: i64) -> Self { - // Only allow using this function for positive timestamp values, - // ie no dates before 1970-01-01 - assert!(ts.is_positive()); - +impl From for DateTime { + fn from(ts: u64) -> Self { let mut year = 1970; let mut days = days_since_epoch(ts); loop { @@ -174,8 +172,9 @@ impl From for DateTime { } } let mut month = 1; - while days_in_month(month, year) <= days { - days -= days_in_month(month, year); + let m_days = u64::from(days_in_month(month, year)); + while m_days <= days { + days -= m_days; month += 1; } let seconds = ts % SECS_PER_DAY; @@ -184,8 +183,8 @@ impl From for DateTime { let hour = minutes / 60; let minute = minutes % 60; DateTime { - year: year as u32, - month: month.try_into().unwrap(), + year, + month, day: (days + 1).try_into().unwrap(), hour: Some((hour).try_into().unwrap()), minute: Some((minute).try_into().unwrap()), @@ -195,20 +194,28 @@ impl From for DateTime { } } -impl From for i64 { +#[allow(clippy::cast_possible_truncation)] +impl From for DateTime { + fn from(value: Id) -> Self { + let time = value.time; + time.into() + } +} + +impl From for u64 { fn from(dt: DateTime) -> Self { let mut seconds = dt.second.unwrap_or(0).into(); - seconds += dt.minute.unwrap_or(0) as i64 * 60; - seconds += (dt.hour.unwrap_or(1)) as i64 * 3600; - seconds += SECS_PER_DAY * (dt.day as i64 - 1); + seconds += u64::from(dt.minute.unwrap_or(0)) * 60; + seconds += u64::from(dt.hour.unwrap_or(1)) * 3600; + seconds += SECS_PER_DAY * (u64::from(dt.day) - 1); let mut m = 1; while m < dt.month { - seconds += SECS_PER_DAY * days_in_month(m, dt.year.into()); + seconds += SECS_PER_DAY * u64::from(days_in_month(m, dt.year)); m += 1; } let mut y = 1970; while y < dt.year { - if is_leap(y.into()) { + if is_leap(y) { seconds += SECS_PER_DAY * 366; } else { seconds += SECS_PER_DAY * 365; @@ -216,9 +223,9 @@ impl From for i64 { y += 1; } if let Some(TimeZone::Offset(offset)) = dt.tz { - let mut offset_seconds = offset.hours as i64 * 3600; + let mut offset_seconds = u64::from(offset.hours) * 3600; if let Some(offset_minutes) = offset.minutes { - offset_seconds += offset_minutes as i64 * 60; + offset_seconds += u64::from(offset_minutes) * 60; } match offset.sign { Sign::Positive => seconds += offset_seconds, @@ -237,7 +244,7 @@ impl DateTime { /// offset is applied and the result returned. pub fn normalize(&mut self) -> &Self { if let Some(TimeZone::Offset(_)) = self.tz { - *self = i64::from(*self).into(); + *self = u64::from(*self).into(); } self } diff --git a/src/time/parser.rs b/src/time/parser.rs index d5dea23..cce12d3 100644 --- a/src/time/parser.rs +++ b/src/time/parser.rs @@ -55,7 +55,7 @@ impl<'a> Parser<'a> { #[allow(clippy::missing_panics_doc)] fn validate_day(&self, day: u8) -> Result<(), Error> { - let max = days_in_month(self.month.unwrap(), self.year.unwrap() as i64) as u8; + let max = days_in_month(self.month.unwrap(), self.year.unwrap()); if day > max { Err(Error::InvalidDay) } else { @@ -316,18 +316,22 @@ impl<'a> Parser<'a> { } _ => {} } - if self.year.is_none() || self.month.is_none() || self.day.is_none() { - return Err(Error::Truncated); + if let Some(year) = self.year { + if let Some(month) = self.month { + if let Some(day) = self.day { + return Ok(DateTime { + year, + month, + day, + hour: self.hour, + minute: self.minute, + second: self.second, + tz: self.tz, + }); + } + } } - Ok(DateTime { - year: self.year.unwrap(), - month: self.month.unwrap(), - day: self.day.unwrap(), - hour: self.hour, - minute: self.minute, - second: self.second, - tz: self.tz, - }) + Err(Error::Truncated) } }