*** maia-1.0.0-rc5-2/amavisd-p10-orig Wed Jun 30 04:30:23 2004 --- maia-1.0.0-rc5-2/amavisd-maia Fri Jul 2 02:30:09 2004 *************** *** 1,5 **** --- 1,18 ---- #!/usr/bin/perl -T + # This is amavisd-maia, a derivative of amavisd-new that has been modified to + # include support for Maia Mailguard. Maia Mailguard is a set of PHP4 and + # Perl scripts designed to offer users the ability to view and modify personal + # virus- and spam-checking preferences, whitelists/blacklists, manage their + # quarantined files, and report spam effectively. + # + # Maia Mailguard was written by Robert LeBlanc , and is + # available at http://www.renaissoft.com/maia/ + # + # $Id: amavisd-maia,v 1.40 2004/07/02 09:30:09 rjl Exp $ + #------------------------------------------------------------------------------ + # What follows here are the comments from amavisd-new-20030616-p10: + # # This is amavisd-new. # It is a high-performance interface between message transfer agent (MTA) *************** *** 86,89 **** --- 99,104 ---- # Amavis::AV # Amavis::SpamControl + #Maia-related packages: -------------------------------------------------------- + # Amavis::Maia #------------------------------------------------------------------------------ *************** *** 200,203 **** --- 215,220 ---- $spam_modifies_subj_ldap $local_domains_ldap %local_domains @local_domains_acl $local_domains_re + %no_autocreate_domains @no_autocreate_domains_acl $no_autocreate_domains_re + $key_file )], 'notifyconf' => [qw( *************** *** 239,242 **** --- 256,260 ---- $unicode_aware $eol + $encryption_key &D_REJECT &D_BOUNCE &D_DISCARD &D_PASS )], *************** *** 247,254 **** use POSIX qw(uname); use Errno qw(ENOENT); use vars @EXPORT; ! $myversion = 'amavisd-new-20030616-p10'; $eol = "\n"; # native record separator in files: LF or CRLF or even CR --- 265,273 ---- use POSIX qw(uname); use Errno qw(ENOENT); + use IO::File; use vars @EXPORT; ! $myversion = 'amavisd-maia-1.0.0-rc5'; $eol = "\n"; # native record separator in files: LF or CRLF or even CR *************** *** 355,360 **** # the %k will be sender addresses (e.g. full address, domain only, catchall). $sql_select_white_black_list = ! 'SELECT wb FROM wblist,mailaddr'. ! ' WHERE (wblist.rid=?) AND (wblist.sid=mailaddr.id) AND (mailaddr.email IN (%k))'. ' ORDER BY mailaddr.priority DESC'; --- 374,382 ---- # the %k will be sender addresses (e.g. full address, domain only, catchall). $sql_select_white_black_list = ! 'SELECT wblist.wb FROM wblist,mailaddr,users'. ! ' WHERE (users.id=?)'. ! ' AND (wblist.rid=users.maia_user_id)'. ! ' AND (wblist.sid=mailaddr.id)'. ! ' AND (mailaddr.email IN (%k))'. ' ORDER BY mailaddr.priority DESC'; *************** *** 559,566 **** *local_domains = \@local_domains_acl; # read and evaluate configuration file sub read_config($) { my($config_file) = @_; ! my($msg); my($errn) = stat($config_file) ? 0 : 0+$!; if ($errn == ENOENT) { $msg = "does not exist" } --- 581,591 ---- *local_domains = \@local_domains_acl; + # By default all domains should allow user autocreation + @no_autocreate_domains_acl = qw(); + # read and evaluate configuration file sub read_config($) { my($config_file) = @_; ! my($msg, $fh); my($errn) = stat($config_file) ? 0 : 0+$!; if ($errn == ENOENT) { $msg = "does not exist" } *************** *** 586,589 **** --- 611,622 ---- $pid_file = "$MYHOME/amavisd.pid" if !defined $pid_file; $lock_file = "$MYHOME/amavisd.lock" if !defined $lock_file; + $encryption_key = undef; + if (defined $key_file) { + my $fh = new IO::File; + if ($fh->open("<" . $key_file)) { + sysread($fh, $encryption_key, 56); + $fh->close; + } + } $hdrfrom_notify_sender = "amavisd-new " if !defined $hdrfrom_notify_sender; *************** *** 1625,1629 **** import Amavis::Conf qw(:platform $recipient_delimiter $localpart_is_case_sensitive ! %local_domains @local_domains_acl $local_domains_re); import Amavis::Timing qw(section_time); import Amavis::rfc2821_2822_Tools qw(split_address split_localpart); --- 1658,1663 ---- import Amavis::Conf qw(:platform $recipient_delimiter $localpart_is_case_sensitive ! %local_domains @local_domains_acl $local_domains_re ! %no_autocreate_domains @no_autocreate_domains_acl $no_autocreate_domains_re); import Amavis::Timing qw(section_time); import Amavis::rfc2821_2822_Tools qw(split_address split_localpart); *************** *** 1899,1902 **** --- 1933,4296 ---- # + package Amavis::Maia; + use strict; + + BEGIN { + use Exporter (); + use vars qw(@ISA @EXPORT @EXPORT_OK %EXPORT_TAGS $VERSION); + use POSIX qw(strftime); + use Net::SMTP; + use Digest::MD5; + $VERSION = '1.15'; + @ISA = qw(Exporter); + %EXPORT_TAGS = (); + @EXPORT = (); + @EXPORT_OK = qw(&maia_connect + &maia_disconnect + &maia_read_system_config + &maia_autocreate_users + &maia_store_mail + &maia_get_recipient_id + &maia_set_mail_status + &maia_record_viruses + &maia_record_tests + &maia_record_banned_files + &maia_delete_mail + &maia_get_database_type + &maia_get_mysql_size_limit + &maia_should_discard_ham + &maia_delete_mail_recipient_reference + &maia_discard_if_requested + &maia_recipient_is_local + &maia_recipient_can_be_autocreated + &maia_cleanup); + } + use subs @EXPORT_OK; + + BEGIN { + import Amavis::Conf qw(:confvars :platform); + import Amavis::Timing qw(section_time); + import Amavis::Util qw(do_log prolong_timer); + import Amavis::Lookup qw(lookup); + import Amavis::rfc2821_2822_Tools qw(split_address); + import Mail::SpamAssassin; + } + + # Connect to a Maia DSN (not exported) + sub maia_connect_to_sql(@) { + my(@dsns) = @_; # a list of DSNs to try connecting to sequentially + my($dbh); + do_log(3,"Maia: [connect_to_sql] Connecting to SQL database server"); + for my $tmpdsn (@dsns) { + my($dsn, $username, $password) = @$tmpdsn; + do_log(4, "Maia: [connect_to_sql] Trying '$dsn'"); + $dbh = DBI->connect($dsn, $username, $password, + {PrintError => 0, RaiseError => 0, Taint => 1, AutoCommit => 1} ); + if ($dbh) { do_log(3,"Maia: [connect_to_sql] '$dsn' succeeded"); last } + do_log(0, "Maia: [connect_to_sql] Couldn't connect to DSN '$dsn': " . + $DBI::errstr); + } + do_log(0, "Maia: [connect_to_sql] Couldn't connect to any DSN at all!") if !$dbh && @dsns>1; + + return $dbh; + } + + + # Connect to the Maia database, return a connection handle + sub maia_connect() { + my($dbh) = maia_connect_to_sql(@lookup_sql_dsn); + if (!defined($dbh)) { + do_log(0, "Maia: [connect] SQL server(s) not reachable, ABORTING"); + die; + } else { + $dbh->{'RaiseError'} = 1; + my $dbtype = maia_get_database_type($dbh); + if ($dbtype =~ /^mysql$/si) { + do_log(3, "Maia: [connect] Database type is MySQL"); + } elsif ($dbtype =~ /^pg$/si) { + do_log(3, "Maia: [connect] Database type is PostgreSQL"); + } else { + do_log(0, sprintf("Maia: [connect] Database type '%s' is not supported, ABORTING", $dbtype)); + die; + } + } + section_time("maia_connect"); + return $dbh; + } + + + # Close the connection to the Maia database + sub maia_disconnect($) { + my($dbh) = @_; + + do_log(3, "Maia: [disconnect] Disconnecting from SQL database"); + $dbh->disconnect(); + section_time("maia_disconnect"); + } + + + # Read amavisd-relevant system-wide Maia configuration parameters + sub maia_read_system_config($) { + my($dbh) = @_; + my($query, $sth, @row); + my $enable_false_negative_management = 0; + my $enable_virus_checking = 0; + my $enable_spam_checking = 0; + my $enable_bad_header_checking = 0; + my $enable_banned_files_checking = 0; + my $enable_user_autocreation = 0; + my $enable_stats_tracking = 0; + my $enable_spamtraps = 0; + my $size_limit = 0; + my $oversize_policy = "R"; + my $internal_auth = 0; + my $system_default_user_is_local = 1; + + $query = "SELECT enable_false_negative_management, " . + "enable_virus_scanning, " . + "enable_spam_filtering, " . + "enable_bad_header_checking, " . + "enable_banned_files_checking, " . + "enable_user_autocreation, " . + "enable_stats_tracking, " . + "enable_spamtraps, " . + "size_limit, " . + "oversize_policy, " . + "internal_auth, " . + "system_default_user_is_local " . + "FROM maia_config"; + + $sth = $dbh->prepare($query) + or do_log(0, sprintf("Maia: [read_system_config] Couldn't prepare query: %s", $dbh->errstr)); + $sth->execute() + or do_log(0, sprintf("Maia: [read_system_config] Couldn't execute query: %s", $sth->errstr)); + + if (@row = $sth->fetchrow_array()) { + $enable_false_negative_management = 1 * ($row[0] =~ /^Y$/); + $enable_virus_checking = 1 * ($row[1] =~ /^Y$/); + $enable_spam_checking = 1 * ($row[2] =~ /^Y$/); + $enable_bad_header_checking = 1 * ($row[3] =~ /^Y$/); + $enable_banned_files_checking = 1 * ($row[4] =~ /^Y$/); + $enable_user_autocreation = 1 * ($row[5] =~ /^Y$/); + $enable_stats_tracking = 1 * ($row[6] =~ /^Y$/); + $enable_spamtraps = 1 * ($row[7] =~ /^Y$/); + $size_limit = $1 if $row[8] =~ /^([0-9]+)$/si; + $oversize_policy = $1 if $row[9] =~ /^([PB])$/si; + $internal_auth = 1 * ($row[10] =~ /^Y$/); + $system_default_user_is_local = 1 * ($row[11] =~ /^Y$/); + } else { + do_log(0, "Maia: [read_system_config] Config table is empty!"); + die "Maia configuration table is empty, ABORTING"; + } + + $sth->finish; + + do_log(3, sprintf("Maia: [read_system_config] False negative management is %s", ($enable_false_negative_management ? "ENABLED" : "DISABLED"))); + do_log(3, sprintf("Maia: [read_system_config] Virus scanning is %s", ($enable_virus_checking ? "ENABLED" : "DISABLED"))); + do_log(3, sprintf("Maia: [read_system_config] Spam filtering is %s", ($enable_spam_checking ? "ENABLED" : "DISABLED"))); + do_log(3, sprintf("Maia: [read_system_config] Bad header checking is %s", ($enable_bad_header_checking ? "ENABLED" : "DISABLED"))); + do_log(3, sprintf("Maia: [read_system_config] Banned files checking is %s", ($enable_banned_files_checking ? "ENABLED" : "DISABLED"))); + do_log(3, sprintf("Maia: [read_system_config] User autocreation is %s", ($enable_user_autocreation ? "ENABLED" : "DISABLED"))); + do_log(3, sprintf("Maia: [read_system_config] Internal authentication mechanism is %s", ($internal_auth ? "ENABLED" : "DISABLED"))); + do_log(3, sprintf("Maia: [read_system_config] Stats tracking is %s", ($enable_stats_tracking ? "ENABLED" : "DISABLED"))); + do_log(3, sprintf("Maia: [read_system_config] Spam traps are %s", ($enable_spamtraps ? "ENABLED" : "DISABLED"))); + do_log(3, sprintf("Maia: [read_system_config] Mail larger than %ld bytes will be %s", $size_limit, ($oversize_policy =~ /^P$/si ? "PASSED" : "BOUNCED"))); + do_log(3, sprintf("Maia: [read_system_config] The system default user (@.) %s apply to non-local recipients", ($system_default_user_is_local ? "WILL NOT" : "WILL"))); + do_log(3, sprintf("Maia: [read_system_config] Blowfish encryption is %s", ((defined $encryption_key) ? "ENABLED" : "DISABLED"))); + + section_time("maia_read_system_config"); + return $enable_false_negative_management, + $enable_virus_checking, + $enable_spam_checking, + $enable_bad_header_checking, + $enable_banned_files_checking, + $enable_user_autocreation, + $enable_stats_tracking, + $enable_spamtraps, + $size_limit, + $oversize_policy, + $internal_auth, + $system_default_user_is_local; + } + + + # Encrypt a text string using a specified key (not exported). + sub maia_encrypt_text($$) { + my ($key, $plaintext) = @_; + my ($cipher); + + use Crypt::CBC; + + $key = $1 if $key =~ /^([^\0]{56})$/si; # untaint + $cipher = Crypt::CBC->new( {'key' => $key, + 'cipher' => 'Blowfish', + 'regenerate_key' => 0, + 'padding' => 'null', + 'prepend_iv' => 1 + } ); + + return $cipher->encrypt($plaintext); + } + + + # Returns a randomly-generated 8-character alphanumeric + # password and its 32-byte MD5 hash. (not exported) + sub maia_generate_random_password() { + my(@chars, $password, $digest); + my($md5) = Digest::MD5->new; + + @chars = ("A".."Z", "a".."z", 0..9); + $password = join("", @chars[ map { rand @chars } (1..8) ]); + $md5->add($password); + $digest = $1 if $md5->hexdigest =~ /^([0-9a-f]{32})$/; # untaint + return ($password, $digest); + } + + + # Returns the name of the DBD driver in use + # e.g. "mysql", "pg", etc. + sub maia_get_database_type($) { + my ($dbh) = @_; + + return $dbh->{Driver}->{Name}; + } + + + # Returns MySQL's max_allowed_packet size, in bytes + sub maia_get_mysql_size_limit($) { + my ($dbh) = @_; + my ($sth, @row, $show, $bytes); + + $bytes = 0; + $show = "SHOW VARIABLES LIKE 'max_allowed_packet'"; + $sth = $dbh->prepare($show) + or die (sprintf("Maia: [get_mysql_size_limit] Couldn't prepare query: %s", $dbh->errstr)); + $sth->execute() + or die (sprintf("Maia: [get_mysql_size_limit] Couldn't execute query: %s", $dbh->errstr)); + if (@row = $sth->fetchrow_array()) { + $bytes = $1 if $row[1] =~ /^([0-9]+)$/si; # untaint + } + do_log(3, sprintf("Maia: [get_mysql_size_limit] MySQL max_allowed_packet size is %ld bytes", $bytes)); + + section_time("maia_get_mysql_size_limit"); + return $bytes; + } + + + # Sends an e-mail via the downstream MTA (not exported) + sub maia_send_email($$$$$$) { + my ($smtp_server, $smtp_port, $from, $to, $subject, $body) = @_; + + my($smtp) = Net::SMTP->new($smtp_server, Port => $smtp_port) ; + die "Couldn't connect to SMTP server" unless $smtp; + $smtp->mail($from) ; + $smtp->to($to) ; + $smtp->data() ; + $smtp->datasend("To: " . $to . "\n"); + $smtp->datasend("From: Maia Mailguard <" . $from . ">\n"); + $smtp->datasend("Subject: " . $subject . "\n"); + $smtp->datasend("\n"); + $smtp->datasend($body . "\n") ; + $smtp->dataend() ; + $smtp->quit() ; + } + + + # Assigns a random password to a new user and sends + # the user an e-mail with temporary login credentials. (not exported) + sub maia_issue_password($$$) { + my ($dbh, $user_email, $user_id) = @_; + my ($select, $update, $sth, @row); + my ($smtp_server, $smtp_port, $admin_email, $newuser_template_file, $login_url); + $select = "SELECT admin_email, " . + "newuser_template_file, " . + "reminder_login_url, " . + "smtp_server, " . + "smtp_port " . + "FROM maia_config WHERE id = 0"; + $sth = $dbh->prepare($select) + or die (sprintf("Maia: [issue_password] Couldn't prepare query: %s", $dbh->errstr)); + $sth->execute() + or die (sprintf("Maia: [issue_password] Couldn't execute query: %s", $dbh->errstr)); + if (@row = $sth->fetchrow_array()) { + $admin_email = $1 if $row[0] =~ /^(.+\@.+)$/si; # untaint + $newuser_template_file = $1 if $row[1] =~ /^(.+)$/si; # untaint + $login_url = $1 if $row[2] =~ /^(.+)$/si; # untaint + $smtp_server = $1 if $row[3] =~ /^(.+)$/si; # untaint + $smtp_port = $1 if $row[4] =~ /^([1-9]+[0-9]*)$/si; # untaint + } + $sth->finish; + + # Read the e-mail template file into memory once + open TEMPLATEFILE, "<" . $newuser_template_file + or die ("Maia: [issue_password] Unable to open $newuser_template_file\n"); + my($template) = ""; + my($line); + while ($line = ) { + $template .= $line; + } + close TEMPLATEFILE; + + my $subject = "Welcome to Maia Mailguard"; + my($password, $digest) = maia_generate_random_password(); + + $update = "UPDATE maia_users SET password = ? WHERE id = ?"; + $sth = $dbh->prepare($update) + or die (sprintf("Maia: [issue_password] Couldn't prepare query: %s", $dbh->errstr)); + $sth->execute($digest, $user_id) + or die (sprintf("Maia: [issue_password] Couldn't execute query: %s", $dbh->errstr)); + + # Perform substitutions in the template + my($body) = $template; + $body =~ s/%%LOGIN%%/$user_email/g; + $body =~ s/%%PASSWORD%%/$password/g; + $body =~ s/%%ADMINEMAIL%%/$admin_email/g; + $body =~ s/%%LOGINURL%%/$login_url/g; + + # Send the e-mail + maia_send_email($smtp_server, $smtp_port, $admin_email, $user_email, $subject, $body); + } + + + # Determine whether an e-mail recipient is "local" or not + sub maia_recipient_is_local($$$) { + my($dbh, $recipient, $local_domain_tables) = @_; + my($select, $sth, @row, $domain); + my $is_local = 0; + + $recipient = $1 if $recipient =~ /^(.+\@.+)$/si; # untaint + $domain = $1 if $recipient =~ /^.+(\@.+)$/si; # untaint; + + $select = "SELECT id FROM maia_domains WHERE domain = ?"; + $sth = $dbh->prepare($select) + or do_log(0, sprintf("Maia: [recipient_is_local] Couldn't prepare query: %s", $dbh->errstr)); + $sth->execute($domain) + or do_log(0, sprintf("Maia: [recipient_is_local] Couldn't execute query: %s", $dbh->errstr)); + $is_local = ((@row = $sth->fetchrow_array()) || lookup($recipient, @$local_domain_tables)); + $sth->finish; + do_log(3, sprintf("Maia: [recipient_is_local] Recipient %s is %slocal", $recipient, $is_local ? "" : "non-")); + + return $is_local; + } + + + # Determine whether user-autocreation is allowed for a (local) recipient + sub maia_recipient_can_be_autocreated($$$) { + my($dbh, $recipient, $no_autocreate_domain_tables) = @_; + my($select, $sth, @row, $domain); + my $can_be_autocreated = 0; + + $recipient = $1 if $recipient =~ /^(.+\@.+)$/si; # untaint + $domain = $1 if $recipient =~ /^.+(\@.+)$/si; # untaint; + + $select = "SELECT id FROM maia_domains WHERE domain = ? AND enable_user_autocreation = 'N'"; + $sth = $dbh->prepare($select) + or do_log(0, sprintf("Maia: [recipient_can_be_autocreated] Couldn't prepare query: %s", $dbh->errstr)); + $sth->execute($domain) + or do_log(0, sprintf("Maia: [recipient_can_be_autocreated] Couldn't execute query: %s", $dbh->errstr)); + $can_be_autocreated = !((@row = $sth->fetchrow_array()) || lookup($recipient, @$no_autocreate_domain_tables)); + $sth->finish; + do_log(3, sprintf("Maia: [recipient_can_be_autocreated] Recipient %s can%s be auto-created", + $recipient, $can_be_autocreated ? "" : "not")); + + return $can_be_autocreated; + } + + + # Auto-create users as necessary from the recipient list + sub maia_autocreate_users($$$$$) { + my($dbh, $msginfo, $internal_auth, $local_domain_tables, + $no_autocreate_domain_tables) = @_; + my($select, $insert, $update, $sth, $sth2, @row, @row2); + my($recipient, $policy_id, $user_id, $domain, $email_id); + my($policy_virus_lover, $policy_spam_lover, $policy_banned_files_lover); + my($policy_bad_header_lover, $policy_bypass_virus_checks); + my($policy_bypass_spam_checks, $policy_bypass_banned_checks); + my($policy_bypass_header_checks, $policy_spam_modifies_subj); + my($policy_discard_viruses, $policy_discard_spam); + my($policy_discard_banned_files, $policy_discard_bad_headers); + my($policy_spam_tag_level, $policy_spam_tag2_level, $policy_spam_kill_level); + my $nodefault = 0; + my $dbtype = maia_get_database_type($dbh); + + $select = "SELECT id FROM users WHERE email = ?"; + $sth = $dbh->prepare($select) + or do_log(0, sprintf("Maia: [autocreate_users] Couldn't prepare query: %s", $dbh->errstr)); + for my $r (@{$msginfo->per_recip_data}) { + + if (maia_recipient_is_local($dbh, $r->recip_addr, $local_domain_tables)) { + + if (maia_recipient_can_be_autocreated($dbh, $r->recip_addr, $no_autocreate_domain_tables)) { + + $recipient = $1 if $r->recip_addr =~ /^(.+\@.+)$/si; # untaint + + $sth->execute($recipient) + or do_log(0, sprintf("Maia: [autocreate_users] Couldn't execute query: %s", $dbh->errstr)); + + if (@row = $sth->fetchrow_array()) { + do_log(3, sprintf("Maia: [autocreate_users] Recipient %s already exists", $recipient)); + } else { + do_log(3, sprintf("Maia: [autocreate_users] Adding new recipient %s", $recipient)); + + # create a new user + if ($dbtype =~ /^mysql$/si) { # MySQL + + $insert = "INSERT INTO maia_users (user_name) VALUES (?)"; + $sth2 = $dbh->prepare($insert) + or do_log(0, sprintf("Maia: [autocreate_users] Couldn't prepare query: %s", $dbh->errstr)); + $sth2->execute($recipient) + or do_log(0, sprintf("Maia: [autocreate_users] Couldn't execute query: %s", $dbh->errstr)); + $select = "SELECT LAST_INSERT_ID()"; + $sth2 = $dbh->prepare($select) + or do_log(0, sprintf("Maia: [autocreate_users] Couldn't prepare query: %s", $dbh->errstr)); + $sth2->execute() + or do_log(0, sprintf("Maia: [autocreate_users] Couldn't execute query: %s", $dbh->errstr)); + if (@row2 = $sth2->fetchrow_array()) { + $user_id = $1 if $row2[0] =~ /^([1-9]+[0-9]*)$/si; # untaint + } else { + do_log(0, sprintf("Maia: [autocreate_users] Can't find new user %s", $recipient)); + } + $sth2->finish; + + } elsif ($dbtype =~ /^pg$/si) { # PostgreSQL + + $select = "SELECT NEXTVAL('maia_users_id_seq')"; + $sth2 = $dbh->prepare($select) + or do_log(0, sprintf("Maia: [autocreate_users] Couldn't prepare query: %s", $dbh->errstr)); + $sth2->execute() + or do_log(0, sprintf("Maia: [autocreate_users] Couldn't execute query: %s", $dbh->errstr)); + if (@row2 = $sth2->fetchrow_array()) { + $user_id = $1 if $row2[0] =~ /^([1-9]+[0-9]*)$/si; # untaint + } else { + do_log(0, sprintf("Maia: [autocreate_users] Can't find new user %s", $recipient)); + } + $sth2->finish; + $insert = "INSERT INTO maia_users (id, user_name) VALUES (?, ?)"; + $sth2 = $dbh->prepare($insert) + or do_log(0, sprintf("Maia: [autocreate_users] Couldn't prepare query: %s", $dbh->errstr)); + $sth2->execute($user_id, $recipient) + or do_log(0, sprintf("Maia: [autocreate_users] Couldn't execute query: %s", $dbh->errstr)); + } + + # create a new policy for this user + # - try to find a suitable default policy based on the recipient's domain first + # - use the 'Default' policy next, if it exists + # - use the hard-coded database defaults, if all else fails + my($localpart,$domain) = split_address($recipient); + $domain = lc($domain); + $domain = $1 if $domain =~ /^(.+\..+)$/si; # untaint + $select = "SELECT virus_lover, " . + "spam_lover, " . + "banned_files_lover, " . + "bad_header_lover, " . + "bypass_virus_checks, " . + "bypass_spam_checks, " . + "bypass_banned_checks, " . + "bypass_header_checks, " . + "discard_viruses, " . + "discard_spam, " . + "discard_banned_files, " . + "discard_bad_headers, " . + "spam_modifies_subj, " . + "spam_tag_level, " . + "spam_tag2_level, " . + "spam_kill_level " . + "FROM policy WHERE policy_name = ?"; + $sth2 = $dbh->prepare($select) + or do_log(0, sprintf("Maia: [autocreate_users] Couldn't prepare query: %s", $dbh->errstr)); + $sth2->execute($domain) + or do_log(0, sprintf("Maia: [autocreate_users] Couldn't execute query: %s", $dbh->errstr)); + if (@row2 = $sth2->fetchrow_array()) { + # use domain default policy settings + do_log(3, sprintf("Maia: [autocreate_users] Using default policies for domain %s", $domain)); + $policy_virus_lover = $1 if $row2[0] =~ /^([YN])$/si; # untaint + $policy_spam_lover = $1 if $row2[1] =~ /^([YN])$/si; # untaint + $policy_banned_files_lover = $1 if $row2[2] =~ /^([YN])$/si; # untaint + $policy_bad_header_lover = $1 if $row2[3] =~ /^([YN])$/si; # untaint + $policy_bypass_virus_checks = $1 if $row2[4] =~ /^([YN])$/si; # untaint + $policy_bypass_spam_checks = $1 if $row2[5] =~ /^([YN])$/si; # untaint + $policy_bypass_banned_checks = $1 if $row2[6] =~ /^([YN])$/si; # untaint + $policy_bypass_header_checks = $1 if $row2[7] =~ /^([YN])$/si; # untaint + $policy_discard_viruses = $1 if $row2[8] =~ /^([YN])$/si; # untaint + $policy_discard_spam = $1 if $row2[9] =~ /^([YN])$/si; # untaint + $policy_discard_banned_files = $1 if $row2[10] =~ /^([YN])$/si; # untaint + $policy_discard_bad_headers = $1 if $row2[11] =~ /^([YN])$/si; # untaint + $policy_spam_modifies_subj = $1 if $row2[12] =~ /^([YN])$/si; # untaint + $policy_spam_tag_level = $1 if $row2[13] =~ /^([0-9\-\.]+)$/si; # untaint + $policy_spam_tag2_level = $1 if $row2[14] =~ /^([0-9\-\.]+)$/si; # untaint + $policy_spam_kill_level = $1 if $row2[15] =~ /^([0-9\-\.]+)$/si; # untaint + $nodefault = 0; + } else { + $sth2->execute("Default") + or do_log(0, sprintf("Maia: [autocreate_users] Couldn't execute query: %s", $dbh->errstr)); + if (@row2 = $sth2->fetchrow_array()) { + # use global default policy settings + do_log(3, "Maia: [autocreate_users] Using global default policies"); + $policy_virus_lover = $1 if $row2[0] =~ /^([YN])$/si; # untaint + $policy_spam_lover = $1 if $row2[1] =~ /^([YN])$/si; # untaint + $policy_banned_files_lover = $1 if $row2[2] =~ /^([YN])$/si; # untaint + $policy_bad_header_lover = $1 if $row2[3] =~ /^([YN])$/si; # untaint + $policy_bypass_virus_checks = $1 if $row2[4] =~ /^([YN])$/si; # untaint + $policy_bypass_spam_checks = $1 if $row2[5] =~ /^([YN])$/si; # untaint + $policy_bypass_banned_checks = $1 if $row2[6] =~ /^([YN])$/si; # untaint + $policy_bypass_header_checks = $1 if $row2[7] =~ /^([YN])$/si; # untaint + $policy_discard_viruses = $1 if $row2[8] =~ /^([YN])$/si; # untaint + $policy_discard_spam = $1 if $row2[9] =~ /^([YN])$/si; # untaint + $policy_discard_banned_files = $1 if $row2[10] =~ /^([YN])$/si; # untaint + $policy_discard_bad_headers = $1 if $row2[11] =~ /^([YN])$/si; # untaint + $policy_spam_modifies_subj = $1 if $row2[12] =~ /^([YN])$/si; # untaint + $policy_spam_tag_level = $1 if $row2[13] =~ /^([0-9\-\.]+)$/si; # untaint + $policy_spam_tag2_level = $1 if $row2[14] =~ /^([0-9\-\.]+)$/si; # untaint + $policy_spam_kill_level = $1 if $row2[15] =~ /^([0-9\-\.]+)$/si; # untaint + $nodefault = 0; + } else { + do_log(0, "Maia: [autocreate_users] No default policy entry (@.) exists!"); + do_log(3, "Maia: [autocreate_users] Using database default policies"); + $nodefault = 1; + } + } + $sth2->finish; + + if ($dbtype =~ /^mysql$/si) { # MySQL + if ($nodefault) { + $insert = "INSERT INTO policy (policy_name) VALUES (?)"; + $sth2 = $dbh->prepare($insert) + or do_log(0, sprintf("Maia: [autocreate_users] Couldn't prepare query: %s", $dbh->errstr)); + $sth2->execute($recipient) + or do_log(0, sprintf("Maia: [autocreate_users] Couldn't execute query: %s", $dbh->errstr)); + } else { + $insert = "INSERT INTO policy (policy_name, " . + "virus_lover, " . + "spam_lover, " . + "banned_files_lover, " . + "bad_header_lover, " . + "bypass_virus_checks, " . + "bypass_spam_checks, " . + "bypass_banned_checks, " . + "bypass_header_checks, " . + "discard_viruses, " . + "discard_spam, " . + "discard_banned_files, " . + "discard_bad_headers, " . + "spam_modifies_subj, " . + "spam_tag_level, " . + "spam_tag2_level, " . + "spam_kill_level" . + ") VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)"; + $sth2 = $dbh->prepare($insert) + or do_log(0, sprintf("Maia: [autocreate_users] Couldn't prepare query: %s", $dbh->errstr)); + $sth2->execute($recipient, + $policy_virus_lover, + $policy_spam_lover, + $policy_banned_files_lover, + $policy_bad_header_lover, + $policy_bypass_virus_checks, + $policy_bypass_spam_checks, + $policy_bypass_banned_checks, + $policy_bypass_header_checks, + $policy_discard_viruses, + $policy_discard_spam, + $policy_discard_banned_files, + $policy_discard_bad_headers, + $policy_spam_modifies_subj, + $policy_spam_tag_level, + $policy_spam_tag2_level, + $policy_spam_kill_level) + or do_log(0, sprintf("Maia: [autocreate_users] Couldn't execute query: %s", $dbh->errstr)); + } + $select = "SELECT LAST_INSERT_ID()"; + $sth2 = $dbh->prepare($select) + or do_log(0, sprintf("Maia: [autocreate_users] Couldn't prepare query: %s", $dbh->errstr)); + $sth2->execute() + or do_log(0, sprintf("Maia: [autocreate_users] Couldn't execute query: %s", $dbh->errstr)); + if (@row2 = $sth2->fetchrow_array()) { + $policy_id = $1 if $row2[0] =~ /^([1-9]+[0-9]*)$/si; # untaint + } else { + do_log(0, sprintf("Maia: [autocreate_users] Can't find new policy for %s", $recipient)); + } + $sth2->finish; + + } elsif ($dbtype =~ /^pg$/si) { # PostgreSQL + + $select = "SELECT NEXTVAL('policy_id_seq')"; + $sth2 = $dbh->prepare($select) + or do_log(0, sprintf("Maia: [autocreate_users] Couldn't prepare query: %s", $dbh->errstr)); + $sth2->execute() + or do_log(0, sprintf("Maia: [autocreate_users] Couldn't execute query: %s", $dbh->errstr)); + if (@row2 = $sth2->fetchrow_array()) { + $policy_id = $1 if $row2[0] =~ /^([1-9]+[0-9]*)$/si; # untaint + } else { + do_log(0, sprintf("Maia: [autocreate_users] Can't find new policy for %s", $recipient)); + } + $sth2->finish; + if ($nodefault) { + $insert = "INSERT INTO policy (id, policy_name) VALUES (?, ?)"; + $sth2 = $dbh->prepare($insert) + or do_log(0, sprintf("Maia: [autocreate_users] Couldn't prepare query: %s", $dbh->errstr)); + $sth2->execute($policy_id, $recipient) + or do_log(0, sprintf("Maia: [autocreate_users] Couldn't execute query: %s", $dbh->errstr)); + } else { + $insert = "INSERT INTO policy (id, " . + "policy_name, " . + "virus_lover, " . + "spam_lover, " . + "banned_files_lover, " . + "bad_header_lover, " . + "bypass_virus_checks, " . + "bypass_spam_checks, " . + "bypass_banned_checks, " . + "bypass_header_checks, " . + "discard_viruses, " . + "discard_spam, " . + "discard_banned_files, " . + "discard_bad_headers, " . + "spam_modifies_subj, " . + "spam_tag_level, " . + "spam_tag2_level, " . + "spam_kill_level" . + ") VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)"; + $sth2 = $dbh->prepare($insert) + or do_log(0, sprintf("Maia: [autocreate_users] Couldn't prepare query: %s", $dbh->errstr)); + $sth2->execute($policy_id, + $recipient, + $policy_virus_lover, + $policy_spam_lover, + $policy_banned_files_lover, + $policy_bad_header_lover, + $policy_bypass_virus_checks, + $policy_bypass_spam_checks, + $policy_bypass_banned_checks, + $policy_bypass_header_checks, + $policy_discard_viruses, + $policy_discard_spam, + $policy_discard_banned_files, + $policy_discard_bad_headers, + $policy_spam_modifies_subj, + $policy_spam_tag_level, + $policy_spam_tag2_level, + $policy_spam_kill_level) + or do_log(0, sprintf("Maia: [autocreate_users] Couldn't execute query: %s", $dbh->errstr)); + } + + } + + # Assign a reasonable priority to the e-mail address + # based on how "specific" it is--the more "dots", the more specific, + # e.g. user@mail.example.com is more specific than user@example.com + # E-mail addresses start at priority 10 and higher. + my $dot_count = 0; + my $tmp = $recipient; + while ($tmp =~ /(\.)/g) { + $dot_count++; + } + my $priority = 10 + 2 * $dot_count; + + # Create a new address for this user + if ($dbtype =~ /^mysql$/si) { # MySQL + + $insert = "INSERT INTO users(policy_id, email, priority, maia_user_id, maia_domain_id) VALUES (?,?,?,?,?)"; + $sth2 = $dbh->prepare($insert) + or do_log(0, sprintf("Maia: [autocreate_users] Couldn't prepare query: %s", $dbh->errstr)); + $sth2->execute($policy_id, $recipient, $priority, $user_id, 0) + or do_log(0, sprintf("Maia: [autocreate_users] Couldn't execute query: %s", $dbh->errstr)); + $select = "SELECT LAST_INSERT_ID()"; + $sth2 = $dbh->prepare($select) + or do_log(0, sprintf("Maia: [autocreate_users] Couldn't prepare query: %s", $dbh->errstr)); + $sth2->execute() + or do_log(0, sprintf("Maia: [autocreate_users] Couldn't execute query: %s", $dbh->errstr)); + if (@row2 = $sth2->fetchrow_array()) { + $email_id = $1 if $row2[0] =~ /^([1-9]+[0-9]*)$/si; # untaint + } else { + do_log(0, sprintf("Maia: [autocreate_users] Can't find new policy for %s", $recipient)); + } + $sth2->finish; + + } elsif ($dbtype =~ /^pg$/si) { # PostgreSQL + + $select = "SELECT NEXTVAL('users_id_seq')"; + $sth2 = $dbh->prepare($select) + or do_log(0, sprintf("Maia: [autocreate_users] Couldn't prepare query: %s", $dbh->errstr)); + $sth2->execute() + or do_log(0, sprintf("Maia: [autocreate_users] Couldn't execute query: %s", $dbh->errstr)); + if (@row2 = $sth2->fetchrow_array()) { + $email_id = $1 if $row2[0] =~ /^([1-9]+[0-9]*)$/si; # untaint + } else { + do_log(0, sprintf("Maia: [autocreate_users] Can't find new policy for %s", $recipient)); + } + $sth2->finish; + $insert = "INSERT INTO users(id, policy_id, email, priority, maia_user_id, maia_domain_id) VALUES (?,?,?,?,?,?)"; + $sth2 = $dbh->prepare($insert) + or do_log(0, sprintf("Maia: [autocreate_users] Couldn't prepare query: %s", $dbh->errstr)); + $sth2->execute($email_id, $policy_id, $recipient, $priority, $user_id, 0) + or do_log(0, sprintf("Maia: [autocreate_users] Couldn't execute query: %s", $dbh->errstr)); + + } + + # Record this e-mail address as the user's primary address + $update = "UPDATE maia_users SET primary_email_id = ? WHERE id = ?"; + $sth2 = $dbh->prepare($update) + or do_log(0, sprintf("Maia: [autocreate_users] Couldn't prepare query: %s", $dbh->errstr)); + $sth2->execute($email_id, $user_id) + or do_log(0, sprintf("Maia: [autocreate_users] Couldn't execute query: %s", $dbh->errstr)); + + if ($internal_auth) { + + # Issue a random password and send temporary credentials + # to the new user. + maia_issue_password($dbh, $recipient, $user_id); + } + + } + + } else { + + do_log(3, sprintf("Maia: [autocreate_users] Ignoring recipient %s: on do-not-autocreate list by domain", $r->recip_addr)); + + } + + } else { + do_log(3, sprintf("Maia: [autocreate_users] Ignoring non-local recipient %s", $r->recip_addr)); + } + } + $sth->finish; + section_time("maia_autocreate_users"); + } + + + # Store e-mail in the database + sub maia_store_mail($$$$$@) { + my($dbh, $msginfo, $oversized, $enable_stats_tracking, $system_default_user_is_local, @local_domain_tables) = @_; + my($insert, $select, $sth, $sth2, $sth3, @row, @row2); + my $size = $1 if ($msginfo->orig_header_size + 1 + $msginfo->orig_body_size) =~ /^(.*)$/si; # untaint + my $sender = $1 if $msginfo->sender =~ /^(.*)$/si; # untaint + my $subject = ""; + my($header, $mail_id, $recipient, $user_id); + my($received_date) = strftime("%04Y-%02m-%02d %02H:%02M:%02S",localtime); + my($fh) = $msginfo->mail_text; + $fh->seek(0,0) or die "Can't rewind mail file: $!"; + my(@lines) = <$fh>; + my $contents = $1 if join("", @lines) =~ /^(.*)$/si; # untaint + $contents =~ s/\0//g; # strip nulls + if (defined $encryption_key) { + $contents = maia_encrypt_text($encryption_key, $contents); + } + my($recipients) = $1 if join(" ", @{$msginfo->recips}) =~ /^(.*)$/si; # untaint + my $dbtype = maia_get_database_type($dbh); + foreach $header( @{$msginfo->orig_header} ) { + $subject = $1 if $header =~ m/^Subject: (.*)/; # untaint + } + $subject = substr($subject, 0, 255) if length($subject) > 255; + $sender = substr($sender, 0, 255) if length($sender) > 255; + + if (!$oversized) { + if ($dbtype =~ /^mysql$/si) { # MySQL + + $insert = "INSERT INTO maia_mail (received_date, size, sender_email, envelope_to, subject, contents) VALUES (NOW(),?,?,?,?,?)"; + $sth = $dbh->prepare($insert) + or do_log(0, sprintf("Maia: [store_mail] Couldn't prepare query: %s", $dbh->errstr)); + $sth->execute($size, $sender, $recipients, $subject, $contents) + or do_log(0, sprintf("Maia: [store_mail] Couldn't execute query: %s", $dbh->errstr)); + $select = "SELECT LAST_INSERT_ID()"; + $sth = $dbh->prepare($select) + or do_log(0, sprintf("Maia: [store_mail] Couldn't prepare query: %s", $dbh->errstr)); + $sth->execute() + or do_log(0, sprintf("Maia: [store_mail] Couldn't execute query: %s", $dbh->errstr)); + if (@row = $sth->fetchrow_array()) { + $mail_id = $1 if $row[0] =~ /^([1-9]+[0-9]*)$/si; # untaint + } else { + do_log(0, sprintf("Maia: [store_mail] Can't find new mail item from %s!", $sender)); + die; + } + $sth->finish; + + } elsif ($dbtype =~ /^pg$/si) { # PostgreSQL + + $select = "SELECT NEXTVAL('maia_mail_id_seq')"; + $sth = $dbh->prepare($select) + or do_log(0, sprintf("Maia: [store_mail] Couldn't prepare query: %s", $dbh->errstr)); + $sth->execute() + or do_log(0, sprintf("Maia: [store_mail] Couldn't execute query: %s", $dbh->errstr)); + if (@row = $sth->fetchrow_array()) { + $mail_id = $1 if $row[0] =~ /^([1-9]+[0-9]*)$/si; # untaint + } else { + do_log(0, sprintf("Maia: [store_mail] Can't find new mail item from %s!", $sender)); + die; + } + $sth->finish; + $insert = "INSERT INTO maia_mail (id, received_date, size, sender_email, envelope_to, subject, contents) VALUES (?,NOW(),?,?,?,?,?)"; + $sth = $dbh->prepare($insert) + or do_log(0, sprintf("Maia: [store_mail] Couldn't prepare query: %s", $dbh->errstr)); + $sth->execute($mail_id, $size, $sender, $recipients, $subject, $contents) + or do_log(0, sprintf("Maia: [store_mail] Couldn't execute query: %s", $dbh->errstr)); + } + + # create references for registered recipients of this mail item + for my $r (@{$msginfo->per_recip_data}) { + $recipient = $1 if $r->recip_addr =~ /^(.+\@.+)$/si; # untaint + $user_id = maia_get_recipient_id($dbh, $recipient, $system_default_user_is_local, @local_domain_tables); + if ($user_id > 0) { + $user_id = $1 if $user_id =~ /^([1-9]+[0-9]*)$/si; # untaint + $select = "SELECT mail_id, recipient_id FROM maia_mail_recipients WHERE mail_id = ? AND recipient_id = ?"; + $sth = $dbh->prepare($select) + or do_log(0, sprintf("Maia: [store_mail] Couldn't prepare query: %s", $dbh->errstr)); + $sth->execute($mail_id, $user_id) + or do_log(0, sprintf("Maia: [store_mail] Couldn't execute query: %s", $dbh->errstr)); + if (!(@row = $sth->fetchrow_array())) { + $insert = "INSERT INTO maia_mail_recipients (mail_id, recipient_id, type) VALUES (?,?,'H')"; + $sth2 = $dbh->prepare($insert) + or do_log(0, sprintf("Maia: [store_mail] Couldn't prepare query: %s", $dbh->errstr)); + $sth2->execute($mail_id, $user_id) + or do_log(0, sprintf("Maia: [store_mail] Couldn't execute query: %s", $dbh->errstr)); + } + $sth->finish; + } + } + + do_log(3, sprintf("Maia: [store_mail] Stored mail item %ld (%ld bytes)", $mail_id, $size)); + + } else { # oversized item, don't store it, but update stats + + $mail_id = 0; + if ($enable_stats_tracking) { + + my($select, $insert, $update, $sth, $sth2, @row, @row2); + my($recipient, $recipient_id); + for my $r (@{$msginfo->per_recip_data}) { + $recipient = $1 if $r->recip_addr =~ /^(.+\@.+)$/si; # untaint + $recipient_id = maia_get_recipient_id($dbh, $recipient, $system_default_user_is_local, @local_domain_tables); + if ($recipient_id > 0) { + $recipient_id = $1 if $recipient_id =~ /^([1-9]+[0-9]*)$/si; # untaint + my($total_oversized_items, $total_oversized_size, $newest_oversized_date); + my($oldest_oversized_date, $smallest_oversized_size, $largest_oversized_size); + $select = "SELECT total_oversized_items, " . + "total_oversized_size, " . + "oldest_oversized_date, " . + "smallest_oversized_size, " . + "largest_oversized_size " . + "FROM maia_stats WHERE user_id = ?"; + $sth = $dbh->prepare($select) + or do_log(0, sprintf("Maia: [store_mail] Couldn't prepare query: %s", $dbh->errstr)); + $sth->execute($recipient_id) + or do_log(0, sprintf("Maia: [store_mail] Couldn't execute query: %s", $dbh->errstr)); + if (@row = $sth->fetchrow_array()) { # user already has a stats record, update it + $total_oversized_items = $1 if ($row[0] + 1) =~ /^([0-9]*)$/si; # untaint + if ($total_oversized_items == 1) { + $total_oversized_size = $size; + $smallest_oversized_size = $size; + $largest_oversized_size = $size; + $oldest_oversized_date = $received_date; + $newest_oversized_date = $received_date; + } else { + $total_oversized_size = $1 if ($row[1] + $size) =~ /^([0-9]*)$/si; # untaint + $newest_oversized_date = $1 if $received_date =~ /^(.*)$/si; # untaint + $oldest_oversized_date = $1 if $row[2] =~ /^(.*)$/si; # untaint + if ($oldest_oversized_date == "") { + $oldest_oversized_date = $received_date; + } + $smallest_oversized_size = $1 if $row[3] =~ /^([0-9]*)$/si; # untaint + if ($smallest_oversized_size > $size) { + $smallest_oversized_size = $size; + } + $largest_oversized_size = $1 if $row[4] =~ /^([0-9]*)$/si; # untaint + if ($largest_oversized_size < $size) { + $largest_oversized_size = $size; + } + } + $update = "UPDATE maia_stats SET total_oversized_items = ?, " . + "total_oversized_size = ?, " . + "newest_oversized_date = ?, " . + "oldest_oversized_date = ?, " . + "smallest_oversized_size = ?, " . + "largest_oversized_size = ? " . + "WHERE user_id = ?"; + $sth = $dbh->prepare($update) + or do_log(0, sprintf("Maia: [store_mail] Couldn't prepare query: %s", $dbh->errstr)); + $sth->execute($total_oversized_items, + $total_oversized_size, + $newest_oversized_date, + $oldest_oversized_date, + $smallest_oversized_size, + $largest_oversized_size, + $recipient_id) + or do_log(0, sprintf("Maia: [store_mail] Couldn't execute query: %s", $dbh->errstr)); + } else { # user has no stats record, create one + $total_oversized_size = $1 if $size =~ /^([0-9]*)$/si; # untaint + $smallest_oversized_size = $total_oversized_size; + $largest_oversized_size = $total_oversized_size; + $newest_oversized_date = $1 if $received_date =~ /^(.*)$/si; # untaint + $oldest_oversized_date = $newest_oversized_date; + $insert = "INSERT INTO maia_stats (user_id, " . + "total_oversized_items, " . + "total_oversized_size, " . + "newest_oversized_date, " . + "oldest_oversized_date, " . + "smallest_oversized_size, " . + "largest_oversized_size) " . + "VALUES (?,1,?,?,?,?,?)"; + $sth = $dbh->prepare($insert) + or do_log(0, sprintf("Maia: [store_mail] Couldn't prepare query: %s", $dbh->errstr)); + $sth->execute($recipient_id, + $total_oversized_size, + $newest_oversized_date, + $oldest_oversized_date, + $smallest_oversized_size, + $largest_oversized_size) + or do_log(0, sprintf("Maia: [store_mail] Couldn't execute query: %s", $dbh->errstr)); + } + $sth->finish; + } + + } + + } + + } + section_time("maia_store_mail"); + return $mail_id; + } + + + # returns true (1) if the user wants all his ham to be discarded + sub maia_should_discard_ham($$) { + my($dbh, $user_id) = @_; + my($sth, @row, $select, $discard_ham); + + $user_id = $1 if $user_id =~ /^([1-9]+[0-9]*)$/si; # untaint + $select = "SELECT discard_ham FROM maia_users WHERE id = ?"; + $sth = $dbh->prepare($select) + or do_log(0, sprintf("Maia: [get_discard_ham_status] Couldn't prepare query: %s", $dbh->errstr)); + $sth->execute($user_id) + or do_log(0, sprintf("Maia: [get_discard_ham_status] Couldn't execute query: %s", $dbh->errstr)); + if (@row = $sth->fetchrow_array()) { + $discard_ham = 1 * ($row[0] =~ /^Y$/i); + } else { + $discard_ham = 0; + } + $sth->finish; + + return $discard_ham; + } + + + # Removes a recipient's reference to a mail item, if mail of a + # particular type is to be discarded. + sub maia_discard_if_requested($$$$$) { + my($dbh, $mail_id, $user_id, $recipient_addr, $type) = @_; + my($sth, @row, $select, $policy_id, $discard); + + $mail_id = $1 if $mail_id =~ /^([1-9]+[0-9]*)$/si; # untaint + $user_id = $1 if $user_id =~ /^([1-9]+[0-9]*)$/si; # untaint + if ($recipient_addr =~ /^(.+@.+\..+)$/si) { + $recipient_addr = $1; # untaint + $select = "SELECT policy_id FROM users WHERE email = ?"; + $sth = $dbh->prepare($select) + or do_log(0, sprintf("Maia: [discard_if_requested] Couldn't prepare query: %s", $dbh->errstr)); + $sth->execute($recipient_addr) + or do_log(0, sprintf("Maia: [discard_if_requested] Couldn't execute query: %s", $dbh->errstr)); + if (@row = $sth->fetchrow_array()) { + $policy_id = $1 if $row[0] =~ /^([1-9]+[0-9]*)$/si; # untaint + } else { + $policy_id = 0; + } + $sth->finish; + } else { + $policy_id = 0; + } + + if ($policy_id > 0) { + if ($type =~ /^V$/si) { + $select = "SELECT discard_viruses FROM policy WHERE id = ?"; + } elsif ($type =~ /^S$/si) { + $select = "SELECT discard_spam FROM policy WHERE id = ?"; + } elsif ($type =~ /^F$/si) { + $select = "SELECT discard_banned_files FROM policy WHERE id = ?"; + } elsif ($type =~ /^B$/si) { + $select = "SELECT discard_bad_headers FROM policy WHERE id = ?"; + } + $sth = $dbh->prepare($select) + or do_log(0, sprintf("Maia: [discard_if_requested] Couldn't prepare query: %s", $dbh->errstr)); + $sth->execute($policy_id) + or do_log(0, sprintf("Maia: [discard_if_requested] Couldn't execute query: %s", $dbh->errstr)); + if (@row = $sth->fetchrow_array()) { + $discard = ($row[0] =~ /^Y$/si); + } + $sth->finish; + + if ($discard) { + do_log(3, sprintf("Maia: [discard_if_requested] Removing reference to mail item %ld for recipient %ld as requested", + $mail_id, $user_id)); + maia_delete_mail_recipient_reference($dbh, $mail_id, $user_id); + } + } + } + + + # returns the Maia user ID corresponding to a recipient e-mail address + sub maia_get_recipient_id($$$@) { + my($dbh, $recipient, $system_default_user_is_local, @local_domain_tables) = @_; + my($user_id, $sth, @row, $select); + my($user, $domain); + + if (!$system_default_user_is_local || maia_recipient_is_local($dbh, $recipient, \@local_domain_tables)) { + + # Look for an explicit user@domain match first + $user = $1 if $recipient =~ /^(.+\@.+)$/si; # untaint + $select = "SELECT maia_user_id FROM users WHERE email = ?"; + $sth = $dbh->prepare($select) + or do_log(0, sprintf("Maia: [get_recipient_id] Couldn't prepare query: %s", $dbh->errstr)); + $sth->execute($user) + or do_log(0, sprintf("Maia: [get_recipient_id] Couldn't execute query: %s", $dbh->errstr)); + if (@row = $sth->fetchrow_array()) { + $user_id = $row[0]; + } else { + $user_id = 0; + } + $sth->finish; + + # If that failed, try the domain default (@domain) + if (!$user_id) { + $domain = $1 if $recipient =~ /^.+(\@.+)$/si; # untaint; + $select = "SELECT maia_user_id FROM users WHERE email = ?"; + $sth = $dbh->prepare($select) + or do_log(0, sprintf("Maia: [get_recipient_id] Couldn't prepare query: %s", $dbh->errstr)); + $sth->execute($domain) + or do_log(0, sprintf("Maia: [get_recipient_id] Couldn't execute query: %s", $dbh->errstr)); + if (@row = $sth->fetchrow_array()) { + $user_id = $row[0]; + } else { + $user_id = 0; + } + $sth->finish; + } + + # If that failed, try the system default (@.) + if (!$user_id) { + $select = "SELECT maia_user_id FROM users WHERE email = '@.'"; + $sth = $dbh->prepare($select) + or do_log(0, sprintf("Maia: [get_recipient_id] Couldn't prepare query: %s", $dbh->errstr)); + $sth->execute() + or do_log(0, sprintf("Maia: [get_recipient_id] Couldn't execute query: %s", $dbh->errstr)); + if (@row = $sth->fetchrow_array()) { + $user_id = $row[0]; + } else { + $user_id = 0; + } + $sth->finish; + } + + do_log(3, sprintf("Maia: [get_recipient_id] Recipient %s (id = %ld)", $recipient, $user_id)); + + } else { + + $user_id = 0; + do_log(3, sprintf("Maia: [get_recipient_id] Receipient %s is non-local", $recipient)); + + } + + return $user_id; + } + + + # Records occurrences of viruses in the viruses table and updates virus stats + sub maia_record_viruses($$@) { + my($dbh, $mail_id, @viruses) = @_; + my($select, $insert, $update, $sth, $sth2, @row); + my($virus_name, $virus_id, $virus_count); + my $dbtype = maia_get_database_type($dbh); + + $mail_id = $1 if $mail_id =~ /^([1-9]+[0-9]*)$/si; # untaint + for my $v (@viruses) { + $virus_id = 0; + $virus_name = $1 if $v =~ /^(.*)$/si; # untaint + + # first, look in the maia_viruses table + $select = "SELECT id, count FROM maia_viruses WHERE virus_name LIKE ?"; + $sth = $dbh->prepare($select) + or do_log(0, sprintf("Maia: [record_viruses] Couldn't prepare query: %s", $dbh->errstr)); + $sth->execute($virus_name) + or do_log(0, sprintf("Maia: [record_viruses] Couldn't execute query: %s", $dbh->errstr)); + if (@row = $sth->fetchrow_array()) { + $virus_id = $1 if $row[0] =~ /^([1-9]+[0-9]*)$/si; # untaint + $virus_count = $1 if $row[1] =~ /^([0-9]*)$/si; # untaint + do_log(3, sprintf("Maia: [record_viruses] Found virus %s (id = %ld)", $virus_name, $virus_id)); + + # not found? try the maia_virus_aliases table + } else { + $sth->finish; + $select = "SELECT maia_viruses.id, maia_viruses.count " . + "FROM maia_viruses, maia_virus_aliases " . + "WHERE maia_viruses.id = maia_virus_aliases.virus_id " . + "AND maia_virus_aliases.virus_alias LIKE ?"; + $sth = $dbh->prepare($select) + or do_log(0, sprintf("Maia: [record_viruses] Couldn't prepare query: %s", $dbh->errstr)); + $sth->execute($virus_name) + or do_log(0, sprintf("Maia: [record_viruses] Couldn't execute query: %s", $dbh->errstr)); + if (@row = $sth->fetchrow_array()) { + $virus_id = $1 if $row[0] =~ /^([1-9]+[0-9]*)$/si; # untaint + $virus_count = $1 if $row[1] =~ /^([0-9]*)$/si; # untaint + do_log(3, sprintf("Maia: [record_viruses] Found aliased virus %s (id = %ld)", $virus_name, $virus_id)); + } + } + $sth->finish; + + # still not found? create a new entry in the maia_viruses table + if ($virus_id == 0) { + if ($dbtype =~ /^mysql$/si) { # MySQL + + $insert = "INSERT INTO maia_viruses (virus_name, count) VALUES (?, 1)"; + $sth = $dbh->prepare($insert) + or do_log(0, sprintf("Maia: [record_viruses] Couldn't prepare query: %s", $dbh->errstr)); + $sth->execute($virus_name) + or do_log(0, sprintf("Maia: [record_viruses] Couldn't execute query: %s", $dbh->errstr)); + $select = "SELECT LAST_INSERT_ID()"; + $sth = $dbh->prepare($select) + or do_log(0, sprintf("Maia: [record_viruses] Couldn't prepare query: %s", $dbh->errstr)); + $sth->execute() + or do_log(0, sprintf("Maia: [record_viruses] Couldn't execute query: %s", $dbh->errstr)); + if (@row = $sth->fetchrow_array()) { + $virus_id = $1 if $row[0] =~ /^([1-9]+[0-9]*)$/si; # untaint + } else { + do_log(0, sprintf("Maia: [record_viruses] Couldn't locate record for %s!", $virus_name)); + die; + } + $sth->finish; + + } elsif ($dbtype =~ /^pg$/si) { # PostgreSQL + + $select = "SELECT NEXTVAL('maia_viruses_id_seq')"; + $sth = $dbh->prepare($select) + or do_log(0, sprintf("Maia: [record_viruses] Couldn't prepare query: %s", $dbh->errstr)); + $sth->execute() + or do_log(0, sprintf("Maia: [record_viruses] Couldn't execute query: %s", $dbh->errstr)); + if (@row = $sth->fetchrow_array()) { + $virus_id = $1 if $row[0] =~ /^([1-9]+[0-9]*)$/si; # untaint + } else { + do_log(0, sprintf("Maia: [record_viruses] Couldn't locate record for %s!", $virus_name)); + die; + } + $sth->finish; + $insert = "INSERT INTO maia_viruses (id, virus_name, count) VALUES (?, ?, 1)"; + $sth = $dbh->prepare($insert) + or do_log(0, sprintf("Maia: [record_viruses] Couldn't prepare query: %s", $dbh->errstr)); + $sth->execute($virus_id, $virus_name) + or do_log(0, sprintf("Maia: [record_viruses] Couldn't execute query: %s", $dbh->errstr)); + + } + do_log(3, sprintf("Maia: [record_viruses] Adding new virus %s (id = %ld)", $virus_name, $virus_id)); + + # found, increment the count for this virus + } else { + $update = "UPDATE maia_viruses SET count = ? WHERE id = ?"; + $sth = $dbh->prepare($update) + or do_log(0, sprintf("Maia: [record_viruses] Couldn't prepare query: %s", $dbh->errstr)); + $sth->execute($virus_count + 1, $virus_id) + or do_log(0, sprintf("Maia: [record_viruses] Couldn't execute query: %s", $dbh->errstr)); + } + + # create a reference to this virus from the mail item itself + $select = "SELECT mail_id, virus_id FROM maia_viruses_detected WHERE mail_id = ? AND virus_id = ?"; + $sth = $dbh->prepare($select) + or do_log(0, sprintf("Maia: [record_viruses] Couldn't prepare query: %s", $dbh->errstr)); + $sth->execute($mail_id, $virus_id) + or do_log(0, sprintf("Maia: [record_viruses] Couldn't execute query: %s", $dbh->errstr)); + if (!(@row = $sth->fetchrow_array())) { + $insert = "INSERT INTO maia_viruses_detected (mail_id, virus_id) VALUES (?,?)"; + $sth2 = $dbh->prepare($insert) + or do_log(0, sprintf("Maia: [record_viruses] Couldn't prepare query: %s", $dbh->errstr)); + $sth2->execute($mail_id, $virus_id) + or do_log(0, sprintf("Maia: [record_viruses] Couldn't execute query: %s", $dbh->errstr)); + do_log(3, sprintf("Maia: [record_viruses] Linking virus %ld to mail item %ld", $virus_id, $mail_id)); + } + $sth->finish; + } + section_time("maia_record_viruses"); + } + + + # Records the triggering of a SpamAssassin rule + sub maia_record_tests($$$$$) { + my($dbh, $mail_id, $spam_status, $spam_level, $score_set) = @_; + my($select, $insert, $update, $sth, $sth2, @row); + my($test_name, $test_id, $test_count, $test_score, $mail_score); + + # Determine which score set SpamAssassin is using + my $ruleset = 0; + $ruleset = $1 if $score_set =~ /^([0123])$/si; + do_log(3, sprintf("Maia: [record_tests] SpamAssassin is using score set %d", $ruleset)); + $update = "UPDATE maia_config SET sa_score_set = ? WHERE id = 0"; + $sth = $dbh->prepare($update) + or do_log(0, sprintf("Maia: [record_tests] Couldn't prepare query: %s", $dbh->errstr)); + $sth->execute($ruleset) + or do_log(0, sprintf("Maia: [record_tests] Couldn't execute query: %s", $dbh->errstr)); + + $mail_id = $1 if $mail_id =~ /^([1-9]+[0-9]*)$/si; # untaint + $mail_score = 0.0; + if ($spam_level =~ /([\-]{0,1}[0-9]+[\.]{0,1}[0-9]*)/si) { + $mail_score = $1; + } + my $test_list = $1 if $spam_status =~ /tests=(.*)/si; + $test_list =~ s/[\s\t\n]//g; + my @tests = split ",", $test_list; + for my $t (@tests) { + $test_id = 0; + $test_name = $1 if $t =~ /^([A-Za-z0-9_]+)$/si; # untaint + + # look up the test by name in the maia_sa_rules table + $select = "SELECT id, rule_count, rule_score_" . $ruleset . " FROM maia_sa_rules WHERE rule_name LIKE ?"; + $sth = $dbh->prepare($select) + or do_log(0, sprintf("Maia: [record_tests] Couldn't prepare query: %s", $dbh->errstr)); + $sth->execute($test_name) + or do_log(0, sprintf("Maia: [record_tests] Couldn't execute query: %s", $dbh->errstr)); + if (@row = $sth->fetchrow_array()) { + $test_id = $1 if $row[0] =~ /^([1-9]+[0-9]*)$/si; # untaint + $test_count = $1 if $row[1] =~ /^([0-9]*)$/si; # untaint + $test_score = $1 if $row[2] =~ /^([0-9\-\.]+)$/si; # untaint + do_log(3, sprintf("Maia: [record_tests] Triggered SpamAssassin rule %s (id = %ld, set = %d, score = %.3f)", + $test_name, $test_id, $ruleset, $test_score)); + } + $sth->finish; + + # found, increment the count for this test + if ($test_id > 0) { + $update = "UPDATE maia_sa_rules SET rule_count = ? WHERE id = ?"; + $sth = $dbh->prepare($update) + or do_log(0, sprintf("Maia: [record_tests] Couldn't prepare query: %s", $dbh->errstr)); + $sth->execute($test_count + 1, $test_id) + or do_log(0, sprintf("Maia: [record_tests] Couldn't execute query: %s", $dbh->errstr)); + } + + # create a reference to this test from the mail item itself + $select = "SELECT mail_id, rule_id FROM maia_sa_rules_triggered WHERE mail_id = ? AND rule_id = ?"; + $sth = $dbh->prepare($select) + or do_log(0, sprintf("Maia: [record_tests] Couldn't prepare query: %s", $dbh->errstr)); + $sth->execute($mail_id, $test_id) + or do_log(0, sprintf("Maia: [record_tests] Couldn't execute query: %s", $dbh->errstr)); + if ($test_score && $test_id && !(@row = $sth->fetchrow_array())) { + $insert = "INSERT INTO maia_sa_rules_triggered (mail_id, rule_id, rule_score) VALUES (?,?,?)"; + $sth2 = $dbh->prepare($insert) + or do_log(0, sprintf("Maia: [record_tests] Couldn't prepare query: %s", $dbh->errstr)); + $sth2->execute($mail_id, $test_id, $test_score) + or do_log(0, sprintf("Maia: [record_tests] Couldn't execute query: %s", $dbh->errstr)); + do_log(3, sprintf("Maia: [record_tests] Linking SpamAssassin test %ld to mail item %ld", $test_id, $mail_id)); + } + $sth->finish; + } + + # update the mail item's final score + $update = "UPDATE maia_mail SET score = ? WHERE id = ?"; + $sth = $dbh->prepare($update) + or do_log(0, sprintf("Maia: [record_tests] Couldn't prepare query: %s", $dbh->errstr)); + $sth->execute($mail_score, $mail_id) + or do_log(0, sprintf("Maia: [record_tests] Couldn't execute query: %s", $dbh->errstr)); + + do_log(3, sprintf("Maia: [record_tests] Assigning mail item %ld a final score of %0.3f", + $mail_id, $mail_score)); + section_time("maia_record_tests"); + } + + + # Records the detection of one or more banned file attachments + sub maia_record_banned_files($$@) { + my($dbh, $mail_id, @attachments) = @_; + my($insert, $select, $sth, $sth2, @row, $file_name, $file_type); + my(%seen, $attachment, @unique_attachments); + + $mail_id = $1 if $mail_id =~ /^([1-9]+[0-9]*)$/si; # untaint + %seen = (); + foreach $attachment (@attachments) { + push(@unique_attachments, $attachment) unless $seen{$attachment}++; + } + for my $f (@unique_attachments) { + if ($f =~ /^(.+\/.+)$/si) { + $file_name = "MIME-type"; + $file_type = $1; # untaint + } elsif ($f =~ /^(.+)$/si) { + $file_name = $1; # untaint + if ($file_name =~ /^.+\.(.+)$/si) { + $file_type = $1; # untaint + } else { + $file_type = "unknown"; + } + } + + # create a reference to this file from the mail item itself + $select = "SELECT mail_id, file_name FROM maia_banned_attachments_found WHERE mail_id = ? AND file_name = ?"; + $sth = $dbh->prepare($select) + or do_log(0, sprintf("Maia: [record_banned_files] Couldn't prepare query: %s", $dbh->errstr)); + $sth->execute($mail_id, $file_name) + or do_log(0, sprintf("Maia: [record_banned_files] Couldn't execute query: %s", $dbh->errstr)); + if (!(@row = $sth->fetchrow_array())) { + $insert = "INSERT INTO maia_banned_attachments_found (mail_id, file_name, file_type) VALUES (?,?,?)"; + $sth2 = $dbh->prepare($insert) + or do_log(0, sprintf("Maia: [record_banned_files] Couldn't prepare query: %s", $dbh->errstr)); + $sth2->execute($mail_id, $file_name, $file_type) + or do_log(0, sprintf("Maia: [record_banned_files] Couldn't execute query: %s", $dbh->errstr)); + do_log(3, sprintf("Maia: [record_banned_file] Linking banned file %s to mail item %ld", $file_name, $mail_id)); + } + $sth->finish; + } + section_time("maia_record_banned_files"); + } + + + # Deletes a mail item and all references to it + sub maia_delete_mail($$) { + my($dbh, $mail_id) = @_; + my($delete, $sth); + + $mail_id = $1 if $mail_id =~ /^([1-9]+[0-9]*)$/si; # untaint + + if ($mail_id) { + do_log(3, sprintf("Maia: [delete_mail] Removing mail item %ld", $mail_id)); + + # Remove the mail item from maia_mail + $delete = "DELETE FROM maia_mail WHERE id = ?"; + $sth = $dbh->prepare($delete) + or do_log(0, sprintf("Maia: [delete_mail] Couldn't prepare query: %s", $dbh->errstr)); + $sth->execute($mail_id) + or do_log(0, sprintf("Maia: [delete_mail] Couldn't execute query: %s", $dbh->errstr)); + + # Remove any references from maia_viruses_detected + $delete = "DELETE FROM maia_viruses_detected WHERE mail_id = ?"; + $sth = $dbh->prepare($delete) + or do_log(0, sprintf("Maia: [delete_mail] Couldn't prepare query: %s", $dbh->errstr)); + $sth->execute($mail_id) + or do_log(0, sprintf("Maia: [delete_mail] Couldn't execute query: %s", $dbh->errstr)); + + # Remove any references from maia_sa_rules_triggered + $delete = "DELETE FROM maia_sa_rules_triggered WHERE mail_id = ?"; + $sth = $dbh->prepare($delete) + or do_log(0, sprintf("Maia: [delete_mail] Couldn't prepare query: %s", $dbh->errstr)); + $sth->execute($mail_id) + or do_log(0, sprintf("Maia: [delete_mail] Couldn't execute query: %s", $dbh->errstr)); + + # Remove any references from maia_banned_attachments_found + $delete = "DELETE FROM maia_banned_attachments_found WHERE mail_id = ?"; + $sth = $dbh->prepare($delete) + or do_log(0, sprintf("Maia: [delete_mail] Couldn't prepare query: %s", $dbh->errstr)); + $sth->execute($mail_id) + or do_log(0, sprintf("Maia: [delete_mail] Couldn't execute query: %s", $dbh->errstr)); + } + section_time("maia_delete_mail"); + } + + + # Removes a single recipient's reference to a single mail item + sub maia_delete_mail_recipient_reference($$$) { + my($dbh, $mail_id, $user_id) = @_; + my($delete, $sth); + + $mail_id = $1 if $mail_id =~ /^([1-9]+[0-9]*)$/si; # untaint + $user_id = $1 if $user_id =~ /^([1-9]+[0-9]*)$/si; # untaint + $delete = "DELETE FROM maia_mail_recipients WHERE mail_id = ? AND recipient_id = ?"; + $sth = $dbh->prepare($delete) + or do_log(0, sprintf("Maia: [delete_mail_recipient_reference] Couldn't prepare query: %s", $dbh->errstr)); + $sth->execute($mail_id, $user_id) + or do_log(0, sprintf("Maia: [delete_mail_recipient_reference] Couldn't execute query: %s", $dbh->errstr)); + section_time("maia_delete_mail_recipient_reference"); + } + + + # Removes mail items without recipient references + sub maia_cleanup($$) { + my($dbh, $mail_id) = @_; + my($select, $sth, @row); + + $mail_id = $1 if $mail_id =~ /^([1-9]+[0-9]*)$/si; # untaint + + if ($mail_id) { + $select = "SELECT recipient_id FROM maia_mail_recipients WHERE mail_id = ?"; + $sth = $dbh->prepare($select) + or do_log(0, sprintf("Maia: [cleanup] Couldn't prepare query: %s", $dbh->errstr)); + $sth->execute($mail_id) + or do_log(0, sprintf("Maia: [cleanup] Couldn't execute query: %s", $dbh->errstr)); + if (!(@row = $sth->fetchrow_array())) { + do_log(3, sprintf("Maia: [cleanup] Unwanted mail item %ld (no registered recipients)", $mail_id)); + maia_delete_mail($dbh, $mail_id); + } + $sth->finish; + } + section_time("maia_cleanup"); + } + + + # sets a recipient's mail status for a given e-mail to: + # Suspected (S)pam + # Suspected (H)am + # (V)irus + # (B)ad header + # banned (F)ile + # b(L)acklisted sender + # (W)hitelisted sender + sub maia_set_mail_status($$$$$$$) { + my($dbh, $mail_id, $user_id, $enable_stats_tracking, + $enable_false_negative_management, $enable_spamtraps, $status) = @_; + my($update, $select, $insert, $delete, $sth, @row, $status_text); + my $is_a_spamtrap = 0; + + $status = $1 if $status =~ /^([SVHBFLW])$/si; # untaint + $mail_id = $1 if $mail_id =~ /^([1-9]+[0-9]*)$/si; # untaint + $user_id = $1 if $user_id =~ /^([1-9]+[0-9]*)$/si; # untaint + + if ($enable_spamtraps) { + + # Is this recipient a spam-trap account? + $select = "SELECT spamtrap FROM maia_users WHERE id = ?"; + $sth = $dbh->prepare($select) + or do_log(0, sprintf("Maia: [set_mail_status] Couldn't prepare query: %s", $dbh->errstr)); + $sth->execute($user_id) + or do_log(0, sprintf("Maia: [set_mail_status] Couldn't execute query: %s", $dbh->errstr)); + if (@row = $sth->fetchrow_array()) { + $is_a_spamtrap = ($row[0] =~ /^Y$/si); + } + $sth->finish; + + # If it is, then *everything* it receives is treated + # as (C)onfirmed Spam! + if ($is_a_spamtrap) { + $status = "C"; + } + + } + + if ($status =~ /^L$/si) { # blacklisted sender + + do_log(3, sprintf("Maia: [set_mail_status] Blacklisted sender's mail item (id = %ld) ignored by recipient %ld", + $mail_id, $user_id)); + $delete = "DELETE FROM maia_mail_recipients WHERE mail_id = ? AND recipient_id = ?"; + $sth = $dbh->prepare($delete) + or do_log(0, sprintf("Maia: [set_mail_status] Couldn't prepare query: %s", $dbh->errstr)); + $sth->execute($mail_id, $user_id) + or do_log(0, sprintf("Maia: [set_mail_status] Couldn't execute query: %s", $dbh->errstr)); + + } elsif ($status =~ /^W$/si) { # whitelisted sender + + do_log(3, sprintf("Maia: [set_mail_status] Sender of mail item (id = %ld) is whitelisted by recipient %ld", + $mail_id, $user_id)); + $delete = "DELETE FROM maia_mail_recipients WHERE mail_id = ? AND recipient_id = ?"; + $sth = $dbh->prepare($delete) + or do_log(0, sprintf("Maia: [set_mail_status] Couldn't prepare query: %s", $dbh->errstr)); + $sth->execute($mail_id, $user_id) + or do_log(0, sprintf("Maia: [set_mail_status] Couldn't execute query: %s", $dbh->errstr)); + + } else { + + # If we're not tracking false negatives, then + # Suspected Ham => Confirmed Ham + if (!$enable_false_negative_management) { + if ($status =~ /^H$/si) { + $status = "G"; + } + } + + $update = "UPDATE maia_mail_recipients SET type = ? WHERE mail_id = ? AND recipient_id = ?"; + $sth = $dbh->prepare($update) + or do_log(0, sprintf("Maia: [set_mail_status] Couldn't prepare query: %s", $dbh->errstr)); + $sth->execute($status, $mail_id, $user_id) + or do_log(0, sprintf("Maia: [set_mail_status] Couldn't execute query: %s", $dbh->errstr)); + if ($status =~ /^H$/si) { + $status_text = "PROBABLE HAM"; + } elsif ($status =~ /^G$/si) { + $status_text = "CONFIRMED HAM"; + } elsif ($status =~ /^S$/si) { + $status_text = "SUSPECTED SPAM"; + } elsif ($status =~ /^C$/si) { + $status_text = "CONFIRMED SPAM"; + } elsif ($status =~ /^V$/si) { + $status_text = "VIRUS-INFECTED"; + } elsif ($status =~ /^B$/si) { + $status_text = "INVALID MAIL HEADER"; + } elsif ($status =~ /^F$/si) { + $status_text = "BANNED FILE ATTACHMENT(S)"; + } else { + $status_text = "UNKNOWN (" . $status . ")"; + } + do_log(3, sprintf("Maia: [set_mail_status] Recording mail item %ld as %s for recipient %ld", + $mail_id, $status_text, $user_id)); + } + + # Only update the stats tables if stats-tracking is enabled + if ($enable_stats_tracking) { + + # Fetch some information about this mail item + my($received_date, $size, $score); + $select = "SELECT received_date, size, score FROM maia_mail WHERE id = ?"; + $sth = $dbh->prepare($select) + or do_log(0, sprintf("Maia: [set_mail_status] Couldn't prepare query: %s", $dbh->errstr)); + $sth->execute($mail_id) + or do_log(0, sprintf("Maia: [set_mail_status] Couldn't execute query: %s", $dbh->errstr)); + if (@row = $sth->fetchrow_array()) { + $received_date = $1 if $row[0] =~ /^(.*)$/si; # untaint + $size = $1 if $row[1] =~ /^([0-9]+)$/si; # untaint + $score = $1 if $row[2] =~ /^([0-9\-\.]+)$/si; # untaint + } + $sth->finish; + + # Bad Header found + if ($status =~ /^B$/si) { + + my($total_bad_header_items, $total_bad_header_size, $newest_bad_header_date); + my($oldest_bad_header_date, $smallest_bad_header_size, $largest_bad_header_size); + $select = "SELECT total_bad_header_items, " . + "total_bad_header_size, " . + "oldest_bad_header_date, " . + "smallest_bad_header_size, " . + "largest_bad_header_size " . + "FROM maia_stats WHERE user_id = ?"; + $sth = $dbh->prepare($select) + or do_log(0, sprintf("Maia: [set_mail_status] Couldn't prepare query: %s", $dbh->errstr)); + $sth->execute($user_id) + or do_log(0, sprintf("Maia: [set_mail_status] Couldn't execute query: %s", $dbh->errstr)); + if (@row = $sth->fetchrow_array()) { # user already has a stats record, update it + $total_bad_header_items = $1 if ($row[0] + 1) =~ /^([0-9]*)$/si; # untaint + if ($total_bad_header_items == 1) { + $total_bad_header_size = $size; + $smallest_bad_header_size = $size; + $largest_bad_header_size = $size; + $oldest_bad_header_date = $received_date; + $newest_bad_header_date = $received_date; + } else { + $total_bad_header_size = $1 if ($row[1] + $size) =~ /^([0-9]*)$/si; # untaint + $newest_bad_header_date = $1 if $received_date =~ /^(.*)$/si; # untaint + $oldest_bad_header_date = $1 if $row[2] =~ /^(.*)$/si; # untaint + if ($oldest_bad_header_date == "") { + $oldest_bad_header_date = $received_date; + } + $smallest_bad_header_size = $1 if $row[3] =~ /^([0-9]*)$/si; # untaint + if ($smallest_bad_header_size > $size) { + $smallest_bad_header_size = $size; + } + $largest_bad_header_size = $1 if $row[4] =~ /^([0-9]*)$/si; # untaint + if ($largest_bad_header_size < $size) { + $largest_bad_header_size = $size; + } + } + $update = "UPDATE maia_stats SET total_bad_header_items = ?, " . + "total_bad_header_size = ?, " . + "newest_bad_header_date = ?, " . + "oldest_bad_header_date = ?, " . + "smallest_bad_header_size = ?, " . + "largest_bad_header_size = ? " . + "WHERE user_id = ?"; + $sth = $dbh->prepare($update) + or do_log(0, sprintf("Maia: [set_mail_status] Couldn't prepare query: %s", $dbh->errstr)); + $sth->execute($total_bad_header_items, + $total_bad_header_size, + $newest_bad_header_date, + $oldest_bad_header_date, + $smallest_bad_header_size, + $largest_bad_header_size, + $user_id) + or do_log(0, sprintf("Maia: [set_mail_status] Couldn't execute query: %s", $dbh->errstr)); + } else { # user has no stats record, create one + $total_bad_header_size = $1 if $size =~ /^([0-9]*)$/si; # untaint + $smallest_bad_header_size = $total_bad_header_size; + $largest_bad_header_size = $total_bad_header_size; + $newest_bad_header_date = $1 if $received_date =~ /^(.*)$/si; # untaint + $oldest_bad_header_date = $newest_bad_header_date; + $insert = "INSERT INTO maia_stats (user_id, " . + "total_bad_header_items, " . + "total_bad_header_size, " . + "newest_bad_header_date, " . + "oldest_bad_header_date, " . + "smallest_bad_header_size, " . + "largest_bad_header_size) " . + "VALUES (?,1,?,?,?,?,?)"; + $sth = $dbh->prepare($insert) + or do_log(0, sprintf("Maia: [set_mail_status] Couldn't prepare query: %s", $dbh->errstr)); + $sth->execute($user_id, + $total_bad_header_size, + $newest_bad_header_date, + $oldest_bad_header_date, + $smallest_bad_header_size, + $largest_bad_header_size) + or do_log(0, sprintf("Maia: [set_mail_status] Couldn't execute query: %s", $dbh->errstr)); + } + $sth->finish; + + # Banned File attachment found + } elsif ($status =~ /^F$/si) { + + my($total_banned_file_items, $total_banned_file_size, $newest_banned_file_date); + my($oldest_banned_file_date, $smallest_banned_file_size, $largest_banned_file_size); + $select = "SELECT total_banned_file_items, " . + "total_banned_file_size, " . + "oldest_banned_file_date, " . + "smallest_banned_file_size, " . + "largest_banned_file_size " . + "FROM maia_stats WHERE user_id = ?"; + $sth = $dbh->prepare($select) + or do_log(0, sprintf("Maia: [set_mail_status] Couldn't prepare query: %s", $dbh->errstr)); + $sth->execute($user_id) + or do_log(0, sprintf("Maia: [set_mail_status] Couldn't execute query: %s", $dbh->errstr)); + if (@row = $sth->fetchrow_array()) { # user already has a stats record, update it + $total_banned_file_items = $1 if ($row[0] + 1) =~ /^([0-9]*)$/si; # untaint + if ($total_banned_file_items == 1) { + $total_banned_file_size = $size; + $smallest_banned_file_size = $size; + $largest_banned_file_size = $size; + $oldest_banned_file_date = $received_date; + $newest_banned_file_date = $received_date; + } else { + $total_banned_file_size = $1 if ($row[1] + $size) =~ /^([0-9]*)$/si; # untaint + $newest_banned_file_date = $1 if $received_date =~ /^(.*)$/si; # untaint + $oldest_banned_file_date = $1 if $row[2] =~ /^(.*)$/si; # untaint + if ($oldest_banned_file_date == "") { + $oldest_banned_file_date = $received_date; + } + $smallest_banned_file_size = $1 if $row[3] =~ /^([0-9]*)$/si; # untaint + if ($smallest_banned_file_size > $size) { + $smallest_banned_file_size = $size; + } + $largest_banned_file_size = $1 if $row[4] =~ /^([0-9]*)$/si; # untaint + if ($largest_banned_file_size < $size) { + $largest_banned_file_size = $size; + } + } + $update = "UPDATE maia_stats SET total_banned_file_items = ?, " . + "total_banned_file_size = ?, " . + "newest_banned_file_date = ?, " . + "oldest_banned_file_date = ?, " . + "smallest_banned_file_size = ?, " . + "largest_banned_file_size = ? " . + "WHERE user_id = ?"; + $sth = $dbh->prepare($update) + or do_log(0, sprintf("Maia: [set_mail_status] Couldn't prepare query: %s", $dbh->errstr)); + $sth->execute($total_banned_file_items, + $total_banned_file_size, + $newest_banned_file_date, + $oldest_banned_file_date, + $smallest_banned_file_size, + $largest_banned_file_size, + $user_id) + or do_log(0, sprintf("Maia: [set_mail_status] Couldn't execute query: %s", $dbh->errstr)); + } else { # user has no stats record, create one + $total_banned_file_size = $1 if $size =~ /^([0-9]*)$/si; # untaint + $smallest_banned_file_size = $total_banned_file_size; + $largest_banned_file_size = $total_banned_file_size; + $newest_banned_file_date = $1 if $received_date =~ /^(.*)$/si; # untaint + $oldest_banned_file_date = $newest_banned_file_date; + $insert = "INSERT INTO maia_stats (user_id, " . + "total_banned_file_items, " . + "total_banned_file_size, " . + "newest_banned_file_date, " . + "oldest_banned_file_date, " . + "smallest_banned_file_size, " . + "largest_banned_file_size) " . + "VALUES (?,1,?,?,?,?,?)"; + $sth = $dbh->prepare($insert) + or do_log(0, sprintf("Maia: [set_mail_status] Couldn't prepare query: %s", $dbh->errstr)); + $sth->execute($user_id, + $total_banned_file_size, + $newest_banned_file_date, + $oldest_banned_file_date, + $smallest_banned_file_size, + $largest_banned_file_size) + or do_log(0, sprintf("Maia: [set_mail_status] Couldn't execute query: %s", $dbh->errstr)); + } + $sth->finish; + + # Virus(es) found + } elsif ($status =~ /^V$/si) { + + my($total_virus_items, $total_virus_size, $newest_virus_date); + my($oldest_virus_date, $smallest_virus_size, $largest_virus_size); + $select = "SELECT total_virus_items, " . + "total_virus_size, " . + "oldest_virus_date, " . + "smallest_virus_size, " . + "largest_virus_size " . + "FROM maia_stats WHERE user_id = ?"; + $sth = $dbh->prepare($select) + or do_log(0, sprintf("Maia: [set_mail_status] Couldn't prepare query: %s", $dbh->errstr)); + $sth->execute($user_id) + or do_log(0, sprintf("Maia: [set_mail_status] Couldn't execute query: %s", $dbh->errstr)); + if (@row = $sth->fetchrow_array()) { # user already has a stats record, update it + $total_virus_items = $1 if ($row[0] + 1) =~ /^([0-9]*)$/si; # untaint + if ($total_virus_items == 1) { + $total_virus_size = $size; + $smallest_virus_size = $size; + $largest_virus_size = $size; + $oldest_virus_date = $received_date; + $newest_virus_date = $received_date; + } else { + $total_virus_size = $1 if ($row[1] + $size) =~ /^([0-9]*)$/si; # untaint + $newest_virus_date = $1 if $received_date =~ /^(.*)$/si; # untaint + $oldest_virus_date = $1 if $row[2] =~ /^(.*)$/si; # untaint + if ($oldest_virus_date == "") { + $oldest_virus_date = $received_date; + } + $smallest_virus_size = $1 if $row[3] =~ /^([0-9]*)$/si; # untaint + if ($smallest_virus_size > $size) { + $smallest_virus_size = $size; + } + $largest_virus_size = $1 if $row[4] =~ /^([0-9]*)$/si; # untaint + if ($largest_virus_size < $size) { + $largest_virus_size = $size; + } + } + $update = "UPDATE maia_stats SET total_virus_items = ?, " . + "total_virus_size = ?, " . + "newest_virus_date = ?, " . + "oldest_virus_date = ?, " . + "smallest_virus_size = ?, " . + "largest_virus_size = ? " . + "WHERE user_id = ?"; + $sth = $dbh->prepare($update) + or do_log(0, sprintf("Maia: [set_mail_status] Couldn't prepare query: %s", $dbh->errstr)); + $sth->execute($total_virus_items, + $total_virus_size, + $newest_virus_date, + $oldest_virus_date, + $smallest_virus_size, + $largest_virus_size, + $user_id) + or do_log(0, sprintf("Maia: [set_mail_status] Couldn't execute query: %s", $dbh->errstr)); + } else { # user has no stats record, create one + $total_virus_size = $1 if $size =~ /^([0-9]*)$/si; # untaint + $smallest_virus_size = $total_virus_size; + $largest_virus_size = $total_virus_size; + $newest_virus_date = $1 if $received_date =~ /^(.*)$/si; # untaint + $oldest_virus_date = $newest_virus_date; + $insert = "INSERT INTO maia_stats (user_id, " . + "total_virus_items, " . + "total_virus_size, " . + "newest_virus_date, " . + "oldest_virus_date, " . + "smallest_virus_size, " . + "largest_virus_size) " . + "VALUES (?,1,?,?,?,?,?)"; + $sth = $dbh->prepare($insert) + or do_log(0, sprintf("Maia: [set_mail_status] Couldn't prepare query: %s", $dbh->errstr)); + $sth->execute($user_id, + $total_virus_size, + $newest_virus_date, + $oldest_virus_date, + $smallest_virus_size, + $largest_virus_size) + or do_log(0, sprintf("Maia: [set_mail_status] Couldn't execute query: %s", $dbh->errstr)); + } + $sth->finish; + + # Suspected spam + } elsif ($status =~ /^S$/si) { + + my($total_suspected_spam_items, $total_suspected_spam_size, $newest_suspected_spam_date); + my($oldest_suspected_spam_date, $smallest_suspected_spam_size, $largest_suspected_spam_size); + my($total_suspected_spam_score, $lowest_suspected_spam_score, $highest_suspected_spam_score); + $select = "SELECT total_suspected_spam_items, " . + "total_suspected_spam_size, " . + "oldest_suspected_spam_date, " . + "smallest_suspected_spam_size, " . + "largest_suspected_spam_size, " . + "total_suspected_spam_score, " . + "lowest_suspected_spam_score, " . + "highest_suspected_spam_score " . + "FROM maia_stats WHERE user_id = ?"; + $sth = $dbh->prepare($select) + or do_log(0, sprintf("Maia: [set_mail_status] Couldn't prepare query: %s", $dbh->errstr)); + $sth->execute($user_id) + or do_log(0, sprintf("Maia: [set_mail_status] Couldn't execute query: %s", $dbh->errstr)); + if (@row = $sth->fetchrow_array()) { # user already has a stats record, update it + $total_suspected_spam_items = $1 if ($row[0] + 1) =~ /^([0-9]*)$/si; # untaint + if ($total_suspected_spam_items == 1) { + $total_suspected_spam_size = $size; + $smallest_suspected_spam_size = $size; + $largest_suspected_spam_size = $size; + $total_suspected_spam_score = $score; + $lowest_suspected_spam_score = $score; + $highest_suspected_spam_score = $score; + $oldest_suspected_spam_date = $received_date; + $newest_suspected_spam_date = $received_date; + } else { + $total_suspected_spam_size = $1 if ($row[1] + $size) =~ /^([0-9]*)$/si; # untaint + $newest_suspected_spam_date = $1 if $received_date =~ /^(.*)$/si; # untaint + $oldest_suspected_spam_date = $1 if $row[2] =~ /^(.*)$/si; # untaint + if ($oldest_suspected_spam_date == "") { + $oldest_suspected_spam_date = $received_date; + } + $smallest_suspected_spam_size = $1 if $row[3] =~ /^([0-9]*)$/si; # untaint + if ($smallest_suspected_spam_size > $size) { + $smallest_suspected_spam_size = $size; + } + $largest_suspected_spam_size = $1 if $row[4] =~ /^([0-9]*)$/si; # untaint + if ($largest_suspected_spam_size < $size) { + $largest_suspected_spam_size = $size; + } + + $total_suspected_spam_score = $1 if ($row[5] + $score) =~ /^([0-9\-\.]*)$/si; # untaint + $lowest_suspected_spam_score = $1 if $row[6] =~ /^([0-9\-\.]*)$/si; # untaint + if ($lowest_suspected_spam_score > $score) { + $lowest_suspected_spam_score = $score; + } + $highest_suspected_spam_score = $1 if $row[7] =~ /^([0-9\-\.]*)$/si; # untaint + if ($highest_suspected_spam_score < $score) { + $highest_suspected_spam_score = $score; + } + } + $update = "UPDATE maia_stats SET total_suspected_spam_items = ?, " . + "total_suspected_spam_size = ?, " . + "newest_suspected_spam_date = ?, " . + "oldest_suspected_spam_date = ?, " . + "smallest_suspected_spam_size = ?, " . + "largest_suspected_spam_size = ?, " . + "total_suspected_spam_score = ?, " . + "lowest_suspected_spam_score = ?, " . + "highest_suspected_spam_score = ? " . + "WHERE user_id = ?"; + $sth = $dbh->prepare($update) + or do_log(0, sprintf("Maia: [set_mail_status] Couldn't prepare query: %s", $dbh->errstr)); + $sth->execute($total_suspected_spam_items, + $total_suspected_spam_size, + $newest_suspected_spam_date, + $oldest_suspected_spam_date, + $smallest_suspected_spam_size, + $largest_suspected_spam_size, + $total_suspected_spam_score, + $lowest_suspected_spam_score, + $highest_suspected_spam_score, + $user_id) + or do_log(0, sprintf("Maia: [set_mail_status] Couldn't execute query: %s", $dbh->errstr)); + } else { # user has no stats record, create one + $total_suspected_spam_size = $1 if $size =~ /^([0-9]*)$/si; # untaint + $smallest_suspected_spam_size = $total_suspected_spam_size; + $largest_suspected_spam_size = $total_suspected_spam_size; + $newest_suspected_spam_date = $1 if $received_date =~ /^(.*)$/si; # untaint + $oldest_suspected_spam_date = $newest_suspected_spam_date; + $insert = "INSERT INTO maia_stats (user_id, " . + "total_suspected_spam_items, " . + "total_suspected_spam_size, " . + "newest_suspected_spam_date, " . + "oldest_suspected_spam_date, " . + "smallest_suspected_spam_size, " . + "largest_suspected_spam_size, " . + "total_suspected_spam_score, " . + "lowest_suspected_spam_score, " . + "highest_suspected_spam_score) " . + "VALUES (?,1,?,?,?,?,?,?,?,?)"; + $sth = $dbh->prepare($insert) + or do_log(0, sprintf("Maia: [set_mail_status] Couldn't prepare query: %s", $dbh->errstr)); + $sth->execute($user_id, + $total_suspected_spam_size, + $newest_suspected_spam_date, + $oldest_suspected_spam_date, + $smallest_suspected_spam_size, + $largest_suspected_spam_size, + $score, + $score, + $score) + or do_log(0, sprintf("Maia: [set_mail_status] Couldn't execute query: %s", $dbh->errstr)); + } + $sth->finish; + + # Confirmed Spam (e.g. in a spam-trap) + } elsif ($status =~ /^C$/si) { + + if ($score eq undef) { + $score = 0.0; + } + my($total_spam_items, $total_spam_size, $newest_spam_date); + my($oldest_spam_date, $smallest_spam_size, $largest_spam_size); + my($total_spam_score, $lowest_spam_score, $highest_spam_score); + $select = "SELECT total_spam_items, " . + "total_spam_size, " . + "oldest_spam_date, " . + "smallest_spam_size, " . + "largest_spam_size, " . + "total_spam_score, " . + "lowest_spam_score, " . + "highest_spam_score " . + "FROM maia_stats WHERE user_id = ?"; + $sth = $dbh->prepare($select) + or do_log(0, sprintf("Maia: [set_mail_status] Couldn't prepare query: %s", $dbh->errstr)); + $sth->execute($user_id) + or do_log(0, sprintf("Maia: [set_mail_status] Couldn't execute query: %s", $dbh->errstr)); + if (@row = $sth->fetchrow_array()) { # user already has a stats record, update it + $total_spam_items = $1 if ($row[0] + 1) =~ /^([0-9]*)$/si; # untaint + if ($total_spam_items == 1) { + $total_spam_size = $size; + $smallest_spam_size = $size; + $largest_spam_size = $size; + $total_spam_score = $score; + $lowest_spam_score = $score; + $highest_spam_score = $score; + $oldest_spam_date = $received_date; + $newest_spam_date = $received_date; + } else { + $total_spam_size = $1 if ($row[1] + $size) =~ /^([0-9]*)$/si; # untaint + $newest_spam_date = $1 if $received_date =~ /^(.*)$/si; # untaint + $oldest_spam_date = $1 if $row[2] =~ /^(.*)$/si; # untaint + if ($oldest_spam_date == "") { + $oldest_spam_date = $received_date; + } + $smallest_spam_size = $1 if $row[3] =~ /^([0-9]*)$/si; # untaint + if ($smallest_spam_size > $size) { + $smallest_spam_size = $size; + } + $largest_spam_size = $1 if $row[4] =~ /^([0-9]*)$/si; # untaint + if ($largest_spam_size < $size) { + $largest_spam_size = $size; + } + + $total_spam_score = $1 if ($row[5] + $score) =~ /^([0-9\-\.]*)$/si; # untaint + $lowest_spam_score = $1 if $row[6] =~ /^([0-9\-\.]*)$/si; # untaint + if ($lowest_spam_score > $score) { + $lowest_spam_score = $score; + } + $highest_spam_score = $1 if $row[7] =~ /^([0-9\-\.]*)$/si; # untaint + if ($highest_spam_score < $score) { + $highest_spam_score = $score; + } + } + $update = "UPDATE maia_stats SET total_spam_items = ?, " . + "total_spam_size = ?, " . + "newest_spam_date = ?, " . + "oldest_spam_date = ?, " . + "smallest_spam_size = ?, " . + "largest_spam_size = ?, " . + "total_spam_score = ?, " . + "lowest_spam_score = ?, " . + "highest_spam_score = ? " . + "WHERE user_id = ?"; + $sth = $dbh->prepare($update) + or do_log(0, sprintf("Maia: [set_mail_status] Couldn't prepare query: %s", $dbh->errstr)); + $sth->execute($total_spam_items, + $total_spam_size, + $newest_spam_date, + $oldest_spam_date, + $smallest_spam_size, + $largest_spam_size, + $total_spam_score, + $lowest_spam_score, + $highest_spam_score, + $user_id) + or do_log(0, sprintf("Maia: [set_mail_status] Couldn't execute query: %s", $dbh->errstr)); + } else { # user has no stats record, create one + $total_spam_size = $1 if $size =~ /^([0-9]*)$/si; # untaint + $smallest_spam_size = $total_spam_size; + $largest_spam_size = $total_spam_size; + $newest_spam_date = $1 if $received_date =~ /^(.*)$/si; # untaint + $oldest_spam_date = $newest_spam_date; + $insert = "INSERT INTO maia_stats (user_id, " . + "total_spam_items, " . + "total_spam_size, " . + "newest_spam_date, " . + "oldest_spam_date, " . + "smallest_spam_size, " . + "largest_spam_size, " . + "total_spam_score, " . + "lowest_spam_score, " . + "highest_spam_score) " . + "VALUES (?,1,?,?,?,?,?,?,?,?)"; + $sth = $dbh->prepare($insert) + or do_log(0, sprintf("Maia: [set_mail_status] Couldn't prepare query: %s", $dbh->errstr)); + $sth->execute($user_id, + $total_spam_size, + $newest_spam_date, + $oldest_spam_date, + $smallest_spam_size, + $largest_spam_size, + $score, + $score, + $score) + or do_log(0, sprintf("Maia: [set_mail_status] Couldn't execute query: %s", $dbh->errstr)); + } + $sth->finish; + + # Suspected Ham + } elsif ($status =~ /^H$/si) { + + my($total_suspected_ham_items, $total_suspected_ham_size, $newest_suspected_ham_date); + my($oldest_suspected_ham_date, $smallest_suspected_ham_size, $largest_suspected_ham_size); + my($total_suspected_ham_score, $lowest_suspected_ham_score, $highest_suspected_ham_score); + $select = "SELECT total_suspected_ham_items, " . + "total_suspected_ham_size, " . + "oldest_suspected_ham_date, " . + "smallest_suspected_ham_size, " . + "largest_suspected_ham_size, " . + "total_suspected_ham_score, " . + "lowest_suspected_ham_score, " . + "highest_suspected_ham_score " . + "FROM maia_stats WHERE user_id = ?"; + $sth = $dbh->prepare($select) + or do_log(0, sprintf("Maia: [set_mail_status] Couldn't prepare query: %s", $dbh->errstr)); + $sth->execute($user_id) + or do_log(0, sprintf("Maia: [set_mail_status] Couldn't execute query: %s", $dbh->errstr)); + if (@row = $sth->fetchrow_array()) { # user already has a stats record, update it + $total_suspected_ham_items = $1 if ($row[0] + 1) =~ /^([0-9]*)$/si; # untaint + if ($total_suspected_ham_items == 1) { + $total_suspected_ham_size = $size; + $smallest_suspected_ham_size = $size; + $largest_suspected_ham_size = $size; + $total_suspected_ham_score = $score; + $lowest_suspected_ham_score = $score; + $highest_suspected_ham_score = $score; + $oldest_suspected_ham_date = $received_date; + $newest_suspected_ham_date = $received_date; + } else { + $total_suspected_ham_size = $1 if ($row[1] + $size) =~ /^([0-9]*)$/si; # untaint + $newest_suspected_ham_date = $1 if $received_date =~ /^(.*)$/si; # untaint + $oldest_suspected_ham_date = $1 if $row[2] =~ /^(.*)$/si; # untaint + if ($oldest_suspected_ham_date == "") { + $oldest_suspected_ham_date = $received_date; + } + $smallest_suspected_ham_size = $1 if $row[3] =~ /^([0-9]*)$/si; # untaint + if ($smallest_suspected_ham_size > $size) { + $smallest_suspected_ham_size = $size; + } + $largest_suspected_ham_size = $1 if $row[4] =~ /^([0-9]*)$/si; # untaint + if ($largest_suspected_ham_size < $size) { + $largest_suspected_ham_size = $size; + } + $total_suspected_ham_score = $1 if $row[5] =~ /^([0-9\-\.]*)$/si; # untaint + $total_suspected_ham_score += $score; + $lowest_suspected_ham_score = $1 if $row[6] =~ /^([0-9\-\.]*)$/si; # untaint + if ($lowest_suspected_ham_score > $score) { + $lowest_suspected_ham_score = $score; + } + $highest_suspected_ham_score = $1 if $row[7] =~ /^([0-9\-\.]*)$/si; # untaint + if ($highest_suspected_ham_score < $score) { + $highest_suspected_ham_score = $score; + } + } + $update = "UPDATE maia_stats SET total_suspected_ham_items = ?, " . + "total_suspected_ham_size = ?, " . + "newest_suspected_ham_date = ?, " . + "oldest_suspected_ham_date = ?, " . + "smallest_suspected_ham_size = ?, " . + "largest_suspected_ham_size = ?, " . + "total_suspected_ham_score = ?, " . + "lowest_suspected_ham_score = ?, " . + "highest_suspected_ham_score = ? " . + "WHERE user_id = ?"; + $sth = $dbh->prepare($update) + or do_log(0, sprintf("Maia: [set_mail_status] Couldn't prepare query: %s", $dbh->errstr)); + $sth->execute($total_suspected_ham_items, + $total_suspected_ham_size, + $newest_suspected_ham_date, + $oldest_suspected_ham_date, + $smallest_suspected_ham_size, + $largest_suspected_ham_size, + $total_suspected_ham_score, + $lowest_suspected_ham_score, + $highest_suspected_ham_score, + $user_id) + or do_log(0, sprintf("Maia: [set_mail_status] Couldn't execute query: %s", $dbh->errstr)); + } else { # user has no stats record, create one + $total_suspected_ham_size = $1 if $size =~ /^([0-9]*)$/si; # untaint + $smallest_suspected_ham_size = $total_suspected_ham_size; + $largest_suspected_ham_size = $total_suspected_ham_size; + $newest_suspected_ham_date = $1 if $received_date =~ /^(.*)$/si; # untaint + $oldest_suspected_ham_date = $newest_suspected_ham_date; + $insert = "INSERT INTO maia_stats (user_id, " . + "total_suspected_ham_items, " . + "total_suspected_ham_size, " . + "newest_suspected_ham_date, " . + "oldest_suspected_ham_date, " . + "smallest_suspected_ham_size, " . + "largest_suspected_ham_size, " . + "total_suspected_ham_score, " . + "lowest_suspected_ham_score, " . + "highest_suspected_ham_score) " . + "VALUES (?,1,?,?,?,?,?,?,?,?)"; + $sth = $dbh->prepare($insert) + or do_log(0, sprintf("Maia: [set_mail_status] Couldn't prepare query: %s", $dbh->errstr)); + $sth->execute($user_id, + $total_suspected_ham_size, + $newest_suspected_ham_date, + $oldest_suspected_ham_date, + $smallest_suspected_ham_size, + $largest_suspected_ham_size, + $score, + $score, + $score) + or do_log(0, sprintf("Maia: [set_mail_status] Couldn't execute query: %s", $dbh->errstr)); + } + $sth->finish; + + # Confirmed Ham (e.g. false negative tracking is disabled) + } elsif ($status =~ /^G$/si) { + + my($total_ham_items, $total_ham_size, $newest_ham_date); + my($oldest_ham_date, $smallest_ham_size, $largest_ham_size); + my($total_ham_score, $lowest_ham_score, $highest_ham_score); + $select = "SELECT total_ham_items, " . + "total_ham_size, " . + "oldest_ham_date, " . + "smallest_ham_size, " . + "largest_ham_size, " . + "total_ham_score, " . + "lowest_ham_score, " . + "highest_ham_score " . + "FROM maia_stats WHERE user_id = ?"; + $sth = $dbh->prepare($select) + or do_log(0, sprintf("Maia: [set_mail_status] Couldn't prepare query: %s", $dbh->errstr)); + $sth->execute($user_id) + or do_log(0, sprintf("Maia: [set_mail_status] Couldn't execute query: %s", $dbh->errstr)); + if (@row = $sth->fetchrow_array()) { # user already has a stats record, update it + $total_ham_items = $1 if ($row[0] + 1) =~ /^([0-9]*)$/si; # untaint + if ($total_ham_items == 1) { + $total_ham_size = $size; + $smallest_ham_size = $size; + $largest_ham_size = $size; + $total_ham_score = $score; + $lowest_ham_score = $score; + $highest_ham_score = $score; + $oldest_ham_date = $received_date; + $newest_ham_date = $received_date; + } else { + $total_ham_size = $1 if ($row[1] + $size) =~ /^([0-9]*)$/si; # untaint + $newest_ham_date = $1 if $received_date =~ /^(.*)$/si; # untaint + $oldest_ham_date = $1 if $row[2] =~ /^(.*)$/si; # untaint + if ($oldest_ham_date == "") { + $oldest_ham_date = $received_date; + } + $smallest_ham_size = $1 if $row[3] =~ /^([0-9]*)$/si; # untaint + if ($smallest_ham_size > $size) { + $smallest_ham_size = $size; + } + $largest_ham_size = $1 if $row[4] =~ /^([0-9]*)$/si; # untaint + if ($largest_ham_size < $size) { + $largest_ham_size = $size; + } + $total_ham_score = $1 if $row[5] =~ /^([0-9\-\.]*)$/si; # untaint + $total_ham_score += $score; + $lowest_ham_score = $1 if $row[6] =~ /^([0-9\-\.]*)$/si; # untaint + if ($lowest_ham_score > $score) { + $lowest_ham_score = $score; + } + $highest_ham_score = $1 if $row[7] =~ /^([0-9\-\.]*)$/si; # untaint + if ($highest_ham_score < $score) { + $highest_ham_score = $score; + } + } + $update = "UPDATE maia_stats SET total_ham_items = ?, " . + "total_ham_size = ?, " . + "newest_ham_date = ?, " . + "oldest_ham_date = ?, " . + "smallest_ham_size = ?, " . + "largest_ham_size = ?, " . + "total_ham_score = ?, " . + "lowest_ham_score = ?, " . + "highest_ham_score = ? " . + "WHERE user_id = ?"; + $sth = $dbh->prepare($update) + or do_log(0, sprintf("Maia: [set_mail_status] Couldn't prepare query: %s", $dbh->errstr)); + $sth->execute($total_ham_items, + $total_ham_size, + $newest_ham_date, + $oldest_ham_date, + $smallest_ham_size, + $largest_ham_size, + $total_ham_score, + $lowest_ham_score, + $highest_ham_score, + $user_id) + or do_log(0, sprintf("Maia: [set_mail_status] Couldn't execute query: %s", $dbh->errstr)); + } else { # user has no stats record, create one + $total_ham_size = $1 if $size =~ /^([0-9]*)$/si; # untaint + $smallest_ham_size = $total_ham_size; + $largest_ham_size = $total_ham_size; + $newest_ham_date = $1 if $received_date =~ /^(.*)$/si; # untaint + $oldest_ham_date = $newest_ham_date; + $insert = "INSERT INTO maia_stats (user_id, " . + "total_ham_items, " . + "total_ham_size, " . + "newest_ham_date, " . + "oldest_ham_date, " . + "smallest_ham_size, " . + "largest_ham_size, " . + "total_ham_score, " . + "lowest_ham_score, " . + "highest_ham_score) " . + "VALUES (?,1,?,?,?,?,?,?,?,?)"; + $sth = $dbh->prepare($insert) + or do_log(0, sprintf("Maia: [set_mail_status] Couldn't prepare query: %s", $dbh->errstr)); + $sth->execute($user_id, + $total_ham_size, + $newest_ham_date, + $oldest_ham_date, + $smallest_ham_size, + $largest_ham_size, + $score, + $score, + $score) + or do_log(0, sprintf("Maia: [set_mail_status] Couldn't execute query: %s", $dbh->errstr)); + } + $sth->finish; + + # Whitelisted sender + } elsif ($status =~ /^W$/si) { + + my($total_wl_items, $total_wl_size, $newest_wl_date); + my($oldest_wl_date, $smallest_wl_size, $largest_wl_size); + $select = "SELECT total_wl_items, " . + "total_wl_size, " . + "oldest_wl_date, " . + "smallest_wl_size, " . + "largest_wl_size " . + "FROM maia_stats WHERE user_id = ?"; + $sth = $dbh->prepare($select) + or do_log(0, sprintf("Maia: [set_mail_status] Couldn't prepare query: %s", $dbh->errstr)); + $sth->execute($user_id) + or do_log(0, sprintf("Maia: [set_mail_status] Couldn't execute query: %s", $dbh->errstr)); + if (@row = $sth->fetchrow_array()) { # user already has a stats record, update it + $total_wl_items = $1 if ($row[0] + 1) =~ /^([0-9]*)$/si; # untaint + if ($total_wl_items == 1) { + $total_wl_size = $size; + $smallest_wl_size = $size; + $largest_wl_size = $size; + $oldest_wl_date = $received_date; + $newest_wl_date = $received_date; + } else { + $total_wl_size = $1 if ($row[1] + $size) =~ /^([0-9]*)$/si; # untaint + $newest_wl_date = $1 if $received_date =~ /^(.*)$/si; # untaint + $oldest_wl_date = $1 if $row[2] =~ /^(.*)$/si; # untaint + if ($oldest_wl_date == "") { + $oldest_wl_date = $received_date; + } + $smallest_wl_size = $1 if $row[3] =~ /^([0-9]*)$/si; # untaint + if ($smallest_wl_size > $size) { + $smallest_wl_size = $size; + } + $largest_wl_size = $1 if $row[4] =~ /^([0-9]*)$/si; # untaint + if ($largest_wl_size < $size) { + $largest_wl_size = $size; + } + } + $update = "UPDATE maia_stats SET total_wl_items = ?, " . +