]> Dogcows Code - chaz/p5-File-KDBX/blobdiff - lib/File/KDBX/Key/YubiKey.pm
Add function for creating class attributes
[chaz/p5-File-KDBX] / lib / File / KDBX / Key / YubiKey.pm
index fb22bf838483117e0481bdfc43057193923da51b..0e42eb0766a68ffe1a8d99f260a4be03504bc4ce 100644 (file)
@@ -6,16 +6,20 @@ use strict;
 
 use File::KDBX::Constants qw(:yubikey);
 use File::KDBX::Error;
-use File::KDBX::Util qw(:io pad_pkcs7);
+use File::KDBX::Util qw(:class :io pad_pkcs7);
 use IPC::Cmd 0.52 qw(run_forked);
 use Ref::Util qw(is_arrayref);
 use Symbol qw(gensym);
 use namespace::clean;
 
-use parent 'File::KDBX::Key::ChallengeResponse';
+extends 'File::KDBX::Key::ChallengeResponse';
 
 our $VERSION = '999.999'; # VERSION
 
+# It can take some time for the USB device to be ready again, so we can retry a few times.
+our $RETRY_COUNT    = 5;
+our $RETRY_INTERVAL = 0.1;
+
 my @CONFIG_VALID = (0, CONFIG1_VALID, CONFIG2_VALID);
 my @CONFIG_TOUCH = (0, CONFIG1_TOUCH, CONFIG2_TOUCH);
 
@@ -37,28 +41,38 @@ sub challenge {
     }
 
     my @cmd = ($self->_program('ykchalresp'), "-n$device", "-$slot", qw{-H -i-}, $timeout == 0 ? '-N' : ());
-    my $r = $self->_run_ykpers(\@cmd, {
-        (0 < $timeout ? (timeout => $timeout) : ()),
-        child_stdin                         => pad_pkcs7($challenge, 64),
-        terminate_on_parent_sudden_death    => 1,
-    });
 
