File Coverage

blib/lib/Protocol/DBus/Authn.pm
Criterion Covered Total %
statement 98 139 70.5
branch 18 50 36.0
condition 5 18 27.7
subroutine 16 21 76.1
pod 0 5 0.0
total 137 233 58.8


line stmt bran cond sub pod time code
1             package Protocol::DBus::Authn;
2              
3 8     8   1848 use strict;
  8         16  
  8         232  
4 8     8   44 use warnings;
  8         16  
  8         184  
5              
6 8     8   3644 use IO::Framed ();
  8         27420  
  8         156  
7 8     8   56 use Module::Load ();
  8         20  
  8         108  
8 8     8   36 use Socket ();
  8         16  
  8         100  
9              
10 8     8   3092 use Protocol::DBus::X ();
  8         24  
  8         232  
11              
12 8     8   52 use constant _CRLF => "\x0d\x0a";
  8         16  
  8         448  
13              
14 8     8   44 use constant DEBUG => 0;
  8         16  
  8         12544  
15              
16             sub new {
17 2     2 0 365923 my ($class, %opts) = @_;
18              
19 2         71 my @missing = grep { !$opts{$_} } qw( socket mechanism );
  4         81  
20 2 50       66 die "Need: @missing" if @missing;
21              
22 2         89 $opts{"_$_"} = delete $opts{$_} for keys %opts;
23              
24 2         248 $opts{'_can_pass_unix_fd'} = Socket::MsgHdr->can('new');
25 2   33     76 $opts{'_can_pass_unix_fd'} &&= Socket->can('SCM_RIGHTS');
26 2   33     43 $opts{'_can_pass_unix_fd'} &&= _is_unix_socket($opts{'_socket'});
27              
28 2         87 $opts{'_io'} = IO::Framed->new( $opts{'_socket'} )->enable_write_queue();
29              
30 2         463 my $self = bless \%opts, $class;
31              
32 2 50       37 $self->_set_mechanism( $opts{'_mechanism'} ) or do {
33 0         0 die "“$opts{'_mechanism'}” is not a valid authn mechanism.";
34             };
35              
36 2         17 return $self;
37             }
38              
39             sub _set_mechanism {
40 2     2   13 my ($self, $mechanism) = @_;
41              
42 2 50       36 if (!ref $mechanism) {
43 2         34 my $module = __PACKAGE__ . "::Mechanism::$mechanism";
44              
45 2         25 my $err = $@;
46 2 50       18 if (!eval { Module::Load::load($module); 1 } ) {
  2         35  
  2         580  
47 0         0 return 0;
48             }
49 2         10 $@ = $err;
50              
51 2         85 $self->{'_mechanism'} = $module->new();
52             }
53              
54 2         42 $self->{'_xaction'} = $self->_create_xaction();
55              
56 2         20 return 1;
57             }
58              
59             sub negotiated_unix_fd {
60 0 0   0 0 0 return $_[0]->{'_negotiated_unix_fd'} ? 1 : 0;
61             }
62              
63             # Whether a send is pending (1) or a receive (0).
64             sub pending_send {
65 0     0 0 0 my ($self) = @_;
66              
67 0         0 my $next_is_receive = $self->{'_xaction'}[0];
68 0   0     0 $next_is_receive &&= $next_is_receive->[0];
69              
70 0 0       0 if (!defined $next_is_receive) {
71 0         0 die "Authn transaction is done!";
72             }
73              
74 0         0 return !$next_is_receive;
75             }
76              
77             sub go {
78 2     2 0 17 my ($self) = @_;
79              
80 2         7 my $s = $self->{'_socket'};
81              
82             # Don’t send_initial() if !must_send_initial().
83 2   33     56 $self->{'_sent_initial'} ||= !$self->{'_mechanism'}->must_send_initial() || $self->{'_mechanism'}->send_initial($s);
      33        
84              
85 2 50       7 if ($self->{'_sent_initial'}) {
86             LINES:
87             {
88 2 50       12 if ( $self->{'_io'}->get_write_queue_count() ) {
  2         36  
89 0 0       0 $self->flush_write_queue() or last LINES;
90             }
91              
92 2         26 my $dollar_at = $@;
93 2         5 my $ok = eval {
94 2         11 while ( my $cur = $self->{'_xaction'}[0] ) {
95 8 100       37 if ($cur->[0]) {
96 3 50       21 my $line = $self->_read_line() or last LINES;
97 3         25 $cur->[1]->($self, $line);
98             }
99             else {
100 5         12 my @line_parts;
101              
102 5 100       17 if ('CODE' eq ref $cur->[1]) {
103 1         4 @line_parts = $cur->[1]->($self);
104             }
105             else {
106 4         14 @line_parts = @{$cur}[ 1 .. $#$cur ];
  4         15  
107             }
108              
109 5 50       49 $self->_send_line("@line_parts") or last LINES;
110              
111 5         3622 push @{ $self->{'_tried_mechanism'} }, $self->{'_mechanism'}->label();
  5         28  
112             }
113              
114 8         17 shift @{ $self->{'_xaction'} };
  8         46  
115             }
116              
117 2         7 1;
118             };
119              
120 2 50       10 if (!$ok) {
121 0         0 my $err = $@;
122 0 0       0 if (eval { $err->isa('Protocol::DBus::X::Rejected') }) {
  0         0  
123 0         0 my @to_try;
124              
125 0         0 for my $mech ( @{ $err->get('mechanisms') } ) {
  0         0  
126 0 0       0 if (!grep { $_ eq $mech } @{ $self->{'_tried_mechanism'} }) {
  0         0  
  0         0  
127 0         0 push @to_try, $mech;
128             }
129             }
130              
131 0         0 while (my $mech = shift @to_try) {
132 0 0       0 if ($self->_set_mechanism($mech)) {
133 0         0 redo LINES;
134             }
135             }
136              
137 0         0 die "Exhausted all authentication mechanisms! (@{ $self->{'_tried_mechanism'} })";
  0         0  
138             }
139             else {
140 0         0 local $@ = $err;
141 0         0 die;
142             }
143             }
144              
145 2         64 return 1;
146             }
147             }
148              
149 0         0 return undef;
150             }
151              
152             sub cancel {
153 0     0 0 0 my ($self) = @_;
154              
155 0         0 die 'unimplemented';
156             }
157              
158             sub _create_xaction {
159 2     2   8 my ($self) = @_;
160              
161 2         18 my $auth_label = 'AUTH';
162              
163             # Unless the mechanism sends its own initial NUL, might as well use the
164             # same system call to send the initial NUL as we use to send the AUTH.
165 2 50 33     71 if (!$self->{'_sent_initial'} && !$self->{'_mechanism'}->must_send_initial()) {
166 2         9 substr( $auth_label, 0, 0 ) = "\0";
167             }
168              
169             # 0 = send; 1 = receive
170             my @xaction = (
171             [ 0 => $auth_label, $self->{'_mechanism'}->label(), $self->{'_mechanism'}->INITIAL_RESPONSE() ],
172              
173             # e.g., for exchange of DATA
174 2         48 $self->{'_mechanism'}->AFTER_AUTH(),
175              
176             [ 1 => \&_consume_ok ],
177             );
178              
179 2 50       12 if ($self->{'_can_pass_unix_fd'}) {
180 0         0 push @xaction, (
181             [ 0 => 'NEGOTIATE_UNIX_FD' ],
182             [ 1 => \&_consume_agree_unix_fd ],
183             );
184             }
185              
186 2         22 push @xaction, [ 0 => 'BEGIN' ];
187              
188 2         41 return \@xaction;
189             }
190              
191             sub _consume_agree_unix_fd {
192 0     0   0 my ($self, $line) = @_;
193              
194 0 0       0 if ($line eq 'AGREE_UNIX_FD') {
    0          
195 0         0 $self->{'_negotiated_unix_fd'} = 1;
196             }
197             elsif (index($line, 'ERROR ') == 0) {
198 0         0 warn "Server rejected unix fd passing: " . substr($line, 6) . $/;
199             }
200              
201 0         0 return;
202             }
203              
204             sub _consume_ok {
205 2     2   24 my ($self, $line) = @_;
206              
207 2 50       13 if (index($line, 'OK ') == 0) {
208 2         25 $self->{'_server_guid'} = substr($line, 3);
209             }
210             else {
211 0         0 die "Unrecognized response: $line";
212             }
213              
214 2         17 return;
215             }
216              
217             sub _send_line {
218 5     5   21 my ($self) = @_;
219              
220 5         15 DEBUG() && print STDERR "AUTHN SENDING: [$_[1]]$/";
221              
222 5         72 $self->{'_io'}->write( $_[1] . _CRLF() );
223              
224 5         112 return $self->_flush_write_queue();
225             }
226              
227             sub _flush_write_queue {
228 5     5   15 my ($self) = @_;
229              
230 5         165 local $SIG{'PIPE'} = 'IGNORE';
231              
232 5         46 return $self->{'_io'}->flush_write_queue();
233             }
234              
235             sub _read_line {
236 3     3   8 my $line;
237              
238 3         6 DEBUG() && print STDERR "AUTHN RECEIVING …$/";
239              
240 3 50       55 if ($line = $_[0]->{'_io'}->read_until("\x0d\x0a")) {
241 3         18893 substr( $line, -2 ) = q<>;
242              
243 3         26 DEBUG() && print STDERR "AUTHN RECEIVED: [$line]$/";
244              
245 3 50       25 if (0 == index( $line, 'REJECTED ')) {
246 0         0 die Protocol::DBus::X->create(
247             'Rejected',
248             split( m< >, substr( $line, 9 ) ),
249             );
250             }
251             }
252              
253 3         20 return $line;
254             }
255              
256             sub _is_unix_socket {
257 0     0     my ($sk) = @_;
258              
259 0 0         my $sname = getsockname($sk) or die "getsockname(): $!";
260              
261 0           return Socket::sockaddr_family($sname) == Socket::AF_UNIX();
262             }
263              
264             1;