Reorder and comment
This commit is contained in:
parent
390e714233
commit
1ec2267ce1
2 changed files with 84 additions and 56 deletions
138
src/main.rs
138
src/main.rs
|
@ -36,6 +36,8 @@ fn main() {
|
|||
print_help();
|
||||
return;
|
||||
}
|
||||
|
||||
// Collect alias files and their default domains
|
||||
let mut alias_files: Vec<AliasFile> = 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<AliasFile>) -> AliasMap {
|
||||
// File must exist in the current path
|
||||
let mut redirect_map: AliasMap = AliasMap::new();
|
||||
let mut destinations: Vec<OrdEmailAddress> = 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<OrdEmailAddress> = 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<AliasFile>) -> AliasMap {
|
||||
// File must exist in the current path
|
||||
let mut redirect_map: AliasMap = AliasMap::new();
|
||||
let mut destinations: Vec<OrdEmailAddress> = 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<OrdEmailAddress> = 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());
|
||||
|
|
2
testdata/infiniterec.aliases
vendored
Normal file
2
testdata/infiniterec.aliases
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
# This creates an infinite recursion
|
||||
orga orga
|
Loading…
Reference in a new issue