!C99Shell v. 2.5 [PHP 8 Update] [24.05.2025]!

Software: Apache. PHP/8.1.30 

uname -a: Linux server1.tuhinhossain.com 5.15.0-151-generic #161-Ubuntu SMP Tue Jul 22 14:25:40 UTC
2025 x86_64
 

uid=1002(picotech) gid=1003(picotech) groups=1003(picotech),0(root)  

Safe-mode: OFF (not secure)

/usr/share/webmin/virtual-server/   drwxrwxr-x
Free 29.37 GB of 117.98 GB (24.9%)
Home    Back    Forward    UPDIR    Refresh    Search    Buffer    Encoder    Tools    Proc.    FTP brute    Sec.    SQL    PHP-code    Update    Self remove    Logout    


Viewing file:     feature-spam.pl (50.4 KB)      -rwxrwxr-x
Select action/file-type:
(+) | (+) | (+) | Code (+) | Session (+) | (+) | SDB (+) | (+) | (+) | (+) | (+) | (+) |
# Functions for turning SpamAssassin filtering on or off on a per-domain basis

sub init_spam
{
$domain_lookup_cmd = "$module_config_directory/lookup-domain.pl";
$procmail_spam_dir = "$module_config_directory/procmail";
$spam_config_dir = "$module_config_directory/spam";
$quota_spam_margin = 5*1024*1024;
$spamassassin_lock_file = "/tmp/virtualmin.spamassassin";
}

sub require_spam
{
return if ($require_spam++);
&foreign_require("procmail");
&foreign_require("spam");
}

sub check_depends_spam
{
if (!$_[0]->{'mail'}) {
    # Mail must be enabled for spam filtering to work!
    return $text{'setup_edepspam'};
    }
if ($config{'mail_system'} == 5) {
    # Not implemented for VPopMail
    return $text{'setup_edepspamvpop'};
    }
return undef;
}

# setup_spam(&domain)
# Adds the master procmail entry for domain-specific spam filtering, plus an
# include file for this domain.
sub setup_spam
{
my ($d) = @_;

&$first_print($text{'setup_spam'});
&require_spam();
&foreign_require("cron");

# Create the needed directories now, so we can lock files in them
if (!-d $procmail_spam_dir) {
    &make_dir($procmail_spam_dir, 0755);
    &set_ownership_permissions(undef, undef, 0755, $procmail_spam_dir);
    }
if (!-d $spam_config_dir) {
    &make_dir($spam_config_dir, 0755);
    &set_ownership_permissions(undef, undef, 0755, $spam_config_dir);
    }
local $spamdir = "$spam_config_dir/$d->{'id'}";
&make_dir($spamdir, 0750);
&set_ownership_permissions($d->{'uid'}, $d->{'gid'}, 0750, $spamdir);

&obtain_lock_spam($d);
&obtain_lock_cron($d);

# Add the procmail entry to get the VIRTUALMIN variable
local @recipes = &procmail::get_procmailrc();
local ($r, $gotvirt, $gotdef);
foreach $r (@recipes) {
    if ($r->{'type'} eq '=' &&
        $r->{'action'} =~ /^VIRTUALMIN=/) {
        $gotvirt++;
        }
    elsif ($r->{'name'} eq "DEFAULT") {
        $gotdef++;
        }
    }
if (!$gotvirt) {
    # Need to add entries to lookup the domain, and run it's include file
    local $var1 = { 'flags' => [ 'w', 'i' ],
            'conds' => [ ],
            'type' => '=',
                'action' => "VIRTUALMIN=|$domain_lookup_cmd \$LOGNAME" };
    local $testcmd = &has_command("test") || "test";
    local $var2 = { 'flags' => [ ],
            'conds' => [ [ "?", "$testcmd \"\$VIRTUALMIN\" != \"\"" ] ],
            'block' => "INCLUDERC=$procmail_spam_dir/\$VIRTUALMIN",
              };
    if ($gconfig{'os_type'} eq 'solaris') {
        # Need to call sh as shell explicitly
        $var2->{'conds'} =
            [ [ "?", "sh -c \"$testcmd '\$VIRTUALMIN' != ''\"" ] ];
        }

    # If the procmailrc file is empty, add at the end.
    # If there is a TRAP variable, add after it (so we do logging properly)
    # Otherwise, add at the top
    if (@recipes) {
        # Has some recipes .. check if there is a TRAP
        local ($trap, $aftertrap);
        for(my $i=0; $i<@recipes; $i++) {
            if ($recipes[$i]->{'name'} eq 'TRAP') {
                $trap = $recipes[$i];
                $trapafter = $recipes[$i+1];
                }
            }
        if ($trapafter) {
            # Add before the recipe that is after TRAP
            &procmail::create_recipe_before($var1, $trapafter);
            &procmail::create_recipe_before($var2, $trapafter);
            }
        elsif ($trap) {
            # Nothing after TRAP, so just add at end
            &procmail::create_recipe($var1);
            &procmail::create_recipe($var2);
            }
        else {
            # Just add at start
            &procmail::create_recipe_before($var1, $recipes[0]);
            &procmail::create_recipe_before($var2, $recipes[0]);
            }
        }
    else {
        &procmail::create_recipe($var1);
        &procmail::create_recipe($var2);
        }
    }

# Add procmail rule to bounce mail if quota is full
&setup_quota_full_bounce();

# Create the lookup-domain.pl wrapper script, and hack it to turn off setuid
&cron::create_wrapper($domain_lookup_cmd, $module_name,
              "lookup-domain.pl");
local $lref = &read_file_lines($domain_lookup_cmd);
splice(@$lref, 1, 0, "delete(\$ENV{'IFS'});",
             "delete(\$ENV{'CDPATH'});",
             "delete(\$ENV{'ENV'});",
             "delete(\$ENV{'BASH_ENV'});",
             "\$ENV{'PATH'} = '/bin:/usr/bin';",
             "\$< = \$>;",
             "\$( = \$);");
&flush_file_lines($domain_lookup_cmd);

# Build spamassassin command to call
local $cmd = &spamassassin_client_command($d);

# Create recipes to call spamassassin
local $spamrc = "$procmail_spam_dir/$d->{'id'}";
local $recipe0 = { 'name' => 'DROPPRIVS',    # Run all commands as user
           'value' => 'yes' };
local @conds;
if ($cmd =~ /spamassassin/ && $config{'spam_size'}) {
    # Add condition for max message size
    push(@conds, [ '<', $config{'spam_size'} ]);
    }
local $recipe1 = { 'flags' => [ 'f', 'w' ],    # Call spamassassin
           'conds' => \@conds,
           'type' => '|',
           'action' => $cmd,
         };
if ($config{'spam_lock'}) {
    # Add locking to prevent concurrent runs
    $recipe1->{'lockfile'} = $spamassassin_lock_file;
    }
local ($recipe2, $recipe3);
local $varon = { 'name' => 'SPAMMODE', 'value' => 1 };
if ($config{'spam_level'}) {
    # Recipe to delete high-score spam
    local $stars = join("", map { "\\*" } (1..$config{'spam_level'}));
    $recipe3 = { 'flags' => [ ],
             'conds' => [ [ '', '^X-Spam-Level: '.$stars ] ],
             'action' => '/dev/null' };
    }
if ($config{'spam_delivery'}) {
    # Receipe to deliver spam to some folder
    $recipe2 = { 'flags' => [ ],
             'conds' => [ [ '', '^X-Spam-Status: Yes' ] ],
             'action' => $config{'spam_delivery'} };
    }
local $varoff = { 'name' => 'SPAMMODE', 'value' => 0 };
&procmail::create_recipe($recipe0, $spamrc);
&procmail::create_recipe($recipe1, $spamrc);
if ($recipe2 || $recipe3) {
    &procmail::create_recipe($varon, $spamrc);
    &procmail::create_recipe($recipe3, $spamrc) if ($recipe3);
    &procmail::create_recipe($recipe2, $spamrc) if ($recipe2);
    &procmail::create_recipe($varoff, $spamrc);
    }

&set_ownership_permissions(undef, undef, 0755, $spamrc);

# Link all files in the default directory (/etc/mail/spamassassin) to
# the domain's directory
&create_spam_config_links($d);

# Create the config file for this server
&open_tempfile(TOUCH, ">$spamdir/virtualmin.cf", 0, 1);
&print_tempfile(TOUCH, "whitelist_from $d->{'emailto_addr'}\n");
&close_tempfile(TOUCH);
&set_ownership_permissions($d->{'uid'}, $d->{'gid'}, 0755,
              "$spamdir/virtualmin.cf");

# Whitelist all domain mailboxes
if ($config{'spam_white'}) {
    $d->{'spam_white'} = 1;
    &update_spam_whitelist($d);
    }

# Setup automatic spam clearing
my $opts = { };
my ($cmode, $cnum) = split(/\s+/, $tmpl->{'spamclear'});
if ($cmode eq 'days' || $cmode eq 'size') {
    $opts->{$cmode} = $cnum;
    }
my ($tmode, $tnum) = split(/\s+/, $tmpl->{'trashclear'});
if ($tmode eq 'days' || $tmode eq 'size') {
    $opts->{'trash'.$tmode} = $tnum;
    }
if (keys %$opts) {
    &save_domain_spam_autoclear($d, $opts);
    }

# Setup spamtrap aliases, if requested
if ($tmpl->{'spamtrap'} eq 'yes') {
    &obtain_lock_mail($d);
    &setup_spamtrap_aliases($d);
    &release_lock_mail($d);
    }

&release_lock_cron($d);
&release_lock_spam($d);
&$second_print($text{'setup_done'});
return 1;
}

