diff options
author | Jannis M. Hoffmann <jannis@fehcom.de> | 2024-11-21 21:14:40 +0100 |
---|---|---|
committer | Jannis M. Hoffmann <jannis@fehcom.de> | 2024-11-21 21:14:40 +0100 |
commit | 48c2945172b88c35c187d298a35bf26716af4e91 (patch) | |
tree | 2af21ddb4dcacd191e07fef156609b7c1488ebaf /src | |
parent | 6ed535387df0dffa72a10e601b8ea37c99345d84 (diff) |
Switch to varlink as IPC protocol
This is a lot! Whole new design on top of a statefult varlink interface.
You can now handle multiple request response cycles over a single
connection.
The error responses are lot more refined than just status codes with
optional messages and finally part of the protocol.
TODO: A lot of error handling needs to be improved.
Diffstat (limited to 'src')
-rw-r--r-- | src/arguments.rs | 25 | ||||
-rw-r--r-- | src/cmd.rs | 114 | ||||
-rw-r--r-- | src/cmd/add_folder.rs | 53 | ||||
-rw-r--r-- | src/cmd/count.rs | 27 | ||||
-rw-r--r-- | src/cmd/folders.rs | 35 | ||||
-rw-r--r-- | src/cmd/init.rs | 35 | ||||
-rw-r--r-- | src/cmd/list.rs | 158 | ||||
-rw-r--r-- | src/cmd/move.rs | 19 | ||||
-rw-r--r-- | src/cmd/move_mail.rs | 18 | ||||
-rw-r--r-- | src/cmd/raw.rs | 185 | ||||
-rw-r--r-- | src/cmd/read.rs | 24 | ||||
-rw-r--r-- | src/cmd/remove.rs | 29 | ||||
-rw-r--r-- | src/cmd/search.rs | 17 | ||||
-rw-r--r-- | src/cmd/show.rs | 26 | ||||
-rw-r--r-- | src/cmd/stats.rs | 25 | ||||
-rw-r--r-- | src/de.jmhoffmann.jwebmail.mail-storage.varlink | 86 | ||||
-rw-r--r-- | src/de_jmhoffmann_jwebmail_mail-storage.rs | 1035 | ||||
-rw-r--r-- | src/error.rs | 47 | ||||
-rw-r--r-- | src/main.rs | 128 | ||||
-rw-r--r-- | src/pb3/mod.rs | 3 | ||||
-rw-r--r-- | src/rfc822.rs | 365 |
21 files changed, 1821 insertions, 633 deletions
diff --git a/src/arguments.rs b/src/arguments.rs deleted file mode 100644 index a944abe..0000000 --- a/src/arguments.rs +++ /dev/null @@ -1,25 +0,0 @@ -use std::path::PathBuf; - -use clap::{Parser, ValueEnum}; - -#[derive(Clone, ValueEnum)] -pub enum Mode { - List, - Search, - Count, - Read, - Raw, - Folders, - Move, - Remove, - AddFolder, -} - -#[derive(Parser)] -#[command(author, version, about, long_about = None)] -pub struct Arguments { - pub maildir_path: PathBuf, - pub sys_user: String, - pub mail_user: String, - pub mode: Mode, -} @@ -1,24 +1,30 @@ use std::path::PathBuf; +use std::sync::RwLock; +use crate::de_jmhoffmann_jwebmail_mailstorage::*; use maildir::Maildir; mod add_folder; -mod count; mod folders; +mod init; mod list; -mod move_mail; +mod r#move; mod raw; -mod read; mod remove; +mod show; +mod stats; +mod search; -pub use add_folder::add_folder; -pub use count::count; -pub use folders::folders; -pub use list::list; -pub use move_mail::move_mail; -pub use raw::raw; -pub use read::read; -pub use remove::remove; +use add_folder::add_folder; +use folders::folders; +use init::init; +use list::list; +use r#move::r#move; +use raw::raw; +use remove::remove; +use show::show; +use stats::stats; +use search::search; pub fn open_submaildir(mut path: PathBuf, sub: &str) -> Maildir { if !sub.is_empty() { @@ -26,3 +32,89 @@ pub fn open_submaildir(mut path: PathBuf, sub: &str) -> Maildir { } Maildir::from(path) } + +pub struct MailStorage { + maildir_path: RwLock<Option<PathBuf>>, +} + +impl Default for MailStorage { + fn default() -> Self { + MailStorage { maildir_path: RwLock::new(None) } + } +} + +impl VarlinkInterface for MailStorage { + fn init( + &self, + call: &mut dyn Call_Init, + unix_user: String, + mailbox_path: String, + ) -> varlink::Result<()> { + init(self, call, unix_user, mailbox_path) + } + + fn add_folder(&self, call: &mut dyn Call_AddFolder, name: String) -> varlink::Result<()> { + add_folder(self, call, name) + } + + fn stats(&self, call: &mut dyn Call_Stats, folder: String) -> varlink::Result<()> { + stats(self, call, folder) + } + + fn folders(&self, call: &mut dyn Call_Folders) -> varlink::Result<()> { + folders(self, call) + } + + fn list( + &self, + call: &mut dyn Call_List, + folder: String, + start: i64, + end: i64, + sort: Sort, + ) -> varlink::Result<()> { + list(self, call, folder, start, end, sort) + } + + fn r#move( + &self, + call: &mut dyn Call_Move, + mid: String, + from_folder: String, + to_folder: String, + ) -> varlink::Result<()> { + r#move(self, call, mid, from_folder, to_folder) + } + + fn raw( + &self, + call: &mut dyn Call_Raw, + folder: String, + mid: String, + path: Option<String>, + ) -> varlink::Result<()> { + raw(self, call, folder, mid, path) + } + + fn remove( + &self, + call: &mut dyn Call_Remove, + folder: String, + mid: String, + ) -> varlink::Result<()> { + remove(self, call, folder, mid) + } + + fn search( + &self, + call: &mut dyn Call_Search, + folder: String, + pattern: String, + ) -> varlink::Result<()> { + search(self, call, folder, pattern) + } + + fn show(&self, call: &mut dyn Call_Show, folder: String, mid: String) -> varlink::Result<()> { + show(self, call, folder, mid) + } +} diff --git a/src/cmd/add_folder.rs b/src/cmd/add_folder.rs index 82df1f9..07ad455 100644 --- a/src/cmd/add_folder.rs +++ b/src/cmd/add_folder.rs @@ -1,37 +1,40 @@ use std::fs::create_dir; -use std::path::PathBuf; -use protobuf::Message as _; +use varlink; -use crate::error::Result; -use crate::pb3::jwebmail::{AddFolderReq, AddFolderResp}; +use crate::cmd::MailStorage; +use crate::de_jmhoffmann_jwebmail_mailstorage::{AddFolder_Reply_status, Call_AddFolder}; -pub fn add_folder(mut p: PathBuf, req: &[u8]) -> Result<Vec<u8>> { - let r = AddFolderReq::parse_from_bytes(req)?; - let mut resp = AddFolderResp::new(); +pub fn add_folder( + ms: &MailStorage, + call: &mut dyn Call_AddFolder, + name: String, +) -> varlink::Result<()> { + if let Some(mut p) = ms.maildir_path.read().unwrap().clone() { + let mut folder = ".".to_owned(); + folder.push_str(&name); - let mut folder = ".".to_owned(); - folder.push_str(&r.name); - p.push(folder); + p.push(folder); - if p.is_dir() { - resp.status = 1; - return resp.write_to_bytes().map_err(|e| e.into()); - } + if p.is_dir() { + return call.reply(AddFolder_Reply_status::skiped); + } - create_dir(&p)?; + create_dir(&p).map_err(varlink::map_context!())?; - p.push("tmp"); - create_dir(&p)?; + p.push("tmp"); + create_dir(&p).map_err(varlink::map_context!())?; - p.pop(); - p.push("new"); - create_dir(&p)?; + p.pop(); + p.push("new"); + create_dir(&p).map_err(varlink::map_context!())?; - p.pop(); - p.push("cur"); - create_dir(p)?; + p.pop(); + p.push("cur"); + create_dir(p).map_err(varlink::map_context!())?; - resp.status = 0; - resp.write_to_bytes().map_err(|e| e.into()) + call.reply(AddFolder_Reply_status::created) + } else { + call.reply_not_initialized() + } } diff --git a/src/cmd/count.rs b/src/cmd/count.rs deleted file mode 100644 index 5de542b..0000000 --- a/src/cmd/count.rs +++ /dev/null @@ -1,27 +0,0 @@ -use std::path::PathBuf; - -use protobuf::Message; - -use crate::cmd::open_submaildir; -use crate::error::Result; -use crate::pb3::jwebmail::{StatsReq, StatsResp}; - -pub fn count(path: PathBuf, req: &[u8]) -> Result<Vec<u8>> { - let r = StatsReq::parse_from_bytes(req)?; - - let md = open_submaildir(path, &r.folder); - - let mut resp = StatsResp::new(); - resp.mail_count = md.count_cur() as u32; - resp.unread_count = md - .list_cur() - .filter(|x| x.as_ref().map_or(false, |z| !z.is_seen())) - .count() as u32; - resp.byte_size = md - .path() - .join("cur") - .read_dir()? - .map(|x| x.map_or(0, |z| z.metadata().map_or(0, |y| y.len()))) - .sum(); - resp.write_to_bytes().map_err(|e| e.into()) -} diff --git a/src/cmd/folders.rs b/src/cmd/folders.rs index 417203c..8bf9a30 100644 --- a/src/cmd/folders.rs +++ b/src/cmd/folders.rs @@ -1,9 +1,7 @@ -use std::path::{Path, PathBuf}; +use std::path::Path; -use protobuf::Message as _; - -use crate::error::Result; -use crate::pb3::jwebmail::{FoldersReq, FoldersResp}; +use crate::cmd::MailStorage; +use crate::de_jmhoffmann_jwebmail_mailstorage::Call_Folders; fn is_mailsubdir(p: &Path) -> bool { if !p.is_dir() { @@ -32,19 +30,20 @@ fn is_mailsubdir(p: &Path) -> bool { true } -pub fn folders(path: PathBuf, req: &[u8]) -> Result<Vec<u8>> { - let _ = FoldersReq::parse_from_bytes(req)?; - - let mut subdirs: Vec<_> = path - .read_dir()? - .filter_map(|d| d.ok()) - .filter(|d| is_mailsubdir(&d.path())) - .filter_map(|d| Some(d.path().file_name()?.to_string_lossy()[1..].to_owned())) - .collect(); +pub fn folders(ms: &MailStorage, call: &mut dyn Call_Folders) -> varlink::Result<()> { + if let Some(path) = &*ms.maildir_path.read().unwrap() { + let mut subdirs: Vec<_> = path + .read_dir() + .map_err(varlink::map_context!())? + .filter_map(|d| d.ok()) + .filter(|d| is_mailsubdir(&d.path())) + .filter_map(|d| Some(d.path().file_name()?.to_string_lossy()[1..].to_owned())) + .collect(); - subdirs.sort(); + subdirs.sort(); - let mut res = FoldersResp::new(); - res.folders = subdirs; - res.write_to_bytes().map_err(|e| e.into()) + call.reply(subdirs) + } else { + call.reply_not_initialized() + } } diff --git a/src/cmd/init.rs b/src/cmd/init.rs new file mode 100644 index 0000000..167d028 --- /dev/null +++ b/src/cmd/init.rs @@ -0,0 +1,35 @@ +use std::ffi::CString; + +use crate::cmd::MailStorage; +use crate::de_jmhoffmann_jwebmail_mailstorage::Call_Init; + +pub fn init( + ms: &MailStorage, + call: &mut dyn Call_Init, + unix_user: String, + mailbox_path: String, +) -> varlink::Result<()> { + unsafe { + *libc::__errno_location() = 0; + } + if let Ok(c_sys_user) = CString::new(unix_user.clone()) { + let user_info: *const libc::passwd = unsafe { libc::getpwnam(c_sys_user.as_ptr()) }; + let err = unsafe { *libc::__errno_location() }; + if err != 0 { + return call.reply_invalid_user(unix_user); + } + if user_info.is_null() { + return call.reply_invalid_user(unix_user); + } + let rc = unsafe { libc::setuid((*user_info).pw_uid) }; + if rc != 0 { + return call.reply_invalid_user(unix_user); + } + + *ms.maildir_path.write().unwrap() = Some(mailbox_path.into()); + + call.reply() + } else { + call.reply_invalid_user(unix_user) + } +} diff --git a/src/cmd/list.rs b/src/cmd/list.rs index 3c2d42a..8aeaf97 100644 --- a/src/cmd/list.rs +++ b/src/cmd/list.rs @@ -1,12 +1,11 @@ use std::cmp::Reverse; -use std::path::PathBuf; use log::warn; -use protobuf::Message as _; -use crate::cmd::open_submaildir; -use crate::error::Result; -use crate::pb3::jwebmail::{ListMailHeader, ListReq, ListResp, MailHeader}; +use crate::cmd::{open_submaildir, MailStorage}; +use crate::de_jmhoffmann_jwebmail_mailstorage::{ + Call_List, ListMailHeader, MailHeader, Sort, Sort_direction, Sort_parameter, +}; use crate::rfc822::me_to_lmh; fn from_or_sender(mh: &MailHeader) -> &str { @@ -37,95 +36,110 @@ fn mid_to_rec_time(mid: &str) -> f64 { fn sort_by_and_take( mut entries: Vec<maildir::MailEntry>, - sortby: &str, + sortby: Sort, s: usize, e: usize, ) -> Vec<ListMailHeader> { - match sortby { - "date" => { - entries.sort_by(|a, b| { - mid_to_rec_time(a.id()) - .partial_cmp(&mid_to_rec_time(b.id())) - .unwrap() - }); - entries - .drain(s..e) - .filter_map(|me| me_to_lmh(me).map_err(|e| warn!("{}", e)).ok()) - .collect() - } - "!date" => { - entries.sort_by(|b, a| { - mid_to_rec_time(a.id()) - .partial_cmp(&mid_to_rec_time(b.id())) - .unwrap() - }); - entries - .drain(s..e) - .filter_map(|me| me_to_lmh(me).map_err(|e| warn!("{}", e)).ok()) - .collect() - } - "size" => { - entries.sort_by_cached_key(|a| a.path().metadata().map_or(0, |m| m.len())); - entries - .drain(s..e) - .filter_map(|me| me_to_lmh(me).map_err(|e| warn!("{}", e)).ok()) - .collect() - } - "!size" => { - entries.sort_by_cached_key(|a| Reverse(a.path().metadata().map_or(0, |m| m.len()))); - entries - .drain(s..e) - .filter_map(|me| me_to_lmh(me).map_err(|e| warn!("{}", e)).ok()) - .collect() - } - "subject" | "!subject" => { + match sortby.parameter { + Sort_parameter::date => match sortby.direction { + Sort_direction::asc => { + entries.sort_by(|a, b| { + mid_to_rec_time(a.id()) + .partial_cmp(&mid_to_rec_time(b.id())) + .unwrap() + }); + entries + .drain(s..e) + .filter_map(|me| me_to_lmh(me).map_err(|e| warn!("{}", e)).ok()) + .collect() + } + Sort_direction::desc => { + entries.sort_by(|b, a| { + mid_to_rec_time(a.id()) + .partial_cmp(&mid_to_rec_time(b.id())) + .unwrap() + }); + entries + .drain(s..e) + .filter_map(|me| me_to_lmh(me).map_err(|e| warn!("{}", e)).ok()) + .collect() + } + }, + Sort_parameter::size => match sortby.direction { + Sort_direction::asc => { + entries.sort_by_cached_key(|a| a.path().metadata().map_or(0, |m| m.len())); + entries + .drain(s..e) + .filter_map(|me| me_to_lmh(me).map_err(|e| warn!("{}", e)).ok()) + .collect() + } + Sort_direction::desc => { + entries.sort_by_cached_key(|a| Reverse(a.path().metadata().map_or(0, |m| m.len()))); + entries + .drain(s..e) + .filter_map(|me| me_to_lmh(me).map_err(|e| warn!("{}", e)).ok()) + .collect() + } + }, + Sort_parameter::subject => { let mut x: Vec<ListMailHeader> = entries .drain(..) .filter_map(|me| me_to_lmh(me).map_err(|e| warn!("{}", e)).ok()) .collect(); - if sortby.as_bytes().first().copied() == Some(b'!') { - x.sort_by(|a, b| a.header.subject.cmp(&b.header.subject)) - } else { - x.sort_by(|b, a| a.header.subject.cmp(&b.header.subject)) + match sortby.direction { + Sort_direction::asc => x.sort_by(|a, b| a.header.subject.cmp(&b.header.subject)), + Sort_direction::desc => x.sort_by(|b, a| a.header.subject.cmp(&b.header.subject)), } x.drain(s..e).collect() } - "sender" | "!sender" => { + Sort_parameter::sender => { let mut x: Vec<ListMailHeader> = entries .drain(..) .filter_map(|me| me_to_lmh(me).map_err(|e| warn!("{}", e)).ok()) .collect(); - if sortby.as_bytes().first().copied() != Some(b'!') { - x.sort_by(|a, b| from_or_sender(&a.header).cmp(from_or_sender(&b.header))) - } else { - x.sort_by(|b, a| from_or_sender(&a.header).cmp(from_or_sender(&b.header))) + match sortby.direction { + Sort_direction::asc => { + x.sort_by(|a, b| from_or_sender(&a.header).cmp(from_or_sender(&b.header))) + } + Sort_direction::desc => { + x.sort_by(|b, a| from_or_sender(&a.header).cmp(from_or_sender(&b.header))) + } } x.drain(s..e).collect() } - _ => todo!(), } } -pub fn list(path: PathBuf, req: &[u8]) -> Result<Vec<u8>> { - let r = ListReq::parse_from_bytes(req)?; - let md = open_submaildir(path, &r.folder); +pub fn list( + ms: &MailStorage, + call: &mut dyn Call_List, + folder: String, + start: i64, + end: i64, + sort: Sort, +) -> varlink::Result<()> { + if let Some(path) = ms.maildir_path.read().unwrap().clone() { + let md = open_submaildir(path, &folder); - for r in md.list_new() { - match r { - Err(e) => warn!("{}", e), - Ok(me) => { - if let Err(e) = md.move_new_to_cur(me.id()) { - warn!("{}", e); + for r in md.list_new() { + match r { + Err(e) => warn!("{}", e), + Ok(me) => { + if let Err(e) = md.move_new_to_cur(me.id()) { + warn!("{}", e); + } } - } - }; - } + }; + } + + let a: Vec<_> = md.list_cur().filter_map(std::result::Result::ok).collect(); + let start = std::cmp::min(a.len(), start as usize); + let end = std::cmp::min(a.len(), end as usize); - let a: Vec<_> = md.list_cur().filter_map(std::result::Result::ok).collect(); - let start = std::cmp::min(a.len(), r.start as usize); - let end = std::cmp::min(a.len(), r.end as usize); + let mail_heads = sort_by_and_take(a, sort, start, end); - let mut resp = ListResp::new(); - resp.mail_heads = sort_by_and_take(a, &r.sort, start, end); - resp.write_to_bytes().map_err(|e| e.into()) + call.reply(mail_heads) + } else { + call.reply_not_initialized() + } } diff --git a/src/cmd/move.rs b/src/cmd/move.rs new file mode 100644 index 0000000..c852d6c --- /dev/null +++ b/src/cmd/move.rs @@ -0,0 +1,19 @@ +use crate::cmd::{open_submaildir, MailStorage}; +use crate::de_jmhoffmann_jwebmail_mailstorage::Call_Move; + +pub fn r#move( + ms: &MailStorage, + call: &mut dyn Call_Move, + mid: String, + from_folder: String, + to_folder: String, +) -> varlink::Result<()> { + if let Some(p) = ms.maildir_path.read().unwrap().clone() { + let from = open_submaildir(p.clone(), &from_folder); + let to = open_submaildir(p, &to_folder); + from.move_to(&mid, &to).unwrap(); + call.reply() + } else { + call.reply_not_initialized() + } +} diff --git a/src/cmd/move_mail.rs b/src/cmd/move_mail.rs deleted file mode 100644 index 146e906..0000000 --- a/src/cmd/move_mail.rs +++ /dev/null @@ -1,18 +0,0 @@ -use std::path::PathBuf; - -use protobuf::Message as _; - -use crate::cmd::open_submaildir; -use crate::error::Result; -use crate::pb3::jwebmail::{MoveReq, MoveResp}; - -pub fn move_mail(p: PathBuf, req: &[u8]) -> Result<Vec<u8>> { - let r = MoveReq::parse_from_bytes(req)?; - - let from = open_submaildir(p.clone(), &r.from_f); - let to = open_submaildir(p, &r.to_f); - from.move_to(&r.mid, &to)?; - - let resp = MoveResp::new(); - resp.write_to_bytes().map_err(|e| e.into()) -} diff --git a/src/cmd/raw.rs b/src/cmd/raw.rs index 9fe7620..a20eded 100644 --- a/src/cmd/raw.rs +++ b/src/cmd/raw.rs @@ -1,106 +1,109 @@ -use std::fs::read; -use std::io::ErrorKind as IOErrKind; -use std::path::PathBuf; +use base64::prelude::BASE64_STANDARD; +use base64::Engine; -use protobuf::Message as _; - -use crate::cmd::open_submaildir; -use crate::error::{Error, Result}; -use crate::pb3::jwebmail::mimeheader::ContentDisposition::*; -use crate::pb3::jwebmail::{MIMEHeader, RawReq, RawResp}; +use crate::cmd::{open_submaildir, MailStorage}; +use crate::de_jmhoffmann_jwebmail_mailstorage::MIMEHeader_content_dispo as CD; +use crate::de_jmhoffmann_jwebmail_mailstorage::{Call_Raw, MIMEHeader, MIMEHeader_mime_type}; use crate::rfc822::parse_mail_content; -pub fn raw(md_path: PathBuf, req: &[u8]) -> Result<Vec<u8>> { - let r = RawReq::parse_from_bytes(req)?; - - let md = open_submaildir(md_path, &r.folder); +pub fn raw( + ms: &MailStorage, + call: &mut dyn Call_Raw, + folder: String, + mid: String, + path: Option<String>, +) -> varlink::Result<()> { + if let Some(p) = ms.maildir_path.read().unwrap().clone() { + let md = open_submaildir(p, &folder); - let mut mail = md.find(&r.mid).ok_or_else(|| { - std::io::Error::new(IOErrKind::NotFound, format!("mail {} not found", &r.mid)) - })?; + if let Some(mut mail) = md.find(&mid) { + match path.as_deref() { + Some("") | None => call.reply( + MIMEHeader { + mime_type: MIMEHeader_mime_type { + main_type: "message".to_owned(), + sub_type: "rfc822".to_owned(), + }, + file_name: Some(mail.id().to_owned()), + content_dispo: CD::none, + }, + BASE64_STANDARD + .encode(std::fs::read(mail.path()).map_err(varlink::map_context!())?), + ), + Some(mime_path) => { + if let Ok(path) = mime_path + .split('.') + .map(|x| x.parse()) + .collect::<std::result::Result<Vec<usize>, std::num::ParseIntError>>() + { + let mut m = mail.parsed().unwrap(); - match r.path.as_deref() { - Some("") | None => { - let mut mh = MIMEHeader::new(); + if path[0] != 0 { + return call.reply_invalid_path_in_mail( + folder, + mid, + mime_path.to_owned(), + ); + } - mh.maintype = "message".to_owned(); - mh.subtype = "rfc822".to_owned(); - mh.file_name = Some(mail.id().to_owned()); - mh.contentdispo = CONTENT_DISPOSITION_NONE.into(); + for i in &path[1..] { + match &m.ctype.mimetype { + x if x.starts_with("message/") => { + if *i != 0 { + return call.reply_invalid_path_in_mail( + folder, + mid, + mime_path.to_owned(), + ); + } + let s: &'static _ = m.get_body_raw().unwrap().leak(); + m = mailparse::parse_mail(s).unwrap(); + } + x if x.starts_with("multipart/") => { + if *i >= m.subparts.len() { + return call.reply_invalid_path_in_mail( + folder, + mid, + mime_path.to_owned(), + ); + } + m = m.subparts.swap_remove(*i); + } + _ => { + return call.reply_invalid_path_in_mail( + folder, + mid, + mime_path.to_owned(), + ); + } + } + } - let mut resp = RawResp::new(); - resp.header = Some(mh).into(); - resp.body = read(mail.path())?; - resp.write_to_bytes().map_err(|e| e.into()) - } - Some(mime_path) => { - let path = mime_path - .split('.') - .map(|x| { - x.parse() - .map_err(|pe: std::num::ParseIntError| Error::PathError { - msg: pe.to_string(), - path: mime_path.to_owned(), - }) - }) - .collect::<Result<Vec<_>>>()?; - let mut m = mail.parsed()?; + if m.ctype.mimetype.starts_with("multipart/") { + return call.reply_invalid_path_in_mail( + folder, + mid, + mime_path.to_owned(), + ); + } - if path[0] != 0 { - return Err(Error::PathError { - msg: "Message must be accessed by a 0".to_owned(), - path: mime_path.to_owned(), - }); - } + let mime_part = parse_mail_content(&m); + let content = if m.ctype.mimetype.starts_with("text/") { + m.get_body().unwrap().into_bytes() + } else { + m.get_body_raw().unwrap() + }; - for i in &path[1..] { - match &m.ctype.mimetype { - x if x.starts_with("message/") => { - if *i != 0 { - return Err(Error::PathError { - msg: "Message must be accessed by a 0".to_owned(), - path: mime_path.to_owned(), - }); - } - let s: &'static _ = m.get_body_raw()?.leak(); - m = mailparse::parse_mail(s)?; - } - x if x.starts_with("multipart/") => { - if *i >= m.subparts.len() { - return Err(Error::PathError { - msg: "Out of bounds access".to_owned(), - path: mime_path.to_owned(), - }); - } - m = m.subparts.swap_remove(*i); - } - _ => { - return Err(Error::PathError { - msg: "Unable to descent into leaf component".to_owned(), - path: mime_path.to_owned(), - }) + call.reply(mime_part, BASE64_STANDARD.encode(content)) + } else { + call.reply_invalid_path_in_mail(folder, mid, mime_path.to_owned()) } } } - - if m.ctype.mimetype.starts_with("multipart/") { - return Err(Error::PathError { - msg: "Can not show multipart component".to_owned(), - path: mime_path.to_owned(), - }); - } - - let mime_part = parse_mail_content(&m)?; - let content = if m.ctype.mimetype.starts_with("text/") { - m.get_body()?.into_bytes() - } else { - m.get_body_raw()? - }; - - let mut resp = RawResp::new(); - resp.header = Some(mime_part).into(); - resp.body = content; - resp.write_to_bytes().map_err(|e| e.into()) + } else { + call.reply_invalid_mid(folder, mid) } + } else { + call.reply_not_initialized() } } diff --git a/src/cmd/read.rs b/src/cmd/read.rs deleted file mode 100644 index 797f4d6..0000000 --- a/src/cmd/read.rs +++ /dev/null @@ -1,24 +0,0 @@ -use std::io::ErrorKind as IOErrKind; -use std::path::PathBuf; - -use protobuf::Message as _; - -use crate::cmd::open_submaildir; -use crate::error::Result; -use crate::pb3::jwebmail::{ShowReq, ShowResp}; -use crate::rfc822::parsed_mail_to_mail; - -pub fn read(path: PathBuf, req: &[u8]) -> Result<Vec<u8>> { - let r = ShowReq::parse_from_bytes(req)?; - let md = open_submaildir(path, &r.folder); - - md.add_flags(&r.mid, "S")?; - - let mut mail = md.find(&r.mid).ok_or_else(|| { - std::io::Error::new(IOErrKind::NotFound, format!("mail {} not found", &r.mid)) - })?; - - let mut resp = ShowResp::new(); - resp.mail = Some(parsed_mail_to_mail(mail.parsed()?)?).into(); - resp.write_to_bytes().map_err(|e| e.into()) -} diff --git a/src/cmd/remove.rs b/src/cmd/remove.rs index 8d26e68..73328a5 100644 --- a/src/cmd/remove.rs +++ b/src/cmd/remove.rs @@ -1,17 +1,18 @@ -use std::path::PathBuf; +use crate::cmd::{open_submaildir, MailStorage}; +use crate::de_jmhoffmann_jwebmail_mailstorage::Call_Remove; -use protobuf::Message as _; +pub fn remove( + ms: &MailStorage, + call: &mut dyn Call_Remove, + folder: String, + mid: String, +) -> varlink::Result<()> { + if let Some(p) = ms.maildir_path.read().unwrap().clone() { + let md = open_submaildir(p, &folder); + md.add_flags(&mid, "T").map_err(varlink::map_context!())?; -use crate::cmd::open_submaildir; -use crate::error::Result; -use crate::pb3::jwebmail::{RemoveReq, RemoveResp}; - -pub fn remove(p: PathBuf, req: &[u8]) -> Result<Vec<u8>> { - let r = RemoveReq::parse_from_bytes(req)?; - - let md = open_submaildir(p, &r.folder); - md.add_flags(&r.mid, "T")?; - - let resp = RemoveResp::new(); - resp.write_to_bytes().map_err(|e| e.into()) + call.reply() + } else { + call.reply_not_initialized() + } } diff --git a/src/cmd/search.rs b/src/cmd/search.rs new file mode 100644 index 0000000..7cf0fb0 --- /dev/null +++ b/src/cmd/search.rs @@ -0,0 +1,17 @@ +use crate::cmd::{open_submaildir, MailStorage}; +use crate::de_jmhoffmann_jwebmail_mailstorage::Call_Search; + +pub fn search( + ms: &MailStorage, + call: &mut dyn Call_Search, + folder: String, + _pattern: String, +) -> varlink::Result<()> { + if let Some(p) = ms.maildir_path.read().unwrap().clone() { + let md = open_submaildir(p, &folder); + + todo!(); + } else { + call.reply_not_initialized() + } +} diff --git a/src/cmd/show.rs b/src/cmd/show.rs new file mode 100644 index 0000000..442eb21 --- /dev/null +++ b/src/cmd/show.rs @@ -0,0 +1,26 @@ +use crate::cmd::{open_submaildir, MailStorage}; +use crate::de_jmhoffmann_jwebmail_mailstorage::Call_Show; +use crate::rfc822::parsed_mail_to_mail; + +pub fn show( + ms: &MailStorage, + call: &mut dyn Call_Show, + folder: String, + mid: String, +) -> varlink::Result<()> { + if let Some(path) = ms.maildir_path.read().unwrap().clone() { + let md = open_submaildir(path, &folder); + + md.add_flags(&mid, "S").map_err(varlink::map_context!())?; + + if let Some(mut mail) = md.find(&mid) { + let mail2 = parsed_mail_to_mail(mail.parsed().unwrap()).unwrap(); + + call.reply(mail2) + } else { + call.reply_invalid_mid(folder, mid) + } + } else { + call.reply_not_initialized() + } +} diff --git a/src/cmd/stats.rs b/src/cmd/stats.rs new file mode 100644 index 0000000..e8e8325 --- /dev/null +++ b/src/cmd/stats.rs @@ -0,0 +1,25 @@ +use crate::cmd::{open_submaildir, MailStorage}; +use crate::de_jmhoffmann_jwebmail_mailstorage::Call_Stats; + +pub fn stats(ms: &MailStorage, call: &mut dyn Call_Stats, folder: String) -> varlink::Result<()> { + if let Some(maildir_path) = ms.maildir_path.read().unwrap().clone() { + let maildir = open_submaildir(maildir_path, &folder); + + let mail_count = maildir.count_cur() as i64; + let unread_count = maildir + .list_cur() + .filter(|x| x.as_ref().map_or(false, |z| !z.is_seen())) + .count() as i64; + let byte_size: u64 = maildir + .path() + .join("cur") + .read_dir() + .map_err(varlink::map_context!())? + .map(|x| x.map_or(0, |z| z.metadata().map_or(0, |y| y.len()))) + .sum(); + + call.reply(mail_count, unread_count, byte_size as i64) + } else { + call.reply_not_initialized() + } +} diff --git a/src/de.jmhoffmann.jwebmail.mail-storage.varlink b/src/de.jmhoffmann.jwebmail.mail-storage.varlink new file mode 100644 index 0000000..f61f624 --- /dev/null +++ b/src/de.jmhoffmann.jwebmail.mail-storage.varlink @@ -0,0 +1,86 @@ +interface de.jmhoffmann.jwebmail.mail-storage + + +type MIMEHeader ( + mime_type: (main_type: string, sub_type: string), + content_dispo: (none, inline, attachment), + file_name: ?string +) + +type MailAddr ( + name: ?string, + address: string +) + +# send_date is of ISO 8601 date-time format +type MailHeader ( + send_date: string, + written_from: []MailAddr, + sender: ?MailAddr, + reply_to: []MailAddr, + send_to: []MailAddr, + cc: []MailAddr, + bcc: []MailAddr, + subject: string, + comments: []string, + keywords: []string, + mime: MIMEHeader +) + +type ListMailHeader ( + byte_size: int, + unread: bool, + rec_date: string, + mid: string, + header: MailHeader +) + +type Multipart ( + preamble: ?string, + parts: []MIMEPart, + epilogue: ?string +) + +# exactly one of these fileds must be present +type MailBody ( + discrete: ?string, + multipart: ?Multipart, + mail: ?Mail +) + +type Mail ( + head: MailHeader, + body: MailBody +) + +type MIMEPart ( + mime_header: MIMEHeader, + body: MailBody +) + +type Sort ( + direction: (asc, desc), + parameter: (date, size, sender) +) + + +method Init(unix_user: string, mailbox_path: string) -> () +method List(folder: string, start: int, end: int, sort: Sort) -> (mail_heads: []ListMailHeader) +method Stats(folder: string) -> (mail_count: int, unread_count: int, byte_size: int) +method Show(folder: string, mid: string) -> (mail: Mail) +# body is base64 encoded +method Raw(folder: string, mid: string, path: ?string) -> (header: MIMEHeader, body: string) +method Search(folder: string, pattern: string) -> (found: []ListMailHeader) +method Folders() -> (folders: []string) +method Move(mid: string, from_folder: string, to_folder: string) -> () +method Remove(folder: string, mid: string) -> () +method AddFolder(name: string) -> (status: (created, skiped)) + + +error NotInitialized() +error InvalidFolder(folder: string) +error InvalidMID(folder: string, mid: string) +error InvalidPathInMail(folder: string, mid: string, path: string) +error InvalidSearchPattern(pattern: string) +error InvalidUser(unix_user: string) +error InvalidMailbox(path: string, not_a_mailbox: bool, user_mismatch: bool) diff --git a/src/de_jmhoffmann_jwebmail_mail-storage.rs b/src/de_jmhoffmann_jwebmail_mail-storage.rs new file mode 100644 index 0000000..bf3b148 --- /dev/null +++ b/src/de_jmhoffmann_jwebmail_mail-storage.rs @@ -0,0 +1,1035 @@ +#![doc = "This file was automatically generated by the varlink rust generator"] +#![allow(non_camel_case_types)] +#![allow(non_snake_case)] +use serde_derive::{Deserialize, Serialize}; +use std::io::BufRead; +use std::sync::{Arc, RwLock}; +use varlink::{self, CallTrait}; +#[allow(dead_code)] +#[derive(Clone, PartialEq, Debug)] +#[allow(clippy::enum_variant_names)] +pub enum ErrorKind { + Varlink_Error, + VarlinkReply_Error, + InvalidFolder(Option<InvalidFolder_Args>), + InvalidMID(Option<InvalidMID_Args>), + InvalidMailbox(Option<InvalidMailbox_Args>), + InvalidPathInMail(Option<InvalidPathInMail_Args>), + InvalidSearchPattern(Option<InvalidSearchPattern_Args>), + InvalidUser(Option<InvalidUser_Args>), + NotInitialized(Option<NotInitialized_Args>), +} +impl ::std::fmt::Display for ErrorKind { + fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { + match self { + ErrorKind::Varlink_Error => write!(f, "Varlink Error"), + ErrorKind::VarlinkReply_Error => write!(f, "Varlink error reply"), + ErrorKind::InvalidFolder(v) => write!( + f, + "de.jmhoffmann.jwebmail.mail-storage.InvalidFolder: {:#?}", + v + ), + ErrorKind::InvalidMID(v) => write!( + f, + "de.jmhoffmann.jwebmail.mail-storage.InvalidMID: {:#?}", + v + ), + ErrorKind::InvalidMailbox(v) => write!( + f, + "de.jmhoffmann.jwebmail.mail-storage.InvalidMailbox: {:#?}", + v + ), + ErrorKind::InvalidPathInMail(v) => write!( + f, + "de.jmhoffmann.jwebmail.mail-storage.InvalidPathInMail: {:#?}", + v + ), + ErrorKind::InvalidSearchPattern(v) => write!( + f, + "de.jmhoffmann.jwebmail.mail-storage.InvalidSearchPattern: {:#?}", + v + ), + ErrorKind::InvalidUser(v) => write!( + f, + "de.jmhoffmann.jwebmail.mail-storage.InvalidUser: {:#?}", + v + ), + ErrorKind::NotInitialized(v) => write!( + f, + "de.jmhoffmann.jwebmail.mail-storage.NotInitialized: {:#?}", + v + ), + } + } +} +pub struct Error( + pub ErrorKind, + pub Option<Box<dyn std::error::Error + 'static + Send + Sync>>, + pub Option<&'static str>, +); +impl Error { + #[allow(dead_code)] + pub fn kind(&self) -> &ErrorKind { + &self.0 + } +} +impl From<ErrorKind> for Error { + fn from(e: ErrorKind) -> Self { + Error(e, None, None) + } +} +impl std::error::Error for Error { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + self.1 + .as_ref() + .map(|e| e.as_ref() as &(dyn std::error::Error + 'static)) + } +} +impl std::fmt::Display for Error { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + std::fmt::Display::fmt(&self.0, f) + } +} +impl std::fmt::Debug for Error { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + use std::error::Error as StdError; + if let Some(ref o) = self.2 { + std::fmt::Display::fmt(o, f)?; + } + std::fmt::Debug::fmt(&self.0, f)?; + if let Some(e) = self.source() { + std::fmt::Display::fmt("\nCaused by:\n", f)?; + std::fmt::Debug::fmt(&e, f)?; + } + Ok(()) + } +} +#[allow(dead_code)] +pub type Result<T> = std::result::Result<T, Error>; +impl From<varlink::Error> for Error { + fn from(e: varlink::Error) -> Self { + match e.kind() { + varlink::ErrorKind::VarlinkErrorReply(r) => Error( + ErrorKind::from(r), + Some(Box::from(e)), + Some(concat!(file!(), ":", line!(), ": ")), + ), + _ => Error( + ErrorKind::Varlink_Error, + Some(Box::from(e)), + Some(concat!(file!(), ":", line!(), ": ")), + ), + } + } +} +#[allow(dead_code)] +impl Error { + pub fn source_varlink_kind(&self) -> Option<&varlink::ErrorKind> { + use std::error::Error as StdError; + let mut s: &dyn StdError = self; + while let Some(c) = s.source() { + let k = self + .source() + .and_then(|e| e.downcast_ref::<varlink::Error>()) + .map(|e| e.kind()); + if k.is_some() { + return k; + } + s = c; + } + None + } +} +impl From<&varlink::Reply> for ErrorKind { + #[allow(unused_variables)] + fn from(e: &varlink::Reply) -> Self { + match e { + varlink::Reply { + error: Some(ref t), .. + } if t == "de.jmhoffmann.jwebmail.mail-storage.InvalidFolder" => match e { + varlink::Reply { + parameters: Some(p), + .. + } => match serde_json::from_value(p.clone()) { + Ok(v) => ErrorKind::InvalidFolder(v), + Err(_) => ErrorKind::InvalidFolder(None), + }, + _ => ErrorKind::InvalidFolder(None), + }, + varlink::Reply { + error: Some(ref t), .. + } if t == "de.jmhoffmann.jwebmail.mail-storage.InvalidMID" => match e { + varlink::Reply { + parameters: Some(p), + .. + } => match serde_json::from_value(p.clone()) { + Ok(v) => ErrorKind::InvalidMID(v), + Err(_) => ErrorKind::InvalidMID(None), + }, + _ => ErrorKind::InvalidMID(None), + }, + varlink::Reply { + error: Some(ref t), .. + } if t == "de.jmhoffmann.jwebmail.mail-storage.InvalidMailbox" => match e { + varlink::Reply { + parameters: Some(p), + .. + } => match serde_json::from_value(p.clone()) { + Ok(v) => ErrorKind::InvalidMailbox(v), + Err(_) => ErrorKind::InvalidMailbox(None), + }, + _ => ErrorKind::InvalidMailbox(None), + }, + varlink::Reply { + error: Some(ref t), .. + } if t == "de.jmhoffmann.jwebmail.mail-storage.InvalidPathInMail" => match e { + varlink::Reply { + parameters: Some(p), + .. + } => match serde_json::from_value(p.clone()) { + Ok(v) => ErrorKind::InvalidPathInMail(v), + Err(_) => ErrorKind::InvalidPathInMail(None), + }, + _ => ErrorKind::InvalidPathInMail(None), + }, + varlink::Reply { + error: Some(ref t), .. + } if t == "de.jmhoffmann.jwebmail.mail-storage.InvalidSearchPattern" => match e { + varlink::Reply { + parameters: Some(p), + .. + } => match serde_json::from_value(p.clone()) { + Ok(v) => ErrorKind::InvalidSearchPattern(v), + Err(_) => ErrorKind::InvalidSearchPattern(None), + }, + _ => ErrorKind::InvalidSearchPattern(None), + }, + varlink::Reply { + error: Some(ref t), .. + } if t == "de.jmhoffmann.jwebmail.mail-storage.InvalidUser" => match e { + varlink::Reply { + parameters: Some(p), + .. + } => match serde_json::from_value(p.clone()) { + Ok(v) => ErrorKind::InvalidUser(v), + Err(_) => ErrorKind::InvalidUser(None), + }, + _ => ErrorKind::InvalidUser(None), + }, + varlink::Reply { + error: Some(ref t), .. + } if t == "de.jmhoffmann.jwebmail.mail-storage.NotInitialized" => match e { + varlink::Reply { + parameters: Some(p), + .. + } => match serde_json::from_value(p.clone()) { + Ok(v) => ErrorKind::NotInitialized(v), + Err(_) => ErrorKind::NotInitialized(None), + }, + _ => ErrorKind::NotInitialized(None), + }, + _ => ErrorKind::VarlinkReply_Error, + } + } +} +pub trait VarlinkCallError: varlink::CallTrait { + fn reply_invalid_folder(&mut self, r#folder: String) -> varlink::Result<()> { + self.reply_struct(varlink::Reply::error( + "de.jmhoffmann.jwebmail.mail-storage.InvalidFolder", + Some( + serde_json::to_value(InvalidFolder_Args { r#folder }) + .map_err(varlink::map_context!())?, + ), + )) + } + fn reply_invalid_mid(&mut self, r#folder: String, r#mid: String) -> varlink::Result<()> { + self.reply_struct(varlink::Reply::error( + "de.jmhoffmann.jwebmail.mail-storage.InvalidMID", + Some( + serde_json::to_value(InvalidMID_Args { r#folder, r#mid }) + .map_err(varlink::map_context!())?, + ), + )) + } + fn reply_invalid_mailbox( + &mut self, + r#path: String, + r#not_a_mailbox: bool, + r#user_mismatch: bool, + ) -> varlink::Result<()> { + self.reply_struct(varlink::Reply::error( + "de.jmhoffmann.jwebmail.mail-storage.InvalidMailbox", + Some( + serde_json::to_value(InvalidMailbox_Args { + r#path, + r#not_a_mailbox, + r#user_mismatch, + }) + .map_err(varlink::map_context!())?, + ), + )) + } + fn reply_invalid_path_in_mail( + &mut self, + r#folder: String, + r#mid: String, + r#path: String, + ) -> varlink::Result<()> { + self.reply_struct(varlink::Reply::error( + "de.jmhoffmann.jwebmail.mail-storage.InvalidPathInMail", + Some( + serde_json::to_value(InvalidPathInMail_Args { + r#folder, + r#mid, + r#path, + }) + .map_err(varlink::map_context!())?, + ), + )) + } + fn reply_invalid_search_pattern(&mut self, r#pattern: String) -> varlink::Result<()> { + self.reply_struct(varlink::Reply::error( + "de.jmhoffmann.jwebmail.mail-storage.InvalidSearchPattern", + Some( + serde_json::to_value(InvalidSearchPattern_Args { r#pattern }) + .map_err(varlink::map_context!())?, + ), + )) + } + fn reply_invalid_user(&mut self, r#unix_user: String) -> varlink::Result<()> { + self.reply_struct(varlink::Reply::error( + "de.jmhoffmann.jwebmail.mail-storage.InvalidUser", + Some( + serde_json::to_value(InvalidUser_Args { r#unix_user }) + .map_err(varlink::map_context!())?, + ), + )) + } + fn reply_not_initialized(&mut self) -> varlink::Result<()> { + self.reply_struct(varlink::Reply::error( + "de.jmhoffmann.jwebmail.mail-storage.NotInitialized", + None, + )) + } +} +impl<'a> VarlinkCallError for varlink::Call<'a> {} +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] +pub struct r#ListMailHeader { + pub r#byte_size: i64, + pub r#unread: bool, + pub r#rec_date: String, + pub r#mid: String, + pub r#header: MailHeader, +} +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] +pub struct r#MIMEHeader_mime_type { + pub r#main_type: String, + pub r#sub_type: String, +} +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] +pub enum r#MIMEHeader_content_dispo { + r#none, + r#inline, + r#attachment, +} +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] +pub struct r#MIMEHeader { + pub r#mime_type: MIMEHeader_mime_type, + pub r#content_dispo: MIMEHeader_content_dispo, + pub r#file_name: Option<String>, +} +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] +pub struct r#MIMEPart { + pub r#mime_header: MIMEHeader, + pub r#body: MailBody, +} +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] +pub struct r#Mail { + pub r#head: MailHeader, + pub r#body: MailBody, +} +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] +pub struct r#MailAddr { + pub r#name: Option<String>, + pub r#address: String, +} +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] +pub struct r#MailBody { + pub r#discrete: Option<String>, + pub r#multipart: Option<Multipart>, + pub r#mail: Option<Box<Mail>>, +} +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] +pub struct r#MailHeader { + pub r#send_date: String, + pub r#written_from: Vec<MailAddr>, + pub r#sender: Option<MailAddr>, + pub r#reply_to: Vec<MailAddr>, + pub r#send_to: Vec<MailAddr>, + pub r#cc: Vec<MailAddr>, + pub r#bcc: Vec<MailAddr>, + pub r#subject: String, + pub r#comments: Vec<String>, + pub r#keywords: Vec<String>, + pub r#mime: MIMEHeader, +} +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] +pub struct r#Multipart { + pub r#preamble: Option<String>, + pub r#parts: Vec<MIMEPart>, + pub r#epilogue: Option<String>, +} +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] +pub enum r#Sort_direction { + r#asc, + r#desc, +} +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] +pub enum r#Sort_parameter { + r#date, + r#size, + r#sender, + r#subject, +} +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] +pub struct r#Sort { + pub r#direction: Sort_direction, + pub r#parameter: Sort_parameter, +} +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] +pub struct InvalidFolder_Args { + pub r#folder: String, +} +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] +pub struct InvalidMID_Args { + pub r#folder: String, + pub r#mid: String, +} +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] +pub struct InvalidMailbox_Args { + pub r#path: String, + pub r#not_a_mailbox: bool, + pub r#user_mismatch: bool, +} +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] +pub struct InvalidPathInMail_Args { + pub r#folder: String, + pub r#mid: String, + pub r#path: String, +} +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] +pub struct InvalidSearchPattern_Args { + pub r#pattern: String, +} +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] +pub struct InvalidUser_Args { + pub r#unix_user: String, +} +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] +pub struct NotInitialized_Args {} +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] +pub enum r#AddFolder_Reply_status { + r#created, + r#skiped, +} +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] +pub struct AddFolder_Reply { + pub r#status: AddFolder_Reply_status, +} +impl varlink::VarlinkReply for AddFolder_Reply {} +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] +pub struct AddFolder_Args { + pub r#name: String, +} +pub trait Call_AddFolder: VarlinkCallError { + fn reply(&mut self, r#status: AddFolder_Reply_status) -> varlink::Result<()> { + self.reply_struct(AddFolder_Reply { r#status }.into()) + } +} +impl<'a> Call_AddFolder for varlink::Call<'a> {} +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] +pub struct Folders_Reply { + pub r#folders: Vec<String>, +} +impl varlink::VarlinkReply for Folders_Reply {} +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] +pub struct Folders_Args {} +pub trait Call_Folders: VarlinkCallError { + fn reply(&mut self, r#folders: Vec<String>) -> varlink::Result<()> { + self.reply_struct(Folders_Reply { r#folders }.into()) + } +} +impl<'a> Call_Folders for varlink::Call<'a> {} +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] +pub struct Init_Reply {} +impl varlink::VarlinkReply for Init_Reply {} +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] +pub struct Init_Args { + pub r#unix_user: String, + pub r#mailbox_path: String, +} +pub trait Call_Init: VarlinkCallError { + fn reply(&mut self) -> varlink::Result<()> { + self.reply_struct(varlink::Reply::parameters(None)) + } +} +impl<'a> Call_Init for varlink::Call<'a> {} +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] +pub struct List_Reply { + pub r#mail_heads: Vec<ListMailHeader>, +} +impl varlink::VarlinkReply for List_Reply {} +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] +pub struct List_Args { + pub r#folder: String, + pub r#start: i64, + pub r#end: i64, + pub r#sort: Sort, +} +pub trait Call_List: VarlinkCallError { + fn reply(&mut self, r#mail_heads: Vec<ListMailHeader>) -> varlink::Result<()> { + self.reply_struct(List_Reply { r#mail_heads }.into()) + } +} +impl<'a> Call_List for varlink::Call<'a> {} +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] +pub struct Move_Reply {} +impl varlink::VarlinkReply for Move_Reply {} +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] +pub struct Move_Args { + pub r#mid: String, + pub r#from_folder: String, + pub r#to_folder: String, +} +pub trait Call_Move: VarlinkCallError { + fn reply(&mut self) -> varlink::Result<()> { + self.reply_struct(varlink::Reply::parameters(None)) + } +} +impl<'a> Call_Move for varlink::Call<'a> {} +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] +pub struct Raw_Reply { + pub r#header: MIMEHeader, + pub r#body: String, +} +impl varlink::VarlinkReply for Raw_Reply {} +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] +pub struct Raw_Args { + pub r#folder: String, + pub r#mid: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub r#path: Option<String>, +} +pub trait Call_Raw: VarlinkCallError { + fn reply(&mut self, r#header: MIMEHeader, r#body: String) -> varlink::Result<()> { + self.reply_struct(Raw_Reply { r#header, r#body }.into()) + } +} +impl<'a> Call_Raw for varlink::Call<'a> {} +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] +pub struct Remove_Reply {} +impl varlink::VarlinkReply for Remove_Reply {} +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] +pub struct Remove_Args { + pub r#folder: String, + pub r#mid: String, +} +pub trait Call_Remove: VarlinkCallError { + fn reply(&mut self) -> varlink::Result<()> { + self.reply_struct(varlink::Reply::parameters(None)) + } +} +impl<'a> Call_Remove for varlink::Call<'a> {} +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] +pub struct Search_Reply { + pub r#found: Vec<ListMailHeader>, +} +impl varlink::VarlinkReply for Search_Reply {} +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] +pub struct Search_Args { + pub r#folder: String, + pub r#pattern: String, +} +pub trait Call_Search: VarlinkCallError { + fn reply(&mut self, r#found: Vec<ListMailHeader>) -> varlink::Result<()> { + self.reply_struct(Search_Reply { r#found }.into()) + } +} +impl<'a> Call_Search for varlink::Call<'a> {} +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] +pub struct Show_Reply { + pub r#mail: Mail, +} +impl varlink::VarlinkReply for Show_Reply {} +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] +pub struct Show_Args { + pub r#folder: String, + pub r#mid: String, +} +pub trait Call_Show: VarlinkCallError { + fn reply(&mut self, r#mail: Mail) -> varlink::Result<()> { + self.reply_struct(Show_Reply { r#mail }.into()) + } +} +impl<'a> Call_Show for varlink::Call<'a> {} +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] +pub struct Stats_Reply { + pub r#mail_count: i64, + pub r#unread_count: i64, + pub r#byte_size: i64, +} +impl varlink::VarlinkReply for Stats_Reply {} +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] +pub struct Stats_Args { + pub r#folder: String, +} +pub trait Call_Stats: VarlinkCallError { + fn reply( + &mut self, + r#mail_count: i64, + r#unread_count: i64, + r#byte_size: i64, + ) -> varlink::Result<()> { + self.reply_struct( + Stats_Reply { + r#mail_count, + r#unread_count, + r#byte_size, + } + .into(), + ) + } +} +impl<'a> Call_Stats for varlink::Call<'a> {} +pub trait VarlinkInterface { + fn add_folder(&self, call: &mut dyn Call_AddFolder, r#name: String) -> varlink::Result<()>; + fn folders(&self, call: &mut dyn Call_Folders) -> varlink::Result<()>; + fn init( + &self, + call: &mut dyn Call_Init, + r#unix_user: String, + r#mailbox_path: String, + ) -> varlink::Result<()>; + fn list( + &self, + call: &mut dyn Call_List, + r#folder: String, + r#start: i64, + r#end: i64, + r#sort: Sort, + ) -> varlink::Result<()>; + fn r#move( + &self, + call: &mut dyn Call_Move, + r#mid: String, + r#from_folder: String, + r#to_folder: String, + ) -> varlink::Result<()>; + fn raw( + &self, + call: &mut dyn Call_Raw, + r#folder: String, + r#mid: String, + r#path: Option<String>, + ) -> varlink::Result<()>; + fn remove( + &self, + call: &mut dyn Call_Remove, + r#folder: String, + r#mid: String, + ) -> varlink::Result<()>; + fn search( + &self, + call: &mut dyn Call_Search, + r#folder: String, + r#pattern: String, + ) -> varlink::Result<()>; + fn show( + &self, + call: &mut dyn Call_Show, + r#folder: String, + r#mid: String, + ) -> varlink::Result<()>; + fn stats(&self, call: &mut dyn Call_Stats, r#folder: String) -> varlink::Result<()>; + fn call_upgraded( + &self, + _call: &mut varlink::Call, + _bufreader: &mut dyn BufRead, + ) -> varlink::Result<Vec<u8>> { + Ok(Vec::new()) + } +} +pub trait VarlinkClientInterface { + fn add_folder( + &mut self, + r#name: String, + ) -> varlink::MethodCall<AddFolder_Args, AddFolder_Reply, Error>; + fn folders(&mut self) -> varlink::MethodCall<Folders_Args, Folders_Reply, Error>; + fn init( + &mut self, + r#unix_user: String, + r#mailbox_path: String, + ) -> varlink::MethodCall<Init_Args, Init_Reply, Error>; + fn list( + &mut self, + r#folder: String, + r#start: i64, + r#end: i64, + r#sort: Sort, + ) -> varlink::MethodCall<List_Args, List_Reply, Error>; + fn r#move( + &mut self, + r#mid: String, + r#from_folder: String, + r#to_folder: String, + ) -> varlink::MethodCall<Move_Args, Move_Reply, Error>; + fn raw( + &mut self, + r#folder: String, + r#mid: String, + r#path: Option<String>, + ) -> varlink::MethodCall<Raw_Args, Raw_Reply, Error>; + fn remove( + &mut self, + r#folder: String, + r#mid: String, + ) -> varlink::MethodCall<Remove_Args, Remove_Reply, Error>; + fn search( + &mut self, + r#folder: String, + r#pattern: String, + ) -> varlink::MethodCall<Search_Args, Search_Reply, Error>; + fn show( + &mut self, + r#folder: String, + r#mid: String, + ) -> varlink::MethodCall<Show_Args, Show_Reply, Error>; + fn stats(&mut self, r#folder: String) -> varlink::MethodCall<Stats_Args, Stats_Reply, Error>; +} +#[allow(dead_code)] +pub struct VarlinkClient { + connection: Arc<RwLock<varlink::Connection>>, +} +impl VarlinkClient { + #[allow(dead_code)] + pub fn new(connection: Arc<RwLock<varlink::Connection>>) -> Self { + VarlinkClient { connection } + } +} +impl VarlinkClientInterface for VarlinkClient { + fn add_folder( + &mut self, + r#name: String, + ) -> varlink::MethodCall<AddFolder_Args, AddFolder_Reply, Error> { + varlink::MethodCall::<AddFolder_Args, AddFolder_Reply, Error>::new( + self.connection.clone(), + "de.jmhoffmann.jwebmail.mail-storage.AddFolder", + AddFolder_Args { r#name }, + ) + } + fn folders(&mut self) -> varlink::MethodCall<Folders_Args, Folders_Reply, Error> { + varlink::MethodCall::<Folders_Args, Folders_Reply, Error>::new( + self.connection.clone(), + "de.jmhoffmann.jwebmail.mail-storage.Folders", + Folders_Args {}, + ) + } + fn init( + &mut self, + r#unix_user: String, + r#mailbox_path: String, + ) -> varlink::MethodCall<Init_Args, Init_Reply, Error> { + varlink::MethodCall::<Init_Args, Init_Reply, Error>::new( + self.connection.clone(), + "de.jmhoffmann.jwebmail.mail-storage.Init", + Init_Args { + r#unix_user, + r#mailbox_path, + }, + ) + } + fn list( + &mut self, + r#folder: String, + r#start: i64, + r#end: i64, + r#sort: Sort, + ) -> varlink::MethodCall<List_Args, List_Reply, Error> { + varlink::MethodCall::<List_Args, List_Reply, Error>::new( + self.connection.clone(), + "de.jmhoffmann.jwebmail.mail-storage.List", + List_Args { + r#folder, + r#start, + r#end, + r#sort, + }, + ) + } + fn r#move( + &mut self, + r#mid: String, + r#from_folder: String, + r#to_folder: String, + ) -> varlink::MethodCall<Move_Args, Move_Reply, Error> { + varlink::MethodCall::<Move_Args, Move_Reply, Error>::new( + self.connection.clone(), + "de.jmhoffmann.jwebmail.mail-storage.Move", + Move_Args { + r#mid, + r#from_folder, + r#to_folder, + }, + ) + } + fn raw( + &mut self, + r#folder: String, + r#mid: String, + r#path: Option<String>, + ) -> varlink::MethodCall<Raw_Args, Raw_Reply, Error> { + varlink::MethodCall::<Raw_Args, Raw_Reply, Error>::new( + self.connection.clone(), + "de.jmhoffmann.jwebmail.mail-storage.Raw", + Raw_Args { + r#folder, + r#mid, + r#path, + }, + ) + } + fn remove( + &mut self, + r#folder: String, + r#mid: String, + ) -> varlink::MethodCall<Remove_Args, Remove_Reply, Error> { + varlink::MethodCall::<Remove_Args, Remove_Reply, Error>::new( + self.connection.clone(), + "de.jmhoffmann.jwebmail.mail-storage.Remove", + Remove_Args { r#folder, r#mid }, + ) + } + fn search( + &mut self, + r#folder: String, + r#pattern: String, + ) -> varlink::MethodCall<Search_Args, Search_Reply, Error> { + varlink::MethodCall::<Search_Args, Search_Reply, Error>::new( + self.connection.clone(), + "de.jmhoffmann.jwebmail.mail-storage.Search", + Search_Args { + r#folder, + r#pattern, + }, + ) + } + fn show( + &mut self, + r#folder: String, + r#mid: String, + ) -> varlink::MethodCall<Show_Args, Show_Reply, Error> { + varlink::MethodCall::<Show_Args, Show_Reply, Error>::new( + self.connection.clone(), + "de.jmhoffmann.jwebmail.mail-storage.Show", + Show_Args { r#folder, r#mid }, + ) + } + fn stats(&mut self, r#folder: String) -> varlink::MethodCall<Stats_Args, Stats_Reply, Error> { + varlink::MethodCall::<Stats_Args, Stats_Reply, Error>::new( + self.connection.clone(), + "de.jmhoffmann.jwebmail.mail-storage.Stats", + Stats_Args { r#folder }, + ) + } +} +#[allow(dead_code)] +pub struct VarlinkInterfaceProxy { + inner: Box<dyn VarlinkInterface + Send + Sync>, +} +#[allow(dead_code)] +pub fn new(inner: Box<dyn VarlinkInterface + Send + Sync>) -> VarlinkInterfaceProxy { + VarlinkInterfaceProxy { inner } +} +impl varlink::Interface for VarlinkInterfaceProxy { + fn get_description(&self) -> &'static str { + "interface de.jmhoffmann.jwebmail.mail-storage\n\n\ntype MIMEHeader (\n mime_type: (main_type: string, sub_type: string),\n content_dispo: (none, inline, attachment),\n file_name: ?string\n)\n\ntype MailAddr (\n name: ?string,\n address: string\n)\n\n# send_date is of ISO 8601 date-time format\ntype MailHeader (\n send_date: string,\n written_from: []MailAddr,\n sender: ?MailAddr,\n reply_to: []MailAddr,\n send_to: []MailAddr,\n cc: []MailAddr,\n bcc: []MailAddr,\n subject: string,\n comments: []string,\n keywords: []string,\n mime: MIMEHeader\n)\n\ntype ListMailHeader (\n byte_size: int,\n unread: bool,\n rec_date: string,\n mid: string,\n header: MailHeader\n)\n\ntype Multipart (\n preamble: ?string,\n parts: []MIMEPart,\n epilogue: ?string\n)\n\n# exactly one of these fileds must be present\ntype MailBody (\n discrete: ?string,\n multipart: ?Multipart,\n mail: ?Mail\n)\n\ntype Mail (\n head: MailHeader,\n body: MailBody\n)\n\ntype MIMEPart (\n mime_header: MIMEHeader,\n body: MailBody\n)\n\ntype Sort (\n direction: (asc, desc),\n parameter: (date, size, sender)\n)\n\n\nmethod Init(unix_user: string, mailbox_path: string) -> ()\nmethod List(folder: string, start: int, end: int, sort: Sort) -> (mail_heads: []ListMailHeader)\nmethod Stats(folder: string) -> (mail_count: int, unread_count: int, byte_size: int)\nmethod Show(folder: string, mid: string) -> (mail: Mail)\n# body is base64 encoded\nmethod Raw(folder: string, mid: string, path: ?string) -> (header: MIMEHeader, body: string)\nmethod Search(folder: string, pattern: string) -> (found: []ListMailHeader)\nmethod Folders() -> (folders: []string)\nmethod Move(mid: string, from_folder: string, to_folder: string) -> ()\nmethod Remove(folder: string, mid: string) -> ()\nmethod AddFolder(name: string) -> (status: (created, skiped))\n\n\nerror NotInitialized()\nerror InvalidFolder(folder: string)\nerror InvalidMID(folder: string, mid: string)\nerror InvalidPathInMail(folder: string, mid: string, path: string)\nerror InvalidSearchPattern(pattern: string)\nerror InvalidUser(unix_user: string)\nerror InvalidMailbox(path: string, not_a_mailbox: bool, user_mismatch: bool)\n" + } + fn get_name(&self) -> &'static str { + "de.jmhoffmann.jwebmail.mail-storage" + } + fn call_upgraded( + &self, + call: &mut varlink::Call, + bufreader: &mut dyn BufRead, + ) -> varlink::Result<Vec<u8>> { + self.inner.call_upgraded(call, bufreader) + } + fn call(&self, call: &mut varlink::Call) -> varlink::Result<()> { + let req = call.request.unwrap(); + match req.method.as_ref() { + "de.jmhoffmann.jwebmail.mail-storage.AddFolder" => { + if let Some(args) = req.parameters.clone() { + let args: AddFolder_Args = match serde_json::from_value(args) { + Ok(v) => v, + Err(e) => { + let es = format!("{}", e); + let _ = call.reply_invalid_parameter(es.clone()); + return Err(varlink::context!(varlink::ErrorKind::SerdeJsonDe(es))); + } + }; + self.inner + .add_folder(call as &mut dyn Call_AddFolder, args.r#name) + } else { + call.reply_invalid_parameter("parameters".into()) + } + } + "de.jmhoffmann.jwebmail.mail-storage.Folders" => { + self.inner.folders(call as &mut dyn Call_Folders) + } + "de.jmhoffmann.jwebmail.mail-storage.Init" => { + if let Some(args) = req.parameters.clone() { + let args: Init_Args = match serde_json::from_value(args) { + Ok(v) => v, + Err(e) => { + let es = format!("{}", e); + let _ = call.reply_invalid_parameter(es.clone()); + return Err(varlink::context!(varlink::ErrorKind::SerdeJsonDe(es))); + } + }; + self.inner.init( + call as &mut dyn Call_Init, + args.r#unix_user, + args.r#mailbox_path, + ) + } else { + call.reply_invalid_parameter("parameters".into()) + } + } + "de.jmhoffmann.jwebmail.mail-storage.List" => { + if let Some(args) = req.parameters.clone() { + let args: List_Args = match serde_json::from_value(args) { + Ok(v) => v, + Err(e) => { + let es = format!("{}", e); + let _ = call.reply_invalid_parameter(es.clone()); + return Err(varlink::context!(varlink::ErrorKind::SerdeJsonDe(es))); + } + }; + self.inner.list( + call as &mut dyn Call_List, + args.r#folder, + args.r#start, + args.r#end, + args.r#sort, + ) + } else { + call.reply_invalid_parameter("parameters".into()) + } + } + "de.jmhoffmann.jwebmail.mail-storage.Move" => { + if let Some(args) = req.parameters.clone() { + let args: Move_Args = match serde_json::from_value(args) { + Ok(v) => v, + Err(e) => { + let es = format!("{}", e); + let _ = call.reply_invalid_parameter(es.clone()); + return Err(varlink::context!(varlink::ErrorKind::SerdeJsonDe(es))); + } + }; + self.inner.r#move( + call as &mut dyn Call_Move, + args.r#mid, + args.r#from_folder, + args.r#to_folder, + ) + } else { + call.reply_invalid_parameter("parameters".into()) + } + } + "de.jmhoffmann.jwebmail.mail-storage.Raw" => { + if let Some(args) = req.parameters.clone() { + let args: Raw_Args = match serde_json::from_value(args) { + Ok(v) => v, + Err(e) => { + let es = format!("{}", e); + let _ = call.reply_invalid_parameter(es.clone()); + return Err(varlink::context!(varlink::ErrorKind::SerdeJsonDe(es))); + } + }; + self.inner.raw( + call as &mut dyn Call_Raw, + args.r#folder, + args.r#mid, + args.r#path, + ) + } else { + call.reply_invalid_parameter("parameters".into()) + } + } + "de.jmhoffmann.jwebmail.mail-storage.Remove" => { + if let Some(args) = req.parameters.clone() { + let args: Remove_Args = match serde_json::from_value(args) { + Ok(v) => v, + Err(e) => { + let es = format!("{}", e); + let _ = call.reply_invalid_parameter(es.clone()); + return Err(varlink::context!(varlink::ErrorKind::SerdeJsonDe(es))); + } + }; + self.inner + .remove(call as &mut dyn Call_Remove, args.r#folder, args.r#mid) + } else { + call.reply_invalid_parameter("parameters".into()) + } + } + "de.jmhoffmann.jwebmail.mail-storage.Search" => { + if let Some(args) = req.parameters.clone() { + let args: Search_Args = match serde_json::from_value(args) { + Ok(v) => v, + Err(e) => { + let es = format!("{}", e); + let _ = call.reply_invalid_parameter(es.clone()); + return Err(varlink::context!(varlink::ErrorKind::SerdeJsonDe(es))); + } + }; + self.inner + .search(call as &mut dyn Call_Search, args.r#folder, args.r#pattern) + } else { + call.reply_invalid_parameter("parameters".into()) + } + } + "de.jmhoffmann.jwebmail.mail-storage.Show" => { + if let Some(args) = req.parameters.clone() { + let args: Show_Args = match serde_json::from_value(args) { + Ok(v) => v, + Err(e) => { + let es = format!("{}", e); + let _ = call.reply_invalid_parameter(es.clone()); + return Err(varlink::context!(varlink::ErrorKind::SerdeJsonDe(es))); + } + }; + self.inner + .show(call as &mut dyn Call_Show, args.r#folder, args.r#mid) + } else { + call.reply_invalid_parameter("parameters".into()) + } + } + "de.jmhoffmann.jwebmail.mail-storage.Stats" => { + if let Some(args) = req.parameters.clone() { + let args: Stats_Args = match serde_json::from_value(args) { + Ok(v) => v, + Err(e) => { + let es = format!("{}", e); + let _ = call.reply_invalid_parameter(es.clone()); + return Err(varlink::context!(varlink::ErrorKind::SerdeJsonDe(es))); + } + }; + self.inner.stats(call as &mut dyn Call_Stats, args.r#folder) + } else { + call.reply_invalid_parameter("parameters".into()) + } + } + m => call.reply_method_not_found(String::from(m)), + } + } +} diff --git a/src/error.rs b/src/error.rs deleted file mode 100644 index 0c0167f..0000000 --- a/src/error.rs +++ /dev/null @@ -1,47 +0,0 @@ -use maildir::MailEntryError; -use mailparse::MailParseError; -use protobuf::Error as PBError; - -pub type Result<T> = std::result::Result<T, Error>; - -#[derive(Debug)] -pub enum Error { - IoError(std::io::Error), - MailEntryError(MailEntryError), - SortOrder(String), - Setuid(String), - Protobuf(PBError), - PathError { msg: String, path: String }, -} - -impl std::fmt::Display for Error { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> { - write!(f, "{:?}", self) - } -} - -impl std::error::Error for Error {} - -impl From<std::io::Error> for Error { - fn from(io_err: std::io::Error) -> Self { - Error::IoError(io_err) - } -} - -impl From<MailEntryError> for Error { - fn from(me_err: MailEntryError) -> Self { - Error::MailEntryError(me_err) - } -} - -impl From<MailParseError> for Error { - fn from(mp_err: MailParseError) -> Self { - Error::MailEntryError(MailEntryError::ParseError(mp_err)) - } -} - -impl From<PBError> for Error { - fn from(pb_err: PBError) -> Self { - Error::Protobuf(pb_err) - } -} diff --git a/src/main.rs b/src/main.rs index 5598b30..a8e33b2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,46 +1,19 @@ -use std::ffi::{CStr, CString}; -use std::io::{stdin, stdout, Read, Write}; +use std::io::{BufRead, BufReader}; +use std::net::Shutdown; +use std::os::fd::FromRawFd; +use std::os::unix::net::UnixStream; +use std::str::FromStr; -use clap::Parser as _; +use varlink::{ConnectionHandler, ErrorKind, Stream, VarlinkService}; -mod arguments; mod cmd; -mod error; -mod pb3; +#[path = "de_jmhoffmann_jwebmail_mail-storage.rs"] +mod de_jmhoffmann_jwebmail_mailstorage; mod rfc822; -use arguments::{Arguments, Mode}; -use error::{Error, Result}; +use cmd::MailStorage; -fn switch_to_user(sys_user: &str) -> Result<()> { - unsafe { - *libc::__errno_location() = 0; - } - let c_sys_user = - CString::new(sys_user).map_err(|e| Error::Setuid(format!("nul char in user, {}", e)))?; - let user_info: *const libc::passwd = unsafe { libc::getpwnam(c_sys_user.as_ptr()) }; - let err = unsafe { *libc::__errno_location() }; - if err != 0 { - return Err(Error::Setuid(format!( - "error calling getpwnam {:?}", - unsafe { libc::strerror(err) } - ))); - }; - if user_info.is_null() { - return Err(Error::Setuid(format!("user {:?} does not exist", sys_user))); - }; - let rc = unsafe { libc::setuid((*user_info).pw_uid) }; - if rc != 0 { - let err = unsafe { *libc::__errno_location() }; - return Err(Error::Setuid(format!( - "error calling setuid {:?}", - unsafe { CStr::from_ptr(libc::strerror(err)) } - ))); - } - Ok(()) -} - -fn main() -> Result<()> { +fn main() { simplelog::TermLogger::init( simplelog::LevelFilter::Info, simplelog::Config::default(), @@ -49,29 +22,66 @@ fn main() -> Result<()> { ) .unwrap(); - let args = Arguments::parse(); - - switch_to_user(&args.sys_user)?; + let mail_storage = MailStorage::default(); - let path = args.maildir_path.join(args.mail_user); + let myinterface = de_jmhoffmann_jwebmail_mailstorage::new(Box::new(mail_storage)); + let service = VarlinkService::new( + "de.jmhoffmann.jwebmail.mail-storage.varlink", + "jwebmails storage service", + "1.0.1", + "https://fehcom.de/cgit/jwebmail2/", + vec![Box::new(myinterface)], + ); - stdout().write_all(b"OPEN\n")?; - stdout().flush()?; - let mut req = Vec::with_capacity(2048); - stdin().read_to_end(&mut req)?; - - let res = match args.mode { - Mode::Read => cmd::read(path, &req), - Mode::Raw => cmd::raw(path, &req), - Mode::List => cmd::list(path, &req), - Mode::Folders => cmd::folders(path, &req), - Mode::Count => cmd::count(path, &req), - //Mode::Search => cmd::search(&path, &req), - Mode::Move => cmd::move_mail(path, &req), - Mode::Remove => cmd::remove(path, &req), - Mode::AddFolder => cmd::add_folder(path, &req), - _ => todo!(), - }?; + if let Ok(listen_fds) = std::env::var("LISTEN_FDS") { + let lfds = u8::from_str(&listen_fds).expect("env variable `LISTEN_FDS` must be an integer"); + if lfds < 1 { + log::error!("No file descriptor to receive commands from!"); + return; + } else if lfds == 1 { + let uds = unsafe { UnixStream::from_raw_fd(3) }; + accept_con(&service, uds); + } else { + let listen_fdnames = std::env::var("LISTEN_FDNAMES") + .expect("when multiple `LISTEN_FDS` are available `LISTEN_FDNAMES` must be set"); + listen_fdnames + .split(':') + .enumerate() + .filter(|(_, name)| *name == "varlink") + .for_each(|(i, _)| { + accept_con(&service, unsafe { UnixStream::from_raw_fd(3 + i as i32) }) + }); + } + } else { + log::error!("No file descriptor to receive commands from!"); + } +} - stdout().write_all(&res).map_err(|e| e.into()) +/// mostly a copy from varlink::listen +fn accept_con(service: &VarlinkService, mut uds: UnixStream) { + let (r, mut w) = uds.split().unwrap(); + let mut br = BufReader::new(r); + let mut iface: Option<String> = None; + loop { + match service.handle(&mut br, &mut w, iface.clone()) { + Ok((_, i)) => { + iface = i; + match br.fill_buf() { + Err(_) => break, + Ok(buf) if buf.is_empty() => break, + _ => {} + } + } + Err(err) => { + match err.kind() { + ErrorKind::ConnectionClosed | ErrorKind::SerdeJsonDe(_) => {} + _ => { + eprintln!("Worker error: {:?}", err); + } + } + let _ = uds.shutdown(Shutdown::Both); + break; + } + } + } } diff --git a/src/pb3/mod.rs b/src/pb3/mod.rs deleted file mode 100644 index 55a7aae..0000000 --- a/src/pb3/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -// @generated - -pub mod jwebmail; diff --git a/src/rfc822.rs b/src/rfc822.rs index 658b167..a8fa5c3 100644 --- a/src/rfc822.rs +++ b/src/rfc822.rs @@ -1,14 +1,15 @@ use chrono::DateTime; -use mailparse::{addrparse_header, body::Body, dateparse, DispositionType, ParsedMail}; - -use crate::error::{Error, Result}; -use crate::pb3::jwebmail::mimeheader::ContentDisposition::*; -use crate::pb3::jwebmail::{ - mail_body::Multipart, mail_header::MailAddr, ListMailHeader, MIMEHeader, MIMEPart, Mail, - MailBody, MailHeader, +use maildir::MailEntryError; +use mailparse::{ + addrparse_header, body::Body, dateparse, DispositionType, MailParseError, ParsedMail, }; -fn parse_mail_addrs(inp: &mailparse::MailHeader) -> Result<Vec<MailAddr>> { +use crate::de_jmhoffmann_jwebmail_mailstorage::MIMEHeader_content_dispo as CD; +use crate::de_jmhoffmann_jwebmail_mailstorage::*; + +fn parse_mail_addrs( + inp: &mailparse::MailHeader, +) -> std::result::Result<Vec<MailAddr>, MailParseError> { let mut mal = addrparse_header(inp)?; Ok(mal @@ -17,17 +18,16 @@ fn parse_mail_addrs(inp: &mailparse::MailHeader) -> Result<Vec<MailAddr>> { mailparse::MailAddr::Group(mut g) => g .addrs .drain(..) - .map(|s| { - let mut r = MailAddr::new(); - r.name = Some(s.display_name.unwrap_or_default()); - r.address = s.addr; - r + .map(|s| MailAddr { + name: Some(s.display_name.unwrap_or_default()), + address: s.addr, }) .collect(), mailparse::MailAddr::Single(s) => { - let mut addr = MailAddr::new(); - addr.name = Some(s.display_name.unwrap_or_default()); - addr.address = s.addr; + let addr = MailAddr { + name: Some(s.display_name.unwrap_or_default()), + address: s.addr, + }; vec![addr] } }) @@ -44,43 +44,44 @@ fn get_received(me: &mut maildir::MailEntry) -> i64 { }) } -pub fn me_to_lmh(mut me: maildir::MailEntry) -> Result<ListMailHeader> { - let mut lmh = ListMailHeader::new(); - lmh.byte_size = me.path().metadata()?.len(); - lmh.unread = !me.is_seen(); - lmh.rec_date = DateTime::from_timestamp(get_received(&mut me), 0) - .unwrap() - .to_rfc3339(); - lmh.mid = me.id().to_owned(); - lmh.header = Some(parse_mail_header(&me.parsed()?)?).into(); +pub fn me_to_lmh( + mut me: maildir::MailEntry, +) -> std::result::Result<ListMailHeader, MailEntryError> { + let lmh = ListMailHeader { + byte_size: me.path().metadata()?.len() as i64, + unread: !me.is_seen(), + rec_date: DateTime::from_timestamp(get_received(&mut me), 0) + .unwrap() + .to_rfc3339(), + mid: me.id().to_owned(), + header: parse_mail_header(&me.parsed()?)?, + }; Ok(lmh) } -pub fn parse_mail_content(v: &ParsedMail) -> Result<MIMEHeader> { - let mut c = MIMEHeader::new(); - - { - let mut val = v.ctype.mimetype.clone(); - if let Some(i) = val.find(';') { - val.truncate(i); - } - let j = val.find('/').unwrap(); - c.subtype = val.split_off(j + 1); - val.pop(); - c.maintype = val; +pub fn parse_mail_content(v: &ParsedMail) -> MIMEHeader { + let mut val = v.ctype.mimetype.clone(); + if let Some(i) = val.find(';') { + val.truncate(i); } + let j = val.find('/').unwrap(); + let subtype = val.split_off(j + 1); + val.pop(); + let maintype = val; + + let mut file_name = None; - match v.get_content_disposition().disposition { - DispositionType::Inline => c.contentdispo = CONTENT_DISPOSITION_INLINE.into(), + let cd = match v.get_content_disposition().disposition { + DispositionType::Inline => CD::inline, DispositionType::Attachment => { - c.contentdispo = CONTENT_DISPOSITION_ATTACHMENT.into(); if let Some(fname) = v.get_content_disposition().params.remove("filename") { - c.file_name = Some(fname); + file_name = Some(fname); } + CD::attachment } - _ => {} - } + _ => CD::none, + }; for h in &v.headers { let mut key = h.get_key(); @@ -89,29 +90,44 @@ pub fn parse_mail_content(v: &ParsedMail) -> Result<MIMEHeader> { key.make_ascii_lowercase(); if key == "filename" { - c.file_name = Some(val); + file_name = Some(val); } } - Ok(c) + MIMEHeader { + mime_type: MIMEHeader_mime_type { + main_type: maintype, + sub_type: subtype, + }, + content_dispo: cd, + file_name, + } } -fn parse_mail_header(pm: &ParsedMail) -> Result<MailHeader> { +fn parse_mail_header(pm: &ParsedMail) -> std::result::Result<MailHeader, MailParseError> { let v = &pm.headers; - let mut mh = MailHeader::new(); - let mut mimeh = MIMEHeader::new(); - - { - let mut val = pm.ctype.mimetype.clone(); - if let Some(i) = val.find(';') { - val.truncate(i); - } - let j = val.find('/').unwrap(); - mimeh.subtype = val.split_off(j + 1); - val.pop(); - mimeh.maintype = val; + let mut val = pm.ctype.mimetype.clone(); + if let Some(i) = val.find(';') { + val.truncate(i); } + let j = val.find('/').unwrap(); + let sub_type = val.split_off(j + 1); + val.pop(); + let main_type = val; + + let mut file_name = None; + let mut content_dispo = CD::none; + let mut cc = vec![]; + let mut bcc = vec![]; + let mut comments = vec![]; + let mut keywords = vec![]; + let mut reply_to = vec![]; + let mut written_from = vec![]; + let mut send_to = vec![]; + let mut subject = String::new(); + let mut sender = None; + let mut send_date = String::new(); let mut key = String::new(); @@ -123,102 +139,130 @@ fn parse_mail_header(pm: &ParsedMail) -> Result<MailHeader> { match key.as_str() { "date" => { - mh.send_date = DateTime::from_timestamp(dateparse(&val)?, 0) + send_date = DateTime::from_timestamp(dateparse(&val)?, 0) .unwrap() .to_rfc3339() } "from" => { - if !mh.written_from.is_empty() { - return Err(Error::SortOrder("from already set".into())); + if !written_from.is_empty() { + const FROM_MULTIPLE_TIMES_ERROR: &str = "from already set"; + return Err(MailParseError::Generic(FROM_MULTIPLE_TIMES_ERROR)); } - mh.written_from = parse_mail_addrs(y)? + written_from = parse_mail_addrs(y)? } - "sender" => mh.sender = parse_mail_addrs(y)?.drain(0..1).next().into(), - "reply-to" => mh.reply_to = parse_mail_addrs(y)?, - "to" => mh.send_to = parse_mail_addrs(y)?, - "cc" => mh.cc = parse_mail_addrs(y)?, - "bcc" => mh.bcc = parse_mail_addrs(y)?, + "sender" => sender = parse_mail_addrs(y)?.drain(0..1).next(), + "reply-to" => reply_to = parse_mail_addrs(y)?, + "to" => send_to = parse_mail_addrs(y)?, + "cc" => cc = parse_mail_addrs(y)?, + "bcc" => bcc = parse_mail_addrs(y)?, "subject" => { - mh.subject = val; + subject = val; } "comments" => { - mh.comments.push(val); + comments.push(val); } "keywords" => { - mh.keywords.push(val); + keywords.push(val); } "mime-version" => { strip_comments(&mut val); if val.trim() != "1.0" { - return Err(Error::MailEntryError(maildir::MailEntryError::DateError( - "unknown mime version", - ))); + const UNKNOWN_MIME_VERSION_ERROR: &str = "unknown mime version"; + return Err(MailParseError::Generic(UNKNOWN_MIME_VERSION_ERROR)); } } "content-disposition" => { let cd = val.to_ascii_lowercase(); match cd.as_ref() { - "inline" => mimeh.contentdispo = CONTENT_DISPOSITION_INLINE.into(), - "attachment" => mimeh.contentdispo = CONTENT_DISPOSITION_ATTACHMENT.into(), + "inline" => content_dispo = CD::inline, + "attachment" => content_dispo = CD::attachment, _ => {} }; } "filename" => { - mimeh.file_name = Some(val); + file_name = Some(val); } _ => {} }; key.clear(); } - mh.mime = Some(mimeh).into(); - Ok(mh) + Ok(MailHeader { + written_from, + sender, + send_to, + cc, + bcc, + reply_to, + subject, + send_date, + keywords, + comments, + mime: MIMEHeader { + mime_type: MIMEHeader_mime_type { + main_type, + sub_type, + }, + content_dispo, + file_name, + }, + }) } -fn parse_mail_body(pm: &ParsedMail) -> Result<MailBody> { +fn parse_mail_body(pm: &ParsedMail) -> std::result::Result<MailBody, MailParseError> { let body = if pm.ctype.mimetype.starts_with("message/") { - let mut mb = MailBody::new(); - mb.set_mail(parsed_mail_to_mail(mailparse::parse_mail( - pm.get_body()?.as_ref(), - )?)?); - mb + MailBody { + mail: Some(Box::new(parsed_mail_to_mail(mailparse::parse_mail( + pm.get_body()?.as_ref(), + )?)?)), + discrete: None, + multipart: None, + } } else if pm.subparts.is_empty() && pm.ctype.mimetype.starts_with("text/") { - let mut mb = MailBody::new(); - mb.set_discrete(pm.get_body()?); - mb + MailBody { + discrete: Some(pm.get_body()?), + multipart: None, + mail: None, + } } else if pm.subparts.is_empty() { - let b = match pm.get_body_encoded() { - Body::Base64(eb) => { - let db = eb.get_raw(); - if db.len() < 512 * 1024 { - String::from_utf8_lossy(db).into_owned() - } else { - String::new() + MailBody { + discrete: Some(match pm.get_body_encoded() { + Body::Base64(eb) => { + let db = eb.get_raw(); + if db.len() < 512 * 1024 { + String::from_utf8_lossy(db).into_owned() + } else { + String::new() + } } - } - Body::SevenBit(eb) => eb.get_as_string()?, - Body::QuotedPrintable(eb) => eb.get_decoded_as_string()?, - _ => todo!(), - }; - let mut mb = MailBody::new(); - mb.set_discrete(b); - mb + Body::SevenBit(eb) => eb.get_as_string()?, + Body::QuotedPrintable(eb) => eb.get_decoded_as_string()?, + _ => todo!(), + }), + multipart: None, + mail: None, + } } else { - let mut mb = MailBody::new(); - let mut mp = Multipart::new(); - mp.parts = pm - .subparts - .iter() - .map(|part| -> Result<MIMEPart> { - let mut mp = MIMEPart::new(); - mp.mime_header = Some(parse_mail_content(part)?).into(); - mp.body = Some(parse_mail_body(part)?).into(); - Ok(mp) - }) - .filter_map(|p| p.ok()) - .collect(); - mb.set_multipart(mp); - mb + let mp = Multipart { + parts: pm + .subparts + .iter() + .map(|part| -> std::result::Result<MIMEPart, MailParseError> { + Ok(MIMEPart { + mime_header: parse_mail_content(part), + body: parse_mail_body(part)?, + }) + }) + .filter_map(|p| p.ok()) + .collect(), + preamble: None, + epilogue: None, + }; + MailBody { + multipart: Some(mp), + discrete: None, + mail: None, + } }; Ok(body) } @@ -316,86 +360,9 @@ fn strip_comments(s: &mut String) { } } -pub fn parsed_mail_to_mail(pm: ParsedMail) -> Result<Mail> { - let mut m = Mail::new(); - m.head = Some(parse_mail_header(&pm)?).into(); - m.body = Some(parse_mail_body(&pm)?).into(); - - Ok(m) -} - -/* -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn comment() { - let mut x = r#"(this is ((some) text)) a "some text with (comment \" in) quotes)(" (example) \( included) (xx)b()"#.to_owned(); - strip_comments(&mut x); - assert_eq!( - &x, - r#" a "some text with (comment \" in) quotes)(" \( included) b"# - ); - } - - #[test] - fn unclosed_comment() { - let mut x = "(this is (some text) example b".to_owned(); - strip_comments(&mut x); - assert_eq!(&x, "(this is example b"); - } - - #[test] - fn find_first_pair() { - let mut r = find_pair(0, "abc def"); - assert_eq!(r, None); - - r = find_pair(0, "abc ( def"); - assert_eq!(r, None); - - r = find_pair(0, "abc ) def"); - assert_eq!(r, None); - - let s = "(abc) def"; - if let Some(i) = find_pair(0, s) { - assert_eq!(i, 0..5); - assert_eq!(&s[i], "(abc)"); - } else { - assert!(false, "Got None expected Some!"); - } - - let s = "abc (def) ghi"; - if let Some(i) = find_pair(0, s) { - assert_eq!(i, 4..9); - assert_eq!(&s[i], "(def)"); - } else { - assert!(false, "Got None expected Some!"); - } - - let s = "(abc (def) ghi"; - if let Some(i) = find_pair(0, s) { - assert_eq!(i, 5..10); - assert_eq!(&s[i], "(def)"); - } else { - assert!(false, "Got None expected Some!"); - } - - let s = "abc ((def) ghi)"; - if let Some(i) = find_pair(0, s) { - assert_eq!(i, 4..15); - assert_eq!(&s[i], "((def) ghi)"); - } else { - assert!(false, "Got None expected Some!"); - } - - let s = r#" a "some text with (comment \" in) quotes)(" (example)"#; - if let Some(i) = find_pair(0, s) { - assert_eq!(i, 45..54); - assert_eq!(&s[i], "(example)"); - } else { - assert!(false, "Got None expected Some!"); - } - } +pub fn parsed_mail_to_mail(pm: ParsedMail) -> std::result::Result<Mail, MailParseError> { + Ok(Mail { + head: parse_mail_header(&pm)?, + body: parse_mail_body(&pm)?, + }) } -*/ |