You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

646 lines
23KB

  1. /*
  2. * meli - mailbox module.
  3. *
  4. * Copyright 2017 Manos Pitsidianakis
  5. *
  6. * This file is part of meli.
  7. *
  8. * meli is free software: you can redistribute it and/or modify
  9. * it under the terms of the GNU General Public License as published by
  10. * the Free Software Foundation, either version 3 of the License, or
  11. * (at your option) any later version.
  12. *
  13. * meli is distributed in the hope that it will be useful,
  14. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. * GNU General Public License for more details.
  17. *
  18. * You should have received a copy of the GNU General Public License
  19. * along with meli. If not, see <http://www.gnu.org/licenses/>.
  20. */
  21. /*!
  22. * https://wiki2.dovecot.org/MailboxFormat/mbox
  23. */
  24. use crate::async_workers::{Async, AsyncBuilder, AsyncStatus, WorkContext};
  25. use crate::backends::BackendOp;
  26. use crate::backends::FolderHash;
  27. use crate::backends::{
  28. BackendFolder, Folder, FolderPermissions, MailBackend, RefreshEvent, RefreshEventConsumer,
  29. RefreshEventKind,
  30. };
  31. use crate::conf::AccountSettings;
  32. use crate::email::parser::BytesExt;
  33. use crate::email::*;
  34. use crate::error::{MeliError, Result};
  35. use crate::shellexpand::ShellExpandTrait;
  36. use fnv::FnvHashMap;
  37. use libc;
  38. use memmap::{Mmap, Protection};
  39. use nom::{IResult, Needed};
  40. extern crate notify;
  41. use self::notify::{watcher, DebouncedEvent, RecursiveMode, Watcher};
  42. use std::collections::hash_map::DefaultHasher;
  43. use std::fs::File;
  44. use std::hash::{Hash, Hasher};
  45. use std::io::BufReader;
  46. use std::io::Read;
  47. use std::os::unix::io::AsRawFd;
  48. use std::path::{Path, PathBuf};
  49. use std::sync::mpsc::channel;
  50. use std::sync::{Arc, Mutex};
  51. const F_OFD_SETLKW: libc::c_int = 38;
  52. // Open file description locking
  53. // # man fcntl
  54. fn get_rw_lock_blocking(f: &File) {
  55. let fd: libc::c_int = f.as_raw_fd();
  56. let mut flock: libc::flock = libc::flock {
  57. l_type: libc::F_WRLCK as libc::c_short,
  58. l_whence: libc::SEEK_SET as libc::c_short,
  59. l_start: 0,
  60. l_len: 0, /* "Specifying 0 for l_len has the special meaning: lock all bytes starting at the location
  61. specified by l_whence and l_start through to the end of file, no matter how large the file grows." */
  62. 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." */
  63. };
  64. let ptr: *mut libc::flock = &mut flock;
  65. let ret_val = unsafe { libc::fcntl(fd, F_OFD_SETLKW, ptr as *mut libc::c_void) };
  66. debug!(&ret_val);
  67. assert!(-1 != ret_val);
  68. }
  69. macro_rules! get_path_hash {
  70. ($path:expr) => {{
  71. let mut hasher = DefaultHasher::new();
  72. $path.hash(&mut hasher);
  73. hasher.finish()
  74. }};
  75. }
  76. #[derive(Debug)]
  77. struct MboxFolder {
  78. hash: FolderHash,
  79. name: String,
  80. path: PathBuf,
  81. content: Vec<u8>,
  82. children: Vec<FolderHash>,
  83. parent: Option<FolderHash>,
  84. permissions: FolderPermissions,
  85. }
  86. impl BackendFolder for MboxFolder {
  87. fn hash(&self) -> FolderHash {
  88. self.hash
  89. }
  90. fn name(&self) -> &str {
  91. self.name.as_str()
  92. }
  93. fn path(&self) -> &str {
  94. /* We know it's valid UTF-8 because we supplied it */
  95. self.path.to_str().unwrap()
  96. }
  97. fn change_name(&mut self, s: &str) {
  98. self.name = s.to_string();
  99. }
  100. fn clone(&self) -> Folder {
  101. Box::new(MboxFolder {
  102. hash: self.hash,
  103. name: self.name.clone(),
  104. path: self.path.clone(),
  105. content: self.content.clone(),
  106. children: self.children.clone(),
  107. parent: self.parent,
  108. permissions: self.permissions,
  109. })
  110. }
  111. fn children(&self) -> &[FolderHash] {
  112. &self.children
  113. }
  114. fn parent(&self) -> Option<FolderHash> {
  115. self.parent
  116. }
  117. fn permissions(&self) -> FolderPermissions {
  118. self.permissions
  119. }
  120. }
  121. /// `BackendOp` implementor for Mbox
  122. #[derive(Debug, Default)]
  123. pub struct MboxOp {
  124. hash: EnvelopeHash,
  125. path: PathBuf,
  126. offset: Offset,
  127. length: Length,
  128. slice: Option<Mmap>,
  129. }
  130. impl MboxOp {
  131. pub fn new(hash: EnvelopeHash, path: &Path, offset: Offset, length: Length) -> Self {
  132. MboxOp {
  133. hash,
  134. path: path.to_path_buf(),
  135. slice: None,
  136. offset,
  137. length,
  138. }
  139. }
  140. }
  141. impl BackendOp for MboxOp {
  142. fn description(&self) -> String {
  143. String::new()
  144. }
  145. fn as_bytes(&mut self) -> Result<&[u8]> {
  146. if self.slice.is_none() {
  147. self.slice = Some(Mmap::open_path(&self.path, Protection::Read)?);
  148. }
  149. /* Unwrap is safe since we use ? above. */
  150. Ok(unsafe {
  151. &self.slice.as_ref().unwrap().as_slice()[self.offset..self.offset + self.length]
  152. })
  153. }
  154. fn fetch_headers(&mut self) -> Result<&[u8]> {
  155. let raw = self.as_bytes()?;
  156. let result = parser::headers_raw(raw).to_full_result()?;
  157. Ok(result)
  158. }
  159. fn fetch_body(&mut self) -> Result<&[u8]> {
  160. let raw = self.as_bytes()?;
  161. let result = parser::body_raw(raw).to_full_result()?;
  162. Ok(result)
  163. }
  164. fn fetch_flags(&self) -> Flag {
  165. let mut flags = Flag::empty();
  166. let file = match std::fs::OpenOptions::new()
  167. .read(true)
  168. .write(true)
  169. .open(&self.path)
  170. {
  171. Ok(f) => f,
  172. Err(e) => {
  173. debug!(e);
  174. return flags;
  175. }
  176. };
  177. get_rw_lock_blocking(&file);
  178. let mut buf_reader = BufReader::new(file);
  179. let mut contents = Vec::new();
  180. if let Err(e) = buf_reader.read_to_end(&mut contents) {
  181. debug!(e);
  182. return flags;
  183. };
  184. if let Ok(headers) = parser::headers_raw(contents.as_slice()).to_full_result() {
  185. if let Some(start) = headers.find(b"Status:") {
  186. if let Some(end) = headers[start..].find(b"\n") {
  187. let start = start + b"Status:".len();
  188. let status = headers[start..start + end].trim();
  189. if status.contains(&b'F') {
  190. flags.set(Flag::FLAGGED, true);
  191. }
  192. if status.contains(&b'A') {
  193. flags.set(Flag::REPLIED, true);
  194. }
  195. if status.contains(&b'R') {
  196. flags.set(Flag::SEEN, true);
  197. }
  198. if status.contains(&b'D') {
  199. flags.set(Flag::TRASHED, true);
  200. }
  201. if status.contains(&b'T') {
  202. flags.set(Flag::DRAFT, true);
  203. }
  204. }
  205. }
  206. if let Some(start) = headers.find(b"X-Status:") {
  207. let start = start + b"X-Status:".len();
  208. if let Some(end) = headers[start..].find(b"\n") {
  209. let status = headers[start..start + end].trim();
  210. if status.contains(&b'F') {
  211. flags.set(Flag::FLAGGED, true);
  212. }
  213. if status.contains(&b'A') {
  214. flags.set(Flag::REPLIED, true);
  215. }
  216. if status.contains(&b'R') {
  217. flags.set(Flag::SEEN, true);
  218. }
  219. if status.contains(&b'D') {
  220. flags.set(Flag::TRASHED, true);
  221. }
  222. if status.contains(&b'T') {
  223. flags.set(Flag::DRAFT, true);
  224. }
  225. }
  226. }
  227. }
  228. flags
  229. }
  230. fn set_flag(&mut self, _envelope: &mut Envelope, _flag: Flag, _value: bool) -> Result<()> {
  231. Ok(())
  232. }
  233. }
  234. pub fn mbox_parse(
  235. index: Arc<Mutex<FnvHashMap<EnvelopeHash, (Offset, Length)>>>,
  236. input: &[u8],
  237. file_offset: usize,
  238. ) -> IResult<&[u8], Vec<Envelope>> {
  239. if input.is_empty() {
  240. return IResult::Incomplete(Needed::Unknown);
  241. }
  242. let mut input = input;
  243. let mut offset = 0;
  244. let mut index = index.lock().unwrap();
  245. let mut envelopes = Vec::with_capacity(32);
  246. while !input.is_empty() {
  247. let next_offset: Option<usize> = input.find(b"\n\nFrom ");
  248. if let Some(len) = next_offset {
  249. match Envelope::from_bytes(&input[..len], None) {
  250. Ok(mut env) => {
  251. let mut flags = Flag::empty();
  252. if env.other_headers().contains_key("Status") {
  253. if env.other_headers()["Status"].contains("F") {
  254. flags.set(Flag::FLAGGED, true);
  255. }
  256. if env.other_headers()["Status"].contains("A") {
  257. flags.set(Flag::REPLIED, true);
  258. }
  259. if env.other_headers()["Status"].contains("R") {
  260. flags.set(Flag::SEEN, true);
  261. }
  262. if env.other_headers()["Status"].contains("D") {
  263. flags.set(Flag::TRASHED, true);
  264. }
  265. }
  266. if env.other_headers().contains_key("X-Status") {
  267. if env.other_headers()["X-Status"].contains("F") {
  268. flags.set(Flag::FLAGGED, true);
  269. }
  270. if env.other_headers()["X-Status"].contains("A") {
  271. flags.set(Flag::REPLIED, true);
  272. }
  273. if env.other_headers()["X-Status"].contains("R") {
  274. flags.set(Flag::SEEN, true);
  275. }
  276. if env.other_headers()["X-Status"].contains("D") {
  277. flags.set(Flag::TRASHED, true);
  278. }
  279. if env.other_headers()["X-Status"].contains("T") {
  280. flags.set(Flag::DRAFT, true);
  281. }
  282. }
  283. env.set_flags(flags);
  284. index.insert(env.hash(), (offset + file_offset, len));
  285. envelopes.push(env);
  286. }
  287. Err(_) => {
  288. debug!("Could not parse mail at byte offset {}", offset);
  289. }
  290. }
  291. offset += len + 2;
  292. input = &input[len + 2..];
  293. } else {
  294. match Envelope::from_bytes(input, None) {
  295. Ok(mut env) => {
  296. let mut flags = Flag::empty();
  297. if env.other_headers().contains_key("Status") {
  298. if env.other_headers()["Status"].contains("F") {
  299. flags.set(Flag::FLAGGED, true);
  300. }
  301. if env.other_headers()["Status"].contains("A") {
  302. flags.set(Flag::REPLIED, true);
  303. }
  304. if env.other_headers()["Status"].contains("R") {
  305. flags.set(Flag::SEEN, true);
  306. }
  307. if env.other_headers()["Status"].contains("D") {
  308. flags.set(Flag::TRASHED, true);
  309. }
  310. }
  311. if env.other_headers().contains_key("X-Status") {
  312. if env.other_headers()["X-Status"].contains("F") {
  313. flags.set(Flag::FLAGGED, true);
  314. }
  315. if env.other_headers()["X-Status"].contains("A") {
  316. flags.set(Flag::REPLIED, true);
  317. }
  318. if env.other_headers()["X-Status"].contains("R") {
  319. flags.set(Flag::SEEN, true);
  320. }
  321. if env.other_headers()["X-Status"].contains("D") {
  322. flags.set(Flag::TRASHED, true);
  323. }
  324. if env.other_headers()["X-Status"].contains("T") {
  325. flags.set(Flag::DRAFT, true);
  326. }
  327. }
  328. env.set_flags(flags);
  329. index.insert(env.hash(), (offset + file_offset, input.len()));
  330. envelopes.push(env);
  331. }
  332. Err(_) => {
  333. debug!("Could not parse mail at byte offset {}", offset);
  334. }
  335. }
  336. break;
  337. }
  338. }
  339. return IResult::Done(&[], envelopes);
  340. }
  341. type Offset = usize;
  342. type Length = usize;
  343. /// Mbox backend
  344. #[derive(Debug, Default)]
  345. pub struct MboxType {
  346. path: PathBuf,
  347. index: Arc<Mutex<FnvHashMap<EnvelopeHash, (Offset, Length)>>>,
  348. folders: Arc<Mutex<FnvHashMap<FolderHash, MboxFolder>>>,
  349. }
  350. impl MailBackend for MboxType {
  351. fn is_online(&self) -> bool {
  352. true
  353. }
  354. fn get(&mut self, folder: &Folder) -> Async<Result<Vec<Envelope>>> {
  355. let mut w = AsyncBuilder::new();
  356. let handle = {
  357. let tx = w.tx();
  358. let index = self.index.clone();
  359. let folder_path = folder.path().to_string();
  360. let folder_hash = folder.hash();
  361. let folders = self.folders.clone();
  362. let closure = move |_work_context| {
  363. let tx = tx.clone();
  364. let index = index.clone();
  365. let file = match std::fs::OpenOptions::new()
  366. .read(true)
  367. .write(true)
  368. .open(&folder_path)
  369. {
  370. Ok(f) => f,
  371. Err(e) => {
  372. tx.send(AsyncStatus::Payload(Err(MeliError::from(e))))
  373. .unwrap();
  374. return;
  375. }
  376. };
  377. get_rw_lock_blocking(&file);
  378. let mut buf_reader = BufReader::new(file);
  379. let mut contents = Vec::new();
  380. if let Err(e) = buf_reader.read_to_end(&mut contents) {
  381. tx.send(AsyncStatus::Payload(Err(MeliError::from(e))))
  382. .unwrap();
  383. return;
  384. };
  385. let payload = mbox_parse(index, contents.as_slice(), 0)
  386. .to_full_result()
  387. .map_err(|e| MeliError::from(e));
  388. {
  389. let mut folder_lock = folders.lock().unwrap();
  390. folder_lock
  391. .entry(folder_hash)
  392. .and_modify(|f| f.content = contents);
  393. }
  394. tx.send(AsyncStatus::Payload(payload)).unwrap();
  395. };
  396. Box::new(closure)
  397. };
  398. w.build(handle)
  399. }
  400. fn watch(
  401. &self,
  402. sender: RefreshEventConsumer,
  403. work_context: WorkContext,
  404. ) -> Result<std::thread::ThreadId> {
  405. let (tx, rx) = channel();
  406. let mut watcher = watcher(tx, std::time::Duration::from_secs(10)).unwrap();
  407. for f in self.folders.lock().unwrap().values() {
  408. watcher.watch(&f.path, RecursiveMode::Recursive).unwrap();
  409. debug!("watching {:?}", f.path.as_path());
  410. }
  411. let index = self.index.clone();
  412. let folders = self.folders.clone();
  413. let handle = std::thread::Builder::new()
  414. .name(format!(
  415. "watching {}",
  416. self.path.file_name().unwrap().to_str().unwrap()
  417. ))
  418. .spawn(move || {
  419. // Move `watcher` in the closure's scope so that it doesn't get dropped.
  420. let _watcher = watcher;
  421. let _work_context = work_context;
  422. let index = index;
  423. let folders = folders;
  424. loop {
  425. match rx.recv() {
  426. /*
  427. * Event types:
  428. *
  429. * pub enum RefreshEventKind {
  430. * Update(EnvelopeHash, Envelope), // Old hash, new envelope
  431. * Create(Envelope),
  432. * Remove(EnvelopeHash),
  433. * Rescan,
  434. * }
  435. */
  436. Ok(event) => match event {
  437. /* Update */
  438. DebouncedEvent::NoticeWrite(pathbuf)
  439. | DebouncedEvent::Write(pathbuf) => {
  440. let folder_hash = get_path_hash!(&pathbuf);
  441. let file = match std::fs::OpenOptions::new()
  442. .read(true)
  443. .write(true)
  444. .open(&pathbuf)
  445. {
  446. Ok(f) => f,
  447. Err(_) => {
  448. continue;
  449. }
  450. };
  451. get_rw_lock_blocking(&file);
  452. let mut folder_lock = folders.lock().unwrap();
  453. let mut buf_reader = BufReader::new(file);
  454. let mut contents = Vec::new();
  455. if let Err(e) = buf_reader.read_to_end(&mut contents) {
  456. debug!(e);
  457. continue;
  458. };
  459. if contents
  460. .starts_with(folder_lock[&folder_hash].content.as_slice())
  461. {
  462. if let Ok(envelopes) = mbox_parse(
  463. index.clone(),
  464. &contents[folder_lock[&folder_hash].content.len()..],
  465. folder_lock[&folder_hash].content.len(),
  466. )
  467. .to_full_result()
  468. {
  469. for env in envelopes {
  470. sender.send(RefreshEvent {
  471. hash: folder_hash,
  472. kind: RefreshEventKind::Create(Box::new(env)),
  473. });
  474. }
  475. }
  476. } else {
  477. sender.send(RefreshEvent {
  478. hash: folder_hash,
  479. kind: RefreshEventKind::Rescan,
  480. });
  481. }
  482. folder_lock
  483. .entry(folder_hash)
  484. .and_modify(|f| f.content = contents);
  485. }
  486. /* Remove */
  487. DebouncedEvent::NoticeRemove(pathbuf)
  488. | DebouncedEvent::Remove(pathbuf) => {
  489. panic!(format!("mbox folder {} was removed.", pathbuf.display()))
  490. }
  491. /* Envelope hasn't changed */
  492. DebouncedEvent::Rename(src, dest) => panic!(format!(
  493. "mbox folder {} was renamed to {}.",
  494. src.display(),
  495. dest.display()
  496. )),
  497. /* Trigger rescan of folder */
  498. DebouncedEvent::Rescan => {
  499. /* Actually should rescan all folders */
  500. unreachable!("Unimplemented: rescan of all folders in MboxType")
  501. }
  502. _ => {}
  503. },
  504. Err(e) => debug!("watch error: {:?}", e),
  505. }
  506. }
  507. })?;
  508. Ok(handle.thread().id())
  509. }
  510. fn folders(&self) -> FnvHashMap<FolderHash, Folder> {
  511. self.folders
  512. .lock()
  513. .unwrap()
  514. .iter()
  515. .map(|(h, f)| (*h, f.clone() as Folder))
  516. .collect()
  517. }
  518. fn operation(&self, hash: EnvelopeHash) -> Box<dyn BackendOp> {
  519. let (offset, length) = {
  520. let index = self.index.lock().unwrap();
  521. index[&hash]
  522. };
  523. Box::new(MboxOp::new(hash, self.path.as_path(), offset, length))
  524. }
  525. fn save(&self, _bytes: &[u8], _folder: &str, _flags: Option<Flag>) -> Result<()> {
  526. unimplemented!();
  527. }
  528. fn as_any(&self) -> &dyn::std::any::Any {
  529. self
  530. }
  531. }
  532. impl MboxType {
  533. pub fn new(s: &AccountSettings, _is_subscribed: Box<dyn Fn(&str) -> bool>) -> Self {
  534. let path = Path::new(s.root_folder.as_str()).expand();
  535. if !path.exists() {
  536. panic!(
  537. "\"root_folder\" {} for account {} is not a valid path.",
  538. s.root_folder.as_str(),
  539. s.name()
  540. );
  541. }
  542. let ret = MboxType {
  543. path,
  544. ..Default::default()
  545. };
  546. let name: String = ret
  547. .path
  548. .file_name()
  549. .map(|f| f.to_string_lossy().into())
  550. .unwrap_or(String::new());
  551. let hash = get_path_hash!(&ret.path);
  552. let read_only = if let Ok(metadata) = std::fs::metadata(&ret.path) {
  553. metadata.permissions().readonly()
  554. } else {
  555. true
  556. };
  557. ret.folders.lock().unwrap().insert(
  558. hash,
  559. MboxFolder {
  560. hash,
  561. path: ret.path.clone(),
  562. name,
  563. content: Vec::new(),
  564. children: Vec::new(),
  565. parent: None,
  566. permissions: FolderPermissions {
  567. create_messages: !read_only,
  568. remove_messages: !read_only,
  569. set_flags: !read_only,
  570. create_child: !read_only,
  571. rename_messages: !read_only,
  572. delete_messages: !read_only,
  573. delete_mailbox: !read_only,
  574. change_permissions: false,
  575. },
  576. },
  577. );
  578. /*
  579. /* Look for other mailboxes */
  580. let parent_folder = Path::new(path).parent().unwrap();
  581. let read_dir = std::fs::read_dir(parent_folder);
  582. if read_dir.is_ok() {
  583. for f in read_dir.unwrap() {
  584. if f.is_err() {
  585. continue;
  586. }
  587. let f = f.unwrap().path();
  588. if f.is_file() && f != path {
  589. let name: String = f
  590. .file_name()
  591. .map(|f| f.to_string_lossy().into())
  592. .unwrap_or(String::new());
  593. let hash = get_path_hash!(f);
  594. ret.folders.lock().unwrap().insert(
  595. hash,
  596. MboxFolder {
  597. hash,
  598. path: f,
  599. name,
  600. content: Vec::new(),
  601. children: Vec::new(),
  602. parent: None,
  603. },
  604. );
  605. }
  606. }
  607. }
  608. */
  609. ret
  610. }
  611. }