#! /usr/bin/perl -w
require '/usr/local/news/lib/innshellvars.pl';

# written April 1996, tale@isc.org (David C Lawrence)
# mostly rewritten 2001-03-21 by Marco d'Itri <md@linux.it>
#
# requirements:
# - GnuPG
# - perl 5.004_03 and working Sys::Syslog
# - syslog daemon
#
# There is no locking because gpg is supposed to not need it and controlchan
# will serialize control messages processing anyway.

require 5.004_03;
use strict;

# if you keep your keyring somewhere that is not the default used by gpg,
# set appropriately the next line.
my $keyring = ($inn::newsetc ? $inn::newsetc . '/pgp/' : '') . 'pubring.gpg';

# If you have INN and the script is able to successfully include your
# innshellvars.pl file, the value of the next two variables will be
# overridden.
my $tmpdir = '/var/log/news/';
my $syslog_facility = 'news';

# 1: print PGP output
my $debug = 0;
#$debug = 1 if -t 1;

### Exit value:
### 0  good signature
### 1  no signature
### 2  unknown signature
### 3  bad signature
### 255 problem not directly related to gpg analysis of signature

##############################################################################
################ NO USER SERVICEABLE PARTS BELOW THIS COMMENT ################
##############################################################################
my $tmp = ($inn::pathtmp ? $inn::pathtmp : $tmpdir) . "/pgp$$";
$syslog_facility = $inn::syslog_facility if $inn::syslog_facility;

my $nntp_format = 0;
$0 =~ s#^.*/##;                # trim /path/to/prog to prog

die "Usage: $0 < message\n" if $#ARGV != -1;

# Path to gpg binary
my $gpg;
if ($inn::gpgv) { 
    $gpg = $inn::gpgv;
} else {
    foreach (split(/:/, $ENV{PATH}), qw(/usr/local/bin /opt/gnu/bin)) {
	if (-x "$_/gpgv") {
            $gpg = "$_/gpgv"; last;
	}
    }
}
fail('cannot find the gpgv binary') if not $gpg;

# this is, by design, case-sensitive with regards to the headers it checks.
# it's also insistent about the colon-space rule.
my ($label, $value, %dup, %header);
while (<STDIN>) {
    # if a header line ends with \r\n, this article is in the encoding
    # it would be in during an NNTP session. some article storage
    # managers keep them this way for efficiency.
    $nntp_format = /\r\n$/ if $. == 1;
    s/\r?\n$//;

    last if /^$/;
    if (/^(\S+):[ \t](.+)/) {
        ($label, $value) = ($1, $2);
        $dup{$label} = 1 if $header{$label};
        $header{$label} = $value;
    } elsif (/^\s/) {
        fail("non-header at line $.: $_") unless $label;
        $header{$label} .= "\n$_";
    } else {
        fail("non-header at line $.: $_");
    }
}

my $pgpheader = 'X-PGP-Sig';
$_ = $header{$pgpheader};
exit 1 if not $_; # no signature

# the $sep value means the separator between the radix64 signature lines
# can have any amount of spaces or tabs, but must have at least one space
# or tab, if there is a newline then the space or tab has to follow the
# newline. any number of newlines can appear as long as each is followed
# by at least one space or tab. *phew*
my $sep = "[ \t]*(\n?[ \t]+)+";
# match all of the characters in a radix64 string
my $r64 = '[a-zA-Z0-9+/]';
fail("$pgpheader not in expected format")
    unless /^(\S+)$sep(\S+)(($sep$r64{64})+$sep$r64+=?=?$sep=$r64{4})$/;

my ($version, $signed_headers, $signature) = ($1, $3, $4);
$signature =~ s/$sep/\n/g;

my $message = "-----BEGIN PGP SIGNED MESSAGE-----\n\n"
            . "X-Signed-Headers: $signed_headers\n";

foreach $label (split(',', $signed_headers)) {
    fail("duplicate signed $label header, can't verify") if $dup{$label};
    $message .= "$label: ";
    $message .= $header{$label} if $header{$label};
    $message .= "\n";
}
$message .= "\n";                # end of headers

while (<STDIN>) {                # read body lines
    if ($nntp_format) {
        # check for end of article; some news servers (eg, Highwind's
        # "Breeze") include the dot-CRLF of the NNTP protocol in the
        # article data passed to this script
        last if $_ eq ".\r\n";

        # remove NNTP encoding
        s/^\.\./\./;
        s/\r\n$/\n/;
    }

    s/^-/- -/;            # pgp quote ("ASCII armor") dashes
    $message .= $_;    
}

$message .=
    "\n-----BEGIN PGP SIGNATURE-----\n" .
    "Version: $version\n" .
    $signature .
    "\n-----END PGP SIGNATURE-----\n";

open(TMP, ">$tmp") or fail("open $tmp: $!");
print TMP $message;
close TMP or errmsg("close $tmp: $!");

my $opts = '--quiet --status-fd=1 --logger-fd=1';
$opts .= " --keyring=$keyring" if $keyring;

open(PGP, "$gpg $opts $tmp |") or fail("failed to execute $gpg: $!");

undef $/;
$_ = <PGP>;

unlink $tmp or errmsg("unlink $tmp: $!");

if (not close PGP) {
    if ($? >> 8) {
        my $status = $? >> 8;
        errmsg("gpg exited status $status") if $status > 1;
    } else {
        errmsg('gpg died on signal ' . ($? & 255));
    }
}

print STDERR $_ if $debug;

my $ok = 255;        # default exit status
my $signer;
if (/^\[GNUPG:\]\s+GOODSIG\s+\S+\s+(\S+)/m) {
    $ok = 0;
    $signer = $1;
} elsif (/^\[GNUPG:\]\s+NODATA/m or /^\[GNUPG:\]\s+UNEXPECTED/m) {
    $ok = 1;
} elsif (/^\[GNUPG:\]\s+NO_PUBKEY/m) {
    $ok = 2;
} elsif (/^\[GNUPG:\]\s+BADSIG\s+/m) {
    $ok = 3;
}

print "$signer\n" if $signer;
exit $ok;

sub errmsg {
    my $msg = $_[0];

    eval 'use Sys::Syslog qw(:DEFAULT setlogsock)';
    die "$0: cannot use Sys::Syslog: $@ [$msg]\n" if $@;

    die "$0: cannot set syslog method [$msg]\n"
        if not (setlogsock('unix') or setlogsock('inet'));

    $msg .= " processing $header{'Message-ID'}" if $header{'Message-ID'};

    openlog($0, 'pid', $syslog_facility);
    syslog('err', '%s', $msg);
    closelog();
}

sub fail {
    errmsg($_[0]);
    unlink $tmp;
    exit 255;
}

__END__

# Copyright 2000 by Marco d'Itri

# License of the original version distributed by David C. Lawrence:

# Copyright (c) 1996 UUNET Technologies, Inc.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
# 1. Redistributions of source code must retain the above copyright
#    notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
#    notice, this list of conditions and the following disclaimer in the
#    documentation and/or other materials provided with the distribution.
# 3. All advertising materials mentioning features or use of this software
#    must display the following acknowledgement:
#      This product includes software developed by UUNET Technologies, Inc.
# 4. The name of UUNET Technologies ("UUNET") may not be used to endorse or
#    promote products derived from this software without specific prior
#    written permission.
#
# THIS SOFTWARE IS PROVIDED BY UUNET ``AS IS'' AND ANY EXPRESS OR
# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED.  IN NO EVENT SHALL UUNET BE LIABLE FOR ANY DIRECT,
# INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
# OF THE POSSIBILITY OF SUCH DAMAGE.
