#!/usr/bin/perl

=for gpg
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

=head1 NAME

perlsign - GPG-sign perl programs and modules.

=head1 VERSION

This documentation describes version 0.04 of C<perlsign>, March 31, 2003.

=cut

use strict;
use IO;
use Getopt::Long;
use vars qw'$VERSION';
$VERSION = 0.04;

# 1. Get configuration parameters.

# Defaults:
my $gpg_path_dflt = 'gpg';
my $tmp_path_dflt = '.';
my $cfg_file_dflt = "$ENV{HOME}/.perlsignrc";

# Command-line choices:
my $gpg_path;
my $tmp_path;
my $cfg_file;
GetOptions ('gpg=s' => \$gpg_path, 'temp=s' => \$tmp_path, 'cfg=s' => \$cfg_file);

# Config file options:
my $cfg;
if (defined $cfg_file)    # If user chose a config file
	{
	$cfg = new IO::File $cfg_file  or die "Cannot read $cfg_file: $!\n";
	}
elsif (-e $cfg_file_dflt)   # If default config file exists
	{
	$cfg = new IO::File $cfg_file_dflt or die "Cannot read $cfg_file_dflt: $!\n";
	}
if (defined $cfg)    # If we found a file to open
	{
	while (<$cfg>)
		{
		s/\#.*//;          # Remove comment chars
		next unless /\S/;  # Skip blank lines
		unless (/=/)
			{
			warn "Syntax error in config file line " . $cfg->input_line_number() . ", ignoring.\n";
			next;
			}
		s/^\s+//;
		s/\s+$//;
		my ($opt, $val) = split /\s*=\s*/, $_, 2;

		$opt =~ s/^--//;
		if ($opt eq 'gpg')
			{
			$gpg_path ||= $val;
			}
		elsif ($opt eq 'temp')
			{
			$tmp_path ||= $val;
			}
		else
			{
			warn "Unrecognized option $opt in config file, ignoring.\n";
			}
		}
	}

$gpg_path ||= $gpg_path_dflt;
$tmp_path ||= $tmp_path_dflt;


# 2. Copy file to 1st temp file, stripping certain lines along the way.

my $in_file   = shift or die "Usage: $0 script-or-module-name\n";
my $bare      = $in_file;    $bare =~ s!.*/!!;
my $pre_sign  = "$tmp_path/$bare.pre";
my $post_sign = "$tmp_path/$bare.post";

my $in  = new IO::File $in_file      or die "Cannot read $in_file: $!\n";
my $out = new IO::File ">$pre_sign"  or die "Cannot create temp file $pre_sign: $!\n";

my $shebang;
my $line;
my $last_was_blank;

