File Coverage

blib/lib/Protocol/DBus/Authn.pm
Criterion Covered Total %
statement 108 145 74.4
branch 22 52 42.3
condition 7 18 38.8
subroutine 18 21 85.7
pod 0 5 0.0
total 155 241 64.3


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