meli/melib/src/backends/mbox.rs

726 lines
27 KiB
Rust
Raw Normal View History

/*
* meli - mailbox module.
*
* Copyright 2017 Manos Pitsidianakis
*
* This file is part of meli.
*
* meli is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* meli is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with meli. If not, see <http://www.gnu.org/licenses/>.
*/
2018-08-07 15:01:15 +03:00
/*!
* https://wiki2.dovecot.org/MailboxFormat/mbox
*/
use crate::async_workers::{Async, AsyncBuilder, AsyncStatus, WorkContext};
2019-07-11 11:44:27 +03:00
use crate::backends::BackendOp;
use crate::backends::MailboxHash;
2019-07-11 11:44:27 +03:00
use crate::backends::{
BackendMailbox, MailBackend, Mailbox, MailboxPermissions, RefreshEvent, RefreshEventConsumer,
RefreshEventKind, SpecialUsageMailbox,
2019-07-11 11:44:27 +03:00
};
use crate::conf::AccountSettings;
use crate::email::parser::BytesExt;
use crate::email::*;
use crate::error::{MeliError, Result};
use crate::get_path_hash;
use crate::shellexpand::ShellExpandTrait;
2019-07-11 11:44:27 +03:00
use libc;
use memmap::{Mmap, Protection};
use nom::{self, error::ErrorKind, IResult};
2019-07-11 11:44:27 +03:00
extern crate notify;
use self::notify::{watcher, DebouncedEvent, RecursiveMode, Watcher};
use std::collections::hash_map::{DefaultHasher, HashMap};
2019-07-11 11:44:27 +03:00
use std::fs::File;
use std::hash::Hasher;
2019-07-11 11:44:27 +03:00
use std::io::BufReader;
use std::io::Read;
use std::os::unix::io::AsRawFd;
use std::path::{Path, PathBuf};
use std::sync::mpsc::channel;
use std::sync::{Arc, Mutex, RwLock};
2019-07-11 11:44:27 +03:00
type Offset = usize;
type Length = usize;
2019-07-11 11:44:27 +03:00
const F_OFD_SETLKW: libc::c_int = 38;
// Open file description locking
// # man fcntl
fn get_rw_lock_blocking(f: &File) {
let fd: libc::c_int = f.as_raw_fd();
let mut flock: libc::flock = libc::flock {
l_type: libc::F_WRLCK as libc::c_short,
l_whence: libc::SEEK_SET as libc::c_short,
l_start: 0,
l_len: 0, /* "Specifying 0 for l_len has the special meaning: lock all bytes starting at the location
specified by l_whence and l_start through to the end of file, no matter how large the file grows." */
l_pid: 0, /* "By contrast with traditional record locks, the l_pid field of that structure must be set to zero when using the commands described below." */
};
let ptr: *mut libc::flock = &mut flock;
let ret_val = unsafe { libc::fcntl(fd, F_OFD_SETLKW, ptr as *mut libc::c_void) };
debug!(&ret_val);
assert!(-1 != ret_val);
}
#[derive(Debug)]
struct MboxMailbox {
hash: MailboxHash,
2019-07-11 11:44:27 +03:00
name: String,
path: PathBuf,
content: Vec<u8>,
children: Vec<MailboxHash>,
parent: Option<MailboxHash>,
usage: Arc<RwLock<SpecialUsageMailbox>>,
is_subscribed: bool,
permissions: MailboxPermissions,
pub total: Arc<Mutex<usize>>,
pub unseen: Arc<Mutex<usize>>,
2019-07-11 11:44:27 +03:00
}
impl BackendMailbox for MboxMailbox {
fn hash(&self) -> MailboxHash {
2019-07-11 11:44:27 +03:00
self.hash
}
fn name(&self) -> &str {
self.name.as_str()
}
fn path(&self) -> &str {
/* We know it's valid UTF-8 because we supplied it */
self.path.to_str().unwrap()
}
fn change_name(&mut self, s: &str) {
self.name = s.to_string();
}
fn clone(&self) -> Mailbox {
Box::new(MboxMailbox {
2019-07-11 11:44:27 +03:00
hash: self.hash,
name: self.name.clone(),
path: self.path.clone(),
content: self.content.clone(),
children: self.children.clone(),
usage: self.usage.clone(),
is_subscribed: self.is_subscribed,
2019-07-11 11:44:27 +03:00
parent: self.parent,
permissions: self.permissions,
unseen: self.unseen.clone(),
total: self.total.clone(),
2019-07-11 11:44:27 +03:00
})
}
fn children(&self) -> &[MailboxHash] {
2019-07-11 11:44:27 +03:00
&self.children
}
fn parent(&self) -> Option<MailboxHash> {
2019-07-11 11:44:27 +03:00
self.parent
}
fn special_usage(&self) -> SpecialUsageMailbox {
*self.usage.read().unwrap()
}
fn permissions(&self) -> MailboxPermissions {
self.permissions
}
fn is_subscribed(&self) -> bool {
self.is_subscribed
}
fn set_is_subscribed(&mut self, new_val: bool) -> Result<()> {
self.is_subscribed = new_val;
Ok(())
}
fn set_special_usage(&mut self, new_val: SpecialUsageMailbox) -> Result<()> {
*self.usage.write()? = new_val;
Ok(())
}
fn count(&self) -> Result<(usize, usize)> {
Ok((*self.unseen.lock()?, *self.total.lock()?))
}
2019-07-11 11:44:27 +03:00
}
/// `BackendOp` implementor for Mbox
2019-07-11 11:44:27 +03:00
#[derive(Debug, Default)]
pub struct MboxOp {
hash: EnvelopeHash,
path: PathBuf,
offset: Offset,
length: Length,
slice: Option<Mmap>,
}
impl MboxOp {
2019-07-11 11:44:27 +03:00
pub fn new(hash: EnvelopeHash, path: &Path, offset: Offset, length: Length) -> Self {
MboxOp {
hash,
path: path.to_path_buf(),
slice: None,
offset,
length,
}
}
}
impl BackendOp for MboxOp {
fn description(&self) -> String {
2019-07-11 11:44:27 +03:00
String::new()
}
2019-07-11 11:44:27 +03:00
fn as_bytes(&mut self) -> Result<&[u8]> {
2019-07-11 11:44:27 +03:00
if self.slice.is_none() {
self.slice = Some(Mmap::open_path(&self.path, Protection::Read)?);
}
/* Unwrap is safe since we use ? above. */
Ok(unsafe {
&self.slice.as_ref().unwrap().as_slice()[self.offset..self.offset + self.length]
})
}
2019-07-11 11:44:27 +03:00
fn fetch_flags(&self) -> Flag {
2019-07-11 11:44:27 +03:00
let mut flags = Flag::empty();
let file = match std::fs::OpenOptions::new()
.read(true)
.write(true)
.open(&self.path)
{
Ok(f) => f,
Err(e) => {
debug!(e);
return flags;
}
};
get_rw_lock_blocking(&file);
let mut buf_reader = BufReader::new(file);
let mut contents = Vec::new();
if let Err(e) = buf_reader.read_to_end(&mut contents) {
debug!(e);
return flags;
};
if let Ok((_, headers)) = parser::headers::headers_raw(contents.as_slice()) {
2019-07-11 11:44:27 +03:00
if let Some(start) = headers.find(b"Status:") {
if let Some(end) = headers[start..].find(b"\n") {
let start = start + b"Status:".len();
let status = headers[start..start + end].trim();
if status.contains(&b'F') {
flags.set(Flag::FLAGGED, true);
}
if status.contains(&b'A') {
flags.set(Flag::REPLIED, true);
}
if status.contains(&b'R') {
flags.set(Flag::SEEN, true);
}
if status.contains(&b'D') {
flags.set(Flag::TRASHED, true);
}
if status.contains(&b'T') {
flags.set(Flag::DRAFT, true);
}
}
}
if let Some(start) = headers.find(b"X-Status:") {
let start = start + b"X-Status:".len();
if let Some(end) = headers[start..].find(b"\n") {
let status = headers[start..start + end].trim();
if status.contains(&b'F') {
flags.set(Flag::FLAGGED, true);
}
if status.contains(&b'A') {
flags.set(Flag::REPLIED, true);
}
if status.contains(&b'R') {
flags.set(Flag::SEEN, true);
}
if status.contains(&b'D') {
flags.set(Flag::TRASHED, true);
}
if status.contains(&b'T') {
flags.set(Flag::DRAFT, true);
}
}
}
}
flags
}
2019-07-11 11:44:27 +03:00
fn set_flag(&mut self, _envelope: &mut Envelope, _flag: Flag, _value: bool) -> Result<()> {
2019-07-11 11:44:27 +03:00
Ok(())
}
fn set_tag(&mut self, _envelope: &mut Envelope, _tag: String, _value: bool) -> Result<()> {
Err(MeliError::new("mbox doesn't support tags."))
}
}
2019-07-11 11:44:27 +03:00
pub fn mbox_parse(
2020-05-10 21:14:49 +03:00
index: Arc<Mutex<HashMap<EnvelopeHash, (Offset, Length)>>>,
2019-07-11 11:44:27 +03:00
input: &[u8],
file_offset: usize,
) -> IResult<&[u8], Vec<Envelope>> {
if input.is_empty() {
return Err(nom::Err::Error((input, ErrorKind::Tag)));
2019-07-11 11:44:27 +03:00
}
let mut input = input;
let mut offset = 0;
let mut index = index.lock().unwrap();
let mut envelopes = Vec::with_capacity(32);
while !input.is_empty() {
let next_offset: Option<(usize, usize)> = input
.find(b"\n\nFrom ")
.and_then(|end| input.find(b"\n").and_then(|start| Some((start + 1, end))));
if let Some((start, len)) = next_offset {
match Envelope::from_bytes(&input[start..len], None) {
2019-07-11 11:44:27 +03:00
Ok(mut env) => {
let mut flags = Flag::empty();
if env.other_headers().contains_key("Status") {
if env.other_headers()["Status"].contains("F") {
flags.set(Flag::FLAGGED, true);
}
if env.other_headers()["Status"].contains("A") {
flags.set(Flag::REPLIED, true);
}
if env.other_headers()["Status"].contains("R") {
flags.set(Flag::SEEN, true);
}
if env.other_headers()["Status"].contains("D") {
flags.set(Flag::TRASHED, true);
}
}
if env.other_headers().contains_key("X-Status") {
if env.other_headers()["X-Status"].contains("F") {
flags.set(Flag::FLAGGED, true);
}
if env.other_headers()["X-Status"].contains("A") {
flags.set(Flag::REPLIED, true);
}
if env.other_headers()["X-Status"].contains("R") {
flags.set(Flag::SEEN, true);
}
if env.other_headers()["X-Status"].contains("D") {
flags.set(Flag::TRASHED, true);
}
if env.other_headers()["X-Status"].contains("T") {
flags.set(Flag::DRAFT, true);
}
}
env.set_flags(flags);
index.insert(env.hash(), (offset + file_offset + start, len - start));
2019-07-11 11:44:27 +03:00
envelopes.push(env);
}
Err(_) => {
debug!("Could not parse mail at byte offset {}", offset);
}
}
offset += len + 2;
input = &input[len + 2..];
} else {
let start: Offset = input.find(b"\n").map(|v| v + 1).unwrap_or(0);
match Envelope::from_bytes(&input[start..], None) {
2019-07-11 11:44:27 +03:00
Ok(mut env) => {
let mut flags = Flag::empty();
if env.other_headers().contains_key("Status") {
if env.other_headers()["Status"].contains("F") {
flags.set(Flag::FLAGGED, true);
}
if env.other_headers()["Status"].contains("A") {
flags.set(Flag::REPLIED, true);
}
if env.other_headers()["Status"].contains("R") {
flags.set(Flag::SEEN, true);
}
if env.other_headers()["Status"].contains("D") {
flags.set(Flag::TRASHED, true);
}
}
if env.other_headers().contains_key("X-Status") {
if env.other_headers()["X-Status"].contains("F") {
flags.set(Flag::FLAGGED, true);
}
if env.other_headers()["X-Status"].contains("A") {
flags.set(Flag::REPLIED, true);
}
if env.other_headers()["X-Status"].contains("R") {
flags.set(Flag::SEEN, true);
}
if env.other_headers()["X-Status"].contains("D") {
flags.set(Flag::TRASHED, true);
}
if env.other_headers()["X-Status"].contains("T") {
flags.set(Flag::DRAFT, true);
}
}
env.set_flags(flags);
index.insert(
env.hash(),
(offset + file_offset + start, input.len() - start),
);
2019-07-11 11:44:27 +03:00
envelopes.push(env);
}
Err(_) => {
debug!("Could not parse mail at byte offset {}", offset);
}
}
break;
}
}
return Ok((&[], envelopes));
2019-07-11 11:44:27 +03:00
}
2018-07-27 21:37:56 +03:00
/// Mbox backend
2019-07-11 11:44:27 +03:00
#[derive(Debug, Default)]
pub struct MboxType {
account_name: String,
2019-07-11 11:44:27 +03:00
path: PathBuf,
2020-05-10 21:14:49 +03:00
index: Arc<Mutex<HashMap<EnvelopeHash, (Offset, Length)>>>,
mailboxes: Arc<Mutex<HashMap<MailboxHash, MboxMailbox>>>,
2019-07-11 11:44:27 +03:00
}
impl MailBackend for MboxType {
fn is_online(&self) -> Result<()> {
Ok(())
}
fn get(&mut self, mailbox: &Mailbox) -> Async<Result<Vec<Envelope>>> {
2019-07-11 11:44:27 +03:00
let mut w = AsyncBuilder::new();
let handle = {
let tx = w.tx();
let index = self.index.clone();
let mailbox_path = mailbox.path().to_string();
let mailbox_hash = mailbox.hash();
let mailboxes = self.mailboxes.clone();
let closure = move |_work_context| {
2019-07-11 11:44:27 +03:00
let tx = tx.clone();
let index = index.clone();
let file = match std::fs::OpenOptions::new()
.read(true)
.write(true)
.open(&mailbox_path)
2019-07-11 11:44:27 +03:00
{
Ok(f) => f,
Err(e) => {
tx.send(AsyncStatus::Payload(Err(MeliError::from(e))))
.unwrap();
2019-07-11 11:44:27 +03:00
return;
}
};
get_rw_lock_blocking(&file);
let mut buf_reader = BufReader::new(file);
let mut contents = Vec::new();
if let Err(e) = buf_reader.read_to_end(&mut contents) {
tx.send(AsyncStatus::Payload(Err(MeliError::from(e))))
.unwrap();
2019-07-11 11:44:27 +03:00
return;
};
let payload = mbox_parse(index, contents.as_slice(), 0)
.map_err(|e| MeliError::from(e))
.map(|(_, v)| v);
2019-07-11 11:44:27 +03:00
{
let mut mailbox_lock = mailboxes.lock().unwrap();
mailbox_lock
.entry(mailbox_hash)
2019-07-11 11:44:27 +03:00
.and_modify(|f| f.content = contents);
}
tx.send(AsyncStatus::Payload(payload)).unwrap();
2019-12-03 13:26:24 +02:00
tx.send(AsyncStatus::Finished).unwrap();
2019-07-11 11:44:27 +03:00
};
Box::new(closure)
};
w.build(handle)
}
fn watch(
&self,
sender: RefreshEventConsumer,
work_context: WorkContext,
) -> Result<std::thread::ThreadId> {
2019-07-11 11:44:27 +03:00
let (tx, rx) = channel();
let mut watcher = watcher(tx, std::time::Duration::from_secs(10))
.map_err(|e| e.to_string())
.map_err(MeliError::new)?;
for f in self.mailboxes.lock().unwrap().values() {
watcher
.watch(&f.path, RecursiveMode::Recursive)
.map_err(|e| e.to_string())
.map_err(MeliError::new)?;
2019-07-11 11:44:27 +03:00
debug!("watching {:?}", f.path.as_path());
}
let account_hash = {
let mut hasher = DefaultHasher::new();
hasher.write(self.account_name.as_bytes());
hasher.finish()
};
2019-07-11 11:44:27 +03:00
let index = self.index.clone();
let mailboxes = self.mailboxes.clone();
let handle = std::thread::Builder::new()
.name(format!("watching {}", self.account_name,))
2019-07-11 11:44:27 +03:00
.spawn(move || {
// Move `watcher` in the closure's scope so that it doesn't get dropped.
let _watcher = watcher;
let _work_context = work_context;
2019-07-11 11:44:27 +03:00
let index = index;
let mailboxes = mailboxes;
2019-07-11 11:44:27 +03:00
loop {
match rx.recv() {
/*
* Event types:
*
* pub enum RefreshEventKind {
* Update(EnvelopeHash, Envelope), // Old hash, new envelope
* Create(Envelope),
* Remove(EnvelopeHash),
* Rescan,
* }
*/
Ok(event) => match event {
/* Update */
DebouncedEvent::NoticeWrite(pathbuf)
| DebouncedEvent::Write(pathbuf) => {
let mailbox_hash = get_path_hash!(&pathbuf);
2019-07-11 11:44:27 +03:00
let file = match std::fs::OpenOptions::new()
.read(true)
.write(true)
.open(&pathbuf)
{
Ok(f) => f,
Err(_) => {
continue;
}
};
get_rw_lock_blocking(&file);
let mut mailbox_lock = mailboxes.lock().unwrap();
2019-07-11 11:44:27 +03:00
let mut buf_reader = BufReader::new(file);
let mut contents = Vec::new();
if let Err(e) = buf_reader.read_to_end(&mut contents) {
debug!(e);
continue;
};
if contents
.starts_with(mailbox_lock[&mailbox_hash].content.as_slice())
2019-07-11 11:44:27 +03:00
{
if let Ok((_, envelopes)) = mbox_parse(
2019-07-11 11:44:27 +03:00
index.clone(),
&contents[mailbox_lock[&mailbox_hash].content.len()..],
mailbox_lock[&mailbox_hash].content.len(),
) {
2019-07-11 11:44:27 +03:00
for env in envelopes {
sender.send(RefreshEvent {
account_hash,
mailbox_hash,
2019-07-11 11:44:27 +03:00
kind: RefreshEventKind::Create(Box::new(env)),
});
}
}
} else {
sender.send(RefreshEvent {
account_hash,
mailbox_hash,
2019-07-11 11:44:27 +03:00
kind: RefreshEventKind::Rescan,
});
}
mailbox_lock
.entry(mailbox_hash)
2019-07-11 11:44:27 +03:00
.and_modify(|f| f.content = contents);
}
/* Remove */
DebouncedEvent::NoticeRemove(pathbuf)
| DebouncedEvent::Remove(pathbuf) => {
if mailboxes
2019-11-27 14:23:35 +02:00
.lock()
.unwrap()
.values()
.any(|f| &f.path == &pathbuf)
{
let mailbox_hash = get_path_hash!(&pathbuf);
2019-11-27 14:23:35 +02:00
sender.send(RefreshEvent {
account_hash,
mailbox_hash,
2019-11-27 14:23:35 +02:00
kind: RefreshEventKind::Failure(MeliError::new(format!(
"mbox mailbox {} was removed.",
2019-11-27 14:23:35 +02:00
pathbuf.display()
))),
});
return;
}
}
DebouncedEvent::Rename(src, dest) => {
if mailboxes.lock().unwrap().values().any(|f| &f.path == &src) {
let mailbox_hash = get_path_hash!(&src);
2019-11-27 14:23:35 +02:00
sender.send(RefreshEvent {
account_hash,
mailbox_hash,
2019-11-27 14:23:35 +02:00
kind: RefreshEventKind::Failure(MeliError::new(format!(
"mbox mailbox {} was renamed to {}.",
2019-11-27 14:23:35 +02:00
src.display(),
dest.display()
))),
});
return;
}
2019-07-11 11:44:27 +03:00
}
/* Trigger rescan of mailboxes */
2019-07-11 11:44:27 +03:00
DebouncedEvent::Rescan => {
for &mailbox_hash in mailboxes.lock().unwrap().keys() {
2019-11-27 14:23:35 +02:00
sender.send(RefreshEvent {
account_hash,
mailbox_hash,
2019-11-27 14:23:35 +02:00
kind: RefreshEventKind::Rescan,
});
}
return;
2019-07-11 11:44:27 +03:00
}
_ => {}
},
Err(e) => debug!("watch error: {:?}", e),
}
}
})?;
Ok(handle.thread().id())
}
2020-05-10 21:14:49 +03:00
fn mailboxes(&self) -> Result<HashMap<MailboxHash, Mailbox>> {
Ok(self
.mailboxes
2019-07-11 11:44:27 +03:00
.lock()
.unwrap()
.iter()
.map(|(h, f)| (*h, f.clone() as Mailbox))
.collect())
2019-07-11 11:44:27 +03:00
}
fn operation(&self, hash: EnvelopeHash) -> Box<dyn BackendOp> {
2019-07-11 11:44:27 +03:00
let (offset, length) = {
let index = self.index.lock().unwrap();
index[&hash]
};
Box::new(MboxOp::new(hash, self.path.as_path(), offset, length))
}
fn save(&self, _bytes: &[u8], _mailbox_hash: MailboxHash, _flags: Option<Flag>) -> Result<()> {
Err(MeliError::new("Unimplemented."))
}
fn as_any(&self) -> &dyn::std::any::Any {
self
}
}
impl MboxType {
pub fn new(
s: &AccountSettings,
_is_subscribed: Box<dyn Fn(&str) -> bool>,
) -> Result<Box<dyn MailBackend>> {
let path = Path::new(s.root_mailbox.as_str()).expand();
2019-07-11 11:44:27 +03:00
if !path.exists() {
return Err(MeliError::new(format!(
"\"root_mailbox\" {} for account {} is not a valid path.",
s.root_mailbox.as_str(),
2019-07-11 11:44:27 +03:00
s.name()
)));
2019-07-11 11:44:27 +03:00
}
let ret = MboxType {
account_name: s.name().to_string(),
path,
2019-07-11 11:44:27 +03:00
..Default::default()
};
let name: String = ret
.path
.file_name()
.map(|f| f.to_string_lossy().into())
.unwrap_or(String::new());
let hash = get_path_hash!(&ret.path);
let read_only = if let Ok(metadata) = std::fs::metadata(&ret.path) {
metadata.permissions().readonly()
} else {
true
};
ret.mailboxes.lock().unwrap().insert(
2019-07-11 11:44:27 +03:00
hash,
MboxMailbox {
2019-07-11 11:44:27 +03:00
hash,
path: ret.path.clone(),
2019-07-11 11:44:27 +03:00
name,
content: Vec::new(),
children: Vec::new(),
parent: None,
usage: Arc::new(RwLock::new(SpecialUsageMailbox::Normal)),
is_subscribed: true,
permissions: MailboxPermissions {
create_messages: !read_only,
remove_messages: !read_only,
set_flags: !read_only,
create_child: !read_only,
rename_messages: !read_only,
delete_messages: !read_only,
delete_mailbox: !read_only,
change_permissions: false,
},
unseen: Arc::new(Mutex::new(0)),
total: Arc::new(Mutex::new(0)),
2019-07-11 11:44:27 +03:00
},
);
/*
/* Look for other mailboxes */
let parent_mailbox = Path::new(path).parent().unwrap();
let read_dir = std::fs::read_dir(parent_mailbox);
2019-07-11 11:44:27 +03:00
if read_dir.is_ok() {
for f in read_dir.unwrap() {
if f.is_err() {
continue;
}
let f = f.unwrap().path();
if f.is_file() && f != path {
let name: String = f
.file_name()
.map(|f| f.to_string_lossy().into())
.unwrap_or(String::new());
let hash = get_path_hash!(f);
ret.mailboxes.lock().unwrap().insert(
2019-07-11 11:44:27 +03:00
hash,
MboxMailbox {
2019-07-11 11:44:27 +03:00
hash,
path: f,
name,
content: Vec::new(),
children: Vec::new(),
parent: None,
},
);
}
}
}
*/
Ok(Box::new(ret))
}
pub fn validate_config(s: &AccountSettings) -> Result<()> {
let path = Path::new(s.root_mailbox.as_str()).expand();
if !path.exists() {
return Err(MeliError::new(format!(
"\"root_mailbox\" {} for account {} is not a valid path.",
s.root_mailbox.as_str(),
s.name()
)));
}
Ok(())
}
}