AnyEvent-GnuPG

 view release on metacpan or  search on metacpan

lib/AnyEvent/GnuPG.pm  view on Meta::CPAN

sub _cmdline {
    my $self = shift;
    my $args = [ $self->{gnupg_path} ];

    # Default options
    push @$args, "--no-tty", "--no-greeting", "--yes";

    # Check for homedir and options file
    push @$args, "--homedir", $self->{homedir} if $self->{homedir};
    push @$args, "--options", $self->{options} if $self->{options};

    # Command options
    push @$args, @{ $self->_options };

    # Command and arguments
    push @$args, "--" . $self->_command;
    push @$args, @{ $self->_args };

    return $args;
}

sub _condvar {
    my $cb = shift;
    return $cb if ref $cb eq 'AnyEvent::CondVar';
    my $cv = AE::cv;
    $cv->cb($cb) if ref $cb eq 'CODE';
    $cb ||= '';
    $cv;
}

sub _croak {
    my ( $cv, $msg ) = @_;
    AE::log error => $msg;
    $cv->croak($msg);
    $cv;
}

sub _catch {
    my ( $cv1, $cb ) = @_;
    AE::cv {
        my $cv2 = shift;
        try {
            $cb->( $cv2->recv );
        }
        catch {
            s{ at \S+ line \d+\.\s+$}{};
            $cv1->croak($_)
        };
    }
}

sub _eq($_) { shift eq pop }    ## no critic

sub _parse_status {
    my ( $self, $cv, %actions ) = @_;
    my $commands;
    $self->{status_fd}->readlines_cb(
        sub {
            my $line = shift;
            unless ( defined $line ) {
                AE::log debug => "end of status parsing";
                $cv->send($commands);
            }
            if ( my ( $cmd, $arg ) =
                $line =~ m{^\[gnupg:\]\s+(\w+)\s*(.+)?\s*$}i )
            {
                $arg ||= '';
                my @args = $arg ? ( split /\s+/, $arg ) : ();
                AE::log debug => "got command: $cmd ($arg)";
                try {
                    for ( lc $cmd ) {
                        _eq('newsig')  && do { last };
                        _eq('goodsig') && do { last };
                        _eq('expsig')
                          && do { die "the signature is expired ($arg)" };
                        _eq('expkeysig') && do {
                            die
                              "the signature was made by an expired key ($arg)";
                        };
                        _eq('revkeysig') && do {
                            die
                              "the signature was made by an revoked key ($arg)";
                        };
                        _eq('badsig') && do {
                            die
                              "the signature has not been verified okay ($arg)";
                        };
                        _eq('errsig') && do {
                            die "the signature could not be verified ($arg)";
                        };
                        _eq('validsig') && do { last };
                        _eq('sig_id')   && do { last };
                        _eq('enc_to')   && do { last };
                        _eq('nodata')   && do {
                            for ($arg) {
                                _eq('1') && die "no armored data";
                                _eq('2')
                                  && die
                                  "expected a packet but did not found one";
                                _eq('3') && die "invalid packet found";
                                _eq('4')
                                  && die "signature expected but not found";
                                die "no data has been found";
                            }
                        };
                        _eq('unexpected')
                          && do { die "unexpected data has been encountered" };
                        _eq('trust_undefined')
                          && do { die "signature trust undefined: $arg" };
                        _eq('trust_never')
                          && do { die "signature trust is never: $arg" };
                        _eq('trust_marginal') && do { last };
                        _eq('trust_fully')    && do { last };
                        _eq('trust_ultimate') && do { last };
                        _eq('pka_trust_good') && do { last };
                        _eq('pka_trust_bad')  && do { last };
                        _eq('sigexpired')
                          or _eq('keyexpired') && do {
                            die "the key has expired since "
                              . ( scalar localtime $arg );
                          };
                        _eq('keyrevoked') && do {
                            die "the used key has been revoked by its owner";
                        };
                        _eq('badarmor')
                          && do { die "the ASCII armor is corrupted" };
                        _eq('rsa_or_idea')         && do { last };
                        _eq('shm_info')            && do { last };
                        _eq('shm_get')             && do { last };

lib/AnyEvent/GnuPG.pm  view on Meta::CPAN

                            for ($cmd) {
                                _eq('inv_recp')
                                  && do { $prefix .= ' recipient' };
                                _eq('inv_sgnr') && do { $prefix .= ' sender' };
                            }
                            $prefix .= ': ';
                            for ( shift(@args) ) {
                                _eq('0') && die $prefix . "no specific reason";
                                _eq('1') && die $prefix . "not found";
                                _eq('2')
                                  && die $prefix . "ambigious specification";
                                _eq('3')  && die $prefix . "wrong key usage";
                                _eq('4')  && die $prefix . "key revoked";
                                _eq('5')  && die $prefix . "key expired";
                                _eq('6')  && die $prefix . "no CRL known";
                                _eq('7')  && die $prefix . "CRL too old";
                                _eq('8')  && die $prefix . "policy mismatch";
                                _eq('9')  && die $prefix . "not a secret key";
                                _eq('10') && die $prefix . "key not trusted";
                                _eq('11')
                                  && die $prefix . "missing certificate";
                                _eq('12')
                                  && die $prefix . "missing issuer certificate";
                                die $prefix . '???';
                            }
                        };
                        _eq('no_recp') && do { die "no recipients are usable" };
                        _eq('no_sgnr') && do { die "no senders are usable" };
                        _eq('already_signed')   && do { last };
                        _eq('truncated')        && do { last };
                        _eq('error')            && do { die $arg };
                        _eq('success')          && do { last };
                        _eq('attribute')        && do { last };
                        _eq('cardctrl')         && do { last };
                        _eq('plaintext')        && do { last };
                        _eq('plaintext_length') && do { last };
                        _eq('sig_subpacket')    && do { last };
                        _eq('sc_op_failure')
                          && do { die "smartcard failure ($arg)" };
                        _eq('sc_op_success')      && do { last };
                        _eq('backup_key_created') && do { last };
                        _eq('mountpoint')         && do { last };
                        AE::log note => "unknown command: $cmd";
                    }
                    my $result;
                    if ( $actions{ lc($cmd) } ) {
                        $result = $actions{ lc($cmd) }->(@args);
                    }
                    push @$commands => {
                        cmd    => $cmd,
                        arg    => $arg,
                        args   => \@args,
                        result => $result
                    };
                }
                catch {
                    s{\s+$}{};
                    $self->_abort_gnupg( $_, $cv );
                }
                finally {
                    AE::log debug => "arguments parsed as: ["
                      . ( join ', ', map { "'$_'" } @args ) . "]";
                }
            }
            else {
                return $self->_abort_gnupg(
                    "error communicating with gnupg: bad status line: $line",
                    $cv );
            }
        }
    );
    $cv;
}

