diff --git a/root/etc/e-smith/templates-user/.sievefilter/40global b/root/etc/e-smith/templates-user/.sievefilter/40global index 44584ae..0741b77 100644 --- a/root/etc/e-smith/templates-user/.sievefilter/40global +++ b/root/etc/e-smith/templates-user/.sievefilter/40global @@ -56,6 +56,14 @@ 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. # Returns '' if no clear domain found. sub extract_domain { @@ -108,7 +116,7 @@ if ($pmGlobRules > 0) { $OUT .= "\n"; - $OUT .= "# --- start of Global Sieve rules (".$pmGlobRules.")---------\n"; + $OUT .= "# --- start of Global Sieve rules ($pmGlobRules)---------\n"; my $pmGlobRule; foreach $pmGlobRule (sort {$a <=> $b} @pmGlobRules) @@ -165,24 +173,38 @@ my $hv_simple = simplify_email_value($hv); my $hv_domain = extract_domain($hv_simple); - if ($all_addr && $hv_domain ne '') { + if ($all_addr) { my @lb = map { lc $_ } @hn; my $hn_list = join '","', @lb; - my $dom_q = sieve_quote($hv_domain); - $cond1 = "address :domain :is [\"$hn_list\"] \"$dom_q\""; + if (is_full_email($hv_simple)) { + 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 { - # 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_list = join '","', @hn_q; + my $hn_list_q = join '","', @hn_q; my $hv_re = sieve_regex_passthrough($hv); - $cond1 = "header :regex [\"$hn_list\"] \"$hv_re\""; + $cond1 = "header :regex [\"$hn_list_q\"] \"$hv_re\""; } } else { # No valid header names extracted: prefer address match only if email/domain-like my $hv_simple = simplify_email_value($h); 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); $cond1 = "address :domain :is [\"from\",\"to\",\"cc\"] \"$dq\""; } else { @@ -196,7 +218,10 @@ # No "Header: value" structure: prefer address match only if email/domain-like my $hv_simple = simplify_email_value($h); 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); $cond1 = "address :domain :is [\"from\",\"to\",\"cc\"] \"$dq\""; } else { @@ -258,23 +283,36 @@ my $hv2_simple = simplify_email_value($hv2); my $hv2_domain = extract_domain($hv2_simple); - if ($all_addr2 && $hv2_domain ne '') { + if ($all_addr2) { my @lb2 = map { lc $_ } @hn2; my $hn2_list = join '","', @lb2; - my $dq2 = sieve_quote($hv2_domain); - $cond2 = "address :domain :is [\"$hn2_list\"] \"$dq2\""; + if (is_full_email($hv2_simple)) { + 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 { 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); - $cond2 = "header :regex [\"$hn2_list\"] \"$hv2_re\""; + $cond2 = "header :regex [\"$hn2_list_q\"] \"$hv2_re\""; } } else { # No valid header names: prefer address match only if email/domain-like my $hv2_simple = simplify_email_value($hh); 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); $cond2 = "address :domain :is [\"from\",\"to\",\"cc\"] \"$dq2\""; } else { @@ -288,7 +326,10 @@ # No "Header: value" structure: prefer address match only if email/domain-like my $hv2_simple = simplify_email_value($hh); 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); $cond2 = "address :domain :is [\"from\",\"to\",\"cc\"] \"$dq2\""; } else { @@ -429,9 +470,8 @@ } $OUT .= " stop;\n"; } - $OUT .= "\}\n"; $OUT .= "# End of Global rule $pmGlobRule\n"; }#foreach rule }#if rules exist -} \ No newline at end of file +} diff --git a/root/etc/e-smith/templates-user/.sievefilter/60user b/root/etc/e-smith/templates-user/.sievefilter/60user index b655eae..0314101 100644 --- a/root/etc/e-smith/templates-user/.sievefilter/60user +++ b/root/etc/e-smith/templates-user/.sievefilter/60user @@ -56,6 +56,14 @@ 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. # Returns '' if no clear domain found. sub extract_domain { @@ -165,24 +173,38 @@ my $hv_simple = simplify_email_value($hv); my $hv_domain = extract_domain($hv_simple); - if ($all_addr && $hv_domain ne '') { + if ($all_addr) { my @lb = map { lc $_ } @hn; my $hn_list = join '","', @lb; - my $dom_q = sieve_quote($hv_domain); - $cond1 = "address :domain :is [\"$hn_list\"] \"$dom_q\""; + if (is_full_email($hv_simple)) { + 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 { - # 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_list = join '","', @hn_q; + my $hn_list_q = join '","', @hn_q; my $hv_re = sieve_regex_passthrough($hv); - $cond1 = "header :regex [\"$hn_list\"] \"$hv_re\""; + $cond1 = "header :regex [\"$hn_list_q\"] \"$hv_re\""; } } else { # No valid header names extracted: prefer address match only if email/domain-like my $hv_simple = simplify_email_value($h); 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); $cond1 = "address :domain :is [\"from\",\"to\",\"cc\"] \"$dq\""; } else { @@ -196,7 +218,10 @@ # No "Header: value" structure: prefer address match only if email/domain-like my $hv_simple = simplify_email_value($h); 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); $cond1 = "address :domain :is [\"from\",\"to\",\"cc\"] \"$dq\""; } else { @@ -258,23 +283,36 @@ my $hv2_simple = simplify_email_value($hv2); my $hv2_domain = extract_domain($hv2_simple); - if ($all_addr2 && $hv2_domain ne '') { + if ($all_addr2) { my @lb2 = map { lc $_ } @hn2; my $hn2_list = join '","', @lb2; - my $dq2 = sieve_quote($hv2_domain); - $cond2 = "address :domain :is [\"$hn2_list\"] \"$dq2\""; + if (is_full_email($hv2_simple)) { + 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 { 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); - $cond2 = "header :regex [\"$hn2_list\"] \"$hv2_re\""; + $cond2 = "header :regex [\"$hn2_list_q\"] \"$hv2_re\""; } } else { # No valid header names: prefer address match only if email/domain-like my $hv2_simple = simplify_email_value($hh); 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); $cond2 = "address :domain :is [\"from\",\"to\",\"cc\"] \"$dq2\""; } else { @@ -288,7 +326,10 @@ # No "Header: value" structure: prefer address match only if email/domain-like my $hv2_simple = simplify_email_value($hh); 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); $cond2 = "address :domain :is [\"from\",\"to\",\"cc\"] \"$dq2\""; } else { diff --git a/root/usr/share/smanager/themes/default/templates/partials/_ms_REMOVE.html.ep b/root/usr/share/smanager/themes/default/templates/partials/_ms_REMOVE.html.ep index c81db95..319b4bd 100644 --- a/root/usr/share/smanager/themes/default/templates/partials/_ms_REMOVE.html.ep +++ b/root/usr/share/smanager/themes/default/templates/partials/_ms_REMOVE.html.ep @@ -14,7 +14,7 @@ %= hidden_field 'trt' => $ms_data->{trt} %# die("here"); %= hidden_field 'Selected' => $c->param('Selected') - %= hidden_field ''account'' => $c->param('account') + %= hidden_field 'account' => $c->param('account') %# Inputs etc in here. diff --git a/smeserver-mailsorting.spec b/smeserver-mailsorting.spec index 6ca4586..0856cdf 100644 --- a/smeserver-mailsorting.spec +++ b/smeserver-mailsorting.spec @@ -6,7 +6,7 @@ Summary: Lets users configure procmail or maildrop rules. %define name smeserver-mailsorting Name: %{name} %define version 11.0.0 -%define release 6 +%define release 7 Version: %{version} Release: %{release}%{?dist} 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 %changelog +* Tue Oct 21 2025 Brian Read 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 11.0.0-6.sme - Make coding for global rules for mailfilters and sieve the same as for user rules [SME: 13245]