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   1886 use strict;
  11         22  
  11         259  
4 11     11   45 use warnings;
  11         27  
  11         244  
5              
6 11     11   4086 use IO::Framed ();
  11         32639  
  11         181  
7 11     11   60 use Module::Runtime ();
  11         22  
  11         115  
8 11     11   44 use Socket ();
  11         17  
  11         105  
9              
10 11     11   1673 use Protocol::DBus::X ();
  11         27  
  11         239  
11              
12 11     11   49 use constant _CRLF => "\x0d\x0a";
  11         16  
  11         511  
13              
14 11     11   50 use constant DEBUG => 0;
  11         17  
  11         14619  
15              
16             sub new {
17 5     5 0 523845 my ($class, %opts) = @_;
18              
19 5         102 my @missing = grep { !$opts{$_} } qw( socket mechanism );
  10         91  
20 5 50       151 die "Need: @missing" if @missing;
21              
22 5         116 $opts{"_$_"} = delete $opts{$_} for keys %opts;
23              
24 5         312 $opts{'_can_pass_unix_fd'} = Socket::MsgHdr->can('new');
25 5   66     174 $opts{'_can_pass_unix_fd'} &&= Socket->can('SCM_RIGHTS');
26 5   66     101 $opts{'_can_pass_unix_fd'} &&= _is_unix_socket($opts{'_socket'});
27              
28 5         162 $opts{'_io'} = IO::Framed->new( $opts{'_socket'} )->enable_write_queue();
29              
30 5         796 my $self = bless \%opts, $class;
31              
32 5 50       69 $self->_set_mechanism( $opts{'_mechanism'} ) or do {
33 0         0 die "“$opts{'_mechanism'}” is not a valid authn mechanism.";
34             };
35              
36 5         33 return $self;
37             }
38              
39             sub _set_mechanism {
40 5     5   22 my ($self, $mechanism) = @_;
41              
42 5 50       33 if (!ref $mechanism) {
43 5         28 my $module = __PACKAGE__ . "::Mechanism::$mechanism";
44              
45 5         12 my $err = $@;
46 5 50       25 if (!eval { Module::Runtime::require_module($module); 1 } ) {
  5         93  
  5         582  
47 0         0 DEBUG && print STDERR "Failed to load $mechanism authn module: $@";
48 0         0 return 0;
49             }
50 5         24 $@ = $err;
51              
52 5         124 $self->{'_mechanism'} = $module->new();
53             }
54              
55 5         67 $self->{'_xaction'} = $self->_create_xaction();
56              
57 5         40 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 20 my ($self) = @_;
80              
81 4         11 my $s = $self->{'_socket'};
82              
83             # Don’t send_initial() if !must_send_initial().
84 4   33     94 $self->{'_sent_initial'} ||= !$self->{'_mechanism'}->must_send_initial() || $self->{'_mechanism'}->send_initial($s);
      33        
85              
86 4 50       14 if ($self->{'_sent_initial'}) {
87             LINES:
88             {
89 4 50       31 if ( $self->{'_io'}->get_write_queue_count() ) {
  4         80  
90 0 0       0 $self->flush_write_queue() or last LINES;
91             }
92              
93 4         47 my $last_lines;
94              
95 4         17 my $dollar_at = $@;
96 4         10 my $ok = eval {
97 4         32 while ( my $cur = $self->{'_xaction'}[0] ) {
98 20 100       47 if ($cur->[0]) {
99 8 50       55 my $line = $self->_read_line() or do {
100 0         0 $last_lines = 1;
101 0         0 last;
102             };
103              
104 8         36 $cur->[1]->($self, $line);
105             }
106             else {
107 12         30 my @line_parts;
108              
109 12 100       40 if ('CODE' eq ref $cur->[1]) {
110 2         6 @line_parts = $cur->[1]->($self);
111             }
112             else {
113 10         28 @line_parts = @{$cur}[ 1 .. $#$cur ];
  10         28  
114             }
115              
116 12 50       73 $self->_send_line("@line_parts") or last LINES;
117              
118 12         5144 push @{ $self->{'_tried_mechanism'} }, $self->{'_mechanism'}->label();
  12         76  
119             }
120              
121 20         28 shift @{ $self->{'_xaction'} };
  20         81  
122             }
123              
124 4         99 1;
125             };
126              
127 4 50       48 last LINES if $last_lines;
128              
129 4 50       14 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         91 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   17 my ($self) = @_;
172              
173 5         47 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     174 if (!$self->{'_sent_initial'} && !$self->{'_mechanism'}->must_send_initial()) {
178 5         21 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         104 $self->{'_mechanism'}->AFTER_AUTH(),
187              
188             [ 1 => \&_consume_ok ],
189             );
190              
191 5 100       53 if ($self->{'_can_pass_unix_fd'}) {
192 2         20 push @xaction, (
193             [ 0 => 'NEGOTIATE_UNIX_FD' ],
194             [ 1 => \&_consume_agree_unix_fd ],
195             );
196             }
197              
198 5         46 push @xaction, [ 0 => 'BEGIN' ];
199              
200 5         66 return \@xaction;
201             }
202              
203             sub _consume_agree_unix_fd {
204 2     2   26 my ($self, $line) = @_;
205              
206 2 50       13 if ($line eq 'AGREE_UNIX_FD') {
    0          
207 2         14 $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   207 my ($self, $line) = @_;
218              
219 4 50       62 if (index($line, 'OK ') == 0) {
220 4         50 $self->{'_server_guid'} = substr($line, 3);
221             }
222             else {
223 0         0 die "Unrecognized response: $line";
224             }
225              
226 4         13 return;
227             }
228              
229             sub _send_line {
230 12     12   38 my ($self) = @_;
231              
232 12         22 DEBUG() && print STDERR "AUTHN SENDING: [$_[1]]$/";
233              
234 12         117 $self->{'_io'}->write( $_[1] . _CRLF() );
235              
236 12         213 return $self->_flush_write_queue();
237             }
238              
239             sub _flush_write_queue {
240 12     12   20 my ($self) = @_;
241              
242 12         343 local $SIG{'PIPE'} = 'IGNORE';
243              
244 12         103 return $self->{'_io'}->flush_write_queue();
245             }
246              
247             sub _read_line {
248 8     8   26 my $line;
249              
250 8         14 DEBUG() && print STDERR "AUTHN RECEIVING …$/";
251              
252 8 50       109 if ($line = $_[0]->{'_io'}->read_until("\x0d\x0a")) {
253 8         38180 substr( $line, -2 ) = q<>;
254              
255 8         15 DEBUG() && print STDERR "AUTHN RECEIVED: [$line]$/";
256              
257 8 50       27 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         30 return $line;
266             }
267              
268             sub _is_unix_socket {
269 2     2   12 my ($sk) = @_;
270              
271 2 50       45 my $sname = getsockname($sk) or die "getsockname(): $!";
272              
273 2         43 return Socket::sockaddr_family($sname) == Socket::AF_UNIX();
274             }
275              
276             #sub DESTROY {
277             # print "DESTROYED: [$_[0]]\n";
278             #}
279              
280             1;