From c31324d205d2690b6356044e7e3435ccacde9bec Mon Sep 17 00:00:00 2001 From: Gonne Date: Sat, 2 Nov 2024 19:22:50 +0100 Subject: [PATCH 01/23] Add gitignore --- .gitignore | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/.gitignore b/.gitignore index 84e7193..8bec710 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,30 @@ result result-* .pre-commit-config.yaml +# Generated by Cargo +# will have compiled files and executables +packages/alias-to-sieve/debug/ +packages/alias-to-sieve/target/ + +# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries +# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html +# Cargo.lock + +# These are backup files generated by rustfmt +**/*.rs.bk + +# MSVC Windows builds of rustc generate these, which store debugging information +*.pdb + +# RustRover +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +packages/alias-to/sieve/.idea/ + +# Testdata with private mailadresses +packages/alias-to/sieve/testdata/mathebau.aliases + +# might contain private mailadresses as well +packages/alias-to/sieve/testdata/virt_aliases -- 2.39.5 From 696370b85e5616f71c7b41d9131a445def8e219a Mon Sep 17 00:00:00 2001 From: Gonne Date: Sun, 3 Nov 2024 07:22:53 +0100 Subject: [PATCH 02/23] First rough parsing of alias file to hashmap --- .gitignore | 4 ++ packages/alias-to-sieve/Cargo.lock | 7 +++ packages/alias-to-sieve/Cargo.toml | 10 +++++ packages/alias-to-sieve/main.rs | 69 ++++++++++++++++++++++++++++++ 4 files changed, 90 insertions(+) create mode 100644 packages/alias-to-sieve/Cargo.lock create mode 100644 packages/alias-to-sieve/Cargo.toml create mode 100644 packages/alias-to-sieve/main.rs diff --git a/.gitignore b/.gitignore index 8bec710..192e941 100644 --- a/.gitignore +++ b/.gitignore @@ -30,3 +30,7 @@ packages/alias-to/sieve/testdata/mathebau.aliases # might contain private mailadresses as well packages/alias-to/sieve/testdata/virt_aliases + + +# Added by cargo +packages/alias-to-sieve/target diff --git a/packages/alias-to-sieve/Cargo.lock b/packages/alias-to-sieve/Cargo.lock new file mode 100644 index 0000000..2c0fdae --- /dev/null +++ b/packages/alias-to-sieve/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "alias_to_sieve" +version = "0.1.0" diff --git a/packages/alias-to-sieve/Cargo.toml b/packages/alias-to-sieve/Cargo.toml new file mode 100644 index 0000000..b1cbd25 --- /dev/null +++ b/packages/alias-to-sieve/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "alias_to_sieve" +version = "0.1.0" +edition = "2021" + +[dependencies] + +[[bin]] +name = "alias_to_sieve" +path = "main.rs" diff --git a/packages/alias-to-sieve/main.rs b/packages/alias-to-sieve/main.rs new file mode 100644 index 0000000..f4279ee --- /dev/null +++ b/packages/alias-to-sieve/main.rs @@ -0,0 +1,69 @@ +use std::fs::File; +use std::io::{self, BufRead}; +use std::path::Path; +use std::env; +use std::collections::HashMap; + +fn main() { + let args: Vec = env::args().collect(); + + let file_path = &args[1]; + + // File must exist in the current path + if let Ok(lines) = read_lines(file_path) { + // Consumes the iterator, returns an (Optional) String + let mut redirect_map : HashMap> = HashMap::new(); + let mut destinations : Vec = Vec::new(); + for line in lines.flatten() { + let line = String::from(line.split_at(line.find("#").unwrap_or(line.len())).0); + let destination = line.split_at(line.find(char::is_whitespace).unwrap_or(0)).0; + if destination == "" { + continue; + } + let redirects: Vec = line.split_at(line.find(char::is_whitespace).unwrap_or(0)).1.split(", ") + .map(|address| to_mailaddress(address)).collect(); + if redirects.len() == 0 { + continue; + } + destinations.push(to_mailaddress(destination)); + redirect_map.insert(to_mailaddress(destination), redirects); + } + let mut changed = true; + while changed { + changed = false; + let mut all_new_redirects : HashMap> = HashMap::new(); + for destination in destinations.iter() { + for forward_to in redirect_map.get(destination).unwrap().iter() { + if let Some(new_redirects) = redirect_map.get(forward_to) { + changed = true; + all_new_redirects.entry(destination.clone()).or_insert(redirect_map.get(destination).unwrap().clone()) + .retain(|dest| *dest != *forward_to); + all_new_redirects.entry(destination.clone()).and_modify(|d| d.extend(new_redirects.iter().map(|x| x.clone()))); + } + } + + } + + for (destination, new_redirect) in all_new_redirects { + *redirect_map.get_mut(&destination).unwrap() = new_redirect; + } + } + println!("{:#?}", redirect_map); + } +} + +fn to_mailaddress(local_part: &str) -> String { + let local_part = local_part.trim(); + if local_part.contains("@") { + return String::from(local_part); + } + return local_part.to_string() + "@mathebau.de"; +} + +// The output is wrapped in a Result to allow matching on errors. +// Returns an Iterator to the Reader of the lines of the file. +fn read_lines

(filename: P) -> io::Result>> +where P: AsRef, { + let file = File::open(filename)?; + Ok(io::BufReader::new(file).lines()) +} \ No newline at end of file -- 2.39.5 From ed9be64e541c214b9f3b910df77792343c039e15 Mon Sep 17 00:00:00 2001 From: Gonne Date: Sun, 3 Nov 2024 08:46:55 +0100 Subject: [PATCH 03/23] Generate sieve script from hashmap --- packages/alias-to-sieve/main.rs | 117 +++++++++++++++++--------------- 1 file changed, 61 insertions(+), 56 deletions(-) diff --git a/packages/alias-to-sieve/main.rs b/packages/alias-to-sieve/main.rs index f4279ee..cd31b7a 100644 --- a/packages/alias-to-sieve/main.rs +++ b/packages/alias-to-sieve/main.rs @@ -1,69 +1,74 @@ -use std::fs::File; -use std::io::{self, BufRead}; -use std::path::Path; -use std::env; +use std::io::{self}; use std::collections::HashMap; fn main() { - let args: Vec = env::args().collect(); + let redirects = parse_alias_to_hashmap(); + let sieve_script = generate_sieve_script(redirects); + println!("{}", sieve_script); +} - let file_path = &args[1]; - - // File must exist in the current path - if let Ok(lines) = read_lines(file_path) { - // Consumes the iterator, returns an (Optional) String - let mut redirect_map : HashMap> = HashMap::new(); - let mut destinations : Vec = Vec::new(); - for line in lines.flatten() { - let line = String::from(line.split_at(line.find("#").unwrap_or(line.len())).0); - let destination = line.split_at(line.find(char::is_whitespace).unwrap_or(0)).0; - if destination == "" { - continue; - } - let redirects: Vec = line.split_at(line.find(char::is_whitespace).unwrap_or(0)).1.split(", ") - .map(|address| to_mailaddress(address)).collect(); - if redirects.len() == 0 { - continue; - } - destinations.push(to_mailaddress(destination)); - redirect_map.insert(to_mailaddress(destination), redirects); - } - let mut changed = true; - while changed { - changed = false; - let mut all_new_redirects : HashMap> = HashMap::new(); - for destination in destinations.iter() { - for forward_to in redirect_map.get(destination).unwrap().iter() { - if let Some(new_redirects) = redirect_map.get(forward_to) { - changed = true; - all_new_redirects.entry(destination.clone()).or_insert(redirect_map.get(destination).unwrap().clone()) - .retain(|dest| *dest != *forward_to); - all_new_redirects.entry(destination.clone()).and_modify(|d| d.extend(new_redirects.iter().map(|x| x.clone()))); - } +fn generate_sieve_script(redirects: HashMap>) -> String { + let mut script : String = "require [\"envelope\", \"copy\"];\n".to_string(); + for (redirect, destinations) in redirects { + script += format!("if envelope :is \"to\" \"{}\" {{\n{}}}\n", redirect, + (|| { + let mut subscript : String = "".to_string(); + for destination in destinations.iter().rev().skip(1).rev() { + subscript += format!(" redirect :copy \"{}\";\n", destination).as_str(); + } + subscript += format!(" redirect \"{}\";\n", destinations.iter().rev().next().unwrap()).as_str(); + return subscript; + })() + ).as_str(); + } + return script; +} + +fn parse_alias_to_hashmap() -> HashMap> { + // File must exist in the current path + let mut redirect_map : HashMap> = HashMap::new(); + let mut destinations : Vec = Vec::new(); + for line in io::stdin().lines() { + let line = line.unwrap(); + let line = String::from(line.split_at(line.find("#").unwrap_or(line.len())).0); + let destination = line.split_at(line.find(char::is_whitespace).unwrap_or(0)).0; + if destination == "" { + continue; + } + let redirects: Vec = line.split_at(line.find(char::is_whitespace).unwrap_or(0)).1.split(" ") + .filter(|address| address.trim().to_string().replace(",","") != "").map(|address| to_mailaddress(address)).collect(); + if redirects.len() == 0 { + continue; + } + destinations.push(to_mailaddress(destination)); + redirect_map.insert(to_mailaddress(destination), redirects); + } + let mut changed = true; + while changed { + changed = false; + let mut all_new_redirects : HashMap> = HashMap::new(); + for destination in destinations.iter() { + for forward_to in redirect_map.get(destination).unwrap().iter() { + if let Some(new_redirects) = redirect_map.get(forward_to) { + changed = true; + all_new_redirects.entry(destination.clone()).or_insert(redirect_map.get(destination).unwrap().clone()) + .retain(|dest| *dest != *forward_to); + all_new_redirects.entry(destination.clone()).and_modify(|d| d.extend(new_redirects.iter().map(|x| x.clone()))); } - - } - - for (destination, new_redirect) in all_new_redirects { - *redirect_map.get_mut(&destination).unwrap() = new_redirect; } } - println!("{:#?}", redirect_map); + for (destination, new_redirect) in all_new_redirects { + *redirect_map.get_mut(&destination).unwrap() = new_redirect; + } } + return redirect_map; } fn to_mailaddress(local_part: &str) -> String { - let local_part = local_part.trim(); - if local_part.contains("@") { - return String::from(local_part); + let mut addr = local_part.trim().to_string(); + addr = addr.replace(",", ""); + if addr.contains("@") { + return addr; } - return local_part.to_string() + "@mathebau.de"; + return addr + "@mathebau.de"; } - -// The output is wrapped in a Result to allow matching on errors. -// Returns an Iterator to the Reader of the lines of the file. -fn read_lines

