Mail-SpamAssassin-Plugin-OpenPGP

 view release on metacpan or  search on metacpan

MANIFEST  view on Meta::CPAN

t/acceptance-base.pl
t/acceptance-encrypted.t
t/acceptance-good.t
t/acceptance-normal.t
t/acceptance-unknown.t
t/boilerplate.t
t/config
t/config.dist
t/data/email2.eml
t/data/expired.eml
t/data/gpg_encrypted.eml
t/data/gpg_evolution.eml
t/data/gpg_part_signed.eml
t/data/gpg_signed_8bit.eml
t/data/gpg_signed_attachment2.eml
t/data/gpg_signed_binary_attachment.eml
t/data/gpg_signed_nokeyfound.eml
t/data/gpg_subkey.eml
t/data/gpg_thunderbird.eml
t/data/gpg_wrongsender.eml
t/data/mismatched_time.eml
t/data/pks_signed.eml
t/data/plain.eml
t/data/signed_inline.eml
t/data/signed_inline_firstpart.eml
t/data/signed_inline_secondpart.eml
t/gpg-home/pubring.gpg
t/pod.t
t/SATest.pm
t/test_dir
SIGNATURE    Added here by Module::Build

MANIFEST.SKIP  view on Meta::CPAN

^t/log
^Mail-SpamAssassin-Plugin-OpenPGP-
^_build
^Build$
^blib
~$
\.bak$
CVS
\.svn
^t/data/01_test_rules.cf$
t/gpg-home/secring.gpg
t/gpg-home/trustdb.gpg

SIGNATURE  view on Meta::CPAN

SHA1 9ba5d3d2e11e11f075e6cffa6c652e3f16ddf740 t/acceptance-base.pl
SHA1 dfe2eaed25f28be8ca2efba7628b782499932943 t/acceptance-encrypted.t
SHA1 9ce883ae9a639045b004c7ed74f825fa01b1c89d t/acceptance-good.t
SHA1 ec7620732ee33756f8f41acb296edebcf6326a8e t/acceptance-normal.t
SHA1 acf21dd4a8fdc491004882fb59f8d523959c1106 t/acceptance-unknown.t
SHA1 2c317c92eb043906db6789f9f01744cbc0e6c53e t/boilerplate.t
SHA1 4529db8acd258e0c931248890af27035d86ae97b t/config
SHA1 2a6200b4970e6761833432f119ab35ff04a6de42 t/config.dist
SHA1 fcce8356d8eb3da3749892fe73fb3e14caeead14 t/data/email2.eml
SHA1 5fb1d47c4c8c66f2c983d5778773c09df4870997 t/data/expired.eml
SHA1 212ad1c42fc61db4701d34d56842ce855936f2f0 t/data/gpg_encrypted.eml
SHA1 70eccc6edad85a2c9233f410d300b8f71bd94c7c t/data/gpg_evolution.eml
SHA1 ed3a240465228954c97a985ec4a85f9e8848a3ab t/data/gpg_part_signed.eml
SHA1 22536d4b9f56cae4226c67c4c8baa100e5c82a6d t/data/gpg_signed_8bit.eml
SHA1 2e3c7144ef70cb6d47aa1a17f0b6271337e20c48 t/data/gpg_signed_attachment2.eml
SHA1 b52c4b62fbd54e5969e15aad460cc8986a946241 t/data/gpg_signed_binary_attachment.eml
SHA1 df21213fb24c86713da63cce037a62b2b9471b23 t/data/gpg_signed_nokeyfound.eml
SHA1 2b17a4efb5674fa24c3895c7fd0b8d7d817b2a3b t/data/gpg_subkey.eml
SHA1 cea7ebdbf911f34e74a08232db9cd6affb87a95f t/data/gpg_thunderbird.eml
SHA1 77bc1554a529a418f5c92418d8ee140268fe6494 t/data/gpg_wrongsender.eml
SHA1 04a10fbffac76d49e7eee588dce9502dde66a4e1 t/data/mismatched_time.eml
SHA1 f3dbbb7747246c423be9357b2149cd7a70a9ef51 t/data/pks_signed.eml
SHA1 09a743a289b64bbfea5d49419d448541e13806fc t/data/plain.eml
SHA1 6bc4af0024b7e30d032fd71a882e94b9c28e40c0 t/data/signed_inline.eml
SHA1 67158ca0c1b0c8a216085a5e8afc5b50ada7ec1a t/data/signed_inline_firstpart.eml
SHA1 dfbc7dd4ac5d5ffdf8cfc3569e6ea0ae26fa8272 t/data/signed_inline_secondpart.eml
SHA1 ee26e956e67616989a3f2810f9719659c68b1d66 t/gpg-home/pubring.gpg
SHA1 0190346d7072d458c8a10a45c19f86db641dcc48 t/pod.t
SHA1 da39a3ee5e6b4b0d3255bfef95601890afd80709 t/test_dir
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.7 (GNU/Linux)