# spamassassin_client_command(&domain, [client])
# Returns the command for calling spamassassin in some domain, plus args
sub spamassassin_client_command
{
my ($d, $client) = @_;
my $spamid = $d->{'parent'} || $d->{'id'};
$client ||= $config{'spam_client'};
my $cmd = &has_command($client);
if ($client eq 'spamc') {
    local ($host, $port) = split(/:/, $config{'spam_host'});
    if ($host) {
        $cmd .= " -d $host";
        if ($port) {
            $cmd .= " -p $port";
            }
        }
    if ($config{'spam_size'}) {
        $cmd .= " -s $config{'spam_size'}";
        }
    }
else {
    $cmd .= " --siteconfigpath $spam_config_dir/$spamid";
    }
return $cmd;
}

# validate_spam(&domain)
# Make sure the domain's procmail config file exists
sub validate_spam
{
my ($d) = @_;
my $spamrc = "$procmail_spam_dir/$d->{'id'}";
return &text('validate_espamprocmail', "<tt>$spamrc</tt>") if (!-r $spamrc);
my $spamdir = "$spam_config_dir/$d->{'id'}";
return &text('validate_espamconfig', "<tt>$spamdir</tt>") if (!-d $spamdir);
&require_spam();
my @recs = &procmail::parse_procmail_file($spamrc);
my $cmd = $spam::config{'spamassassin'};
my $found;
foreach my $r (@recs) {
    $found++ if ($r->{'action'} =~ /\Q$cmd\E|spamc|spamassassin/);
    }
return &text('validate_espamcall', "<tt>$spamrc</tt>") if (!$found);
return undef;
}

# setup_default_delivery()
# Adds or removes a rule at the end of /etc/procmailrc delivering to $DEFAULT,
# depending on the config setting
sub setup_default_delivery
{
&require_spam();
&obtain_lock_spam();
local @recipes = &procmail::get_procmailrc();
my ($gotdef, $gotorgmail, $gotdel, $gotdrop);
foreach my $r (@recipes) {
    if ($r->{'action'} eq '$DEFAULT' && !@{$r->{'conds'}}) {
        $gotdel = $r;
        }
    }

# The rule to deliver to $DEFAULT is needed to prevent users from creating
# their own .procmailrc files
if ($config{'default_procmail'} && !$gotdel) {
    # Append default delivery rule
    my $rec = { 'flags' => [ ],
            'conds' => [ ],
            'action' => '$DEFAULT' };
    &procmail::create_recipe($rec);
    }
elsif (!$config{'default_procmail'} && $gotdel) {
    # Remove default delivery rule
    &procmail::delete_recipe($gotdel);
    }

# Find the DEFAULT variable setting
@recipes = &procmail::get_procmailrc();
foreach my $r (@recipes) {
    if ($r->{'name'} eq 'DEFAULT') {
        $gotdef = $r;
        }
    }

# The DEFAULT destination needs to be set to match the mail server, as procmail
# will deliver to /var/mail/USER by default
local ($dir, $style, $mailbox, $maildir) = &get_mail_style();
local $maildef = $dir && $dir =~ /^(.*)\/$/ ? "$1/\$LOGNAME/" :
             $dir ? "$dir/\$LOGNAME" :
         $maildir ? "\$HOME/$maildir/" :
         $mailbox ? "\$HOME/$mailbox" :
                "/var/mail/\$LOGNAME";
if ($gotdef) {
    # Update default delivery definition
    $gotdef->{'value'} = $maildef;
    &procmail::modify_recipe($gotdef);
    }
else {
    # Create default delivery definition
    my $rec = { 'name' => 'DEFAULT',
            'value' => $maildef };
    if (@recipes) {
        &procmail::create_recipe_before($rec, $recipes[0]);
        }
    else {
        &procmail::create_recipe($rec);
        }
    }

# Find the ORGMAIL variable
@recipes = &procmail::get_procmailrc();
foreach my $r (@recipes) {
    if ($r->{'name'} eq 'ORGMAIL') {
        $gotorgmail = $r;
        }
    }

# Same for the ORGMAIL destination, to prevent delivery falling back to
# /var/mail/XXX in an over-quota situation
if ($gotorgmail) {
    # Update default delivery rule
    $gotorgmail->{'value'} = $maildef;
    &procmail::modify_recipe($gotorgmail);
    }
else {
    # Create default delivery rule
    my $rec = { 'name' => 'ORGMAIL',
            'value' => $maildef };
    if (@recipes) {
        &procmail::create_recipe_before($rec, $recipes[0]);
        }
    else {
        &procmail::create_recipe($rec);
        }
    }

# Re-get the default delivery receipe, and DROPPRIVS
$gotdel = undef;
@recipes = &procmail::get_procmailrc();
foreach my $r (@recipes) {
    if ($r->{'action'} eq '$DEFAULT' &&
        !@{$r->{'conds'}}) {
        $gotdel = $r;
        }
    elsif ($r->{'name'} eq 'DROPPRIVS') {
        $gotdrop = $r;
        }
    }

# DROPPRIVS needs to be set to yes to force delivery as the correct user. This
# must be done before the rule that delivers to $DEFAULT, or at the end of the
# file.
if (!$gotdrop) {
    my $rec = { 'name' => 'DROPPRIVS',
            'value' => 'yes' };
    if ($gotdel) {
        # Add before default rule
        &procmail::create_recipe_before($rec, $gotdel);
        }
    else {
        # Add at end
        &procmail::create_recipe($rec);
        }
    }

&release_lock_spam();
}

