#!/usr/bin/perl
#***********************************************************************
#
# mdf-report
#
# mimedefang-filter reporter
#
# $Id: mdf-report.pl,v 1.22 2009/04/03 18:35:59 jonas Exp $
#
# This program may be distributed under the terms of the GNU General
# Public License, Version 2, or (at your option) any later version.
#
#***********************************************************************
use strict;
use MIME::Head;
use MIME::Words;
use Encode;
use HTML::Entities;
use CGI;
use CGI::Pretty;
use MIME::Lite;
use Email::MessageID;
use Net::SMTP;
use Fortune;
use CSS::Tiny;
use DBI;
use Date::Format;
use Text::CSV_XS;
#***********************************************************************
# Config.
#***********************************************************************
my $debug = 0;
my $quiet = 0;
my $sqldb;
my $sqldbd = '?';
my $mailto = '';
my $forget = 0;
my $FilterRevision = '$Revision: 1.22 $';
my $FilterUtilVers = '?';
if ('$Id: mdf-report.pl,v 1.22 2009/04/03 18:35:59 jonas Exp $' =~ /^\S+?:\s(\S+?),v\s([.\d]+)\s/) { $FilterUtilVers = "$1 $2"; }
my %Features;
$Features{'Path:QUARANTINEDIR'} = '/var/spool/MD-Quarantine';
# Add setting to config parser
my %cfgcfg = ();
$cfgcfg{'@i'}{i} = 0;
$cfgcfg{'@i'}{c} = 0;
sub add_cfg_cfg {
my ($c,$v,$d,$t,$f) = @_;
$d = '' unless (defined($d));
$t = 's' unless ($t);
$f = '' unless ($f);
$c =~ s/[-_]+//g;
$c = lc($c);
$$v = $d;
$cfgcfg{'@i'}{c} ++;
$cfgcfg{$c}{v} = $v;
$cfgcfg{$c}{t} = lc($t);
$cfgcfg{$c}{f} = lc($f);
$cfgcfg{$c}{i} = $cfgcfg{'@i'}{c};
$cfgcfg{$c}{x} = 0;
}
use vars qw($AdminAddress $MyFilterHostName);
add_cfg_cfg('AdminAddress',\$AdminAddress,'postmaster','a');
add_cfg_cfg('MyFilterHostName',\$MyFilterHostName,'host.domain.tld','s');
# md: $DaemonAddress
use vars qw($DaemonAddress $MailResultMailer);
add_cfg_cfg('DaemonAddress',\$DaemonAddress,'mailer-daemon','a');
add_cfg_cfg('MailResultMailer',\$MailResultMailer,'127.0.0.1:25','s');
use vars qw ($hilo_entries);
add_cfg_cfg('hilo_entries',\$hilo_entries,0,'i');
use vars qw($database_spec $database_user $database_pass);
add_cfg_cfg('database_spec',\$database_spec,'dbi:SQLite:dbname=%s/filterdata.db','p');
add_cfg_cfg('database_user',\$database_user,'','s');
add_cfg_cfg('database_pass',\$database_pass,'','s');
use vars qw($silly_fortunes $silly_oneliners);
add_cfg_cfg('silly_fortunes',\$silly_fortunes,'');
add_cfg_cfg('silly_oneliners',\$silly_oneliners,'');
use vars qw($quarantine_url);
add_cfg_cfg('quarantine_url',\$quarantine_url,'','s');
# Get a file path name
sub get_file_path_name {
my $f = shift;
return $f if ($f =~ /[\/\\]/);
foreach my $d (('/usr/local/etc/mimedefang/filter','/etc/mimedefang/filter',
'/usr/local/etc/mimedefang','/etc/mimedefang',
'/usr/local/etc/mail','/etc/mail', )) {
if ($d =~ /mimedefang/) {
return "$d/$f" if (-f "$d/$f");
return "$d/mimedefang-$f" if (-f "$d/mimedefang-$f");
} else {
return "$d/mimedefang-$f" if (-f "$d/mimedefang-$f");
return "$d/$f" if (-f "$d/$f");
}
}
return '';
}
# read the configuration file
sub read_cfg_params {
my @pp = @ARGV;
@ARGV = ();
foreach my $p (@pp) {
$p =~ s/^-+//;
if ($p =~ /^d(ata)?b(ase)?[-_]?s(pec|pecification)?[:=]\s*(.*?)\s*$/i) {
$database_spec = $4;
} elsif ($p =~ /^d(ata)?b(ase)?[-_]?u(sr|ser)?[:=]\s*(.*?)\s*$/i) {
$database_user = $4;
} elsif ($p =~ /^d(ata)?b(ase)?[-_]?p(ass|assword|wd)?[:=]\s*(.*?)\s*$/i) {
$database_pass = $4;
} else {
push @ARGV, $p;
}
}
}
sub read_cfg_cfg {
my $cfgfn = get_file_path_name('filter.conf');
if ($cfgfn) {
die('No filter config!') unless ($cfgfn);
$Features{'Path:CONFDIR'} = $cfgfn; $Features{'Path:CONFDIR'} =~ s/(\/filter)?\/[^\/]*$//;
die('Cannot read filter config!') unless (open(F,'<',$cfgfn));
#md_syslog('info',"Filter config: $cfgfn");
while (my $l = <F>) {
$l =~ s/[\r\n]+//gs;
next unless ($l);
next if ($l =~ /^\s*[#;]/);
if ($l =~ /^\s*(\S+)\s*?[\s:=]\s*(\S.*?)\s*$/) {
my $c = lc($1);
my $v = $2;
$c =~ s/[-_]+//g;
next unless ($c);
next if ($c =~ /^\@/);
if (defined($cfgcfg{$c})) {
if ($cfgcfg{$c}{x} && $cfgcfg{$c}{t} =~ /^m/i) {
${$cfgcfg{$c}{v}} .= ';' if (${$cfgcfg{$c}{v}} ne '');
${$cfgcfg{$c}{v}} .= $v;
} else {
${$cfgcfg{$c}{v}} = $v;
$cfgcfg{$c}{x} = 1;
}
}
}
}
close(F);
#return;
} else {
$Features{'Path:CONFDIR'} = '.';
}
read_cfg_params();
$Features{'Path:SPOOLDIR'} = '.' unless ($Features{'Path:SPOOLDIR'} && (-d $Features{'Path:SPOOLDIR'}));
$Features{'Path:QUARANTINEDIR'} = '.' unless ($Features{'Path:QUARANTINEDIR'} && (-d $Features{'Path:QUARANTINEDIR'}));
my @ck = sort { $cfgcfg{$a}{i} <=> $cfgcfg{$b}{i} } keys %cfgcfg;
foreach my $c (@ck) {
next if ($c =~ /^\@/);
${$cfgcfg{$c}{v}} = ${$cfgcfg{$cfgcfg{$c}{f}}{v}} if ($cfgcfg{$c}{f} && !${$cfgcfg{$c}{v}});
}
foreach my $c (@ck) {
next if ($c =~ /^\@/);
if ($cfgcfg{$c}{t} eq 'l') {
${$cfgcfg{$c}{v}} = '' unless (${$cfgcfg{$c}{v}});
${$cfgcfg{$c}{v}} =~ s/\s//g;
${$cfgcfg{$c}{v}} =~ s/\./\\./g if (${$cfgcfg{$c}{v}} !~ /\\\./);
${$cfgcfg{$c}{v}} =~ s/\@/\\\@/g if (${$cfgcfg{$c}{v}} !~ /\\\@/);
${$cfgcfg{$c}{v}} =~ s/\./\\./g if (${$cfgcfg{$c}{v}} !~ /\\\./);
${$cfgcfg{$c}{v}} =~ s/,/\|/g if (${$cfgcfg{$c}{v}} !~ /[\(\)\|\{\}]/ && ${$cfgcfg{$c}{v}} =~ /\,/);
${$cfgcfg{$c}{v}} = sprintf('(%s)',${$cfgcfg{$c}{v}}) if (${$cfgcfg{$c}{v}} !~ /[\(\)]/ && ${$cfgcfg{$c}{v}} =~ /\|/);
} elsif ($cfgcfg{$c}{t} eq 'a') {
${$cfgcfg{$c}{v}} = '' unless (${$cfgcfg{$c}{v}});
next unless (${$cfgcfg{$c}{v}});
${$cfgcfg{$c}{v}} .= '@'.${$cfgcfg{'myfilterhostname'}{v}} if (${$cfgcfg{$c}{v}} =~ /^[^@]+$/);
} elsif ($cfgcfg{$c}{t} eq 't') {
${$cfgcfg{$c}{v}} = '' unless (${$cfgcfg{$c}{v}});
${$cfgcfg{$c}{v}} = sprintf(${$cfgcfg{$c}{v}},${$cfgcfg{'myfilterhostname'}{v}});
${$cfgcfg{$c}{v}} =~ s/[\r\n]*$/\n\n/s;
} elsif ($cfgcfg{$c}{t} eq 'b') {
${$cfgcfg{$c}{v}} = 0 unless (${$cfgcfg{$c}{v}});
${$cfgcfg{$c}{v}} = 0 if (${$cfgcfg{$c}{v}} =~ /^\s*(false|no?|off|0+)\s*$/);
${$cfgcfg{$c}{v}} = 1 if (${$cfgcfg{$c}{v}});
} elsif ($cfgcfg{$c}{t} eq 'i') {
${$cfgcfg{$c}{v}} = 0 unless (${$cfgcfg{$c}{v}});
${$cfgcfg{$c}{v}} = eval(${$cfgcfg{$c}{v}});
} elsif ($cfgcfg{$c}{t} eq 'ps') {
${$cfgcfg{$c}{v}} = $Features{'Path:SPOOLDIR'} unless (${$cfgcfg{$c}{v}});
${$cfgcfg{$c}{v}} = sprintf('%s/%s',$Features{'Path:SPOOLDIR'},${$cfgcfg{$c}{v}}) if (${$cfgcfg{$c}{v}} !~ /[\/\\]/);
} elsif ($cfgcfg{$c}{t} eq 'pc') {
${$cfgcfg{$c}{v}} = $Features{'Path:CONFDIR'} unless (${$cfgcfg{$c}{v}});
${$cfgcfg{$c}{v}} = sprintf('%s/%s',$Features{'Path:CONFDIR'},${$cfgcfg{$c}{v}}) if (${$cfgcfg{$c}{v}} !~ /[\/\\]/);
} elsif ($cfgcfg{$c}{t} eq 'p') {
${$cfgcfg{$c}{v}} = $Features{'Path:CONFDIR'} unless (${$cfgcfg{$c}{v}});
${$cfgcfg{$c}{v}} = sprintf(${$cfgcfg{$c}{v}},$Features{'Path:SPOOLDIR'});
} elsif ($cfgcfg{$c}{t} eq 'mpsm') {
my @pil = split(/\s*;\s*/,${$cfgcfg{$c}{v}});
for (my $i=0;$i<@pil;$i++) {
my ($fn,$ft,$fo,$x) = split(/\s*,\s*/,$pil[$i]);
$fn = sprintf('%s/%s',${$cfgcfg{'sendmailconfig'}{v}},$fn) if ($fn !~ /[\/\\]/);
$fo = '?' unless ($fo);
unless ($ft) {
if ($fn =~ /table/i) {
$ft = 'table'
} else {
$ft = 'list';
}
}
$pil[$i] = join(',',$fn,lc($ft),lc($fo));
}
${$cfgcfg{$c}{v}} = join(';',@pil);
} elsif ($cfgcfg{$c}{t} eq 'mbs') {
${$cfgcfg{$c}{v}} = 0 unless (${$cfgcfg{$c}{v}});
${$cfgcfg{$c}{v}} = 0 if (${$cfgcfg{$c}{v}} =~ /^\s*(false|no|n|off|0+)\s*$/);
${$cfgcfg{$c}{v}} = 1 if (${$cfgcfg{$c}{v}} =~ /^\s*(true|yes|y|on|\d*[1-9]\d*)\s*$/);
} else {
${$cfgcfg{$c}{v}} = '' unless (${$cfgcfg{$c}{v}});
}
}
if ($database_spec =~ /sqlite/i) {
$sqldbd = 'L';
} elsif ($database_spec =~ /mysql/i) {
$sqldbd = 'M';
}
}
#***********************************************************************
# SQL.
#***********************************************************************
my $sqlr;
sub sql_disconnect {
$sqldb->disconnect() if ($sqldb);
$sqldb = undef;
}
sub sql_connect {
return 1 if ($sqldb);
$sqldb = DBI->connect($database_spec,$database_user,$database_pass,{RaiseError=>0});
return 0 unless ($sqldb);
return 1;
}
sub sql_translate {
my ($cmd) = @_;
$cmd =~ s/ LIMIT -1$//;
return $cmd;
}
sub sql_exec_commands {
for (my $i=0;$i<@_;$i++) {
my @pars = @{$_[$i]};
my $cmd = sql_translate(shift @pars);
return 0 unless ($cmd);
my $st = $sqldb->prepare_cached($cmd);
return 0 unless ($st);
my $res = $st->execute(@pars);
$st->finish;
return 0 unless ($res);
}
return 1;
}
sub sql_execute_multi {
return 0 unless (sql_connect());
my $ok = sql_exec_commands(@_);
return $ok if ($sqldb->{AutoCommit});
return $sqldb->commit if ($ok);
#debug_log(-1,'sql_exec: rollback');
$sqldb->rollback;
return 0;
}
sub sql_execute {
return sql_execute_multi(\@_);
}
sub sql_query {
my $cmd = sql_translate(shift);
return undef unless (sql_connect() && $cmd);
my $st = $sqldb->prepare($cmd);
return undef unless ($st);
$st->execute(@_);
return $st;
}
sub sql_select_one_row {
my $st = sql_query(@_);
return undef unless ($st);
my @res = $st->fetchrow_array;
$st->finish;
return \@res;
}
sub sql_select_one {
my $res = sql_select_one_row(@_);
return undef unless ($res && @{$res});
return $res->[0];
}
#***********************************************************************
# Code.
#***********************************************************************
sub fwords {
my $ws = join(' ',@_);
$ws =~ s/_/ /g;
$ws =~ s/\b([a-z])/uc($1)/ge;
return $ws;
}
# create file
sub create_file {
my ($fn) = @_;
return 1 if (-f $fn);
return 0 unless (open(NF,'>>',$fn));
close(NF);
return 1;
}
my @qdir_reported = ();
my %qdir_reported = ();
sub mark_qdir_reported {
return unless (@qdir_reported);
unless ($forget) {
print "\tqdir\n" unless ($quiet);
return if ($debug);
for (my $i=0;$i<@qdir_reported;$i++) {
next if $qdir_reported{$qdir_reported[$i]};
$qdir_reported{$qdir_reported[$i]} = create_file($qdir_reported[$i]);
}
}
@qdir_reported = ();
}
my @logs_reported = ();
my %logs_reported = ();
sub mark_logs_reported {
return unless (@logs_reported);
unless ($forget) {
print "\tlogs\n" unless ($quiet);
return if ($debug);
my @dels = ([]);
for (my $i=0;$i<@logs_reported;$i++) {
next if $qdir_reported{$logs_reported[$i]};
push @dels, [] if (@{$dels[$#dels]} > 512);
push @{$dels[$#dels]}, $logs_reported[$i];
}
foreach my $del (@dels) {
next unless (@{$del});
if (sql_execute('DELETE FROM logs WHERE logs_id IN (?)',join(',',@{$del}))) {
for (my $i=0;$i<@{$del};$i++) {
logs_reported{$del->[$i]} = 1;
}
}
}
}
@logs_reported = ();
}
sub mark_reported {
return unless (@qdir_reported || @logs_reported);
if ($forget) {
@qdir_reported = ();
@logs_reported = ();
return;
}
print "Marking\n";
mark_qdir_reported();
mark_logs_reported();
}
# Get a fortune cookie...
sub get_fortune_cookie {
my ($fn,$l1,$d) = @_;
$d = '' unless ($d);
return $d unless ($fn);
return $d unless (-f $fn);
my $fo = 0;
my $fh;
eval { $fh = new Fortune($fn); };
return $d unless ($fh);
if (-f "$fn.dat") {
eval { $fh->read_header(); $fo = 1; };
} else {
eval { $fh->compute_header(); $fo = 1; };
}
if ($fo) {
my $c;
my $t = 0;
do {
$t ++;
$c = $fh->get_random_fortune;
next unless ($c);
$c =~ s/^[\r\n\s]+//s;
if ($l1) {
$c =~ s/[\r\n\s]+$//s;
next if ($c =~ /[\r\n]/);
$c =~ s/\s+/ /gs;
}
next if ($c =~ /[^\r\n\s\x20-\x7E]/);
} while ($t < 10 && !$c);
$d = $c if ($c);
}
$fh->close_file;
return $d;
}
# Get a fortune
sub get_fortune {
return get_fortune_cookie($silly_fortunes);
}
# Get a one-liner
sub get_oneliner {
return get_fortune_cookie($silly_oneliners,1);
}
sub read_file {
my ($fn,$one) = @_;
my @r = ();
if (open(F,'<',$fn)) {
while (my $l = <F>) {
$l =~ s/[\r\n]+$//;
push @r, $l;
last if ($one);
}
close(F);
}
return @r;
}
sub read_lines {
my @r = read_file(@_);
return undef unless (@r);
return \@r;
}
sub read_line {
my @r = read_file(@_,1);
return undef unless (@r);
$r[0] =~ s/[\r\n]+$//;
return $r[0];
}
sub read_text {
my @r = read_file(@_);
return undef unless (@r);
return join("\n",@r,'');
}
sub decode_header {
my $r = join('',@_);
$r =~ s/[\r\n]+$//;
if ($r =~ /=\?(\S+)\?[QBqb]\?.*\?=/) {
my $x = $r; $r = '';
while ($x =~ /^(.*?)(=\?[^\?]*\?[QqBb]\?)(.*?)(\?=)(.*)$/) {
$r .= "$1$2";
my $d = $3; my $e = $4;
$x = $5;
$d =~ s/\?/=3F/gs;
$r .= "$d$e";
}
my @vl = MIME::Words::decode_mimewords($r);
if (@vl) {
$r = '';
foreach my $v (@vl) {
if ($v->[1]) {
$v->[1] =~ s/^unicode-\d-\d-(utf-\d)$/$1/;
my $dd;
eval { $dd = decode($v->[1],$v->[0]); };
unless (defined($dd)) {
eval { $dd = decode('iso-8859-1',$v->[0]); };
unless (defined($dd)) {
$dd = $v->[0];
$dd =~ s/=\?\S+\?[QBqb]\?//gs;
$dd =~ s/\?=//gs;
$dd =~ s/=[A-Fa-f0-9][A-Fa-f0-9]/?/gs;
}
}
$r .= $dd;
} else {
$r .= $v->[0];
}
}
}
}
$r =~ s/[\s\r\n\t]+/ /gs;
$r =~ s/^\s+//;
$r =~ s/\s+$//;
return $r;
}
#***********************************************************************
# Reports.
#***********************************************************************
my $cgi = $debug ? new CGI : new CGI::Pretty;
sub css {
my $css = CSS::Tiny->new();
$css->{'body'} = {
'background-color' => 'rgb(220,255,220)',
'color' => 'black',
'margin' => '1%',
'font-family' => 'arial, helvetica, sans-serif',
};
$css->{'h1'} = {
'text-align' => 'center',
'font-weight' => 'bold',
'margin-top' => '0em',
'margin-bottom' => '1em',
'margin-left' => '0',
'clear' => 'both',
};
$css->{'h2'} = {
'font-weight' => 'bold',
'margin-top' => '1em',
'margin-bottom' => '0.5em',
'margin-left' => '0',
'clear' => 'both',
};
$css->{'tr'} = {
'vertical-align' => 'baseline'
};
$css->{'table.top tr'} = {
'vertical-align' => 'top'
};
$css->{'tr.even'} = {
'background-color' => 'rgb(220,220,255)'
};
$css->{'tr.odd'} = {
'background-color' => 'rgb(255,220,220)'
};
$css->{'th'} = {
'background-color' => 'rgb(0,50,0)',
'color' => 'rgb(220,255,220)',
};
$css->{'td'} = {
'border-style' => 'none',
};
my $r = $css->write_string;
$r =~ s/\{\s+/{/gs;
$r =~ s/\s+\}/}/gs;
$r =~ s/\s+\{/{/gs;
$r =~ s/: /:/gs;
return $r;
}
sub report_mail {
my $what = shift;
my $addr = shift;
my $subj = shift;
my $html = $cgi->start_html(-title=>$subj,-style=>{-verbatim=>css}).join(' ',@_).$cgi->end_html;
unless ($debug) {
#$html =~ s/>[\s\r\n]+</></gs;
$html =~ s/>[\s\r\n]+/>/gs;
$html =~ s/[\s\r\n]+</</gs;
$html =~ s/[\s\r\n]+/ /gs;
$html =~ s/^(<!DOCTYPE [^>]*>)/$1\n/;
$html =~ s/a>\|<a/a> | <a/gs;
}
my $mt = $addr;
$mt = $mailto unless ($mt);
$mt = $AdminAddress unless ($mt);
print "\tcreate mail\n" unless ($quiet);
my $eml = MIME::Lite->new(
'X-MDF-Report:' => "($FilterUtilVers) <$what>",
'Message-ID:' => Email::MessageID->new($MyFilterHostName)->format,
From => $DaemonAddress,
To => $mt,
Subject => $subj,
Type => 'multipart/mixed',
);
$eml->attach(
Type => 'text/plain',
Encoding => '7bit',
Disposition => 'inline',
Data => get_oneliner,
);
$eml->attach(
Type => 'text/html',
Encoding => 'quoted-printable',
Disposition => 'inline',
Data => $html,
);
$eml->attach(
Type => 'text/plain',
Encoding => '7bit',
Disposition => 'inline',
Data => get_fortune,
);
if ($debug) {
print $eml->as_string;
return 0;
}
print "\tsend mail ($mt)\n" unless ($quiet);
my $smtp = Net::SMTP->new($MailResultMailer);
return 0 unless ($smtp);
my $sok = ($smtp->mail($DaemonAddress) && $smtp->recipient($mt) && $smtp->data($eml->as_string));
$smtp->quit;
return $sok;
}
my %qdir_ids = ('?'=>0);
sub list_quarantine {
my ($force,$all,$list) = @_;
return 1 if (!$force && $qdir_ids{'?'});
print "\tlist qdirs\n" unless ($quiet);
return 0 unless (opendir('D',$Features{'Path:QUARANTINEDIR'}));
my $now = time();
while (my $f = readdir(D)) {
next if ($f =~ /^\./);
my $pn = $Features{'Path:QUARANTINEDIR'}.'/'.$f;
if (-d $pn) {
my $qid = read_line("$pn/SENDMAIL-QID");
$qdir_ids{$qid} = $pn if ($qid && $qid ne '?');
next unless ($list);
next if ($now - (stat(_))[9] < 12*60*60);
if (-f "$pn/reported") {
$qdir_reported{"$pn/reported"} = 1;
next unless ($all);
}
push @{$list}, {fn=>$f,pn=>$pn,qid=>$qid?encode_entities($qid):''};
}
}
closedir(D);
$qdir_ids{'?'} ++;
return 1;
}
my %qdir_hds = ();
sub read_header {
my ($qid,$qdir) = @_;
$qid = '' if (length($qid) < 2);
$qdir = '' if (length($qdir) < 2);
return undef unless ($qid || $qdir);
return $qdir_hds{$qid}{''} if ($qid && $qdir_hds{$qid}{''});
$qdir = $qdir_ids{$qid} unless ($qdir);
return undef unless ($qdir && length($qdir)>1);
return undef unless (-e "$qdir/HEADERS");
my $hdr = MIME::Head->from_file("$qdir/HEADERS");
return undef unless ($hdr);
$qdir_hds{$qid}{''} = $hdr if ($qid);
return $hdr;
}
sub get_header {
my ($qid,$qdir,$hdrn) = @_;
$qid = '' if (length($qid) < 2);
$qdir = '' if (length($qdir) < 2);
return '' unless ($hdrn && ($qid || $qdir));
$hdrn = lc($hdrn);
return $qdir_hds{$qid}{$hdrn} if ($qid && $qdir_hds{$qid}{$hdrn});
my $hdr = read_header($qid,$qdir);
return '' unless ($hdr);
my $val = $hdr->get($hdrn);
return '' unless (defined($val));
$val = decode_header($val) if ($val);
$qdir_hds{$qid}{$hdrn} = $val if ($qid);
return $val;
}
sub quarantine_table {
my ($qdl,$tables,$fa,$hm) = @_;
print "\tmake table ($fa,$hm)\n" unless ($quiet);
my @trl = ();
foreach my $qd (sort { $a->{fn} cmp $b->{fn} } @{$qdl}) {
next unless ($qd->{fa} eq $fa && $qd->{hm} eq $hm);
$qd->{inc} = 1;
my @qfi = ();
if ($quarantine_url && $qd->{fn}) {
push @qfi, $cgi->a({href=>sprintf('%s/%s',$quarantine_url,$qd->{fn})},$qd->{fn});
} elsif ($qd->{fn}) {
push @qfi, $qd->{fn};
}
push @qfi, $qd->{qid} if ($qd->{qid});
push @trl, $cgi->TR({class=>$#trl%2?'odd':'even'},
#$cgi->td("$fa$hm"),
#$cgi->td($qd->{qid}),
$cgi->td($qd->{snd}),
$cgi->td(join($cgi->br,@{$qd->{rcpt}})),
$cgi->td($qd->{subj}),
$cgi->td($qd->{msgs}),
$cgi->td(join($cgi->br,@qfi)),
);
}
return '' unless (@trl);
unshift @trl, $cgi->TR(
#$cgi->th('what'),
#$cgi->th('Queue ID'),
$cgi->th('Sender'),
$cgi->th('Recipient(s)'),
$cgi->th('Subject'),
$cgi->th('Information'),
$cgi->th('Q'),
);
my $hs;
if ($fa eq 'S') {
$hs = 'Stopped';
} elsif ($fa eq 'R') {
$hs = 'Removed';
} elsif ($fa eq 'M') {
$hs = 'Modified';
} else {
$hs = 'Handled';
}
if ($hm eq 'M') {
$hs .= ' Message';
} elsif ($hm eq 'P') {
$hs .= ' Part';
} else {
$hs .= ' Thing';
}
$hs .= 's' if ($#trl > 0);
my $hsa = lc($hs);
$hsa =~ s/\s+/_/g;
$hsa =~ s/[^_a-z0-9]//g;
push @{$tables}, $cgi->a({href=>"#$hsa"},encode_entities($hs));
return $cgi->div($cgi->h2({id=>$hsa},encode_entities($hs)),$cgi->table(@trl));
}
sub quarantine_report {
print "Quarantine Report\n" unless ($quiet);
my ($all) = @_;
my @qdl = ();
return unless (list_quarantine(1,$all,\@qdl));
return unless (@qdl);
print "\tread qdirs\n" unless ($quiet);
my $dc = 0;
foreach my $qd (@qdl) {
my %msgf = ();
my %flag = ();
my %dir = ();
next unless (opendir(D,$qd->{pn}));
print "\t\t$qd->{fn}\n" if ($debug);
while (my $f = readdir(D)) {
next if ($f =~ /^\./);
next if ($f eq 'reported');
$dir{$f} = 1;
if ($f =~ /^FLAG\.(.+)$/) {
$flag{lc($1)} = 1;
} elsif ($f =~ /^MSG.(\d+)$/) {
$msgf{$1} = $f;
}
}
closedir(D);
next unless (%dir);
$dc ++;
if ($flag{'stopped'}) {
$qd->{fa} = 'S';
} elsif ($flag{'removed'}) {
$qd->{fa} = 'R';
} elsif ($flag{'modified'}) {
$qd->{fa} = 'M';
} else {
$qd->{fa} = '?';
}
if ($dir{'ENTIRE_MESSAGE'}) {
$qd->{hm} = 'M';
} else {
$qd->{hm} = 'P';
}
$qd->{qid} = '';
$qd->{snd} = '';
$qd->{rcpt} = [];
$qd->{subj} = '';
$qd->{snd} = encode_entities(read_line("$qd->{pn}/SENDER")) if ($dir{'SENDER'});
if ($dir{'RECIPIENTS'}) {
$qd->{rcpt} = read_lines("$qd->{pn}/RECIPIENTS");
if ($qd->{rcpt}) {
for (my $i=0;$i<@{$qd->{rcpt}};$i++) {
$qd->{rcpt}->[$i] = encode_entities($qd->{rcpt}->[$i]);
}
}
}
$qd->{subj} = encode_entities(get_header($qd->{qid},$qd->{pn},'Subject'));
foreach my $mn (sort { $a <=> $b } keys %msgf) {
my $msg = read_text("$qd->{pn}/$msgf{$mn}");
next unless ($msg);
$msg =~ s/[\s\n]+$//s;
$msg =~ s/^\n+//s;
next unless ($msg);
$msg = encode_entities($msg);
$msg =~ s/\n+/$cgi->br/gse;
$qd->{msgs} = '' unless ($qd->{msgs});
$qd->{msgs} .= $cgi->p($msg);
}
if ($dir{'SPAM_REPORT'}) {
foreach my $l (read_file("$qd->{pn}/SPAM_REPORT")) {
if ($l =~ /^Score:/i) {
$qd->{msgs} = '' unless ($qd->{msgs});
$qd->{msgs} .= $cgi->p(encode_entities($l));
last;
}
}
}
}
return unless ($dc);
my $html = '';
my @tables = ();
$html .= quarantine_table(\@qdl,\@tables,'S','M');
$html .= quarantine_table(\@qdl,\@tables,'R','M');
$html .= quarantine_table(\@qdl,\@tables,'S','P');
$html .= quarantine_table(\@qdl,\@tables,'R','P');
$html .= quarantine_table(\@qdl,\@tables,'M','M');
$html .= quarantine_table(\@qdl,\@tables,'M','P');
$html .= quarantine_table(\@qdl,\@tables,'?','M');
$html .= quarantine_table(\@qdl,\@tables,'?','P');
return unless ($html);
return unless (report_mail('quarantine','','MDF Quarantine Report',
$cgi->h1('MDF Quarantine Report'),
$cgi->p(join(' | ',@tables)),
$html));
foreach my $qd (@qdl) {
next unless ($qd->{inc});
push @qdir_reported, "$qd->{pn}/reported";
}
}
my %rej_lines = ();
my %rej_reasons = ();
my $rej_collected = 0;
sub reject_collect {
return 0 if ($rej_collected<0);
return 1 if ($rej_collected);
print "\tcollect\n" unless ($quiet);
return 0 unless (list_quarantine());
my $logcsv = Text::CSV_XS->new({sep_char=>',',quote_char=>"'",binary=>1});
my $logssv = Text::CSV_XS->new({sep_char=>';',quote_char=>'"',binary=>1});
my $now = time();
return 0 unless (sql_connect());
#sql_execute('DELETE FROM logs WHERE logs_type=? AND logs_cont!=? AND logs_cont!=?',
# 'stats','reject','discard');
my $st = sql_query('SELECT logs_id,logs_mqid,logs_stamp,logs_line FROM logs WHERE logs_stamp<? AND logs_type=? AND (logs_cont=? OR logs_cont=?) ORDER BY logs_id',
$now-(12*60*60),'stats','reject','discard');
return 0 unless ($st);
$st->execute;
%rej_lines = ();
%rej_reasons = ();
while (my @res = $st->fetchrow_array) {
my $qid = $res[1];
next unless ($logssv->parse($res[3]));
my @flds = $logssv->fields;
$flds[0] = 'phish' if ($flds[0] eq 'virus' && $flds[1] =~ /\.Phishing\./i);
$flds[0] = 'eicar' if ($flds[0] eq 'virus' && $flds[1] =~ /^Eicar[-_.]Test[-_.]/i);
$rej_reasons{$flds[0]}{t} = 0 unless ($rej_reasons{$flds[0]}{t});
$rej_reasons{$flds[0]}{t} ++;
my $bad = 0;
foreach my $i ((1,6)) {
if (defined($flds[$i]) && $flds[$i] ne '') {
unless ($logcsv->parse($flds[$i])) {
$bad = 1;
last;
}
$flds[$i] = [$logcsv->fields];
} else {
$flds[$i] = [];
}
}
next if ($bad);
$flds[3] = undef if ($flds[3] eq "[$flds[2]]");
$flds[4] = undef if (lc($flds[4]) eq lc($flds[3]));
$flds[7] = decode_header($flds[7]) if ($flds[7]);
$rej_reasons{$flds[0]}{l} = [] unless ($rej_reasons{$flds[0]}{l});
$rej_reasons{$flds[0]}{c} = 0 unless ($rej_reasons{$flds[0]}{c});
$rej_reasons{$flds[0]}{c} ++;
$rej_lines{$qid} = [] unless $rej_lines{$qid};
push @{$rej_lines{$qid}}, {
id => $res[0],
stamp => $res[2],
#line => $res[3],
#fields => \@flds,
reason => $flds[0],
info => $flds[1],
relay => $flds[2],
host => $flds[3],
helo => $flds[4],
sender => $flds[5],
rcpts => $flds[6],
subject => $flds[7],
};
push @{$rej_reasons{$flds[0]}{l}}, {q=>$qid,l=>$#{$rej_lines{$qid}}};
$qdir_ids{$qid} = '?' if ($qid && $qid ne '?' && !$qdir_ids{$qid});
$rej_collected ++;
}
$st->finish;
unless ($rej_collected) {
$rej_collected = -1;
return 0
};
return 1;
}
sub reject_cmp {
my $r = $a->[0] <=> $b->[0];
$r = $r ? $r : $a->[7] cmp $b->[7];
return $r if ($r);
for my $i ((5,1,2,3,4)) {
$r = $a->[$i] cmp $b->[$i];
return $r if ($r);
}
return 0;
}
sub reject_table {
my ($what,$logs,$all,$tables,$usersr,$totals) = @_;
print "\tmake table ($what)\n" unless ($quiet);
my $infoslash = $what eq 'spam';
my @used = ();
my @nouse = ();
my @tll = ();
my %lid = ();
my $ls = $logs;
$nouse[1] = 1 if ($what =~ /^(mail_from|unknown_local_user)$/);
$nouse[2] = 1 if ($usersr || $what =~ /^(bad_recipient_map|bad_user|unknown_user)$/);
$nouse[5] = 1 if ($usersr);
$nouse[6] = 1 unless ($usersr);
foreach my $li (@{$ls}) {
my $line = $rej_lines{$li->{q}}->[$li->{l}];
next unless ($line);
next if (!$all && length($li->{q})>1 && $qdir_ids{$li->{q}} && length($qdir_ids{$li->{q}})>1);
if ($usersr) {
next unless ($line->{rcpts} && @{$line->{rcpts}});
my $skip = 1;
foreach my $rcpt (@{$line->{rcpts}}) {
if ($rcpt =~ /^$usersr$/i) {
$skip = 0;
last;
}
}
next if ($skip);
}
my $lused = 0;
my @tl = ();
$tl[8] = $line->{id};
if (!$nouse[1]) {
if ($usersr) {
foreach my $hn ('From','Sender','Reply-To') {
$tl[1] = get_header($li->{q},'',$hn);
last if ($tl[1]);
}
}
$tl[1] = $line->{sender} if (!$tl[1] && defined($line->{sender}) && $line->{sender} ne '');
if ($tl[1]) {
$tl[1] = encode_entities($tl[1]);
$used[1] = 1;
$lused = 1;
}
}
if (!$nouse[2] && $line->{rcpts} && @{$line->{rcpts}}) {
$tl[2] = join($cgi->br,map { encode_entities($_) } @{$line->{rcpts}});
$used[2] = 1;
$lused = 1;
}
if (!$nouse[3]) {
$tl[3] = $line->{subject} if (defined($line->{subject}) && $line->{subject} ne '');
$tl[3] = get_header($li->{q},'','Subject') unless (defined($tl[3]));
if (defined($tl[3]) && $tl[3] ne '') {
$tl[3] = encode_entities($tl[3]);
$used[3] = 1;
$lused = 1;
}
}
if (!$nouse[4] && $line->{info} && @{$line->{info}}) {
if ($infoslash) {
$tl[4] = join(' / ',map { encode_entities($_) } @{$line->{info}});
} else {
$tl[4] = join($cgi->br,map { encode_entities($_) } @{$line->{info}});
}
$used[4] = 1;
$lused = 1;
}
if (!$nouse[5]) {
my @xinfo = ();
foreach my $xi ('relay','host','helo') {
next if ($xi eq $what);
push @xinfo, sprintf('%s: %s',ucfirst($xi),encode_entities($line->{$xi})) if (defined($line->{$xi}) && $line->{$xi} ne '');
}
if (@xinfo) {
$tl[5] = join($cgi->br,@xinfo);
$used[5] = 1;
$lused = 1;
}
}
if (!$nouse[6] && length($li->{q})>1 && $qdir_ids{$li->{q}} && length($qdir_ids{$li->{q}})>0) {
$tl[6] = $qdir_ids{$li->{q}};
$tl[6] =~ s/^[^\/]*\///;
$used[6] = 1;
}
next unless ($lused);
if ($what eq 'spam') {
$tl[7] = sprintf('%09.4f',$line->{info}->[0]);
} elsif ($what eq 'virus') {
$tl[7] = sprintf('%u|%s',$line->{info}->[0]&&$line->{info}->[0]=~/\.Phishing\./i?1:0,$line->{info}->[0]);
} else {
$tl[7] = '';
foreach my $inf (@{$line->{info}}) {
if ($inf =~ /^\d+$/) {
$tl[7] .= sprintf('|%127s',$inf);
} else {
$tl[7] .= sprintf('|%-127s',$inf);
}
}
}
my $rid = join("\x01",@tl);
if ($lid{$rid}) {
$tll[$lid{$rid}]->[0] ++;
$used[0] = 1;
} else {
$tl[0] = 1;
push @tll, \@tl;
$lid{$rid} = $#tll;
}
if ($totals) {
$totals->{$what} = 0 unless ($totals->{$what});
$totals->{$what} ++;
}
}
my @table = ();
foreach my $tl (sort reject_cmp @tll) {
my @ll = ();
my $fi = 0;
for (my $i=0;$i<7;$i++) {
next unless ($used[$i]);
if (defined($tl->[$i]) && $tl->[$i] ne '') {
$fi = 1;
} else {
$tl->[$i];
}
push @ll, $cgi->td($tl->[$i]);
}
next unless ($fi);
#push @logs_reported, $tl->[8];
push @table, $cgi->TR({class=>$#table%2?'odd':'even'},@ll);
}
return '' unless (@table);
my @head = ();
push @head, $cgi->th('Count') if ($used[0]);
push @head, $cgi->th('Sender') if ($used[1]);
push @head, $cgi->th('Recipients') if ($used[2]);
push @head, $cgi->th('Subject') if ($used[3]);
push @head, $cgi->th('Info') if ($used[4]);
push @head, $cgi->th('Mailer') if ($used[5]);
unshift @table, $cgi->TR(@head);
push @{$tables}, $cgi->a({href=>"#$what"},encode_entities(fwords($what)));
return $cgi->div($cgi->h2({id=>$what},encode_entities(fwords($what))),$cgi->table(@table));
}
sub reject_report {
print "Rejection Report\n" unless ($quiet);
my ($show,$hide,$all) = @_;
return unless (reject_collect());
my $html = '';
my @summary = ();
foreach my $rsn (sort { $rej_reasons{$a}{t} <=> $rej_reasons{$b}{t} } keys %rej_reasons) {
push @summary, $cgi->TR({class=>$#summary%2?'odd':'even'},$cgi->td(fwords($rsn)),$cgi->td($rej_reasons{$rsn}{t}));
}
if (@summary) {
#push @tables, $cgi->a({href=>'#summary'},'Summary');
$html .= $cgi->div($cgi->h2({id=>'summary'},'Summary'),$cgi->table(@summary));
}
my @tables = ();
foreach my $rsn (sort keys %rej_reasons) {
next unless ($rej_reasons{$rsn}{c});
next unless ($rsn =~ /^$show$/i && $rsn !~ /^$hide$/i);
$html .= reject_table($rsn,$rej_reasons{$rsn}{l},$all,\@tables);
}
return unless ($html);
#print $html; return;
return unless (report_mail("rejections:$show",'','MDF Rejection Report',
$cgi->h1('MDF Rejection Report'),
$cgi->p(join(' | ',@tables)),
$html));
}
sub reject_report_user {
my $show = shift;
my $hide = shift;
my $all = shift;
my $user = shift;
$user =~ s/^<(.*?)>$/$1/;
@_ = ($user) unless (@_);
my @usersr = ();
my @userst = ();
foreach my $usr (@_) {
my $usrx = $usr;
$usrx =~ s/\s+//g;
next unless ($usrx);
$usrx =~ s/^<(.*?)>$/$1/;
push @userst, "<$usrx>";
push @usersr, "<?$usrx>?";
}
return unless (@usersr);
print "Rejection Report (".join(',',@userst).")\n" unless ($quiet);
return unless (reject_collect());
my @tables = ();
my %totals = ();
my $html = '';
foreach my $rsn (sort keys %rej_reasons) {
next unless ($rej_reasons{$rsn}{c});
next unless ($rsn =~ /^$show$/i && $rsn !~ /^$hide$/i);
$html .= reject_table($rsn,$rej_reasons{$rsn}{l},$all,\@tables,join('|',@usersr),\%totals);
}
my @summary = ();
foreach my $rsn (sort { $totals{$a} <=> $totals{$b} } keys %totals) {
next unless ($totals{$rsn});
push @summary, $cgi->TR({class=>$#summary%2?'odd':'even'},$cgi->td(fwords($rsn)),$cgi->td($totals{$rsn}));
}
if (@summary) {
#push @tables, $cgi->a({href=>'#summary'},'Summary');
$html = $cgi->div($cgi->h2({id=>'summary'},'Summary'),$cgi->table(@summary)).$html;
}
return unless ($html);
return unless (report_mail("user:$show",$user,'MDF Rejection Report',
$cgi->h1('MDF Rejection Report'),
$cgi->blockquote(encode_entities(join(' ',@userst))),
$cgi->p(join(' | ',@tables)),
$html));
}
sub reject_report_users {
my ($rtufn) = @_;
$rtufn = get_file_path_name('user-reports');
return unless (open(UF,'<',$rtufn));
my @users = <UF>;
close(UF);
foreach my $l (@users) {
$l =~ s/[\r\n]+//gs;
next unless ($l);
next if ($l =~ /^\s*[#;]/);
reject_report_user('spam|phish|virus','',1,split(/\s+/,$l));
}
}
sub hiloscores_table {
my $ord = shift;
my $lmt = shift;
my $lst = shift;
return unless (sql_connect());
my $st = sql_query("SELECT hilo_id,hilo_stamp,hilo_score,hilo_info,hilo_report FROM hiloscores ORDER BY hilo_score $ord LIMIT $lmt");
return unless ($st);
$st->execute;
my @trl = ();
while (my @res = $st->fetchrow_array) {
next unless ($res[1] && $res[2] && $res[3]);
my $date = time2str('%Y-%m-%d %H:%M:%S',$res[1]);
my $score = $res[2];
my $info = '';
if ($res[3]) {
my @itl = ();
foreach my $l (split(/[\r\n]+/,$res[3])) {
if ($l =~ /^([^:]+)\s*:\s*(.*?)\s*$/) {
my $k = $1;
my $v = decode_header($2);
$k = encode_entities("$k:");
$k =~ s/ /\ /g;
push @itl, $cgi->TR(
$cgi->td($k),
$cgi->td(encode_entities($v)),
);
}
}
$info = $cgi->table({class=>'top'},@itl) if (@itl);
}
my $report = '';
if ($res[4]) {
$res[4] =~ s/\r?\n/\n/gs;
$res[4] =~ s/\r/\n/gs;
if ($res[4] =~ /----------/s) {
$res[4] =~ s/[- A-Za-z]+:\s+\([^\n\)]*\)\n\s*\n[ a-z]+\n[- ]+----------\n//gs;
$res[4] =~ s/[ a-z]+\n[- ]+----------\n//gs;
$res[4] =~ s/[- ]+----------\n//gs;
}
$res[4] =~ s/\n+/\n/gs;
my @hti = ();
foreach my $l (split(/[\r\n]+/,$res[4])) {
if ($l =~ /^\s*(-?\d+\.\d+)\s+(\S+)\s*(.*?)\s*$/) {
push @hti, {sc=>encode_entities($1),rn=>encode_entities($2),rd=>[]};
push @{$hti[$#hti]->{rd}}, encode_entities($3) if ($3);
} elsif ($l =~ /^\s*(\S+.*?)\s*$/ && @hti) {
push @{$hti[$#hti]->{rd}}, encode_entities($1);
}
}
my @htl = ();
foreach my $ri (@hti) {
push @htl, $cgi->TR(
$cgi->td($ri->{sc}),
$cgi->td($ri->{rn}),
$cgi->td(join($cgi->br,@{$ri->{rd}})),
);
}
$report = $cgi->table({class=>'top'},@htl) if (@htl);
}
push @trl, $cgi->TR({class=>$#trl%2?'odd':'even'},
$cgi->td($date),
$cgi->td($score),
$cgi->td($info),
$cgi->td($report),
);
}
$st->finish;
return '' unless (@trl);
@trl = reverse @trl if ($ord eq 'ASC');
unshift @trl, $cgi->TR(
$cgi->th('Date'),
$cgi->th('Score'),
$cgi->th('Info'),
$cgi->th('Report'),
);
my $hs = "$lst Score List";
my $hsa = lc($hs);
$hsa =~ s/\s+/_/g;
$hsa =~ s/[^_a-z0-9]//g;
#push @{$tables}, $cgi->a({href=>"#$hsa"},$hs);
return $cgi->div($cgi->h2({id=>$hsa},encode_entities($hs)),$cgi->table({class=>'top'},@trl));
}
sub hiloscores_report {
print "High/Low Scores Report\n" unless ($quiet);
my $html = '';
$html .= hiloscores_table('DESC',$hilo_entries,'High');
$html .= hiloscores_table('ASC',$hilo_entries,'Low');
return unless ($html);
report_mail('hiloscores','','Spam High and Low Scores Report',
$cgi->h1('Spam High and Low Scores Report'),
$html);
}
sub external_local_table {
return '' unless (sql_connect());
my $st = sql_query('SELECT logs_id,logs_mqid,logs_stamp,logs_line FROM logs WHERE logs_type=? AND logs_cont=?',
'stats','notice');
return '' unless ($st);
my $logssv = Text::CSV_XS->new({sep_char=>';',quote_char=>'"',binary=>1});
my %addresses = ();
my @qids = ();
while (my @res = $st->fetchrow_array) {
my $qid = $res[1];
next unless ($logssv->parse($res[3]));
my @flds = $logssv->fields;
next unless (@flds && $flds[0] eq 'external_local');
my $stq = sql_query('SELECT logs_mqid FROM logs WHERE logs_type=? AND logs_cont=? AND logs_mqid=?',
'stats','deliver',$qid);
next unless ($stq);
my @resq = $stq->fetchrow_array;
$stq->finish;
next if ($#resq || $resq[0] ne $qid);
$flds[3] =~ s/^<(.*)>$/$1/;
$addresses{$flds[3]} = {
cnt => 0,
qid => {},
relays => {},
hosts => {},
oldt => 0,
newt => 0,
olds => '',
news => '',
} unless ($addresses{$flds[3]});
push @qids, $res[1];
my $relay = "[$flds[1]]";
$relay = "$flds[2] $relay" if ($flds[2] && $flds[2] !~ /^\[?$flds[1]\]?$/);
$addresses{$flds[3]}->{cnt} ++;
$addresses{$flds[3]}->{qid}->{$res[1]} = 1;
$addresses{$flds[3]}->{rly}->{$relay} = 1;
if ($res[2]>$addresses{$flds[3]}->{newt}) {
$addresses{$flds[3]}->{newt} = $res[2];
$addresses{$flds[3]}->{news} = time2str('%Y-%m-%d',$res[2]);
}
if (!$addresses{$flds[3]}->{oldt} || $res[2]<$addresses{$flds[3]}->{oldt}) {
$addresses{$flds[3]}->{oldt} = $res[2];
$addresses{$flds[3]}->{olds} = time2str('%Y-%m-%d',$res[2]);
}
push @logs_reported, $res[0];
}
$st->finish;
return '' unless (%addresses);
my @trl = ();
foreach my $addr (sort { $addresses{$a}->{new} <=> $addresses{$b}->{new} } keys %addresses) {
push @trl, $cgi->TR({class=>$#trl%2?'odd':'even'},
$cgi->td($addr),
$cgi->td($addresses{$addr}->{olds} eq $addresses{$addr}->{news} ?
$addresses{$addr}->{olds} :
join($cgi->br,$addresses{$addr}->{olds},$addresses{$addr}->{news})),
$cgi->td($addresses{$addr}->{cnt}),
$cgi->td(join($cgi->br,keys %{$addresses{$addr}->{rly}})),
$cgi->td(join($cgi->br,keys %{$addresses{$addr}->{qid}})),
);
}
#@trl = reverse @trl if ($ord eq 'ASC');
unshift @trl, $cgi->TR(
$cgi->th('Address'),
$cgi->th('Date(s)'),
$cgi->th('Count'),
$cgi->th('Relays'),
$cgi->th('QIDs'),
);
return $cgi->div($cgi->h2({id=>'external_local'},'Local senders from external relays'),$cgi->table(@trl));
}
sub external_local_report {
print "External Local Report\n" unless ($quiet);
my $html = external_local_table();
return unless ($html);
report_mail('externlocal','','External Local Report',$html);
}
read_cfg_cfg();
#$forget = 1;
#$debug = 1;
for (my $i=0;$i<@ARGV;$i++) {
my ($c,$p) = split(/[:=]+/,$ARGV[$i]);
$c =~ s/[^A-Za-z]+//g;
unless ($c) {
print "? $ARGV[$i]\n" unless ($quiet);
next;
}
$c = lc($c);
if ($c =~ /^d(e?bu?g)?$/) {
$debug = 1;
} elsif ($p && $c =~ /^m(ail)?to?$/) {
$mailto = $p;
} elsif ($c =~ /^q(uarantine?)?$/) {
quarantine_report(0);
} elsif ($c =~ /^f(ull)?q(uarantine?)?$/) {
quarantine_report(1);
} elsif ($c =~ /^(h|hi|high)?(l|lo|low)?(s|score)?(l|lists?)?$/) {
hiloscores_report();
} elsif ($c =~ /^r(eject(ion)?s?)?$/) {
reject_report('.*','(|unknown_user|bad_recipient_map|helo|bad_user|mail_from|relay|unknown_local_user)',0);
} elsif ($c =~ /^f(ull)?r(eject(ion)?s?)?$/) {
reject_report('.*','',1);
} elsif ($c =~ /^s(pam)?r(eject(ion)?s?)?$/) {
reject_report('spam(_.*)?','',0);
} elsif ($c =~ /^(\S+?)r(eject(ion)?s?)?$/) {
reject_report(lc($1),'',0);
} elsif ($c =~ /^u(sers?)(r(eports?)?)?$/) {
reject_report_users();
} elsif ($p && $c =~ /^u(?:sers?)(?:r(?:eports?)?)?$/) {
reject_report_users($p);
} elsif ($c =~ /^(e|x|extern(?:al)?)l(oc(?:al)?)?$/) {
external_local_report();
} elsif ($c =~ /^q(uiet)?$/) {
$quiet = 1;
} elsif ($c =~ /^f(orget)?$/) {
$forget = 1;
} else {
print "? $ARGV[$i]\n" unless ($quiet);
}
}
mark_reported();
sql_disconnect();
(2008-01-11)