#! /usr/bin/perl -w
$ID = q$Id$;
#
# run-tests -- Test signcontrol and pgpverify.
#
# Written by Russ Allbery <eagle@eyrie.org>
# This work is hereby placed in the public domain by its author.

use strict;
use vars qw($ID);

##############################################################################
# Test suite preparation
##############################################################################

# Find a program on the user's path.
sub find_program {
    my ($program) = @_;
    my @path = split (/:/, $ENV{PATH});
    for (@path) {
        return "$_/$program" if -x "$_/$program";
    }
    return $program;
}

# Given the path to the PGP program to use, generate a fixed version of
# pgpverify in the current directory.  Takes care of a few other things, like
# making sure that there's a temporary directory for it to put its files.
# Expects to find normal pgpverify up one directory.
sub fix_pgpverify {
    my ($path) = @_;
    my $gpg = ($path =~ /gpgv$/);
    mkdir ('tmp', 0755);
    open (BASIC, "../pgpverify") or die "Can't open ../pgpverify: $!\n";
    open (FIXED, "> pgpverify") or die "Can't create pgpverify: $!\n";
    while (<BASIC>) {
        s%^\# \$gpgv = \'.*%\$gpgv = '$path';% if $gpg;
        s%^\$pgp = \'.*%\$pgp = '$path';% unless $gpg;
        s%^\# \$keyring = \'.*%\$keyring = './keyring';%;
        s%^\$tmpdir = \".*%\$tmpdir = './tmp';%;
        s%^\$syslog_method = \'.*%\$syslog_method = '';%;
        print FIXED;
    }
    close BASIC;
    close FIXED;
    chmod (0755, 'pgpverify');
}

# Given the path to the PGP program to use, generate a fixed version of
# signcontrol in the current directory.  It also sets up signcontrol to sign
# example.* control messages with the testing key.  Expects to find the normal
# signcontrol up one level.
sub fix_signcontrol {
    my ($path) = @_;
    open (BASIC, "../signcontrol") or die "Can't open ../signcontrol: $!\n";
    open (FIXED, "> signcontrol") or die "Can't create signcontrol: $!\n";
    while (<BASIC>) {
        s/INSERT_YOUR_PGP_USERID/testing/;
        s%^\$pgppassfile = \'.*%\$pgppassfile = 'keyring/passphrase';%;
        s/^\$pgp = \".*/\$pgp = '$path';/;
        s%^\$pgplock = .*%\$pgplock = 'keyring/passphrase';%;
        s/YOUR_ADDRESS_AND_NAME/Test Signer <test\@example.com>/;
        s/ADDRESS_FOR_Approved_HEADER/test\@example.com/;
        s/FULL_HOST_NAME/example.com/;
        s/HIERARCHIES/example/;
        print FIXED;
    }
    close BASIC;
    close FIXED;
    chmod (0755, 'signcontrol');
}

##############################################################################
# Individual tests
##############################################################################

# Run pgpverify on a given file, expecting the given signer.  Warn if
# something goes wrong and return true on success and false on failure.
sub pgpverify {
    my ($file, $expected) = @_;
    my $signer = `./pgpverify < $file`;
    chomp $signer;
    if ($? == 0 && $signer eq $expected) {
        return 1;
    } else {
        print "pgpverify exited with status ", ($? >> 8), "\n" if $? != 0;
        print "pgpverify said the signer was $signer\n" if $signer;
        return 0;
    }
}

# Run pgpverify on a given file, expecting failure with the provided status
# code.  Warn if we succeed and return true on success of the test and false
# on failure.
sub pgpverify_fail {
    my ($file, $status) = @_;
    my $signer = `./pgpverify < $file 2> /dev/null`;
    chomp $signer;
    if (($? >> 8) == $status && !$signer) {
        return 1;
    } else {
        print "pgpverify exited with status ", ($? >> 8), "\n";
        print "pgpverify said the signer was $signer\n" if $signer;
        return 0;
    }
}

##############################################################################
# Test suite
##############################################################################

# Running totals.
my $tests = 0;
my $failed = 0;

# Tell signcontrol where to find the PGP keyrings.
$ENV{PGPPATH} = './keyring';
$ENV{GNUPGHOME} = './keyring';

# Set up pgpverify to use PGP first.
my $pgp = find_program ('pgp');
fix_pgpverify ($pgp);

# Check the signature on sample.control.
if (pgpverify ('../sample.control', 'news.announce.newgroups')) {
    print "PASS: pgpverify-pgp\n";
} else {
    print "FAIL: pgpverify-pgp\n";
    $failed++;
}
$tests++;

# Now, try with GnuPG.
my $gpgv = find_program ('gpgv');
fix_pgpverify ($gpgv);

# Check the signature on sample.control.
if (pgpverify ('../sample.control', 'news.announce.newgroups')) {
    print "PASS: pgpverify-gpg\n";
} else {
    print "FAIL: pgpverify-gpg\n";
    $failed++;
}
$tests++;

# Convert sample.control to wire format and then check its signature.
open (SAMPLE, '../sample.control')
    or die "Can't open ../sample.control: $!\n";