# Check for shebang line
$line = <$in>;
if ($line =~ /^ \s* \# \s* \!/x)
	{
	$shebang = $line;
	$line = <$in>;
	}

# Skip any blank lines
while ($line =~ /^\cM?\n/)
	{
	$line = <$in>;
	}

# If next line is not a POD line, we'll need to insert a =cut.
if ($line !~ /^=/)
	{
	print $out "=cut\n\n";
	}

# Copy rest of input to output, skipping =for gpg and =begin gpg sections
my $in_for;
my $in_begin;
my $skip_blanks;

while (defined $line)
	{
	if ($in_for)
		{
		if ($line =~ /^\cM?\n/)
			{
			$in_for = 0;
			$skip_blanks = 1;
			}
		next;
		}

	if ($in_begin)
		{
		if ($line =~ /^=end \s+ (pgp|gpg)/x)
			{
			$in_begin = 0;
			$skip_blanks = 1;
			}
		next;
		}

	if ($line =~ /^=for \s+ (pgp|gpg)/x)
		{
		$in_for = 1;
		next;
		}

	if ($line =~ /^=begin \s+ (pgp|gpg)/x)
		{
		$in_begin = 1;
		next;
		}

	if ($skip_blanks && $line =~ /^\cM?\n/)
		{
		next;
		}
	$skip_blanks = 0;

	$last_was_blank = $line =~ /^\cM?\n/;
	print $out $line;
	}
continue
	{
	$line = <$in>;
	}

# Prepare for signature area
print $out "\n" unless $last_was_blank;
print $out "=begin gpg\n\n";

close $in;
close $out;


# 3. Remove 2nd temp file, if it exists.
unlink $post_sign  if -e $post_sign;    # Ignore error; GPG'll catch it.


# 4. Invoke GPG to sign the 1st temp file (into the 2nd temp file)
system $gpg_path, '--output', $post_sign, '--clearsign', $pre_sign;


# 5. Remove first temp file
unlink $pre_sign  or warn "Couldn't remove temp file $pre_sign: $!\n";


# 6. Now copy back to original, replacing stuff that was stripped away.
$in  = new IO::File $post_sign   or die "Cannot read $post_sign: $!\n";
$out = new IO::File ">$in_file"  or die "Cannot write $in_file: $!\n";

print $out "$shebang\n"  if $shebang;
print $out "=for gpg\n";

while (<$in>)
	{
	tr/\cM//d;
	print $out $_;
	}

print $out "\n=end gpg\n";

close $in;
close $out;


# 7. Remove second temp file
unlink $post_sign or warn "Couldn't remove temp file $post_sign: $!\n";

__END__

=head1 SYNOPSIS

 perlsign [options] file.pl

Options:

 --gpg=/path/to/gpg      # default: gpg
 --temp=/temp/dir        # Default: current dir
 --cfg=config_filename   # Default: $HOME/.perlsignrc

=head1 DESCRIPTION

This program invokes GPG to digitally sign Perl source files.  The GPG
signature becomes embedded into the source file as POD paragraphs.
The functionality of the program or module should not be affected.

This may be useful to prove authorship of a program, or to prove that
changes have or have not been made to a source file.

Note that the file you specify is overwritten with a signed version of
the file.

The signature can be verified with the following command:

 gpg --verify file

=head1 OPTIONS

=over 4

=item --gpg

Specifies the path to the gpg executable.  The default is just 'gpg';
i.e., it looks for gpg in the user's PATH.

=item --temp

Specifies the directory in which to place temporary files.  C<sign>
uses two temporary files, named by appending ".pre" and ".post" to
the input file's name.  The temp directory defaults to '.', ie the
current directory.

=item --cfg

Specifies a config file to use for setting options.  The default is
C<.perlsignrc> in the user's home directory.

=back

=head1 CONFIG FILE

Instead of specifying command-line options each time you run C<sign>,
you can place them in a config file.  The format of the config file is
very simple:

 --option = value

One option per line.  You may omit the "--".  Whitespace is more or
less ignored.  Blank lines are ignored.  Anything after a "#"
character on a line is ignored.

=head1 POSSIBLE FUTURE FEATURES

=over 1

=item *

Support PGP in addition to GPG.  The problem with this is that there
are so many different, incompatible versions of the PGP command-line
utility.

=back

=begin changelog

CHANGE HISTORY

v0.01,  2002/07/05   First version.

v0.02,  2002/12/10   Changed name from 'sign' to 'perlsign'.

v0.03,  2003/03/29   Formatting changes for upload to CPAN.

=end changelog

=head1 README

This program invokes GPG to digitally sign Perl source files.  The GPG
signature becomes embedded into the source file as POD paragraphs.
The functionality of the program or module isn't affected.

This may be useful to prove authorship of a program, or to prove that
changes have or have not been made to a source file.

=head1 SCRIPT CATEGORIES

CPAN
VersionControl/CVS

=head1 PREREQUISITES

strict
IO
Getopt::Long

=head1 AUTHOR / COPYRIGHT

Eric J. Roode, roode@cpan.org

Copyright (c) 2002-3 by Eric J. Roode. All Rights Reserved.  This module
is free software; you can redistribute it and/or modify it under the
same terms as Perl itself.

If you have suggestions for improvement, please drop me a line.  If
you make improvements to this software, I ask that you please send me
a copy of your changes. Thanks.

=begin gpg

-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.2.1 (GNU/Linux)

iD8DBQE+iEBQY96i4h5M0egRAvQ1AKCm3u4kztJjcFcmCY6A8U9Z9KU+7QCgm4nV
Dl4UKmLVvgoPVdIyNInBpzM=
=nbOK
-----END PGP SIGNATURE-----

=end gpg