From dfdb71dea2c2d92078fb40465a85225c3c43021e Mon Sep 17 00:00:00 2001 From: Michael Zhang Date: Sun, 22 Aug 2021 19:19:27 -0500 Subject: [PATCH] mbsync --- Cargo.lock | 114 ++++++++------ Cargo.toml | 1 + Justfile | 3 + daemon/Cargo.toml | 1 + daemon/src/config/mod.rs | 10 +- daemon/src/main.rs | 2 + imap/src/proto/command.rs | 2 +- imap/src/proto/rfc3501.rs | 2 +- mbsync/Cargo.toml | 13 ++ mbsync/src/config.rs | 315 ++++++++++++++++++++++++++++++++++++++ mbsync/src/lib.rs | 11 ++ mbsync/src/main.rs | 36 +++++ mbsync/src/store.rs | 71 +++++++++ proto-common/src/rule.rs | 3 +- smtp/src/client/mod.rs | 3 +- smtp/src/lib.rs | 5 +- smtp/src/proto/mod.rs | 2 + smtp/src/proto/rfc5321.rs | 3 + 18 files changed, 545 insertions(+), 52 deletions(-) create mode 100644 mbsync/Cargo.toml create mode 100644 mbsync/src/config.rs create mode 100644 mbsync/src/lib.rs create mode 100644 mbsync/src/main.rs create mode 100644 mbsync/src/store.rs diff --git a/Cargo.lock b/Cargo.lock index f611eb9..d8cbe34 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,9 +4,9 @@ version = 3 [[package]] name = "anyhow" -version = "1.0.42" +version = "1.0.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "595d3cfa7a60d4555cb5067b99f07142a08ea778de5cf993f7b75c7d8fabc486" +checksum = "28ae2b3dec75a406790005a200b1bd89785afc02517a00ca99ecfe093ee9e6cf" [[package]] name = "arrayvec" @@ -50,9 +50,9 @@ checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" [[package]] name = "bitflags" -version = "1.2.1" +version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitvec" @@ -116,9 +116,9 @@ dependencies = [ [[package]] name = "clap" -version = "3.0.0-beta.2" +version = "3.0.0-beta.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bd1061998a501ee7d4b6d449020df3266ca3124b941ec56cf2005c3779ca142" +checksum = "fcd70aa5597dbc42f7217a543f9ef2768b2ef823ba29036072d30e1d88e98406" dependencies = [ "atty", "bitflags", @@ -129,15 +129,14 @@ dependencies = [ "strsim", "termcolor", "textwrap", - "unicode-width", "vec_map", ] [[package]] name = "clap_derive" -version = "3.0.0-beta.2" +version = "3.0.0-beta.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "370f715b81112975b1b69db93e0b56ea4cd4e5002ac43b2da8474106a54096a1" +checksum = "0b5bb0d655624a0b8770d1c178fb8ffcb1f91cc722cb08f451e3dc72465421ac" dependencies = [ "heck", "proc-macro-error", @@ -181,6 +180,17 @@ dependencies = [ "syn", ] +[[package]] +name = "derivative" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "derive_builder" version = "0.10.2" @@ -342,9 +352,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.3.3" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "825343c4eef0b63f541f8903f395dc5beb362a979b5799a84062527ef1e37726" +checksum = "d7f3675cfef6a30c8031cf9e6493ebdc3bb3272a3fea3923c4210d1830e6a472" dependencies = [ "bytes", "fnv", @@ -407,9 +417,9 @@ dependencies = [ [[package]] name = "httparse" -version = "1.4.1" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3a87b616e37e93c22fb19bcd386f02f3af5ea98a25670ad0fce773de23c5e68" +checksum = "acd94fdbe1d4ff688b67b04eee2e17bd50995534a61539e45adfefb45e5e5503" [[package]] name = "httpdate" @@ -489,15 +499,15 @@ dependencies = [ [[package]] name = "itoa" -version = "0.4.7" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736" +checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" [[package]] name = "js-sys" -version = "0.3.52" +version = "0.3.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce791b7ca6638aae45be056e068fc756d871eb3b3b10b8efa62d1c9cec616752" +checksum = "e4bf49d50e2961077d9c99f4b7997d770a1114f087c3c2e0069b36c13fc2979d" dependencies = [ "wasm-bindgen", ] @@ -523,9 +533,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.98" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "320cfe77175da3a483efed4bc0adc1968ca050b098ce4f2f1c13a56626128790" +checksum = "a1fa8cddc8fbbee11227ef194b5317ed014b8acbf15139bd716a18ad3fe99ec5" [[package]] name = "lock_api" @@ -545,6 +555,19 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "mbsync" +version = "0.1.0" +dependencies = [ + "anyhow", + "bitflags", + "clap", + "derivative", + "derive_builder", + "panorama-imap", + "tokio", +] + [[package]] name = "memchr" version = "2.3.4" @@ -632,9 +655,9 @@ checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56" [[package]] name = "os_str_bytes" -version = "2.4.0" +version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afb2e1c3ee07430c2cf76151675e583e0f19985fa6efae47d6848a3e2c824f85" +checksum = "6acbef58a60fe69ab50510a55bc8cdd4d6cf2283d27ad338f54cb52747a9cf2d" [[package]] name = "panorama-daemon" @@ -643,6 +666,7 @@ dependencies = [ "anyhow", "async-trait", "clap", + "derivative", "futures", "hyper", "inotify", @@ -863,18 +887,18 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.127" +version = "1.0.128" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f03b9878abf6d14e6779d3f24f07b2cfa90352cfec4acc5aab8f1ac7f146fae8" +checksum = "1056a0db1978e9dbf0f6e4fca677f6f9143dc1c19de346f22cac23e422196834" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.127" +version = "1.0.128" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a024926d3432516606328597e0f224a51355a493b49fdd67e9209187cbe55ecc" +checksum = "13af2fbb8b60a8950d6c72a56d2095c28870367cc8e10c55e9745bac4995a2c4" dependencies = [ "proc-macro2", "quote", @@ -935,9 +959,9 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] name = "syn" -version = "1.0.74" +version = "1.0.75" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1873d832550d4588c3dbc20f01361ab00bfe741048f71e3fecf145a7cc18b29c" +checksum = "b7f58f7e8eaa0009c5fec437aabf511bd9933e4b2d7407bd05273c01a8906ea7" dependencies = [ "proc-macro2", "quote", @@ -961,9 +985,9 @@ dependencies = [ [[package]] name = "textwrap" -version = "0.12.1" +version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "203008d98caf094106cfaba70acfed15e18ed3ddb7d94e49baec153a2b462789" +checksum = "0066c8d12af8b5acd21e00547c3797fde4e8677254a7ee429176ccebbe93dd80" dependencies = [ "unicode-width", ] @@ -990,9 +1014,9 @@ dependencies = [ [[package]] name = "tokio" -version = "1.9.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b7b349f11a7047e6d1276853e612d152f5e8a352c61917887cc2169e2366b4c" +checksum = "01cf844b23c6131f624accf65ce0e4e9956a8bb329400ea5bcc26ae3a5c20b0b" dependencies = [ "autocfg", "bytes", @@ -1072,9 +1096,9 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.18" +version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9ff14f98b1a4b289c6248a023c1c2fa1491062964e9fed67ab29c4e4da4a052" +checksum = "2ca517f43f0fb96e0c3072ed5c275fe5eece87e8cb52f4a77b69226d3b1c9df8" dependencies = [ "lazy_static", ] @@ -1139,9 +1163,9 @@ checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" [[package]] name = "wasm-bindgen" -version = "0.2.75" +version = "0.2.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b608ecc8f4198fe8680e2ed18eccab5f0cd4caaf3d83516fa5fb2e927fda2586" +checksum = "8ce9b1b516211d33767048e5d47fa2a381ed8b76fc48d2ce4aa39877f9f183e0" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -1149,9 +1173,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.75" +version = "0.2.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "580aa3a91a63d23aac5b6b267e2d13cb4f363e31dce6c352fca4752ae12e479f" +checksum = "cfe8dc78e2326ba5f845f4b5bf548401604fa20b1dd1d365fb73b6c1d6364041" dependencies = [ "bumpalo", "lazy_static", @@ -1164,9 +1188,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.75" +version = "0.2.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "171ebf0ed9e1458810dfcb31f2e766ad6b3a89dbda42d8901f2b268277e5f09c" +checksum = "44468aa53335841d9d6b6c023eaab07c0cd4bddbcfdee3e2bb1e8d2cb8069fef" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -1174,9 +1198,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.75" +version = "0.2.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c2657dd393f03aa2a659c25c6ae18a13a4048cebd220e147933ea837efc589f" +checksum = "0195807922713af1e67dc66132c7328206ed9766af3858164fb583eedc25fbad" dependencies = [ "proc-macro2", "quote", @@ -1187,15 +1211,15 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.75" +version = "0.2.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e0c4a743a309662d45f4ede961d7afa4ba4131a59a639f29b0069c3798bbcc2" +checksum = "acdb075a845574a1fa5f09fd77e43f7747599301ea3417a9fbffdeedfc1f4a29" [[package]] name = "web-sys" -version = "0.3.52" +version = "0.3.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01c70a82d842c9979078c772d4a1344685045f1a5628f677c2b2eab4dd7d2696" +checksum = "224b2f6b67919060055ef1a67807367c2066ed520c3862cc013d26cf893a783c" dependencies = [ "js-sys", "wasm-bindgen", diff --git a/Cargo.toml b/Cargo.toml index 0ac3dd0..b477b33 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,6 +4,7 @@ members = [ "imap", "smtp", "proto-common", + "mbsync", ] [profile.release] diff --git a/Justfile b/Justfile index 0a1164c..5c32701 100644 --- a/Justfile +++ b/Justfile @@ -3,3 +3,6 @@ fmt: doc: cargo doc --workspace --no-deps + +watch: + cargo watch -c \ No newline at end of file diff --git a/daemon/Cargo.toml b/daemon/Cargo.toml index d0744e6..826d0fc 100644 --- a/daemon/Cargo.toml +++ b/daemon/Cargo.toml @@ -7,6 +7,7 @@ edition = "2018" anyhow = "1.0.42" async-trait = "0.1.50" clap = "3.0.0-beta.2" +derivative = "2.2.0" futures = "0.3.16" hyper = { version = "0.14.11", features = ["server", "http2", "stream"] } inotify = { version = "0.9.3", features = ["stream"] } diff --git a/daemon/src/config/mod.rs b/daemon/src/config/mod.rs index e63c3c2..516ba09 100644 --- a/daemon/src/config/mod.rs +++ b/daemon/src/config/mod.rs @@ -59,12 +59,18 @@ pub struct ImapConfig { } /// Method of authentication for the IMAP server -#[derive(Serialize, Deserialize, Clone, Debug)] +#[derive(Serialize, Deserialize, Clone, Derivative)] +#[derivative(Debug)] #[serde(tag = "auth")] pub enum ImapAuth { /// Use plain username/password authentication #[serde(rename = "plain")] - Plain { username: String, password: String }, + Plain { + username: String, + + #[derivative(Debug = "ignore")] + password: String, + }, } /// Describes when to perform the TLS handshake diff --git a/daemon/src/main.rs b/daemon/src/main.rs index 68f11d9..accb49a 100644 --- a/daemon/src/main.rs +++ b/daemon/src/main.rs @@ -6,6 +6,8 @@ extern crate anyhow; extern crate log; #[macro_use] extern crate futures; +#[macro_use] +extern crate derivative; mod config; mod mail; diff --git a/imap/src/proto/command.rs b/imap/src/proto/command.rs index 713a51f..eabcd6d 100644 --- a/imap/src/proto/command.rs +++ b/imap/src/proto/command.rs @@ -84,7 +84,7 @@ impl DisplayBytes for Command { #[cfg(feature = "rfc2177")] Command::Idle => write_bytes!(w, b"IDLE"), - _ => Ok(()), + _ => todo!("unimplemented command"), } } } diff --git a/imap/src/proto/rfc3501.rs b/imap/src/proto/rfc3501.rs index ad1e7b7..80b0c38 100644 --- a/imap/src/proto/rfc3501.rs +++ b/imap/src/proto/rfc3501.rs @@ -19,7 +19,7 @@ use super::response::{ }; use super::rfc2234::{is_char, is_cr, is_ctl, is_digit, is_dquote, is_lf, is_sp, CRLF, DQUOTE, SP}; -#[macro_export] +/// Grammar rule `T / nil` produces `Option` macro_rules! opt_nil { ($t:expr) => { alt((map($t, Some), map(crate::proto::rfc3501::nil, |_| None))) diff --git a/mbsync/Cargo.toml b/mbsync/Cargo.toml new file mode 100644 index 0000000..a9ab2e4 --- /dev/null +++ b/mbsync/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "mbsync" +version = "0.1.0" +edition = "2018" + +[dependencies] +anyhow = "1.0.42" +bitflags = "1.2.1" +clap = "3.0.0-beta.2" +derivative = "2.2.0" +derive_builder = "0.10.2" +panorama-imap = { path = "../imap" } +tokio = { version = "1.9.0", features = ["full"] } diff --git a/mbsync/src/config.rs b/mbsync/src/config.rs new file mode 100644 index 0000000..255991c --- /dev/null +++ b/mbsync/src/config.rs @@ -0,0 +1,315 @@ +use std::collections::HashMap; +use std::fs::File; +use std::io::{BufRead, BufReader, Read}; +use std::path::{Path, PathBuf}; + +use anyhow::Result; + +pub fn read_from_file(path: impl AsRef) -> Result { + let path = path.as_ref(); + let file = File::open(path)?; + read_from_reader(file) +} + +pub fn read_from_reader(r: R) -> Result { + let r = BufReader::new(r); + + let mut accounts = HashMap::new(); + let mut stores = HashMap::new(); + let mut channels = HashMap::new(); + + enum Section { + ImapAccount(ImapAccountBuilder), + ImapStore(ImapStoreBuilder), + MaildirStore(MaildirStoreBuilder), + Channel(ChannelBuilder), + } + let mut current_section = None; + let finish_section = |accounts: &mut HashMap<_, _>, + stores: &mut HashMap<_, _>, + channels: &mut HashMap<_, _>, + current_section: &mut Option
| + -> Result<()> { + match current_section { + Some(Section::ImapAccount(builder)) => { + let account = builder.build()?; + accounts.insert(account.name.clone(), Account::Imap(account)); + } + Some(Section::ImapStore(builder)) => { + let store = builder.build()?; + stores.insert(store.name.clone(), Store::Imap(store)); + } + Some(Section::MaildirStore(builder)) => { + let store = builder.build()?; + stores.insert(store.name.clone(), Store::Maildir(store)); + } + Some(Section::Channel(builder)) => { + let channel = builder.build()?; + channels.insert(channel.name.clone(), channel); + } + None => {} + }; + *current_section = None; + Ok(()) + }; + + for line in r.lines() { + let line = line?.trim().to_string(); + if line.is_empty() || line.starts_with('#') { + continue; + } + + let parts = line.split(" ").collect::>(); + + match parts[0].to_lowercase().as_str() { + "imapaccount" => { + finish_section( + &mut accounts, + &mut stores, + &mut channels, + &mut current_section, + )?; + let mut builder = ImapAccountBuilder::default(); + builder.name(parts[1].to_string()); + current_section = Some(Section::ImapAccount(builder)); + } + "host" => match current_section.as_mut() { + Some(Section::ImapAccount(ref mut builder)) => { + builder.host(parts[1].to_string()); + } + _ => panic!("unexpected host keyword"), + }, + "user" => match current_section.as_mut() { + Some(Section::ImapAccount(ref mut builder)) => { + builder.user(parts[1].to_string()); + } + _ => panic!("unexpected user keyword"), + }, + "pass" => match current_section.as_mut() { + Some(Section::ImapAccount(ref mut builder)) => { + builder.pass(parts[1].to_string()); + } + _ => panic!("unexpected pass keyword"), + }, + "ssltype" => match current_section.as_mut() { + Some(Section::ImapAccount(ref mut builder)) => { + builder.ssltype(match parts[1].to_lowercase().as_str() { + "none" => SslType::None, + "starttls" => SslType::Starttls, + "imaps" => SslType::Imaps, + unknown => panic!("unknown ssl type '{}'", unknown), + }); + } + _ => panic!("unexpected ssltype keyword"), + }, + + "imapstore" => { + finish_section( + &mut accounts, + &mut stores, + &mut channels, + &mut current_section, + )?; + let mut builder = ImapStoreBuilder::default(); + builder.name(parts[1].to_string()); + current_section = Some(Section::ImapStore(builder)); + } + "account" => match current_section.as_mut() { + Some(Section::ImapStore(ref mut builder)) => { + builder.account(parts[1].to_string()); + } + _ => panic!("unexpected account keyword"), + }, + + "maildirstore" => { + finish_section( + &mut accounts, + &mut stores, + &mut channels, + &mut current_section, + )?; + let mut builder = MaildirStoreBuilder::default(); + builder.name(parts[1].to_string()); + current_section = Some(Section::MaildirStore(builder)); + } + "path" => match current_section.as_mut() { + Some(Section::MaildirStore(ref mut builder)) => { + builder.path(PathBuf::from(parts[1].to_string())); + } + _ => panic!("unexpected path keyword"), + }, + "inbox" => match current_section.as_mut() { + Some(Section::MaildirStore(ref mut builder)) => { + builder.inbox(PathBuf::from(parts[1].to_string())); + } + _ => panic!("unexpected inbox keyword"), + }, + "subfolders" => match current_section.as_mut() { + Some(Section::MaildirStore(ref mut builder)) => { + builder.subfolders(match parts[1].to_lowercase().as_str() { + "verbatim" => MaildirSubfolderStyle::Verbatim, + "maildir++" => MaildirSubfolderStyle::Maildirpp, + "legacy" => MaildirSubfolderStyle::Legacy, + unknown => panic!("unknown subfolder style '{}'", unknown), + }); + } + _ => panic!("unexpected subfolders keyword"), + }, + + "channel" => { + finish_section( + &mut accounts, + &mut stores, + &mut channels, + &mut current_section, + )?; + let mut builder = ChannelBuilder::default(); + builder.name(parts[1].to_string()); + current_section = Some(Section::Channel(builder)); + } + "far" => match current_section.as_mut() { + Some(Section::Channel(ref mut builder)) => { + builder.far(parts[1].trim_matches(':').to_string()); + } + _ => panic!("unexpected far keyword"), + }, + "near" => match current_section.as_mut() { + Some(Section::Channel(ref mut builder)) => { + builder.near(parts[1].trim_matches(':').to_string()); + } + _ => panic!("unexpected near keyword"), + }, + "sync" => match current_section.as_mut() { + Some(Section::Channel(ref mut builder)) => { + builder.sync(parts[1..].iter().fold(ChannelSyncOps::empty(), |a, b| { + a | match b.to_lowercase().as_str() { + "none" => ChannelSyncOps::empty(), + "pull" => ChannelSyncOps::PULL, + "push" => ChannelSyncOps::PUSH, + "new" => ChannelSyncOps::NEW, + "renew" => ChannelSyncOps::RENEW, + "delete" => ChannelSyncOps::DELETE, + "flags" => ChannelSyncOps::FLAGS, + "all" => ChannelSyncOps::all(), + unknown => panic!("unknown sync op '{}'", unknown), + } + })); + } + _ => panic!("unexpected near keyword"), + }, + + unknown => panic!("unknown keyword '{}'", unknown), + } + } + + finish_section( + &mut accounts, + &mut stores, + &mut channels, + &mut current_section, + )?; + + let config = Config { + accounts, + stores, + channels, + }; + check_config(&config)?; + Ok(config) +} + +fn check_config(config: &Config) -> Result<()> { + for store in config.stores.values() { + if let Store::Imap(store) = store { + ensure!(config.accounts.contains_key(&store.account)); + } + } + + for channel in config.channels.values() { + ensure!(config.stores.contains_key(&channel.near)); + ensure!(config.stores.contains_key(&channel.far)); + } + + Ok(()) +} + +#[derive(Debug)] +pub struct Config { + pub accounts: HashMap, + pub stores: HashMap, + pub channels: HashMap, +} + +#[derive(Debug)] +pub enum Account { + Imap(ImapAccount), +} + +#[derive(Derivative, Builder)] +#[derivative(Debug)] +pub struct ImapAccount { + pub name: String, + pub host: String, + #[builder(default)] + pub port: Option, + #[builder(default = "20")] + pub timeout: u32, + pub user: String, + pub ssltype: SslType, + + #[derivative(Debug = "ignore")] + pub pass: String, +} + +#[derive(Debug, Clone)] +pub enum SslType { + None, + Starttls, + Imaps, +} + +#[derive(Debug)] +pub enum Store { + Maildir(MaildirStore), + Imap(ImapStore), +} + +#[derive(Debug, Builder)] +pub struct MaildirStore { + pub name: String, + pub path: PathBuf, + pub inbox: PathBuf, + pub subfolders: MaildirSubfolderStyle, +} + +#[derive(Debug, Clone)] +pub enum MaildirSubfolderStyle { + Verbatim, + Maildirpp, + Legacy, +} + +#[derive(Debug, Builder)] +pub struct ImapStore { + pub name: String, + pub account: String, +} + +#[derive(Debug, Builder)] +pub struct Channel { + pub name: String, + pub far: String, + pub near: String, + pub sync: ChannelSyncOps, +} + +bitflags! { + pub struct ChannelSyncOps: u32 { + const PULL = 1 << 1; + const PUSH = 1 << 2; + const NEW = 1 << 3; + const RENEW = 1 << 4; + const DELETE = 1 << 5; + const FLAGS = 1 << 6; + } +} diff --git a/mbsync/src/lib.rs b/mbsync/src/lib.rs new file mode 100644 index 0000000..92802af --- /dev/null +++ b/mbsync/src/lib.rs @@ -0,0 +1,11 @@ +#[macro_use] +extern crate anyhow; +#[macro_use] +extern crate bitflags; +#[macro_use] +extern crate derivative; +#[macro_use] +extern crate derive_builder; + +pub mod config; +pub mod store; \ No newline at end of file diff --git a/mbsync/src/main.rs b/mbsync/src/main.rs new file mode 100644 index 0000000..2aa17d6 --- /dev/null +++ b/mbsync/src/main.rs @@ -0,0 +1,36 @@ +use std::path::PathBuf; + +use anyhow::Result; +use clap::Clap; +use mbsync::{config, store}; + +#[derive(Debug, Clap)] +struct Opt { + /// The path to the config file (defaults to ~/.mbsyncrc). + #[clap(name = "config", long = "config", short = 'c')] + config_path: Option, +} + +#[tokio::main] +async fn main() -> Result<()> { + let opt = Opt::parse(); + println!("opts: {:?}", opt); + + let config_path = match opt.config_path { + Some(path) => path, + None => PathBuf::from(env!("HOME")).join(".mbsyncrc"), + }; + println!("config path: {:?}", config_path); + + let config = config::read_from_file(&config_path)?; + println!("config: {:?}", config); + + for channel in config.channels.values() { + println!("beginning to sync {}", channel.name); + + let far = store::open(&config, &channel.far).await?; + let near = store::open(&config, &channel.near).await?; + } + + Ok(()) +} diff --git a/mbsync/src/store.rs b/mbsync/src/store.rs new file mode 100644 index 0000000..9a25989 --- /dev/null +++ b/mbsync/src/store.rs @@ -0,0 +1,71 @@ +use anyhow::Result; +use panorama_imap::client::{auth::Login, ClientAuthenticated, ConfigBuilder}; + +use crate::config::{Account, Config, SslType, Store}; + +pub async fn open(config: &Config, store_name: impl AsRef) -> Result { + let store = config + .stores + .get(store_name.as_ref()) + .expect("already checked by config reader"); + + match store { + Store::Imap(store) => { + let account = config + .accounts + .get(&store.account) + .expect("already checked by config reader"); + match account { + Account::Imap(account) => { + let port = account.port.unwrap_or_else(|| match account.ssltype { + SslType::None | SslType::Starttls => 143, + SslType::Imaps => 993, + }); + let tls = match account.ssltype { + SslType::None | SslType::Starttls => false, + SslType::Imaps => true, + }; + let mut client = ConfigBuilder::default() + .hostname(account.host.clone()) + .port(port) + .tls(tls) + .open() + .await?; + + if let SslType::Starttls = account.ssltype { + client = client.upgrade().await?; + } + + println!("connected"); + + let login = Login { + username: account.user.clone(), + password: account.pass.clone(), + }; + let client = client.auth(login).await?; + + println!("authenticated"); + Ok(OpenedStore::Imap(ImapStore { client })) + } + } + } + Store::Maildir(store) => { + todo!() + } + } +} + +pub enum OpenedStore { + Imap(ImapStore), + Maildir(MaildirStore), +} + +impl OpenedStore { + +} + +pub struct ImapStore { + client: ClientAuthenticated, +} + +pub struct MaildirStore {} diff --git a/proto-common/src/rule.rs b/proto-common/src/rule.rs index 8da01b8..65504ac 100644 --- a/proto-common/src/rule.rs +++ b/proto-common/src/rule.rs @@ -1,7 +1,8 @@ #[allow(unused_imports)] use crate::{Bytes, VResult}; -/// Defines a new parser rule that operates on [`Bytes`] and produces a [`VResult`]. +/// Defines a new parser rule that operates on [`Bytes`] and produces a +/// [`VResult`]. #[macro_export] macro_rules! rule { ($vis:vis $name:ident : $ret:ty => $expr:expr) => { diff --git a/smtp/src/client/mod.rs b/smtp/src/client/mod.rs index 8b13789..07d8692 100644 --- a/smtp/src/client/mod.rs +++ b/smtp/src/client/mod.rs @@ -1 +1,2 @@ - +//! High-level SMTP Client +//! --- diff --git a/smtp/src/lib.rs b/smtp/src/lib.rs index ead41dc..daae022 100644 --- a/smtp/src/lib.rs +++ b/smtp/src/lib.rs @@ -1,2 +1,5 @@ -mod client; +#[macro_use] +extern crate panorama_proto_common; + +pub mod client; pub mod proto; diff --git a/smtp/src/proto/mod.rs b/smtp/src/proto/mod.rs index d3756d1..1da09da 100644 --- a/smtp/src/proto/mod.rs +++ b/smtp/src/proto/mod.rs @@ -1,3 +1,5 @@ //! Helper functions for manipulating the wire protocol. #![allow(non_snake_case, dead_code)] + +pub mod rfc5321; diff --git a/smtp/src/proto/rfc5321.rs b/smtp/src/proto/rfc5321.rs index 9ccdb1c..8f8afea 100644 --- a/smtp/src/proto/rfc5321.rs +++ b/smtp/src/proto/rfc5321.rs @@ -3,3 +3,6 @@ //! //! Grammar from +use panorama_proto_common::{tagi, Bytes}; + +rule!(pub ehlo : Bytes => tagi(b"EHLO"));