Eric J. Roode on 19 Jan 2005 17:58:11 -0000


[Date Prev] [Date Next] [Thread Prev] [Thread Next] [Date Index] [Thread Index]

[PLUG] gpg2ps


In response to the problems some have had with gpg-key2ps, I have written a program called gpg2ps, which does basically the same thing (except that it works) :-)

I suppose I could/should have patched the old gpg-key2ps, but I wound up rewriting the whole thing in Perl, and taking a somewhat different tack than the author of gpg-key2ps did.

So here it is. It attempts to be smart about sizing text to fit. It has documentation. It generates valid PostScript (so far). It won't format your hard drive (I'm pretty sure).

Let me know if it works for you.

--
Eric

#!/usr/bin/perl

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

=head1 NAME

gpg2ps - Format a gpg key for printing (as for a keysigning) in PostScript.

=head1 VERSION

This document describes version 0.01 of gpg2ps, January 18, 2005.

=cut

use strict;
our $VERSION = '0.01';
use Getopt::Long;

sub ps_safe
{
    my $str = shift;
    $str =~ s/([\(\|\)])/\\\1/g;
    return $str;
}

my $username = '';
my $key;
my ($finger1, $finger2);

GetOptions("name=s" => \$username, "key=s" => \$key);
die "No key specified
Usage:
    gpg2ps --key='string'  [--name='Your Name Here']
" if !defined $key;

$username = ps_safe($username);

my @lines = `gpg --list-key --fingerprint '$key'`;
chomp @lines;
my $pubcount = grep /^pub/, @lines;

die "No keys matching '$key'.\n" if 0 == $pubcount;
if ($pubcount > 1)
{
    my @list = grep /^pub/, @lines;
    s/^pub // for @list;
    print STDERR "'$key' matches multiple keys:\n";
    print STDERR "$_\n" for @list;
    die "Too many keys\n";
}

my @uids;
my $pub;
foreach my $line (@lines)
{
    if ($line =~ m%^pub\s+(\d+)(.)/([[:xdigit:]]+)\s+(\S+)\s+(.*)%)
    {
        @$pub{qw/bitsize algorithm keyid cdate/} = ($1, $2, $3, $4);
        push @uids, $5;
        next;
    }
    elsif ($line =~ /^pub /)
    {
        die "Cannot parse public key.  Contact author!\n";
    }
    elsif ($line =~ /^\s+Key fingerprint = ((?:[[:xdigit:]]{4} ?){5})  ((?:[[:xdigit:]]{4} ?){5})/)
    {
        ($finger1, $finger2) = ($1, $2);
    }
    elsif ($line =~ /^\s+Key fingerprint = /)
    {
        die "Cannot parse fingerprint.  Contact author!\n";
    }
    elsif ($line =~ /^uid\s+(.*)/)
    {
        my $uid = $1;
        next if $uid =~ /^\[.*\]$/;
        push @uids, $uid;
        next;
    }
}

foreach my $uid (@uids)
{
    if ($uid =~ /^([^\(]+) (\([^\)]*\)) (<[^>]*>)/)
    {
        my ($n, $c, $e) = map ps_safe ($_), ($1, $2, $3);
        $uid = [$n, $c, $e];
    }
    elsif ($uid =~ /^([^<]+) (<[^>]*>)/)
    {
        my ($n,     $e) = map ps_safe ($_), ($1,     $2);
        $uid = [$n, '', $e];
    }
    else
    {
        $uid = [ps_safe ($uid), '', ''];
    }
}

my $drawit = <<DRAW;
/drawit
{
    tgo ($pub->{cdate}) ($pub->{keyid}) ($username) tit
    fgo ($finger1) ($finger2) fpr
    space-dy down
DRAW

$drawit .= "    ugo ($_->[0]) ($_->[1]) ($_->[2]) uid\n"  for @uids;
$drawit .= <<DRAW;
    bm down
    prog
} def
DRAW


print <<EOPS;
%!PS