iQIVAwUBR9NdY2c36lVi77tzAQopHQ//X9Ge9/waoce793Bl7cFUqfNJc/8BOe9h
6QGwhK3lBTGa8gs29qM16Q29Iryy7VV6mQiSLvmjiaW7VBSONls0c+DVB6saS5jj
fdTMLeKJD9QUeHBD5n1x9Q1gDNUecx+QAWWvaslrAiu2hbiIpdrf6bWIms+UKvtd
rBr90BZzKo3CrySwMJSBpRLFv7GriANd59WwE4QXLd3kHfsejNi+M6+iXisFdUhs
7b3IWeASSqMkZrTcLEHUVCGlo4h98X0UAjIm7OiKlLYyLaATdNJOwI0pNGh5UUGx

lib/Mail/SpamAssassin/Plugin/OpenPGP.pm  view on Meta::CPAN

 score OPENPGP_SIGNED -1
 # this would total to -2
 score OPENPGP_SIGNED_GOOD -1
 # this would total to 0
 score OPENPGP_SIGNED_BAD 1

=head1 DESCRIPTION

This uses Mail::GPG which uses GnuPG::Interface which uses Gnu Privacy Guard via IPC.

Make sure the homedir you use for gnupg has a gpg.conf with something like the following in it, so that it will automatically fetch public keys.  And make sure that the directory & files are only readable by owner (a gpg security requirement).

 keyserver-options auto-key-retrieve timeout=5
 # any keyserver will do
 keyserver  x-hkp://random.sks.keyserver.penguin.de

If a public key cannot be retrieved, the email will be marked as SIGNED but neither GOOD nor BAD.  To ensure that your local public keys don't get out of date, you should probably set up a scheduled job to delete pubring.gpg regularly

For project information, see L<http://konfidi.org>

=head1 USER SETTINGS

 gpg_executable /path/to/gpg
 gpg_homedir /var/foo/gpg-homedir-for-spamassassin
 openpgp_add_header_fingerprint 1 # default 1 (true)
 openpgp_add_header_failure_info 0 # default 1 (true)

The OpenPGP headers are never added to emails without a signature.

=cut

=head1 TAGS

The following per-message SpamAssassin "tags" are set.

lib/Mail/SpamAssassin/Plugin/OpenPGP.pm  view on Meta::CPAN

}

