diff --git a/Cargo.lock b/Cargo.lock
index efdfabe..e183bf0 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -131,12 +131,12 @@ checksum = "065374052e7df7ee4047b1160cca5e1467a12351a40b3da123c870ba0b8eda2a"
[[package]]
name = "atty"
-version = "0.2.14"
+version = "0.2.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
+checksum = "9a7d5b8723950951411ee34d271d99dddcc2035a16ab25310ea2c8cfd4369652"
dependencies = [
- "hermit-abi",
"libc",
+ "termion",
"winapi 0.3.9",
]
@@ -378,7 +378,7 @@ dependencies = [
"autocfg",
"cfg-if",
"crossbeam-utils",
- "lazy_static",
+ "lazy_static 1.4.0",
"maybe-uninit",
"memoffset",
"scopeguard",
@@ -403,7 +403,7 @@ checksum = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8"
dependencies = [
"autocfg",
"cfg-if",
- "lazy_static",
+ "lazy_static 1.4.0",
]
[[package]]
@@ -893,6 +893,7 @@ checksum = "86b45e59b16c76b11bf9738fd5d38879d3bd28ad292d7b313608becb17ae2df9"
dependencies = [
"autocfg",
"hashbrown",
+ "serde",
]
[[package]]
@@ -946,6 +947,12 @@ version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a91d884b6667cd606bb5a69aa0c99ba811a115fc68915e7056ec08a46e93199a"
+[[package]]
+name = "lazy_static"
+version = "0.2.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "76f033c7ad61445c5b347c7382dd1237847eb1bce590fe50365dcb33d546be73"
+
[[package]]
name = "lazy_static"
version = "1.4.0"
@@ -1036,6 +1043,7 @@ dependencies = [
"chrono",
"diesel",
"error-chain",
+ "log 0.4.11",
"melib",
"rusqlite",
"serde",
@@ -1049,6 +1057,7 @@ name = "mailpot-cli"
version = "0.1.0"
dependencies = [
"mailpot",
+ "stderrlog",
"structopt",
]
@@ -1085,11 +1094,13 @@ dependencies = [
"data-encoding",
"encoding",
"futures",
+ "indexmap",
"libc",
"libloading",
"native-tls",
"nix",
"nom",
+ "once_cell",
"serde",
"serde_derive",
"smallvec",
@@ -1181,7 +1192,7 @@ version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b0d88c06fe90d5ee94048ba40409ef1d9315d86f6f38c2efdaad4fb50c58b2d"
dependencies = [
- "lazy_static",
+ "lazy_static 1.4.0",
"libc",
"log 0.4.11",
"openssl",
@@ -1275,6 +1286,12 @@ dependencies = [
"libc",
]
+[[package]]
+name = "numtoa"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b8f8bdf33df195859076e54ab11ee78a1b208382d3a26ec40d142ffc1ecc49ef"
+
[[package]]
name = "object"
version = "0.20.0"
@@ -1302,7 +1319,7 @@ dependencies = [
"bitflags",
"cfg-if",
"foreign-types",
- "lazy_static",
+ "lazy_static 1.4.0",
"libc",
"openssl-sys",
]
@@ -1545,6 +1562,15 @@ version = "0.1.57"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce"
+[[package]]
+name = "redox_termios"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7e891cfe48e9100a70a3b6eb652fef28920c117d366339687bd5576160db0f76"
+dependencies = [
+ "redox_syscall",
+]
+
[[package]]
name = "remove_dir_all"
version = "0.5.3"
@@ -1668,7 +1694,7 @@ version = "0.1.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f05ba609c234e60bee0d547fe94a4c7e9da733d1c962cf6e59efa4cd9c8bc75"
dependencies = [
- "lazy_static",
+ "lazy_static 1.4.0",
"winapi 0.3.9",
]
@@ -1738,6 +1764,12 @@ dependencies = [
"serde",
]
+[[package]]
+name = "sha1"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2579985fda508104f7587689507983eadd6a6e84dd35d6d115361f530916fa0d"
+
[[package]]
name = "sha2"
version = "0.8.2"
@@ -1810,6 +1842,19 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
+[[package]]
+name = "stderrlog"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "32e5ee9b90a5452c570a0b0ac1c99ae9498db7e56e33d74366de7f2a7add7f25"
+dependencies = [
+ "atty",
+ "chrono",
+ "log 0.4.11",
+ "termcolor",
+ "thread_local",
+]
+
[[package]]
name = "strsim"
version = "0.8.0"
@@ -1823,7 +1868,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "de5472fb24d7e80ae84a7801b7978f95a19ec32cb1876faea59ab711eb901976"
dependencies = [
"clap",
- "lazy_static",
+ "lazy_static 1.4.0",
"structopt-derive",
]
@@ -1888,6 +1933,27 @@ dependencies = [
"winapi 0.3.9",
]
+[[package]]
+name = "termcolor"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bb6bfa289a4d7c5766392812c0a1f4c1ba45afa1ad47803c11e1f407d846d75f"
+dependencies = [
+ "winapi-util",
+]
+
+[[package]]
+name = "termion"
+version = "1.5.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c22cec9d8978d906be5ac94bceb5a010d885c626c4c8855721a4dbd20e3ac905"
+dependencies = [
+ "libc",
+ "numtoa",
+ "redox_syscall",
+ "redox_termios",
+]
+
[[package]]
name = "textwrap"
version = "0.11.0"
@@ -1897,6 +1963,16 @@ dependencies = [
"unicode-width",
]
+[[package]]
+name = "thread_local"
+version = "0.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1697c4b57aeeb7a536b647165a2825faddffb1d3bad386d507709bd51a90bb14"
+dependencies = [
+ "lazy_static 0.2.11",
+ "unreachable",
+]
+
[[package]]
name = "time"
version = "0.1.43"
@@ -2010,6 +2086,15 @@ dependencies = [
"subtle 2.2.3",
]
+[[package]]
+name = "unreachable"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "382810877fe448991dfc7f0dd6e3ae5d58088fd0ea5e35189655f84e6814fa56"
+dependencies = [
+ "void",
+]
+
[[package]]
name = "url"
version = "1.7.2"
@@ -2029,6 +2114,7 @@ checksum = "9fde2f6a4bea1d6e007c4ad38c6839fa71cbb63b6dbf5b595aa38dc9b1093c11"
dependencies = [
"rand",
"serde",
+ "sha1",
]
[[package]]
diff --git a/README.md b/README.md
index a36991f..5fea023 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,4 @@
-# Mailpot
-## WIP mailing list manager
+# Mailpot - WIP mailing list manager
Crates:
@@ -7,6 +6,19 @@ Crates:
- `cli` a command line tool to manage lists
- `rest-http` a REST http server to manage lists
+## Project goals
+
+- easy setup
+- extensible through Rust API as a library
+- extensible through HTTP REST API as an HTTP server, with webhooks
+- basic management through CLI
+- replaceable lightweight web archiver
+- custom storage?
+- useful for both newsletters, discussions
+
+
+## Examples
+
```text
% mpot help
mailpot 0.1.0
@@ -31,3 +43,124 @@ SUBCOMMANDS:
list-lists Lists all registered mailing lists
post Post message from STDIN to list
```
+
+### Receiving mail
+
+```shell
+$ cat list-request.eml | cargo run --bin mpot -- -vvvvvv post --dry-run
+```
+
+output
+
+```shell
+TRACE - Received envelope to post: Envelope {
+ Subject: "unsubscribe",
+ Date: "Tue, 04 Aug 2020 14:10:13 +0300",
+ From: [
+ Address::Mailbox {
+ display_name: "Mxxxx Pxxxxxxxxxxxx",
+ address_spec: "exxxxx@localhost",
+ },
+ ],
+ To: [
+ Address::Mailbox {
+ display_name: "",
+ address_spec: "test-announce+request@localhost",
+ },
+ ],
+ Message-ID: "",
+ In-Reply-To: None,
+ References: None,
+ Hash: 12581897380059220314,
+}
+TRACE - unsubscribe action for addresses [Address::Mailbox { display_name: "Mxxxx Pxxxxxxxxxxxx", address_spec: "exxxxx@localhost" }] in list [#2 test-announce] test announcements
+TRACE - Is post related to list [#1 test] Test list ? false
+```
+
+
+```shell
+$ cat list-post.eml | cargo run --bin mpot -- -vvvvvv post --dry-run
+```
+
+output
+
+```shell
+TRACE - Received envelope to post: Envelope {
+ Subject: "[test-announce] new test releases",
+ Date: "Tue, 04 Aug 2020 14:10:13 +0300",
+ From: [
+ Address::Mailbox {
+ display_name: "Mxxxx Pxxxxxxxxxxxx",
+ address_spec: "exxxxx@localhost",
+ },
+ ],
+ To: [
+ Address::Mailbox {
+ display_name: "",
+ address_spec: "test-announce@localhost",
+ },
+ ],
+ Message-ID: "",
+ In-Reply-To: None,
+ References: None,
+ Hash: 10220641455578979007,
+}
+TRACE - Is post related to list [#1 test] Test list ? false
+TRACE - Is post related to list [#2 test-announce] test announcements ? true
+TRACE - Examining list "test announcements"
+TRACE - List members [
+ ListMembership {
+ list: 2,
+ address: "exxxxx@localhost",
+ name: None,
+ digest: false,
+ hide_address: false,
+ receive_duplicates: false,
+ receive_own_posts: true,
+ receive_confirmation: true,
+ enabled: true,
+ },
+]
+TRACE - Running FixCRLF filter
+TRACE - Running PostRightsCheck filter
+TRACE - Running AddListHeaders filter
+TRACE - Running FinalizeRecipients filter
+TRACE - examining member ListMembership { list: 2, address: "exxxxx@localhost", name: None, digest: false, hide_address: false, receive_duplicates: false, receive_own_posts: true, receive_confirmation: true, enabled: true }
+TRACE - member is submitter
+TRACE - Member gets copy
+TRACE - result Ok(
+ Post {
+ list: MailingList {
+ pk: 2,
+ name: "test announcements",
+ id: "test-announce",
+ address: "test-announce@localhost",
+ description: None,
+ archive_url: None,
+ },
+ from: Address::Mailbox {
+ display_name: "Mxxxx Pxxxxxxxxxxxx",
+ address_spec: "exxxxx@localhost",
+ },
+ members: 1,
+ bytes: 851,
+ policy: None,
+ to: [
+ Address::Mailbox {
+ display_name: "",
+ address_spec: "test-announce@localhost",
+ },
+ ],
+ action: Accept {
+ recipients: [
+ Address::Mailbox {
+ display_name: "",
+ address_spec: "exxxxx@localhost",
+ },
+ ],
+ digests: [],
+ },
+ },
+)
+```
+
diff --git a/cli/Cargo.toml b/cli/Cargo.toml
index 9ea7587..f226625 100644
--- a/cli/Cargo.toml
+++ b/cli/Cargo.toml
@@ -18,3 +18,4 @@ path = "src/main.rs"
[dependencies]
mailpot = { version = "0.1.0", path = "../core" }
structopt = "0.3.16"
+stderrlog = "0.4"
diff --git a/cli/README.md b/cli/README.md
new file mode 100644
index 0000000..f5e323d
--- /dev/null
+++ b/cli/README.md
@@ -0,0 +1,5 @@
+# mailpot-cli
+
+```shell
+cargo run --bin mpot -- help
+```
diff --git a/cli/src/main.rs b/cli/src/main.rs
index 550e42b..dd9e320 100644
--- a/cli/src/main.rs
+++ b/cli/src/main.rs
@@ -18,6 +18,7 @@
*/
extern crate mailpot;
+extern crate stderrlog;
pub use mailpot::config::*;
pub use mailpot::db::*;
@@ -44,6 +45,15 @@ struct Opt {
config: Option,
#[structopt(flatten)]
cmd: Command,
+ /// Silence all output
+ #[structopt(short = "q", long = "quiet")]
+ quiet: bool,
+ /// Verbose mode (-v, -vv, -vvv, etc)
+ #[structopt(short = "v", long = "verbose", parse(from_occurrences))]
+ verbose: usize,
+ /// Timestamp (sec, ms, ns, none)
+ #[structopt(short = "t", long = "timestamp")]
+ ts: Option,
}
#[derive(Debug, StructOpt)]
@@ -108,6 +118,8 @@ enum ListCommand {
receive_duplicates: Option,
#[structopt(long)]
receive_own_posts: Option,
+ #[structopt(long)]
+ enabled: Option,
},
RemoveMember {
#[structopt(long)]
@@ -144,6 +156,21 @@ fn run_app(opt: Opt) -> Result<()> {
} else {
for l in lists {
println!("- {} {:?}", l.id, l);
+ let list_owners = db.get_list_owners(l.pk)?;
+ if list_owners.is_empty() {
+ println!("\tList owners: None");
+ } else {
+ println!("\tList owners:");
+ for o in db.get_list_owners(l.pk)? {
+ println!("\t- {}", o);
+ }
+ }
+ if let Some(s) = db.get_list_policy(l.pk)? {
+ println!("\tList policy: {}", s);
+ } else {
+ println!("\tList policy: None");
+ }
+ println!("");
}
}
}
@@ -176,6 +203,7 @@ fn run_app(opt: Opt) -> Result<()> {
receive_confirmation,
receive_duplicates,
receive_own_posts,
+ enabled,
} => {
db.add_member(
list.pk,
@@ -188,6 +216,7 @@ fn run_app(opt: Opt) -> Result<()> {
receive_confirmation: receive_confirmation.unwrap_or(true),
receive_duplicates: receive_duplicates.unwrap_or(true),
receive_own_posts: receive_own_posts.unwrap_or(false),
+ enabled: enabled.unwrap_or(true),
},
)?;
}
@@ -260,6 +289,14 @@ fn run_app(opt: Opt) -> Result<()> {
fn main() -> std::result::Result<(), i32> {
let opt = Opt::from_args();
+ stderrlog::new()
+ .module(module_path!())
+ .module("mailpot")
+ .quiet(opt.quiet)
+ .verbosity(opt.verbose)
+ .timestamp(opt.ts.unwrap_or(stderrlog::Timestamp::Off))
+ .init()
+ .unwrap();
if let Err(err) = run_app(opt) {
println!("{}", err);
std::process::exit(-1);
diff --git a/core/Cargo.toml b/core/Cargo.toml
index 5296a56..fb81e1c 100644
--- a/core/Cargo.toml
+++ b/core/Cargo.toml
@@ -20,4 +20,5 @@ rusqlite = {version = "0.20.0"}
serde = { version = "1.0.114" }
serde_json = "1.0.57"
toml = "^0.5"
+log = "0.4"
xdg = "2.1.0"
diff --git a/core/migrations/2020-09-09-165759_membership_enabled/down.sql b/core/migrations/2020-09-09-165759_membership_enabled/down.sql
new file mode 100644
index 0000000..ae50177
--- /dev/null
+++ b/core/migrations/2020-09-09-165759_membership_enabled/down.sql
@@ -0,0 +1,35 @@
+-- This file should undo anything in `up.sql`
+
+BEGIN TRANSACTION;
+PRAGMA foreign_keys = false;
+CREATE TEMPORARY TABLE membership_backup(
+ list INTEGER NOT NULL,
+ address TEXT NOT NULL,
+ name TEXT,
+ enabled BOOLEAN CHECK (enabled in (0, 1)) NOT NULL DEFAULT 1,
+ digest BOOLEAN CHECK (digest in (0, 1)) NOT NULL DEFAULT 0,
+ hide_address BOOLEAN CHECK (hide_address in (0, 1)) NOT NULL DEFAULT 0,
+ receive_duplicates BOOLEAN CHECK (receive_duplicates in (0, 1)) NOT NULL DEFAULT 1,
+ receive_own_posts BOOLEAN CHECK (receive_own_posts in (0, 1)) NOT NULL DEFAULT 0,
+ receive_confirmation BOOLEAN CHECK (receive_confirmation in (0, 1)) NOT NULL DEFAULT 1,
+ PRIMARY KEY (list, address),
+ FOREIGN KEY (list) REFERENCES mailing_lists(pk) ON DELETE CASCADE
+);
+INSERT INTO membership_backup SELECT list,address,name,enabled,digest,hide_address,receive_own_posts,receive_duplicates,receive_confirmation FROM membership;
+DROP TABLE membership;
+CREATE TABLE membership(
+ list INTEGER NOT NULL,
+ address TEXT NOT NULL,
+ name TEXT,
+ digest BOOLEAN CHECK (digest in (0, 1)) NOT NULL DEFAULT 0,
+ hide_address BOOLEAN CHECK (hide_address in (0, 1)) NOT NULL DEFAULT 0,
+ receive_duplicates BOOLEAN CHECK (receive_duplicates in (0, 1)) NOT NULL DEFAULT 1,
+ receive_own_posts BOOLEAN CHECK (receive_own_posts in (0, 1)) NOT NULL DEFAULT 0,
+ receive_confirmation BOOLEAN CHECK (receive_confirmation in (0, 1)) NOT NULL DEFAULT 1,
+ PRIMARY KEY (list, address),
+ FOREIGN KEY (list) REFERENCES mailing_lists(pk) ON DELETE CASCADE
+);
+INSERT INTO membership SELECT list,address,name,digest,hide_address,receive_own_posts,receive_duplicates,receive_confirmation FROM membership_backup;
+DROP TABLE membership_backup;
+PRAGMA foreign_keys = true;
+COMMIT TRANSACTION;
diff --git a/core/migrations/2020-09-09-165759_membership_enabled/up.sql b/core/migrations/2020-09-09-165759_membership_enabled/up.sql
new file mode 100644
index 0000000..3871de1
--- /dev/null
+++ b/core/migrations/2020-09-09-165759_membership_enabled/up.sql
@@ -0,0 +1,5 @@
+-- Your SQL goes here
+
+PRAGMA foreign_keys = false;
+ALTER TABLE membership ADD COLUMN enabled BOOLEAN CHECK (enabled in (0, 1)) NOT NULL DEFAULT 1;
+PRAGMA foreign_keys = true;
diff --git a/core/src/db.rs b/core/src/db.rs
index 5d4e7c5..3d991aa 100644
--- a/core/src/db.rs
+++ b/core/src/db.rs
@@ -123,7 +123,7 @@ impl Database {
let db_path = Self::db_path()?;
let mut set_mode = false;
if !db_path.exists() {
- println!("Creating {} database in {}", DB_NAME, db_path.display());
+ info!("Creating {} database in {}", DB_NAME, db_path.display());
set_mode = true;
}
let conn = SqliteConnection::establish(&db_path.to_str().unwrap())?;
@@ -152,32 +152,75 @@ impl Database {
}
pub fn post(&self, env: Envelope, raw: &[u8]) -> Result<()> {
+ trace!("Received envelope to post: {:#?}", &env);
let mut lists = self.list_lists()?;
- let tos = env
- .to()
- .iter()
- .map(|addr| addr.get_email())
- .collect::>();
+ let tos = env.to().iter().cloned().collect::>();
if tos.is_empty() {
return Err("Envelope To: field is empty!".into());
}
if env.from().is_empty() {
return Err("Envelope From: field is empty!".into());
}
+ for t in &tos {
+ if let Some((addr, subaddr)) = t.subaddress("+") {
+ lists.retain(|list| {
+ if !addr.contains_address(&list.list_address()) {
+ return true;
+ }
+ if let Err(err) = self.request(
+ list,
+ match subaddr.as_str() {
+ "subscribe" | "request" if env.subject().trim() == "subscribe" => {
+ ListRequest::Subscribe
+ }
+ "unsubscribe" | "request" if env.subject().trim() == "unsubscribe" => {
+ ListRequest::Unsubscribe
+ }
+ "request" => ListRequest::Other(env.subject().trim().to_string()),
+ _ => {
+ trace!(
+ "unknown action = {} for addresses {:?} in list {}",
+ subaddr,
+ env.from(),
+ list
+ );
+ ListRequest::Other(subaddr.trim().to_string())
+ }
+ },
+ &env,
+ raw,
+ ) {
+ info!("Processing request returned error: {}", err);
+ }
+ false
+ });
+ }
+ }
- lists.retain(|list| tos.iter().any(|a| a == &list.address));
+ lists.retain(|list| {
+ trace!(
+ "Is post related to list {}? {}",
+ &list,
+ tos.iter().any(|a| a.contains_address(&list.list_address()))
+ );
+
+ tos.iter().any(|a| a.contains_address(&list.list_address()))
+ });
if lists.is_empty() {
- return Err("Envelope To: field doesn't contain any known lists!".into());
+ return Ok(());
}
let mut configuration = crate::config::Configuration::new();
crate::config::CONFIG.with(|f| {
configuration = f.borrow().clone();
});
+ trace!("Configuration is {:#?}", &configuration);
use crate::post::{Post, PostAction};
for mut list in lists {
+ trace!("Examining list {}", list.list_id());
let filters = self.get_list_filters(&list);
let memberships = self.list_members(list.pk)?;
+ trace!("List members {:#?}", &memberships);
let mut post = Post {
policy: self.get_list_policy(list.pk)?,
list_owners: self.get_list_owners(list.pk)?,
@@ -191,10 +234,9 @@ impl Database {
let result = filters
.into_iter()
.fold(Ok(&mut post), |p, f| p.and_then(|p| f.feed(p)));
- eprintln!("result {:?}", result);
+ trace!("result {:#?}", result);
let Post { bytes, action, .. } = post;
- eprintln!("send_mail {:?}", &configuration.send_mail);
match configuration.send_mail {
crate::config::SendMail::Smtp(ref smtp_conf) => {
let smtp_conf = smtp_conf.clone();
@@ -233,4 +275,80 @@ impl Database {
Ok(())
}
+
+ pub fn request(
+ &self,
+ list: &MailingList,
+ request: ListRequest,
+ env: &Envelope,
+ _raw: &[u8],
+ ) -> Result<()> {
+ match request {
+ ListRequest::Subscribe => {
+ trace!(
+ "subscribe action for addresses {:?} in list {}",
+ env.from(),
+ list
+ );
+
+ let list_policy = self.get_list_policy(list.pk)?;
+ let approval_needed = list_policy
+ .as_ref()
+ .map(|p| p.approval_needed)
+ .unwrap_or(false);
+ for f in env.from() {
+ let membership = ListMembership {
+ list: list.pk,
+ address: f.get_email(),
+ name: f.get_display_name(),
+ digest: false,
+ hide_address: false,
+ receive_duplicates: true,
+ receive_own_posts: false,
+ receive_confirmation: true,
+ enabled: !approval_needed,
+ };
+ if approval_needed {
+ //FIXME: send notification to list-owner
+ }
+ if let Err(_err) = self.add_member(list.pk, membership) {
+ //FIXME: send failure notice to f
+ } else {
+ //FIXME: send success notice
+ }
+ }
+ }
+ ListRequest::Unsubscribe => {
+ trace!(
+ "unsubscribe action for addresses {:?} in list {}",
+ env.from(),
+ list
+ );
+ for f in env.from() {
+ if let Err(_err) = self.remove_member(list.pk, &f.get_email()) {
+ //FIXME: send failure notice to f
+ } else {
+ //FIXME: send success notice to f
+ }
+ }
+ }
+ ListRequest::Other(ref req) if req == "owner" => {
+ trace!(
+ "list-owner mail action for addresses {:?} in list {}",
+ env.from(),
+ list
+ );
+ //FIXME: mail to list-owner
+ }
+ ListRequest::Other(ref req) => {
+ trace!(
+ "unknown request action {} for addresses {:?} in list {}",
+ req,
+ env.from(),
+ list
+ );
+ }
+ }
+ Ok(())
+ }
}
diff --git a/core/src/lib.rs b/core/src/lib.rs
index ebd2f8c..c33084f 100644
--- a/core/src/lib.rs
+++ b/core/src/lib.rs
@@ -20,6 +20,7 @@
#![recursion_limit = "1024"]
//#![warn(missing_docs)]
+use log::{info, trace};
#[macro_use]
extern crate error_chain;
#[macro_use]
diff --git a/core/src/models.rs b/core/src/models.rs
index 908ef80..1a85891 100644
--- a/core/src/models.rs
+++ b/core/src/models.rs
@@ -20,6 +20,8 @@
use super::*;
use schema::*;
+use melib::email::Address;
+
#[derive(Debug, Clone, Insertable, Queryable, Deserialize, Serialize)]
#[table_name = "mailing_lists"]
pub struct MailingList {
@@ -69,6 +71,10 @@ impl MailingList {
pub fn list_archive(&self) -> Option {
self.archive_url.as_ref().map(|url| format!("<{}>", url))
}
+
+ pub fn list_address(&self) -> Address {
+ Address::new(Some(self.name.clone()), self.address.clone())
+ }
}
#[derive(Debug, Clone, Insertable, Queryable, Deserialize, Serialize)]
@@ -82,36 +88,29 @@ pub struct ListMembership {
pub receive_duplicates: bool,
pub receive_own_posts: bool,
pub receive_confirmation: bool,
+ pub enabled: bool,
}
impl std::fmt::Display for ListMembership {
fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
- if let Some(name) = self.name.as_ref() {
- write!(
- fmt,
- "{} <{}> [digest: {}, hide_address: {}]",
- name, self.address, self.digest, self.hide_address
- )
- } else {
- write!(
- fmt,
- "{} [digest: {}, hide_address: {}]",
- self.address, self.digest, self.hide_address
- )
- }
+ write!(
+ fmt,
+ "{} [digest: {}, hide_address: {} {}]",
+ self.into_address(),
+ self.digest,
+ self.hide_address,
+ if self.enabled {
+ "enabled"
+ } else {
+ "not enabled"
+ },
+ )
}
}
impl ListMembership {
- pub fn into_address(&self) -> melib::email::Address {
- use melib::email::Address;
- use melib::email::StrBuilder;
- use melib::MailboxAddress;
- if let Some(name) = self.name.as_ref() {
- melib::make_address!(name, self.address)
- } else {
- melib::make_address!("", self.address)
- }
+ pub fn into_address(&self) -> Address {
+ Address::new(self.name.clone(), self.address.clone())
}
}
@@ -142,15 +141,7 @@ pub struct ListOwner {
impl std::fmt::Display for ListOwner {
fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
- if let Some(ref name) = self.name {
- write!(
- fmt,
- "[#{} {}] \"{}\" <{}>",
- self.pk, self.list, name, self.address
- )
- } else {
- write!(fmt, "[#{} {}] {}", self.pk, self.list, self.address)
- }
+ write!(fmt, "[#{} {}] {}", self.pk, self.list, self.into_address())
}
}
@@ -165,6 +156,20 @@ impl From for ListMembership {
receive_duplicates: true,
receive_own_posts: false,
receive_confirmation: true,
+ enabled: true,
}
}
}
+
+impl ListOwner {
+ pub fn into_address(&self) -> Address {
+ Address::new(self.name.clone(), self.address.clone())
+ }
+}
+
+#[derive(Clone, Debug, PartialEq, Eq)]
+pub enum ListRequest {
+ Subscribe,
+ Unsubscribe,
+ Other(String),
+}
diff --git a/core/src/post.rs b/core/src/post.rs
index 1306fc4..0177a63 100644
--- a/core/src/post.rs
+++ b/core/src/post.rs
@@ -77,8 +77,10 @@ impl PostFilter for PostRightsCheck {
self: Box,
post: &'p mut Post<'list>,
) -> std::result::Result<&'p mut Post<'list>, String> {
+ trace!("Running PostRightsCheck filter");
if let Some(ref policy) = post.policy {
if policy.announce_only {
+ trace!("post policy is announce_only");
let owner_addresses = post
.list_owners
.iter()
@@ -87,15 +89,23 @@ impl PostFilter for PostRightsCheck {
lm.into_address()
})
.collect::>();
+ trace!("Owner addresses are: {:#?}", &owner_addresses);
+ trace!("Envelope from is: {:?}", &post.from);
if !owner_addresses.iter().any(|addr| *addr == post.from) {
+ trace!("Envelope From does not include any owner");
return Err("You are not allowed to post on this list.".to_string());
}
} else if policy.subscriber_only {
+ trace!("post policy is subscriber_only");
let email_from = post.from.get_email();
+ trace!("post from is {:?}", &email_from);
+ trace!("post memberships are {:#?}", &post.memberships);
if !post.memberships.iter().any(|lm| lm.address == email_from) {
+ trace!("Envelope from is not subscribed to this list");
return Err("You are not subscribed to this list.".to_string());
}
} else if policy.approval_needed {
+ trace!("post policy says approval_needed");
post.action = PostAction::Defer {
reason: "Approval from the list's moderators is required.".to_string(),
};
@@ -112,6 +122,7 @@ impl PostFilter for FixCRLF {
self: Box,
post: &'p mut Post<'list>,
) -> std::result::Result<&'p mut Post<'list>, String> {
+ trace!("Running FixCRLF filter");
use std::io::prelude::*;
let mut new_vec = Vec::with_capacity(post.bytes.len());
for line in post.bytes.lines() {
@@ -130,6 +141,7 @@ impl PostFilter for AddListHeaders {
self: Box,
post: &'p mut Post<'list>,
) -> std::result::Result<&'p mut Post<'list>, String> {
+ trace!("Running AddListHeaders filter");
let (mut headers, body) = melib::email::parser::mail(&post.bytes).unwrap();
let list_id = post.list.list_id();
headers.push((&b"List-ID"[..], list_id.as_bytes()));
@@ -174,6 +186,7 @@ impl PostFilter for ArchivedAtLink {
self: Box,
post: &'p mut Post<'list>,
) -> std::result::Result<&'p mut Post<'list>, String> {
+ trace!("Running ArchivedAtLink filter");
Ok(post)
}
}
@@ -186,14 +199,20 @@ impl PostFilter for FinalizeRecipients {
self: Box,
post: &'p mut Post<'list>,
) -> std::result::Result<&'p mut Post<'list>, String> {
+ trace!("Running FinalizeRecipients filter");
let mut recipients = vec![];
let mut digests = vec![];
let email_from = post.from.get_email();
for member in post.memberships {
+ trace!("examining member {:?}", &member);
+ if member.address != email_from {
+ trace!("member is submitter");
+ }
if member.digest {
if (member.address == email_from && member.receive_own_posts)
|| (member.address != email_from)
{
+ trace!("Member gets digest");
digests.push(member.into_address());
}
continue;
@@ -201,6 +220,7 @@ impl PostFilter for FinalizeRecipients {
if (member.address == email_from && member.receive_own_posts)
|| (member.address != email_from)
{
+ trace!("Member gets copy");
recipients.push(member.into_address());
}
// TODO:
diff --git a/core/src/schema.rs b/core/src/schema.rs
index 0208b8f..501718d 100644
--- a/core/src/schema.rs
+++ b/core/src/schema.rs
@@ -28,6 +28,7 @@ table! {
receive_duplicates -> Bool,
receive_own_posts -> Bool,
receive_confirmation -> Bool,
+ enabled -> Bool,
}
}
diff --git a/core/src/schema.sql b/core/src/schema.sql
index da8ec67..0bc65ef 100644
--- a/core/src/schema.sql
+++ b/core/src/schema.sql
@@ -32,6 +32,7 @@ CREATE TABLE IF NOT EXISTS membership (
list INTEGER NOT NULL,
address TEXT NOT NULL,
name TEXT,
+ enabled BOOLEAN CHECK (enabled in (0, 1)) NOT NULL DEFAULT 1,
digest BOOLEAN CHECK (digest in (0, 1)) NOT NULL DEFAULT 0,
hide_address BOOLEAN CHECK (hide_address in (0, 1)) NOT NULL DEFAULT 0,
receive_duplicates BOOLEAN CHECK (receive_duplicates in (0, 1)) NOT NULL DEFAULT 1,
diff --git a/core/src/schema.sql.m4 b/core/src/schema.sql.m4
index 6fa1876..608ce93 100644
--- a/core/src/schema.sql.m4
+++ b/core/src/schema.sql.m4
@@ -36,6 +36,7 @@ CREATE TABLE IF NOT EXISTS membership (
list INTEGER NOT NULL,
address TEXT NOT NULL,
name TEXT,
+ BOOLEAN_TYPE(enabled) DEFAULT BOOLEAN_TRUE(),
BOOLEAN_TYPE(digest) DEFAULT BOOLEAN_FALSE(),
BOOLEAN_TYPE(hide_address) DEFAULT BOOLEAN_FALSE(),
BOOLEAN_TYPE(receive_duplicates) DEFAULT BOOLEAN_TRUE(),