#!/usr/bin/perl #*********************************************************************** # # mdfdb-clean # # mimedefang-filter database cleaner/creator # # $Id: mdfdb-clean.pl,v 1.42 2009/06/22 16:43:40 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. # #*********************************************************************** #*********************************************************************** # Code... #*********************************************************************** use strict; use Sys::Syslog; use DBI; use IO::File; use Text::CSV_XS; my $sqldb; my $sqlr; my $sqldbd = '?'; openlog("mdfdbc","pid","mail"); my $verbose = 0; sub logmsg { syslog(@_); print sprintf("%s %s\n",shift,sprintf(shift,@_)) if ($verbose); } #*********************************************************************** # Config. #*********************************************************************** my $vacuum = 0; my $clean = 1; my $backup = ''; my $restore = ''; my $noconfig = 0; my $cleanl = ''; my @tables = (); my %Features; $Features{'Path:SPOOLDIR'} = '/var/spool/MIMEDefang'; # 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($dc_keep $mx_keep $sc_keep $gdb_keep $gdb_keep_host $nsdb_keep $rdb_keep $sdb_keep $bnc_keep); use vars qw($pgp_keep $ts_keep $msgl_keep $out_keep $smtp_keep $expn_keep $gsnd_keep $virus_keep $trap_keep); use vars qw($abuse_keep); add_cfg_cfg('dc_keep',\$dc_keep,5*60,'i'); add_cfg_cfg('mx_keep',\$mx_keep,30*24*60*60,'i'); add_cfg_cfg('sc_keep',\$sc_keep,30*24*60*60,'i'); add_cfg_cfg('gdb_keep',\$gdb_keep,60*24*60*60,'i'); add_cfg_cfg('gdb_keep_host',\$gdb_keep_host,60*24*60*60,'i'); add_cfg_cfg('gdb_keep_msg',\$msgl_keep,60*24*60*60,'i'); add_cfg_cfg('nsdb_keep',\$nsdb_keep,90*24*60*60,'i'); add_cfg_cfg('sdb_keep',\$sdb_keep,90*24*60*60,'i'); add_cfg_cfg('rdb_keep',\$rdb_keep,90*24*60*60,'i'); add_cfg_cfg('pgp_keep',\$pgp_keep,90*24*60*60,'i'); add_cfg_cfg('ts_keep',\$ts_keep,24*60*60,'i'); add_cfg_cfg('out_keep',\$out_keep,90*24*60*60,'i'); add_cfg_cfg('smtp_keep',\$smtp_keep,7*24*60*60,'i'); add_cfg_cfg('expn_keep',\$expn_keep,7*24*60*60,'i'); add_cfg_cfg('gsnd_keep',\$gsnd_keep,40*24*60*60,'i'); add_cfg_cfg('avc_keep',\$virus_keep,7*24*60*60,'i'); add_cfg_cfg('bnc_keep',\$bnc_keep,366*24*60*60,'i'); add_cfg_cfg('trap_keep',\$trap_keep,24*60*60,'i'); add_cfg_cfg('abuse_keep',\$trap_keep,366*24*60*60,'i'); use vars qw ($hilo_keep $hilo_entries $hilo_margin $logs_keep); add_cfg_cfg('hilo_keep',\$hilo_keep,366*24*60*60,'i'); add_cfg_cfg('hilo_entries',\$hilo_entries,0,'i'); add_cfg_cfg('hilo_margin',\$hilo_margin,0,'i'); add_cfg_cfg('logs_keep',\$logs_keep,14*24*60*60); 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'); # 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 { foreach my $p (@ARGV) { $p =~ s/^-+//; if ($p =~ /^v(acuum)?$/i) { $vacuum = 1; } elsif ($p =~ /^no?[-_]?c(lean)?$/i) { $clean = 0; } elsif ($p =~ /^c(lean)?\s*[:=]\s*(.*?)\s*$/i) { $cleanl .= ",$2,"; } elsif ($p =~ /^b(ackup)?\s*[:=]\s*(.*?)\s*$/i) { $backup = $2; } elsif ($p =~ /^r(estore)?\s*[:=]\s*(.*?)\s*$/i) { $restore = $2; } elsif ($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; } elsif ($p =~ /^v(erbose)?$/i) { $verbose = 1; } else { print "? $p\n"; } } } 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_logmsg('info',"Filter config: $cfgfn"); while (my $l = ) { $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'})); 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. #*********************************************************************** sub sql_translate { my ($cmd) = @_; if ($sqldbd eq 'M') { $cmd =~ s/^INSERT OR IGNORE /INSERT IGNORE /; } return $cmd; } sub sql_command { $sqlr = undef; for (my $i=0;$i<@_;$i++) { my $cmd = @_[$i]; return 0 unless ($sqldb && $cmd); if ($cmd =~ /^(CREATE|ALTER) TABLE/) { if ($sqldbd eq 'L') { $cmd =~ s//INTEGER PRIMARY KEY AUTOINCREMENT/; } if ($sqldbd eq 'M') { $cmd =~ s//TINYINT/g; $cmd =~ s//INTEGER/g; $cmd =~ s//INTEGER UNSIGNED/g; $cmd =~ s//CHAR/g; $cmd =~ s//VARCHAR/g; $cmd =~ s//SERIAL/; } else { $cmd =~ s/<[TU]?INT>/INTEGER/g; $cmd =~ s/(\(\d+\))?/TEXT/g; $cmd =~ s/<(TINY|MEDIUM|LONG)(BLOB|TEXT)>/$2/g; $cmd =~ s//INTEGER NOT NULL AUTOINCREMENT UNIQUE/; } $cmd =~ s/<([A-Z]+)>/$1/g; } elsif ($cmd =~ /^CREATE (UNIQUE )?INDEX/) { $cmd =~ s/\(\d+\)//g unless ($sqldbd eq 'M'); } else { $cmd = sql_translate($cmd); } $sqlr = $sqldb->do($cmd); return 0 unless (defined($sqlr)); } return 1; } sub sql_select_one_row { my $cmd = shift; return undef unless ($cmd); my $st = $sqldb->prepare(sql_translate($cmd)); return undef unless ($st); $st->execute; 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]; } sub sql_create_tables { return unless ($sqldb); #logmsg('info','Checking tables'); my @tl = $sqldb->tables(); #foreach my $tt (@tl) { # logmsg('info',"Table: $tt"); #} push @tables, 'dictionary'; unless (grep(/dictionary/,@tl)) { logmsg('info','Creating dictionary table'); sql_command( 'CREATE TABLE dictionary ('. 'dc_stamp NOT NULL DEFAULT 0, '. 'dc_host (15) NOT NULL)', 'CREATE INDEX idx_dc_host ON dictionary (dc_host)', 'CREATE INDEX idx_dc_stamp ON dictionary (dc_stamp)', ); } push @tables, 'relaylist'; unless (grep(/relaylist/,@tl)) { logmsg('info','Creating relaylist table'); sql_command( 'CREATE TABLE relaylist ('. 'rl_host (255) PRIMARY KEY, '. 'rl_stamp NOT NULL DEFAULT 0, '. 'rl_touch NOT NULL DEFAULT 0, '. 'rl_spam NOT NULL DEFAULT 0, '. 'rl_ham NOT NULL DEFAULT 0)', 'CREATE INDEX idx_rl_stamp ON relaylist (rl_stamp)', ); } push @tables, 'hostlist'; unless (grep(/hostlist/,@tl)) { logmsg('info','Creating hostlist table'); sql_command( 'CREATE TABLE hostlist ('. 'hl_host (15) PRIMARY KEY, '. 'hl_stamp NOT NULL DEFAULT 0, '. 'hl_touch NOT NULL DEFAULT 0)', 'CREATE INDEX idx_hl_stamp ON hostlist (hl_stamp)', ); } push @tables, 'sendercheck'; unless (grep(/sendercheck/,@tl)) { logmsg('info','Creating sendercheck table'); sql_command( 'CREATE TABLE sendercheck ('. 'sc_address (255) PRIMARY KEY, '. 'sc_stamp NOT NULL DEFAULT 0, '. 'sc_count NOT NULL DEFAULT 0, '. 'sc_result NOT NULL DEFAULT 0, '. 'sc_text (255) NOT NULL DEFAULT \'\')', 'CREATE INDEX idx_sc_stamp ON sendercheck (sc_stamp)', ); } push @tables, 'greylist'; unless (grep(/greylist/,@tl)) { logmsg('info','Creating greylist table'); sql_command( 'CREATE TABLE greylist ('. 'gl_reset NOT NULL DEFAULT 0, '. 'gl_accepted NOT NULL DEFAULT 0, '. 'gl_host (15) NOT NULL, '. 'gl_sender (255) NOT NULL, '. 'gl_recipient (255) NOT NULL)', 'CREATE UNIQUE INDEX idx_gl_triplet ON greylist (gl_host,gl_sender,gl_recipient)', 'CREATE INDEX idx_gl_resetaccept ON greylist (gl_reset,gl_accepted)', ); } push @tables, 'mxcheck'; unless (grep(/mxcheck/,@tl)) { logmsg('info','Creating mxcheck table'); sql_command( 'CREATE TABLE mxcheck ('. 'mx_domain (255) PRIMARY KEY, '. 'mx_stamp NOT NULL DEFAULT 0, '. 'mx_error (255) NOT NULL DEFAULT \'\')', 'CREATE INDEX idx_mx_stamp ON mxcheck (mx_stamp)', ); } push @tables, 'nospamlist'; unless (grep(/nospamlist/,@tl)) { logmsg('info','Creating nospam table'); sql_command( 'CREATE TABLE nospamlist ('. 'ns_host (255) PRIMARY KEY, '. 'ns_stamp NOT NULL DEFAULT 0, '. 'ns_touch NOT NULL DEFAULT 0, '. 'ns_count NOT NULL DEFAULT 0)', 'CREATE INDEX idx_ns_stamp ON nospamlist (ns_stamp)', ); } push @tables, 'pgpring'; unless (grep(/pgpring/,@tl)) { logmsg('info','Creating pgp ring table'); sql_command( 'CREATE TABLE pgpring ('. 'pgp_id (40) PRIMARY KEY, '. 'pgp_stamp NOT NULL DEFAULT 0, '. 'pgp_note (255), '. 'pgp_key , '. 'pgp_mail , '. 'pgp_person )', 'CREATE INDEX idx_pgp_stamp ON pgpring (pgp_stamp)', ); } push @tables, 'times'; unless (grep(/times/,@tl)) { logmsg('info','Creating time stamp table'); sql_command( 'CREATE TABLE times ('. 'ts_id (14) PRIMARY KEY, '. 'ts_stamp NOT NULL DEFAULT 0, '. 'ts_end NOT NULL DEFAULT 0, '. 'ts_time NOT NULL DEFAULT 0)', 'CREATE INDEX idx_ts_stamp ON times (ts_stamp)', ); } push @tables, 'messages'; unless (grep(/messages/,@tl)) { logmsg('info','Creating messages table'); sql_command( 'CREATE TABLE messages ('. 'msg_id (255) NOT NULL DEFAULT \'\', '. 'msg_sender (255) NOT NULL DEFAULT \'\', '. 'msg_recipient (255) NOT NULL DEFAULT \'\', '. 'msg_stamp NOT NULL DEFAULT 0, '. 'msg_count NOT NULL DEFAULT 0, '. 'msg_spam NOT NULL DEFAULT 0)', 'CREATE UNIQUE INDEX idx_msg_triplet ON messages (msg_id,msg_sender,msg_recipient)', 'CREATE INDEX idx_msg_stamp ON messages (msg_stamp)', ); } push @tables, 'sentout'; unless (grep(/sentout/,@tl)) { logmsg('info','Creating sent out table'); sql_command( 'CREATE TABLE sentout ('. 'out_id (255) NOT NULL DEFAULT \'\', '. 'out_sender (255) NOT NULL DEFAULT \'\', '. 'out_rcpt_usr (255) NOT NULL DEFAULT \'\', '. 'out_rcpt_dom (255) NOT NULL DEFAULT \'\', '. 'out_subject (255) NOT NULL DEFAULT \'\', '. 'out_stamp NOT NULL DEFAULT 0)', 'CREATE INDEX idx_out_id_recipient_sender ON sentout (out_id(40),out_rcpt_dom(15),out_sender(20))', 'CREATE INDEX idx_out_sender_recipient_subject ON sentout (out_sender(20),out_rcpt_dom(15),out_rcpt_usr(15),out_subject(20))', 'CREATE INDEX idx_out_stamp ON sentout (out_stamp)', ); } push @tables, 'smtpcheck'; unless (grep(/smtpcheck/,@tl)) { logmsg('info','Creating smtp check table'); sql_command( 'CREATE TABLE smtpcheck ('. 'smtp_from (255) NOT NULL DEFAULT \'\', '. 'smtp_to (255) NOT NULL DEFAULT \'\', '. 'smtp_host (255) NOT NULL DEFAULT \'\', '. 'smtp_stamp NOT NULL DEFAULT 0,'. 'smtp_result (8) NOT NULL DEFAULT \'\', '. 'smtp_text (255) NOT NULL DEFAULT \'\')', 'CREATE UNIQUE INDEX idx_smtp_triplet ON smtpcheck (smtp_from,smtp_to,smtp_host)', 'CREATE INDEX idx_smtp_stamp ON smtpcheck (smtp_stamp)', ); } push @tables, 'expncheck'; unless (grep(/expncheck/,@tl)) { logmsg('info','Creating expn check table'); sql_command( 'CREATE TABLE expncheck ('. 'expn_address (255) PRIMARY KEY, '. 'expn_newaddress (255) NOT NULL DEFAULT \'\', '. 'expn_stamp NOT NULL DEFAULT 0)', 'CREATE INDEX idx_expn_stamp ON expncheck (expn_stamp)', ); } push @tables, 'hiloscores'; unless (grep(/hiloscores/,@tl)) { logmsg('info','Creating high/low scores table'); sql_command( 'CREATE TABLE hiloscores ('. 'hilo_id ,'. 'hilo_stamp NOT NULL DEFAULT 0,'. 'hilo_score NOT NULL DEFAULT 0.0,'. 'hilo_spam NOT NULL DEFAULT 0,'. 'hilo_names NOT NULL,'. 'hilo_report NOT NULL,'. 'hilo_info NOT NULL,'. 'hilo_message NOT NULL)', 'CREATE INDEX idx_hilo_score ON hiloscores (hilo_score)', 'CREATE INDEX idx_hilo_stamp ON hiloscores (hilo_stamp)', ); } push @tables, 'generatedsenders'; unless (grep(/generatedsenders/,@tl)) { logmsg('info','Creating generated senders table'); sql_command( 'CREATE TABLE generatedsenders ('. 'gsnd_address (255) PRIMARY KEY,'. 'gsnd_stamp NOT NULL DEFAULT 0)', 'gsnd_rcpts NOT NULL DEFAULT 0)', 'gsnd_bounces NOT NULL DEFAULT 0)', 'CREATE INDEX idx_gsnd_stamp ON generatedsenders (gsnd_stamp)', ); } push @tables, 'virusresults'; unless (grep(/virusresults/,@tl)) { logmsg('info','Creating virus cache table'); sql_command( 'CREATE TABLE virusresults ('. 'vir_hash (255) NOT NULL, '. 'vir_local NOT NULL, '. 'vir_entity NOT NULL, '. 'vir_scanners NOT NULL DEFAULT 0, '. 'vir_stamp NOT NULL DEFAULT 0, '. 'vir_code (255) NOT NULL DEFAULT \'\', '. 'vir_category (20) NOT NULL DEFAULT \'\', '. 'vir_action (15) NOT NULL DEFAULT \'\', '. 'vir_name (255) NOT NULL DEFAULT \'\')', 'CREATE UNIQUE INDEX idx_vir_quad ON virusresults (vir_hash,vir_local,vir_entity,vir_scanners)', 'CREATE INDEX idx_vir_stamp ON virusresults (vir_stamp)', ); } push @tables, 'spamresults'; unless (grep(/spamresults/,@tl)) { logmsg('info','Creating spam cache table'); sql_command( 'CREATE TABLE spamresults ('. 'spam_hash (255) NOT NULL, '. 'spam_sender (255) NOT NULL DEFAULT \'\', '. 'spam_recipients (255) NOT NULL DEFAULT \'\', '. 'spam_stamp NOT NULL DEFAULT 0, '. 'spam_count NOT NULL DEFAULT 0, '. 'spam_id (14) NOT NULL DEFAULT \'\', '. 'spam_score NOT NULL DEFAULT 0, '. 'spam_required NOT NULL DEFAULT 0)', 'CREATE UNIQUE INDEX idx_spam ON spamresults (spam_hash,spam_sender,spam_recipients)', 'CREATE INDEX idx_spam_stamp ON spamresults (spam_stamp)', ); } push @tables, 'bouncing'; unless (grep(/bouncing/,@tl)) { logmsg('info','Creating bounce collection table'); sql_command( 'CREATE TABLE bouncing ('. 'bnc_address (255) PRIMARY KEY, '. 'bnc_stamp NOT NULL DEFAULT 0, '. 'bnc_severity NOT NULL DEFAULT -1, '. 'bnc_count NOT NULL DEFAULT 0, '. 'bnc_problem (20), '. 'bnc_reason (255))', 'CREATE INDEX idx_bnc_stamp ON bouncing (bnc_stamp)', ); } push @tables, 'trapper'; unless (grep(/trapper/,@tl)) { logmsg('info','Creating trapper table'); sql_command( 'CREATE TABLE trapper ('. 'trap_stamp NOT NULL DEFAULT 0, '. 'trap_host (15) NOT NULL)', 'CREATE INDEX idx_trap_stamp ON trapper (trap_stamp)', ); } push @tables, 'abuse'; unless (grep(/abuse/,@tl)) { logmsg('info','Creating abuse table'); sql_command( 'CREATE TABLE abuse ('. 'abuse_stamp NOT NULL DEFAULT 0, '. 'abuse_from (255), '. 'abuse_addr (255))', 'CREATE INDEX idx_abuse_stamp ON abuse (abuse_stamp)', 'CREATE UNIQUE INDEX idx_abuse_from ON abuse (abuse_from,abuse_addr)', ); } push @tables, 'logs'; unless (grep(/logs/,@tl)) { logmsg('info','Creating logs table'); sql_command( 'CREATE TABLE logs ('. 'logs_id ,'. 'logs_stamp NOT NULL DEFAULT 0,'. 'logs_type (15) NOT NULL,'. 'logs_cont (15) NOT NULL DEFAULT \'\','. 'logs_mqid (14) NOT NULL DEFAULT \'\','. 'logs_line NOT NULL)', 'CREATE INDEX idx_logs_type_cont ON logs (logs_type,logs_cont)', 'CREATE INDEX idx_logs_stamp ON logs (logs_stamp)', ); } my %ci; foreach my $tn (@tables) { my $st = $sqldb->column_info(undef,undef,$tn,'%'); next unless ($st); while (my @cir = $st->fetchrow_array) { $ci{$cir[2]}{$cir[3]} = {dt=>$cir[4],tn=>$cir[5],sz=>$cir[6]}; } $st->finish; } unless ($ci{messages}{msg_spam}) { logmsg('info','Updating messages table'); sql_command('ALTER TABLE messages ADD COLUMN msg_spam NOT NULL DEFAULT 0'); } unless ($ci{virusresults}{vir_name}) { logmsg('info','Updating virus cache table'); sql_command('ALTER TABLE virusresults ADD COLUMN vir_name (255) NOT NULL DEFAULT \'\''); } } #*********************************************************************** # Backup/restore. #*********************************************************************** sub db_backup { my ($fn) = @_; if (my $fh = new IO::File(">$fn")) { my $ssv = Text::CSV_XS->new({sep_char=>';',quote_char=>'"',binary=>1}); my $tc = 0; foreach my $table (@tables) { if ($sqldbd eq 'L') { $table =~ s/^\"(.*)\"$/$1/; next if ($table =~ /^sqlite_/); } elsif ($sqldbd eq 'M') { $table =~ s/^\`(.*)\`$/$1/; } my $st = $sqldb->prepare("SELECT * FROM $table"); next unless ($st); my $c = 0; logmsg('info','Backing up %s',$table); $ssv->print($fh,['=',$table]); $fh->print("\n"); $st->execute; while (my @res = $st->fetchrow_array) { next unless (@res); unshift @res, '+'; $ssv->print($fh,\@res); $fh->print("\n"); $c ++; } $st->finish; logmsg('info','Backed up %s = %u',$table,$c); $tc += $c; } $fh->close(); logmsg('info','Backed up %u',$tc); } } sub make_statement { my ($table,$ref) = @_; my @q = (); for (my $i=0;$i<@$ref;$i++) { push @q, '?'; } my $sts; return sql_translate(sprintf('INSERT OR IGNORE INTO %s VALUES (%s)',$table,join(',',@q))); } sub db_restore { my ($fn) = @_; if (my $fh = new IO::File("<$fn")) { my $ssv = Text::CSV_XS->new({sep_char=>';',quote_char=>'"',binary=>1}); my $table = ''; my $tc = 0; my $cc = 0; my $st = undef; my $sts = ''; while (!$fh->eof && (my $ref = $ssv->getline($fh))) { next unless (@$ref); if ($ref->[0] eq '=') { logmsg('info','Restored %s = %u',$table,$cc) if ($table); $sts = ''; $table = $ref->[1]; logmsg('info','Restoring %s',$table) if ($table); } elsif ($table && $ref->[0] eq '+') { shift @$ref; my $x = make_statement($table,$ref); if ($sts ne $x) { $st->finish if ($st); $sts = $x; $st = $sqldb->prepare($sts); unless ($st) { print STDERR "Error preparing statement: $sts\n"; logmsg('err','Error preparing statement: %s',$sts); last; } } unless ($st->execute(@$ref)) { print STDERR "Error executing statement: $sts\n"; logmsg('err','Error executing statement: %s',$sts); last; } $cc ++; $tc ++; } } logmsg('info','Restored %s = %u',$table,$cc) if ($table); $st->finish if ($st); $fh->close(); logmsg('info','Restored %u',$tc); } } #*********************************************************************** # Cleaners. #*********************************************************************** # Clean dictionary list for all hosts sub dc_clean_list { my $t = time() - $dc_keep; logmsg('info','%u dc rec(s) removed',$sqlr) if ((sql_command("DELETE FROM dictionary WHERE (dc_stamp < $t)") && $sqlr>0) || $verbose); return 1; } # Clean relay list for all hosts sub rdb_clean_list { my $t = time() - $rdb_keep; logmsg('info','%u rdb rec(s) removed',$sqlr) if ((sql_command("DELETE FROM relaylist WHERE (rl_stamp < $t)") && $sqlr>0) || $verbose); return 1; } # Clean greylist for all hosts sub gdb_clean_list { my $t = time() - $msgl_keep; logmsg('info','%u msgdb rec(s) removed',$sqlr) if ((sql_command("DELETE FROM messages WHERE (msg_stamp < $t)") && $sqlr>0) || $verbose); $t = time() - $gdb_keep_host; logmsg('info','%u ghdb rec(s) removed',$sqlr) if ((sql_command("DELETE FROM hostlist WHERE (hl_stamp < $t)") && $sqlr>0) || $verbose); $t = time() - $gdb_keep; logmsg('info','%u gdb rec(s) removed',$sqlr) if ((sql_command("DELETE FROM greylist WHERE (gl_reset < $t AND gl_accepted < $t)") && $sqlr>0) || $verbose); return 1; } # Clean sender check cache sub sc_clean_list { my $t = time() - $sc_keep; logmsg('info','%u sc rec(s) removed',$sqlr) if ((sql_command("DELETE FROM sendercheck WHERE (sc_stamp < $t)") && $sqlr>0) || $verbose); return 1; } # Clean MX check cache sub mx_clean_list { my $t = time() - $sc_keep; logmsg('info','%u mx rec(s) removed',$sqlr) if ((sql_command("DELETE FROM mxcheck WHERE (mx_stamp < $t)") && $sqlr>0) || $verbose); return 1; } # Clean nospam list for all hosts sub nsdb_clean_list { my $t = time() - $nsdb_keep; logmsg('info','%u nsdb rec(s) removed',$sqlr) if ((sql_command("DELETE FROM nospamlist WHERE (ns_stamp < $t)") && $sqlr>0) || $verbose); return 1; } # Clean pgp ring list sub pgpdb_clean_list { my $t = time() - $pgp_keep; logmsg('info','%u pgpdb rec(s) removed',$sqlr) if ((sql_command("DELETE FROM pgpring WHERE (pgp_stamp < $t)") && $sqlr>0) || $verbose); return 1; } # Clean time stamp list sub tsdb_clean_list { my $t = time() - $ts_keep; logmsg('info','%u tsdb rec(s) removed',$sqlr) if ((sql_command("DELETE FROM times WHERE (ts_stamp < $t)") && $sqlr>0) || $verbose); return 1; } # Clean sent out list sub outdb_clean_list { my $t = time() - $out_keep; logmsg('info','%u outdb rec(s) removed',$sqlr) if ((sql_command("DELETE FROM sentout WHERE (out_stamp < $t)") && $sqlr>0) || $verbose); return 1; } # Clean smtp check list sub smtpdb_clean_list { my $t = time() - $smtp_keep; logmsg('info','%u smtpdb rec(s) removed',$sqlr) if ((sql_command("DELETE FROM smtpcheck WHERE (smtp_stamp < $t)") && $sqlr>0) || $verbose); return 1; } # Clean expn check list sub expndb_clean_list { my $t = time() - $expn_keep; logmsg('info','%u expndb rec(s) removed',$sqlr) if ((sql_command("DELETE FROM expncheck WHERE (expn_stamp < $t)") && $sqlr>0) || $verbose); return 1; } # Clean high/low scores sub hilodb_clean_list { my $km = $hilo_entries+$hilo_margin; my $rc = 0; $rc += $sqlr if (sql_command("DELETE FROM hiloscores WHERE (hilo_names='GTUBE') OR (hilo_names LIKE 'GTUBE,%') OR (hilo_names LIKE '%,GTUBE') OR (hilo_names LIKE '%,GTUBE,%')")); if ($hilo_keep && $hilo_keep>0) { my $old = time()-$hilo_keep; my $new = $km+1; $new = sql_select_one("SELECT count(hilo_score) FROM hiloscores WHERE hilo_stamp>$old") if ($hilo_entries && $hilo_entries>0); if ($new > $km) { $rc += $sqlr if (($new>$km) && sql_command("DELETE FROM hiloscores WHERE (hilo_stamp<$old)")); #logmsg('info',"<$new> DELETE FROM hiloscores WHERE (hilo_stamp<$old)"); } } unless ($hilo_entries && $hilo_entries<0) { my $min = sql_select_one("SELECT max(hilo_score) FROM (SELECT hilo_score FROM hiloscores ORDER BY hilo_score ASC LIMIT $km) AS hilo_score_set"); my $max = sql_select_one("SELECT min(hilo_score) FROM (SELECT hilo_score FROM hiloscores ORDER BY hilo_score DESC LIMIT $km) AS hilo_score_set"); if (defined($min) && defined($max) && $max>$min) { $rc += $sqlr if (sql_command("DELETE FROM hiloscores WHERE (hilo_score<$max && hilo_score>$min)")); #logmsg('info',"DELETE FROM hiloscores WHERE (hilo_score<$max && hilo_score>$min)"); } } logmsg('info','%u hilodb rec(s) removed',$rc) if ($rc>0 || $verbose); return 1; } # Clean expn check list sub gsnddb_clean_list { my $t = time() - $gsnd_keep; logmsg('info','%u gsnddb rec(s) removed',$sqlr) if ((sql_command("DELETE FROM generatedsenders WHERE (gsnd_stamp < $t)") && $sqlr>0) || $verbose); return 1; } # Clean virus cache list sub virus_clean_list { my $t = time() - $virus_keep; logmsg('info','%u avcdb rec(s) removed',$sqlr) if ((sql_command("DELETE FROM virusresults WHERE (vir_stamp < $t)") && $sqlr>0) || $verbose); return 1; } # Clean spam cache list sub spam_clean_list { my $t = time() - $sdb_keep; logmsg('info','%u sdb rec(s) removed',$sqlr) if ((sql_command("DELETE FROM spamresults WHERE (spam_stamp < $t)") && $sqlr>0) || $verbose); return 1; } # Clean bounce collection sub bnc_clean_list { my $t = time() - $bnc_keep; logmsg('info','%u bncdb rec(s) removed',$sqlr) if ((sql_command("DELETE FROM bouncing WHERE (bnc_stamp < $t)") && $sqlr>0) || $verbose); return 1; } # Clean trapper list sub trap_clean_list { my $t = time() - $trap_keep; logmsg('info','%u trap rec(s) removed',$sqlr) if ((sql_command("DELETE FROM trapper WHERE (trap_stamp < $t)") && $sqlr>0) || $verbose); return 1; } # Clean abuse contact list sub abuse_clean_list { my $t = time() - $abuse_keep; logmsg('info','%u abuse rec(s) removed',$sqlr) if ((sql_command("DELETE FROM abuse WHERE (abuse_stamp < $t)") && $sqlr>0) || $verbose); return 1; } # Clean bounce collection sub logs_clean_list { my $t = time() - $logs_keep; logmsg('info','%u logdb rec(s) removed',$sqlr) if ((sql_command("DELETE FROM logs WHERE (logs_stamp < $t)") && $sqlr>0) || $verbose); return 1; } # Defrag, remove emptyness, etc... sub db_vacuum { if ($sqldbd eq 'L') { logmsg('info','database vacuumed') if (sql_command('VACUUM')); } elsif ($sqldbd eq 'M') { logmsg('info','database vacuumed') if (sql_command('OPTIMIZE TABLE '.join(',',@tables))); } else { logmsg('info','cannot vacuum this database'); } } #*********************************************************************** # Main. #*********************************************************************** read_cfg_cfg(); logmsg('info','MIMEDefang-filter Database Cleaner/Creator'); $sqldb = DBI->connect($database_spec,$database_user,$database_pass,{RaiseError=>0,AutoCommit=>1}); if ($sqldb) { sql_create_tables(); sql_command('PRAGMA SYNCHRONOUS=OFF') if ($sqldbd eq 'L'); if ($clean) { dc_clean_list() if ($cleanl eq '' || $cleanl =~ /,d(c|ict)?(db)?,/i); rdb_clean_list() if ($cleanl eq '' || $cleanl =~ /,r(elay)?(db)?,/i); gdb_clean_list() if ($cleanl eq '' || $cleanl =~ /,(g|grey)?h(ost)?(db)?,/i); sc_clean_list() if ($cleanl eq '' || $cleanl =~ /,s(ender)?c(heck)?(db)?,/i); mx_clean_list() if ($cleanl eq '' || $cleanl =~ /,mx(db)?,/i); nsdb_clean_list() if ($cleanl eq '' || $cleanl =~ /,no?s(pam)?(db)?,/i); outdb_clean_list() if ($cleanl eq '' || $cleanl =~ /,(s|sent)?o(ut)?(db)?,/i); pgpdb_clean_list() if ($cleanl eq '' || $cleanl =~ /,pgp(db)?,/i); tsdb_clean_list() if ($cleanl eq '' || $cleanl =~ /,t(ime)?s(tamps?)?(db)?,/i); smtpdb_clean_list() if ($cleanl eq '' || $cleanl =~ /,s(mtp)?(db)?,/i); expndb_clean_list() if ($cleanl eq '' || $cleanl =~ /,e(xpn)?(db)?,/i); hilodb_clean_list() if ($cleanl eq '' || $cleanl =~ /,h(i|igh)?l(o|ow)?(s|score)?(db)?,/i); gsnddb_clean_list() if ($cleanl eq '' || $cleanl =~ /,g(en(erated)?)?se?nd(ers?)?(db)?,/i); virus_clean_list() if ($cleanl eq '' || $cleanl =~ /,a(nti)?v(ir(us)?)?c(ache)?(db)?,/i); spam_clean_list() if ($cleanl eq '' || $cleanl =~ /,s(pam)?(db)?,/i); bnc_clean_list() if ($cleanl eq '' || $cleanl =~ /,b((ou)?nce?)?(db)?,/i); trap_clean_list() if ($cleanl eq '' || $cleanl =~ /,tra?p?(db)?,/i); abuse_clean_list() if ($cleanl eq '' || $cleanl =~ /,a(use)?c(ontact)?(db)?,/i); logs_clean_list() if ($cleanl eq '' || $cleanl =~ /,l(ogs?)?(db)?,/i); } db_vacuum() if ($vacuum); db_restore($restore) if ($restore); db_backup($backup) if ($backup); $sqldb->disconnect(); $sqldb = undef; }