#!/usr/bin/perl
#
# $Id: PhishBLPlugin.pm 4094 2006-11-02 12:25:37Z eriker $

use strict;
use warnings;

package FS::PhishBLPlugin;

=head1 NAME

FS::PhishBLPlugin - F-Secure SpamAssassin 3.x anti-phishing plugin 

=head1 SYNOPSYS

  # Add to rules/init.pre or rules/v3xx.pre

  loadplugin FS::PhishBLPlugin

  # Add to any rules/*.cf file

  phish_enabled        0
  phish_salt           <salt-code>
  phish_lookup_url     http://<f-secure-lookup-url-example>/phish-<SLAKjlaskd>/


=head1 DESCRIPTION

This module interfaces spamassassin 3.x (tested with 3.1) to
the F-Secure phishing URL blocklist.

=cut

use Mail::SpamAssassin::Plugin;
use Mail::SpamAssassin::Util;
use Mail::SpamAssassin::Logger qw//;
use FS::Logger;
BEGIN {
    FS::Logger::set_logger(\&Mail::SpamAssassin::Logger::_log);
}
our $FS_Logger_facility = "pheed";

use File::Spec;

use bytes;
use FS::PhishBL;

use vars qw(@ISA);
@ISA = qw(Mail::SpamAssassin::Plugin);

# number of seconds to back off lookups after an error
my $LOOKUP_TEST_RETRY_INTERVAL = 5*60;

my $prev_enabled = 0;         # just for logging
my $prev_dns_available = "";  # just for logging

# constructor
sub new {
    my $class = shift;
    my $samain = shift;

    debug(">>PhishBLPlugin::new()");
    # some boilerplate...
    $class = ref($class) || $class;
    my $self = $class->SUPER::new($samain);
    bless ($self, $class);

    return $self;
}

sub check_post_learn {
    goto &check_phishbl;
}

sub check_phishbl {
    my ($self, $options) = @_;
    my $msg = $options->{permsgstatus};

    # set initial classification result
    $msg->{fs_phishblplugin} = {};

    my $conf = $msg->{main}{conf}{phish};
    # no: unconditionally disabled
    return 0 if $conf->{enabled} == 0;
    # when_dns: disabled if dns is not available
    return 0 if	$conf->{enabled} == 1 && !$msg->is_dns_available();

    $self->{src} ||= FS::PhishBL->new($conf);

    if (time() < $conf->{lookup_test_deadline}) {
        debug "skipping lookup query temporarily because of earlier failure";
        return 0;
    }

    my %duplicate;
    my %classification = ();
    
    eval {
	foreach my $uri ($msg->get_uri_list()) {
	    next if $duplicate{$uri}++;
	    my $classes = $self->{src}->is_phish($uri);
		while (my ($k, $v) = each %$classes) {
		    # store only if this is higer score than
		    # existing one for this class
		    my $current = $classification{$k} || 0; 
		    $classification{$k} = $v if
			!exists $classification{$k} ||
			$current < $v;
	    }
	}
    };
    if ($@) {
        info "lookup failed: " . $@->status_line;
        info "disabling lookups for $LOOKUP_TEST_RETRY_INTERVAL seconds";
        $conf->{lookup_test_deadline} = time() + $LOOKUP_TEST_RETRY_INTERVAL;
    }

    # set classification result
    $msg->{fs_phishblplugin} = \%classification;
    return %classification ? 1 : 0;
}

sub parse_config {
    my ($self, $opts) = @_;
    my $key = $opts->{key};
    my $value = $opts->{value};

    # map configuration file keys -> keys of our config hash ref
    my %phish_config = (
	phish_enabled         => "enabled",
	phish_salt            => "salt",
	phish_lookup_url      => "query_url",
    );

    if (exists $phish_config{$key}) {
	debug "$key = $value";
	$key = $phish_config{$key};
	if ($key eq "enabled") {
	    # normalize value to 0, 1 or 2
	    $value = $value eq "yes" ? 2 : $value eq "when_dns" ? 1 : 0;
	}
	$opts->{conf}{phish}{$key} = $value;
	$self->inhibit_further_callbacks();
	return 1;
    }
    my %common_config = (
	http_proxy  => "proxy",
    );
    if (exists $common_config{$key}) {
	debug "$key = $value";
	$opts->{conf}{phish}{$common_config{$key}} = $value;
	return 1;
    }

    return 0;
}

sub finish_parsing_end {
    my ($self) = @_;

    my $conf = $self->{main}{conf}{phish};
    if ($prev_enabled != $conf->{enabled}) {
	$prev_enabled = $conf->{enabled};
	info "Enabled: " . ("no", "when_dns", "yes")[$prev_enabled];
    }
    if ($prev_dns_available ne $self->{main}{conf}{dns_available}) {
	$prev_dns_available = $self->{main}{conf}{dns_available};
	info("dns_available $self->{main}{conf}{dns_available}");
    }

    $conf->{lookup_test_deadline} = 0;

    if ($self->{src}) {
	debug "Refreshing settings";
	$self->{src}->refresh_settings($conf);
    } else {
	debug "Not yet refreshing settings";
    }
}

=head1 COPYRIGHT

Copyright (C) 2006 F-Secure Corp, all rights reserved

=cut

1;