open (WIRE, '> signed') or die "Can't create signed: $!\n";
while (<SAMPLE>) {
    s/\n\z/\r\n/;
    s/^\./../;
    print WIRE;
}
print WIRE ".\r\n";
close SAMPLE;
close WIRE;
if (pgpverify ('signed', 'news.announce.newgroups')) {
    print "PASS: pgpverify-wire\n";
} else {
    print "FAIL: pgpverify-wire\n";
    $failed++;
}
$tests++;

# Sign a message with signcontrol.
fix_signcontrol ($pgp);
my $status = system ('./signcontrol < ./messages/newgroup > signed');
if ($? == 0) {
    print "PASS: signcontrol-pgp\n";
} else {
    print "signcontrol exited with status ", ($? >> 8), "\n";
    print "FAIL: signcontrol-pgp\n";
    $failed++;
}
$tests++;

# We still have a GnuPG pgpverify, so check cross-compatibility.
if (pgpverify ('./signed', 'testing')) {
    print "PASS: signcontrol-pgp-gpg\n";
} else {
    print "FAIL: signcontrol-pgp-gpg\n";
    $failed++;
}
$tests++;

# Switch to a PGP pgpverify and check again.
fix_pgpverify ($pgp);
if (pgpverify ('./signed', 'testing')) {
    print "PASS: signcontrol-pgp-pgp\n";
} else {
    print "FAIL: signcontrol-pgp-pgp\n";
    $failed++;
}
$tests++;

# Check with an old copy of pgpverify to make sure that the generated
# signatures still verify properly with a pgpverify that uses attached
# signatures.
my $signer = `./pgpverify-old $pgp < ./signed`;
chomp $signer;
if ($? == 0 && $signer eq 'testing') {
    print "PASS: signcontrol-pgp-old\n";
} else {
    print "pgpverify exited with status ", ($? >> 8), "\n" if $? != 0;
    print "pgpverify said the signer was $signer\n" if $signer;
    print "FAIL: signcontrol-pgp-old\n";
    $failed++;
}
$tests++;

# Switch to a GnuPG signcontrol and try again.
my $gpg = find_program ('gpg');
fix_signcontrol ($gpg);
$status = system ('./signcontrol < ./messages/newgroup > signed');
if ($? == 0) {
    print "PASS: signcontrol-gpg\n";
} else {
    print "signcontrol exited with status ", ($? >> 8), "\n";
    print "FAIL: signcontrol-gpg\n";
    $failed++;
}
$tests++;

# This will only verify with a GnuPG pgpverify.
fix_pgpverify ($gpgv);
if (pgpverify ('./signed', 'testing')) {
    print "PASS: signcontrol-gpg-gpg\n";
} else {
    print "FAIL: signcontrol-gpg-gpg\n";
    $failed++;
}
$tests++;

# Generate signed messages with News::Article.
system ('./sign-newsart', './messages/newgroup');

# Verify both with the GnuPG pgpverify.
if (pgpverify ('./signed.pgp', 'testing')) {
    print "PASS: news-article-pgp-gpg\n";
} else {
    print "FAIL: news-article-pgp-gpg\n";
    $failed++;
}
$tests++;
if (pgpverify ('./signed.gpg', 'testing')) {
    print "PASS: news-article-gpg-gpg\n";
} else {
    print "FAIL: news-article-gpg-gpg\n";
    $failed++;
}
$tests++;

# Switch to the PGP pgpverify and verify just the PGP one.
fix_pgpverify ($pgp);
if (pgpverify ('./signed.pgp', 'testing')) {
    print "PASS: news-article-pgp-pgp\n";
} else {
    print "FAIL: news-article-pgp-pgp\n";
    $failed++;
}
$tests++;

# Check the return status for a truncated signature.
if (pgpverify_fail ('./messages/bad-syntax', 255)) {
    print "PASS: pgpverify-pgp-syntax\n";
} else {
    print "FAIL: pgpverify-pgp-syntax\n";
    $failed++;
}
$tests++;

# Check the return status for a bad signature.
if (pgpverify_fail ('./messages/bad-corrupt', 3)) {
    print "PASS: pgpverify-pgp-bad\n";
} else {
    print "FAIL: pgpverify-pgp-bad\n";
    $failed++;
}
$tests++;

# Switch to GnuPG and check the return status for a truncated signature.
fix_pgpverify ($gpgv);
if (pgpverify_fail ('./messages/bad-syntax', 255)) {
    print "PASS: pgpverify-gpg-syntax\n";
} else {
    print "FAIL: pgpverify-gpg-syntax\n";
    $failed++;
}
$tests++;

# Check the return status for a bad signature.
if (pgpverify_fail ('./messages/bad-corrupt', 3)) {
    print "PASS: pgpverify-gpg-bad\n";
} else {
    print "FAIL: pgpverify-gpg-bad\n";
    $failed++;
}
$tests++;

# Check the return status for an unknown signer.
if (pgpverify_fail ('./messages/gnu', 2)) {
    print "PASS: pgpverify-gpg-unknown\n";
} else {
    print "FAIL: pgpverify-gpg-unknown\n";
    $failed++;
}
$tests++;

# Print out a summary of the tests.
unlink ('pgpverify', 'signcontrol', 'signed', 'signed.pgp', 'signed.gpg')
    unless $failed > 0;
if ($failed == 0) {
    print "All $tests tests passed\n";
} else {
    print "Failed $failed tests out of $tests total\n";
    exit 1;
}
