Split into crates 2
parent
3c582f7729
commit
ae36860ad1
|
@ -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]]
|
||||
|
|
137
README.md
137
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
|
||||
```
|
||||
|
||||
<details><summary>output</summary>
|
||||
|
||||
```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: "<ejduu.fddf8sgen4j7@localhost>",
|
||||
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 <test-announce@localhost>
|
||||
TRACE - Is post related to list [#1 test] Test list <test@localhost>? false
|
||||
```
|
||||
</details>
|
||||
|
||||
```shell
|
||||
$ cat list-post.eml | cargo run --bin mpot -- -vvvvvv post --dry-run
|
||||
```
|
||||
|
||||
<details><summary>output</summary>
|
||||
|
||||
```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: "<ejduu.sddf8sgen4j7@localhost>",
|
||||
In-Reply-To: None,
|
||||
References: None,
|
||||
Hash: 10220641455578979007,
|
||||
}
|
||||
TRACE - Is post related to list [#1 test] Test list <test@localhost>? false
|
||||
TRACE - Is post related to list [#2 test-announce] test announcements <test-announce@localhost>? true
|
||||
TRACE - Examining list "test announcements" <test-announce@localhost>
|
||||
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: [],
|
||||
},
|
||||
},
|
||||
)
|
||||
```
|
||||
</details>
|
||||
|
|
|
@ -18,3 +18,4 @@ path = "src/main.rs"
|
|||
[dependencies]
|
||||
mailpot = { version = "0.1.0", path = "../core" }
|
||||
structopt = "0.3.16"
|
||||
stderrlog = "0.4"
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
# mailpot-cli
|
||||
|
||||
```shell
|
||||
cargo run --bin mpot -- help
|
||||
```
|
|
@ -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<PathBuf>,
|
||||
#[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<stderrlog::Timestamp>,
|
||||
}
|
||||
|
||||
#[derive(Debug, StructOpt)]
|
||||
|
@ -108,6 +118,8 @@ enum ListCommand {
|
|||
receive_duplicates: Option<bool>,
|
||||
#[structopt(long)]
|
||||
receive_own_posts: Option<bool>,
|
||||
#[structopt(long)]
|
||||
enabled: Option<bool>,
|
||||
},
|
||||
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);
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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;
|
|
@ -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;
|
138
core/src/db.rs
138
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::<Vec<String>>();
|
||||
let tos = env.to().iter().cloned().collect::<Vec<_>>();
|
||||
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(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
#![recursion_limit = "1024"]
|
||||
//#![warn(missing_docs)]
|
||||
|
||||
use log::{info, trace};
|
||||
#[macro_use]
|
||||
extern crate error_chain;
|
||||
#[macro_use]
|
||||
|
|
|
@ -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<String> {
|
||||
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<ListOwner> 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),
|
||||
}
|
||||
|
|
|
@ -77,8 +77,10 @@ impl PostFilter for PostRightsCheck {
|
|||
self: Box<Self>,
|
||||
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::<Vec<Address>>();
|
||||
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<Self>,
|
||||
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<Self>,
|
||||
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<Self>,
|
||||
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<Self>,
|
||||
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:
|
||||
|
|
|
@ -28,6 +28,7 @@ table! {
|
|||
receive_duplicates -> Bool,
|
||||
receive_own_posts -> Bool,
|
||||
receive_confirmation -> Bool,
|
||||
enabled -> Bool,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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(),
|
||||
|
|
Loading…
Reference in New Issue