(filename: P) -> io::Result>> -where P: AsRef, { - let file = File::open(filename)?; - Ok(io::BufReader::new(file).lines()) -} \ No newline at end of file -- 2.39.5 From b6bc2c026b5be6f75865bd91654dee029b260ea4 Mon Sep 17 00:00:00 2001 From: Gonne Date: Sun, 3 Nov 2024 09:08:58 +0100 Subject: [PATCH 04/23] Convert project to flake --- packages/alias-to-sieve/Cargo.toml | 5 ----- packages/alias-to-sieve/{ => src}/main.rs | 0 2 files changed, 5 deletions(-) rename packages/alias-to-sieve/{ => src}/main.rs (100%) diff --git a/packages/alias-to-sieve/Cargo.toml b/packages/alias-to-sieve/Cargo.toml index b1cbd25..12f8388 100644 --- a/packages/alias-to-sieve/Cargo.toml +++ b/packages/alias-to-sieve/Cargo.toml @@ -3,8 +3,3 @@ name = "alias_to_sieve" version = "0.1.0" edition = "2021" -[dependencies] - -[[bin]] -name = "alias_to_sieve" -path = "main.rs" diff --git a/packages/alias-to-sieve/main.rs b/packages/alias-to-sieve/src/main.rs similarity index 100% rename from packages/alias-to-sieve/main.rs rename to packages/alias-to-sieve/src/main.rs -- 2.39.5 From f9b5e7017061b220bd631768c4a7cdc2b779f839 Mon Sep 17 00:00:00 2001 From: Gonne Date: Sun, 3 Nov 2024 09:28:48 +0100 Subject: [PATCH 06/23] sort consistently --- packages/alias-to-sieve/src/main.rs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/packages/alias-to-sieve/src/main.rs b/packages/alias-to-sieve/src/main.rs index cd31b7a..b2242bd 100644 --- a/packages/alias-to-sieve/src/main.rs +++ b/packages/alias-to-sieve/src/main.rs @@ -1,18 +1,19 @@ use std::io::{self}; -use std::collections::HashMap; +use std::collections::BTreeMap; fn main() { - let redirects = parse_alias_to_hashmap(); + let redirects = parse_alias_to_BTreeMap(); let sieve_script = generate_sieve_script(redirects); println!("{}", sieve_script); } -fn generate_sieve_script(redirects: HashMap>) -> String { +fn generate_sieve_script(redirects: BTreeMap>) -> String { let mut script : String = "require [\"envelope\", \"copy\"];\n".to_string(); - for (redirect, destinations) in redirects { + for (redirect, mut destinations) in redirects { script += format!("if envelope :is \"to\" \"{}\" {{\n{}}}\n", redirect, (|| { let mut subscript : String = "".to_string(); + destinations.sort(); for destination in destinations.iter().rev().skip(1).rev() { subscript += format!(" redirect :copy \"{}\";\n", destination).as_str(); } @@ -24,9 +25,9 @@ fn generate_sieve_script(redirects: HashMap>) -> String { return script; } -fn parse_alias_to_hashmap() -> HashMap> { +fn parse_alias_to_BTreeMap() -> BTreeMap> { // File must exist in the current path - let mut redirect_map : HashMap> = HashMap::new(); + let mut redirect_map : BTreeMap> = BTreeMap::new(); let mut destinations : Vec = Vec::new(); for line in io::stdin().lines() { let line = line.unwrap(); @@ -46,7 +47,7 @@ fn parse_alias_to_hashmap() -> HashMap> { let mut changed = true; while changed { changed = false; - let mut all_new_redirects : HashMap> = HashMap::new(); + let mut all_new_redirects : BTreeMap> = BTreeMap::new(); for destination in destinations.iter() { for forward_to in redirect_map.get(destination).unwrap().iter() { if let Some(new_redirects) = redirect_map.get(forward_to) { -- 2.39.5 From 137ed5055e567e89b4dadfe7a0d0e6bf69b6b03e Mon Sep 17 00:00:00 2001 From: Gonne Date: Sun, 3 Nov 2024 09:59:05 +0100 Subject: [PATCH 07/23] clippy suggestions --- packages/alias-to-sieve/src/main.rs | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/packages/alias-to-sieve/src/main.rs b/packages/alias-to-sieve/src/main.rs index b2242bd..42d55f1 100644 --- a/packages/alias-to-sieve/src/main.rs +++ b/packages/alias-to-sieve/src/main.rs @@ -2,30 +2,29 @@ use std::io::{self}; use std::collections::BTreeMap; fn main() { - let redirects = parse_alias_to_BTreeMap(); + let redirects = parse_alias_to_map(); let sieve_script = generate_sieve_script(redirects); println!("{}", sieve_script); } fn generate_sieve_script(redirects: BTreeMap>) -> String { - let mut script : String = "require [\"envelope\", \"copy\"];\n".to_string(); + let mut script : String = "require [\"envelope\", \"copy\"];\n\n".to_string(); for (redirect, mut destinations) in redirects { script += format!("if envelope :is \"to\" \"{}\" {{\n{}}}\n", redirect, - (|| { + { let mut subscript : String = "".to_string(); destinations.sort(); for destination in destinations.iter().rev().skip(1).rev() { subscript += format!(" redirect :copy \"{}\";\n", destination).as_str(); } - subscript += format!(" redirect \"{}\";\n", destinations.iter().rev().next().unwrap()).as_str(); - return subscript; - })() + subscript + format!(" redirect \"{}\";\n", destinations.iter().next_back().unwrap()).as_str() + } ).as_str(); } - return script; + script } -fn parse_alias_to_BTreeMap() -> BTreeMap> { +fn parse_alias_to_map() -> BTreeMap> { // File must exist in the current path let mut redirect_map : BTreeMap> = BTreeMap::new(); let mut destinations : Vec = Vec::new(); @@ -33,12 +32,12 @@ fn parse_alias_to_BTreeMap() -> BTreeMap> { let line = line.unwrap(); let line = String::from(line.split_at(line.find("#").unwrap_or(line.len())).0); let destination = line.split_at(line.find(char::is_whitespace).unwrap_or(0)).0; - if destination == "" { + if destination.is_empty() { continue; } let redirects: Vec = line.split_at(line.find(char::is_whitespace).unwrap_or(0)).1.split(" ") - .filter(|address| address.trim().to_string().replace(",","") != "").map(|address| to_mailaddress(address)).collect(); - if redirects.len() == 0 { + .filter(|address| address.trim().to_string().replace(",","") != "").map(to_mailaddress).collect(); + if redirects.is_empty() { continue; } destinations.push(to_mailaddress(destination)); @@ -54,7 +53,7 @@ fn parse_alias_to_BTreeMap() -> BTreeMap> { changed = true; all_new_redirects.entry(destination.clone()).or_insert(redirect_map.get(destination).unwrap().clone()) .retain(|dest| *dest != *forward_to); - all_new_redirects.entry(destination.clone()).and_modify(|d| d.extend(new_redirects.iter().map(|x| x.clone()))); + all_new_redirects.entry(destination.clone()).and_modify(|d| d.extend(new_redirects.iter().cloned())); } } } @@ -62,7 +61,7 @@ fn parse_alias_to_BTreeMap() -> BTreeMap> { *redirect_map.get_mut(&destination).unwrap() = new_redirect; } } - return redirect_map; + redirect_map } fn to_mailaddress(local_part: &str) -> String { @@ -71,5 +70,5 @@ fn to_mailaddress(local_part: &str) -> String { if addr.contains("@") { return addr; } - return addr + "@mathebau.de"; + addr + "@mathebau.de" } -- 2.39.5 From f82d6f7c9ad02efedb8d300c085eafd72f7fcebb Mon Sep 17 00:00:00 2001 From: Gonne Date: Sun, 3 Nov 2024 18:11:39 +0100 Subject: [PATCH 08/23] Pass default domain via commandline argument --- packages/alias-to-sieve/src/main.rs | 28 +++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/packages/alias-to-sieve/src/main.rs b/packages/alias-to-sieve/src/main.rs index 42d55f1..763f97e 100644 --- a/packages/alias-to-sieve/src/main.rs +++ b/packages/alias-to-sieve/src/main.rs @@ -1,8 +1,17 @@ use std::io::{self}; use std::collections::BTreeMap; +use std::env; fn main() { - let redirects = parse_alias_to_map(); + let args: Vec = env::args().collect(); + if args.len() < 2 { + print_help(); + return; + } + + let default_domain = &args[1]; + + let redirects = parse_alias_to_map(default_domain); let sieve_script = generate_sieve_script(redirects); println!("{}", sieve_script); } @@ -24,7 +33,7 @@ fn generate_sieve_script(redirects: BTreeMap>) -> String { script } -fn parse_alias_to_map() -> BTreeMap> { +fn parse_alias_to_map(default_domain : &str) -> BTreeMap> { // File must exist in the current path let mut redirect_map : BTreeMap> = BTreeMap::new(); let mut destinations : Vec = Vec::new(); @@ -36,12 +45,12 @@ fn parse_alias_to_map() -> BTreeMap> { continue; } let redirects: Vec = line.split_at(line.find(char::is_whitespace).unwrap_or(0)).1.split(" ") - .filter(|address| address.trim().to_string().replace(",","") != "").map(to_mailaddress).collect(); + .filter(|address| address.trim().to_string().replace(",","") != "").map(|addr| to_mailaddress(addr, default_domain)).collect(); if redirects.is_empty() { continue; } - destinations.push(to_mailaddress(destination)); - redirect_map.insert(to_mailaddress(destination), redirects); + destinations.push(to_mailaddress(destination, default_domain)); + redirect_map.insert(to_mailaddress(destination, default_domain), redirects); } let mut changed = true; while changed { @@ -64,11 +73,16 @@ fn parse_alias_to_map() -> BTreeMap> { redirect_map } -fn to_mailaddress(local_part: &str) -> String { +fn to_mailaddress(local_part: &str, default_domain : &str) -> String { let mut addr = local_part.trim().to_string(); addr = addr.replace(",", ""); if addr.contains("@") { return addr; } - addr + "@mathebau.de" + addr + "@" + default_domain } + +fn print_help(){ + print!("Reads a virtual alias file from STDIN and needs a default domain to append to local paths, e.g. + cat virt_aliases | ./alias_to_sieve example.com"); +} \ No newline at end of file -- 2.39.5 From e8560799d42ad568c99e1a8de4a4e09d1ae06860 Mon Sep 17 00:00:00 2001 From: Gonne Date: Thu, 7 Nov 2024 16:24:52 +0100 Subject: [PATCH 09/23] Add rust version --- packages/alias-to-sieve/Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/alias-to-sieve/Cargo.toml b/packages/alias-to-sieve/Cargo.toml index 12f8388..622f3eb 100644 --- a/packages/alias-to-sieve/Cargo.toml +++ b/packages/alias-to-sieve/Cargo.toml @@ -3,3 +3,4 @@ name = "alias_to_sieve" version = "0.1.0" edition = "2021" +rust-version = "1.68.2" -- 2.39.5 From ede3c48ea71264111370121f15126ce818af8189 Mon Sep 17 00:00:00 2001 From: Gonne Date: Sun, 10 Nov 2024 20:43:35 +0100 Subject: [PATCH 10/23] Don't output preamble --- packages/alias-to-sieve/src/main.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/alias-to-sieve/src/main.rs b/packages/alias-to-sieve/src/main.rs index 763f97e..9b32738 100644 --- a/packages/alias-to-sieve/src/main.rs +++ b/packages/alias-to-sieve/src/main.rs @@ -17,7 +17,8 @@ fn main() { } fn generate_sieve_script(redirects: BTreeMap>) -> String { - let mut script : String = "require [\"envelope\", \"copy\"];\n\n".to_string(); +// let mut script : String = "require [\"envelope\", \"copy\"];\n\n".to_string(); + let mut script : String = "".to_string(); for (redirect, mut destinations) in redirects { script += format!("if envelope :is \"to\" \"{}\" {{\n{}}}\n", redirect, { @@ -85,4 +86,4 @@ fn to_mailaddress(local_part: &str, default_domain : &str) -> String { fn print_help(){ print!("Reads a virtual alias file from STDIN and needs a default domain to append to local paths, e.g. cat virt_aliases | ./alias_to_sieve example.com"); -} \ No newline at end of file +} -- 2.39.5 From e0eded2aba2071ea55377c0d3d911c6e332e52b6 Mon Sep 17 00:00:00 2001 From: Gonne Date: Thu, 14 Nov 2024 09:39:20 +0100 Subject: [PATCH 11/23] Conflate multiple alias input files --- packages/alias-to-sieve/src/main.rs | 74 +++++++++++++++++++---------- 1 file changed, 49 insertions(+), 25 deletions(-) diff --git a/packages/alias-to-sieve/src/main.rs b/packages/alias-to-sieve/src/main.rs index 9b32738..252594c 100644 --- a/packages/alias-to-sieve/src/main.rs +++ b/packages/alias-to-sieve/src/main.rs @@ -1,24 +1,36 @@ -use std::io::{self}; use std::collections::BTreeMap; use std::env; +use std::fs::File; +use std::io::{self, BufRead}; +use std::path::Path; + +#[derive(Debug)] +struct AliasFile { + content: io::Lines>, + default_domain: String, +} + fn main() { let args: Vec = env::args().collect(); - if args.len() < 2 { + if args.len() < 2 || args.len() % 2 == 0{ print_help(); return; } + let mut alias_files : Vec = Vec::new(); + for i in (1..args.len()).step_by(2) { + if let Ok(lines) = read_lines(&args[i]) { + alias_files.push(AliasFile{content: lines, default_domain: args[i+1].to_string()}) + } + } - let default_domain = &args[1]; - - let redirects = parse_alias_to_map(default_domain); + let redirects = parse_alias_to_map(alias_files); let sieve_script = generate_sieve_script(redirects); println!("{}", sieve_script); } fn generate_sieve_script(redirects: BTreeMap>) -> String { -// let mut script : String = "require [\"envelope\", \"copy\"];\n\n".to_string(); - let mut script : String = "".to_string(); + let mut script : String = "require [\"envelope\", \"copy\"];\n\n".to_string(); for (redirect, mut destinations) in redirects { script += format!("if envelope :is \"to\" \"{}\" {{\n{}}}\n", redirect, { @@ -34,24 +46,26 @@ fn generate_sieve_script(redirects: BTreeMap>) -> String { script } -fn parse_alias_to_map(default_domain : &str) -> BTreeMap> { +fn parse_alias_to_map(alias_files: Vec) -> BTreeMap> { // File must exist in the current path let mut redirect_map : BTreeMap> = BTreeMap::new(); let mut destinations : Vec = Vec::new(); - for line in io::stdin().lines() { - let line = line.unwrap(); - let line = String::from(line.split_at(line.find("#").unwrap_or(line.len())).0); - let destination = line.split_at(line.find(char::is_whitespace).unwrap_or(0)).0; - if destination.is_empty() { - continue; + for alias_file in alias_files.into_iter() { + for line in alias_file.content { + let line = line.unwrap(); + let line = String::from(line.split_at(line.find("#").unwrap_or(line.len())).0); + let destination = line.split_at(line.find(char::is_whitespace).unwrap_or(0)).0; + if destination.is_empty() { + continue; + } + let redirects: Vec = line.split_at(line.find(char::is_whitespace).unwrap_or(0)).1.split(" ") + .filter(|address| address.trim().to_string().replace(",","") != "").map(|addr| to_mailaddress(addr, &alias_file.default_domain)).collect(); + if redirects.is_empty() { + continue; + } + destinations.push(to_mailaddress(destination, &alias_file.default_domain)); + redirect_map.insert(to_mailaddress(destination, &alias_file.default_domain), redirects); } - let redirects: Vec = line.split_at(line.find(char::is_whitespace).unwrap_or(0)).1.split(" ") - .filter(|address| address.trim().to_string().replace(",","") != "").map(|addr| to_mailaddress(addr, default_domain)).collect(); - if redirects.is_empty() { - continue; - } - destinations.push(to_mailaddress(destination, default_domain)); - redirect_map.insert(to_mailaddress(destination, default_domain), redirects); } let mut changed = true; while changed { @@ -74,7 +88,7 @@ fn parse_alias_to_map(default_domain : &str) -> BTreeMap> { redirect_map } -fn to_mailaddress(local_part: &str, default_domain : &str) -> String { +fn to_mailaddress(local_part: &str, default_domain : &String) -> String { let mut addr = local_part.trim().to_string(); addr = addr.replace(",", ""); if addr.contains("@") { @@ -83,7 +97,17 @@ fn to_mailaddress(local_part: &str, default_domain : &str) -> String { addr + "@" + default_domain } -fn print_help(){ - print!("Reads a virtual alias file from STDIN and needs a default domain to append to local paths, e.g. - cat virt_aliases | ./alias_to_sieve example.com"); + +// The output is wrapped in a Result to allow matching on errors. +// Returns an Iterator to the Reader of the lines of the file. +fn read_lines

(filename: P) -> io::Result>> +where P: AsRef, { + let file = File::open(filename)?; + Ok(io::BufReader::new(file).lines()) +} + + +fn print_help(){ + print!("Reads a virtual alias file and needs a default domain to append to local paths, e.g. + ./alias_to_sieve example.com.txt example.com "); } -- 2.39.5 From 828fade1e62b5cb650f39b4205408e46b6dc8167 Mon Sep 17 00:00:00 2001 From: Gonne Date: Mon, 25 Nov 2024 20:19:09 +0100 Subject: [PATCH 12/23] Add some type safety --- .gitignore | 2 +- packages/alias-to-sieve/Cargo.lock | 310 ++++++++++++++++++++++++++++ packages/alias-to-sieve/Cargo.toml | 4 + packages/alias-to-sieve/src/main.rs | 35 ++-- 4 files changed, 333 insertions(+), 18 deletions(-) diff --git a/.gitignore b/.gitignore index 192e941..7b4a195 100644 --- a/.gitignore +++ b/.gitignore @@ -27,10 +27,10 @@ packages/alias-to/sieve/.idea/ # Testdata with private mailadresses packages/alias-to/sieve/testdata/mathebau.aliases +packages/alias-to-sieve/testdata/koma.aliases # might contain private mailadresses as well packages/alias-to/sieve/testdata/virt_aliases - # Added by cargo packages/alias-to-sieve/target diff --git a/packages/alias-to-sieve/Cargo.lock b/packages/alias-to-sieve/Cargo.lock index 2c0fdae..33171c2 100644 --- a/packages/alias-to-sieve/Cargo.lock +++ b/packages/alias-to-sieve/Cargo.lock @@ -5,3 +5,313 @@ version = 3 [[package]] name = "alias_to_sieve" version = "0.1.0" +dependencies = [ + "email-address-parser", + "fqdn", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bumpalo" +version = "3.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "console_error_panic_hook" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc" +dependencies = [ + "cfg-if", + "wasm-bindgen", +] + +[[package]] +name = "cpufeatures" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16b80225097f2e5ae4e7179dd2266824648f3e2f49d9134d584b76389d31c4c3" +dependencies = [ + "libc", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "email-address-parser" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fe19a4967eca30062be4abaf813d929ba48b3bfb21830367f7e1baae37f213a" +dependencies = [ + "console_error_panic_hook", + "pest", + "pest_derive", + "quick-xml", + "wasm-bindgen", +] + +[[package]] +name = "fqdn" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d649848b92394c9e1357f6574e48ba9b073a20ceac342f7fbfd0ab5f12524d" + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "libc" +version = "0.2.164" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "433bfe06b8c75da9b2e3fbea6e5329ff87748f0b144ef75306e674c3f6f7c13f" + +[[package]] +name = "log" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "once_cell" +version = "1.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" + +[[package]] +name = "pest" +version = "2.7.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879952a81a83930934cbf1786752d6dedc3b1f29e8f8fb2ad1d0a36f377cf442" +dependencies = [ + "memchr", + "thiserror", + "ucd-trie", +] + +[[package]] +name = "pest_derive" +version = "2.7.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d214365f632b123a47fd913301e14c946c61d1c183ee245fa76eb752e59a02dd" +dependencies = [ + "pest", + "pest_generator", +] + +[[package]] +name = "pest_generator" +version = "2.7.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb55586734301717aea2ac313f50b2eb8f60d2fc3dc01d190eefa2e625f60c4e" +dependencies = [ + "pest", + "pest_meta", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pest_meta" +version = "2.7.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b75da2a70cf4d9cb76833c990ac9cd3923c9a8905a8929789ce347c84564d03d" +dependencies = [ + "once_cell", + "pest", + "sha2", +] + +[[package]] +name = "proc-macro2" +version = "1.0.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quick-xml" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3cc440ee4802a86e357165021e3e255a9143724da31db1e2ea540214c96a0f82" +dependencies = [ + "memchr", +] + +[[package]] +name = "quote" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "sha2" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "syn" +version = "2.0.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d46482f1c1c87acd84dea20c1bf5ebff4c757009ed6bf19cfd36fb10e92c4e" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + +[[package]] +name = "ucd-trie" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" + +[[package]] +name = "unicode-ident" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "wasm-bindgen" +version = "0.2.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "128d1e363af62632b8eb57219c8fd7877144af57558fb2ef0368d0087bddeb2e" +dependencies = [ + "cfg-if", + "once_cell", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb6dd4d3ca0ddffd1dd1c9c04f94b868c37ff5fac97c30b97cff2d74fce3a358" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e79384be7f8f5a9dd5d7167216f022090cf1f9ec128e6e6a482a2cb5c5422c56" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d" diff --git a/packages/alias-to-sieve/Cargo.toml b/packages/alias-to-sieve/Cargo.toml index 622f3eb..f9f5c5b 100644 --- a/packages/alias-to-sieve/Cargo.toml +++ b/packages/alias-to-sieve/Cargo.toml @@ -4,3 +4,7 @@ version = "0.1.0" edition = "2021" rust-version = "1.68.2" + +[dependencies] +fqdn = {version = "0.4.2", features = ["domain-label-length-limited-to-63", "domain-name-without-special-chars"]} +email-address-parser = "2.0.0" \ No newline at end of file diff --git a/packages/alias-to-sieve/src/main.rs b/packages/alias-to-sieve/src/main.rs index 252594c..98557ae 100644 --- a/packages/alias-to-sieve/src/main.rs +++ b/packages/alias-to-sieve/src/main.rs @@ -1,15 +1,19 @@ -use std::collections::BTreeMap; +use std::collections::HashMap; use std::env; use std::fs::File; use std::io::{self, BufRead}; use std::path::Path; +use std::str::FromStr; +use fqdn::*; +use email_address_parser::EmailAddress; #[derive(Debug)] struct AliasFile { content: io::Lines>, - default_domain: String, + default_domain: FQDN, } +type AliasMap = HashMap>; fn main() { let args: Vec = env::args().collect(); @@ -20,7 +24,7 @@ fn main() { let mut alias_files : Vec = Vec::new(); for i in (1..args.len()).step_by(2) { if let Ok(lines) = read_lines(&args[i]) { - alias_files.push(AliasFile{content: lines, default_domain: args[i+1].to_string()}) + alias_files.push(AliasFile{content: lines, default_domain: FQDN::from_str(&args[i+1]).unwrap()}) } } @@ -29,13 +33,12 @@ fn main() { println!("{}", sieve_script); } -fn generate_sieve_script(redirects: BTreeMap>) -> String { +fn generate_sieve_script(redirects: AliasMap) -> String { let mut script : String = "require [\"envelope\", \"copy\"];\n\n".to_string(); - for (redirect, mut destinations) in redirects { + for (redirect, destinations) in redirects { script += format!("if envelope :is \"to\" \"{}\" {{\n{}}}\n", redirect, { let mut subscript : String = "".to_string(); - destinations.sort(); for destination in destinations.iter().rev().skip(1).rev() { subscript += format!(" redirect :copy \"{}\";\n", destination).as_str(); } @@ -46,10 +49,10 @@ fn generate_sieve_script(redirects: BTreeMap>) -> String { script } -fn parse_alias_to_map(alias_files: Vec) -> BTreeMap> { +fn parse_alias_to_map(alias_files: Vec) -> AliasMap { // File must exist in the current path - let mut redirect_map : BTreeMap> = BTreeMap::new(); - let mut destinations : Vec = Vec::new(); + let mut redirect_map : AliasMap = AliasMap::new(); + let mut destinations : Vec = Vec::new(); for alias_file in alias_files.into_iter() { for line in alias_file.content { let line = line.unwrap(); @@ -58,7 +61,7 @@ fn parse_alias_to_map(alias_files: Vec) -> BTreeMap = line.split_at(line.find(char::is_whitespace).unwrap_or(0)).1.split(" ") + let redirects: Vec = line.split_at(line.find(char::is_whitespace).unwrap_or(0)).1.split(" ") .filter(|address| address.trim().to_string().replace(",","") != "").map(|addr| to_mailaddress(addr, &alias_file.default_domain)).collect(); if redirects.is_empty() { continue; @@ -70,7 +73,7 @@ fn parse_alias_to_map(alias_files: Vec) -> BTreeMap> = BTreeMap::new(); + let mut all_new_redirects : AliasMap = AliasMap::new(); for destination in destinations.iter() { for forward_to in redirect_map.get(destination).unwrap().iter() { if let Some(new_redirects) = redirect_map.get(forward_to) { @@ -88,16 +91,15 @@ fn parse_alias_to_map(alias_files: Vec) -> BTreeMap String { +fn to_mailaddress(local_part: &str, default_domain : &FQDN) -> EmailAddress { let mut addr = local_part.trim().to_string(); addr = addr.replace(",", ""); if addr.contains("@") { - return addr; + return EmailAddress::parse(&addr, None).unwrap(); } - addr + "@" + default_domain + EmailAddress::new(&addr, &default_domain.to_string(), None).unwrap() } - // The output is wrapped in a Result to allow matching on errors. // Returns an Iterator to the Reader of the lines of the file. fn read_lines

(filename: P) -> io::Result>> @@ -106,8 +108,7 @@ where P: AsRef, { Ok(io::BufReader::new(file).lines()) } - fn print_help(){ print!("Reads a virtual alias file and needs a default domain to append to local paths, e.g. - ./alias_to_sieve example.com.txt example.com "); + ./alias_to_sieve example.com.txt example.com"); } -- 2.39.5 From 09be518a37439d571940202e240294147298fc6c Mon Sep 17 00:00:00 2001 From: Gonne Date: Mon, 25 Nov 2024 20:31:28 +0100 Subject: [PATCH 13/23] Clippy suggestions --- packages/alias-to-sieve/src/main.rs | 29 +++++++++++++---------------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/packages/alias-to-sieve/src/main.rs b/packages/alias-to-sieve/src/main.rs index 98557ae..975ae2d 100644 --- a/packages/alias-to-sieve/src/main.rs +++ b/packages/alias-to-sieve/src/main.rs @@ -4,7 +4,7 @@ use std::fs::File; use std::io::{self, BufRead}; use std::path::Path; use std::str::FromStr; -use fqdn::*; +use fqdn::FQDN; use email_address_parser::EmailAddress; #[derive(Debug)] @@ -24,13 +24,10 @@ fn main() { let mut alias_files : Vec = Vec::new(); for i in (1..args.len()).step_by(2) { if let Ok(lines) = read_lines(&args[i]) { - alias_files.push(AliasFile{content: lines, default_domain: FQDN::from_str(&args[i+1]).unwrap()}) + alias_files.push(AliasFile{content: lines, default_domain: FQDN::from_str(&args[i+1]).unwrap()}); } } - - let redirects = parse_alias_to_map(alias_files); - let sieve_script = generate_sieve_script(redirects); - println!("{}", sieve_script); + println!("{}", generate_sieve_script(parse_alias_to_map(alias_files))); } fn generate_sieve_script(redirects: AliasMap) -> String { @@ -38,9 +35,9 @@ fn generate_sieve_script(redirects: AliasMap) -> String { for (redirect, destinations) in redirects { script += format!("if envelope :is \"to\" \"{}\" {{\n{}}}\n", redirect, { - let mut subscript : String = "".to_string(); + let mut subscript : String = String::new(); for destination in destinations.iter().rev().skip(1).rev() { - subscript += format!(" redirect :copy \"{}\";\n", destination).as_str(); + subscript += format!(" redirect :copy \"{destination}\";\n").as_str(); } subscript + format!(" redirect \"{}\";\n", destinations.iter().next_back().unwrap()).as_str() } @@ -53,16 +50,16 @@ fn parse_alias_to_map(alias_files: Vec) -> AliasMap { // File must exist in the current path let mut redirect_map : AliasMap = AliasMap::new(); let mut destinations : Vec = Vec::new(); - for alias_file in alias_files.into_iter() { + for alias_file in alias_files { for line in alias_file.content { let line = line.unwrap(); - let line = String::from(line.split_at(line.find("#").unwrap_or(line.len())).0); + let line = String::from(line.split_at(line.find('#').unwrap_or(line.len())).0); let destination = line.split_at(line.find(char::is_whitespace).unwrap_or(0)).0; if destination.is_empty() { continue; } - let redirects: Vec = line.split_at(line.find(char::is_whitespace).unwrap_or(0)).1.split(" ") - .filter(|address| address.trim().to_string().replace(",","") != "").map(|addr| to_mailaddress(addr, &alias_file.default_domain)).collect(); + let redirects: Vec = line.split_at(line.find(char::is_whitespace).unwrap_or(0)).1.split(' ') + .filter(|address| address.trim().to_string().replace(',',"") != "").map(|addr| to_mailaddress(addr, &alias_file.default_domain)).collect(); if redirects.is_empty() { continue; } @@ -74,8 +71,8 @@ fn parse_alias_to_map(alias_files: Vec) -> AliasMap { while changed { changed = false; let mut all_new_redirects : AliasMap = AliasMap::new(); - for destination in destinations.iter() { - for forward_to in redirect_map.get(destination).unwrap().iter() { + for destination in &destinations { + for forward_to in redirect_map.get(destination).unwrap() { if let Some(new_redirects) = redirect_map.get(forward_to) { changed = true; all_new_redirects.entry(destination.clone()).or_insert(redirect_map.get(destination).unwrap().clone()) @@ -93,8 +90,8 @@ fn parse_alias_to_map(alias_files: Vec) -> AliasMap { fn to_mailaddress(local_part: &str, default_domain : &FQDN) -> EmailAddress { let mut addr = local_part.trim().to_string(); - addr = addr.replace(",", ""); - if addr.contains("@") { + addr = addr.replace(',', ""); + if addr.contains('@') { return EmailAddress::parse(&addr, None).unwrap(); } EmailAddress::new(&addr, &default_domain.to_string(), None).unwrap() -- 2.39.5 From 3386d6afa05da9c26861bab81130e6710ed7357e Mon Sep 17 00:00:00 2001 From: Gonne Date: Mon, 25 Nov 2024 20:40:40 +0100 Subject: [PATCH 14/23] cargo fmt --- packages/alias-to-sieve/src/main.rs | 81 ++++++++++++++++++----------- 1 file changed, 52 insertions(+), 29 deletions(-) diff --git a/packages/alias-to-sieve/src/main.rs b/packages/alias-to-sieve/src/main.rs index 975ae2d..65eb210 100644 --- a/packages/alias-to-sieve/src/main.rs +++ b/packages/alias-to-sieve/src/main.rs @@ -1,11 +1,11 @@ +use email_address_parser::EmailAddress; +use fqdn::FQDN; use std::collections::HashMap; use std::env; use std::fs::File; use std::io::{self, BufRead}; use std::path::Path; use std::str::FromStr; -use fqdn::FQDN; -use email_address_parser::EmailAddress; #[derive(Debug)] struct AliasFile { @@ -17,39 +17,46 @@ type AliasMap = HashMap>; fn main() { let args: Vec = env::args().collect(); - if args.len() < 2 || args.len() % 2 == 0{ + if args.len() < 2 || args.len() % 2 == 0 { print_help(); return; } - let mut alias_files : Vec = Vec::new(); + let mut alias_files: Vec = Vec::new(); for i in (1..args.len()).step_by(2) { if let Ok(lines) = read_lines(&args[i]) { - alias_files.push(AliasFile{content: lines, default_domain: FQDN::from_str(&args[i+1]).unwrap()}); + alias_files.push(AliasFile { + content: lines, + default_domain: FQDN::from_str(&args[i + 1]).unwrap(), + }); } } println!("{}", generate_sieve_script(parse_alias_to_map(alias_files))); } fn generate_sieve_script(redirects: AliasMap) -> String { - let mut script : String = "require [\"envelope\", \"copy\"];\n\n".to_string(); + let mut script: String = "require [\"envelope\", \"copy\"];\n\n".to_string(); for (redirect, destinations) in redirects { - script += format!("if envelope :is \"to\" \"{}\" {{\n{}}}\n", redirect, - { - let mut subscript : String = String::new(); - for destination in destinations.iter().rev().skip(1).rev() { - subscript += format!(" redirect :copy \"{destination}\";\n").as_str(); - } - subscript + format!(" redirect \"{}\";\n", destinations.iter().next_back().unwrap()).as_str() + script += format!("if envelope :is \"to\" \"{}\" {{\n{}}}\n", redirect, { + let mut subscript: String = String::new(); + for destination in destinations.iter().rev().skip(1).rev() { + subscript += format!(" redirect :copy \"{destination}\";\n").as_str(); } - ).as_str(); + subscript + + format!( + " redirect \"{}\";\n", + destinations.iter().next_back().unwrap() + ) + .as_str() + }) + .as_str(); } script } fn parse_alias_to_map(alias_files: Vec) -> AliasMap { // File must exist in the current path - let mut redirect_map : AliasMap = AliasMap::new(); - let mut destinations : Vec = Vec::new(); + let mut redirect_map: AliasMap = AliasMap::new(); + let mut destinations: Vec = Vec::new(); for alias_file in alias_files { for line in alias_file.content { let line = line.unwrap(); @@ -58,26 +65,38 @@ fn parse_alias_to_map(alias_files: Vec) -> AliasMap { if destination.is_empty() { continue; } - let redirects: Vec = line.split_at(line.find(char::is_whitespace).unwrap_or(0)).1.split(' ') - .filter(|address| address.trim().to_string().replace(',',"") != "").map(|addr| to_mailaddress(addr, &alias_file.default_domain)).collect(); + let redirects: Vec = line + .split_at(line.find(char::is_whitespace).unwrap_or(0)) + .1 + .split(' ') + .filter(|address| address.trim().to_string().replace(',', "") != "") + .map(|addr| to_mailaddress(addr, &alias_file.default_domain)) + .collect(); if redirects.is_empty() { continue; } destinations.push(to_mailaddress(destination, &alias_file.default_domain)); - redirect_map.insert(to_mailaddress(destination, &alias_file.default_domain), redirects); + redirect_map.insert( + to_mailaddress(destination, &alias_file.default_domain), + redirects, + ); } } - let mut changed = true; + let mut changed = true; while changed { changed = false; - let mut all_new_redirects : AliasMap = AliasMap::new(); + let mut all_new_redirects: AliasMap = AliasMap::new(); for destination in &destinations { for forward_to in redirect_map.get(destination).unwrap() { if let Some(new_redirects) = redirect_map.get(forward_to) { changed = true; - all_new_redirects.entry(destination.clone()).or_insert(redirect_map.get(destination).unwrap().clone()) - .retain(|dest| *dest != *forward_to); - all_new_redirects.entry(destination.clone()).and_modify(|d| d.extend(new_redirects.iter().cloned())); + all_new_redirects + .entry(destination.clone()) + .or_insert(redirect_map.get(destination).unwrap().clone()) + .retain(|dest| *dest != *forward_to); + all_new_redirects + .entry(destination.clone()) + .and_modify(|d| d.extend(new_redirects.iter().cloned())); } } } @@ -88,7 +107,7 @@ fn parse_alias_to_map(alias_files: Vec) -> AliasMap { redirect_map } -fn to_mailaddress(local_part: &str, default_domain : &FQDN) -> EmailAddress { +fn to_mailaddress(local_part: &str, default_domain: &FQDN) -> EmailAddress { let mut addr = local_part.trim().to_string(); addr = addr.replace(',', ""); if addr.contains('@') { @@ -100,12 +119,16 @@ fn to_mailaddress(local_part: &str, default_domain : &FQDN) -> EmailAddress { // The output is wrapped in a Result to allow matching on errors. // Returns an Iterator to the Reader of the lines of the file. fn read_lines

(filename: P) -> io::Result>> -where P: AsRef, { +where + P: AsRef, +{ let file = File::open(filename)?; Ok(io::BufReader::new(file).lines()) } -fn print_help(){ - print!("Reads a virtual alias file and needs a default domain to append to local paths, e.g. - ./alias_to_sieve example.com.txt example.com"); +fn print_help() { + print!( + "Reads a virtual alias file and needs a default domain to append to local paths, e.g. + ./alias_to_sieve example.com.txt example.com" + ); } -- 2.39.5 From c7bb1194d2924300a15a0b8244f9a2cecceb83f8 Mon Sep 17 00:00:00 2001 From: Gonne Date: Mon, 25 Nov 2024 21:26:39 +0100 Subject: [PATCH 15/23] Reintroduce ordering --- packages/alias-to-sieve/Cargo.toml | 2 +- packages/alias-to-sieve/src/main.rs | 40 ++++++++++++++++++++--------- 2 files changed, 29 insertions(+), 13 deletions(-) diff --git a/packages/alias-to-sieve/Cargo.toml b/packages/alias-to-sieve/Cargo.toml index f9f5c5b..77b7e28 100644 --- a/packages/alias-to-sieve/Cargo.toml +++ b/packages/alias-to-sieve/Cargo.toml @@ -7,4 +7,4 @@ rust-version = "1.68.2" [dependencies] fqdn = {version = "0.4.2", features = ["domain-label-length-limited-to-63", "domain-name-without-special-chars"]} -email-address-parser = "2.0.0" \ No newline at end of file +email-address-parser = "2.0.0" diff --git a/packages/alias-to-sieve/src/main.rs b/packages/alias-to-sieve/src/main.rs index 65eb210..547dbc0 100644 --- a/packages/alias-to-sieve/src/main.rs +++ b/packages/alias-to-sieve/src/main.rs @@ -1,19 +1,34 @@ use email_address_parser::EmailAddress; use fqdn::FQDN; -use std::collections::HashMap; +use std::cmp::Ordering; +use std::collections::BTreeMap; use std::env; use std::fs::File; use std::io::{self, BufRead}; use std::path::Path; use std::str::FromStr; -#[derive(Debug)] struct AliasFile { content: io::Lines>, default_domain: FQDN, } -type AliasMap = HashMap>; +#[derive(PartialEq, Eq, Clone)] +struct OrdEmailAddress(EmailAddress); + +impl PartialOrd for OrdEmailAddress { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.0.to_string().cmp(&other.0.to_string())) + } +} + +impl Ord for OrdEmailAddress { + fn cmp(&self, other: &Self) -> Ordering { + self.0.to_string().cmp(&other.0.to_string()) + } +} + +type AliasMap = BTreeMap>; fn main() { let args: Vec = env::args().collect(); @@ -35,16 +50,17 @@ fn main() { fn generate_sieve_script(redirects: AliasMap) -> String { let mut script: String = "require [\"envelope\", \"copy\"];\n\n".to_string(); - for (redirect, destinations) in redirects { - script += format!("if envelope :is \"to\" \"{}\" {{\n{}}}\n", redirect, { + for (redirect, mut destinations) in redirects { + script += format!("if envelope :is \"to\" \"{}\" {{\n{}}}\n", redirect.0, { let mut subscript: String = String::new(); + destinations.sort(); for destination in destinations.iter().rev().skip(1).rev() { - subscript += format!(" redirect :copy \"{destination}\";\n").as_str(); + subscript += format!(" redirect :copy \"{}\";\n", destination.0).as_str(); } subscript + format!( " redirect \"{}\";\n", - destinations.iter().next_back().unwrap() + destinations.iter().next_back().unwrap().0 ) .as_str() }) @@ -56,7 +72,7 @@ fn generate_sieve_script(redirects: AliasMap) -> String { fn parse_alias_to_map(alias_files: Vec) -> AliasMap { // File must exist in the current path let mut redirect_map: AliasMap = AliasMap::new(); - let mut destinations: Vec = Vec::new(); + let mut destinations: Vec = Vec::new(); for alias_file in alias_files { for line in alias_file.content { let line = line.unwrap(); @@ -65,7 +81,7 @@ fn parse_alias_to_map(alias_files: Vec) -> AliasMap { if destination.is_empty() { continue; } - let redirects: Vec = line + let redirects: Vec = line .split_at(line.find(char::is_whitespace).unwrap_or(0)) .1 .split(' ') @@ -107,13 +123,13 @@ fn parse_alias_to_map(alias_files: Vec) -> AliasMap { redirect_map } -fn to_mailaddress(local_part: &str, default_domain: &FQDN) -> EmailAddress { +fn to_mailaddress(local_part: &str, default_domain: &FQDN) -> OrdEmailAddress { let mut addr = local_part.trim().to_string(); addr = addr.replace(',', ""); if addr.contains('@') { - return EmailAddress::parse(&addr, None).unwrap(); + return OrdEmailAddress(EmailAddress::parse(&addr, None).unwrap()); } - EmailAddress::new(&addr, &default_domain.to_string(), None).unwrap() + OrdEmailAddress(EmailAddress::new(&addr, &default_domain.to_string(), None).unwrap()) } // The output is wrapped in a Result to allow matching on errors. -- 2.39.5 From 0d71c66d4b7995bfc0f675752f3858144efad7a9 Mon Sep 17 00:00:00 2001 From: Gonne Date: Tue, 26 Nov 2024 19:15:25 +0100 Subject: [PATCH 16/23] Reorder and comment --- packages/alias-to-sieve/src/main.rs | 138 +++++++++++------- .../testdata/infiniterec.aliases | 2 + 2 files changed, 84 insertions(+), 56 deletions(-) create mode 100644 packages/alias-to-sieve/testdata/infiniterec.aliases diff --git a/packages/alias-to-sieve/src/main.rs b/packages/alias-to-sieve/src/main.rs index 547dbc0..d14790d 100644 --- a/packages/alias-to-sieve/src/main.rs +++ b/packages/alias-to-sieve/src/main.rs @@ -36,6 +36,8 @@ fn main() { print_help(); return; } + + // Collect alias files and their default domains let mut alias_files: Vec = Vec::new(); for i in (1..args.len()).step_by(2) { if let Ok(lines) = read_lines(&args[i]) { @@ -48,6 +50,82 @@ fn main() { println!("{}", generate_sieve_script(parse_alias_to_map(alias_files))); } +/// Read a virtual alias file (http://www.postfix.org/virtual.5.html) +/// and convert it to a map of destination addresses to a list of their final forwarding addresses. +fn parse_alias_to_map(alias_files: Vec) -> AliasMap { + // File must exist in the current path + let mut redirect_map: AliasMap = AliasMap::new(); + let mut destinations: Vec = Vec::new(); + + // Extract all pairs (destination to redirect addresses) from the alias files + for alias_file in alias_files { + for line in alias_file.content { + let line = line.unwrap(); + + // Ignore comments in the alias file + let line = String::from(line.split_at(line.find('#').unwrap_or(line.len())).0); + let destination = line.split_at(line.find(char::is_whitespace).unwrap_or(0)).0; + + if destination.is_empty() { + continue; + } + + let redirects: Vec = line + .split_at(line.find(char::is_whitespace).unwrap_or(0)) + .1 + .split(' ') + .filter(|address| address.trim().to_string().replace(',', "") != "") + .map(|addr| to_mailaddress(addr, &alias_file.default_domain)) + .collect(); + + if redirects.is_empty() { + continue; + } + destinations.push(to_mailaddress(destination, &alias_file.default_domain)); + redirect_map.insert( + to_mailaddress(destination, &alias_file.default_domain), + redirects, + ); + } + } + + // Replace redirects that are again forwarded elsewhere by that. + // Break after depth max_iterations and assume infinite recursion afterwards. + let mut changed = true; + let mut iterations = 0; + let max_iterations = 100; + while changed && iterations < max_iterations { + changed = false; + iterations += 1; + let mut all_new_redirects: AliasMap = AliasMap::new(); + for destination in &destinations { + for forward_to in redirect_map.get(destination).unwrap() { + if let Some(new_redirects) = redirect_map.get(forward_to) { + changed = true; + all_new_redirects + .entry(destination.clone()) + .or_insert(redirect_map.get(destination).unwrap().clone()) + .retain(|dest| *dest != *forward_to); + all_new_redirects + .entry(destination.clone()) + .and_modify(|d| d.extend(new_redirects.iter().cloned())); + } + } + } + for (destination, new_redirect) in all_new_redirects { + *redirect_map.get_mut(&destination).unwrap() = new_redirect; + } + } + if iterations == max_iterations { + panic!("Possibly infinite recursion detected in parse_alias_map. Did not terminate after {max_iterations} rounds."); + } + redirect_map +} + +/// Generate a Sieve script (https://en.wikipedia.org/wiki/Sieve_(mail_filtering_language)) +/// from a map of destination addresses to a list of their forwarding addresses. +/// +/// Addresses are sorted according to the order on OrdEmailAddress. fn generate_sieve_script(redirects: AliasMap) -> String { let mut script: String = "require [\"envelope\", \"copy\"];\n\n".to_string(); for (redirect, mut destinations) in redirects { @@ -69,62 +147,10 @@ fn generate_sieve_script(redirects: AliasMap) -> String { script } -fn parse_alias_to_map(alias_files: Vec) -> AliasMap { - // File must exist in the current path - let mut redirect_map: AliasMap = AliasMap::new(); - let mut destinations: Vec = Vec::new(); - for alias_file in alias_files { - for line in alias_file.content { - let line = line.unwrap(); - let line = String::from(line.split_at(line.find('#').unwrap_or(line.len())).0); - let destination = line.split_at(line.find(char::is_whitespace).unwrap_or(0)).0; - if destination.is_empty() { - continue; - } - let redirects: Vec = line - .split_at(line.find(char::is_whitespace).unwrap_or(0)) - .1 - .split(' ') - .filter(|address| address.trim().to_string().replace(',', "") != "") - .map(|addr| to_mailaddress(addr, &alias_file.default_domain)) - .collect(); - if redirects.is_empty() { - continue; - } - destinations.push(to_mailaddress(destination, &alias_file.default_domain)); - redirect_map.insert( - to_mailaddress(destination, &alias_file.default_domain), - redirects, - ); - } - } - let mut changed = true; - while changed { - changed = false; - let mut all_new_redirects: AliasMap = AliasMap::new(); - for destination in &destinations { - for forward_to in redirect_map.get(destination).unwrap() { - if let Some(new_redirects) = redirect_map.get(forward_to) { - changed = true; - all_new_redirects - .entry(destination.clone()) - .or_insert(redirect_map.get(destination).unwrap().clone()) - .retain(|dest| *dest != *forward_to); - all_new_redirects - .entry(destination.clone()) - .and_modify(|d| d.extend(new_redirects.iter().cloned())); - } - } - } - for (destination, new_redirect) in all_new_redirects { - *redirect_map.get_mut(&destination).unwrap() = new_redirect; - } - } - redirect_map -} - -fn to_mailaddress(local_part: &str, default_domain: &FQDN) -> OrdEmailAddress { - let mut addr = local_part.trim().to_string(); +/// Create an OrdEmailAddress from some alias entry. +/// Return parameter for complete mail addresses and append the default domain for local parts. +fn to_mailaddress(alias_entry: &str, default_domain: &FQDN) -> OrdEmailAddress { + let mut addr = alias_entry.trim().to_string(); addr = addr.replace(',', ""); if addr.contains('@') { return OrdEmailAddress(EmailAddress::parse(&addr, None).unwrap()); diff --git a/packages/alias-to-sieve/testdata/infiniterec.aliases b/packages/alias-to-sieve/testdata/infiniterec.aliases new file mode 100644 index 0000000..1ac500e --- /dev/null +++ b/packages/alias-to-sieve/testdata/infiniterec.aliases @@ -0,0 +1,2 @@ +# This creates an infinite recursion +orga orga -- 2.39.5 From 9a493ba2d169bf284a224b6d56d26866551588e4 Mon Sep 17 00:00:00 2001 From: Gonne Date: Thu, 28 Nov 2024 16:38:09 +0100 Subject: [PATCH 17/23] cargo clippy suggestions --- packages/alias-to-sieve/src/main.rs | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/packages/alias-to-sieve/src/main.rs b/packages/alias-to-sieve/src/main.rs index d14790d..512efd0 100644 --- a/packages/alias-to-sieve/src/main.rs +++ b/packages/alias-to-sieve/src/main.rs @@ -50,7 +50,7 @@ fn main() { println!("{}", generate_sieve_script(parse_alias_to_map(alias_files))); } -/// Read a virtual alias file (http://www.postfix.org/virtual.5.html) +/// Read a virtual alias file /// and convert it to a map of destination addresses to a list of their final forwarding addresses. fn parse_alias_to_map(alias_files: Vec) -> AliasMap { // File must exist in the current path @@ -74,7 +74,7 @@ fn parse_alias_to_map(alias_files: Vec) -> AliasMap { .split_at(line.find(char::is_whitespace).unwrap_or(0)) .1 .split(' ') - .filter(|address| address.trim().to_string().replace(',', "") != "") + .filter(|address| !address.trim().to_string().replace(',', "").is_empty()) .map(|addr| to_mailaddress(addr, &alias_file.default_domain)) .collect(); @@ -116,16 +116,14 @@ fn parse_alias_to_map(alias_files: Vec) -> AliasMap { *redirect_map.get_mut(&destination).unwrap() = new_redirect; } } - if iterations == max_iterations { - panic!("Possibly infinite recursion detected in parse_alias_map. Did not terminate after {max_iterations} rounds."); - } + assert!(iterations != max_iterations, "Possibly infinite recursion detected in parse_alias_map. Did not terminate after {max_iterations} rounds."); redirect_map } -/// Generate a Sieve script (https://en.wikipedia.org/wiki/Sieve_(mail_filtering_language)) +/// Generate a Sieve script /// from a map of destination addresses to a list of their forwarding addresses. /// -/// Addresses are sorted according to the order on OrdEmailAddress. +/// Addresses are sorted according to the order on `OrdEmailAddress`. fn generate_sieve_script(redirects: AliasMap) -> String { let mut script: String = "require [\"envelope\", \"copy\"];\n\n".to_string(); for (redirect, mut destinations) in redirects { @@ -147,7 +145,7 @@ fn generate_sieve_script(redirects: AliasMap) -> String { script } -/// Create an OrdEmailAddress from some alias entry. +/// Create an `OrdEmailAddress` from some alias entry. /// Return parameter for complete mail addresses and append the default domain for local parts. fn to_mailaddress(alias_entry: &str, default_domain: &FQDN) -> OrdEmailAddress { let mut addr = alias_entry.trim().to_string(); -- 2.39.5 From cc432e8b2cb5d2b7e52b8094cdead65a6530a538 Mon Sep 17 00:00:00 2001 From: Gonne Date: Mon, 2 Dec 2024 20:52:32 +0100 Subject: [PATCH 18/23] Some basic tests --- .gitignore | 2 + packages/alias-to-sieve/src/lib.rs | 183 ++++++++++++++++++ packages/alias-to-sieve/src/main.rs | 150 +------------- .../alias-to-sieve/testdata/simple.aliases | 4 + 4 files changed, 194 insertions(+), 145 deletions(-) create mode 100644 packages/alias-to-sieve/src/lib.rs create mode 100644 packages/alias-to-sieve/testdata/simple.aliases diff --git a/.gitignore b/.gitignore index 7b4a195..9f9742f 100644 --- a/.gitignore +++ b/.gitignore @@ -34,3 +34,5 @@ packages/alias-to/sieve/testdata/virt_aliases # Added by cargo packages/alias-to-sieve/target + +packages/alias-to-sieve/tarpaulin-report.html diff --git a/packages/alias-to-sieve/src/lib.rs b/packages/alias-to-sieve/src/lib.rs new file mode 100644 index 0000000..1cb6577 --- /dev/null +++ b/packages/alias-to-sieve/src/lib.rs @@ -0,0 +1,183 @@ +use email_address_parser::EmailAddress; +use fqdn::FQDN; +use std::cmp::Ordering; +use std::collections::BTreeMap; +use std::error::Error; +use std::fs::File; +use std::io::{self, BufRead}; +use std::path::Path; + +pub struct AliasFile { + pub content: io::Lines>, + pub default_domain: FQDN, +} + +#[derive(PartialEq, Eq, Clone)] +pub struct OrdEmailAddress(EmailAddress); + +impl PartialOrd for OrdEmailAddress { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.0.to_string().cmp(&other.0.to_string())) + } +} + +impl Ord for OrdEmailAddress { + fn cmp(&self, other: &Self) -> Ordering { + self.0.to_string().cmp(&other.0.to_string()) + } +} + +pub type AliasMap = BTreeMap>; + +/// Read a virtual alias file +/// and convert it to a map of destination addresses to a list of their final forwarding addresses. +pub fn parse_alias_to_map(alias_files: Vec) -> Result> { + // File must exist in the current path + let mut redirect_map: AliasMap = AliasMap::new(); + let mut destinations: Vec = Vec::new(); + + // Extract all pairs (destination to redirect addresses) from the alias files + for alias_file in alias_files { + for line in alias_file.content { + // Ignore comments in the alias file + let line = line?; + let line = String::from(line.split_at(line.find('#').unwrap_or(line.len())).0); + let destination = line.split_at(line.find(char::is_whitespace).unwrap_or(0)).0; + + if destination.is_empty() { + continue; + } + + let redirects: Vec = line + .split_at(line.find(char::is_whitespace).unwrap_or(0)) + .1 + .split(' ') + .filter(|address| !address.trim().to_string().replace(',', "").is_empty()) + .map(|addr| to_mailaddress(addr, &alias_file.default_domain)) + .collect::, _>>()?; + + if redirects.is_empty() { + continue; + } + destinations.push(to_mailaddress(destination, &alias_file.default_domain)?); + redirect_map.insert( + to_mailaddress(destination, &alias_file.default_domain)?, + redirects, + ); + } + } + + // Replace redirects that are again forwarded elsewhere by that. + // Break after depth max_iterations and assume infinite recursion afterwards. + let mut changed = true; + let mut iterations = 0; + let max_iterations = 100; + while changed && iterations < max_iterations { + changed = false; + iterations += 1; + let mut all_new_redirects: AliasMap = AliasMap::new(); + for destination in &destinations { + for forward_to in redirect_map.get(destination).unwrap() { + if let Some(new_redirects) = redirect_map.get(forward_to) { + changed = true; + all_new_redirects + .entry(destination.clone()) + .or_insert(redirect_map.get(destination).unwrap().clone()) + .retain(|dest| *dest != *forward_to); + all_new_redirects + .entry(destination.clone()) + .and_modify(|d| d.extend(new_redirects.iter().cloned())); + } + } + } + for (destination, new_redirect) in all_new_redirects { + *redirect_map.get_mut(&destination).unwrap() = new_redirect; + } + } + if iterations == max_iterations { + return Err(String::from("Possibly infinite recursion detected in parse_alias_map. Did not terminate after {max_iterations} rounds.").into()); + } + Ok(redirect_map) +} + +/// Create an `OrdEmailAddress` from some alias entry. +/// Return parameter for complete mail addresses and append the default domain for local parts. +fn to_mailaddress( + alias_entry: &str, + default_domain: &FQDN, +) -> Result> { + let mut addr = alias_entry.trim().to_string(); + addr = addr.replace(',', ""); + if addr.contains('@') { + return Ok(OrdEmailAddress( + EmailAddress::parse(&addr, None) + .ok_or::>(String::from("Mailaddress {addr} not parsable.").into())?, + )); + } + let unsortable_mail = EmailAddress::new(&addr, &default_domain.to_string(), None)?; + Ok(OrdEmailAddress(unsortable_mail)) +} + +// The output is wrapped in a Result to allow matching on errors. +// Returns an Iterator to the Reader of the lines of the file. +pub fn read_lines

(filename: P) -> io::Result>> +where + P: AsRef, +{ + let file = File::open(filename)?; + Ok(io::BufReader::new(file).lines()) +} + +/// Generate a Sieve script +/// from a map of destination addresses to a list of their forwarding addresses. +/// +/// Addresses are sorted according to the order on `OrdEmailAddress`. +pub fn generate_sieve_script(redirects: AliasMap) -> String { + let mut script: String = "require [\"envelope\", \"copy\"];\n\n".to_string(); + for (redirect, mut destinations) in redirects { + script += format!("if envelope :is \"to\" \"{}\" {{\n{}}}\n", redirect.0, { + let mut subscript: String = String::new(); + destinations.sort(); + for destination in destinations.iter().rev().skip(1).rev() { + subscript += format!(" redirect :copy \"{}\";\n", destination.0).as_str(); + } + subscript + + format!( + " redirect \"{}\";\n", + destinations.iter().next_back().unwrap().0 + ) + .as_str() + }) + .as_str(); + } + script +} + + +#[cfg(test)] +mod tests { + use super::*; + use std::str::FromStr; + + #[test] + fn recursion_detection() { + let result = parse_alias_to_map(vec![AliasFile { + content: read_lines("testdata/infiniterec.aliases").unwrap(), + default_domain: FQDN::from_str("example.com").unwrap(), + }]); + assert!(result.is_err()); + } + + #[test] + fn basic_parsing() { + let result = parse_alias_to_map(vec![AliasFile { + content: read_lines("testdata/simple.aliases").unwrap(), + default_domain: FQDN::from_str("example.com").unwrap(), + }]).unwrap(); + assert_eq!(result.len(), 4); + + for redirects in result.iter() { + assert_eq!(redirects.1[0].0.to_string(), "me@example.org"); + } + } +} \ No newline at end of file diff --git a/packages/alias-to-sieve/src/main.rs b/packages/alias-to-sieve/src/main.rs index 512efd0..82b2cd3 100644 --- a/packages/alias-to-sieve/src/main.rs +++ b/packages/alias-to-sieve/src/main.rs @@ -1,35 +1,8 @@ -use email_address_parser::EmailAddress; +use alias_to_sieve::*; use fqdn::FQDN; -use std::cmp::Ordering; -use std::collections::BTreeMap; use std::env; -use std::fs::File; -use std::io::{self, BufRead}; -use std::path::Path; use std::str::FromStr; -struct AliasFile { - content: io::Lines>, - default_domain: FQDN, -} - -#[derive(PartialEq, Eq, Clone)] -struct OrdEmailAddress(EmailAddress); - -impl PartialOrd for OrdEmailAddress { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.0.to_string().cmp(&other.0.to_string())) - } -} - -impl Ord for OrdEmailAddress { - fn cmp(&self, other: &Self) -> Ordering { - self.0.to_string().cmp(&other.0.to_string()) - } -} - -type AliasMap = BTreeMap>; - fn main() { let args: Vec = env::args().collect(); if args.len() < 2 || args.len() % 2 == 0 { @@ -47,123 +20,10 @@ fn main() { }); } } - println!("{}", generate_sieve_script(parse_alias_to_map(alias_files))); -} - -/// Read a virtual alias file -/// and convert it to a map of destination addresses to a list of their final forwarding addresses. -fn parse_alias_to_map(alias_files: Vec) -> AliasMap { - // File must exist in the current path - let mut redirect_map: AliasMap = AliasMap::new(); - let mut destinations: Vec = Vec::new(); - - // Extract all pairs (destination to redirect addresses) from the alias files - for alias_file in alias_files { - for line in alias_file.content { - let line = line.unwrap(); - - // Ignore comments in the alias file - let line = String::from(line.split_at(line.find('#').unwrap_or(line.len())).0); - let destination = line.split_at(line.find(char::is_whitespace).unwrap_or(0)).0; - - if destination.is_empty() { - continue; - } - - let redirects: Vec = line - .split_at(line.find(char::is_whitespace).unwrap_or(0)) - .1 - .split(' ') - .filter(|address| !address.trim().to_string().replace(',', "").is_empty()) - .map(|addr| to_mailaddress(addr, &alias_file.default_domain)) - .collect(); - - if redirects.is_empty() { - continue; - } - destinations.push(to_mailaddress(destination, &alias_file.default_domain)); - redirect_map.insert( - to_mailaddress(destination, &alias_file.default_domain), - redirects, - ); - } - } - - // Replace redirects that are again forwarded elsewhere by that. - // Break after depth max_iterations and assume infinite recursion afterwards. - let mut changed = true; - let mut iterations = 0; - let max_iterations = 100; - while changed && iterations < max_iterations { - changed = false; - iterations += 1; - let mut all_new_redirects: AliasMap = AliasMap::new(); - for destination in &destinations { - for forward_to in redirect_map.get(destination).unwrap() { - if let Some(new_redirects) = redirect_map.get(forward_to) { - changed = true; - all_new_redirects - .entry(destination.clone()) - .or_insert(redirect_map.get(destination).unwrap().clone()) - .retain(|dest| *dest != *forward_to); - all_new_redirects - .entry(destination.clone()) - .and_modify(|d| d.extend(new_redirects.iter().cloned())); - } - } - } - for (destination, new_redirect) in all_new_redirects { - *redirect_map.get_mut(&destination).unwrap() = new_redirect; - } - } - assert!(iterations != max_iterations, "Possibly infinite recursion detected in parse_alias_map. Did not terminate after {max_iterations} rounds."); - redirect_map -} - -/// Generate a Sieve script -/// from a map of destination addresses to a list of their forwarding addresses. -/// -/// Addresses are sorted according to the order on `OrdEmailAddress`. -fn generate_sieve_script(redirects: AliasMap) -> String { - let mut script: String = "require [\"envelope\", \"copy\"];\n\n".to_string(); - for (redirect, mut destinations) in redirects { - script += format!("if envelope :is \"to\" \"{}\" {{\n{}}}\n", redirect.0, { - let mut subscript: String = String::new(); - destinations.sort(); - for destination in destinations.iter().rev().skip(1).rev() { - subscript += format!(" redirect :copy \"{}\";\n", destination.0).as_str(); - } - subscript - + format!( - " redirect \"{}\";\n", - destinations.iter().next_back().unwrap().0 - ) - .as_str() - }) - .as_str(); - } - script -} - -/// Create an `OrdEmailAddress` from some alias entry. -/// Return parameter for complete mail addresses and append the default domain for local parts. -fn to_mailaddress(alias_entry: &str, default_domain: &FQDN) -> OrdEmailAddress { - let mut addr = alias_entry.trim().to_string(); - addr = addr.replace(',', ""); - if addr.contains('@') { - return OrdEmailAddress(EmailAddress::parse(&addr, None).unwrap()); - } - OrdEmailAddress(EmailAddress::new(&addr, &default_domain.to_string(), None).unwrap()) -} - -// The output is wrapped in a Result to allow matching on errors. -// Returns an Iterator to the Reader of the lines of the file. -fn read_lines

(filename: P) -> io::Result>> -where - P: AsRef, -{ - let file = File::open(filename)?; - Ok(io::BufReader::new(file).lines()) + println!( + "{}", + generate_sieve_script(parse_alias_to_map(alias_files).unwrap()) + ); } fn print_help() { diff --git a/packages/alias-to-sieve/testdata/simple.aliases b/packages/alias-to-sieve/testdata/simple.aliases new file mode 100644 index 0000000..690f620 --- /dev/null +++ b/packages/alias-to-sieve/testdata/simple.aliases @@ -0,0 +1,4 @@ +admin root +sudo root +postmaster admin +root me@example.org -- 2.39.5 From c5dc52d0ebe73d782eecf25a49222fb71272ed33 Mon Sep 17 00:00:00 2001 From: Gonne Date: Tue, 25 Feb 2025 14:42:44 +0100 Subject: [PATCH 19/23] Dependency updates --- packages/alias-to-sieve/Cargo.lock | 99 ++++++++++++++++-------------- 1 file changed, 54 insertions(+), 45 deletions(-) diff --git a/packages/alias-to-sieve/Cargo.lock b/packages/alias-to-sieve/Cargo.lock index 33171c2..114d415 100644 --- a/packages/alias-to-sieve/Cargo.lock +++ b/packages/alias-to-sieve/Cargo.lock @@ -21,9 +21,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.16.0" +version = "3.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" +checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" [[package]] name = "cfg-if" @@ -43,9 +43,9 @@ dependencies = [ [[package]] name = "cpufeatures" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16b80225097f2e5ae4e7179dd2266824648f3e2f49d9134d584b76389d31c4c3" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" dependencies = [ "libc", ] @@ -85,9 +85,9 @@ dependencies = [ [[package]] name = "fqdn" -version = "0.4.2" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d649848b92394c9e1357f6574e48ba9b073a20ceac342f7fbfd0ab5f12524d" +checksum = "3e7cf4b6cb33615d9adab21d74fd820753c532ef7c15ff556e382abde22e4023" [[package]] name = "generic-array" @@ -101,15 +101,15 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.164" +version = "0.2.170" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "433bfe06b8c75da9b2e3fbea6e5329ff87748f0b144ef75306e674c3f6f7c13f" +checksum = "875b3680cb2f8f71bdcf9a30f38d48282f5d3c95cbf9b3fa57269bb5d5c06828" [[package]] name = "log" -version = "0.4.22" +version = "0.4.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" +checksum = "30bde2b3dc3671ae49d8e2e9f044c7c005836e7a023ee57cffa25ab82764bb9e" [[package]] name = "memchr" @@ -119,15 +119,15 @@ checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "once_cell" -version = "1.20.2" +version = "1.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" +checksum = "945462a4b81e43c4e3ba96bd7b49d834c6f61198356aa858733bc4acf3cbe62e" [[package]] name = "pest" -version = "2.7.14" +version = "2.7.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "879952a81a83930934cbf1786752d6dedc3b1f29e8f8fb2ad1d0a36f377cf442" +checksum = "8b7cafe60d6cf8e62e1b9b2ea516a089c008945bb5a275416789e7db0bc199dc" dependencies = [ "memchr", "thiserror", @@ -136,9 +136,9 @@ dependencies = [ [[package]] name = "pest_derive" -version = "2.7.14" +version = "2.7.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d214365f632b123a47fd913301e14c946c61d1c183ee245fa76eb752e59a02dd" +checksum = "816518421cfc6887a0d62bf441b6ffb4536fcc926395a69e1a85852d4363f57e" dependencies = [ "pest", "pest_generator", @@ -146,9 +146,9 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.7.14" +version = "2.7.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb55586734301717aea2ac313f50b2eb8f60d2fc3dc01d190eefa2e625f60c4e" +checksum = "7d1396fd3a870fc7838768d171b4616d5c91f6cc25e377b673d714567d99377b" dependencies = [ "pest", "pest_meta", @@ -159,9 +159,9 @@ dependencies = [ [[package]] name = "pest_meta" -version = "2.7.14" +version = "2.7.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b75da2a70cf4d9cb76833c990ac9cd3923c9a8905a8929789ce347c84564d03d" +checksum = "e1e58089ea25d717bfd31fb534e4f3afcc2cc569c70de3e239778991ea3b7dea" dependencies = [ "once_cell", "pest", @@ -170,9 +170,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.92" +version = "1.0.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" +checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" dependencies = [ "unicode-ident", ] @@ -188,13 +188,19 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.37" +version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" dependencies = [ "proc-macro2", ] +[[package]] +name = "rustversion" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4" + [[package]] name = "sha2" version = "0.10.8" @@ -208,9 +214,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.89" +version = "2.0.98" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44d46482f1c1c87acd84dea20c1bf5ebff4c757009ed6bf19cfd36fb10e92c4e" +checksum = "36147f1a48ae0ec2b5b3bc5b537d267457555a10dc06f3dbc8cb11ba3006d3b1" dependencies = [ "proc-macro2", "quote", @@ -219,18 +225,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.69" +version = "2.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +checksum = "d452f284b73e6d76dd36758a0c8684b1d5be31f92b89d07fd5822175732206fc" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.69" +version = "2.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +checksum = "26afc1baea8a989337eeb52b6e72a039780ce45c3edfcc9c5b9d112feeb173c2" dependencies = [ "proc-macro2", "quote", @@ -239,9 +245,9 @@ dependencies = [ [[package]] name = "typenum" -version = "1.17.0" +version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" +checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" [[package]] name = "ucd-trie" @@ -251,9 +257,9 @@ checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" [[package]] name = "unicode-ident" -version = "1.0.14" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" +checksum = "00e2473a93778eb0bad35909dff6a10d28e63f792f16ed15e404fca9d5eeedbe" [[package]] name = "version_check" @@ -263,24 +269,24 @@ checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "wasm-bindgen" -version = "0.2.95" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "128d1e363af62632b8eb57219c8fd7877144af57558fb2ef0368d0087bddeb2e" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" dependencies = [ "cfg-if", "once_cell", + "rustversion", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" -version = "0.2.95" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb6dd4d3ca0ddffd1dd1c9c04f94b868c37ff5fac97c30b97cff2d74fce3a358" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" dependencies = [ "bumpalo", "log", - "once_cell", "proc-macro2", "quote", "syn", @@ -289,9 +295,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.95" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e79384be7f8f5a9dd5d7167216f022090cf1f9ec128e6e6a482a2cb5c5422c56" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -299,9 +305,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.95" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", @@ -312,6 +318,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.95" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +dependencies = [ + "unicode-ident", +] -- 2.39.5 From 281642aa38dd17d62aad1191bde6ed6cbc6c45ea Mon Sep 17 00:00:00 2001 From: Gonne Date: Tue, 11 Mar 2025 09:12:09 +0100 Subject: [PATCH 20/23] Loop through all recipients --- packages/alias-to-sieve/src/lib.rs | 34 ++++++++++++++++-------------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/packages/alias-to-sieve/src/lib.rs b/packages/alias-to-sieve/src/lib.rs index 1cb6577..99a83e2 100644 --- a/packages/alias-to-sieve/src/lib.rs +++ b/packages/alias-to-sieve/src/lib.rs @@ -133,27 +133,28 @@ where /// /// Addresses are sorted according to the order on `OrdEmailAddress`. pub fn generate_sieve_script(redirects: AliasMap) -> String { - let mut script: String = "require [\"envelope\", \"copy\"];\n\n".to_string(); + let mut script: String = + "require [\"variables\", \"copy\", \"vnd.stalwart.expressions\", \"envelope\"];\n\n" + .to_string(); for (redirect, mut destinations) in redirects { - script += format!("if envelope :is \"to\" \"{}\" {{\n{}}}\n", redirect.0, { - let mut subscript: String = String::new(); - destinations.sort(); - for destination in destinations.iter().rev().skip(1).rev() { - subscript += format!(" redirect :copy \"{}\";\n", destination.0).as_str(); + script += format!( + // inspired by https://github.com/stalwartlabs/mail-server/issues/916#issuecomment-2474844389 + "if eval \"contains_ignore_case(envelope.to, '{}')\" {{\n{}}}\n", + redirect.0, + { + let mut subscript: String = String::new(); + destinations.sort(); + for destination in destinations.iter() { + subscript += format!(" redirect :copy \"{}\";\n", destination.0).as_str(); + } + subscript } - subscript - + format!( - " redirect \"{}\";\n", - destinations.iter().next_back().unwrap().0 - ) - .as_str() - }) + ) .as_str(); } script } - #[cfg(test)] mod tests { use super::*; @@ -173,11 +174,12 @@ mod tests { let result = parse_alias_to_map(vec![AliasFile { content: read_lines("testdata/simple.aliases").unwrap(), default_domain: FQDN::from_str("example.com").unwrap(), - }]).unwrap(); + }]) + .unwrap(); assert_eq!(result.len(), 4); for redirects in result.iter() { assert_eq!(redirects.1[0].0.to_string(), "me@example.org"); } } -} \ No newline at end of file +} -- 2.39.5 From 51416950e631119ccad6c9dfe798fc7b10958220 Mon Sep 17 00:00:00 2001 From: Gonne Date: Mon, 24 Mar 2025 11:30:29 +0100 Subject: [PATCH 21/23] Redirect for all incoming recipients as a copy and discard original incoming mail This way it is not saved in the catch-all account. --- packages/alias-to-sieve/src/lib.rs | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/packages/alias-to-sieve/src/lib.rs b/packages/alias-to-sieve/src/lib.rs index 99a83e2..a398846 100644 --- a/packages/alias-to-sieve/src/lib.rs +++ b/packages/alias-to-sieve/src/lib.rs @@ -134,24 +134,41 @@ where /// Addresses are sorted according to the order on `OrdEmailAddress`. pub fn generate_sieve_script(redirects: AliasMap) -> String { let mut script: String = - "require [\"variables\", \"copy\", \"vnd.stalwart.expressions\", \"envelope\"];\n\n" - .to_string(); + "require [\"variables\", \"copy\", \"vnd.stalwart.expressions\", \"envelope\"]; + +let \"i\" \"0\"; +while \"i < count(envelope.to)\" { + let \"redirected\" \"false\"; +" + .to_string(); for (redirect, mut destinations) in redirects { script += format!( // inspired by https://github.com/stalwartlabs/mail-server/issues/916#issuecomment-2474844389 - "if eval \"contains_ignore_case(envelope.to, '{}')\" {{\n{}}}\n", + " if eval \"eq_ignore_case(envelope.to[i], '{}')\" {{ +{} + let \"redirected\" \"true\"; + }} +", redirect.0, { let mut subscript: String = String::new(); destinations.sort(); for destination in destinations.iter() { - subscript += format!(" redirect :copy \"{}\";\n", destination.0).as_str(); + subscript += format!(" redirect :copy \"{}\";", destination.0).as_str(); } subscript } ) .as_str(); } + script += " if eval \"!redirected\" { + let \"destination\" \"envelope.to[i]\"; + redirect :copy \"${destination}\"; + } +"; + script += " let \"i\" \"i+1\";\n"; + script += "} +discard;"; script } -- 2.39.5 From ecc88b5539d16fe1b64ebc09b986361e8c365c8b Mon Sep 17 00:00:00 2001 From: Gonne Date: Wed, 26 Mar 2025 07:30:12 +0100 Subject: [PATCH 22/23] Add `Delivered-To` headers --- packages/alias-to-sieve/src/lib.rs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/packages/alias-to-sieve/src/lib.rs b/packages/alias-to-sieve/src/lib.rs index a398846..35ebea2 100644 --- a/packages/alias-to-sieve/src/lib.rs +++ b/packages/alias-to-sieve/src/lib.rs @@ -134,7 +134,7 @@ where /// Addresses are sorted according to the order on `OrdEmailAddress`. pub fn generate_sieve_script(redirects: AliasMap) -> String { let mut script: String = - "require [\"variables\", \"copy\", \"vnd.stalwart.expressions\", \"envelope\"]; + "require [\"variables\", \"copy\", \"vnd.stalwart.expressions\", \"envelope\", \"editheader\"]; let \"i\" \"0\"; while \"i < count(envelope.to)\" { @@ -145,19 +145,23 @@ while \"i < count(envelope.to)\" { script += format!( // inspired by https://github.com/stalwartlabs/mail-server/issues/916#issuecomment-2474844389 " if eval \"eq_ignore_case(envelope.to[i], '{}')\" {{ + addheader \"Delivered-To\" \"{}\"; {} + deleteheader :index 1 :is \"Delivered-To\" \"{}\"; let \"redirected\" \"true\"; }} ", + redirect.0, redirect.0, { let mut subscript: String = String::new(); destinations.sort(); for destination in destinations.iter() { - subscript += format!(" redirect :copy \"{}\";", destination.0).as_str(); + subscript += format!(" redirect :copy \"{}\";\n", destination.0).as_str(); } subscript - } + }, + redirect.0 ) .as_str(); } -- 2.39.5 From 428788afe7302184c18d669b3f0f183693fa83c6 Mon Sep 17 00:00:00 2001 From: Dennis Frieberg Date: Wed, 26 Mar 2025 22:21:09 +0100 Subject: [PATCH 23/23] added alias-to-sieve to this repository --- flake-module.nix | 4 +- flake.lock | 854 +++++++++++++++++++++++++++++++++++--- flake.nix | 11 +- packages/flake-module.nix | 29 ++ 4 files changed, 835 insertions(+), 63 deletions(-) create mode 100644 packages/flake-module.nix diff --git a/flake-module.nix b/flake-module.nix index 1c9cbd3..0531612 100644 --- a/flake-module.nix +++ b/flake-module.nix @@ -9,6 +9,7 @@ # build our own packages, that are not flakes. imports = [ ./nixos/flake-module.nix + ./packages/flake-module.nix inputs.pre-commit-hooks.flakeModule # To import a flake module # 1. Add foo to inputs @@ -20,6 +21,7 @@ config, pkgs, system, + self', ... }: { devShells.default = config.pre-commit.devShell; @@ -60,7 +62,7 @@ overlays = [ (_: prev: { - alias-to-sieve = inputs.alias-to-sieve.packages.x86_64-linux.default; # add custom package to convert alias files to sieve scripts on the stalwart machine + inherit (self'.packages) alias-to-sieve; # add custom package to convert alias files to sieve scripts on the stalwart machine stalwart-mail = assert lib.assertMsg (prev.stalwart-mail.version == "0.11.6-unstable-2025-02-04") '' 1. If the bug https://github.com/stalwartlabs/sieve/issues/11 is resolved to our satisfaction, try to remove this overlay. 2. Check whether sieve-rs recieved new updates that our patch needs to be rebased upon. diff --git a/flake.lock b/flake.lock index 02016e4..a5e2a10 100644 --- a/flake.lock +++ b/flake.lock @@ -1,28 +1,374 @@ { "nodes": { - "alias-to-sieve": { + "cachix": { "inputs": { - "flake-parts": "flake-parts", + "devenv": [ + "crate2nix" + ], + "flake-compat": [ + "crate2nix" + ], + "nixpkgs": "nixpkgs", + "pre-commit-hooks": [ + "crate2nix" + ] + }, + "locked": { + "lastModified": 1709700175, + "narHash": "sha256-A0/6ZjLmT9qdYzKHmevnEIC7G+GiZ4UCr8v0poRPzds=", + "owner": "cachix", + "repo": "cachix", + "rev": "be97b37989f11b724197b5f4c7ffd78f12c8c4bf", + "type": "github" + }, + "original": { + "owner": "cachix", + "ref": "latest", + "repo": "cachix", + "type": "github" + } + }, + "cachix_2": { + "inputs": { + "devenv": [ + "crate2nix", + "crate2nix_stable" + ], + "flake-compat": [ + "crate2nix", + "crate2nix_stable" + ], + "nixpkgs": "nixpkgs_2", + "pre-commit-hooks": [ + "crate2nix", + "crate2nix_stable" + ] + }, + "locked": { + "lastModified": 1716549461, + "narHash": "sha256-lHy5kgx6J8uD+16SO47dPrbob98sh+W1tf4ceSqPVK4=", + "owner": "cachix", + "repo": "cachix", + "rev": "e2bb269fb8c0828d5d4d2d7b8d09ea85abcacbd4", + "type": "github" + }, + "original": { + "owner": "cachix", + "ref": "latest", + "repo": "cachix", + "type": "github" + } + }, + "cachix_3": { + "inputs": { + "devenv": [ + "crate2nix", + "crate2nix_stable", + "crate2nix_stable" + ], + "flake-compat": [ + "crate2nix", + "crate2nix_stable", + "crate2nix_stable" + ], + "nixpkgs": "nixpkgs_3", + "pre-commit-hooks": [ + "crate2nix", + "crate2nix_stable", + "crate2nix_stable" + ] + }, + "locked": { + "lastModified": 1716549461, + "narHash": "sha256-lHy5kgx6J8uD+16SO47dPrbob98sh+W1tf4ceSqPVK4=", + "owner": "cachix", + "repo": "cachix", + "rev": "e2bb269fb8c0828d5d4d2d7b8d09ea85abcacbd4", + "type": "github" + }, + "original": { + "owner": "cachix", + "ref": "latest", + "repo": "cachix", + "type": "github" + } + }, + "crate2nix": { + "inputs": { + "cachix": "cachix", + "crate2nix_stable": "crate2nix_stable", + "devshell": "devshell_3", + "flake-compat": "flake-compat_3", + "flake-parts": "flake-parts_3", + "nix-test-runner": "nix-test-runner_3", "nixpkgs": [ "nixpkgs" ], - "rust-overlay": "rust-overlay" + "pre-commit-hooks": "pre-commit-hooks_3" }, "locked": { - "lastModified": 1742970612, - "narHash": "sha256-+/irvF5TgMTCyHWE30BhearVDmeMHRFSBG4D6kCGlHc=", - "ref": "refs/heads/main", - "rev": "a9a819e659c0fc1baa84c83c50ec839e6819249d", - "revCount": 24, - "type": "git", - "url": "https://gitea.mathebau.de/fachschaft/alias_to_sieve" + "lastModified": 1739473963, + "narHash": "sha256-ItAhpjNUzEWd/cgZVyW/jvoGbCec4TK29e1Mnmn1oJE=", + "owner": "nix-community", + "repo": "crate2nix", + "rev": "be31feae9a82c225c0fd1bdf978565dc452a483a", + "type": "github" }, "original": { - "type": "git", - "url": "https://gitea.mathebau.de/fachschaft/alias_to_sieve" + "owner": "nix-community", + "repo": "crate2nix", + "type": "github" + } + }, + "crate2nix_stable": { + "inputs": { + "cachix": "cachix_2", + "crate2nix_stable": "crate2nix_stable_2", + "devshell": "devshell_2", + "flake-compat": "flake-compat_2", + "flake-parts": "flake-parts_2", + "nix-test-runner": "nix-test-runner_2", + "nixpkgs": "nixpkgs_5", + "pre-commit-hooks": "pre-commit-hooks_2" + }, + "locked": { + "lastModified": 1719760004, + "narHash": "sha256-esWhRnt7FhiYq0CcIxw9pvH+ybOQmWBfHYMtleaMhBE=", + "owner": "nix-community", + "repo": "crate2nix", + "rev": "1dee214bb20855fa3e1e7bb98d28922ddaff8c57", + "type": "github" + }, + "original": { + "owner": "nix-community", + "ref": "0.14.1", + "repo": "crate2nix", + "type": "github" + } + }, + "crate2nix_stable_2": { + "inputs": { + "cachix": "cachix_3", + "crate2nix_stable": "crate2nix_stable_3", + "devshell": "devshell", + "flake-compat": "flake-compat", + "flake-parts": "flake-parts", + "nix-test-runner": "nix-test-runner", + "nixpkgs": "nixpkgs_4", + "pre-commit-hooks": "pre-commit-hooks" + }, + "locked": { + "lastModified": 1712821484, + "narHash": "sha256-rGT3CW64cJS9nlnWPFWSc1iEa3dNZecVVuPVGzcsHe8=", + "owner": "nix-community", + "repo": "crate2nix", + "rev": "42883afcad3823fa5811e967fb7bff54bc3c9d6d", + "type": "github" + }, + "original": { + "owner": "nix-community", + "ref": "0.14.0", + "repo": "crate2nix", + "type": "github" + } + }, + "crate2nix_stable_3": { + "inputs": { + "flake-utils": "flake-utils" + }, + "locked": { + "lastModified": 1702842982, + "narHash": "sha256-A9AowkHIjsy1a4LuiPiVP88FMxyCWK41flZEZOUuwQM=", + "owner": "nix-community", + "repo": "crate2nix", + "rev": "75ac2973affa6b9b4f661a7b592cba6e4f51d426", + "type": "github" + }, + "original": { + "owner": "nix-community", + "ref": "0.12.0", + "repo": "crate2nix", + "type": "github" + } + }, + "devshell": { + "inputs": { + "flake-utils": "flake-utils_2", + "nixpkgs": [ + "crate2nix", + "crate2nix_stable", + "crate2nix_stable", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1717408969, + "narHash": "sha256-Q0OEFqe35fZbbRPPRdrjTUUChKVhhWXz3T9ZSKmaoVY=", + "owner": "numtide", + "repo": "devshell", + "rev": "1ebbe68d57457c8cae98145410b164b5477761f4", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "devshell", + "type": "github" + } + }, + "devshell_2": { + "inputs": { + "flake-utils": "flake-utils_3", + "nixpkgs": [ + "crate2nix", + "crate2nix_stable", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1717408969, + "narHash": "sha256-Q0OEFqe35fZbbRPPRdrjTUUChKVhhWXz3T9ZSKmaoVY=", + "owner": "numtide", + "repo": "devshell", + "rev": "1ebbe68d57457c8cae98145410b164b5477761f4", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "devshell", + "type": "github" + } + }, + "devshell_3": { + "inputs": { + "flake-utils": "flake-utils_4", + "nixpkgs": [ + "crate2nix", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1711099426, + "narHash": "sha256-HzpgM/wc3aqpnHJJ2oDqPBkNsqWbW0WfWUO8lKu8nGk=", + "owner": "numtide", + "repo": "devshell", + "rev": "2d45b54ca4a183f2fdcf4b19c895b64fbf620ee8", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "devshell", + "type": "github" + } + }, + "flake-compat": { + "locked": { + "lastModified": 1696426674, + "narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=", + "rev": "0f9255e01c2351cc7d116c072cb317785dd33b33", + "revCount": 57, + "type": "tarball", + "url": "https://api.flakehub.com/f/pinned/edolstra/flake-compat/1.0.1/018afb31-abd1-7bff-a5e4-cff7e18efb7a/source.tar.gz" + }, + "original": { + "type": "tarball", + "url": "https://flakehub.com/f/edolstra/flake-compat/1.tar.gz" + } + }, + "flake-compat_2": { + "locked": { + "lastModified": 1696426674, + "narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=", + "rev": "0f9255e01c2351cc7d116c072cb317785dd33b33", + "revCount": 57, + "type": "tarball", + "url": "https://api.flakehub.com/f/pinned/edolstra/flake-compat/1.0.1/018afb31-abd1-7bff-a5e4-cff7e18efb7a/source.tar.gz" + }, + "original": { + "type": "tarball", + "url": "https://flakehub.com/f/edolstra/flake-compat/1.tar.gz" + } + }, + "flake-compat_3": { + "locked": { + "lastModified": 1696426674, + "narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=", + "rev": "0f9255e01c2351cc7d116c072cb317785dd33b33", + "revCount": 57, + "type": "tarball", + "url": "https://api.flakehub.com/f/pinned/edolstra/flake-compat/1.0.1/018afb31-abd1-7bff-a5e4-cff7e18efb7a/source.tar.gz" + }, + "original": { + "type": "tarball", + "url": "https://flakehub.com/f/edolstra/flake-compat/1.tar.gz" } }, "flake-parts": { + "inputs": { + "nixpkgs-lib": [ + "crate2nix", + "crate2nix_stable", + "crate2nix_stable", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1719745305, + "narHash": "sha256-xwgjVUpqSviudEkpQnioeez1Uo2wzrsMaJKJClh+Bls=", + "owner": "hercules-ci", + "repo": "flake-parts", + "rev": "c3c5ecc05edc7dafba779c6c1a61cd08ac6583e9", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "flake-parts", + "type": "github" + } + }, + "flake-parts_2": { + "inputs": { + "nixpkgs-lib": [ + "crate2nix", + "crate2nix_stable", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1719745305, + "narHash": "sha256-xwgjVUpqSviudEkpQnioeez1Uo2wzrsMaJKJClh+Bls=", + "owner": "hercules-ci", + "repo": "flake-parts", + "rev": "c3c5ecc05edc7dafba779c6c1a61cd08ac6583e9", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "flake-parts", + "type": "github" + } + }, + "flake-parts_3": { + "inputs": { + "nixpkgs-lib": [ + "crate2nix", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1712014858, + "narHash": "sha256-sB4SWl2lX95bExY2gMFG5HIzvva5AVMJd4Igm+GpZNw=", + "owner": "hercules-ci", + "repo": "flake-parts", + "rev": "9126214d0a59633752a136528f5f3b9aa8565b7d", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "flake-parts", + "type": "github" + } + }, + "flake-parts_4": { "inputs": { "nixpkgs-lib": "nixpkgs-lib" }, @@ -35,26 +381,167 @@ "type": "github" }, "original": { - "owner": "hercules-ci", - "repo": "flake-parts", - "type": "github" + "id": "flake-parts", + "type": "indirect" } }, - "flake-parts_2": { + "flake-utils": { "inputs": { - "nixpkgs-lib": "nixpkgs-lib_2" + "systems": "systems" }, "locked": { - "lastModified": 1738453229, - "narHash": "sha256-7H9XgNiGLKN1G1CgRh0vUL4AheZSYzPm+zmZ7vxbJdo=", - "owner": "hercules-ci", - "repo": "flake-parts", - "rev": "32ea77a06711b758da0ad9bd6a844c5740a87abd", + "lastModified": 1694529238, + "narHash": "sha256-zsNZZGTGnMOf9YpHKJqMSsa0dXbfmxeoJ7xHlrt+xmY=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "ff7b65b44d01cf9ba6a71320833626af21126384", "type": "github" }, "original": { - "id": "flake-parts", - "type": "indirect" + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "flake-utils_2": { + "inputs": { + "systems": "systems_2" + }, + "locked": { + "lastModified": 1701680307, + "narHash": "sha256-kAuep2h5ajznlPMD9rnQyffWG8EM/C73lejGofXvdM8=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "4022d587cbbfd70fe950c1e2083a02621806a725", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "flake-utils_3": { + "inputs": { + "systems": "systems_3" + }, + "locked": { + "lastModified": 1701680307, + "narHash": "sha256-kAuep2h5ajznlPMD9rnQyffWG8EM/C73lejGofXvdM8=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "4022d587cbbfd70fe950c1e2083a02621806a725", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "flake-utils_4": { + "inputs": { + "systems": "systems_4" + }, + "locked": { + "lastModified": 1701680307, + "narHash": "sha256-kAuep2h5ajznlPMD9rnQyffWG8EM/C73lejGofXvdM8=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "4022d587cbbfd70fe950c1e2083a02621806a725", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "flake-utils_5": { + "inputs": { + "systems": "systems_5" + }, + "locked": { + "lastModified": 1710146030, + "narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "gitignore": { + "inputs": { + "nixpkgs": [ + "crate2nix", + "crate2nix_stable", + "crate2nix_stable", + "pre-commit-hooks", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1709087332, + "narHash": "sha256-HG2cCnktfHsKV0s4XW83gU3F57gaTljL9KNSuG6bnQs=", + "owner": "hercules-ci", + "repo": "gitignore.nix", + "rev": "637db329424fd7e46cf4185293b9cc8c88c95394", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "gitignore.nix", + "type": "github" + } + }, + "gitignore_2": { + "inputs": { + "nixpkgs": [ + "crate2nix", + "crate2nix_stable", + "pre-commit-hooks", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1709087332, + "narHash": "sha256-HG2cCnktfHsKV0s4XW83gU3F57gaTljL9KNSuG6bnQs=", + "owner": "hercules-ci", + "repo": "gitignore.nix", + "rev": "637db329424fd7e46cf4185293b9cc8c88c95394", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "gitignore.nix", + "type": "github" + } + }, + "gitignore_3": { + "inputs": { + "nixpkgs": [ + "crate2nix", + "pre-commit-hooks", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1709087332, + "narHash": "sha256-HG2cCnktfHsKV0s4XW83gU3F57gaTljL9KNSuG6bnQs=", + "owner": "hercules-ci", + "repo": "gitignore.nix", + "rev": "637db329424fd7e46cf4185293b9cc8c88c95394", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "gitignore.nix", + "type": "github" } }, "impermanence": { @@ -72,18 +559,66 @@ "type": "github" } }, + "nix-test-runner": { + "flake": false, + "locked": { + "lastModified": 1588761593, + "narHash": "sha256-FKJykltAN/g3eIceJl4SfDnnyuH2jHImhMrXS2KvGIs=", + "owner": "stoeffel", + "repo": "nix-test-runner", + "rev": "c45d45b11ecef3eb9d834c3b6304c05c49b06ca2", + "type": "github" + }, + "original": { + "owner": "stoeffel", + "repo": "nix-test-runner", + "type": "github" + } + }, + "nix-test-runner_2": { + "flake": false, + "locked": { + "lastModified": 1588761593, + "narHash": "sha256-FKJykltAN/g3eIceJl4SfDnnyuH2jHImhMrXS2KvGIs=", + "owner": "stoeffel", + "repo": "nix-test-runner", + "rev": "c45d45b11ecef3eb9d834c3b6304c05c49b06ca2", + "type": "github" + }, + "original": { + "owner": "stoeffel", + "repo": "nix-test-runner", + "type": "github" + } + }, + "nix-test-runner_3": { + "flake": false, + "locked": { + "lastModified": 1588761593, + "narHash": "sha256-FKJykltAN/g3eIceJl4SfDnnyuH2jHImhMrXS2KvGIs=", + "owner": "stoeffel", + "repo": "nix-test-runner", + "rev": "c45d45b11ecef3eb9d834c3b6304c05c49b06ca2", + "type": "github" + }, + "original": { + "owner": "stoeffel", + "repo": "nix-test-runner", + "type": "github" + } + }, "nixpkgs": { "locked": { - "lastModified": 1736320768, - "narHash": "sha256-nIYdTAiKIGnFNugbomgBJR+Xv5F1ZQU+HfaBqJKroC0=", + "lastModified": 1700612854, + "narHash": "sha256-yrQ8osMD+vDLGFX7pcwsY/Qr5PUd6OmDMYJZzZi0+zc=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "4bc9c909d9ac828a039f288cf872d16d38185db8", + "rev": "19cbff58383a4ae384dea4d1d0c823d72b49d614", "type": "github" }, "original": { "owner": "NixOS", - "ref": "nixpkgs-unstable", + "ref": "nixos-unstable", "repo": "nixpkgs", "type": "github" } @@ -100,19 +635,65 @@ "url": "https://github.com/NixOS/nixpkgs/archive/072a6db25e947df2f31aab9eccd0ab75d5b2da11.tar.gz" } }, - "nixpkgs-lib_2": { + "nixpkgs_2": { "locked": { - "lastModified": 1738452942, - "narHash": "sha256-vJzFZGaCpnmo7I6i416HaBLpC+hvcURh/BQwROcGIp8=", - "type": "tarball", - "url": "https://github.com/NixOS/nixpkgs/archive/072a6db25e947df2f31aab9eccd0ab75d5b2da11.tar.gz" + "lastModified": 1715534503, + "narHash": "sha256-5ZSVkFadZbFP1THataCaSf0JH2cAH3S29hU9rrxTEqk=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "2057814051972fa1453ddfb0d98badbea9b83c06", + "type": "github" }, "original": { - "type": "tarball", - "url": "https://github.com/NixOS/nixpkgs/archive/072a6db25e947df2f31aab9eccd0ab75d5b2da11.tar.gz" + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" } }, - "nixpkgs_2": { + "nixpkgs_3": { + "locked": { + "lastModified": 1715534503, + "narHash": "sha256-5ZSVkFadZbFP1THataCaSf0JH2cAH3S29hU9rrxTEqk=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "2057814051972fa1453ddfb0d98badbea9b83c06", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs_4": { + "locked": { + "lastModified": 1719506693, + "narHash": "sha256-C8e9S7RzshSdHB7L+v9I51af1gDM5unhJ2xO1ywxNH8=", + "path": "/nix/store/4p0avw1s3vf27hspgqsrqs37gxk4i83i-source", + "rev": "b2852eb9365c6de48ffb0dc2c9562591f652242a", + "type": "path" + }, + "original": { + "id": "nixpkgs", + "type": "indirect" + } + }, + "nixpkgs_5": { + "locked": { + "lastModified": 1719506693, + "narHash": "sha256-C8e9S7RzshSdHB7L+v9I51af1gDM5unhJ2xO1ywxNH8=", + "path": "/nix/store/4p0avw1s3vf27hspgqsrqs37gxk4i83i-source", + "rev": "b2852eb9365c6de48ffb0dc2c9562591f652242a", + "type": "path" + }, + "original": { + "id": "nixpkgs", + "type": "indirect" + } + }, + "nixpkgs_6": { "locked": { "lastModified": 1740367490, "narHash": "sha256-WGaHVAjcrv+Cun7zPlI41SerRtfknGQap281+AakSAw=", @@ -129,6 +710,106 @@ } }, "pre-commit-hooks": { + "inputs": { + "flake-compat": [ + "crate2nix", + "crate2nix_stable", + "crate2nix_stable", + "flake-compat" + ], + "gitignore": "gitignore", + "nixpkgs": [ + "crate2nix", + "crate2nix_stable", + "crate2nix_stable", + "nixpkgs" + ], + "nixpkgs-stable": [ + "crate2nix", + "crate2nix_stable", + "crate2nix_stable", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1719259945, + "narHash": "sha256-F1h+XIsGKT9TkGO3omxDLEb/9jOOsI6NnzsXFsZhry4=", + "owner": "cachix", + "repo": "pre-commit-hooks.nix", + "rev": "0ff4381bbb8f7a52ca4a851660fc7a437a4c6e07", + "type": "github" + }, + "original": { + "owner": "cachix", + "repo": "pre-commit-hooks.nix", + "type": "github" + } + }, + "pre-commit-hooks_2": { + "inputs": { + "flake-compat": [ + "crate2nix", + "crate2nix_stable", + "flake-compat" + ], + "gitignore": "gitignore_2", + "nixpkgs": [ + "crate2nix", + "crate2nix_stable", + "nixpkgs" + ], + "nixpkgs-stable": [ + "crate2nix", + "crate2nix_stable", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1719259945, + "narHash": "sha256-F1h+XIsGKT9TkGO3omxDLEb/9jOOsI6NnzsXFsZhry4=", + "owner": "cachix", + "repo": "pre-commit-hooks.nix", + "rev": "0ff4381bbb8f7a52ca4a851660fc7a437a4c6e07", + "type": "github" + }, + "original": { + "owner": "cachix", + "repo": "pre-commit-hooks.nix", + "type": "github" + } + }, + "pre-commit-hooks_3": { + "inputs": { + "flake-compat": [ + "crate2nix", + "flake-compat" + ], + "flake-utils": "flake-utils_5", + "gitignore": "gitignore_3", + "nixpkgs": [ + "crate2nix", + "nixpkgs" + ], + "nixpkgs-stable": [ + "crate2nix", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1712055707, + "narHash": "sha256-4XLvuSIDZJGS17xEwSrNuJLL7UjDYKGJSbK1WWX2AK8=", + "owner": "cachix", + "repo": "pre-commit-hooks.nix", + "rev": "e35aed5fda3cc79f88ed7f1795021e559582093a", + "type": "github" + }, + "original": { + "owner": "cachix", + "repo": "pre-commit-hooks.nix", + "type": "github" + } + }, + "pre-commit-hooks_4": { "inputs": { "flake-compat": [], "gitignore": [], @@ -150,32 +831,14 @@ }, "root": { "inputs": { - "alias-to-sieve": "alias-to-sieve", - "flake-parts": "flake-parts_2", + "crate2nix": "crate2nix", + "flake-parts": "flake-parts_4", "impermanence": "impermanence", - "nixpkgs": "nixpkgs_2", - "pre-commit-hooks": "pre-commit-hooks", + "nixpkgs": "nixpkgs_6", + "pre-commit-hooks": "pre-commit-hooks_4", "sops-nix": "sops-nix" } }, - "rust-overlay": { - "inputs": { - "nixpkgs": "nixpkgs" - }, - "locked": { - "lastModified": 1740450604, - "narHash": "sha256-T/lqASXzCzp5lJISCUw+qwfRmImVUnhKgAhn8ymRClI=", - "owner": "oxalica", - "repo": "rust-overlay", - "rev": "5961ca311c85c31fc5f51925b4356899eed36221", - "type": "github" - }, - "original": { - "owner": "oxalica", - "repo": "rust-overlay", - "type": "github" - } - }, "sops-nix": { "inputs": { "nixpkgs": [ @@ -195,6 +858,81 @@ "repo": "sops-nix", "type": "github" } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + }, + "systems_2": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + }, + "systems_3": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + }, + "systems_4": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + }, + "systems_5": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } } }, "root": "root", diff --git a/flake.nix b/flake.nix index b2699bc..616dcd0 100644 --- a/flake.nix +++ b/flake.nix @@ -2,10 +2,6 @@ description = "Description for the project"; inputs = { - alias-to-sieve = { - url = "git+https://gitea.mathebau.de/fachschaft/alias_to_sieve"; - inputs.nixpkgs.follows = "nixpkgs"; - }; nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; sops-nix = { url = "github:Mic92/sops-nix"; @@ -14,6 +10,13 @@ impermanence = { url = "github:nix-community/impermanence"; }; + # We need this dependency for generating the nix build files for + # the alias-to-sieve program. We can do without this if we + # would generate it by hand and version it. See ./packages/flake-module.nix + crate2nix = { + url = "github:nix-community/crate2nix"; + inputs.nixpkgs.follows = "nixpkgs"; + }; pre-commit-hooks = { url = "github:cachix/pre-commit-hooks.nix"; inputs = { diff --git a/packages/flake-module.nix b/packages/flake-module.nix new file mode 100644 index 0000000..2ffe2c2 --- /dev/null +++ b/packages/flake-module.nix @@ -0,0 +1,29 @@ +{inputs, ...}: { + perSystem = { + pkgs, + system, + self', + ... + }: { + # We build the alias to sieve tool here, this is a two step process, we first generate + # a nix expression with the crate2nix tool from nixpkgs that directly calls rustc + # and thus bypasses cargo. We could also generate this file by hand and version it, + # but that would need regeneration every time some dependency is updated, so we + # opt for build time generation. Afterwards we import and evaluate the generated + # nix expression to build the package. For the explicit solution see: + # https://nix-community.github.io/crate2nix/00_guides/30_generating/ + # and for the documentation to this one + # https://nix-community.github.io/crate2nix/00_guides/31_auto_generating/ + # as this module uses idf and forces builds during the evaluation it might + # have drastic performance implications, see + # https://nix.dev/manual/nix/2.24/language/import-from-derivation + packages.alias-to-sieve = let + alias-to-sieve-nix = inputs.crate2nix.tools.${system}.generatedCargoNix { + name = "alias-to-sieve"; + src = ./alias-to-sieve; + }; + in + (pkgs.callPackage alias-to-sieve-nix {}).rootCrate.build; + checks.alias-to-sieve = self'.packages.alias-to-sieve.override {runTests = true;}; + }; +} -- 2.39.5