/page-height  8.5 72 mul def
/page-width   11  72 mul def
/T page-height  36 sub def
/L 36 def
/R page-width 36 sub def
/M R L sub 2 div L add def
/B 18 def
/H T B sub def
/W R L sub def

/width W 2 div def

/tm  9 def
/bm 18 def
/lm 18 def
/rm 18 def
/maxuid width lm sub rm sub def

/top       T def
/left      L 18 add def
/title-fs  11 def
/title-dy  title-fs def
/fpr-fs-x  9 def
/fpr-fs-y  10 def
/fpr-dy    fpr-fs-y def
/program-fs 4.5 def
/name-fs    9 def
/comment-fs name-fs def
/email-fs   name-fs def
/uid-dy     name-fs def
/space-dy  8 def
/label-fs fpr-fs-y def
/fgap     13 def
/ugap     10 def

/TF1 /Times-Roman  findfont title-fs   scalefont def       /tf { TF setfont } def  /ts { tf show } def
/FF  /Courier      findfont [fpr-fs-x 0 0 fpr-fs-y 0 0] makefont def  /ff { FF setfont } def  /fs { ff show } def
/NF1 /Times-Roman  findfont name-fs    scalefont def       /nf { NF setfont } def  /ns { nf show } def
/CF1 /Times-Italic findfont comment-fs scalefont def       /cf { CF setfont } def  /cs { cf show } def
/EF1 /courier      findfont email-fs   scalefont def       /ef { EF setfont } def  /es { ef show } def
/LF  /Times-Roman  findfont label-fs   scalefont def       /lf { LF setfont } def  /ls { lf show } def
/PF  /Helvetica    findfont program-fs scalefont def       /pf { PF setfont } def  /ps { pf show } def

/TF2 TF1 [0.9 0 0 1 0 0] makefont def
/TF3 TF1 [0.8 0 0 1 0 0] makefont def
/TF4 TF1 [0.7 0 0 1 0 0] makefont def
/NF2 NF1 [0.9 0 0 1 0 0] makefont def
/NF3 NF1 [0.8 0 0 1 0 0] makefont def
/NF4 NF1 [0.7 0 0 1 0 0] makefont def
/CF2 CF1 [0.9 0 0 1 0 0] makefont def
/CF3 CF1 [0.8 0 0 1 0 0] makefont def
/CF4 CF1 [0.7 0 0 1 0 0] makefont def
/EF2 EF1 [0.9 0 0 1 0 0] makefont def
/EF3 EF1 [0.8 0 0 1 0 0] makefont def
/EF4 EF1 [0.7 0 0 1 0 0] makefont def

/in { 72 mul } bind def
/cm { 72 mul 2.54 div } bind def
/landscape { 90 rotate 0 -8.5 in translate} bind def
/strcat
{
    2 copy length exch length add
    string dup
    4 2 roll
    2 index 0 3 index
    putinterval
    exch length exch putinterval
} bind def

/go   { left ypos moveto } bind def
/down { ypos exch sub /ypos exch def go } bind def

/tgo
{
    /sectop ypos def
    tm title-fs add down
} def

/tit
{
    /name exch def

    (GPG key info for )
    name () ne { name strcat (, ) strcat } if
    (key id ) strcat exch strcat (, created ) strcat exch strcat (.) strcat
    /title exch def

    /TF TF1 def
    title TF len  maxuid gt
    {
        /TF TF2 def
        title TF len  maxuid gt
        {
            /TF TF3 def
            title TF len  maxuid gt
            {
                /TF TF4 def
            } if
        } if
    } if

    title show
} def

/fg { fgap 0 rmoveto } def

/fgo { space-dy fpr-dy add down} def

/fpr {
    (Fingerprint:  ) ls exch fs fg show
} def

/ug { ugap 0 rmoveto } def

/ugo { uid-dy down} def