-    if (my $t = $r->{timeout}) {
-        throw 'Timed out while waiting for challenge response',
-            command     => \@cmd,
-            challenge   => $challenge,
-            timeout     => $t,
-            result      => $r;
-    }
+    my $r;
+    my $try = 0;
+    TRY:
+    {
+        $r = $self->_run_ykpers(\@cmd, {
+            (0 < $timeout ? (timeout => $timeout) : ()),
+            child_stdin                         => pad_pkcs7($challenge, 64),
+            terminate_on_parent_sudden_death    => 1,
+        });
+
+        if (my $t = $r->{timeout}) {
+            throw 'Timed out while waiting for challenge response',
+                command     => \@cmd,
+                challenge   => $challenge,
+                timeout     => $t,
+                result      => $r;
+        }
 
-    my $exit_code = $r->{exit_code};
-    if ($exit_code != 0) {
-        my $err = $r->{stderr};
-        chomp $err;
-        my $yk_errno = _yk_errno($err);
-        throw 'Failed to receive challenge response: ' . ($err ? $err : ''),
-            error       => $err,
-            yk_errno    => $yk_errno || 0;
+        my $exit_code = $r->{exit_code};
+        if ($exit_code != 0) {
+            my $err = $r->{stderr};
+            chomp $err;
+            my $yk_errno = _yk_errno($err);
+            if ($yk_errno == YK_EUSBERR && $err =~ /resource busy/i && ++$try <= $RETRY_COUNT) {
+                sleep $RETRY_INTERVAL;
+                goto TRY;
+            }
+            throw 'Failed to receive challenge response: ' . ($err ? $err : 'Something happened'),
+                error       => $err,
+                yk_errno    => $yk_errno || 0;
+        }
     }
 
     my $resp = $r->{stdout};
@@ -208,40 +222,13 @@ Get or set the L<ykinfo(1)> program name or filepath. Defaults to C<$ENV{YKINFO}
 
 =cut
 
-my %ATTRS = (
-    device          => 0,
-    slot            => 1,
-    timeout         => 10,
-    pre_challenge   => undef,
-    post_challenge  => undef,
-    ykchalresp      => sub { $ENV{YKCHALRESP} || 'ykchalresp' },
-    ykinfo          => sub { $ENV{YKINFO} || 'ykinfo' },
-);
-while (my ($subname, $default) = each %ATTRS) {
-    no strict 'refs'; ## no critic (ProhibitNoStrict)
-    *{$subname} = sub {
-        my $self = shift;
-        $self->{$subname} = shift if @_;
-        $self->{$subname} //= (ref $default eq 'CODE') ? $default->($self) : $default;
-    };
-}
-
-my %INFO = (
-    serial      => undef,
-    version     => undef,
-    touch_level => undef,
-    vendor_id   => undef,
-    product_id  => undef,
-);
-while (my ($subname, $default) = each %INFO) {
-    no strict 'refs'; ## no critic (ProhibitNoStrict)
-    *{$subname} = sub {
-        my $self = shift;
-        $self->{$subname} = shift if @_;
-        defined $self->{$subname} or $self->_set_yubikey_info;
-        $self->{$subname} // $default;
-    };
-}
+has device          => 0;
+has slot            => 1;
+has timeout         => 10;
+has pre_challenge   => undef;
+has post_challenge  => undef;
+has ykchalresp      => sub { $ENV{YKCHALRESP} || 'ykchalresp' };
+has ykinfo          => sub { $ENV{YKINFO}     || 'ykinfo' };
 
 =method serial
 
@@ -261,6 +248,14 @@ Get the "touch level" value for the device associated with this key (or C<undef>
 
 Get the vendor ID or product ID for the device associated with this key (or C<undef>).
 
+=cut
+
+has serial      => sub { $_[0]->_set_yubikey_info; $_[0]->{serial} };
+has version     => sub { $_[0]->_set_yubikey_info; $_[0]->{version} };
+has touch_level => sub { $_[0]->_set_yubikey_info; $_[0]->{touch_level} };
+has vendor_id   => sub { $_[0]->_set_yubikey_info; $_[0]->{vendor_id} };
+has product_id  => sub { $_[0]->_set_yubikey_info; $_[0]->{product_id} };
+
 =method name
 
     $name = $key->name;
@@ -301,27 +296,30 @@ sub _get_yubikey_info {
     my $timeout = $self->timeout;
     my @cmd = ($self->_program('ykinfo'), "-n$device", qw{-a});
 
+    my $r;
     my $try = 0;
     TRY:
-    my $r = $self->_run_ykpers(\@cmd, {
-        (0 < $timeout ? (timeout => $timeout) : ()),
-        terminate_on_parent_sudden_death    => 1,
-    });
-
-    my $exit_code = $r->{exit_code};
-    if ($exit_code != 0) {
-        my $err = $r->{stderr};
-        chomp $err;
-        my $yk_errno = _yk_errno($err);
-        return if $yk_errno == YK_ENOKEY;
-        if ($yk_errno == YK_EWOULDBLOCK && ++$try <= 3) {
-            sleep 0.1;
-            goto TRY;
+    {
+        $r = $self->_run_ykpers(\@cmd, {
+            (0 < $timeout ? (timeout => $timeout) : ()),
+            terminate_on_parent_sudden_death    => 1,
+        });
+
+        my $exit_code = $r->{exit_code};
+        if ($exit_code != 0) {
+            my $err = $r->{stderr};
+            chomp $err;
+            my $yk_errno = _yk_errno($err);
+            return if $yk_errno == YK_ENOKEY;
+            if ($yk_errno == YK_EWOULDBLOCK && ++$try <= $RETRY_COUNT) {
+                sleep $RETRY_INTERVAL;
+                goto TRY;
+            }
+            alert 'Failed to get YubiKey device info: ' . ($err ? $err : 'Something happened'),
+                error       => $err,
+                yk_errno    => $yk_errno || 0;
+            return;
         }
-        alert 'Failed to get YubiKey device info: ' . ($err ? $err : 'Something happened'),
-            error       => $err,
-            yk_errno    => $yk_errno || 0;
-        return;
     }
 
     my $out = $r->{stdout};
This page took 0.024652 seconds and 4 git commands to generate.