| | 78 | use Getopt::Long; |
| | 79 | |
| | 80 | # constants |
| | 81 | use constant SECS_PER_HOUR => 60 * 60; |
| | 82 | use constant SECS_PER_DAY => SECS_PER_HOUR * 24; |
| | 83 | use constant SUSPECTED_NONSPAM => 1; # Type H |
| | 84 | use constant SUSPECTED_SPAM => 2; # Type S |
| | 85 | use constant VIRUSES => 4; # Type V |
| | 86 | use constant BAD_HEADERS => 8; # Type B |
| | 87 | use constant BANNED_ATTACHMENTS => 16; # Type F |
| | 88 | use constant CONFIRMED_NONSPAM => 32; # Type G |
| | 89 | use constant CONFIRMED_SPAM => 64; # Type C |
| | 90 | use constant ALL => (SUSPECTED_NONSPAM + |
| | 91 | SUSPECTED_SPAM + |
| | 92 | VIRUSES + |
| | 93 | BAD_HEADERS + |
| | 94 | BANNED_ATTACHMENTS + |
| | 95 | CONFIRMED_NONSPAM + |
| | 96 | CONFIRMED_SPAM); |
| | 97 | |
| | 98 | my %types = ( |
| | 99 | "S" => "Suspected Spam", |
| | 100 | "H" => "Suspected Non-Spam", |
| | 101 | "V" => "Virus/Malware", |
| | 102 | "B" => "Invalid Mail Header", |
| | 103 | "F" => "Banned File Attachment", |
| | 104 | "G" => "Confirmed Non-Spam", |
| | 105 | "C" => "Confirmed Spam" |
| | 106 | ); |
| | 137 | # defaults (overridden by settings in /etc/maia.conf) |
| | 138 | $mail_types = (ALL - CONFIRMED_NONSPAM - CONFIRMED_SPAM) |
| | 139 | if !defined($mail_types); |
| | 140 | |
| | 141 | my $suspected_nonspam = ($mail_types & SUSPECTED_NONSPAM) != 0; |
| | 142 | my $suspected_spam = ($mail_types & SUSPECTED_SPAM) != 0; |
| | 143 | my $viruses = ($mail_types & VIRUSES) != 0; |
| | 144 | my $bad_headers = ($mail_types & BAD_HEADERS) != 0; |
| | 145 | my $banned_files = ($mail_types & BANNED_ATTACHMENTS) != 0; |
| | 146 | my $confirmed_nonspam = ($mail_types & CONFIRMED_NONSPAM) != 0; |
| | 147 | my $confirmed_spam = ($mail_types & CONFIRMED_SPAM) != 0; |
| | 148 | my $debug = 0; |
| | 149 | my $quiet = 0; |
| | 150 | |
| | 151 | GetOptions("suspected-nonspam|suspected-ham!" => \$suspected_nonspam, |
| | 152 | "suspected-spam!" => \$suspected_spam, |
| | 153 | "viruses!" => \$viruses, |
| | 154 | "bad-headers!" => \$bad_headers, |
| | 155 | "banned-files|banned-attachments!" => \$banned_files, |
| | 156 | "confirmed-nonspam|confirmed-ham!" => \$confirmed_nonspam, |
| | 157 | "confirmed-spam!" => \$confirmed_spam, |
| | 158 | "debug" => \$debug, |
| | 159 | "quiet" => \$quiet); |
| | 160 | |
| | 161 | # Sanity-check any supplied arguments |
| | 162 | if ($debug && $quiet) { |
| | 163 | $debug = 0; |
| | 164 | $quiet = 0; |
| | 165 | output("Warning: --debug and --quiet negate each other."); |
| | 166 | } |
| | 167 | if (!$suspected_nonspam && ($mail_types & SUSPECTED_NONSPAM)) { |
| | 168 | $mail_types -= SUSPECTED_NONSPAM; |
| | 169 | } elsif ($suspected_nonspam && !($mail_types & SUSPECTED_NONSPAM)) { |
| | 170 | $mail_types += SUSPECTED_NONSPAM; |
| | 171 | } |
| | 172 | if (!$suspected_spam && ($mail_types & SUSPECTED_SPAM)) { |
| | 173 | $mail_types -= SUSPECTED_SPAM; |
| | 174 | } elsif ($suspected_spam && !($mail_types & SUSPECTED_SPAM)) { |
| | 175 | $mail_types += SUSPECTED_SPAM; |
| | 176 | } |
| | 177 | if (!$viruses && ($mail_types & VIRUSES)) { |
| | 178 | $mail_types -= VIRUSES; |
| | 179 | } elsif ($viruses && !($mail_types & VIRUSES)) { |
| | 180 | $mail_types += VIRUSES; |
| | 181 | } |
| | 182 | if (!$bad_headers && ($mail_types & BAD_HEADERS)) { |
| | 183 | $mail_types -= BAD_HEADERS; |
| | 184 | } elsif ($bad_headers && !($mail_types & BAD_HEADERS)) { |
| | 185 | $mail_types += BAD_HEADERS; |
| | 186 | } |
| | 187 | if (!$banned_files && ($mail_types & BANNED_ATTACHMENTS)) { |
| | 188 | $mail_types -= BANNED_ATTACHMENTS; |
| | 189 | } elsif ($banned_files && !($mail_types & BANNED_ATTACHMENTS)) { |
| | 190 | $mail_types += BANNED_ATTACHMENTS; |
| | 191 | } |
| | 192 | if (!$confirmed_nonspam && ($mail_types & CONFIRMED_NONSPAM)) { |
| | 193 | $mail_types -= CONFIRMED_NONSPAM; |
| | 194 | } elsif ($confirmed_nonspam && !($mail_types & CONFIRMED_NONSPAM)) { |
| | 195 | $mail_types += CONFIRMED_NONSPAM; |
| | 196 | } |
| | 197 | if (!$confirmed_spam && ($mail_types & CONFIRMED_SPAM)) { |
| | 198 | $mail_types -= CONFIRMED_SPAM; |
| | 199 | } elsif ($confirmed_spam && !($mail_types & CONFIRMED_SPAM)) { |
| | 200 | $mail_types += CONFIRMED_SPAM; |
| | 201 | } |
| | 202 | |
| | 203 | if ($debug) { |
| | 204 | output("Starting"); |
| | 205 | output(sprintf("Expire Suspected Non-Spam: %s", |
| | 206 | ($mail_types & SUSPECTED_NONSPAM) ? "Yes" : "No")); |
| | 207 | output(sprintf("Expire Suspected Spam: %s", |
| | 208 | ($mail_types & SUSPECTED_SPAM) ? "Yes" : "No")); |
| | 209 | output(sprintf("Expire Viruses/Malware: %s", |
| | 210 | ($mail_types & VIRUSES) ? "Yes" : "No")); |
| | 211 | output(sprintf("Expire items with Invalid Mail Headers: %s", |
| | 212 | ($mail_types & BAD_HEADERS) ? "Yes" : "No")); |
| | 213 | output(sprintf("Expire items with Banned File Attachments: %s", |
| | 214 | ($mail_types & BANNED_ATTACHMENTS) ? "Yes" : "No")); |
| | 215 | output(sprintf("Expire Confirmed Non-Spam: %s", |
| | 216 | ($mail_types & CONFIRMED_NONSPAM) ? "Yes" : "No")); |
| | 217 | output(sprintf("Expire Confirmed Spam: %s", |
| | 218 | ($mail_types & CONFIRMED_SPAM) ? "Yes" : "No")); |
| | 219 | } |
| | 220 | |
| 126 | | output(sprintf("%d quarantined items expired", $qcount)); |
| 127 | | output(sprintf("%d cached non-spam items expired", $hcount)); |
| 128 | | if ($cleanup > 0) { |
| 129 | | output(sprintf("%d orphaned items expired - please investigate!", $cleanup)); |
| 130 | | } |
| 131 | | if ($strayclean > 0) { |
| 132 | | output(sprintf("%d stray items expired - please investigate!", $strayclean)); |
| 133 | | } |
| 134 | | output("Current Cache After Expiration:"); |
| 135 | | output(sprintf("Quarantined Items: %d", $curqcount)); |
| 136 | | output(sprintf("Non-spam Items: %d", $curhamcount)); |
| | 243 | if (!$quiet) { |
| | 244 | output(sprintf("%d Suspected Spam items expired", $s_count)); |
| | 245 | output(sprintf("%d Virus/Malware items expired", $v_count)); |
| | 246 | output(sprintf("%d items with Invalid Mail Headers expired", $b_count)); |
| | 247 | output(sprintf("%d items with Banned File Attachments expired", $f_count)); |
| | 248 | output(sprintf("%d Confirmed Spam items expired", $c_count)); |
| | 249 | output(sprintf("%d Suspected Non-Spam items expired", $h_count)); |
| | 250 | output(sprintf("%d Confirmed Non-Spam items expired", $g_count)); |
| | 251 | if ($cleanup > 0) { |
| | 252 | output(sprintf("%d orphaned items expired", $cleanup)); |
| | 253 | } |
| | 254 | if ($strayclean > 0) { |
| | 255 | output(sprintf("%d stray references expired", $strayclean)); |
| | 256 | } |
| | 257 | output("Current Cache After Expiration:"); |
| | 258 | output(sprintf("Suspected Spam items: %d", $cur_s_count)); |
| | 259 | output(sprintf("Virus/Malware items: %d", $cur_v_count)); |
| | 260 | output(sprintf("Items with Invalid Mail Headers: %d", $cur_b_count)); |
| | 261 | output(sprintf("Items with Banned File Attachments: %d", $cur_f_count)); |
| | 262 | output(sprintf("Suspected Non-Spam items: %d", $cur_h_count)); |
| | 263 | } |
| 248 | | if ($op eq "spam") { |
| 249 | | |
| 250 | | # Delete mail references for all recipients |
| 251 | | # who see this mail item as suspected spam, |
| 252 | | # virus-infected, a banned attachment, or an |
| 253 | | # invalid mail header. |
| 254 | | $delete = "DELETE FROM maia_mail_recipients " . |
| 255 | | "WHERE (type = 'S' OR type = 'B' OR type = 'F' OR type = 'V') " . |
| 256 | | "AND mail_id = ?"; |
| 257 | | } else { |
| 258 | | |
| 259 | | # Delete mail references for all recipients |
| 260 | | # who see this mail item as suspected ham. |
| 261 | | $delete = "DELETE FROM maia_mail_recipients " . |
| 262 | | "WHERE type = 'H' " . |
| 263 | | "AND mail_id = ?"; |
| 264 | | } |
| | 377 | # Delete mail references for all recipients |
| | 378 | # who see this mail item as suspected spam, |
| | 379 | # virus-infected, a banned attachment, or an |
| | 380 | # invalid mail header. |
| | 381 | $delete = "DELETE FROM maia_mail_recipients " . |
| | 382 | "WHERE type = ? " . |
| | 383 | "AND mail_id = ?"; |
| 297 | | |
| 298 | | $days = get_config_value($dbh, "expiry_period"); |
| 299 | | $days = $1 if $days =~ /^([1-9]+[0-9]*)$/si; # untaint |
| 300 | | my $secs = $days * $secsperday; |
| 301 | | output(sprintf("Expiring quarantined items [SVBF] older than %d days", $days)); |
| 302 | | $select = "SELECT DISTINCT maia_mail.id, maia_mail.received_date " . |
| 303 | | "FROM maia_mail, maia_mail_recipients " . |
| 304 | | "WHERE maia_mail.id = maia_mail_recipients.mail_id " . |
| 305 | | "AND (maia_mail_recipients.type = 'S' OR " . |
| 306 | | "maia_mail_recipients.type = 'B' OR " . |
| 307 | | "maia_mail_recipients.type = 'F' OR " . |
| 308 | | "maia_mail_recipients.type = 'V') "; |
| 309 | | if ($dbtype =~ /^mysqli?$/si) { |
| | 413 | my $secs = $days * SECS_PER_DAY; |
| | 414 | my $count = 0; |
| | 415 | |
| | 416 | output(sprintf("Expiring %s items [%s] older than %d days", $types{$type}, $type, $days)) |
| | 417 | if (!$quiet); |
| | 418 | my $select = "SELECT DISTINCT maia_mail.id, maia_mail.received_date " . |
| | 419 | "FROM maia_mail, maia_mail_recipients " . |
| | 420 | "WHERE maia_mail.id = maia_mail_recipients.mail_id " . |
| | 421 | "AND (maia_mail_recipients.type = ?) "; |
| | 422 | if ($dbtype =~ /^mysql$/si) { |
| 314 | | $sth = $dbh->prepare($select) |
| 315 | | or fatal(sprintf("Couldn't prepare query: %s", $dbh->errstr)); |
| 316 | | $sth->execute() |
| 317 | | or fatal(sprintf("Couldn't execute query: %s", $dbh->errstr)); |
| 318 | | $expiry_count = 0; |
| 319 | | while (@row = $sth->fetchrow_array()) { |
| 320 | | delete_mail_references($dbh, $row[0], "spam"); |
| 321 | | $expiry_count++; |
| | 427 | my $sth = $dbh->prepare($select) |
| | 428 | or fatal(sprintf("Couldn't prepare query: %s", $dbh->errstr)); |
| | 429 | $sth->execute($type) |
| | 430 | or fatal(sprintf("Couldn't execute query: %s", $dbh->errstr)); |
| | 431 | while (my @row = $sth->fetchrow_array()) { |
| | 432 | output(sprintf("Deleting %s references to mail item %d", $types{$type}, $row[0])) |
| | 433 | if $debug; |
| | 434 | delete_mail_references($dbh, $row[0], $type); |
| | 435 | $count++; |
| 340 | | my $secs = $days * $secsperday; |
| 341 | | output(sprintf("Expiring cached ham items [H] older than %d days", $days)); |
| 342 | | $select = "SELECT DISTINCT maia_mail.id, maia_mail.received_date " . |
| 343 | | "FROM maia_mail, maia_mail_recipients " . |
| 344 | | "WHERE maia_mail.id = maia_mail_recipients.mail_id " . |
| 345 | | "AND maia_mail_recipients.type = 'H' "; |
| 346 | | if ($dbtype =~ /^mysqli?$/si) { |
| 347 | | $select .= "AND maia_mail.received_date < DATE_SUB(CURRENT_TIMESTAMP, INTERVAL " . $secs . " SECOND)"; |
| 348 | | } elsif ($dbtype =~ /^pg$/si) { |
| 349 | | $select .= "AND maia_mail.received_date < NOW() - INTERVAL '" . $secs . " SECOND'"; |
| 350 | | } |
| 351 | | $sth = $dbh->prepare($select) |
| 352 | | or fatal(sprintf("Couldn't prepare query: %s", $dbh->errstr)); |
| 353 | | $sth->execute() |
| 354 | | or fatal(sprintf("Couldn't execute query: %s", $dbh->errstr)); |
| 355 | | $expiry_count = 0; |
| 356 | | while (@row = $sth->fetchrow_array()) { |
| 357 | | delete_mail_references($dbh, $row[0], "ham"); |
| 358 | | $expiry_count++; |
| 359 | | } |
| 360 | | $sth->finish; |
| 361 | | |
| 362 | | return $expiry_count; |
| 363 | | } |
| 364 | | |
| 365 | | |
| | 455 | |
| | 456 | $s_count = run_expiry_query($dbh, "S", $days) if ($mail_types & SUSPECTED_SPAM); |
| | 457 | $v_count = run_expiry_query($dbh, "V", $days) if ($mail_types & VIRUSES); |
| | 458 | $b_count = run_expiry_query($dbh, "B", $days) if ($mail_types & BAD_HEADERS); |
| | 459 | $f_count = run_expiry_query($dbh, "F", $days) if ($mail_types & BANNED_ATTACHMENTS); |
| | 460 | $c_count = run_expiry_query($dbh, "C", $days) if ($mail_types & CONFIRMED_SPAM); |
| | 461 | |
| | 462 | return ($s_count, $v_count, $b_count, $f_count, $c_count); |
| | 463 | } |
| | 464 | |
| | 465 | |
| | 466 | # Expire cached ham items [HG] older than the specified |
| | 467 | # expiration period. |
| | 468 | sub expire_ham_cache($$) { |
| | 469 | my($dbh, $mail_types) = @_; |
| | 470 | my $h_count = 0; |
| | 471 | my $g_count = 0; |
| | 472 | |
| | 473 | my $days = get_config_value($dbh, "ham_cache_expiry_period"); |
| | 474 | $days = $1 if $days =~ /^([1-9]+[0-9]*)$/si; # untaint |
| | 475 | |
| | 476 | $h_count = run_expiry_query($dbh, "H", $days) if ($mail_types & SUSPECTED_NONSPAM); |
| | 477 | $g_count = run_expiry_query($dbh, "G", $days) if ($mail_types & CONFIRMED_NONSPAM); |
| | 478 | |
| | 479 | return ($h_count, $g_count); |
| | 480 | } |
| | 481 | |
| | 482 | |
| | 483 | # Expire orphaned items older than one hour. Orphans are mail items |
| | 484 | # that exist in spite of the fact that no recipient references are |
| | 485 | # left pointing to it. Orphans should not exist--they suggest that |
| | 486 | # a bug or some other database inconsistency is to blame--but we do |
| | 487 | # basic housecleaning here just in case. |
| 456 | | my($select, $sth, @row); |
| 457 | | my($q_count); |
| 458 | | |
| 459 | | $select = "SELECT COUNT(DISTINCT maia_mail_recipients.mail_id) " . |
| 460 | | "FROM maia_mail_recipients " . |
| 461 | | "WHERE (maia_mail_recipients.type = 'S' OR " . |
| 462 | | "maia_mail_recipients.type = 'B' OR " . |
| 463 | | "maia_mail_recipients.type = 'F' OR " . |
| 464 | | "maia_mail_recipients.type = 'V') "; |
| 465 | | |
| 466 | | $sth= $dbh->prepare($select) |
| 467 | | or fatal(sprintf("Couldn't prepare query: %s", $dbh->errstr)); |
| 468 | | $sth->execute() |
| 469 | | or fatal(sprintf("Couldn't execute query: %s", $dbh->errstr)); |
| 470 | | @row = $sth->fetchrow_array(); |
| 471 | | $q_count = $row[0]; |
| 472 | | |
| 473 | | $sth->finish; |
| 474 | | |
| 475 | | return $q_count; |
| 476 | | |
| | 592 | |
| | 593 | my $cur_s_count = run_count_query($dbh, "S"); |
| | 594 | my $cur_v_count = run_count_query($dbh, "V"); |
| | 595 | my $cur_b_count = run_count_query($dbh, "B"); |
| | 596 | my $cur_f_count = run_count_query($dbh, "F"); |
| | 597 | |
| | 598 | return ($cur_s_count, $cur_v_count, $cur_b_count, $cur_f_count); |