BEGIN {
require
DBI; DBI->
import
; }
our
@ISA
=
qw(Mail::SpamAssassin::PersistentAddrList)
;
sub
new {
my
(
$proto
) =
@_
;
my
$class
=
ref
(
$proto
) ||
$proto
;
my
$self
=
$class
->SUPER::new(
@_
);
$self
->{class} =
$class
;
bless
(
$self
,
$class
);
$self
;
}
sub
new_checker {
my
(
$self
,
$main
) =
@_
;
my
$class
=
$self
->{class};
if
(!
$main
->{conf}->{user_awl_dsn} ||
!
$main
->{conf}->{user_awl_sql_table}) {
dbg(
"auto-welcomelist: sql-based invalid config"
);
return
;
}
my
$dsn
=
$main
->{conf}->{user_awl_dsn};
my
$dbuser
=
$main
->{conf}->{user_awl_sql_username};
my
$dbpass
=
$main
->{conf}->{user_awl_sql_password};
my
$dbh
= DBI->
connect
(
$dsn
,
$dbuser
,
$dbpass
, {
'PrintError'
=> 0});
if
(!
$dbh
) {
info(
"auto-welcomelist: sql-based unable to connect to database (%s) : %s"
,
$dsn
, DBI::errstr);
return
;
}
dbg(
"auto-welcomelist: sql-based connected to $dsn"
);
$self
= {
'main'
=>
$main
,
'dsn'
=>
$dsn
,
'dbh'
=>
$dbh
,
'tablename'
=>
$main
->{conf}->{user_awl_sql_table},
};
my
$override_username
=
$main
->{conf}->{user_awl_sql_override_username};
if
(
defined
$override_username
&&
$override_username
ne
''
) {
$self
->{_username} =
$override_username
;
}
else
{
$self
->{_username} =
$main
->{username};
if
(!
defined
$self
->{_username} ||
$self
->{_username} eq
''
) {
$self
->{_username} =
"GLOBAL"
;
}
}
$self
->{_with_awl_signer} =
$main
->{conf}->{auto_welcomelist_distinguish_signed};
dbg(
"auto-welcomelist: sql-based using username: "
.
$self
->{_username});
return
bless
(
$self
,
$class
);
}
sub
get_addr_entry {
my
(
$self
,
$addr
,
$signedby
) =
@_
;
my
$entry
= {
addr
=>
$addr
,
exists_p
=> 0,
msgcount
=> 0,
totscore
=> 0,
signedby
=>
$signedby
,
};
my
(
$email
,
$ip
) =
$self
->_unpack_addr(
$addr
);
return
$entry
unless
$email
ne
''
&& (
defined
$ip
||
defined
$signedby
);
my
$sql
=
"SELECT msgcount, totscore FROM $self->{tablename} "
.
"WHERE username = ? AND email = ?"
;
my
@args
= (
$email
);
if
(!
$self
->{_with_awl_signer}) {
$sql
.=
" AND ip = ?"
;
push
(
@args
,
$ip
);
}
else
{
my
@signedby
= !
defined
$signedby
? () :
split
(
' '
,
lc
$signedby
);
if
(!
@signedby
) {
$sql
.=
" AND signedby = '' AND ip = ?"
;
push
(
@args
,
$ip
);
}
elsif
(
@signedby
== 1) {
$sql
.=
" AND signedby = ?"
;
}
elsif
(
@signedby
> 1) {
$sql
.=
" AND signedby IN ("
.
join
(
','
, (
'?'
) x
@signedby
) .
")"
;
}
push
(
@args
,
@signedby
);
}
$sql
.=
" ORDER BY last_hit"
;
my
$sth
=
$self
->{dbh}->prepare(
$sql
);
unless
(
defined
(
$sth
)) {
info(
"auto-welcomelist: sql-based get_addr_entry %s: SQL prepare error: %s"
,
join
(
'|'
,
@args
),
$self
->{dbh}->errstr);
return
$entry
;
}
my
$rc
=
$sth
->execute(
$self
->{_username},
@args
);
if
(!
$rc
) {
info(
"auto-welcomelist: sql-based get_addr_entry %s: SQL error: %s"
,
join
(
'|'
,
@args
),
$sth
->errstr);
$entry
->{msgcount} = 0;
$entry
->{totscore} = 0;
}
else
{
my
$cnt
= 0;
my
$aryref
;
while
(
defined
(
$aryref
=
$sth
->fetchrow_arrayref()) ) {
if
(
defined
$entry
->{msgcount} &&
defined
$aryref
->[1]) {
$entry
->{msgcount} =
$aryref
->[0];
$entry
->{totscore} =
$aryref
->[1];
}
$entry
->{exists_p} = 1;
$cnt
++;
}
dbg(
"auto-welcomelist: sql-based get_addr_entry: %s for %s"
,
$cnt
?
"found $cnt entries"
:
'no entries found'
,
join
(
'|'
,
@args
) );
}
$sth
->finish();
dbg(
"auto-welcomelist: sql-based %s scores %.1f, msgcount %s"
,
join
(
'|'
,
@args
),
$entry
->{totscore},
$entry
->{msgcount});
return
$entry
;
}
sub
add_score {
my
(
$self
,
$entry
,
$score
) =
@_
;
return
if
(!
$entry
->{addr});
my
(
$email
,
$ip
) =
$self
->_unpack_addr(
$entry
->{addr});
$entry
->{msgcount} += 1;
$entry
->{totscore} +=
$score
;
my
$signedby
=
$entry
->{signedby};
return
$entry
unless
$email
ne
''
&& (
defined
$ip
||
defined
$signedby
);
my
$inserted
= 0;
{
my
@fields
=
qw(username email ip msgcount totscore)
;
my
@signedby
;
if
(
$self
->{_with_awl_signer}) {
push
(
@fields
,
'signedby'
);
@signedby
= !
defined
$signedby
? () :
split
(
' '
,
lc
$signedby
);
@signedby
= (
''
)
if
!
@signedby
;
}
my
@args
= (
$self
->{_username},
$email
,
$ip
, 1,
$score
);
my
$sql
=
sprintf
(
"INSERT INTO %s (%s) VALUES (%s)"
,
$self
->{tablename},
join
(
','
,
@fields
),
join
(
','
, (
'?'
) x
@fields
));
if
(
$self
->{dsn} =~ /^DBI:(?:pg|SQLite)/i) {
$sql
.=
" ON CONFLICT (username, email, signedby, ip) DO UPDATE set msgcount = ?, totscore = totscore + ?"
;
}
elsif
(
$self
->{dsn} =~ /^DBI:(?:mysql|MariaDB)/i) {
$sql
.=
" ON DUPLICATE KEY UPDATE msgcount = ?, totscore = totscore + ?"
;
}
my
$sth
=
$self
->{dbh}->prepare(
$sql
);
unless
(
defined
(
$sth
)) {
info(
"auto-welcomelist: sql-based add_score/insert %s: SQL prepare error: %s"
,
join
(
'|'
,
@args
),
$self
->{dbh}->errstr);
return
$entry
;
}
if
(!
$self
->{_with_awl_signer}) {
my
$rc
;
if
(
$self
->{dsn} =~ /^DBI:(?:pg|SQLite|mysql|MariaDB)/i) {
$rc
=
$sth
->execute(
@args
,
$entry
->{msgcount},
$score
);
}
else
{
$rc
=
$sth
->execute(
@args
);
}
if
(!
$rc
) {
dbg(
"auto-welcomelist: sql-based add_score/insert %s: SQL error: %s"
,
join
(
'|'
,
@args
),
$sth
->errstr);
}
else
{
dbg(
"auto-welcomelist: sql-based add_score/insert "
.
"score %s: %s"
,
$score
,
join
(
'|'
,
@args
));
$inserted
= 1;
$entry
->{exists_p} = 1;
}
}
else
{
for
my
$s
(
@signedby
) {
my
$rc
;
if
(
$self
->{dsn} =~ /^DBI:(?:pg|SQLite|mysql|MariaDB)/i) {
$rc
=
$sth
->execute(
@args
,
$s
,
$entry
->{msgcount},
$score
);
}
else
{
$rc
=
$sth
->execute(
@args
,
$s
);
}
if
(!
$rc
) {
dbg(
"auto-welcomelist: sql-based add_score/insert %s: SQL error: %s"
,
join
(
'|'
,
@args
,
$s
),
$sth
->errstr);
}
else
{
dbg(
"auto-welcomelist: sql-based add_score/insert "
.
"score %s: %s"
,
$score
,
join
(
'|'
,
@args
,
$s
));
$inserted
= 1;
$entry
->{exists_p} = 1;
}
}
}
}
if
(!
$inserted
&&
$self
->{dsn} !~ /^DBI:(?:pg|SQLite|mysql|MariaDB)/i) {
my
$sql
=
"UPDATE $self->{tablename} "
.
"SET msgcount = ?, totscore = totscore + ? "
.
"WHERE username = ? AND email = ?"
;
my
(
@args
) = (
$entry
->{msgcount},
$score
,
$self
->{_username},
$email
);
if
(
$self
->{_with_awl_signer}) {
my
@signedby
= !
defined
$signedby
? () :
split
(
' '
,
lc
$signedby
);
if
(!
@signedby
) {
$sql
.=
" AND signedby = ''"
;
}
elsif
(
@signedby
== 1) {
$sql
.=
" AND signedby = ?"
;
}
elsif
(
@signedby
> 1) {
$sql
.=
" AND signedby IN ("
.
join
(
','
, (
'?'
) x
@signedby
) .
")"
;
}
push
(
@args
,
@signedby
);
}
$sql
.=
" AND ip = ?"
;
push
(
@args
,
$ip
);
my
$sth
=
$self
->{dbh}->prepare(
$sql
);
unless
(
defined
(
$sth
)) {
info(
"auto-welcomelist: sql-based add_score/update %s: SQL prepare error: %s"
,
join
(
'|'
,
@args
),
$self
->{dbh}->errstr);
return
$entry
;
}
my
$rc
=
$sth
->execute(
@args
);
if
(!
$rc
) {
info(
"auto-welcomelist: sql-based add_score/update %s: SQL error: %s"
,
join
(
'|'
,
@args
),
$sth
->errstr);
}
else
{
dbg(
"auto-welcomelist: sql-based add_score/update "
.
"new msgcount: %s, new totscore: %s for %s"
,
$entry
->{msgcount},
$entry
->{totscore},
join
(
'|'
,
@args
));
$entry
->{exists_p} = 1;
}
}
return
$entry
;
}
sub
remove_entry {
my
(
$self
,
$entry
) =
@_
;
my
(
$email
,
$ip
) =
$self
->_unpack_addr(
$entry
->{addr});
return
unless
(
$email
&&
$ip
);
my
$sql
=
"DELETE FROM $self->{tablename} WHERE username = ? AND email = ?"
;
my
@args
= (
$self
->{_username},
$email
);
if
(
$ip
eq
'none'
) {
dbg(
"auto-welcomelist: sql-based remove_entry: removing all entries matching $email"
);
}
else
{
$sql
.=
" AND ip = ?"
;
push
(
@args
,
$ip
);
dbg(
"auto-welcomelist: sql-based remove_entry: removing single entry matching "
.
$entry
->{addr});
}
my
$signedby
=
$entry
->{signedby};
if
(
$self
->{_with_awl_signer} &&
defined
$signedby
) {
my
@signedby
=
split
(
' '
,
lc
$signedby
);
if
(
@signedby
== 1) {
$sql
.=
" AND signedby = ?"
;
}
elsif
(
@signedby
> 1) {
$sql
.=
" AND signedby IN ("
.
join
(
','
, (
'?'
) x
@signedby
) .
")"
;
}
push
(
@args
,
@signedby
);
}
my
$sth
=
$self
->{dbh}->prepare(
$sql
);
unless
(
defined
(
$sth
)) {
info(
"auto-welcomelist: sql-based remove_entry %s: SQL prepare error: %s"
,
join
(
'|'
,
@args
),
$self
->{dbh}->errstr);
return
;
}
my
$rc
=
$sth
->execute(
@args
);
if
(!
$rc
) {
info(
"auto-welcomelist: sql-based remove_entry %s: SQL error: %s"
,
join
(
'|'
,
@args
),
$sth
->errstr);
}
else
{
}
$entry
=
undef
;
}
sub
finish {
my
(
$self
) =
@_
;
dbg(
"auto-welcomelist: sql-based finish: disconnected from "
.
$self
->{dsn});
$self
->{dbh}->disconnect();
}
sub
_unpack_addr {
my
(
$self
,
$addr
) =
@_
;
my
(
$email
,
$ip
) =
split
(/\|ip=/,
$addr
);
unless
(
$email
&&
$ip
) {
dbg(
"auto-welcomelist: sql-based _unpack_addr: unable to decode $addr"
);
}
return
(
$email
,
$ip
);
}
1;