#! /usr/bin/perl

use strict;
use warnings;
use IPC::Open3;
use IO::Select;
use Fcntl qw(F_GETFL F_SETFL O_NONBLOCK);
use LWP::Simple;
use DB_File;

my $sigtime = 1124056800;
my $gpg = "/usr/bin/gpg --batch --no-tty";
my $homedir = "/tmp/check-$$";
my $keyserver = "wwwkeys.de.pgp.net";
my $pkscachefile = "/tmp/pkscache";

mkdir($homedir) || die $!;
END {
  if($homedir && -d $homedir) {
    ####### Clean up
    system("rm -rf $homedir");
  }
}

%ENV=("HOME" => $homedir);

$SIG{PIPE} = 'IGNORE';
sub forkexec($@) {
  my $input = shift;
  my $program = shift;
  my ($stdout, $stderr) = ("","");

  my ($in, $out, $err) = (undef, undef, 1); # open3 requires $out != $err
  my $pid = open3($in, $out, $err, $program);
  fcntl($$_, F_SETFL, fcntl($$_, F_GETFL, 0) | O_NONBLOCK)
    foreach ((\$in, \$out, \$err));
    
  my ($sr, $sw) = (IO::Select->new(), IO::Select->new());
  $sw->add($in) if $input;
  $sr->add($out);
  $sr->add($err);
  
  my $running = 1;
  my $len;
  $SIG{CHLD} = sub {$running = 0};
  while($sr->count() > 0 && $running) {
    my ($reads,$writes,$ex) = IO::Select::select($sr, $sw, undef, undef);
    if(grep($in,@$writes)) {
      $len = syswrite($in,$input);
      substr($input, 0, $len) = "" if $len > 0;
      unless($input) {
        $sw->remove($in);
        close($in);
      }
    }
    if(grep $out, @$reads) {
      $len = sysread($out,$stdout,1024,length $stdout);
      $sr->remove($out) unless defined $len && $len > 0;
    }
    if(grep $err, @$reads) {
      $len = sysread($err,$stderr,1024,length $stderr);
      $sr->remove($err) unless defined $len && $len > 0;
    }
    foreach(@$ex) {
      $sr->remove($_);
      $sw->remove($_);
    }
  }
  waitpid($pid,0);
  
  do {
    $len = sysread($out,$stdout,1024,length $stdout);
  } while(defined $len && $len > 0);
  do {
    $len = sysread($err,$stderr,1024,length $stderr);
  } while(defined $len && $len > 0);

  return ($stdout, $stderr);
}


####### Read input

my $input = "";
while (<>) {
  $input .= $_ if /-----BEGIN PGP MESSAGE-----/../-----END PGP MESSAGE-----/;
}

die "No PGP MESSAGE in input data\n" unless $input;

####### Check structure
my ($signed,$error) = forkexec($input, "$gpg");
die "No PGP signature found\n" unless $error =~
  /gpg: Signature made .* using \S+ key ID (\S+)/;

my $keyid = $1;
my ($pks,$kennung,$wahlkreis);
if($signed =~ /^Internetwahl 2005\r?\nPGP-KeyID: (\S+)\r?\nWahlkreis: (0\d+)[\r\n\s]*$/m) {
  my ($claimedid) = ($1);
  $wahlkreis = $2;
  die "PGP-KeyID does not match signature\n" unless $claimedid =~ /$keyid$/i;
  $kennung = "PGP:$keyid";

  ####### Get key
  tie(my %pkscache, 'DB_File', $pkscachefile);
  if(defined $pkscache{"k$keyid"} && $pkscache{"t$keyid"} > time() - 24*3600) {
    $pks = $pkscache{"k$keyid"};
  } else {
    $pks = get("http://$keyserver:11371/pks/lookup?op=get&search=0x$keyid") ||
      die "Keyserver $keyserver not reachable\n";
    $pkscache{"k$keyid"} = $pks;
    $pkscache{"t$keyid"} = time();
  };
  die "PGP key not found on $keyserver\n" unless $pks =~ /-----BEGIN PGP PUBLIC KEY BLOCK-----/;
  untie %pkscache;

  my $content;
  ($content,$error) = forkexec($pks, "$gpg --list-packets");

  my $sigmin = time();
  foreach(split(/\r?\n/,$content)) {
    next unless /created (\d+), .+, sigclass 1[0-3]/;
    $sigmin = $sigmin > $1 ? $1 : $sigmin;
  }
  die "PGP key too new for this voting\n" unless $sigmin < $sigtime;
} elsif($signed =~ /^Internetwahl 2005\r?\n.*(Reisepass|Personalausweis)Nummer: \S+\r?\nWahlkreis: ([1-9]\d*)\r?\nWahlbezirk: ([1-9]\d*)\r?\nListenplatz: ([1-9]\d*)[\r\n\s]*$/m) {
  $wahlkreis = $2;
  $kennung = "Listenplatz:$4\@$3\@$2";
  open(PKS,"</var/home/www/wahltag/helfer.asc") || die "Helfer Schlüssel nicht gefunden: $!\n";
  while(<PKS>) {
    $pks .= $_;
  }
  close(PKS);
} else {
  die "Content of PGP MESSAGE invalid\n" unless $signed =~ /Internetwahl 2005/m;
  die "Content of PGP MESSAGE unreadable\n";
}

####### Add keys
($_, $error) = forkexec($pks, "$gpg --import");

####### Check signature
($signed, $error) = forkexec($input, "$gpg");
die "Bad PGP signature\n"
  unless $error =~ /gpg: Good signature from/;

print "[Internetwahl 2005,$kennung,$wahlkreis]";

