#!/usr/bin/perl -w

#    debsigs: Package signing/verification system
#    Copyright (C) 2000   Progeny Linux Systems, Inc. <jgoerzen@progeny.com>
#
#    This program is free software; you can redistribute it and/or modify
#    it under the terms of the GNU General Public License as published by
#    the Free Software Foundation; either version 2 of the License, or
#    (at your option) any later version.
#
#    This program is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU General Public License for more details.
#
#    You should have received a copy of the GNU General Public License
#    along with this program; if not, write to the Free Software
#    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

$CVSVERSION = '$Progeny: debsigs,v 1.14 2001/12/16 07:12:25 branden Exp $';
$VERSION = "$CVSVERSION+2002.09.19";

use strict;
use Debian::debsigs::arf;
use Debian::debsigs::forktools ':all';
use Debian::debsigs::gpg;
use Getopt::Long;
use IO::File;
use POSIX ":sys_wait_h";

Getopt::Long::Configure('no_ignore_case');

my ($delete, $sign, $key, $keyring, $gpgopts, $list, $verbose, $verify) = undef;

# Parse the command line; @ARGV will then contain filenames to operate on.

my($result) =  GetOptions("sign|s=s" => \$sign,
                   "default-key|k=s" => \$key,
                "secret-keyring|K=s" => \$keyring,
                       "gpgopts|g=s" => \$gpgopts,
                          "list|l|t" => \$list,
                          "delete=s" => \$delete,
                        "verbose|v+" => \$verbose,
                    "verify|check|c" => \$verify);

unless (($#ARGV >= 0) && $result) {
  syntax();
  exit(1);
}

# Process each file.

foreach my $file (@ARGV) {
  print " *** Processing file $file\n" if (defined($verbose) && $verbose >= 1);
  if ($sign) {
    cmd_sign($file);
  } elsif ($list) {
    cmd_list($file);
  } elsif ($delete) {
    cmd_delete($file);
  } elsif ($verify) {
    die "Verify not yet implemented.";
  }
}

sub cmd_sign {
  my $file = shift @_;
  if (length($sign) > 10) {
    die "Signature type must be 10 characters or less.";
  }

  unless (-r $file && -f $file) {
    die "File $file does not exist, is not a file, or is not readable.";
  }

  my $arobj = new Debian::debsigs::arf($file);
  my ($arfd,$arpid) = $arobj->getfiles("debian-binary", "control.tar.gz", "data.tar.gz");
  my $dir = mktempdir();
  my $sigfile = new IO::File(">$dir/_gpg$sign") or die
    "Couldn't open: $!";

  # forktools::assertsuccess($arpid, 'ar');

  # Why doesn't this work?

  #  my $gpgout = forktools::forkboth($arfd, $sigfile, "/usr/bin/gpg",
  #"--detach-sign");

  my @cmdline = ("gpg", "--openpgp", "--detach-sign");

  if ($key) {
    push (@cmdline, "--default-key", $key);
  }

  if ($keyring) {
    push (@cmdline, "--secret-keyring", $keyring);
  }

  if ($verbose) {
    warn("RUNNING: " . join(" ", @cmdline));
  }

  my ($gpgout, $gpgpid)
    = forkreader($arfd, @cmdline);
  my($line);
  while (defined($line = <$gpgout>)) {
    print $sigfile $line;
  }

  close $sigfile;

  Debian::debsigs::forktools::assertsuccess($arpid, 'ar', 1);
  Debian::debsigs::forktools::assertsuccess($gpgpid, "gpg", 1);

  $arobj->setfile("$dir/_gpg$sign");
  system("rm -rf $dir");
  exit(0);
}

sub cmd_list {
  my $file = shift @_;
  my $arobj = new Debian::debsigs::arf($file);
  my @list = $arobj->contents();

  print "GPG signatures in $file:\n";

  foreach my $sigfile (@list) {
    if ($sigfile =~ /^_gpg/) {
      my ($sig) = $sigfile =~ /^_gpg(.+)$/;
      my ($arfd, $arpid) = $arobj->getfiles($sigfile);
      my ($gpgkey, $gpgdate) = Debian::debsigs::gpg::getkeyfromfd($arfd);
      Debian::debsigs::forktools::assertsuccess($arpid, 'ar');
      if ($verbose) {
        my $gpgkeyname = Debian::debsigs::gpg::getkeynamefromid($gpgkey);
        print "$sig: signed by ";
        print $gpgkeyname ? $gpgkeyname : $gpgkey;
        print " on ", scalar(localtime($gpgdate)), "\n";
      } else {
        print "$sig: signed by $gpgkey on ", scalar(localtime($gpgdate)), "\n";
      }
    }
  }

  print " *** NO ATTEMPT HAS BEEN MADE TO VERIFY THE LISTED SIGNATURES ***\n";
  exit(0);
}

sub cmd_delete {
  my $file = shift @_;
  my $arobj = new Debian::debsigs::arf($file);
  my @list = $arobj->contents();

  die "File $file is not readable or does not exist." unless (-f $file);

  unless(scalar(grep(/^_gpg$delete$/, @list))) {
    die "File $file does not contain signature type $delete to remove.\n";
  }

  if ($arobj->delete("_gpg$delete")) {
    die "ar failed: $!\n";
  }
}


sub mktempdir {
  mkdir("/tmp/debsigndeb.$$", 0700) or die "couldn't mkdir: $!";
  return "/tmp/debsigndeb.$$";
}

sub syntax {
  system("pod2text $0");
}

__END__

=head1 NAME

debsigs - process signatures in .deb packages

=head1 SYNOPSIS

B<debsigs> B<--list>|B<-l> [B<-v>] file [file...]

B<debsigs> B<--sign=type> [B<--default-key=keyID>] [B<-v>] file [file...]

B<debsigs> B<--verify>|B<--check>|B<-c> file [file...]

B<debsigs> B<--delete=type> file [file...]

=head1 DESCRIPTION

I<debsigs> is used to manipulate the cryptographic signatures stored inside
a .deb file.  It is not used to verify those signatures; for that purpose,
see debsig-verify(1).

=head1 OPTIONS

=over 5

=item B<--list> or B<-l> or B<-t>

Lists the signatures found in the specified file.

=item B<--sign=type>

Creates a new signature of the type specified in the given file.
The signature will be created using the default key for your GPG keyring.

=item B<--default-key=keyID>

Uses a key other than the default for signing the package.

=item B<--secret-keyring=file> or B<-K file>

Uses a keyring other than the default for signing the package.  This
option is passed along to GPG verbatim; see the discussion in the gpg(1)
manpage for information on how to specify the keyring file.

=item B<-v>

Displays verbose output.

=item B<--verify> or B<--check> or B<-c>

Invokes debsig-verify to check the validity of the signature on this
package.

=item B<--delete=type>

Deletes the signature of the specified type from the package.

=back

=head1 FUTURE DIRECTIONS

It would be nice to have a command-line option to change the command used
for signing, instead of hard-coding "gpg".

=head1 AUTHOR

John Goerzen <jgoerzen@progenylinux.com>

=head1 SEE ALSO

debsig-verify(1), gpg(1)

=cut
