home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
ftp.f-secure.com
/
2014.06.ftp.f-secure.com.tar
/
ftp.f-secure.com
/
support
/
hotfix
/
fsis
/
IS-SpamControl.fsfix
/
iufssc
/
lib
/
Mail
/
SpamAssassin
/
Client.pm
< prev
next >
Wrap
Text File
|
2006-11-29
|
10KB
|
500 lines
# NOTE: This interface is alpha at best, and almost guaranteed to change
# <@LICENSE>
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to you under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at:
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# </@LICENSE>
=head1 NAME
Mail::SpamAssassin::Client - Client for spamd Protocol
NOTE: This interface is alpha at best, and almost guaranteed to change
=head1 SYNOPSIS
my $client = new Mail::SpamAssassin::Client({port => 783,
host => 'localhost',
username => 'someuser'});
if ($client->ping()) {
print "Ping is ok\n";
}
my $result = $client->process($testmsg);
if ($result->{isspam} eq 'True') {
do something with spam message here
}
=head1 DESCRIPTION
Mail::SpamAssassin::Client is a module that provides a perl implementation for
the spamd protocol.
=cut
package Mail::SpamAssassin::Client;
use IO::Socket;
my $EOL = "\015\012";
my $BLANK = $EOL x 2;
my $PROTOVERSION = 'SPAMC/1.3';
=head1 PUBLIC METHODS
=head2 new
public class (Mail::SpamAssassin::Client) new (\% $args)
Description:
This method creates a new Mail::SpamAssassin::Client object.
=cut
sub new {
my ($class, $args) = @_;
$class = ref($class) || $class;
my $self = {};
# with a sockets_path set then it makes no sense to set host and port
if ($args->{socketpath}) {
$self->{socketpath} = $args->{socketpath};
}
else {
$self->{port} = $args->{port};
$self->{host} = $args->{host};
}
if ($args->{username}) {
$self->{username} = $args->{username};
}
bless($self, $class);
$self;
}
=head2 process
public instance (\%) process (String $msg, Boolean $is_check_p)
Description:
This method makes a call to the spamd server and depending on the value of
C<$is_check_p> either calls PROCESS or CHECK.
The return value is a hash reference containing several pieces of information,
if available:
content_length
isspam
score
threshold
message
=cut
sub process {
my ($self, $msg, $is_check_p) = @_;
my %data;
my $command = $is_check_p ? 'CHECK' : 'PROCESS';
$self->_clear_errors();
my $remote = $self->_create_connection();
return 0 unless ($remote);
my $msgsize = length($msg.$EOL);
print $remote "$command $PROTOVERSION$EOL";
print $remote "Content-length: $msgsize$EOL";
print $remote "User: $self->{username}$EOL" if ($self->{username});
print $remote "$EOL";
print $remote $msg;
print $remote "$EOL";
my $line = <$remote>;
return undef 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 undef unless ($resp_code == 0);
while ($line = <$remote>) {
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;
}
}
my $return_msg;
while(<$remote>) {
$return_msg .= $_;
}
$data{message} = $return_msg if ($return_msg);
close $remote;
return \%data;
}
=head2 check
public instance (\%) check (String $msg)
Description:
The method implements the check call.
Since check and process are so similar, we simply pass this
call along to the process method with a flag to indicate
to actually make the CHECK call.
See the process method for the return value.
=cut
sub check {
my ($self, $msg) = @_;
return $self->process($msg, 1);
}
=head2 learn
public instance (Boolean) learn (String $msg, Integer $learntype)
Description:
This method implements the learn call. C<$learntype> should be
an integer, 0 for spam, 1 for ham and 2 for forget. The return
value is a boolean indicating if the message was learned or not.
An undef return value indicates that there was an error and you should
check the resp_code/resp_msg values to determine what the error was.
=cut
sub learn {
my ($self, $msg, $learntype) = @_;
$self->_clear_errors();
my $remote = $self->_create_connection();
return undef 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 ($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 { # bad learntype
$self->{resp_code} = 00;
$self->{resp_msg} = 'do not know';
return undef;
}
print $remote "$EOL";
print $remote $msg;
print $remote "$EOL";
my $line = <$remote>;
return undef 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 undef unless ($resp_code == 0);
my $did_set;
my $did_remove;
while ($line = <$remote>) {
if ($line =~ /DidSet: (.*)/i) {
$did_set = $1;
}
elsif ($line =~ /DidRemove: (.*)/i) {
$did_remove = $1;
}
elsif ($line =~ /^${EOL}$/) {
last;
}
}
close $remote;
if ($learntype == 0 || $learntype == 1) {
return $did_set =~ /local/;
}
else { #safe since we've already checked the $learntype values
return $did_remove =~ /local/;
}
}
=head2 report
public instance (Boolean) report (String $msg)
Description:
This method provides the report interface to spamd.
=cut
sub report {
my ($self, $msg) = @_;
$self->_clear_errors();
my $remote = $self->_create_connection();
return undef 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 ($self->{username});
print $remote "Message-class: spam$EOL";
print $remote "Set: local,remote$EOL";
print $remote "$EOL";
print $remote $msg;
print $remote "$EOL";
my $line = <$remote>;
return undef 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 undef unless ($resp_code == 0);
my $reported_p = 0;
while (($line = <$remote>)) {
if ($line =~ /DidSet:\s+.*remote/i) {
$reported_p = 1;
last;
}
elsif ($line =~ /^${EOL}$/) {
last;
}
}
close $remote;
return $reported_p;
}
=head2 revoke
public instance (Boolean) revoke (String $msg)
Description:
This method provides the revoke interface to spamd.
=cut
sub revoke {
my ($self, $msg) = @_;
$self->_clear_errors();
my $remote = $self->_create_connection();
return undef 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 ($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";
my $line = <$remote>;
return undef 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 undef unless ($resp_code == 0);
my $revoked_p = 0;
while (!$revoked_p && ($line = <$remote>)) {
if ($line =~ /DidRemove:\s+remote/i) {
$revoked_p = 1;
last;
}
elsif ($line =~ /^${EOL}$/) {
last;
}
}
close $remote;
return $revoked_p;
}
=head2 ping
public instance (Boolean) ping ()
Description:
This method performs a server ping and returns 0 or 1 depending on
if the server responded correctly.
=cut
sub ping {
my ($self) = @_;
my $remote = $self->_create_connection();
return 0 unless ($remote);
print $remote "PING $PROTOVERSION$EOL";
print $remote "$EOL";
my $line = <$remote>;
close $remote;
return undef unless (defined $line);
my ($version, $resp_code, $resp_msg) = $self->_parse_response_line($line);
return 0 unless ($resp_msg =~ /^PONG/);
return 1;
}
=head1 PRIVATE METHODS
=head2 _create_connection
private instance (IO::Socket) _create_connection ()
Description:
This method sets up a proper IO::Socket connection based on the arguments
used when greating the client object.
On failure, it sets an internal error code and returns undef.
=cut
sub _create_connection {
my ($self) = @_;
my $remote;
if ($self->{socketpath}) {
$remote = IO::Socket::UNIX->new( Peer => $self->{socketpath},
Type => SOCK_STREAM,
);
}
else {
$remote = IO::Socket::INET->new( Proto => "tcp",
PeerAddr => $self->{host},
PeerPort => $self->{port},
);
}
unless ($remote) {
print "Failed to create connection to spamd daemon: $!\n";
return undef;
}
$remote;
}
=head2 _parse_response_line
private instance (@) _parse_response_line (String $line)
Description:
This method parses the initial response line/header from the server
and returns its parts.
We have this as a seperate method in case we ever decide to get fancy
with the response line.
=cut
sub _parse_response_line {
my ($self, $line) = @_;
return split(/\s+/, $line, 3);
}
=head2 _clear_errors
private instance () _clear_errors ()
Description:
This method clears out any current errors.
=cut
sub _clear_errors {
my ($self) = @_;
$self->{resp_code} = undef;
$self->{resp_msg} = undef;
}
1;