# enable_procmail_logging()
# Configure Procmail to log to /var/log/procmail.log, and setup logrotate
# for that directory.
sub enable_procmail_logging
{
&require_spam();
&obtain_lock_spam();
local @recipes = &procmail::get_procmailrc();
local ($gotlog, $gottrap);
foreach my $r (@recipes) {
    if ($r->{'name'} eq 'LOGFILE') {
        $gotlog = 1;
        }
    if ($r->{'name'} eq 'TRAP') {
        $gottrap = 1;
        }
    }
if (!$gotlog) {
    # Add LOGFILE variables
    my $rec0 = { 'name' => 'LOGFILE',
             'value' => $procmail_log_file };
    &procmail::create_recipe_before($rec0, $recipes[0]);
    }
if (!$gottrap) {
    # Add TRAP, which specifies a command to output logging info about
    # the email after delivery
    my $rec1 = { 'name' => 'TRAP', 'value' => $procmail_log_cmd };
    &procmail::create_recipe_before($rec1, $recipes[0]);
    }

# For any domains with spam or virus filtering enabled, add SPAMMODE and
# VIRUSMODE procmail variables so that the logger knows what kind of destination
# email ended up at
foreach my $d (&list_domains()) {
    next if (!$d->{'spam'});
    &obtain_lock_spam($d);
    local $spamrc = "$procmail_spam_dir/$d->{'id'}";
    local @recipes = &procmail::parse_procmail_file($spamrc);
    local ($spamrec, $spamrecafter, $gotspammode);
    local $i = 0;
    foreach my $r (@recipes) {
        if ($r->{'name'} eq 'SPAMMODE') {
            $gotspammode = 1;
            }
        elsif ($r->{'conds'}->[0]->[1] eq '^X-Spam-Status: Yes') {
            # Found place to insert
            $spamrec = $r;
            $spamrecafter = $recipes[$i+1];
            last;
            }
        $i++;
        }
    if ($spamrec && !$gotspammode) {
        local $varon = { 'name' => 'SPAMMODE', 'value' => 1 };
        local $varoff = { 'name' => 'SPAMMODE', 'value' => 0 };
        if ($spamrecafter) {
            &procmail::create_recipe_before($varoff, $spamrecafter,
                            $spamrc);
            }
        else {
            &procmail::create_recipe($varoff, $spamrc);
            }
        &procmail::create_recipe_before($varon, $spamrec, $spamrc);
        }

    # Do the same for viruses
    if ($d->{'virus'}) {
        local @recipes = &procmail::parse_procmail_file($spamrc);
        local ($clamrec, $clamafter, $gotclammode);
        local $i = 0;
        foreach my $r (@recipes) {
            if ($r->{'name'} eq 'VIRUSMODE') {
                $gotclammode = 1;
                }
            elsif ($r->{'action'} =~ /^\Q$clam_wrapper_cmd\E/) {
                # Insert after this one
                $clamrec = $recipes[$i+1];
                $clamrecafter = $recipes[$i+2];
                }
            $i++;
            }
        if ($clamrec && !$gotclammode) {
            local $varon = { 'name' => 'VIRUSMODE', 'value' => 1 };
            local $varoff = { 'name' => 'VIRUSMODE', 'value' => 0 };
            if ($clamrecafter) {
                &procmail::create_recipe_before(
                    $varoff, $clamrecafter, $spamrc);
                }
            else {
                &procmail::create_recipe($varoff, $spamrc);
                }
            &procmail::create_recipe_before(
                $varon, $clamrec, $spamrc);
            }
        }
    &release_lock_spam($d);
    }

# Copy the log writer command to /etc/webmin
&copy_source_dest("$module_root_directory/procmail-logger.pl",
          $procmail_log_cmd);
&set_ownership_permissions(undef, undef, 0755, $procmail_log_cmd);

if ($config{'logrotate'} && &foreign_installed("logrotate")) {
    # Add logrotate section, if needed
    &require_logrotate();
    local $log = &get_logrotate_section($procmail_log_file);
    if (!$log) {
        local $parent = &logrotate::get_config_parent();
        local $lconf = { 'file' => &logrotate::get_add_file(),
                 'name' => [ $procmail_log_file ] };
        $lconf->{'members'} = [
                { 'name' => 'rotate',
                  'value' => $config{'logrotate_num'} || 5 },
                { 'name' => 'daily' },
                { 'name' => 'compress' },
                ];
        &lock_file($lconf->{'file'});
        &logrotate::save_directive($parent, undef, $lconf);
        &flush_file_lines($lconf->{'file'});
        &unlock_file($lconf->{'file'});
        }
    # Make sure file exists, so logrotate doesn't complain
    if (!-r $procmail_log_file) {
        open(LOG, ">$procmail_log_file");
        close(LOG);
        }
    }
&release_lock_spam();
}

# procmail_logging_enabled()
# Returns 1 if logging entries exist in /etc/procmailrc
sub procmail_logging_enabled
{
&require_spam();
local @recipes = &procmail::get_procmailrc();
foreach my $r (@recipes) {
    if ($r->{'name'} eq 'LOGFILE') {
        return 1;
        }
    }
return 0;
}

# modify_spam(&domain, &olddomain)
# Doesn't have to do anything
sub modify_spam
{
my ($d, $oldd) = @_;
return 1;
}

# delete_spam(&domain)
# Just remove the domain's procmail config file
sub delete_spam
{
my ($d) = @_;
&$first_print($d->{'virus'} ? $text{'delete_spamvirus'}
                : $text{'delete_spam'});
&obtain_lock_spam($d);
local $spamrc = "$procmail_spam_dir/$d->{'id'}";
&unlink_logged($spamrc);
local $spamdir = "$spam_config_dir/$d->{'id'}";
&system_logged("rm -rf ".quotemeta($spamdir));
&clear_lookup_domain_cache($d);
&save_domain_spam_autoclear($d, undef);
&release_lock_spam($d);
&$second_print($text{'setup_done'});
return 1;
}

# clone_spam(&domain, &old-domain)
# Copy per-domain procmail rules and spamassassin config files to new domain,
# correcting the domain ID
sub clone_spam
{
local ($d, $oldd) = @_;
&$first_print($text{'clone_spam'});
&obtain_lock_spam($d);
local $pm = "$procmail_spam_dir/$d->{'id'}";
local $opm = "$procmail_spam_dir/$oldd->{'id'}";
&copy_source_dest($opm, $pm);
local $lref = &read_file_lines($pm);
foreach my $l (@$lref) {
    $l =~ s/\Q$oldd->{'id'}\E/$d->{'id'}/;
    }
&flush_file_lines($pm);
local $spamdir = "$spam_config_dir/$d->{'id'}";
local $ospamdir = "$spam_config_dir/$oldd->{'id'}";
&system_logged("rm -rf ".quotemeta($spamdir)."/*");
&system_logged("cd ".quotemeta($ospamdir)." && tar cf - . | ".
           "(cd $spamdir && tar xpf -)");

# Fix email addresses in per-domain spamassassin config file
local $spamfile = "$spamdir/virtualmin.cf";
&set_ownership_permissions($d->{'uid'}, $d->{'gid'}, undef, $spamfile);
local $lref = &read_file_lines($spamfile);
foreach my $l (@$lref) {
    if ($l =~ /^whitelist_from\s+\Q$oldd->{'emailto_addr'}\E/) {
        $l = "whitelist_from $d->{'emailto_addr'}";
        }
    }
&flush_file_lines($spamfile);

# Re-update whitelist
if ($d->{'spam_white'}) {
    &update_spam_whitelist($d);
    }

# Copy automatic spam clearing
&save_domain_spam_autoclear($d, &get_domain_spam_autoclear($oldd));

&release_lock_spam($d);
&$second_print($text{'setup_done'});
return 1;
}

# check_spam_clash()
# No need to check for clashes ..
sub check_spam_clash
{
return 0;
}

# backup_spam(&domain, file)
# Saves the server's procmail and spamassassin configuration to a file.
# Also saves the auto-spam clearing settings.
sub backup_spam
{
local ($d, $file, $opts, $homefmt, $increment, $asd, $allopts, $key) = @_;
local $compression = $allopts->{'dir'}->{'compression'};
&$first_print($text{'backup_spamcp'});
local $spamrc = "$procmail_spam_dir/$d->{'id'}";
local $spamdir = "$spam_config_dir/$d->{'id'}";
if (-r $spamrc) {
    &copy_write_as_domain_user($d, $spamrc, $file);
    local $temp = &transname();
    &execute_command(&make_archive_command(
        $compression, $spamdir, $temp, ".")." 2>&1");
    &copy_write_as_domain_user($d, $temp, $file."_cf");
    &unlink_file($temp);

    # Save spam clearing
    local $auto = &get_domain_spam_autoclear($_[0]);
    &write_as_domain_user($d,
        sub { &write_file($file."_auto", $auto || { }) });
    &$second_print($text{'setup_done'});
    return 1;
    }
else {
    &$second_print($text{'backup_nospam'});
    return 0;
    }
}