/uid
{
    /email exch def
    /comm  exch def
    /name  exch def

    /NF NF1 def  /CF CF1 def  /EF EF1 def
    name NF len  comm CF len  email EF len  add add ugap add ugap add
    maxuid gt
    {
        /NF NF2 def  /CF CF2 def  /EF EF2 def
        name NF len  comm CF len  email EF len  add add ugap add ugap add
        maxuid gt
        {
            /NF NF3 def  /CF CF3 def  /EF EF3 def
            name NF len  comm CF len  email EF len  add add ugap add ugap add
            maxuid gt
            {
                /NF NF4 def  /CF CF4 def  /EF EF4 def
            } if
        } if
    } if

    name ns  ug  comm () ne { comm cs  ug } if  email es
} def

/len { setfont stringwidth pop} bind def
/hr { L ypos moveto R ypos lineto stroke } bind def
/vr { dup ypos moveto sectop lineto stroke } bind def
/vrs { L vr M vr R vr } bind def
/prog
{
    gsave
    left ypos program-fs add moveto
    (Generated by gpg2ps v$VERSION) ps
    grestore
} def

/do-two
{
    hr
    /left L lm add def
    drawit

    vrs
    /left L width add lm add def
    /height sectop ypos sub def
    /ypos sectop def
    drawit
} def

$drawit

landscape
0 setlinewidth
/ypos T def
/height 0 def
/minY { B height add } def
{ ypos minY le {exit} if  do-two } loop

hr

showpage

EOPS


__END__

=head1 SYNOPSIS

 gpg2ps --key 'key identifier'

Optional parameter:

    --name 'Your Name Here'

=head1 DESCRIPTION

C<gpg2ps> formats some information about a GnuPG public key for
display on a PostScript printer.  The output is repeated as many times
as will fit on the page, and outlines are drawn, so that you may cut
the page into slips of paper to be handed out at a keysigning party.

The program invokes the gpg executable (assumed to be in the C<PATH>),
and sends its output to standard output.  Typical usage might be:

 gpg2ps --key schmoe --name 'Joe Schmoe' | lpr -P myprinter

As usual, use shell quoting wisely when using key identifiers and/or
names that have special characters in them.

=head1 OPTIONS

=over 4

=item key

 --key 'identifier'

This option is mandatory.  It specifies the key to be printed.  It
accepts any identifier that gpg accepts for its C<--list-keys> option.

If more than one key matches, gpg2ps will exit with an error message.

=item name

 --name 'Your name'

Optional.  This specifies your name (human-readable) to be displayed
on the title line of each slip of paper.

=back

=head1 SECURITY

This program does very little checking of its input.  It is possible
to craft a C<--key> option that does funky things with C<gpg>.  But
that would be silly, because this program does nothing that the user
could not do on the command line anyhow.  Likewise, the C<--user>
option is barely checked, and it is conceivable that strange values
could do strange things to the printer.  But again, the user could do
that anyhow.

=head1 LIMITATIONS / BUGS

=over 4

=item *

This whole program is in a fairly early beta stage.  It's rather hacky
and it generates rather hacky postscript.

=item *

The PostScript code that is generated is not compliant to Adobe's
recommended page-formatting standards.

=item *

The PostScript code that is generated assumes a page size of 8.5 by 11
inches (U.S. "Letter" size).  It I<should> work fine on A4 paper... I
think.  TODO: make sure it works on A4 paper.

=back

=head1 AUTHOR / COPYRIGHT

Eric J. Roode, roode@cpan.org

Copyright (c) 2005 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.  Also,
if you find a bug, or if any behavior of gpg2ps surprises you, please
let me know.

=cut

=begin gpg

-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.2.6 (Cygwin)

iD8DBQFB7p6TY96i4h5M0egRAizyAKCZnFfrAO0tooi7LIS1asAXi5/ctQCffIWQ
YNTv2eLIzFXXwoeLzBeuCY0=
=WcTf
-----END PGP SIGNATURE-----

=end gpg
___________________________________________________________________________
Philadelphia Linux Users Group         --        http://www.phillylinux.org
Announcements - http://lists.phillylinux.org/mailman/listinfo/plug-announce
General Discussion  --   http://lists.phillylinux.org/mailman/listinfo/plug