From 1ec2267ce12f13c4caa1e7528fb7cd73e9d33fcb Mon Sep 17 00:00:00 2001 From: Gonne Date: Tue, 26 Nov 2024 19:15:25 +0100 Subject: [PATCH] Reorder and comment --- src/main.rs | 138 +++++++++++++++++++++-------------- testdata/infiniterec.aliases | 2 + 2 files changed, 84 insertions(+), 56 deletions(-) create mode 100644 testdata/infiniterec.aliases diff --git a/src/main.rs b/src/main.rs index 547dbc0..d14790d 100644 --- a/src/main.rs +++ b/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/testdata/infiniterec.aliases b/testdata/infiniterec.aliases new file mode 100644 index 0000000..1ac500e --- /dev/null +++ b/testdata/infiniterec.aliases @@ -0,0 +1,2 @@ +# This creates an infinite recursion +orga orga