File Coverage

blib/lib/Protocol/DBus/Authn.pm
Criterion Covered Total %
statement 98 141 69.5
branch 18 50 36.0
condition 5 18 27.7
subroutine 16 21 76.1
pod 0 5 0.0
total 137 235 58.3


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