simple perl wrapper

Brian Warner warner at lothar.com
Sat Sep 12 15:11:48 CEST 1998


Hi all. I had a need to make a number of test cases for GPG (to try and catch
all possibilities that can come up when decrypting/verifying a message in my
mailcrypt gpg code: bad signature, signature by a non-trusted key, signature
by a key we don't have, encryption to a key we don't have, etc). I came up
with a perl wrapper around the GPG program that passes in the passphrase on an
extra filehandle, the standard secure way (but slightly tricky because we
can't multiplex it in on stdin). Something like this could form the basis of a
more generalized perl wrapper (not the same as a perl extension module to do
gpg stuff, which I think would be cooler, and on which I have some thoughts,
but useful for certain things anyway). I thought I'd post it here in case
anyone found it useful. Do what you will with it.

You give it the GPG command (in two parts, the second part being the "command"
as opposed to the options, since this code has to add in some more options and
they must come before the command proper), the data that should get fed to
stdin, and the optional passphrase. You get back the exit code, the data
printed to stdout, the data printed to stderr (not actually implemented yet),
and the data printed to the --status-fd which is usually the easiest thing to
parse to determine what GPG did. It uses the same techniques as the IPC::Open3
module except that it has even more pipes being created (stdin, stdout,
[stderr], passphrase, status), and adds the --passphrase-fd and --status-fd
arguments after opening the pipes once it knows the filenos.

enjoy,
 -Brian
  warner at lothar.com


use IO::Handle;

=head2 run_gpg

 ($rc, $out, $stderr, $status) = run_gpg($cmd, $cmd2, $in, $pw);

Runs C<$cmd> (which should include C<--batch --output -> if GPG is likely to
try and decrypt a file into the original filename).  Writes C<$in> to stdin,
and if C<$pw> is defined, writes it to a filehandle specified in a
C<--passphrase-fd N> argument. Appends C<$cmd2> to the end of the built-up
command, useful to make sure that C<$cmd2> is the very last argument (as
required by GPG's argument parser).

Returns the return code of the program, anything printed to stdout, stderr, and
anything printed to the --status-fd filehandle.

=cut


sub run_gpg {
    my($cmd, $cmd2, $in, $pw) = @_;
    my($kid_msg_read, $dad_msg_write, $kid_msg_write, $dad_msg_read) =
      (new IO::Handle, new IO::Handle, new IO::Handle, new IO::Handle);
    my($kid_status_write, $dad_status_read) = (new IO::Handle, new IO::Handle);
    my($kid_pw_read, $dad_pw_write) = (new IO::Handle, new IO::Handle)
      if defined($pw);

    pipe($kid_msg_read, $dad_msg_write) or die;
    pipe($dad_msg_read, $kid_msg_write) or die;
    pipe($dad_status_read, $kid_status_write) or die;
    if (defined($pw)) {
	pipe($kid_pw_read, $dad_pw_write) or die;
    }

    # at this point we can determine the final command, since the filehandle
    # numbers (at least for status and pwfd) are the same in the parent and
    # the child

    # --status-fd (fileno(KID_STATUS_WTR)
    my $status_fdno = $kid_status_write->fileno();
    
    # --passphrase-fd (fileno(KID_PW_RDR))
    my $pw_fdno = $kid_pw_read->fileno() if defined($pw);
    
    $cmd .= " --status-fd $status_fdno";
    $cmd .= " --passphrase-fd $pw_fdno" if defined($pw);
    $cmd .= " $cmd2";

    #print "cmd: $cmd\n";

    my $kidpid = fork();
    if ($kidpid == 0) {
	# child
	undef($dad_msg_write); undef($dad_msg_read); undef($dad_status_read);
	undef($dad_pw_write);
	$kid_msg_write->autoflush(1);
	$kid_status_write->autoflush(1);

	# dup2 KID_MSG_RDR to stdin 0
	my $stdin = \*STDIN;
	bless $stdin, 'IO::Handle' unless eval { $stdin->isa('IO::Handle') };
	$stdin->fdopen($kid_msg_read, "r");

	# dup2 KID_MSG_WTR to stdout 1
	my $stdout = \*STDOUT;
	bless $stdout, 'IO::Handle' unless eval { $stdout->isa('IO::Handle') };
	$stdout->fdopen($kid_msg_write, "w");
	
	exec($cmd); # never returns

	die "failed to exec";
    }

    # parent
    undef($kid_msg_read);
    undef($kid_msg_write);
    undef($kid_status_write);
    undef($kid_pw_read);
    $dad_msg_write->autoflush(1);
    $dad_pw_write->autoflush(1) if defined($pw);

    $dad_msg_write->print($in);
    $dad_msg_write->close(); # signal EOF

    if (defined($pw)) {
	$dad_pw_write->print($pw);
	$dad_pw_write->close();
    }

    # fetch response
    my($stdout, $stderr, $status);
    $stdout = join('', $dad_msg_read->getlines());
    $stderr = 'UNIMPLEMENTED';
    $status = join('', $dad_status_read->getlines());

    # wait, reap, and get return code
    wait();

    my $rc = $?;

    return($rc, $stdout, $stderr, $status);
}




More information about the Gnupg-devel mailing list