|
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775 |
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- package Snf4sa;
- use strict;
- use Mail::SpamAssassin;
- use Mail::SpamAssassin::Plugin;
- use Mail::SpamAssassin::PerMsgStatus;
- use Mail::SpamAssassin::Logger;
- use IO::Socket;
- use IO::File;
-
- use File::Temp qw/ tempfile tempdir /;
- our @ISA = qw(Mail::SpamAssassin::Plugin);
-
-
- my $CRLF = "\x0d\x0a";
-
-
- my $rule_code_xlat = {
- 0 => 'Standard White Rules',
- 20 => 'GBUdb Truncate (superblack)',
- 40 => 'GBUdb Caution (suspicious)',
- 47 => 'Travel',
- 48 => 'Insurance',
- 49 => 'Antivirus Push',
- 50 => 'Media Theft',
- 51 => 'Spamware',
- 52 => 'Snake Oil',
- 53 => 'Scam Patterns',
- 54 => 'Porn/Adult',
- 55 => 'Malware & Scumware Greetings',
- 56 => 'Ink & Toner',
- 57 => 'Get Rich',
- 58 => 'Debt & Credit',
- 59 => 'Casinos & Gambling',
- 60 => 'Ungrouped Black Rules',
- 61 => 'Experimental Abstract',
- 62 => 'Obfuscation Techniques',
- 63 => 'Experimental Received [ip]',
- };
-
- sub new {
- my ($class, $mailsa) = @_;
- $class = ref($class) || $class;
- my $self = $class->SUPER::new($mailsa);
- bless ($self, $class);
-
-
- $self->register_eval_rule ("snf4sa_sacheck");
-
-
- $self->{SNF_Host} = "localhost";
-
-
- $self->{SNF_Port} = 9001;
-
-
- $self->{SNF_Timeout} = 1;
-
-
- $self->{Temp_Dir} = '/tmp/snf4sa';
-
-
- $self->{SNF_MaxTempFileSize} = 64 * 1024;
-
-
- $self->{GBUdb_ConfidenceKey} = "c=";
-
-
- $self->{GBUdb_ProbabilityKey} = "p=";
-
-
- $self->{GBUdb_MaxWeightKey} = "gbudb_max_weight";
-
-
- $self->{SNF_CodeKey} = "snf_result";
-
-
- $self->{SA_DeltaScoreKey} = "sa_score";
-
-
- $self->{SA_ShortCircuitYesKey} = "short_circuit_yes";
-
-
- $self->{SA_ShortCircuitNoKey} = "short_circuit_no";
-
-
- $self->{Plugin_score_thresholdKey} = "pre_3.2_plugin_score_threshold";
-
- return $self;
- }
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- sub have_shortcircuited {
-
- my ($self, $options) = @_;
-
- if (defined($options->{permsgstatus}->{shortCircuit})) {
-
- return $options->{permsgstatus}->{shortCircuit};
-
- }
- return 0;
- }
-
- sub parse_config {
-
- my ($self, $options) = @_;
-
-
-
-
-
-
-
-
- if (lc($options->{key}) eq $self->{GBUdb_MaxWeightKey}) {
-
-
- my $tempValue = $options->{value};
-
-
-
- if ($tempValue =~ /^([+-]?)(?=\d|\.\d)\d*(\.\d*)?([Ee]([+-]?\d+))?$/) {
-
-
- $options->{conf}->{gbuDbMaxWeight} = $tempValue;
- $self->inhibit_further_callbacks();
- return 1;
-
- } else {
-
- $self->log_debug("Invalid value for $self->{GBUdb_MaxWeightKey} " .
- $tempValue);
-
- }
- } elsif (lc($options->{key}) eq $self->{Plugin_score_thresholdKey}) {
-
-
- my $tempValue = $options->{value};
-
-
-
- if ($tempValue =~ /^([+-]?)(?=\d|\.\d)\d*(\.\d*)?([Ee]([+-]?\d+))?$/) {
-
-
- $options->{conf}->{pluginScoreThreshold} = $tempValue;
- $self->inhibit_further_callbacks();
- return 1;
-
- } else {
-
- $self->log_debug("Invalid value for $self->{Plugin_score_thresholdKey} " .
- $tempValue);
-
- }
- } elsif (lc($options->{key}) eq $self->{SNF_CodeKey}) {
-
-
- my $snf = $self->parse_snf_sa_mapping($options);
- if (defined($snf)) {
- my @snfCode = @{$snf->{snfCode}};
-
-
-
-
-
- foreach my $i (@{$snf->{snfCode}}) {
-
-
-
- $options->{conf}->{snfSaMapping}->[$i] = {
- deltaScore => $snf->{deltaScore},
- shortCircuit => $snf->{shortCircuit}
- };
-
- }
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- $self->inhibit_further_callbacks();
- return 1;
- }
- }
-
-
- return 0;
-
- }
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- sub parse_snf_sa_mapping
- {
- my ($self, $options) = @_;
-
- my $value = $options->{value};
-
- my $ret_hash = {
- snfCode => undef,
- deltaScore => undef,
- shortCircuit => undef
- };
-
-
- my @snfCode = ();
-
-
- $value =~ s/^\s+//;
- $value =~ s/\s+$//;
-
-
- $value = lc($value);
-
-
- my @specVal = split(/\s+/, $value);
-
- if (0 == @specVal) {
-
-
- $self->log_debug("No separate words found in configuration line '" .
- $options->{line} . "'");
- return undef;
-
- }
-
-
- my $lastSpec;
- for ($lastSpec = 0; $lastSpec < @specVal; $lastSpec++) {
-
-
- if ($specVal[$lastSpec] eq $self->{SA_DeltaScoreKey}) {
-
-
-
- last;
-
- }
-
-
- my @codeVal = $self->get_code_values($specVal[$lastSpec]);
- if (0 == @codeVal) {
-
-
- $self->log_debug("Couldn't parse all the SNFServer code values " .
- "in configuration line '" .
- $options->{line} . "'");
- return undef;
-
- }
-
-
- @snfCode = (@snfCode, @codeVal);
-
- }
-
-
- @snfCode = sort { $a <=> $b } @snfCode;
- my $prev = -1;
- my @temp = grep($_ != $prev && (($prev) = $_), @snfCode);
- $ret_hash->{snfCode} = \@temp;
-
-
-
- $lastSpec++;
- if ($lastSpec >= @specVal) {
-
-
- $self->log_debug("Not enough parameters in configuration line '" .
- $options->{line} . "'");
- return undef;
-
- }
-
-
- $ret_hash->{deltaScore} = $specVal[$lastSpec];
- if (!($ret_hash->{deltaScore} =~
- /^([+-]?)(?=\d|\.\d)\d*(\.\d*)?([Ee]([+-]?\d+))?$/)) {
-
-
- $self->log_debug("Value after '" . $self->{SA_DeltaScoreKey} .
- "' ($specVal[$lastSpec]) must be a number " .
- "in configuration line '" .
- $options->{line} . "'");
- return undef;
-
- }
-
-
- $lastSpec++;
- $ret_hash->{shortCircuit} = 0;
- if ( ($lastSpec + 1) == @specVal) {
-
-
- my $shortCircuitSpec = $specVal[$lastSpec];
- if ($self->{SA_ShortCircuitYesKey} eq $shortCircuitSpec) {
-
-
- $ret_hash->{shortCircuit} = 1;
-
- } elsif ($self->{SA_ShortCircuitNoKey} ne $shortCircuitSpec) {
-
-
- $self->log_debug("Invalid short-circuit specification: '" .
- $specVal[$lastSpec] .
- "' in configuration line '" . $options->{line} .
- "'. Must be '$self->{SA_ShortCircuitYesKey}' " .
- " or '$self->{SA_ShortCircuitNoKey}'.");
- return undef;
-
- }
- } elsif ($lastSpec != @specVal) {
-
-
- $self->log_debug("Too many parameters were specified in " .
- "configuration line '" . $options->{line} . "'");
- return undef;
-
- }
-
- return $ret_hash;
-
- }
-
- sub get_code_values
- {
- my ($self, $specElement) = @_;
-
- my @snfCode = ();
-
-
- my @codeVal = split(/-/, $specElement);
-
- if (1 == @codeVal) {
-
- if ($specElement =~ /^\d+$/) {
-
-
- $snfCode[0] = 1 * $specElement;
-
- }
-
- } elsif (2 == @codeVal) {
-
-
- if ( ($codeVal[0] =~ /^\d+$/) && ($codeVal[1] =~ /^\d+$/) ) {
-
-
- $codeVal[0] = 1 * $codeVal[0];
- $codeVal[1] = 1 * $codeVal[1];
- if ($codeVal[0] <= $codeVal[1]) {
-
-
- for (my $i = $codeVal[0]; $i <= $codeVal[1]; $i++) {
-
- push(@snfCode, $i);
-
- }
-
- }
- }
- }
- return @snfCode;
- }
-
-
-
-
-
-
-
- sub log_debug
- {
- my ($self, $message) = @_;
- dbg("snf4sa: $message");
- }
-
-
- sub snf4sa_sacheck {
-
- my ($self, $permsgstatus, $fulltext) = @_;
-
- my $response ='';
- my $exitvalue;
-
-
- unless(-d $self->{Temp_Dir}) {
- mkdir($self->{Temp_Dir});
- chmod(0777, $self->{Temp_Dir});
- };
-
-
- my $mailtext = substr( ${$fulltext}, 0, $self->{SNF_MaxTempFileSize});
-
-
- my ($fh, $filename) = tempfile( DIR => $self->{Temp_Dir} );
-
-
- my $SNF_fh = IO::File->new( $filename, "w" ) ||
- die(__PACKAGE__ . ": Unable to create temporary file '" . $filename . "'");
- $SNF_fh->print($mailtext) ||
- $self->cleanup_die($filename,
- __PACKAGE__ . ": Unable to write to temporary file '" .
- $filename . "'");
- $SNF_fh->close ||
- $self->cleanup_die($filename,
- __PACKAGE__ . ": Unable to close temporary file '" .
- $filename . "'");
-
-
- my $cnt = chmod(0666, $filename) ||
- $self->cleanup_die($filename, __PACKAGE__ .
- ": Unable to change permissions of temporary file '" .
- $filename . "'");
-
-
- my $SNF_XCI_Return = $self->xci_scan( $filename );
-
-
-
-
- unlink($filename);
-
-
- if (! $SNF_XCI_Return ) {
- die(__PACKAGE__ . ": Internal error");
- }
-
-
- if (! $SNF_XCI_Return->{"success"}) {
- die(__PACKAGE__ . ": Error from SNFServer: " .
- $SNF_XCI_Return->{"message"});
- }
-
-
- my ( $rc, $rcx ) = ( $SNF_XCI_Return->{"code"},
- $rule_code_xlat->{ $SNF_XCI_Return->{"code"} } );
-
- $rc = -1 unless defined $rc;
- $rcx = 'Unknown' unless $rcx;
- my $rch = $SNF_XCI_Return->{"header"};
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- my $deltaScore = 0.0;
-
-
- if (defined($permsgstatus->{main}->{conf}->{snfSaMapping}->[$rc])) {
-
- $deltaScore +=
- $permsgstatus->{main}->{conf}->{snfSaMapping}->[$rc]->{deltaScore};
-
- $permsgstatus->{shortCircuit} =
- $permsgstatus->{main}->{conf}->{snfSaMapping}->[$rc]->{shortCircuit};
-
- }
-
-
- if (defined($permsgstatus->{main}->{conf}->{gbuDbMaxWeight})) {
-
-
-
-
- $deltaScore +=
- $self->calc_GBUdb($SNF_XCI_Return->{header},
- $permsgstatus->{main}->{conf}->{gbuDbMaxWeight});
-
- }
-
-
- $permsgstatus->set_tag("SNFRESULTTAG", "$rc ($rcx)");
- $permsgstatus->set_tag("SNFMESSAGESNIFFERSCANRESULT",
- $self->extract_header_body($SNF_XCI_Return->{header},
- "X-MessageSniffer-Scan-Result"));
- $permsgstatus->set_tag("SNFMESSAGESNIFFERRULES",
- $self->extract_header_body($SNF_XCI_Return->{header},
- "X-MessageSniffer-Rules"));
- $permsgstatus->set_tag("SNFGBUDBANALYSIS",
- $self->extract_header_body($SNF_XCI_Return->{header},
- "X-GBUdb-Analysis"));
-
-
-
- if (Mail::SpamAssassin::Version() le "3.2.0") {
- my $pluginScoreThreshold = 2.5;
- if (defined($permsgstatus->{main}->{conf}->{pluginScoreThreshold})) {
- $pluginScoreThreshold = $permsgstatus->{main}->{conf}->{pluginScoreThreshold};
- }
-
- if ($deltaScore < $pluginScoreThreshold) {
- $deltaScore = 0.0;
- }
- }
-
-
-
- if ($deltaScore) {
-
- $permsgstatus->got_hit("SNF4SA", "", score => $deltaScore);
- for my $set (0..3) {
- $permsgstatus->{conf}->{scoreset}->[$set]->{"SNF4SA"} =
- sprintf("%0.3f", $deltaScore);
- }
-
- }
-
-
-
- return 0;
-
- }
-
-
-
-
-
-
-
-
-
-
-
- sub calc_GBUdb
- {
- my ( $self, $headers, $weight ) = @_;
-
-
- my @headerLine = split(/\n/, $headers);
-
-
- my $line;
- foreach $line (@headerLine) {
-
-
- if ($line =~ /^X-GBUdb-Analysis:/) {
-
-
- my $ind0 = index($line, $self->{GBUdb_ConfidenceKey});
- my $ind1 = index($line, " ", $ind0 + 2);
- if (-1 == $ind0) {
-
- return 0.0;
- }
- my $c = 1.0 * substr($line, $ind0 + 2, $ind1 - $ind0 - 2);
-
-
- $ind0 = index($line, $self->{GBUdb_ProbabilityKey});
- $ind1 = index($line, " ", $ind0 + 2);
- if (-1 == $ind0) {
-
- return 0.0;
- }
- my $p = 1.0 * substr($line, $ind0 + 2, $ind1 - $ind0 - 2);
-
-
-
-
- my $score = abs($p * $c) ** 0.5;
- $score *= $weight;
- if ($p < 0.0) {
- $score *= -1.0;
- }
-
-
-
-
- return $score;
-
- }
- }
- }
-
-
-
-
-
-
-
-
-
-
-
-
- sub extract_header_body
- {
- my ( $self, $headers, $head ) = @_;
- my $body = "";
- if ($headers =~ /$head:(.*)/s) {
-
- my $temp = $1;
- $temp =~ /(.*)\nX-(.*)/s;
- $body = $1;
-
- }
- return $body;
- }
-
-
-
-
-
-
- sub xci_scan
- {
- my ( $self, $file ) = @_;
- return undef unless $self and $file;
-
- my $ret_hash = {
- success => undef,
- code => undef,
- message => undef,
- header => undef,
- xml => undef
- };
-
- my $xci = $self->connect_socket( $self->{SNF_Host}, $self->{SNF_Port} )
- or return $self->err_hash("cannot connect to socket ($!)");
-
- $xci->print("<snf><xci><scanner><scan file='$file' xhdr='yes' /></scanner></xci></snf>\n");
- my $rc = $ret_hash->{xml} = $self->socket_response($xci, $file);
- $xci->close;
-
-
- if ( $rc =~ /^<snf><xci><scanner><result code='(\d*)'>/ ) {
- $ret_hash->{success} = 1;
- $ret_hash->{code} = $1;
- $rc =~ /<xhdr>(.*)<\/xhdr>/s and $ret_hash->{header} = $1;
- } elsif ( $rc =~ /^<snf><xci><error message='(.*)'/ ) {
- $ret_hash->{message} = $1;
- } else {
- $ret_hash->{message} = "unknown XCI response: $rc";
- }
-
- return $ret_hash;
- }
-
-
-
- sub connect_socket
- {
- my ( $self, $host, $port ) = @_;
- return undef unless $self and $host and $port;
- my $protoname = 'tcp';
-
- $self->{XCI_Socket} = IO::Socket::INET->new(
- PeerAddr => $host,
- PeerPort => $port,
- Proto => $protoname,
- Timeout => $self->{SNF_Timeout} ) or return undef;
-
- $self->{XCI_Socket}->autoflush(1);
- return $self->{XCI_Socket};
- }
-
-
-
- sub socket_response
- {
- my ( $self, $rs, $file ) = @_;
- my $buf = '';
-
-
- eval {
- local $SIG{ALRM} = sub { die "timeout\n" };
- alarm $self->{SNF_Timeout};
-
- while (<$rs>) {
- $buf .= $_;
- }
-
- alarm 0;
- };
-
-
- if ( $@ eq "timeout\n" ) {
- $self->cleanup_die($file,
- __PACKAGE__ . ": Timeout waiting for response from SNFServer");
- } elsif ( $@ =~ /alarm.*unimplemented/ ) {
- while (<$rs>) {
-
- $buf .= $_;
- }
- }
- return $buf;
- }
-
-
- sub err_hash
- {
- my ( $self, $message ) = @_;
-
- return {
- success => undef,
- code => undef,
- message => $message
- };
- }
-
- sub cleanup_die
- {
- my ( $self, $file, $message ) = @_;
-
- unlink($file);
- die($message);
- }
- 1;
|