* Tue Oct 21 2025 Brian Read <brianr@koozali.org> 11.0.0-7.sme
- Typo in REMOVE partial template [SME: 13251] - Update to sievefilter generation for Headers - use whole email address if provided [SME: 13251]
This commit is contained in:
@@ -56,6 +56,14 @@
|
|||||||
return $s;
|
return $s;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Detect a full email address (simple, robust)
|
||||||
|
sub is_full_email {
|
||||||
|
my ($s) = @_;
|
||||||
|
$s //= '';
|
||||||
|
$s =~ s/^\s+|\s+$//g;
|
||||||
|
return ($s =~ /^[^@\s<>"',;]+@[^@\s<>"',;]+$/) ? 1 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
# Extract a domain from a simplified email-like string.
|
# Extract a domain from a simplified email-like string.
|
||||||
# Returns '' if no clear domain found.
|
# Returns '' if no clear domain found.
|
||||||
sub extract_domain {
|
sub extract_domain {
|
||||||
@@ -108,7 +116,7 @@
|
|||||||
if ($pmGlobRules > 0)
|
if ($pmGlobRules > 0)
|
||||||
{
|
{
|
||||||
$OUT .= "\n";
|
$OUT .= "\n";
|
||||||
$OUT .= "# --- start of Global Sieve rules (".$pmGlobRules.")---------\n";
|
$OUT .= "# --- start of Global Sieve rules ($pmGlobRules)---------\n";
|
||||||
|
|
||||||
my $pmGlobRule;
|
my $pmGlobRule;
|
||||||
foreach $pmGlobRule (sort {$a <=> $b} @pmGlobRules)
|
foreach $pmGlobRule (sort {$a <=> $b} @pmGlobRules)
|
||||||
@@ -165,24 +173,38 @@
|
|||||||
my $hv_simple = simplify_email_value($hv);
|
my $hv_simple = simplify_email_value($hv);
|
||||||
my $hv_domain = extract_domain($hv_simple);
|
my $hv_domain = extract_domain($hv_simple);
|
||||||
|
|
||||||
if ($all_addr && $hv_domain ne '') {
|
if ($all_addr) {
|
||||||
my @lb = map { lc $_ } @hn;
|
my @lb = map { lc $_ } @hn;
|
||||||
my $hn_list = join '","', @lb;
|
my $hn_list = join '","', @lb;
|
||||||
my $dom_q = sieve_quote($hv_domain);
|
if (is_full_email($hv_simple)) {
|
||||||
$cond1 = "address :domain :is [\"$hn_list\"] \"$dom_q\"";
|
my $addr_q = sieve_quote($hv_simple);
|
||||||
|
$cond1 = "address :is [\"$hn_list\"] \"$addr_q\"";
|
||||||
|
} elsif ($hv_domain ne '') {
|
||||||
|
my $dom_q = sieve_quote($hv_domain);
|
||||||
|
$cond1 = "address :domain :is [\"$hn_list\"] \"$dom_q\"";
|
||||||
|
} else {
|
||||||
|
# Fall back to true regex against listed headers
|
||||||
|
my @hn_q = map { sieve_quote($_) } @hn;
|
||||||
|
my $hn_list_q = join '","', @hn_q;
|
||||||
|
my $hv_re = sieve_regex_passthrough($hv);
|
||||||
|
$cond1 = "header :regex [\"$hn_list_q\"] \"$hv_re\"";
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
# Fall back to true regex against listed headers
|
# Mixed/non-address headers: regex against the listed headers
|
||||||
my @hn_q = map { sieve_quote($_) } @hn;
|
my @hn_q = map { sieve_quote($_) } @hn;
|
||||||
my $hn_list = join '","', @hn_q;
|
my $hn_list_q = join '","', @hn_q;
|
||||||
my $hv_re = sieve_regex_passthrough($hv);
|
my $hv_re = sieve_regex_passthrough($hv);
|
||||||
$cond1 = "header :regex [\"$hn_list\"] \"$hv_re\"";
|
$cond1 = "header :regex [\"$hn_list_q\"] \"$hv_re\"";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
# No valid header names extracted: prefer address match only if email/domain-like
|
# No valid header names extracted: prefer address match only if email/domain-like
|
||||||
my $hv_simple = simplify_email_value($h);
|
my $hv_simple = simplify_email_value($h);
|
||||||
my $hv_domain = extract_domain($hv_simple);
|
my $hv_domain = extract_domain($hv_simple);
|
||||||
if ($hv_domain ne '') {
|
if (is_full_email($hv_simple)) {
|
||||||
|
my $aq = sieve_quote($hv_simple);
|
||||||
|
$cond1 = "address :is [\"from\",\"to\",\"cc\"] \"$aq\"";
|
||||||
|
} elsif ($hv_domain ne '') {
|
||||||
my $dq = sieve_quote($hv_domain);
|
my $dq = sieve_quote($hv_domain);
|
||||||
$cond1 = "address :domain :is [\"from\",\"to\",\"cc\"] \"$dq\"";
|
$cond1 = "address :domain :is [\"from\",\"to\",\"cc\"] \"$dq\"";
|
||||||
} else {
|
} else {
|
||||||
@@ -196,7 +218,10 @@
|
|||||||
# No "Header: value" structure: prefer address match only if email/domain-like
|
# No "Header: value" structure: prefer address match only if email/domain-like
|
||||||
my $hv_simple = simplify_email_value($h);
|
my $hv_simple = simplify_email_value($h);
|
||||||
my $hv_domain = extract_domain($hv_simple);
|
my $hv_domain = extract_domain($hv_simple);
|
||||||
if ($hv_domain ne '') {
|
if (is_full_email($hv_simple)) {
|
||||||
|
my $aq = sieve_quote($hv_simple);
|
||||||
|
$cond1 = "address :is [\"from\",\"to\",\"cc\"] \"$aq\"";
|
||||||
|
} elsif ($hv_domain ne '') {
|
||||||
my $dq = sieve_quote($hv_domain);
|
my $dq = sieve_quote($hv_domain);
|
||||||
$cond1 = "address :domain :is [\"from\",\"to\",\"cc\"] \"$dq\"";
|
$cond1 = "address :domain :is [\"from\",\"to\",\"cc\"] \"$dq\"";
|
||||||
} else {
|
} else {
|
||||||
@@ -258,23 +283,36 @@
|
|||||||
my $hv2_simple = simplify_email_value($hv2);
|
my $hv2_simple = simplify_email_value($hv2);
|
||||||
my $hv2_domain = extract_domain($hv2_simple);
|
my $hv2_domain = extract_domain($hv2_simple);
|
||||||
|
|
||||||
if ($all_addr2 && $hv2_domain ne '') {
|
if ($all_addr2) {
|
||||||
my @lb2 = map { lc $_ } @hn2;
|
my @lb2 = map { lc $_ } @hn2;
|
||||||
my $hn2_list = join '","', @lb2;
|
my $hn2_list = join '","', @lb2;
|
||||||
my $dq2 = sieve_quote($hv2_domain);
|
if (is_full_email($hv2_simple)) {
|
||||||
$cond2 = "address :domain :is [\"$hn2_list\"] \"$dq2\"";
|
my $aq2 = sieve_quote($hv2_simple);
|
||||||
|
$cond2 = "address :is [\"$hn2_list\"] \"$aq2\"";
|
||||||
|
} elsif ($hv2_domain ne '') {
|
||||||
|
my $dq2 = sieve_quote($hv2_domain);
|
||||||
|
$cond2 = "address :domain :is [\"$hn2_list\"] \"$dq2\"";
|
||||||
|
} else {
|
||||||
|
my @hn2_q = map { sieve_quote($_) } @hn2;
|
||||||
|
my $hn2_list_q = join '","', @hn2_q;
|
||||||
|
my $hv2_re = sieve_regex_passthrough($hv2);
|
||||||
|
$cond2 = "header :regex [\"$hn2_list_q\"] \"$hv2_re\"";
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
my @hn2_q = map { sieve_quote($_) } @hn2;
|
my @hn2_q = map { sieve_quote($_) } @hn2;
|
||||||
my $hn2_list = join '","', @hn2_q;
|
my $hn2_list_q = join '","', @hn2_q;
|
||||||
my $hv2_re = sieve_regex_passthrough($hv2);
|
my $hv2_re = sieve_regex_passthrough($hv2);
|
||||||
$cond2 = "header :regex [\"$hn2_list\"] \"$hv2_re\"";
|
$cond2 = "header :regex [\"$hn2_list_q\"] \"$hv2_re\"";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
# No valid header names: prefer address match only if email/domain-like
|
# No valid header names: prefer address match only if email/domain-like
|
||||||
my $hv2_simple = simplify_email_value($hh);
|
my $hv2_simple = simplify_email_value($hh);
|
||||||
my $hv2_domain = extract_domain($hv2_simple);
|
my $hv2_domain = extract_domain($hv2_simple);
|
||||||
if ($hv2_domain ne '') {
|
if (is_full_email($hv2_simple)) {
|
||||||
|
my $aq2 = sieve_quote($hv2_simple);
|
||||||
|
$cond2 = "address :is [\"from\",\"to\",\"cc\"] \"$aq2\"";
|
||||||
|
} elsif ($hv2_domain ne '') {
|
||||||
my $dq2 = sieve_quote($hv2_domain);
|
my $dq2 = sieve_quote($hv2_domain);
|
||||||
$cond2 = "address :domain :is [\"from\",\"to\",\"cc\"] \"$dq2\"";
|
$cond2 = "address :domain :is [\"from\",\"to\",\"cc\"] \"$dq2\"";
|
||||||
} else {
|
} else {
|
||||||
@@ -288,7 +326,10 @@
|
|||||||
# No "Header: value" structure: prefer address match only if email/domain-like
|
# No "Header: value" structure: prefer address match only if email/domain-like
|
||||||
my $hv2_simple = simplify_email_value($hh);
|
my $hv2_simple = simplify_email_value($hh);
|
||||||
my $hv2_domain = extract_domain($hv2_simple);
|
my $hv2_domain = extract_domain($hv2_simple);
|
||||||
if ($hv2_domain ne '') {
|
if (is_full_email($hv2_simple)) {
|
||||||
|
my $aq2 = sieve_quote($hv2_simple);
|
||||||
|
$cond2 = "address :is [\"from\",\"to\",\"cc\"] \"$aq2\"";
|
||||||
|
} elsif ($hv2_domain ne '') {
|
||||||
my $dq2 = sieve_quote($hv2_domain);
|
my $dq2 = sieve_quote($hv2_domain);
|
||||||
$cond2 = "address :domain :is [\"from\",\"to\",\"cc\"] \"$dq2\"";
|
$cond2 = "address :domain :is [\"from\",\"to\",\"cc\"] \"$dq2\"";
|
||||||
} else {
|
} else {
|
||||||
@@ -429,9 +470,8 @@
|
|||||||
}
|
}
|
||||||
$OUT .= " stop;\n";
|
$OUT .= " stop;\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
$OUT .= "\}\n";
|
$OUT .= "\}\n";
|
||||||
$OUT .= "# End of Global rule $pmGlobRule\n";
|
$OUT .= "# End of Global rule $pmGlobRule\n";
|
||||||
}#foreach rule
|
}#foreach rule
|
||||||
}#if rules exist
|
}#if rules exist
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,6 +56,14 @@
|
|||||||
return $s;
|
return $s;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Detect a full email address (simple, robust)
|
||||||
|
sub is_full_email {
|
||||||
|
my ($s) = @_;
|
||||||
|
$s //= '';
|
||||||
|
$s =~ s/^\s+|\s+$//g;
|
||||||
|
return ($s =~ /^[^@\s<>"',;]+@[^@\s<>"',;]+$/) ? 1 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
# Extract a domain from a simplified email-like string.
|
# Extract a domain from a simplified email-like string.
|
||||||
# Returns '' if no clear domain found.
|
# Returns '' if no clear domain found.
|
||||||
sub extract_domain {
|
sub extract_domain {
|
||||||
@@ -165,24 +173,38 @@
|
|||||||
my $hv_simple = simplify_email_value($hv);
|
my $hv_simple = simplify_email_value($hv);
|
||||||
my $hv_domain = extract_domain($hv_simple);
|
my $hv_domain = extract_domain($hv_simple);
|
||||||
|
|
||||||
if ($all_addr && $hv_domain ne '') {
|
if ($all_addr) {
|
||||||
my @lb = map { lc $_ } @hn;
|
my @lb = map { lc $_ } @hn;
|
||||||
my $hn_list = join '","', @lb;
|
my $hn_list = join '","', @lb;
|
||||||
my $dom_q = sieve_quote($hv_domain);
|
if (is_full_email($hv_simple)) {
|
||||||
$cond1 = "address :domain :is [\"$hn_list\"] \"$dom_q\"";
|
my $addr_q = sieve_quote($hv_simple);
|
||||||
|
$cond1 = "address :is [\"$hn_list\"] \"$addr_q\"";
|
||||||
|
} elsif ($hv_domain ne '') {
|
||||||
|
my $dom_q = sieve_quote($hv_domain);
|
||||||
|
$cond1 = "address :domain :is [\"$hn_list\"] \"$dom_q\"";
|
||||||
|
} else {
|
||||||
|
# Fall back to true regex against listed headers
|
||||||
|
my @hn_q = map { sieve_quote($_) } @hn;
|
||||||
|
my $hn_list_q = join '","', @hn_q;
|
||||||
|
my $hv_re = sieve_regex_passthrough($hv);
|
||||||
|
$cond1 = "header :regex [\"$hn_list_q\"] \"$hv_re\"";
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
# Fall back to true regex against listed headers
|
# Mixed/non-address headers: regex against the listed headers
|
||||||
my @hn_q = map { sieve_quote($_) } @hn;
|
my @hn_q = map { sieve_quote($_) } @hn;
|
||||||
my $hn_list = join '","', @hn_q;
|
my $hn_list_q = join '","', @hn_q;
|
||||||
my $hv_re = sieve_regex_passthrough($hv);
|
my $hv_re = sieve_regex_passthrough($hv);
|
||||||
$cond1 = "header :regex [\"$hn_list\"] \"$hv_re\"";
|
$cond1 = "header :regex [\"$hn_list_q\"] \"$hv_re\"";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
# No valid header names extracted: prefer address match only if email/domain-like
|
# No valid header names extracted: prefer address match only if email/domain-like
|
||||||
my $hv_simple = simplify_email_value($h);
|
my $hv_simple = simplify_email_value($h);
|
||||||
my $hv_domain = extract_domain($hv_simple);
|
my $hv_domain = extract_domain($hv_simple);
|
||||||
if ($hv_domain ne '') {
|
if (is_full_email($hv_simple)) {
|
||||||
|
my $aq = sieve_quote($hv_simple);
|
||||||
|
$cond1 = "address :is [\"from\",\"to\",\"cc\"] \"$aq\"";
|
||||||
|
} elsif ($hv_domain ne '') {
|
||||||
my $dq = sieve_quote($hv_domain);
|
my $dq = sieve_quote($hv_domain);
|
||||||
$cond1 = "address :domain :is [\"from\",\"to\",\"cc\"] \"$dq\"";
|
$cond1 = "address :domain :is [\"from\",\"to\",\"cc\"] \"$dq\"";
|
||||||
} else {
|
} else {
|
||||||
@@ -196,7 +218,10 @@
|
|||||||
# No "Header: value" structure: prefer address match only if email/domain-like
|
# No "Header: value" structure: prefer address match only if email/domain-like
|
||||||
my $hv_simple = simplify_email_value($h);
|
my $hv_simple = simplify_email_value($h);
|
||||||
my $hv_domain = extract_domain($hv_simple);
|
my $hv_domain = extract_domain($hv_simple);
|
||||||
if ($hv_domain ne '') {
|
if (is_full_email($hv_simple)) {
|
||||||
|
my $aq = sieve_quote($hv_simple);
|
||||||
|
$cond1 = "address :is [\"from\",\"to\",\"cc\"] \"$aq\"";
|
||||||
|
} elsif ($hv_domain ne '') {
|
||||||
my $dq = sieve_quote($hv_domain);
|
my $dq = sieve_quote($hv_domain);
|
||||||
$cond1 = "address :domain :is [\"from\",\"to\",\"cc\"] \"$dq\"";
|
$cond1 = "address :domain :is [\"from\",\"to\",\"cc\"] \"$dq\"";
|
||||||
} else {
|
} else {
|
||||||
@@ -258,23 +283,36 @@
|
|||||||
my $hv2_simple = simplify_email_value($hv2);
|
my $hv2_simple = simplify_email_value($hv2);
|
||||||
my $hv2_domain = extract_domain($hv2_simple);
|
my $hv2_domain = extract_domain($hv2_simple);
|
||||||
|
|
||||||
if ($all_addr2 && $hv2_domain ne '') {
|
if ($all_addr2) {
|
||||||
my @lb2 = map { lc $_ } @hn2;
|
my @lb2 = map { lc $_ } @hn2;
|
||||||
my $hn2_list = join '","', @lb2;
|
my $hn2_list = join '","', @lb2;
|
||||||
my $dq2 = sieve_quote($hv2_domain);
|
if (is_full_email($hv2_simple)) {
|
||||||
$cond2 = "address :domain :is [\"$hn2_list\"] \"$dq2\"";
|
my $aq2 = sieve_quote($hv2_simple);
|
||||||
|
$cond2 = "address :is [\"$hn2_list\"] \"$aq2\"";
|
||||||
|
} elsif ($hv2_domain ne '') {
|
||||||
|
my $dq2 = sieve_quote($hv2_domain);
|
||||||
|
$cond2 = "address :domain :is [\"$hn2_list\"] \"$dq2\"";
|
||||||
|
} else {
|
||||||
|
my @hn2_q = map { sieve_quote($_) } @hn2;
|
||||||
|
my $hn2_list_q = join '","', @hn2_q;
|
||||||
|
my $hv2_re = sieve_regex_passthrough($hv2);
|
||||||
|
$cond2 = "header :regex [\"$hn2_list_q\"] \"$hv2_re\"";
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
my @hn2_q = map { sieve_quote($_) } @hn2;
|
my @hn2_q = map { sieve_quote($_) } @hn2;
|
||||||
my $hn2_list = join '","', @hn2_q;
|
my $hn2_list_q = join '","', @hn2_q;
|
||||||
my $hv2_re = sieve_regex_passthrough($hv2);
|
my $hv2_re = sieve_regex_passthrough($hv2);
|
||||||
$cond2 = "header :regex [\"$hn2_list\"] \"$hv2_re\"";
|
$cond2 = "header :regex [\"$hn2_list_q\"] \"$hv2_re\"";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
# No valid header names: prefer address match only if email/domain-like
|
# No valid header names: prefer address match only if email/domain-like
|
||||||
my $hv2_simple = simplify_email_value($hh);
|
my $hv2_simple = simplify_email_value($hh);
|
||||||
my $hv2_domain = extract_domain($hv2_simple);
|
my $hv2_domain = extract_domain($hv2_simple);
|
||||||
if ($hv2_domain ne '') {
|
if (is_full_email($hv2_simple)) {
|
||||||
|
my $aq2 = sieve_quote($hv2_simple);
|
||||||
|
$cond2 = "address :is [\"from\",\"to\",\"cc\"] \"$aq2\"";
|
||||||
|
} elsif ($hv2_domain ne '') {
|
||||||
my $dq2 = sieve_quote($hv2_domain);
|
my $dq2 = sieve_quote($hv2_domain);
|
||||||
$cond2 = "address :domain :is [\"from\",\"to\",\"cc\"] \"$dq2\"";
|
$cond2 = "address :domain :is [\"from\",\"to\",\"cc\"] \"$dq2\"";
|
||||||
} else {
|
} else {
|
||||||
@@ -288,7 +326,10 @@
|
|||||||
# No "Header: value" structure: prefer address match only if email/domain-like
|
# No "Header: value" structure: prefer address match only if email/domain-like
|
||||||
my $hv2_simple = simplify_email_value($hh);
|
my $hv2_simple = simplify_email_value($hh);
|
||||||
my $hv2_domain = extract_domain($hv2_simple);
|
my $hv2_domain = extract_domain($hv2_simple);
|
||||||
if ($hv2_domain ne '') {
|
if (is_full_email($hv2_simple)) {
|
||||||
|
my $aq2 = sieve_quote($hv2_simple);
|
||||||
|
$cond2 = "address :is [\"from\",\"to\",\"cc\"] \"$aq2\"";
|
||||||
|
} elsif ($hv2_domain ne '') {
|
||||||
my $dq2 = sieve_quote($hv2_domain);
|
my $dq2 = sieve_quote($hv2_domain);
|
||||||
$cond2 = "address :domain :is [\"from\",\"to\",\"cc\"] \"$dq2\"";
|
$cond2 = "address :domain :is [\"from\",\"to\",\"cc\"] \"$dq2\"";
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
%= hidden_field 'trt' => $ms_data->{trt}
|
%= hidden_field 'trt' => $ms_data->{trt}
|
||||||
%# die("here");
|
%# die("here");
|
||||||
%= hidden_field 'Selected' => $c->param('Selected')
|
%= hidden_field 'Selected' => $c->param('Selected')
|
||||||
%= hidden_field ''account'' => $c->param('account')
|
%= hidden_field 'account' => $c->param('account')
|
||||||
|
|
||||||
%# Inputs etc in here.
|
%# Inputs etc in here.
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ Summary: Lets users configure procmail or maildrop rules.
|
|||||||
%define name smeserver-mailsorting
|
%define name smeserver-mailsorting
|
||||||
Name: %{name}
|
Name: %{name}
|
||||||
%define version 11.0.0
|
%define version 11.0.0
|
||||||
%define release 6
|
%define release 7
|
||||||
Version: %{version}
|
Version: %{version}
|
||||||
Release: %{release}%{?dist}
|
Release: %{release}%{?dist}
|
||||||
License: GPL
|
License: GPL
|
||||||
@@ -32,6 +32,10 @@ SME Server enhancement to enable procmail or maildrop filtering for users.
|
|||||||
Optionally provides user panels where users can create mail rules for themselves
|
Optionally provides user panels where users can create mail rules for themselves
|
||||||
|
|
||||||
%changelog
|
%changelog
|
||||||
|
* Tue Oct 21 2025 Brian Read <brianr@koozali.org> 11.0.0-7.sme
|
||||||
|
- Typo in REMOVE partial template [SME: 13251]
|
||||||
|
- Update to sievefilter generation for Headers - use whole email address if provided [SME: 13251]
|
||||||
|
|
||||||
* Tue Oct 21 2025 Brian Read <brianr@koozali.org> 11.0.0-6.sme
|
* Tue Oct 21 2025 Brian Read <brianr@koozali.org> 11.0.0-6.sme
|
||||||
- Make coding for global rules for mailfilters and sieve the same as for user rules [SME: 13245]
|
- Make coding for global rules for mailfilters and sieve the same as for user rules [SME: 13245]
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user