sub _abort_gnupg {
    my ( $self, $msg, $cb ) = @_;
    my $cv = _condvar($cb);
    AE::log error => $msg if $msg;
    if ( $self->{gnupg_proc} ) {
        $self->{gnupg_proc}->fire_and_kill(
            10,
            sub {
                AE::log debug => "fired and killed";
                $self->_end_gnupg(
                    sub {
                        AE::log debug => "gnupg aborted";
                        $cv->croak($msg);
                    }
                );
            }
        );
    }
    $cv;
}

sub _end_gnupg {
    my ( $self, $cb ) = @_;
    my $cv = _condvar($cb);

    if ( ref $self->{input} eq 'GLOB' ) {
        close $self->{input};
    }

    if ( $self->{command_fd} ) {
        $self->{command_fd}->finish;
    }

    if ( 0 && $self->{status_fd} ) {
        $self->{status_fd}->A->destroy;
    }

    if ( $self->{gnupg_proc} ) {

        $self->{gnupg_proc}->wait(
            sub {
                if ( ref $self->{output} eq 'GLOB' ) {
                    close $self->{output};
                }

                for (
                    qw(protocol proc command options args status_fd command_fd input output next_status )
                  )
                {
                    delete $self->{$_};
                }

                AE::log debug => "gnupg exited";
                $cv->send;
            }
        );

    }
    else {
        $cv->send;
    }
    $cv;
}

sub _run_gnupg {
    my ( $self, $cv ) = @_;

    if ( defined $self->{input} and not ref $self->{input} ) {
        my $file = $self->{input};
        open( my $fh, '<', $file ) or die "cannot open file $file: $!";
        AE::log info => "input file $file opened at $fh";
        $self->{input} = $fh;
    }

    if ( defined $self->{output} and not ref $self->{output} ) {
        my $file = $self->{output};
        open( my $fh, '>', $file ) or die "cannot open file $file: $!";
        AE::log info => "output file $file opened at $fh";
        $self->{output} = $fh;
    }

    my $cmdline = $self->_cmdline;

    my $gpg = shift @$cmdline;

    my $status  = AnyEvent::Proc::reader();
    my $command = AnyEvent::Proc::writer();

    unshift @$cmdline, '--status-fd'  => $status;
    unshift @$cmdline, '--command-fd' => $command;

    my $err;

    AE::log debug => "running $gpg " . join( ' ' => @$cmdline );
    my $proc = AnyEvent::Proc->new(
        bin           => $gpg,
        args          => $cmdline,
        extras        => [ $status, $command ],
        ttl           => 600,
        on_ttl_exceed => sub { $self->_abort_gnupg( 'ttl exceeded', $cv ) },
        errstr        => \$err,
    );

    if ( defined $self->{input} ) {
        $proc->pull( $self->{input} );
    }

    if ( defined $self->{output} ) {
        $proc->pipe( out => $self->{output} );
    }

    $self->{command_fd} = $command;
    $self->{status_fd}  = $status;
    $self->{gnupg_proc} = $proc;

    AE::log debug => "gnupg ready";

    $proc;
}

sub _send_command {
    shift->{command_fd}->writeln(pop);
}

sub DESTROY {
    my $self = shift;

    $self->{gnupg_proc}->kill if $self->{gnupg_proc};
}

sub new {
    my $proto = shift;
    my $class = ref $proto || $proto;

    my %args = @_;

    my $self = {};
    if ( $args{homedir} ) {
        confess("Invalid home directory: $args{homedir}")
          unless -d $args{homedir} && -x _;
        $self->{homedir} = $args{homedir};
    }
    if ( $args{options} ) {
        confess("Invalid options file: $args{options}")
          unless -r $args{options};
        $self->{options} = $args{options};
    }
    if ( $args{gnupg_path} ) {
        confess("Invalid gpg path: $args{gnupg_path}")
          unless -x $args{gnupg_path};
        $self->{gnupg_path} = $args{gnupg_path};
    }
    else {
        my ($path) = grep { -x "$_/gpg" } split /:/, $ENV{PATH};
        confess("Couldn't find gpg in PATH ($ENV{PATH})") unless $path;
        $self->{gnupg_path} = "$path/gpg";
    }

    bless $self, $class;
}

sub version {
    shift->version_cb(@_)->recv;
}

sub version_cb {
    my ( $self, $cb ) = @_;
    my $cv = _condvar($cb);

    $self->_command("version");
    $self->_options( [] );
    $self->_args(    [] );

    my $version;

    my $proc = $self->_run_gnupg($cv);



( run in 1.742 second using v1.01-cache-2.11-cpan-39bf76dae61 )