File Coverage

blib/lib/Protocol/DBus/Authn.pm
Criterion Covered Total %
statement 100 145 68.9
branch 19 52 36.5
condition 5 18 27.7
subroutine 16 21 76.1
pod 0 5 0.0
total 140 241 58.0


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