our
(
$io_socket_module_name
);
BEGIN {
$io_socket_module_name
=
'IO::Socket::IP'
;
$io_socket_module_name
=
'IO::Socket::INET6'
;
$io_socket_module_name
=
'IO::Socket::INET'
;
}
}
my
$EOL
=
"\015\012"
;
my
$BLANK
=
$EOL
x 2;
my
$PROTOVERSION
=
'SPAMC/1.5'
;
sub
new {
my
(
$class
,
$args
) =
@_
;
$class
=
ref
(
$class
) ||
$class
;
my
$self
= {};
if
(
$args
->{socketpath}) {
$self
->{socketpath} =
$args
->{socketpath};
}
else
{
$self
->{port} =
$args
->{port};
$self
->{host} =
$args
->{host};
}
if
(
defined
$args
->{username}) {
$self
->{username} =
$args
->{username};
}
if
(
$args
->{max_size}) {
$self
->{max_size} =
$args
->{max_size};
}
if
(
$args
->{timeout}) {
$self
->{timeout} =
$args
->{timeout} || 30;
}
bless
(
$self
,
$class
);
$self
;
}
sub
process {
my
(
$self
,
$msg
,
$is_check_p
) =
@_
;
my
$command
=
'PROCESS'
;
if
(
$is_check_p
) {
warn
"Passing in \$is_check_p is deprecated, just call the check method instead.\n"
;
$command
=
'CHECK'
;
}
return
$self
->_filter(
$msg
,
$command
);
}
sub
spam_report {
my
(
$self
,
$msg
) =
@_
;
return
$self
->_filter(
$msg
,
'REPORT'
);
}
sub
spam_report_ifspam {
my
(
$self
,
$msg
) =
@_
;
return
$self
->_filter(
$msg
,
'REPORT_IFSPAM'
);
}
sub
check {
my
(
$self
,
$msg
) =
@_
;
return
$self
->_filter(
$msg
,
'CHECK'
);
}
sub
headers {
my
(
$self
,
$msg
) =
@_
;
return
$self
->_filter(
$msg
,
'HEADERS'
);
}
sub
learn {
my
(
$self
,
$msg
,
$learntype
) =
@_
;
$self
->_clear_errors();
my
$remote
=
$self
->_create_connection();
return
unless
$remote
;
my
$msgsize
=
length
(
$msg
.
$EOL
);
print
$remote
"TELL $PROTOVERSION$EOL"
;
print
$remote
"Content-length: $msgsize$EOL"
;
print
$remote
"User: $self->{username}$EOL"
if
defined
$self
->{username};
if
(
$learntype
== 0) {
print
$remote
"Message-class: spam$EOL"
;
print
$remote
"Set: local$EOL"
;
}
elsif
(
$learntype
== 1) {
print
$remote
"Message-class: ham$EOL"
;
print
$remote
"Set: local$EOL"
;
}
elsif
(
$learntype
== 2) {
print
$remote
"Remove: local$EOL"
;
}
else
{
$self
->{resp_code} = 00;
$self
->{resp_msg} =
'do not know'
;
return
;
}
print
$remote
"$EOL"
;
print
$remote
$msg
;
print
$remote
"$EOL"
;
$! = 0;
my
$line
= <
$remote
>;
defined
$line
|| $!==0 or
$!==EBADF ? dbg(
"error reading from spamd (1): $!"
)
:
die
"error reading from spamd (1): $!"
;
return
unless
defined
$line
;
my
(
$version
,
$resp_code
,
$resp_msg
) =
$self
->_parse_response_line(
$line
);
$self
->{resp_code} =
$resp_code
;
$self
->{resp_msg} =
$resp_msg
;
return
unless
$resp_code
== 0;
my
$did_set
=
''
;
my
$did_remove
=
''
;
for
($!=0;
defined
(
$line
=<
$remote
>); $!=0) {
local
$1;
if
(
$line
=~ /DidSet: (.*)/i) {
$did_set
= $1;
}
elsif
(
$line
=~ /DidRemove: (.*)/i) {
$did_remove
= $1;
}
elsif
(
$line
=~ /^${EOL}$/) {
last
;
}
}
defined
$line
|| $!==0 or
$!==EBADF ? dbg(
"error reading from spamd (2): $!"
)
:
die
"error reading from spamd (2): $!"
;
close
$remote
or
die
"error closing socket: $!"
;
if
(
$learntype
== 0 ||
$learntype
== 1) {
return
index
(
$did_set
,
'local'
) >= 0;
}
else
{
return
index
(
$did_remove
,
'local'
) >= 0;
}
}
sub
report {
my
(
$self
,
$msg
) =
@_
;
$self
->_clear_errors();
my
$remote
=
$self
->_create_connection();
return
unless
$remote
;
my
$msgsize
=
length
(
$msg
.
$EOL
);
print
$remote
"TELL $PROTOVERSION$EOL"
;
print
$remote
"Content-length: $msgsize$EOL"
;
print
$remote
"User: $self->{username}$EOL"
if
defined
$self
->{username};
print
$remote
"Message-class: spam$EOL"
;
print
$remote
"Set: local,remote$EOL"
;
print
$remote
"$EOL"
;
print
$remote
$msg
;
print
$remote
"$EOL"
;
$! = 0;
my
$line
= <
$remote
>;
defined
$line
|| $!==0 or
$!==EBADF ? dbg(
"error reading from spamd (3): $!"
)
:
die
"error reading from spamd (3): $!"
;
return
unless
defined
$line
;
my
(
$version
,
$resp_code
,
$resp_msg
) =
$self
->_parse_response_line(
$line
);
$self
->{resp_code} =
$resp_code
;
$self
->{resp_msg} =
$resp_msg
;
return
unless
$resp_code
== 0;
my
$reported_p
= 0;
for
($!=0;
defined
(
$line
=<
$remote
>); $!=0) {
if
(
$line
=~ /DidSet:\s+.
*remote
/i) {
$reported_p
= 1;
last
;
}
elsif
(
$line
=~ /^${EOL}$/) {
last
;
}
}
defined
$line
|| $!==0 or
$!==EBADF ? dbg(
"error reading from spamd (4): $!"
)
:
die
"error reading from spamd (4): $!"
;
close
$remote
or
die
"error closing socket: $!"
;
return
$reported_p
;
}
sub
revoke {
my
(
$self
,
$msg
) =
@_
;
$self
->_clear_errors();
my
$remote
=
$self
->_create_connection();
return
unless
$remote
;
my
$msgsize
=
length
(
$msg
.
$EOL
);
print
$remote
"TELL $PROTOVERSION$EOL"
;
print
$remote
"Content-length: $msgsize$EOL"
;
print
$remote
"User: $self->{username}$EOL"
if
defined
$self
->{username};
print
$remote
"Message-class: ham$EOL"
;
print
$remote
"Set: local$EOL"
;
print
$remote
"Remove: remote$EOL"
;
print
$remote
"$EOL"
;
print
$remote
$msg
;
print
$remote
"$EOL"
;
$! = 0;
my
$line
= <
$remote
>;
defined
$line
|| $!==0 or
$!==EBADF ? dbg(
"error reading from spamd (5): $!"
)
:
die
"error reading from spamd (5): $!"
;
return
unless
defined
$line
;
my
(
$version
,
$resp_code
,
$resp_msg
) =
$self
->_parse_response_line(
$line
);
$self
->{resp_code} =
$resp_code
;
$self
->{resp_msg} =
$resp_msg
;
return
unless
$resp_code
== 0;
my
$revoked_p
= 0;
for
($!=0;
defined
(
$line
=<
$remote
>); $!=0) {
if
(
$line
=~ /DidRemove:\s+remote/i) {
$revoked_p
= 1;
last
;
}
elsif
(
$line
=~ /^${EOL}$/) {
last
;
}
}
defined
$line
|| $!==0 or
$!==EBADF ? dbg(
"error reading from spamd (6): $!"
)
:
die
"error reading from spamd (6): $!"
;
close
$remote
or
die
"error closing socket: $!"
;
return
$revoked_p
;
}
sub
ping {
my
(
$self
) =
@_
;
my
$remote
=
$self
->_create_connection();
return
0
unless
(
$remote
);
print
$remote
"PING $PROTOVERSION$EOL"
;
print
$remote
"$EOL"
;
$! = 0;
my
$line
= <
$remote
>;
defined
$line
|| $!==0 or
$!==EBADF ? dbg(
"error reading from spamd (7): $!"
)
:
die
"error reading from spamd (7): $!"
;
close
$remote
or
die
"error closing socket: $!"
;
return
unless
defined
$line
;
my
(
$version
,
$resp_code
,
$resp_msg
) =
$self
->_parse_response_line(
$line
);
return
0
unless
(
$resp_msg
eq
'PONG'
);
return
1;
}
sub
_create_connection {
my
(
$self
) =
@_
;
my
$remote
;
if
(
$self
->{socketpath}) {
$remote
= IO::Socket::UNIX->new(
Peer
=>
$self
->{socketpath},
Type
=> SOCK_STREAM,
Timeout
=>
$self
->{timeout},
);
}
else
{
my
%params
= (
Proto
=>
"tcp"
,
PeerAddr
=>
$self
->{host},
PeerPort
=>
$self
->{port},
Timeout
=>
$self
->{timeout},
);
$remote
=
$io_socket_module_name
->new(
%params
);
}
unless
(
$remote
) {
warn
"Failed to create connection to spamd daemon: $!\n"
;
return
;
}
$remote
;
}
sub
_parse_response_line {
my
(
$self
,
$line
) =
@_
;
$line
=~ s/\r?\n$//;
return
split
(/\s+/,
$line
, 3);
}
sub
_clear_errors {
my
(
$self
) =
@_
;
$self
->{resp_code} =
undef
;
$self
->{resp_msg} =
undef
;
}
sub
_filter {
my
(
$self
,
$msg
,
$command
) =
@_
;
my
%data
;
my
$msgsize
;
$self
->_clear_errors();
my
$remote
=
$self
->_create_connection();
return
0
unless
(
$remote
);
if
(
defined
$self
->{max_size}) {
$msg
=
substr
(
$msg
,0,
$self
->{max_size});
}
$msgsize
=
length
(
$msg
);
print
$remote
"$command $PROTOVERSION$EOL"
;
print
$remote
"Content-length: $msgsize$EOL"
;
print
$remote
"User: $self->{username}$EOL"
if
defined
$self
->{username};
print
$remote
"$EOL"
;
print
$remote
$msg
;
print
$remote
"$EOL"
;
$! = 0;
my
$line
= <
$remote
>;
defined
$line
|| $!==0 or
$!==EBADF ? dbg(
"error reading from spamd (8): $!"
)
:
die
"error reading from spamd (8): $!"
;
return
unless
defined
$line
;
my
(
$version
,
$resp_code
,
$resp_msg
) =
$self
->_parse_response_line(
$line
);
$self
->{resp_code} =
$resp_code
;
$self
->{resp_msg} =
$resp_msg
;
return
unless
$resp_code
== 0;
for
($!=0;
defined
(
$line
=<
$remote
>); $!=0) {
local
($1,$2,$3);
if
(
$line
=~ /Content-
length
: (\d+)/) {
$data
{content_length} = $1;
}
elsif
(
$line
=~ m!Spam: (\S+) ; (\S+) / (\S+)!) {
$data
{isspam} = $1;
$data
{score} = $2 + 0;
$data
{threshold} = $3 + 0;
}
elsif
(
$line
=~ /^${EOL}$/) {
last
;
}
}
defined
$line
|| $!==0 or
$!==EBADF ? dbg(
"error reading from spamd (9): $!"
)
:
die
"error reading from spamd (9): $!"
;
my
$return_msg
;
for
($!=0;
defined
(
$line
=<
$remote
>); $!=0) {
$return_msg
.=
$line
;
}
defined
$line
|| $!==0 or
$!==EBADF ? dbg(
"error reading from spamd (10): $!"
)
:
die
"error reading from spamd (10): $!"
;
if
(
$command
=~ /^REPORT/) {
$data
{report} =
$return_msg
if
(
$return_msg
);
}
else
{
$data
{message} =
$return_msg
if
(
$return_msg
);
}
close
$remote
or
die
"error closing socket: $!"
;
return
\
%data
;
}
1;