# restore_spam(&domain, file)
# Restores the domains procmail and spamassassin configuration files.
# Also restores auto-clearing setting, if in backup.
sub restore_spam
{
local ($d, $file) = @_;
&$first_print($text{'restore_spamcp'});
&obtain_lock_spam($d);
local $spamrc = "$procmail_spam_dir/$d->{'id'}";
local $spamdir = "$spam_config_dir/$d->{'id'}";
&copy_source_dest($file, $spamrc);
&execute_command(&make_unarchive_command($spamdir, $file."_cf")." 2>&1");

# Fix any incorrect /etc/webmin references
my $lref = &read_file_lines($spamrc);
foreach my $l (@$lref) {
    if ($l =~ /^(.*\s+)(\S+\/clam-wrapper.pl)(\s+.*)$/) {
        $l = $1.$clam_wrapper_cmd.$3;
        }
    if ($l =~ /^(.*\s+--siteconfigpath\s+)(\S+\/virtual-server\/spam\/\S+)$/) {
        $l = $1.$spamdir;
        }
    }
&flush_file_lines($spamrc);

if (-r $file."_auto") {
    # Replace auto-clearing setting
    &save_domain_spam_autoclear($d, undef);
    local %auto;
    &read_file($file."_auto", \%auto);
    if (%auto) {
        &save_domain_spam_autoclear($d, \%auto);
        }
    }

# If spamtrap aliases exist, make sure the files and cron job do
local $st = &get_spamtrap_aliases($d);
if ($st > 0) {
    &setup_spamtrap_directories($d);
    &setup_spamtrap_cron();
    }

# Re-create all spam links
&create_spam_config_links($d);

&release_lock_spam($d);
&$second_print($text{'setup_done'});
return 1;
}

# save_global_spam_lockfile(enable)
# Adds or removes a lockfile to all domains' spamassassin calls
sub save_global_spam_lockfile
{
local ($enabled) = @_;
foreach my $d (&get_domain_by("spam", 1)) {
    local $spamrc = "$procmail_spam_dir/$d->{'id'}";
    local @recipes = &procmail::parse_procmail_file($spamrc);
    local @spamrec = &find_spam_recipe(\@recipes);
    if ($spamrec[0]) {
        # Found it .. modify
        if ($enabled) {
            $spamrec[0]->{'lockfile'} = $spamassassin_lock_file;
            }
        else {
            delete($spamrec[0]->{'lockfile'});
            }
        &procmail::modify_recipe($spamrec[0]);
        }
    }
}

# sysinfo_spam()
# Returns the SpamAssassin version
sub sysinfo_spam
{
&require_spam();
local $vers = &spam::get_spamassassin_version();
return ( [ $text{'sysinfo_spam'}, $vers ] );
}

sub links_spam
{
local ($d) = @_;
local $client = &get_domain_spam_client($d);
if ($client ne "spamc") {
    return ( { 'mod' => 'spam',
           'desc' => $text{'links_spam'},
           'page' => 'index.cgi?file='.&urlize(
            "$spam_config_dir/$d->{'id'}/virtualmin.cf").
            '&title='.&urlize(&show_domain_name($d)),
           'cat' => 'mail',
         });
    }
return ( );
}

# find_spam_recipe(&recipes)
# Returns the five recipes used for spam filtering, some of which may be
# undef if not set. They are :
# 0 - Call to spamassassin or spamc
# 1 - Setting of SPAMMODE=1
# 2 - Delivery for high-score spam
# 3 - Delivery for other spam
# 4 - Setting of SPAMMODE=0
sub find_spam_recipe
{
local ($recs) = @_;
local @rv;
for(my $i=0; $i<@$recs; $i++) {
    if ($recs->[$i]->{'action'} =~ /(spamassassin|spamc)($|\s)/) {
        # Found spamassassin
        $rv[0] = $recs->[$i];
        }
    elsif ($recs->[$i]->{'name'} eq 'SPAMMODE') {
        # Start or end of spam delivery block
        if ($recs->[$i]->{'value'} eq '1') {
            $rv[1] = $recs->[$i];
            }
        else {
            # End of recipes we care about
            $rv[4] = $recs->[$i];
            last;
            }
        }
    else {
        # Look at conditions
        my $r = $recs->[$i];
        foreach my $c (@{$r->{'conds'}}) {
            if ($c->[1] =~ /X-Spam-Status/i) {
                $rv[3] ||= $r;
                }
            elsif ($c->[1] =~ /X-Spam-Level/i) {
                $rv[2] ||= $r;
                }
            }
        }
    }
return @rv;
}

# get_domain_spam_delivery(&domain)
# Returns the delivery mode and dest for some domain. The modes can be :
# 0 - Throw away , 1 - File under home , 2 - Forward to email , 3 - Other file,
# 4 - Normal ~/mail/spam file , 5 - Deliver normally , 6 - ~/Maildir/.spam/ ,
# -1 - Broken!
# Also returns the score at which spam is deleted, and the destination (usually
# /dev/null)
sub get_domain_spam_delivery
{
local ($d) = @_;
&require_spam();
local $spamrc = "$procmail_spam_dir/$d->{'id'}";
local @recipes = &procmail::parse_procmail_file($spamrc);
local @spamrec = &find_spam_recipe(\@recipes);
local @rv;
if (!@spamrec) {
    @rv = (-1, undef);
    }
elsif (!$spamrec[3]) {
    @rv = (5, undef);
    }
elsif ($spamrec[3]->{'action'} eq '/dev/null') {
    @rv = (0, undef);
    }
elsif ($spamrec[3]->{'action'} =~ /^\$HOME\/mail\/(spam|Spam|junk|Junk)$/) {
    @rv = (4, $1);
    }
elsif ($spamrec[3]->{'action'} =~ /^\$HOME\/Maildir\/\.(spam|Spam|junk|Junk)\/$/) {
    @rv = (6, $1);
    }
elsif ($spamrec[3]->{'action'} =~ /^\$HOME\/(.*)$/) {
    @rv = (1, $1);
    }
elsif ($spamrec[3]->{'action'} =~ /\@/) {
    @rv = (2, $spamrec[3]->{'action'});
    }
else {
    @rv = (3, $spamrec[3]->{'action'});
    }
if ($spamrec[2]) {
    # Add deletion recipe info
    foreach my $c (@{$spamrec[2]->{'conds'}}) {
        if ($c->[1] =~ /X-Spam-Level:\s+((\\\*)+)/i) {
            # Found it
            push(@rv, length($1)/2, $r->{'action'});
            }
        }
    }
return @rv;
}

