mirror of
https://github.com/webmin/webmin.git
synced 2025-07-20 16:48:46 +00:00
674 lines
18 KiB
Perl
Executable File
674 lines
18 KiB
Perl
Executable File
# Functions for creating simple mail filtering rules
|
|
# XXX use same virtualmin spam detection trick for spam module
|
|
|
|
BEGIN { push(@INC, ".."); };
|
|
use WebminCore;
|
|
&init_config();
|
|
do 'aliases-lib.pl';
|
|
do 'autoreply-file-lib.pl';
|
|
|
|
if (&get_product_name() eq 'usermin') {
|
|
# If configured, check if this user has virtualmin spam filtering
|
|
# enabled before switching away from root
|
|
$autoreply_cmd = "$config_directory/forward/autoreply.pl";
|
|
if ($< == 0) {
|
|
if ($config{'virtualmin_spam'} &&
|
|
-x $config{'virtualmin_spam'}) {
|
|
local $out = &backquote_command(
|
|
"$config{'virtualmin_spam'} $remote_user ".
|
|
"</dev/null 2>/dev/null");
|
|
$out =~ s/\r|\n//g;
|
|
if ($out =~ /\d/) {
|
|
# Yes - we can show the user this
|
|
$global_spamassassin = 2;
|
|
$virtualmin_domain_id = $out;
|
|
}
|
|
}
|
|
|
|
# Copy autoreply.pl to /etc/usermin/forward, while we
|
|
# are still root
|
|
local $autoreply_src = "$root_directory/forward/autoreply.pl";
|
|
local @rst = stat($autoreply_src);
|
|
local @cst = stat($autoreply_cmd);
|
|
if (!@cst || $cst[7] != $rst[7]) {
|
|
©_source_dest($autoreply_src, $autoreply_cmd);
|
|
&set_ownership_permissions(
|
|
undef, undef, 0755, $autoreply_cmd);
|
|
}
|
|
|
|
&switch_to_remote_user();
|
|
}
|
|
|
|
&create_user_config_dirs();
|
|
&foreign_require("mailbox");
|
|
}
|
|
else {
|
|
# Running under Webmin, so different modules are used
|
|
&foreign_require("mailboxes");
|
|
&foreign_require("usermin");
|
|
$mail_system_module =
|
|
$mailboxes::config{'mail_system'} == 1 ? "postfix" :
|
|
$mailboxes::config{'mail_system'} == 2 ? "qmailadmin" :
|
|
"sendmail";
|
|
$autoreply_cmd = "$config_directory/$mail_system_module/autoreply.pl";
|
|
$user_autoreply_cmd = "$usermin::config{'usermin_dir'}/forward/autoreply.pl";
|
|
}
|
|
&foreign_require("spam");
|
|
&foreign_require("procmail");
|
|
|
|
# list_filters([file])
|
|
# Returns a list of filter objects, which have a 1-to-1 correlation with
|
|
# procmail recipes. Any recipes too complex for parsing are not included.
|
|
sub list_filters
|
|
{
|
|
local ($file) = @_;
|
|
local @rv;
|
|
local @pmrc = &procmail::parse_procmail_file($file || $procmail::procmailrc);
|
|
foreach my $r (@pmrc) {
|
|
# Check for un-supported recipes
|
|
local @conds = @{$r->{'conds'}};
|
|
if ($r->{'block'} || $r->{'name'}) {
|
|
next;
|
|
}
|
|
|
|
# Check for flags
|
|
local %flags = map { $_, 1 } @{$r->{'flags'}};
|
|
|
|
# Check for bounce condition
|
|
local $nobounce;
|
|
if (@conds && $conds[0]->[0] eq '!' &&
|
|
$conds[0]->[1] =~ /FROM_MAILER/) {
|
|
$nobounce = 1;
|
|
shift(@conds);
|
|
}
|
|
next if (@conds > 1); # Multiple conditions are not supported
|
|
|
|
# Work out condition type
|
|
local ($condtype, $cond);
|
|
if (@conds) {
|
|
($condtype, $cond) = @{$conds[0]};
|
|
if ($condtype && $condtype ne "<" && $condtype ne ">") {
|
|
# Unsupported conditon type
|
|
next;
|
|
}
|
|
}
|
|
|
|
# Work out action type
|
|
local ($actionspam, $actionreply);
|
|
if ($r->{'type'} eq '|' &&
|
|
$r->{'action'} =~ /spamassassin|spamc/) {
|
|
$actionspam = 1;
|
|
}
|
|
elsif ($r->{'type'} eq '|' &&
|
|
($r->{'action'} =~ /^\Q$autoreply_cmd\E\s+(\S+)/ ||
|
|
$user_autoreply_cmd &&
|
|
$user_autoreply_cmd &&
|
|
$r->{'action'} =~ /^\Q$user_autoreply_cmd\E\s+(\S+)/)) {
|
|
$actionreply = $1;
|
|
}
|
|
elsif ($r->{'type'} && $r->{'type'} ne '!') {
|
|
# Unsupported action type
|
|
next;
|
|
}
|
|
|
|
# Finally create the simple object
|
|
local $simple = { 'condtype' => $condtype,
|
|
'cond' => $cond,
|
|
'nocond' => !scalar(@conds),
|
|
'body' => $flags{'B'},
|
|
'continue' => $flags{'c'},
|
|
'actiontype' => $r->{'type'},
|
|
'action' => $r->{'action'},
|
|
'nobounce' => $nobounce,
|
|
'index' => scalar(@rv),
|
|
'recipe' => $r };
|
|
|
|
# Set spam flag
|
|
if ($actionspam) {
|
|
$simple->{'actionspam'} = 1;
|
|
delete($simple->{'actiontype'});
|
|
delete($simple->{'action'});
|
|
}
|
|
|
|
# Check for throw away
|
|
if ($simple->{'actiontype'} eq '' &&
|
|
$simple->{'action'} eq '/dev/null') {
|
|
$simple->{'actionthrow'} = 1;
|
|
delete($simple->{'actiontype'});
|
|
delete($simple->{'action'});
|
|
}
|
|
|
|
# Check for default delivery
|
|
if ($simple->{'actiontype'} eq '' &&
|
|
$simple->{'action'} eq '$DEFAULT') {
|
|
$simple->{'actiondefault'} = 1;
|
|
delete($simple->{'actiontype'});
|
|
delete($simple->{'action'});
|
|
}
|
|
|
|
# Read autoreply file
|
|
if ($actionreply) {
|
|
$simple->{'actionreply'} = $actionreply;
|
|
$simple->{'reply'} = { };
|
|
&read_autoreply($actionreply, $simple->{'reply'});
|
|
delete($simple->{'actiontype'});
|
|
delete($simple->{'action'});
|
|
}
|
|
|
|
# Split condition regexp into header and value, if possible
|
|
if ($simple->{'condtype'} ne '<' && $simple->{'condtype'} ne '>' &&
|
|
!$simple->{'body'} &&
|
|
$simple->{'cond'} =~ /^\^?([a-zA-Z0-9\-]+):\s*(.*)/) {
|
|
local ($h, $v) = ($1, $2);
|
|
if ($h eq "X-Spam-Status" && $v eq "Yes") {
|
|
# Special case for spam detection
|
|
$simple->{'condspam'} = 1;
|
|
}
|
|
elsif ($h eq "X-Spam-Level" && $v =~ /^(\\\*)+$/) {
|
|
# Spam above some level
|
|
$simple->{'condlevel'} = length($v)/2;
|
|
}
|
|
else {
|
|
# Match on some header
|
|
$simple->{'condheader'} = $h;
|
|
$simple->{'condvalue'} = $v;
|
|
}
|
|
delete($simple->{'cond'});
|
|
}
|
|
|
|
push(@rv, $simple);
|
|
}
|
|
return @rv;
|
|
}
|
|
|
|
# create_filter(&filter)
|
|
# Create a new filter by adding a procmail recipe
|
|
sub create_filter
|
|
{
|
|
local ($filter) = @_;
|
|
local $recipe = { };
|
|
&update_filter_recipe($filter, $recipe);
|
|
&procmail::create_recipe($recipe);
|
|
&setup_forward_procmail();
|
|
}
|
|
|
|
# modify_filter(&filter)
|
|
# Change a filter by modifying the underlying procmail recipe
|
|
sub modify_filter
|
|
{
|
|
local ($filter) = @_;
|
|
&update_filter_recipe($filter, $filter->{'recipe'});
|
|
&procmail::modify_recipe($filter->{'recipe'});
|
|
&setup_forward_procmail();
|
|
}
|
|
|
|
# insert_filter(&filter)
|
|
# Like create_filter, but adds to the top of the .procmailrc
|
|
sub insert_filter
|
|
{
|
|
local ($filter) = @_;
|
|
local $recipe = { };
|
|
&update_filter_recipe($filter, $recipe);
|
|
local @pmrc = &procmail::parse_procmail_file(
|
|
$filter->{'file'} || $procmail::procmailrc);
|
|
if (@pmrc) {
|
|
&procmail::create_recipe_before($recipe, $pmrc[0]);
|
|
}
|
|
else {
|
|
&procmail::create_recipe($recipe);
|
|
}
|
|
&setup_forward_procmail();
|
|
}
|
|
|
|
# update_filter_recipe(&filter, &recipe)
|
|
# Update a procmail recipe based on some filter
|
|
sub update_filter_recipe
|
|
{
|
|
local ($filter, $recipe) = @_;
|
|
|
|
# Set condition section
|
|
local @conds;
|
|
local @flags;
|
|
if ($filter->{'condspam'}) {
|
|
@conds = ( [ "", "X-Spam-Status: Yes" ] );
|
|
}
|
|
elsif ($filter->{'condlevel'}) {
|
|
local $stars = join("", map { "\\*" } (1..$filter->{'condlevel'}));
|
|
@conds = ( [ "", "^"."X-Spam-Level: $stars" ] );
|
|
}
|
|
elsif ($filter->{'condheader'}) {
|
|
@conds = ( [ "", "^".$filter->{'condheader'}.": ".
|
|
$filter->{'condvalue'} ] );
|
|
}
|
|
elsif ($filter->{'condtype'} eq '<' || $filter->{'condtype'} eq '>') {
|
|
@conds = ( [ $filter->{'condtype'}, $filter->{'cond'} ] );
|
|
}
|
|
elsif ($filter->{'cond'}) {
|
|
@conds = ( [ "", $filter->{'cond'} ] );
|
|
}
|
|
|
|
# Set action section
|
|
if ($filter->{'actionspam'}) {
|
|
$recipe->{'type'} = '|';
|
|
$recipe->{'action'} = &spam::get_procmail_command();
|
|
push(@flags, "f", "w");
|
|
}
|
|
elsif ($filter->{'actionthrow'}) {
|
|
$recipe->{'type'} = '';
|
|
$recipe->{'action'} = '/dev/null';
|
|
}
|
|
elsif ($filter->{'actiondefault'}) {
|
|
$recipe->{'type'} = '';
|
|
$recipe->{'action'} = '$DEFAULT';
|
|
}
|
|
elsif ($filter->{'actionreply'}) {
|
|
$recipe->{'type'} = '|';
|
|
$recipe->{'action'} =
|
|
"$autoreply_cmd $filter->{'reply'}->{'autoreply'} $remote_user";
|
|
&write_autoreply($filter->{'reply'}->{'autoreply'},
|
|
$filter->{'reply'});
|
|
}
|
|
else {
|
|
$recipe->{'type'} = $filter->{'actiontype'};
|
|
$recipe->{'action'} = $filter->{'action'};
|
|
local $folder = &file_to_folder($filter->{'action'}, [ ], undef, 1);
|
|
if ($recipe->{'type'} eq '' && $folder->{'type'} == 1) {
|
|
# Enable locking for file delivery
|
|
$recipe->{'lockfile'} ||= "";
|
|
}
|
|
if ($filter->{'actiontype'} eq '!' && $filter->{'nobounce'}) {
|
|
# Add condition to suppress forwarding of bounces
|
|
unshift(@conds, [ '!', '^FROM_MAILER' ]);
|
|
}
|
|
}
|
|
$recipe->{'conds'} = \@conds;
|
|
|
|
# Set flags
|
|
push(@flags, "B") if ($filter->{'body'});
|
|
push(@flags, "c") if ($filter->{'continue'});
|
|
$recipe->{'flags'} = [ &unique(@flags) ];
|
|
}
|
|
|
|
# delete_filter(&filter)
|
|
# Delete a filter by removing the underlying procmail rule
|
|
sub delete_filter
|
|
{
|
|
local ($filter) = @_;
|
|
&procmail::delete_recipe($filter->{'recipe'});
|
|
&setup_forward_procmail();
|
|
if ($filter->{'actionreply'} && !-d $filter->{'actionreply'}) {
|
|
&unlink_file($filter->{'actionreply'});
|
|
}
|
|
}
|
|
|
|
# swap_filters(&filter1, &filter2)
|
|
# Swap two filters in the config file
|
|
sub swap_filters
|
|
{
|
|
local ($filter1, $filter2) = @_;
|
|
&procmail::swap_recipes($filter1->{'recipe'}, $filter2->{'recipe'});
|
|
&setup_forward_procmail();
|
|
}
|
|
|
|
# file_to_folder(file, &folders, [homedir], [fake-if-missing])
|
|
# Given a path like mail/foo or ~/mail/foo or $HOME/mail/foo or
|
|
# /home/bob/mail/foo, returns the folder object for it.
|
|
sub file_to_folder
|
|
{
|
|
local ($file, $folders, $home, $fake) = @_;
|
|
$home ||= $remote_user_info[7];
|
|
$file =~ s/^\~/$home/;
|
|
$file =~ s/^\$HOME/$home/;
|
|
if ($file !~ /^\//) {
|
|
$file = "$home/$file";
|
|
}
|
|
local ($folder) = grep { $_->{'file'} eq $file ||
|
|
$_->{'file'}.'/' eq $file } @$folders;
|
|
if (!$folder && $fake) {
|
|
# Create a fake folder object to match
|
|
$folder = { 'file' => $file,
|
|
'type' => 1,
|
|
'fake' => 1 };
|
|
if ($folder->{'file'} =~ s/\/$//) {
|
|
$folder->{'type'} = 2;
|
|
}
|
|
$folder->{'file'} =~ /\/\.?([^\/]+)$/;
|
|
$folder->{'name'} = $1;
|
|
if (lc($folder->{'name'}) eq 'spam') {
|
|
$folder->{'spam'} = 1;
|
|
$folder->{'name'} = "Spam";
|
|
}
|
|
}
|
|
return $folder;
|
|
}
|
|
|
|
# get_global_spamassassin()
|
|
# Returns true if spamasassin is run globally
|
|
sub get_global_spamassassin
|
|
{
|
|
return $global_spamassassin if ($global_spamassassin);
|
|
local @recipes = &procmail::parse_procmail_file(
|
|
$spam::config{'global_procmailrc'});
|
|
return &spam::find_spam_recipe(\@recipes) ? 1 : 0;
|
|
}
|
|
|
|
# get_global_spam_path()
|
|
# Returns the global path to which spam is delivered, typically by a
|
|
# Virtualmin per-domain procmail file
|
|
sub get_global_spam_path
|
|
{
|
|
if ($virtualmin_domain_id) {
|
|
# Read the Virtualmin procmailrc for the domain
|
|
local $vmpmrc = "$config{'virtualmin_config'}/procmail/".
|
|
$virtualmin_domain_id;
|
|
local @vmrecipes = &procmail::parse_procmail_file($vmpmrc);
|
|
local $spamrec = &spam::find_file_recipe(\@vmrecipes);
|
|
if ($spamrec) {
|
|
return $spamrec->{'action'};
|
|
}
|
|
}
|
|
# Also check the global /etc/procmailrc
|
|
local @recipes = &procmail::parse_procmail_file(
|
|
$spam::config{'global_procmailrc'});
|
|
local $spamrec = &spam::find_file_recipe(\@recipes);
|
|
if ($spamrec) {
|
|
return $spamrec->{'action'};
|
|
}
|
|
else {
|
|
return undef;
|
|
}
|
|
}
|
|
|
|
# get_global_spam_delete()
|
|
# Returns the global score above which spam is deleted, typically by a
|
|
# Virtualmin per-domain procmail file
|
|
sub get_global_spam_delete
|
|
{
|
|
if ($virtualmin_domain_id) {
|
|
# Read the Virtualmin procmailrc for the domain
|
|
local $vmpmrc = "$config{'virtualmin_config'}/procmail/".
|
|
$virtualmin_domain_id;
|
|
local @vmrecipes = &procmail::parse_procmail_file($vmpmrc);
|
|
local ($spamrec, $level) = &spam::find_delete_recipe(\@vmrecipes);
|
|
if ($spamrec) {
|
|
return $level;
|
|
}
|
|
}
|
|
# Also check the global /etc/procmailrc
|
|
local @recipes = &procmail::parse_procmail_file(
|
|
$spam::config{'global_procmailrc'});
|
|
local ($spamrec, $level) = &spam::find_delete_recipe(\@recipes);
|
|
if ($spamrec) {
|
|
return $level;
|
|
}
|
|
else {
|
|
return undef;
|
|
}
|
|
}
|
|
|
|
sub has_spamassassin
|
|
{
|
|
return &foreign_installed("spam");
|
|
}
|
|
|
|
# get_override_alias()
|
|
# Check for any mail alias matching this user, which is defined in /etc/aliases
|
|
# as an entry matching his username.
|
|
sub get_override_alias
|
|
{
|
|
local @afiles = split(/\t+/, $config{'alias_files'});
|
|
foreach my $alias (&list_aliases(\@afiles)) {
|
|
if ($alias->{'name'} eq $remote_user && $alias->{'enabled'}) {
|
|
return $alias;
|
|
}
|
|
}
|
|
return undef;
|
|
}
|
|
|
|
# describe_alias_dest(&values)
|
|
# Returns a text description of some alias destination
|
|
sub describe_alias_dest
|
|
{
|
|
local ($values) = @_;
|
|
local @rv;
|
|
foreach my $v (@$values) {
|
|
local ($atype, $adesc) = &alias_type($v);
|
|
if ($atype == 1 && $adesc eq "\\$remote_user") {
|
|
push(@rv, $text{'aliases_your'});
|
|
}
|
|
elsif ($atype == 1 && $adesc =~ /^\\(\S+)$/) {
|
|
push(@rv, &text('aliases_other', "<tt>$1</tt>"));
|
|
}
|
|
elsif ($atype == 3 && $adesc eq "/dev/null") {
|
|
push(@rv, $text{'aliases_delete'});
|
|
}
|
|
elsif ($atype == 4 && $adesc =~ /^(.*)\/autoreply.pl\s+(\S+)/) {
|
|
# Autoreply from file .. check contents
|
|
local $auto = &read_file_contents("$2");
|
|
if ($auto) {
|
|
local @lines = grep { !/^(\S+):/} split(/\r?\n/, $auto);
|
|
local $msg = join(" ", @lines);
|
|
$msg = substr($msg, 0, 100)." ..."
|
|
if (length($msg) > 100);
|
|
push(@rv, &text('aliases_auto', "<i>$msg</i>"));
|
|
}
|
|
else {
|
|
push(@rv, &text('aliases_type5', "<tt>$2</tt>"));
|
|
}
|
|
}
|
|
elsif ($atype == 4 && $adesc =~ /^(.*)\/filter.pl\s+(\S+)/) {
|
|
# Apply filter file
|
|
push(@rv, &text('aliases_type6', "<tt>$2</tt>"));
|
|
}
|
|
else {
|
|
push(@rv, &text('aliases_type'.$atype, $adesc));
|
|
}
|
|
}
|
|
return @rv;
|
|
}
|
|
|
|
# is_table_comment(line, [force-prefix])
|
|
# Returns the comment text if a line contains a comment, like # foo. This is
|
|
# defined only because functions in aliases-lib.pl call it.
|
|
sub is_table_comment
|
|
{
|
|
local ($line, $force) = @_;
|
|
if ($force) {
|
|
return $line =~ /^\s*#+\s*Webmin:\s*(.*)/ ? $1 : undef;
|
|
}
|
|
else {
|
|
return $line =~ /^\s*#+\s*(.*)/ ? $1 : undef;
|
|
}
|
|
}
|
|
|
|
# describe_condition(&filter)
|
|
# Returns a human-readable description of the filter condition, and a flag
|
|
# indicating if this is an 'always' condition.
|
|
sub describe_condition
|
|
{
|
|
local ($f) = @_;
|
|
local $cond;
|
|
local $lastalways = 0;
|
|
if ($f->{'condspam'}) {
|
|
$cond = $text{'index_cspam'};
|
|
}
|
|
elsif ($f->{'condlevel'}) {
|
|
$cond = &text('index_clevel', $f->{'condlevel'});
|
|
}
|
|
elsif ($f->{'condheader'}) {
|
|
if ($f->{'condvalue'} =~ /^\.\*(.*)\$$/) {
|
|
$cond = &text('index_cheader2',
|
|
"<tt>".&html_escape($f->{'condheader'})."</tt>",
|
|
"<tt>".&html_escape(&prettify_regexp("$1"))."</tt>");
|
|
}
|
|
elsif ($f->{'condvalue'} =~ /^\.\*(.*)\.\*$/ ||
|
|
$f->{'condvalue'} =~ /^\.\*(.*)$/) {
|
|
$cond = &text('index_cheader1',
|
|
"<tt>".&html_escape($f->{'condheader'})."</tt>",
|
|
"<tt>".&html_escape(&prettify_regexp("$1"))."</tt>");
|
|
}
|
|
elsif ($f->{'condvalue'} =~ /^(.*)\.\*$/ ||
|
|
$f->{'condvalue'} =~ /^(.*)$/) {
|
|
$cond = &text('index_cheader0',
|
|
"<tt>".&html_escape($f->{'condheader'})."</tt>",
|
|
"<tt>".&html_escape(&prettify_regexp("$1"))."</tt>");
|
|
}
|
|
}
|
|
elsif ($f->{'condtype'} eq '<' || $f->{'condtype'} eq '>') {
|
|
$cond = &text('index_csize'.$f->{'condtype'},
|
|
&nice_size($f->{'cond'}));
|
|
}
|
|
elsif ($f->{'cond'}) {
|
|
$cond = &text($f->{'body'} ? 'index_cre2' : 'index_cre',
|
|
"<tt>".&html_escape($f->{'cond'})."</tt>");
|
|
}
|
|
else {
|
|
$cond = $text{'index_calways'};
|
|
if (!$f->{'continue'} && !$f->{'actionspam'}) {
|
|
$lastalways = 1;
|
|
}
|
|
}
|
|
return wantarray ? ( $cond, $lastalways ) : $cond;
|
|
}
|
|
|
|
# prettify_regexp(string)
|
|
# If a string contains only \ quoted special characters, remove the \s
|
|
# Also, undo any mimewords encoding.
|
|
sub prettify_regexp
|
|
{
|
|
my ($str) = @_;
|
|
my $re = $str;
|
|
$re =~ s/\\./x/g;
|
|
if ($re =~ /^[a-zA-Z0-9_ ]*$/) {
|
|
$str =~ s/\\(.)/$1/g;
|
|
}
|
|
if (&get_product_name() eq "webmin") {
|
|
&foreign_require("mailboxes");
|
|
return &mailboxes::decode_mimewords($str);
|
|
}
|
|
elsif (&get_product_name() eq "usermin") {
|
|
&foreign_require("mailbox");
|
|
return &mailbox::decode_mimewords($str);
|
|
}
|
|
else {
|
|
return $str;
|
|
}
|
|
}
|
|
|
|
# describe_action(&filter, &folder, [homedir])
|
|
# Returns a human-readable description for the delivery action for some folder
|
|
sub describe_action
|
|
{
|
|
local ($f, $folders, $home) = @_;
|
|
local $action;
|
|
if ($f->{'actionspam'}) {
|
|
$action = $text{'index_aspam'};
|
|
}
|
|
elsif ($f->{'actionthrow'}) {
|
|
$action = $text{'index_athrow'};
|
|
}
|
|
elsif ($f->{'actiondefault'}) {
|
|
$action = $text{'index_adefault'};
|
|
}
|
|
elsif ($f->{'actiontype'} eq '!') {
|
|
$action = &text('index_aforward',
|
|
"<tt>".&html_escape($f->{'action'})."</tt>");
|
|
}
|
|
elsif ($f->{'actionreply'}) {
|
|
$action = &text('index_areply',
|
|
"<i>".&html_escape(substr(
|
|
$f->{'reply'}->{'autotext'}, 0, 50))."</i>");
|
|
}
|
|
else {
|
|
# Work out what folder
|
|
local $folder = &file_to_folder($f->{'action'}, $folders, $home);
|
|
if ($folder) {
|
|
if (&get_product_name() eq 'usermin') {
|
|
local $id = &mailbox::folder_name($folder);
|
|
$action = &text('index_afolder',
|
|
"<a href='../mailbox/index.cgi?id=$id'>".
|
|
"$folder->{'name'}</a>");
|
|
}
|
|
else {
|
|
local $id = &mailboxes::folder_name($folder);
|
|
if (&foreign_available("mailboxes")) {
|
|
$action = &text('index_afolder',
|
|
"<a href='../mailboxes/list_mail.cgi?user=".
|
|
&urlize($folder->{'user'})."&folder=".
|
|
$folder->{'index'}."'>$folder->{'name'}</a>");
|
|
}
|
|
else {
|
|
$action = &text('index_afolder',
|
|
$folder->{'name'});
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
$action = &text('index_afile',
|
|
"<tt>$f->{'action'}</tt>");
|
|
}
|
|
}
|
|
if ($f->{'continue'}) {
|
|
$action = &text('index_acontinue', $action);
|
|
}
|
|
return $action;
|
|
}
|
|
|
|
# can_simple_autoreply()
|
|
# Returns 1 if the current filter rules are simple enough to allow an autoreply
|
|
# to be added or removed.
|
|
sub can_simple_autoreply
|
|
{
|
|
return 1; # Always true for now
|
|
}
|
|
|
|
# can_simple_forward()
|
|
# Returns 1 if the current filter rules are simple enough to allow a mail
|
|
# forwarder to be added or removed
|
|
sub can_simple_forward
|
|
{
|
|
return 1; # Always can for now
|
|
}
|
|
|
|
# no_user_procmailrc()
|
|
# Returns 1 if /etc/procmailrc has a recipe to always deliver to the user's
|
|
# mailbox, which prevents this module from configuring anything useful
|
|
sub no_user_procmailrc
|
|
{
|
|
local %sconfig = &foreign_config("spam");
|
|
local @recipes = &procmail::parse_procmail_file(
|
|
$sconfig{'global_procmailrc'});
|
|
local ($force) = grep { $_->{'action'} eq '$DEFAULT' &&
|
|
!@{$_->{'conds'}} } @recipes;
|
|
return $force;
|
|
}
|
|
|
|
# setup_forward_procmail()
|
|
# If configured, create a .forward file that runs procmail (if not setup yet)
|
|
sub setup_forward_procmail
|
|
{
|
|
return 0 if (!$config{'forward_procmail'});
|
|
return 0 if (!$module_info{'usermin'});
|
|
local $fwdfile = "$remote_user_info[7]/.forward";
|
|
local $procmail = &has_command("procmail");
|
|
return 0 if (!$procmail);
|
|
local $lref = &read_file_lines($fwdfile);
|
|
local $found;
|
|
foreach my $l (@$lref) {
|
|
if ($l =~ /\Q$procmail\E/) {
|
|
$found++;
|
|
}
|
|
}
|
|
if ($found) {
|
|
&unflush_file_lines($fwdfile);
|
|
}
|
|
else {
|
|
# Add procmail call
|
|
push(@$lref, "|$procmail");
|
|
&flush_file_lines($fwdfile);
|
|
}
|
|
}
|
|
|
|
1;
|
|
|