File Coverage

blib/lib/Protocol/DBus/Authn/Mechanism/EXTERNAL.pm
Criterion Covered Total %
statement 25 47 53.1
branch 3 10 30.0
condition 2 9 22.2
subroutine 7 9 77.7
pod 0 4 0.0
total 37 79 46.8


line stmt bran cond sub pod time code
1             package Protocol::DBus::Authn::Mechanism::EXTERNAL;
2              
3 12     12   797166 use strict;
  12         83  
  12         290  
4 12     12   55 use warnings;
  12         28  
  12         299  
5              
6 12     12   50 use parent 'Protocol::DBus::Authn::Mechanism';
  12         22  
  12         61  
7              
8 12     12   2610 use Protocol::DBus::Socket ();
  12         28  
  12         238  
9              
10 12     12   51 use constant SOCKET_MSGHDR_MINIMUM_VERSION => 0.05;
  12         24  
  12         4453  
11              
12             # The methods of user credential retrieval that the reference D-Bus
13             # server relies on prefer “out-of-band” methods like SO_PEERCRED
14             # on Linux rather than SCM_CREDS. (See See dbus/dbus-sysdeps-unix.c.)
15             # So for some OSes it’s just not necessary to do anything special to
16             # send credentials.
17             #
18             # This list is exposed for the sake of tests.
19             #
20             our @_OS_NO_MSGHDR_LIST = (
21              
22             # Reference server doesn’t need our help:
23             'linux',
24             'netbsd', # via LOCAL_PEEREID, which dbus calls
25              
26             # MacOS works, though … ??
27             'darwin',
28              
29             # 'openbsd', ??? Still trying to test.
30              
31             # No way to pass credentials via UNIX socket,
32             # so let’s just send EXTERNAL and see what happens.
33             # It’ll likely just fail over to DBUS_COOKIE_SHA1.
34             'cygwin',
35             'mswin32',
36             );
37              
38 3     3 0 142 sub INITIAL_RESPONSE { unpack 'H*', $> }
39              
40             # The reference server implementation does a number of things to try to
41             # fetch the peer credentials. .
42             sub must_send_initial {
43 10     10 0 39 my ($self) = @_;
44              
45 10 100       55 if (!defined $self->{'_must_send_initial'}) {
46              
47 8         24 my $can_skip_msghdr = grep { $_ eq $^O } @_OS_NO_MSGHDR_LIST;
  40         108  
48              
49 8   33     26 $can_skip_msghdr ||= eval { my $v = Socket::SO_PEERCRED(); 1 };
  0         0  
  0         0  
50 8   33     25 $can_skip_msghdr ||= eval { my $v = Socket::LOCAL_PEEREID(); 1 };
  0         0  
  0         0  
51              
52 8 50       35 if (!$can_skip_msghdr) {
53 0         0 my $ok = eval {
54 0         0 require Socket::MsgHdr;
55 0         0 Socket::MsgHdr->VERSION(SOCKET_MSGHDR_MINIMUM_VERSION);
56             };
57              
58 0 0       0 if (!$ok) {
59 0         0 $self->{'_failed_socket_msghdr'} = $@;
60 0         0 $can_skip_msghdr = 1;
61             }
62             }
63              
64             # As of this writing it seems FreeBSD and DragonflyBSD do require
65             # Socket::MsgHdr, even though they both have LOCAL_PEERCRED which
66             # should take care of that.
67 8         33 $self->{'_must_send_initial'} = !$can_skip_msghdr;
68             }
69              
70 10         56 return $self->{'_must_send_initial'};
71             }
72              
73             sub on_rejected {
74 0     0 0   my ($self) = @_;
75              
76 0 0         if ($self->{'_failed_socket_msghdr'}) {
77 0           warn "EXTERNAL authentication failed. Socket::MsgHdr failed to load earlier; maybe making it available would fix this? (Load failure was: $self->{'_failed_socket_msghdr'})";
78             }
79              
80 0           return;
81             }
82              
83             sub send_initial {
84 0     0 0   my ($self, $s) = @_;
85              
86 0           my $msg = Socket::MsgHdr->new( buf => "\0" );
87              
88             # The kernel should fill in the payload.
89 0           $msg->cmsghdr( Socket::SOL_SOCKET(), Socket::SCM_CREDS(), "\0" x 64 );
90              
91 0           local $!;
92 0           my $ok = Protocol::DBus::Socket::sendmsg_nosignal($s, $msg, 0);
93              
94 0 0 0       if (!$ok && !$!{'EAGAIN'}) {
95 0           die "sendmsg($s): $!";
96             }
97              
98 0           return $ok;
99             }
100              
101             1;