diff options
Diffstat (limited to 'utils/cgp_rspamd.pl')
-rw-r--r-- | utils/cgp_rspamd.pl | 194 |
1 files changed, 194 insertions, 0 deletions
diff --git a/utils/cgp_rspamd.pl b/utils/cgp_rspamd.pl new file mode 100644 index 000000000..41a48026b --- /dev/null +++ b/utils/cgp_rspamd.pl @@ -0,0 +1,194 @@ +#!/usr/bin/env perl + +use warnings; +use strict; +use JSON::XS; +use AnyEvent; +use AnyEvent::HTTP; +use AnyEvent::IO; +use EV; +use Pod::Usage; +use Getopt::Long; + +my $rspamd_host = "localhost:11333"; +my $man = 0; +my $help = 0; +my $local = 0; +my $header = "X-Spam: yes"; +my $reject_message = "Spam message rejected"; + +GetOptions( + "host=s" => \$rspamd_host, + "header=s" => \$header, + "local" => \$local, + "reject-message=s" => \$reject_message, + "help|?" => \$help, + "man" => \$man +) or pod2usage(2); + +pod2usage(1) if $help; +pod2usage(-exitval => 0, -verbose => 2) if $man; + +sub cgp_string { + my ($in) = @_; + + $in =~ s/\"/\\"/; + $in =~ s/\n/\\n/; + $in =~ s/\r/\\r/; + + return "\"$in\""; +} + +sub rspamd_scan { + my ($tag, $file) = @_; + + my $http_callback = sub { + my ($body, $hdr) = @_; + + if ($hdr->{Status} =~ /^2/) { + my $js = decode_json($body); + + if (!$js) { + print "* Rspamd: Bad response for $file: invalid JSON: parse error\n"; + print "$tag FAILURE\n"; + } + else { + my $def = $js->{'default'}; + + if (!$def) { + print "* Rspamd: Bad response for $file: invalid JSON: default is missing\n"; + print "$tag FAILURE\n"; + } + else { + my $action = $def->{'action'}; + my $id = $js->{'message-id'}; + + my $symbols = ""; + while (my ($k, $s) = each(%{$def})) { + if (ref($s) eq "HASH" && $s->{'score'}) { + $symbols .= sprintf "%s(%.2f);", $k, $s->{'score'}; + } + } + + printf "* Rspamd: Scanned %s; id: <%s>; Score: %.2f / %.2f; Symbols: [%s]\n", + $file, $id, $def->{'score'}, $def->{'required_score'}, $symbols; + + if ($action eq 'reject') { + print "$tag ERROR " . cgp_string($reject_message) . "\n"; + } + elsif ($action eq 'add header' || $action eq 'rewrite subject') { + print "$tag ADDHEADER " . cgp_string($header) . " OK\n"; + } + elsif ($action eq 'soft reject') { + print "$tag REJECT Try again later\n"; + } + else { + print "$tag OK\n"; + } + } + } + } else { + print "* Rspamd: Bad response for $file: HTTP error: $hdr->{Status} $hdr->{Reason}\n"; + print "$tag FAILURE\n"; + } + }; + + if ($local) { + # Use file scan + http_get("http://$rspamd_host/symbols?file=$file", $http_callback); + } + else { + aio_load($file, sub { + my ($data) = @_ or return print "* Cannot open $file: $!\n$tag FAILURE\n"; + + http_post("http://$rspamd_host/symbols", $data, $http_callback); + }); + } +} + +# Show informational message +print "* Rspamd CGP filter has been started\n"; + +my $w = AnyEvent->io( + fh => \*STDIN, + poll => 'r', cb => sub { + chomp (my $input = <STDIN>); + + if ($input =~ /^(\d+)\s+(\S+)(\s+(\S+)\s*)?$/) { + my $tag = $1; + my $cmd = $2; + + if ($cmd eq "INTF") { + print "$input\n"; + } + elsif ($cmd eq "FILE" && $4) { + my $file = $4; + print "* Scanning file $file\n"; + rspamd_scan $tag, $file; + } + } + } +); + +EV::run; + +__END__ + +=head1 NAME + +cgp_rspamd - implements Rspamd filter for CommunigatePro MTA + +=head1 SYNOPSIS + +cgp_rspamd [options] + + Options: + --host=hostport Rspamd host to connect (localhost:11333 by default) + --local Rspamd runs locally and can access CGP files (false by default) + --header Add specific header for a spam message ("X-Spam: yes" by default) + --reject-message Rejection message for spam mail ("Spam message rejected" by default) + --help brief help message + --man full documentation + +=head1 OPTIONS + +=over 8 + +=item B<--host> + +Specifies Rspamd host to use for scanning + +=item B<--local> + +Should be used if Rspamd runs on the same machine and can access CGP files + +=item B<--header> + +Specifies the header that should be added when Rspamd action is B<add header> +or B<rewrite subject>. + +=item B<--reject-message> + +Specifies the rejection message for spam. + +=item B<--help> + +Print a brief help message and exits. + +=item B<--man> + +Prints the manual page and exits. + +=back + +=head1 DESCRIPTION + +B<cgp_rspamd> is intended to scan messages processed with B<CommunigatePro> MTA +on some Rspamd scanner. It reads standard input and parses CGP helpers +protocol. On scan requests, this filter can query Rspamd to process a message. +B<cgp_rspamd> can tell CGP to add header or reject SPAM messages depending on +Rspamd scan result. + +=back + +=cut |