From 288fd2ca59ccd5bba3d08f8d632e4e868d595a51 Mon Sep 17 00:00:00 2001 From: Martin Osvald Date: Fri, 12 Jul 2024 06:24:08 +0200 Subject: [PATCH] Bind token as SQL_BINARY Resolves: RHEL-43418 --- spamassassin-bind-token-as-sql_binary.patch | 327 ++++++++++++++++++++ spamassassin.spec | 8 +- 2 files changed, 334 insertions(+), 1 deletion(-) create mode 100644 spamassassin-bind-token-as-sql_binary.patch diff --git a/spamassassin-bind-token-as-sql_binary.patch b/spamassassin-bind-token-as-sql_binary.patch new file mode 100644 index 0000000..98869fc --- /dev/null +++ b/spamassassin-bind-token-as-sql_binary.patch @@ -0,0 +1,327 @@ +commit 3dd8ea4ff51d50a72212ac8cbb2f6f8d443c3489 +Author: Henrik Krohns +Date: Sun Apr 10 19:23:45 2022 +0000 + + Properly bind token as SQL_BINARY, allowing DBD::MariaDB driver to work also + + + git-svn-id: https://svn.apache.org/repos/asf/spamassassin/trunk@1899715 13f79535-47bb-0310-9956-ffa450edef68 + +diff --git a/lib/Mail/SpamAssassin/BayesStore/MySQL.pm b/lib/Mail/SpamAssassin/BayesStore/MySQL.pm +index c4bfb920c..dac7b6990 100644 +--- a/lib/Mail/SpamAssassin/BayesStore/MySQL.pm ++++ b/lib/Mail/SpamAssassin/BayesStore/MySQL.pm +@@ -339,6 +339,147 @@ sub remove_running_expire_tok { + return 1; + } + ++=head2 tok_get ++ ++public instance (Integer, Integer, Integer) tok_get (String $token) ++ ++Description: ++This method retrieves a specified token (C<$token>) from the database ++and returns it's spam_count, ham_count and last access time. ++ ++=cut ++ ++sub tok_get { ++ my ($self, $token) = @_; ++ ++ return (0,0,0) unless (defined($self->{_dbh})); ++ ++ my $sql = "SELECT spam_count, ham_count, atime ++ FROM bayes_token ++ WHERE id = ? ++ AND token = ?"; ++ ++ my $sth = $self->{_dbh}->prepare_cached($sql); ++ ++ unless (defined($sth)) { ++ dbg("bayes: tok_get: SQL error: ".$self->{_dbh}->errstr()); ++ $self->{_dbh}->rollback(); ++ return (0,0,0); ++ } ++ ++ $sth->bind_param(1, $self->{_userid}); ++ $sth->bind_param(2, $token, DBI::SQL_BINARY); ++ ++ my $rc = $sth->execute(); ++ ++ unless ($rc) { ++ dbg("bayes: tok_get: SQL error: ".$self->{_dbh}->errstr()); ++ $self->{_dbh}->rollback(); ++ return (0,0,0); ++ } ++ ++ my ($spam_count, $ham_count, $atime) = $sth->fetchrow_array(); ++ ++ $sth->finish(); ++ ++ $spam_count = 0 if (!$spam_count || $spam_count < 0); ++ $ham_count = 0 if (!$ham_count || $ham_count < 0); ++ $atime = 0 if (!$atime); ++ ++ return ($spam_count, $ham_count, $atime) ++} ++ ++=head2 tok_get_all ++ ++public instance (\@) tok_get (@ $tokens) ++ ++Description: ++This method retrieves the specified tokens (C<$tokens>) from storage and returns ++an array ref of arrays spam count, ham count and last access time. ++ ++=cut ++ ++sub tok_get_all { ++ my ($self, @tokens) = @_; ++ ++ return [] unless (defined($self->{_dbh})); ++ ++ my $token_list_size = scalar(@tokens); ++ dbg("bayes: tok_get_all: token count: $token_list_size"); ++ my @tok_results; ++ ++ my $search_index = 0; ++ my $results_index = 0; ++ my $bunch_end; ++ ++ my $token_select = $self->_token_select_string(); ++ ++ my $multi_sql = "SELECT $token_select, spam_count, ham_count, atime ++ FROM bayes_token ++ WHERE id = ? ++ AND token IN "; ++ ++ # fetch tokens in bunches of 100 until there are <= 100 left, then just fetch the rest ++ while ($token_list_size > $search_index) { ++ my $bunch_size; ++ if ($token_list_size - $search_index > 100) { ++ $bunch_size = 100; ++ } ++ else { ++ $bunch_size = $token_list_size - $search_index; ++ } ++ while ($token_list_size - $search_index >= $bunch_size) { ++ my @tok; ++ my $in_str = '('; ++ ++ $bunch_end = $search_index + $bunch_size; ++ for ( ; $search_index < $bunch_end; $search_index++) { ++ $in_str .= '?,'; ++ push(@tok, $tokens[$search_index]); ++ } ++ chop $in_str; ++ $in_str .= ')'; ++ ++ my $dynamic_sql = $multi_sql . $in_str; ++ ++ my $sth = $self->{_dbh}->prepare($dynamic_sql); ++ ++ unless (defined($sth)) { ++ dbg("bayes: tok_get_all: SQL error: ".$self->{_dbh}->errstr()); ++ $self->{_dbh}->rollback(); ++ return []; ++ } ++ ++ my $idx = 0; ++ $sth->bind_param(++$idx, $self->{_userid}); ++ $sth->bind_param(++$idx, $_, DBI::SQL_BINARY) foreach (@tok); ++ ++ my $rc = $sth->execute(); ++ ++ unless ($rc) { ++ dbg("bayes: tok_get_all: SQL error: ".$self->{_dbh}->errstr()); ++ $self->{_dbh}->rollback(); ++ return []; ++ } ++ ++ my $results = $sth->fetchall_arrayref(); ++ ++ $sth->finish(); ++ ++ foreach my $result (@{$results}) { ++ # Make sure that spam_count and ham_count are not negative ++ $result->[1] = 0 if (!$result->[1] || $result->[1] < 0); ++ $result->[2] = 0 if (!$result->[2] || $result->[2] < 0); ++ # Make sure that atime has a value ++ $result->[3] = 0 if (!$result->[3]); ++ $tok_results[$results_index++] = $result; ++ } ++ } ++ } ++ ++ return \@tok_results; ++} ++ + =head2 nspam_nham_change + + public instance (Boolean) nspam_nham_change (Integer $num_spam, +@@ -421,10 +562,22 @@ sub tok_touch { + AND token = ? + AND atime < ?"; + +- my $rows = $self->{_dbh}->do($sql, undef, $atime, $self->{_userid}, +- $token, $atime); ++ my $sth = $self->{_dbh}->prepare_cached($sql); ++ ++ unless (defined($sth)) { ++ dbg("bayes: tok_touch: SQL error: ".$self->{_dbh}->errstr()); ++ $self->{_dbh}->rollback(); ++ return 0; ++ } + +- unless (defined($rows)) { ++ $sth->bind_param(1, $atime); ++ $sth->bind_param(2, $self->{_userid}); ++ $sth->bind_param(3, $token, DBI::SQL_BINARY); ++ $sth->bind_param(4, $atime); ++ ++ my $rows = $sth->execute(); ++ ++ unless ($rows) { + dbg("bayes: tok_touch: SQL error: ".$self->{_dbh}->errstr()); + $self->{_dbh}->rollback(); + return 0; +@@ -478,20 +631,29 @@ sub tok_touch_all { + return 1 unless (scalar(@{$tokens})); + + my $sql = "UPDATE bayes_token SET atime = ? WHERE id = ? AND token IN ("; +- +- my @bindings = ($atime, $self->{_userid}); +- foreach my $token (@{$tokens}) { ++ foreach (@{$tokens}) { + $sql .= "?,"; +- push(@bindings, $token); + } + chop($sql); # get rid of trailing , +- + $sql .= ") AND atime < ?"; +- push(@bindings, $atime); + +- my $rows = $self->{_dbh}->do($sql, undef, @bindings); ++ my $sth = $self->{_dbh}->prepare($sql); + +- unless (defined($rows)) { ++ unless (defined($sth)) { ++ dbg("bayes: tok_get_all: SQL error: ".$self->{_dbh}->errstr()); ++ $self->{_dbh}->rollback(); ++ return []; ++ } ++ ++ my $idx = 0; ++ $sth->bind_param(++$idx, $atime); ++ $sth->bind_param(++$idx, $self->{_userid}); ++ $sth->bind_param(++$idx, $_, DBI::SQL_BINARY) foreach (@{$tokens}); ++ $sth->bind_param(++$idx, $atime); ++ ++ my $rows = $sth->execute(); ++ ++ unless ($rows) { + dbg("bayes: tok_touch_all: SQL error: ".$self->{_dbh}->errstr()); + $self->{_dbh}->rollback(); + return 0; +@@ -735,7 +897,8 @@ sub _initialize_db { + return 0; + } + +- $id = $self->{_dbh}->{'mysql_insertid'}; ++ $id = $self->{_dsn} =~ /^DBI:MariaDB/i ? ++ $self->{_dbh}->{'mariadb_insertid'} : $self->{_dbh}->{'mysql_insertid'}; + + $self->{_dbh}->commit(); + +@@ -797,10 +960,12 @@ sub _put_token { + return 0; + } + +- my $rc = $sth->execute($spam_count, +- $ham_count, +- $self->{_userid}, +- $token); ++ $sth->bind_param(1, $spam_count); ++ $sth->bind_param(2, $ham_count); ++ $sth->bind_param(3, $self->{_userid}); ++ $sth->bind_param(4, $token, DBI::SQL_BINARY); ++ ++ my $rc = $sth->execute(); + + unless ($rc) { + dbg("bayes: _put_token: SQL error: ".$self->{_dbh}->errstr()); +@@ -824,14 +989,16 @@ sub _put_token { + return 0; + } + +- my $rc = $sth->execute($self->{_userid}, +- $token, +- $spam_count, +- $ham_count, +- $atime, +- $spam_count, +- $ham_count, +- $atime); ++ $sth->bind_param(1, $self->{_userid}); ++ $sth->bind_param(2, $token, DBI::SQL_BINARY); ++ $sth->bind_param(3, $spam_count); ++ $sth->bind_param(4, $ham_count); ++ $sth->bind_param(5, $atime); ++ $sth->bind_param(6, $spam_count); ++ $sth->bind_param(7, $ham_count); ++ $sth->bind_param(8, $atime); ++ ++ my $rc = $sth->execute(); + + unless ($rc) { + dbg("bayes: _put_token: SQL error: ".$self->{_dbh}->errstr()); +@@ -948,12 +1115,15 @@ sub _put_tokens { + return 0; + } + ++ $sth->bind_param(1, $spam_count); ++ $sth->bind_param(2, $ham_count); ++ $sth->bind_param(3, $self->{_userid}); ++ # 4, update token in foreach loop ++ + my $error_p = 0; + foreach my $token (keys %{$tokens}) { +- my $rc = $sth->execute($spam_count, +- $ham_count, +- $self->{_userid}, +- $token); ++ $sth->bind_param(4, $token, DBI::SQL_BINARY); ++ my $rc = $sth->execute(); + + unless ($rc) { + dbg("bayes: _put_tokens: SQL error: ".$self->{_dbh}->errstr()); +@@ -984,18 +1154,21 @@ sub _put_tokens { + return 0; + } + ++ $sth->bind_param(1, $self->{_userid}); ++ # 2, update token in foreach loop ++ $sth->bind_param(3, $spam_count); ++ $sth->bind_param(4, $ham_count); ++ $sth->bind_param(5, $atime); ++ $sth->bind_param(6, $spam_count); ++ $sth->bind_param(7, $ham_count); ++ $sth->bind_param(8, $atime); ++ + my $error_p = 0; + my $new_tokens = 0; + my $need_atime_update_p = 0; + foreach my $token (keys %{$tokens}) { +- my $rc = $sth->execute($self->{_userid}, +- $token, +- $spam_count, +- $ham_count, +- $atime, +- $spam_count, +- $ham_count, +- $atime); ++ $sth->bind_param(2, $token, DBI::SQL_BINARY); ++ my $rc = $sth->execute(); + + if (!$rc) { + dbg("bayes: _put_tokens: SQL error: ".$self->{_dbh}->errstr()); diff --git a/spamassassin.spec b/spamassassin.spec index 6f167ec..d866992 100644 --- a/spamassassin.spec +++ b/spamassassin.spec @@ -60,7 +60,7 @@ Summary: Spam filter for email which can be invoked from mail delivery agents Name: spamassassin Version: 3.4.6 #Release: 0.8.%%{prerev}%%{?dist} -Release: 5%{?dist} +Release: 6%{?dist} License: ASL 2.0 URL: https://spamassassin.apache.org/ Source0: https://www.apache.org/dist/%{name}/source/%{real_name}-%{version}.tar.bz2 @@ -90,6 +90,8 @@ Source17: sa-update.timer # Switch to using gnupg2 instead of gnupg1 Patch0: spamassassin-3.3.2-gnupg2.patch Patch1: spamassassin-3.4.1-add-logfile-homedir-options.patch +# https://issues.redhat.com/browse/RHEL-43418 +Patch2: spamassassin-bind-token-as-sql_binary.patch # end of patches Requires: perl(:MODULE_COMPAT_%(eval "`%{__perl} -V:version`"; echo $version)) %if %{use_systemd} == 0 @@ -209,6 +211,7 @@ To filter spam for all users, add that line to /etc/procmailrc # Patches 0-99 are RH specific %patch0 -p1 %patch1 -p1 +%patch2 -p1 # end of patches echo "RHEL=%{?rhel} FEDORA=%{?fedora}" @@ -388,6 +391,9 @@ exit 0 %endif %changelog +* Fri Jul 12 2024 Martin Osvald - 3.4.6-6 +- Bind token as SQL_BINARY (RHEL-43418) + * Mon Jan 17 2022 Martin Osvald - 3.4.6-5 - Forgot to reference CVE-2020-1946 as fixed after rebase to 3.4.6