# save_domain_spam_delivery(&domain, [mode, dest], [delete-level, delete-dest])
# Updates the delivery method for spam for some domain
sub save_domain_spam_delivery
{
local ($d, $mode, $dest, $level, $ddest) = @_;
&require_spam();
local $spamrc = "$procmail_spam_dir/$d->{'id'}";
local @recipes = &procmail::parse_procmail_file($spamrc);
local @spamrec = &find_spam_recipe(\@recipes);
return 0 if (!@spamrec);

# Preserve existing settings if not set
local ($oldmode, $olddest, $oldlevel, $oldddest) =
    &get_domain_spam_delivery($d);
if (!defined($mode)) {
    ($mode, $dest) = ($oldmode, $olddest);
    }
elsif (!defined($dest)) {
    $dest = $olddest;
    }
if (!defined($level)) {
    $level = $oldlevel;
    }
if (!defined($ddest)) {
    $ddest = $oldddest;
    }
$ddest ||= "/dev/null";

# Remove the existing recipes (except the spamassassin call)
local @todel = sort { $b->{'line'} <=> $a->{'line'} }
            grep { $_ } @spamrec[1..$#spamrec];
foreach my $r (@todel) {
    &procmail::delete_recipe($r);
    }

# Make those we now want
local @want;
if ($mode != 5 || $level) {
    # Start of delivery section
    push(@want, { 'name' => 'SPAMMODE', 'value' => 1 });
    }
if ($level) {
    # High-level deletion
    local $stars = join("", map { "\\*" } (1..$level));
    push(@want, { 'conds' => [ [ '', '^X-Spam-Level: '.$stars ] ],
              'action' => $ddest });
    }
if ($mode != 5) {
    # Regular delivery
    local $folder;
    if ($mode == 4 || $mode == 6) {
        if ($dest =~ /^[a-z0-9\.\_\-]+$/i) {
            $folder = $dest;
            }
        else {
            $folder = "Junk";
            }
        }
    local $action = $mode == 0 ? "/dev/null" :
            $mode == 4 ? "\$HOME/mail/$folder" :
            $mode == 6 ? "\$HOME/Maildir/.$folder/" :
            $mode == 1 ? "\$HOME/$dest" :
                      $dest;
    local $type = $mode == 2 ? "!" : "";
    push(@want, { 'conds' => [ [ '', '^X-Spam-Status: Yes' ] ],
              'action' => $action,
              'type' => $type });
    }
if ($mode != 5 || $level) {
    # End of delivery section
    push(@want, { 'name' => 'SPAMMODE', 'value' => 0 });
    }

# Add them
if (@want) {
    @recipes = &procmail::parse_procmail_file($spamrc);
    @spamrec = &find_spam_recipe(\@recipes);
    local $idx = &indexof($spamrec[0], @recipes);
    if ($idx == $#recipes) {
        # Add at end
        foreach my $r (@want) {
            &procmail::create_recipe($r, $spamrc);
            }
        }
    else {
        # After spamassassin call
        foreach my $r (@want) {
            procmail::create_recipe_before($r, $recipes[$idx+1],
                               $spamrc);
            }
        }
    }
&clear_lookup_domain_cache($_[0]);
return 1;
}

# get_domain_spam_client(&domain)
# Returns the client program (spamassassin or spamc) used by some domain
sub get_domain_spam_client
{
local ($d) = @_;
&require_spam();
local $spamrc = "$procmail_spam_dir/$d->{'id'}";
local @recipes = &procmail::parse_procmail_file($spamrc);
foreach my $r (@recipes) {
    if ($r->{'action'} =~ /\/\S+\/(spamassassin|spamc)/) {
        return $1;
        }
    }
return undef;    # Cannot happen!
}

# save_domain_spam_client(&domain, spamassassin|spamc)
# Updates the procmail rule which calls spamassassin or spamc
sub save_domain_spam_client
{
local ($d, $client) = @_;
&require_spam();
local $spamrc = "$procmail_spam_dir/$d->{'id'}";
local @recipes = &procmail::parse_procmail_file($spamrc);
foreach my $r (@recipes) {
    if ($r->{'action'} =~ /\/\S+\/(spamassassin|spamc)/) {
        $r->{'action'} = &spamassassin_client_command($d, $client);
        local ($c) = grep { $_->[0] eq '<' } @{$r->{'conds'}};
        if ($c && !$config{'spam_size'}) {
            # Remove size condition
            @{$r->{'conds'}} = grep { $_ ne $c } @{$r->{'conds'}};
            }
        elsif (!$c && $config{'spam_size'}) {
            # Add size condition
            push(@{$r->{'conds'}}, [ '<', $config{'spam_size'} ]);
            }
        elsif ($c && $config{'spam_size'}) {
            # Fix size in condition
            $c->[1] = $config{'spam_size'};
            }
        &procmail::modify_recipe($r);
        }
    }
}

# get_global_spam_client()
# Returns the spam client that is supposed to be used by all domains. If this
# is spamc, also returns the spamd hostname and max message size
sub get_global_spam_client
{
local ($client, $host, $size);
if ($config{'spam_client_global'}) {
    # We know the global setting for sure
    $client = $config{'spam_client'};
    }
else {
    # Find the most used one for all domains
    local (%cmdcount, $maxcmd);
    foreach my $d (grep { $_->{'spam'} } &list_domains()) {
        local $cmd = &get_domain_spam_client($d);
        if ($cmd) {
            $cmdcount{$cmd}++;
            if (!$maxcmd || $cmdcount{$cmd} > $cmdcount{$maxcmd}) {
                $maxcmd = $cmd;
                }
            }
        }
    return $maxcmd || $config{'spam_client'};
    }
$host = $config{'spam_host'};
$size = $config{'spam_size'};
return wantarray ? ( $client, $host, $size ) : $client;
}

# save_global_spam_client(client, spamc-host, spamc-size)
# Updates all domains with a new SpamAssassin client program
sub save_global_spam_client
{
local ($client, $host, $size) = @_;
$config{'spam_client'} = $client;
$config{'spam_client_global'} = 1;
$config{'spam_host'} = $host;
$config{'spam_size'} = $size;
&save_module_config();
foreach my $d (grep { $_->{'spam'} } &list_domains()) {
    &save_domain_spam_client($d, $client);
    }
}

# get_global_quota_exitcode()
# Returns the exit code from lookup-domain when a user is close to or over quota
sub get_global_quota_exitcode
{
&require_spam();
local @recipes = &procmail::get_procmailrc();
foreach my $r (@recipes) {
    if ($r->{'action'} =~ /\Q$domain_lookup_cmd\E.*\s+\-\-exitcode\s+(\d+)/) {
        return $1;
        }
    }
return 73;
}

# save_global_quota_exitcode(code)
# Updates the exit code from lookup-domain when a user is close to or over quota
sub save_global_quota_exitcode
{
local ($code) = @_;
&require_spam();
&lock_file($procmail::procmailrc);
local @recipes = &procmail::get_procmailrc();
local $changed = 0;
foreach my $r (@recipes) {
    if ($r->{'action'} =~ /\Q$domain_lookup_cmd\E(.*?)\s+\$LOGNAME/) {
        # Fix call to lookup-domain to return correct exit code
        my $flags = $1;
        $flags =~ s/\s+--exitcode\s+\d+//;
        $flags .= " --exitcode $code";
        $r->{'action'} = "VIRTUALMIN=|$domain_lookup_cmd".$flags." \$LOGNAME";
        &procmail::modify_recipe($r);
        $changed++;
        }
    elsif ($r->{'conds'} && @{$r->{'conds'}} &&
           $r->{'conds'}->[0]->[1] =~ /EXITCODE/) {
        # Fix rule that uses the exit code
        $r->{'conds'}->[0]->[1] =~ s/'(\d+)'/'$code'/;
        $r->{'conds'}->[0]->[1] =~ s/"(\d+)"/"$code"/;
        &procmail::modify_recipe($r);
        $changed++;
        }
    }
&unlock_file($procmail::procmailrc);
return $changed ? undef : "No receipes found";
}

# save_lookup_domain_port(port)
# Update the procmail config and the lookup domain server port
sub save_lookup_domain_port
{
my ($port) = @_;

# Update the procmail config
&require_spam();
&lock_file($procmail::procmailrc);
my @recipes = &procmail::get_procmailrc();
foreach my $r (@recipes) {
    if ($r->{'action'} =~ /\Q$domain_lookup_cmd\E(.*?)\s+\$LOGNAME/) {
        my $flags = $1;
        $flags =~ s/\s+--port\s+\d+//;
        if ($port) {
            $flags .= " --port $port";
            }
        $r->{'action'} = "VIRTUALMIN=|$domain_lookup_cmd".$flags." \$LOGNAME";
        &procmail::modify_recipe($r);
        }
    }
&unlock_file($procmail::procmailrc);

# Update the server
&lock_file($module_config_file);
$config{'lookup_domain_port'} = $port;
&save_module_config();
&unlock_file($module_config_file);
&foreign_require("init");
&init::restart_action("lookup-domain");
}

# update_spam_whitelist(&domain)
# Adds all mailboxes in this domain to the spamassassin whitelist in its
# configuration, and removes any whitelists that don't correspond to users.
sub update_spam_whitelist
{
local ($d) = @_;
return if (!$d->{'spam'} || !$d->{'spam_white'});
&require_spam();
local $spamfile = "$spam_config_dir/$d->{'id'}/virtualmin.cf";
local $conf = &spam::get_config($spamfile);
local @whites = &spam::find_value("whitelist_from", $conf);
local @oldwhites = @whites;
@whites = grep { !/\@$d->{'dom'}$/ } @whites;
foreach my $user (&list_domain_users($d, 0, 1, 1, 1)) {
    push(@whites, &remove_userdom($user->{'user'}, $d)."\@".$d->{'dom'});
    }
@whites = sort { $a cmp $b } @whites;
if (join(" ", @whites) ne join(" ", @oldwhites)) {
    # Need to update spamassassin config
    $spam::add_cf = $spamfile;
    &spam::save_directives($conf, "whitelist_from", \@whites, 1); 
    &flush_file_lines($spamfile);
    }
}

# show_template_spam(&template)
# Outputs HTML for editing spamassassin related template options
sub show_template_spam
{
local ($tmpl) = @_;

# Default spam clearing mode
local ($cmode, $cnum) = split(/\s+/, $tmpl->{'spamclear'});
local $cdays = $cmode eq 'days' ? $cnum : undef;
local $csize = $cmode eq 'size' ? $cnum : undef;
print &ui_table_row(&hlink($text{'tmpl_spamclear'}, 'template_spamclear'),
        &ui_radio("spamclear", $cmode,
            [ $tmpl->{'default'} ? ( )
                     : ( [ "", $text{'default'}."<br>" ] ),
          [ "none", $text{'no'}."<br>" ],
          [ "days", &text('spam_cleardays',
                 &ui_textbox("spamclear_days", $cdays, 5))."<br>" ],
          [ "size", &text('spam_clearsize',
                 &ui_bytesbox("spamclear_size", $csize)) ],
        ]));

# Default trash clearing mode
local ($cmode, $cnum) = split(/\s+/, $tmpl->{'trashclear'});
local $cdays = $cmode eq 'days' ? $cnum : undef;
local $csize = $cmode eq 'size' ? $cnum : undef;
print &ui_table_row(&hlink($text{'tmpl_trashclear'}, 'template_trashclear'),
        &ui_radio("trashclear", $cmode,
            [ $tmpl->{'default'} ? ( )
                     : ( [ "", $text{'default'}."<br>" ] ),
          [ "none", $text{'no'}."<br>" ],
          [ "days", &text('spam_cleardays',
                 &ui_textbox("trashclear_days", $cdays, 5))."<br>"],
          [ "size", &text('spam_clearsize',
                 &ui_bytesbox("trashclear_size", $csize)) ],
        ]));

# Spamtrap default
print &ui_table_row(&hlink($text{'tmpl_spamtrap'}, 'template_spamtrap'),
        &ui_radio("spamtrap", $tmpl->{'spamtrap'},
              [ $tmpl->{'default'} ? ( )
                   : ( [ "", $text{'default'}."<br>" ] ),
             [ "yes", $text{'yes'} ],
                [ "none", $text{'no'} ] ]));
}

# parse_template_spam(&tmpl)
# Updates spamassassin related template options from %in
sub parse_template_spam
{
local ($tmpl) = @_;

# Parse spam clearing option
if ($in{'spamclear'} eq '') {
    $tmpl->{'spamclear'} = '';
    }
elsif ($in{'spamclear'} eq 'none') {
    $tmpl->{'spamclear'} = 'none';
    }
elsif ($in{'spamclear'} eq 'days') {
    $in{'spamclear_days'} =~ /^\d+$/ && $in{'spamclear_days'} > 0 ||
        &error($text{'spam_edays'});
    $tmpl->{'spamclear'} = 'days '.$in{'spamclear_days'};
    }
elsif ($in{'spamclear'} eq 'size') {
    $in{'spamclear_size'} =~ /^\d+$/ && $in{'spamclear_size'} > 0 ||
        &error($text{'spam_esize'});
    $tmpl->{'spamclear'} = 'size '.($in{'spamclear_size'}*
                    $in{'spamclear_size_units'});
    }

# Parse spam clearing option
if ($in{'trashclear'} eq '') {
    $tmpl->{'trashclear'} = '';
    }
elsif ($in{'trashclear'} eq 'none') {
    $tmpl->{'trashclear'} = 'none';
    }
elsif ($in{'trashclear'} eq 'days') {
    $in{'trashclear_days'} =~ /^\d+$/ && $in{'trashclear_days'} > 0 ||
        &error($text{'spam_edays'});
    $tmpl->{'trashclear'} = 'days '.$in{'trashclear_days'};
    }
elsif ($in{'trashclear'} eq 'size') {
    $in{'trashclear_size'} =~ /^\d+$/ && $in{'trashclear_size'} > 0 ||
        &error($text{'spam_esize'});
    $tmpl->{'trashclear'} = 'size '.($in{'trashclear_size'}*
                    $in{'trashclear_size_units'});
    }

# Parse spam trap
$tmpl->{'spamtrap'} = $in{'spamtrap'};
}

# clear_lookup_domain_cache(&domain, [&user])
# Removes entries from the lookup-domain cache for a user all users in a domain
sub clear_lookup_domain_cache
{
local ($d, $user) = @_;

# Open the cache DBM
local $cachefile = "$ENV{'WEBMIN_VAR'}/lookup-domain-cache";
local %cache;
eval "use SDBM_File";
dbmopen(%cache, $cachefile, 0700);
eval "\$cache{'1111111111'} = 1";
if ($@) {
    dbmclose(%cache);
    eval "use NDBM_File";
    dbmopen(%cache, $cachefile, 0700);
    }

if ($user) {
    # For just one user
    delete($cache{$user->{'user'}});
    }
else {
    # For all users in a domain
    foreach my $u (&list_domain_users($d, 0, 1, 1, 1)) {
        delete($cache{$u->{'user'}});
        }
    }
}

# get_domain_spam_autoclear(&domain)
# Returns an object containing spam clearing info for this domain, if defined
sub get_domain_spam_autoclear
{
local ($d) = @_;
local %spamclear;
&read_file_cached($spamclear_file, \%spamclear);
local $ds = $spamclear{$d->{'id'}};
return undef if (!$ds);
local %auto = map { split(/=/, $_, 2) } split(/\s+/, $ds);
return \%auto;
}

# save_domain_spam_autoclear(&domain, &autoclear)
# Saves the automatic spam clearing policy for a domain, and sets up the 
# cron job if needed
sub save_domain_spam_autoclear
{
local ($d, $auto) = @_;

# Update config file
local %spamclear;
&read_file_cached($spamclear_file, \%spamclear);
if ($auto) {
    $spamclear{$d->{'id'}} = join(" ", map { $_."=".$auto->{$_} }
                           keys %$auto);
    }
else {
    delete($spamclear{$d->{'id'}});
    }
&write_file($spamclear_file, \%spamclear);
&setup_spamclear_cron_job();
}

# setup_spamclear_cron_job()
# Create or remove the spam-clearing cron job based on whether retention or
# spam clearing is enabled for any domain
sub setup_spamclear_cron_job
{
local $job = &find_cron_script($spamclear_cmd);
local %spamclear;
&read_file_cached($spamclear_file, \%spamclear);
local $want = %spamclear || $config{'retention_policy'};
if ($job && !$want) {
    # Disable job, as we don't need it
    &delete_cron_script($job);
    }
elsif (!$job && $want) {
    # Enable the job
    $job = { 'user' => 'root',
         'command' => $spamclear_cmd,
         'active' => 1,
         'mins' => int(rand()*60),
         'hours' => 0,
         'days' => '*',
         'months' => '*',
         'weekdays' => '*' };
    &setup_cron_script($job);
    }
}

# create_spam_config_links(&domain)
# Creates links from the global spamasasassin config directory to the domain's
# spam directory.
sub create_spam_config_links
{
local ($d) = @_;
local $defdir;
&require_spam();
if (-d $spam::config{'local_cf'}) {
    $defdir = $spam::config{'local_cf'};
    }
elsif ($spam::config{'local_cf'} =~ /^(.*)\//) {
    $defdir = $1;
    }
local $spamdir = "$spam_config_dir/$d->{'id'}";
if ($defdir) {
    # Remove any old links
    opendir(DIR, $spamdir);
    foreach my $f (readdir(DIR)) {
        local $p = "$spamdir/$f";
        if ($f ne "." && $f ne "..") {
            local $lnk = readlink($p);
            if ($lnk && !-e $lnk) {
                unlink($p);
                }
            }
        }
    closedir(DIR);

    # Create the new links
    opendir(DIR, $defdir);
    foreach my $f (readdir(DIR)) {
        if ($f ne "." && $f ne "..") {
            &symlink_logged("$defdir/$f", "$spamdir/$f");
            }
        }
    closedir(DIR);
    }
}

# setup_spam_config_job()
# Create the cron job to link up spamassassin config files, and delete clamav-*
# files in /tmp
sub setup_spam_config_job
{
local $job = &find_cron_script($spamconfig_cron_cmd);
if (!$job) {
    # Create, and run for the first time
    $job = { 'mins' => int(rand()*60),
         'hours' => '*',
         'days' => '*',
         'months' => '*',
         'weekdays' => '*',
         'user' => 'root',
         'active' => 1,
         'command' => $spamconfig_cron_cmd };
    &setup_cron_script($job);
    }

# And run now, just in case spamassassin was upgraded recently
foreach my $d (grep { $_->{'spam'} } &list_domains()) {
    &create_spam_config_links($d);
    }
}

# setup_lookup_domain_daemon()
# Create the lookup-domain.pl wrapper script, and setup the lookup-domain-daemon
# background process
sub setup_lookup_domain_daemon
{
&foreign_require("init");
local $pidfile = "$ENV{'WEBMIN_VAR'}/lookup-domain-daemon.pid";
local $helper = &get_api_helper_command();
if (!&init::action_status("lookup-domain")) {
    # Need to create and start the action
    &init::enable_at_boot(
          "lookup-domain",
          "Daemon for quickly looking up Virtualmin servers from procmail",
          "$helper lookup-domain-daemon",
          "@{[&has_command('kill')]} `cat $pidfile`",
          undef,
          { 'fork' => 1, 'pidfie' => $pidfile });
    &init::start_action("lookup-domain");
    }
else {
    # Stop and re-start the daemon to pick up new version
    my ($ok) = &init::stop_action("lookup-domain");
    if ($ok) {
        sleep(5);    # Wait for port to free up
        &init::start_action("lookup-domain");
        }
    }
}

# delete_lookup_domain_daemon()
# Turn off the background domain-lookup daemon
sub delete_lookup_domain_daemon
{
&foreign_require("init");
&init::disable_at_boot("lookup-domain");
local $pidfile = "$ENV{'WEBMIN_VAR'}/lookup-domain-daemon.pid";
&init::stop_action("lookup-domain");
local $pid = &check_pid_file($pidfile);
if ($pid) {
    kill('TERM', $pid);
    }
}

# check_lookup_domain_daemon()
# Returns 1 if the domain lookup daemon is running, 0 if not
sub check_lookup_domain_daemon
{
&foreign_require("init");
return &init::action_status("lookup-domain") == 2 ? 1 : 0;
}

# spam_alias_name(&domain)
# Returns the full email address for the spam alias, like spamtrap@foo.com
sub spam_alias_name
{
local ($d) = @_;
return 'spamtrap'.'@'.$d->{'dom'};
}

# ham_alias_name(&domain)
# Returns the full email address for the ham alias, like hamtrap@foo.com
sub ham_alias_name
{
local ($d) = @_;
return 'hamtrap'.'@'.$d->{'dom'};
}

# spam_alias_file(&domain)
# Returns the file in which spam is stored for some domain
sub spam_alias_file
{
local ($d) = @_;
return $spam_alias_dir."/".$d->{'id'};
}

# ham_alias_file(&domain)
# Returns the file in which ham is stored for some domain
sub ham_alias_file
{
local ($d) = @_;
return $ham_alias_dir."/".$d->{'id'};
}

# get_spamtrap_aliases(&domain)
# Returns 1 if spamtrap and hamtrap aliases exist, 0 if not, -1 if cannot be
# created due to clashes. If called in an array context, returns the spam and
# ham aliases too.
sub get_spamtrap_aliases
{
local ($d) = @_;
local (%got, $clash);
foreach my $a (&list_domain_aliases($d)) {
    if ($a->{'from'} eq &spam_alias_name($d)) {
        # Spam alias .. make sure it goes to the right file
        if (@{$a->{'to'}} == 1 &&
            $a->{'to'}->[0] &spam_alias_file($d)) {
            $got{'spam'} = $a;
            }
        else {
            $clash = 1;
            }
        }
    elsif ($a->{'from'} eq &ham_alias_name($d)) {
        # Ham alias
        if (@{$a->{'to'}} == 1 &&
            $a->{'to'}->[0] &ham_alias_file($d)) {
            $got{'ham'} = $a;
            }
        else {
            $clash = 1;
            }
        }
    }
local $rv = $clash ? -1 : $got{'spam'} && $got{'ham'} ? 1 : 0;
return wantarray ? ($rv, $got{'spam'}, $got{'ham'}) : $rv;
}

# setup_spamtrap_aliases(&domain)
# Create aliases in a domain for spamtrap and hamtrap, which deliver to files
# under /var/webmin/spamtrap/$ID. Returns undef on success, or an error message
# on failure.
sub setup_spamtrap_aliases
{
local ($d) = @_;

# Check for aliases already
local ($ok, $spama, $hama) = &get_spamtrap_aliases($d);
if ($ok == 1) {
    return $text{'spamtrap_already'};
    }
elsif ($ok < 0) {
    return &text('spamtrap_clash',
             join(", ", $spama ? ( $spama->{'from'} ) : ( ),
                    $hama ? ( $hama->{'from'} ) : ( )));
    }

# Create dirs and empty files
&setup_spamtrap_directories($d);

# Create aliases
local $spamfile = &spam_alias_file($d);
local $hamfile = &ham_alias_file($d);
$spama = { 'from' => &spam_alias_name($d), 'to' => [ $spamfile ] };
$hama = { 'from' => &ham_alias_name($d), 'to' => [ $hamfile ] };
&create_virtuser($spama);
&create_virtuser($hama);

# Setup cron job
&setup_spamtrap_cron();
return undef;
}

# setup_spamtrap_directories(&domain)
# Create the spamtrap directories and mail files for a domain
sub setup_spamtrap_directories
{
local ($d) = @_;
&make_dir($trap_base_dir, 0755) if (!-d $trap_base_dir);
&make_dir($spam_alias_dir, 01777) if (!-d $spam_alias_dir);
&make_dir($ham_alias_dir, 01777) if (!-d $ham_alias_dir);
local $spamfile = &spam_alias_file($d);
local $hamfile = &ham_alias_file($d);
foreach my $f ($spamfile, $hamfile) {
    if (!-r $f) {
        &open_tempfile(SPAMFILE, ">$f", 0, 1);
        &close_tempfile(SPAMFILE);
        &set_ownership_permissions(undef, undef, 0666, $f);
        }
    }
}

# setup_spamtrap_cron()
# Create the cron job that blacklists trapped spam, if needed
sub setup_spamtrap_cron
{
&foreign_require("cron");
local $job = &find_cron_script($spamtrap_cron_cmd);
if (!$job) {
    $job = { 'user' => 'root',
         'command' => $spamtrap_cron_cmd,
         'active' => 1,
         'mins' => int(rand()*60),
         'hours' => '*',
                 'days' => '*',
                 'weekdays' => '*',
                 'months' => '*' };
    &setup_cron_script($job);
    }
}

# delete_spamtrap_aliases(&domain)
# Remove the spamtrap and hamtrap aliases for a domain, and any mail files
sub delete_spamtrap_aliases
{
local ($d) = @_;

# Get the aliases, and remove them
local ($ok, $spama, $hama) = &get_spamtrap_aliases($d);
if ($ok == 1) {
    &delete_virtuser($spama);
    &delete_virtuser($hama);
    }
else {
    return $text{'spamtrap_noaliases'};
    }

# Delete the mail files
local $spamfile = &spam_alias_file($d);
local $hamfile = &ham_alias_file($d);
&unlink_file($spamfile);
&unlink_file($hamfile);

return undef;
}

# check_spamd_status()
# Checks if spamd is configured and running on this system. Returns 0 if not,
# 1 if yes, or -1 if we can't tell (due to a non-supported OS).
sub check_spamd_status
{
local @pids = grep { $_ != $$ } &find_byname("spamd");
if (@pids) {
    # Running already, so we assume everything is cool
    return 1;
    }
local $spamd = &has_command("spamd") ||
           &has_command("/opt/csw/bin/spamd");
if (!$spamd) {
    # Not installed
    return -1;
    }
return 0;    # Can be started
}

# enable_spamd()
# Do everything needed to configure and start spamd. May print stuff with the
# standard functions.
sub enable_spamd
{
local $st = &check_spamd_status();
return 1 if ($st == 1 || $st == -1);

# Find init script
&foreign_require("init");
local $init;
foreach my $i ("spamassassin", "spamd", "sa-spamd") {
    if (&init::action_status($i)) {
        $init = $i;
        last;
        }
    }
$init ||= "virtualmin-spamassassin";    # Fall back to ours

# Create or enable init script
&$first_print(&text('spamd_boot'));
local $spamd = &has_command("spamd") ||
           &has_command("/opt/csw/bin/spamd");
&init::enable_at_boot($init, "Start SpamAssassin filter server",
    "spamd --pidfile=/var/run/spamd.pid -d",
    "kill `cat /var/run/spamd.pid`");

# Update OS-specific config files
local $dfile = "/etc/default/spamassassin";
if (-r $dfile) {
    # Init script won't start on Debian without this flag
    local %defs;
    &lock_file($dfile);
    &read_env_file($dfile, \%defs);
    if (!$defs{'ENABLED'} || !$defs{'CRON'}) {
        $defs{'ENABLED'} = 1;
        $defs{'CRON'} = 1;
        &write_env_file($dfile, \%defs);
        }
    &unlock_file($dfile);
    }
if ($init::init_mode eq "rc") {
    # On FreeBSD, the boot script name differs from the rc.conf entry
    &init::enable_rc_script("spamd_enable");
    }
if (&init::action_status("spamassassin-maintenance.timer")) {
    &init::enable_at_boot("spamassassin-maintenance.timer");
    }
&$second_print($text{'setup_done'});

# Start now
&$first_print($text{'spamd_start'});
local ($ok, $out) = &init::start_action($init);
if ($ok) {
    &$second_print($text{'setup_done'});
    }
else {
    &$second_print(&text('spamd_startfailed',
                 "<tt>".&html_escape($out)."</tt>"));
    return 0;
    }

return 1;
}

# disable_spamd()
# Turn off spamd now and at boot time
sub disable_spamd
{
local $st = &check_spamd_status();
return 1 if ($st == 0 || $st == -1);

# Find init script
&$first_print(&text('spamd_unboot'));
&foreign_require("init");
local $init;
foreach my $i ("spamassassin", "spamd", "sa-spamd", "virtualmin-spamassassin") {
    if (&init::action_status($i)) {
        $init = $i;
        last;
        }
    }
if (!$init) {
    &$second_print($text{'spamd_unbootact'});
    return 0;
    }
&init::disable_at_boot($init);
if ($init::init_mode eq "rc") {
    # On FreeBSD, the boot script name differs from the rc.conf entry
    &init::disable_rc_script("spamd_enable");
    }
if (&init::action_status("spamassassin-maintenance.timer")) {
    &init::disable_at_boot("spamassassin-maintenance.timer");
    }
&$second_print($text{'setup_done'});

# Stop spamd process
&$first_print($text{'spamd_stop'});
local ($ok, $out) = &init::stop_action($init);
if (!$ok) {
    &kill_byname_logged('spamd', 'TERM');
    }
&$second_print($text{'setup_done'});

return 1;
}

# setup_quota_full_bounce()
# Update /etc/procmailrc with rules after the VIRTUALMIN line to bounce mail
# if quota if full.
sub setup_quota_full_bounce
{
local @recipes = &procmail::get_procmailrc();

# Find existing VIRTUALMIN= and EXITCODE= recipes
my ($virt, $exitcode, $virtafter);
foreach my $r (@recipes) {
    if ($r->{'type'} eq '=' && $r->{'action'} =~ /^VIRTUALMIN=/) {
        $virt = $r;
        }
    elsif ($r->{'name'} eq 'EXITCODE') {
        $exitcode = $r;
        }
    elsif ($virt && !$virtafter && $r->{'index'} == $virt->{'index'}+1) {
        $virtafter = $r;
        }
    }
return 0 if (!$virt || $exitcode);

# Create new recipe objects
local $var1 = { 'name' => 'EXITCODE',
        'value' => '$?' };
local $testcmd = &has_command("test") || "test";
local $cmd;
if ($gconfig{'os_type'} eq 'solaris') {
    $cmd = "sh -c \"$testcmd '\$EXITCODE' = '73'\"";
    }
else {
    $cmd = "$testcmd \"\$EXITCODE\" = \"73\"";
    }
local $var2 = { 'flags' => [ ],
        'conds' => [ [ "?", $cmd ] ],
            'action' => '/dev/null' };
local $var3 = { 'name' => 'EXITCODE',
                'value' => '0' };

# Add after the VIRTUALMIN= line
&procmail::create_recipe_before($var1, $virtafter);
&procmail::create_recipe_before($var2, $virtafter);
&procmail::create_recipe_before($var3, $virtafter);
}

# startstop_spam([&typestatus])
# Returns a hash containing the current status of the spamd server and short
# and long descriptions for the action to switch statuses
sub startstop_spam
{
local ($scanner, $host) = &get_global_spam_client();
if ($scanner ne "spamc" || $host) {
    # Spamd isn't being used
    return ( );
    }
local @pids = grep { $_ != $$ } &find_byname("spamd");
if (@pids) {
    return ( { 'status' => 1,
           'name' => $text{'index_spamname'},
           'desc' => $text{'index_spamstop'},
           'restartdesc' => $text{'index_spamrestart'},
           'longdesc' => $text{'index_spamstopdesc'} } );
    }
else {
    return ( { 'status' => 0,
           'name' => $text{'index_spamname'},
           'desc' => $text{'index_spamstart'},
           'longdesc' => $text{'index_spamstartdesc'} } );
    }
}

# start_service_spam()
# Attempts to start the spamd server, returning undef on success or any error
# message on failure.
sub start_service_spam
{
&push_all_print();
&set_all_null_print();
local $rv = &enable_spamd();
&pop_all_print();
return $rv ? undef : $text{'spamd_estartmsg'};
}

# stop_service_spam()
# Attempts to stop the spamd server, returning undef on success or any error
# message on failure.
sub stop_service_spam
{
&foreign_require("init");
foreach my $init ("spamassassin", "spamd", "sa-spamd",
              "virtualmin-spamassassin") {
    if (&init::action_status($init)) {
        local ($ok, $out) = &init::stop_action($init);
        return $ok ? undef : "<tt>".&html_escape($out)."</tt>";
        }
    }
local @pids = grep { $_ != $$ } &find_byname("spamd");
if (@pids) {
    if (&kill_logged('TERM', @pids)) {
        return undef;
        }
    return &text('spamd_ekillmsg', $!);
    }
else {
    return $text{'spamd_estopmsg'};
    }
}

# obtain_lock_spam(&domain)
# Lock a domain's spamassassin config file and procmail file
sub obtain_lock_spam
{
local ($d) = @_;
return if (!$config{'spam'});
&obtain_lock_anything($d);

if ($d) {
    # Lock domain's files
    if ($main::got_lock_spam_dom{$d->{'id'}} == 0) {
        &require_spam();
        &lock_file("$procmail_spam_dir/$d->{'id'}");
        &lock_file("$spam_config_dir/$d->{'id'}");
        &lock_file("$spam_config_dir/$d->{'id'}/virtualmin.cf");
        }
    $main::got_lock_spam_dom{$d->{'id'}}++;
    }

# Lock master procmail config file
if ($main::get_lock_spam == 0) {
    &require_spam();
    &lock_file($procmail::procmailrc);
    &lock_file($spamclear_file);
    }
$main::get_lock_spam++;
}

# release_lock_spam(&domain)
# Un-lock a domain's spamassassin config file and procmail file
sub release_lock_spam
{
local ($d) = @_;
return if (!$config{'spam'});

if ($d) {
    # Unlock domain's files
    if ($main::got_lock_spam_dom{$d->{'id'}} == 1) {
        &require_spam();
        &unlock_file("$procmail_spam_dir/$d->{'id'}");
        &unlock_file("$spam_config_dir/$d->{'id'}");
        &unlock_file("$spam_config_dir/$d->{'id'}/virtualmin.cf");
        }
    $main::got_lock_spam_dom{$d->{'id'}}--
        if ($main::got_lock_spam_dom{$d->{'id'}});
    }

# Unlock only master procmail config file
if ($main::get_lock_spam == 1) {
    &require_spam();
    &unlock_file($procmail::procmailrc);
    &unlock_file($spamclear_file);
    }
$main::got_lock_spam-- if ($main::got_lock_spam);
&release_lock_anything($d);
}

# obtain_lock_spam_all()
# Lock the spamassassin and procmail config files for all domains
sub obtain_lock_spam_all
{
foreach my $d (grep { $_->{'spam'} } &list_domains()) {
    &obtain_lock_spam($d);
    }
}

# release_lock_spam_all()
# Un-lock the spamassassin and procmail config files for all domains
sub release_lock_spam_all
{
foreach my $d (grep { $_->{'spam'} } &list_domains()) {
    &release_lock_spam($d);
    }
}

$done_feature_script{'spam'} = 1;

1;


:: Command execute ::

Enter:
 
Select:
 

:: Search ::
  - regexp 

:: Upload ::
 
[ ok ]

:: Make Dir ::
 
[ ok ]
:: Make File ::
 
[ ok ]

:: Go Dir ::
 
:: Go File ::
 

--[ c99shell v. 2.5 [PHP 8 Update] [24.05.2025] | Generation time: 0.0062 ]--