# SA 3.1 style of parsing config options
sub set_config {
  my($self, $conf) = @_;
  my @cmds = ();

  # see Mail::SpamAssassin::Conf::Parser for expected format of the "config blocks" stored in @cmds

  push(@cmds, {
    setting => 'gpg_homedir', 
    # FIXME: default => 1, 
    type => $Mail::SpamAssassin::Conf::CONF_TYPE_STRING,
  });
  push(@cmds, {
    setting => 'gpg_executable', 
    type => $Mail::SpamAssassin::Conf::CONF_TYPE_STRING,
  });
  push(@cmds, {
    setting => 'openpgp_add_header_fingerprint', 
    default => 1, 
    type => $Mail::SpamAssassin::Conf::CONF_TYPE_BOOLEAN,
  });
  push(@cmds, {
    setting => 'openpgp_add_header_failure_info', 
    default => 1, 

lib/Mail/SpamAssassin/Plugin/OpenPGP.pm  view on Meta::CPAN

    # strip out the "quoted text"
    $result =~ s/(?<!<)"[^"]*"(?!@)//g;
    # Foo Blah <jm@xxx> or <jm@xxx>
    $result =~ s/^[^<]*?<(.*?)>.*$/$1/;
    # multiple addresses on one line? remove all but first
    $result =~ s/,.*$//;
    return $result;
}

# TODO contribute back to Mail::GPG::Result
sub _gpg_result_date {
    my $result = shift;
    my $gpg_status = $result->get_gpg_status;
    ## dbg "openpgp: status: " . $$gpg_status;
    # based on Mail::GPG::Result's analyze_result
    pos($$gpg_status) = undef; # reset /g modifier since this module uses the following regex multiple times
    while ( $$gpg_status && $$gpg_status =~ m{^\[GNUPG:\]\s+(.*)$}mg ) {
        my $line = $1;
        ## dbg "openpgp: line: " . $line;
        # 3rd field after VALIDSIG
        if ( $line =~ /^VALIDSIG\s+([^\s]+)\s+([^\s]+)\s+([^\s]+)/ ) {
            #$sign_fingerprint = $1;
            return $3;
        }
    }
}

# TODO contribute back to Mail::GPG::Result
# it's get_sign_fingerprint does signing key, not primary key if signing key is a subkey
sub _gpg_result_primary_key_fingerprint {
    my $result = shift;
    my $gpg_status = $result->get_gpg_status;
    pos($$gpg_status) = undef; # reset /g modifier since this module uses the following regex multiple times
    # based on Mail::GPG::Result's analyze_result
    while ( $$gpg_status && $$gpg_status =~ m{^\[GNUPG:\]\s+(.*)$}mg ) {
        my $line = $1;
        # if signed with a subkey, subkey comes first and primary key comes later
        # [GNUPG:] VALIDSIG D1892B5C772E643EBB97397E6737EA5562EFBB73 2008-01-21 1200891462 0 3 0 1 10 01 EAB0FABEDEA81AD4086902FE56F0526F9BB3CE70
        # some gnupg versions may only output 3 fields after VALIDSIG
        # get last 40hex-digit sequence
        if ( $line =~ /^VALIDSIG.+([0-9A-F]{40})/ ) {
            return $1;
        }
    }
}

lib/Mail/SpamAssassin/Plugin/OpenPGP.pm  view on Meta::CPAN

sub _check_openpgp {
    my ($self, $scan) = @_;
    return if $scan->{openpgp_checked};
    
    $scan->{openpgp_checked} = 0;
    $scan->{openpgp_signed} = 0;
    $scan->{openpgp_signed_good} = 0;
    $scan->{openpgp_signed_bad} = 0;
    
    my %opts;
    if (defined $scan->{conf}->{gpg_executable}) {
        $opts{gpg_call} = $scan->{conf}->{gpg_executable};
    }
    # see GnuPG::Interface's hash_init (correlates to gpg commandline arguments)
    $opts{gnupg_hash_init} = {
        homedir => $scan->{conf}->{gpg_homedir}
    };
    
    my $gpg = Mail::GPG->new(%opts);
    # TODO: use SA-parsed entity instead of having Mail::GPG reparse it into a MIME::Entity?
    my $entity = Mail::GPG->parse(mail_sref => \$scan->{msg}->get_pristine());
    # TODO: configurable option to use is_signed_quick
	if ($gpg->is_signed(entity => $entity)) {
        $scan->{openpgp_signed} = 1;
        dbg "openpgp: is signed";
    }
	if ($gpg->is_encrypted(entity => $entity)) {
        $scan->{openpgp_encrypted} = 1;
        dbg "openpgp: is encrypted";
    }
    
    if ($scan->{openpgp_signed}) {
        my $result = $gpg->verify(entity => $entity); 
        if (!$result->get_is_signed) {
            warn "openpgp: \$gpg->is_signed != \$result->get_is_signed";
            $scan->{openpgp_signed} = 1;
        } else {
            #dbg "openpgp: " . $result->as_string();
            if (${$result->get_gpg_stdout}) {
                dbg "openpgp: gpg stdout:" . ${$result->get_gpg_stdout};
            }
            if (${$result->get_gpg_stderr}) {
                dbg "openpgp: gpg stderr:" . ${$result->get_gpg_stderr};
            }
            if ($result->get_gpg_rc != 0) {
                my $err = "Error running gpg: " . ${$result->get_gpg_stdout} . ${$result->get_gpg_stderr};
                dbg "openpgp: $err";
                if ($scan->{conf}->{openpgp_add_header_fingerprint}) {
                    $scan->{conf}->{headers_spam}->{'OpenPGP-Failure'} = $err;
                    $scan->{conf}->{headers_ham}->{'OpenPGP-Failure'} = $err;
                }
            } else {
                $scan->{openpgp_fingerprint} = _gpg_result_primary_key_fingerprint($result);
                $scan->{openpgp_signed_good} = $result->get_sign_ok;
                $scan->{openpgp_signed_bad} = !$result->get_sign_ok;
                
                if ($scan->{conf}->{openpgp_add_header_fingerprint}) {
                    $scan->{conf}->{headers_spam}->{'OpenPGP-Fingerprint'} = $scan->{openpgp_fingerprint};
                    $scan->{conf}->{headers_ham}->{'OpenPGP-Fingerprint'} = $scan->{openpgp_fingerprint};
                }
            }
            
            if ($scan->{openpgp_signed_bad}) {
                my $err = "bad signature: " . ${$result->get_gpg_stderr};
                dbg "openpgp: $err";
                if ($scan->{conf}->{openpgp_add_header_fingerprint}) {
                    $scan->{conf}->{headers_spam}->{'OpenPGP-Failure'} = $err;
                    $scan->{conf}->{headers_ham}->{'OpenPGP-Failure'} = $err;
                }
            }
            
            # additional checks if good
            if ($scan->{openpgp_signed_good}) {
                # From address must match one in the public key

lib/Mail/SpamAssassin/Plugin/OpenPGP.pm  view on Meta::CPAN

                    }
                    $scan->{openpgp_signed_good} = 0;
                    $scan->{openpgp_signed_bad} = 1;
                } else {
                    dbg "openpgp: fingerprint: " . $scan->{openpgp_fingerprint};
                }
            }
            if ($scan->{openpgp_signed_good}) {
                # date of email must be close to that of the signature
                my $sent_date = Mail::SpamAssassin::Util::parse_rfc822_date($scan->get('Date'));
                my $signature_date = _gpg_result_date($result);

                
                # TODO configurable threshold
                my $threshold = 60*60;
                if (abs($sent_date - $signature_date) > $threshold) {
                    my $err = "mail sent date and signature data are more than $threshold seconds apart: $sent_date vs $signature_date";
                    dbg "openpgp: $err";
                    if ($scan->{conf}->{openpgp_add_header_fingerprint}) {
                        $scan->{conf}->{headers_spam}->{'OpenPGP-Failure'} = $err;
                        $scan->{conf}->{headers_ham}->{'OpenPGP-Failure'} = $err;

t/acceptance-bad.t  view on Meta::CPAN

    'OPENPGP_SIGNED' => 'signed',
    'OPENPGP_SIGNED_BAD' => 'signed_bad',
);
our %anti_patterns = (
    'OPENPGP_SIGNED_GOOD' => 'signed_good',
);

sarun("-t < data/expired.eml", \&patterns_run_cb);
ok_all_patterns(); # one test per pattern & anti-pattern

sarun("-t < data/gpg_wrongsender.eml", \&patterns_run_cb);
ok_all_patterns(); # one test per pattern & anti-pattern

sarun("-t < data/mismatched_time.eml", \&patterns_run_cb);
ok_all_patterns(); # one test per pattern & anti-pattern


t/acceptance-base.pl  view on Meta::CPAN


sub acceptance_setup() {
    # add lines to test-local rules
    tstlocalrules (q{
    score OPENPGP_SIGNED -1
    score OPENPGP_SIGNED_GOOD -1
    score OPENPGP_SIGNED_BAD 1
}   );

    # make sure it has the right permissions, otherwise gnupg won't use it (no permissions to group or world)
    chmod 0700, 'gpg-home';

    tstprefs (q{
    dns_available no
    
    #gpg_executable /usr/bin/gpg
    gpg_homedir gpg-home
}   );

};

acceptance_init();

t/acceptance-encrypted.t  view on Meta::CPAN

acceptance_setup();

our %patterns = (
    'OPENPGP_ENCRYPTED' => 'encrypted',
);
our %anti_patterns = (
    'OPENPGP_SIGNED' => 'signed',
    'OPENPGP_SIGNED_BAD' => 'signed_bad',
    'OPENPGP_SIGNED_GOOD' => 'signed_good',
);
sarun("-t < data/gpg_encrypted.eml", \&patterns_run_cb);
ok_all_patterns(); # one test per pattern & anti-pattern



t/acceptance-good.t  view on Meta::CPAN

acceptance_setup();

our %patterns = (
    'OPENPGP_SIGNED' => 'signed',
    'OPENPGP_SIGNED_GOOD' => 'signed_good',
);
our %anti_patterns = (
    'OPENPGP_SIGNED_BAD' => 'signed_bad',
);

sarun("-t < data/gpg_thunderbird.eml", \&patterns_run_cb);
ok_all_patterns(); # one test per pattern & anti-pattern

sarun("-t < data/gpg_evolution.eml", \&patterns_run_cb);
ok_all_patterns(); # one test per pattern & anti-pattern

$patterns{'EAB0FABEDEA81AD4086902FE56F0526F9BB3CE70'} = 'openpgp fingerprint';
sarun("-t < data/gpg_subkey.eml", \&patterns_run_cb);
ok_all_patterns(); # one test per pattern & anti-pattern
delete $patterns{'EAB0FABEDEA81AD4086902FE56F0526F9BB3CE70'};

sarun("-t < data/gpg_signed_attachment2.eml", \&patterns_run_cb);
ok_all_patterns(); # one test per pattern & anti-pattern

sarun("-t < data/gpg_signed_binary_attachment.eml", \&patterns_run_cb);
ok_all_patterns(); # one test per pattern & anti-pattern

sarun("-t < data/gpg_signed_8bit.eml", \&patterns_run_cb);
ok_all_patterns(); # one test per pattern & anti-pattern

sarun("-t < data/signed_inline.eml", \&patterns_run_cb);
ok_all_patterns(); # one test per pattern & anti-pattern

# email address is not the primary one on the key
sarun("-t < data/email2.eml", \&patterns_run_cb);
ok_all_patterns(); # one test per pattern & anti-pattern


t/acceptance-normal.t  view on Meta::CPAN

    'OPENPGP_SIGNED_BAD' => 'signed_bad',
    'OPENPGP_SIGNED_GOOD' => 'signed_good',
);
sarun("-t < data/pks_signed.eml", \&patterns_run_cb);
ok_all_patterns(); # one test per pattern & anti-pattern

sarun("-t < data/plain.eml", \&patterns_run_cb);
ok_all_patterns(); # one test per pattern & anti-pattern

# TODO make this OPENPGP_PART_SIGNED
sarun("-t < data/gpg_part_signed.eml", \&patterns_run_cb);
ok_all_patterns(); # one test per pattern & anti-pattern

t/acceptance-unknown.t  view on Meta::CPAN


our %patterns = (
    'OPENPGP_SIGNED' => 'signed',
);
our %anti_patterns = (
    'OPENPGP_SIGNED_BAD' => 'signed_bad',
    'OPENPGP_SIGNED_GOOD' => 'signed_good',
);

# this key has never been published
sarun("-t < data/gpg_signed_nokeyfound.eml", \&patterns_run_cb);
ok_all_patterns(); # one test per pattern & anti-pattern



( run in 0.851 second using v1.01-cache-2.11-cpan-df04353d9ac )