File Coverage

blib/lib/Thrift/SASL/Transport.pm
Criterion Covered Total %
statement 18 109 16.5
branch 0 52 0.0
condition 0 22 0.0
subroutine 6 19 31.5
pod 0 9 0.0
total 24 211 11.3


line stmt bran cond sub pod time code
1             package Thrift::SASL::Transport;
2              
3 1     1   21549 use strict;
  1         3  
  1         26  
4 1     1   6 use warnings;
  1         1  
  1         28  
5 1     1   1035 use Data::Dumper;
  1         10460  
  1         78  
6              
7             our $VERSION = '0.005'; # VERSION
8              
9             # Nasty hack to make the Thrift libs handle the extra 4-bytes
10             # header put by GSSAPI in front of unencoded (auth only) replies
11              
12 1     1   840 use Thrift::BinaryProtocol;
  1         26450  
  1         45  
13             my $real_readMessageBegin = \&Thrift::BinaryProtocol::readMessageBegin;
14             {
15 1     1   8 no warnings 'redefine';
  1         2  
  1         140  
16             *Thrift::BinaryProtocol::readMessageBegin = \&BinaryProtocolOverride_readMessageBegin;
17             }
18              
19             sub BinaryProtocolOverride_readMessageBegin {
20 0     0 0   my $self = shift;
21 0 0 0       if ( $self->{trans}{_sasl_client} && !$self->{trans}{_sasl_encode} ) {
22 0           $self->readI32( \my $foo ); # discard GSSAPI auth header (message length)
23             }
24 0           return $real_readMessageBegin->( $self, @_ );
25             }
26              
27             # end of nasty hack, phew.
28              
29             use constant {
30 1         1501 SASL_START => 1,
31             SASL_OK => 2,
32             SASL_BAD => 3,
33             SASL_ERROR => 4,
34             SASL_COMPLETE => 5,
35 1     1   6 };
  1         1  
36              
37             sub new {
38 0     0 0   my ( $class, $transport, $sasl, $debug ) = @_;
39 0   0       return bless {
40             _transport => $transport,
41             _sasl => $sasl,
42             _debug => $debug || 0,
43             }, $class;
44             }
45              
46             sub _sasl_write {
47 0     0     my ( $self, $code, $payload ) = @_;
48 0   0       $payload //= '';
49 0           print STDERR "<< code $code + payload (@{[bytes::length($payload)]} bytes)\n"
50 0 0         if $self->{_debug};
51 0           $self->{_transport}->write( pack "CN", $code, bytes::length($payload) );
52 0           $self->{_transport}->write($payload);
53 0           $self->{_transport}->flush;
54             }
55              
56             sub _sasl_read {
57 0     0     my ($self) = @_;
58 0           my $data = $self->read(5);
59 0 0         die "No data from server" unless defined $data;
60 0           my ( $code, $length ) = unpack "CN", $data;
61 0 0         if ($length) {
62 0           $data = $self->read($length);
63             }
64             else {
65 0           $data = undef;
66             }
67             print STDERR ">> code $code + response ($length bytes)\n"
68 0 0         if $self->{_debug};
69 0           return ( $code, $data );
70             }
71              
72             sub open {
73 0     0 0   my ($self) = @_;
74 0 0 0       $self->{_transport}->open if !( $self->{_transport} && $self->isOpen );
75 0 0         die "Could not open transport" if !$self->isOpen;
76 0   0       return $self->{_sasl_client} || $self->_sasl_handshake;
77             }
78              
79             sub close {
80 0     0 0   my ($self) = @_;
81 0 0 0       $self->{_transport}->close if $self->{_transport} && $self->isOpen;
82 0           return 1;
83             }
84              
85             sub _sasl_handshake {
86 0     0     my ($self) = @_;
87              
88             print STDERR "SASL start: "
89 0 0         if $self->{_debug};
90 0           $self->_sasl_write( SASL_START, $self->{_sasl}->mechanism );
91              
92             # The socket passed to BufferedTransport was put in that object's
93             # "transport" property, this is a bit confusing imho
94 0           my $client = $self->{_sasl}->client_new( 'hive', $self->{_transport}{transport}{host} );
95 0           my $resp = $client->client_start();
96              
97 0           my $step;
98 0           while ( ++$step ) {
99             print STDERR "SASL step $step: "
100 0 0         if $self->{_debug};
101              
102             #print STDERR Dumper{map {$_ => [$client->$_()]} qw(error code mechanism need_step)};
103 0           $self->_sasl_write( SASL_OK, $resp );
104 0           my ( $code, $data ) = $self->_sasl_read();
105              
106 0 0         if ( $code == SASL_COMPLETE ) {
107             print STDERR "Authentication OK\n"
108 0 0         if $self->{_debug};
109              
110             #$client->client_step($data // '') if $client->need_step;
111 0           last;
112             }
113 0 0 0       if ( $code == SASL_BAD || $code == SASL_ERROR ) {
114 0           die "Authentication failed: $code > $data";
115             }
116 0           $resp = $client->client_step($data);
117 0 0         die "Client rejected authentication" unless defined $data;
118             }
119              
120 0           $self->{_sasl_encode_check} = 1;
121 0           $self->{_sasl_client} = $client;
122              
123 0           return $self->{_sasl_client};
124             }
125              
126             sub write {
127 0     0 0   my $self = shift;
128 0           my $buffer = shift;
129             print STDERR "<< writing " . bytes::length($buffer) . " bytes\n"
130 0 0         if $self->{_debug} > 1;
131 0           $self->{_out_buffer} .= $buffer;
132 0           return 1;
133             }
134              
135             sub read {
136 0     0 0   my $self = shift;
137 0           my @passthru_args = @_;
138             print STDERR ">> reading\n"
139 0 0         if $self->{_debug};
140 0           my $buf = $self->{_transport}->read(@passthru_args);
141 0 0         return $buf if bytes::length($buf) > 0;
142              
143             # not sure about this, it is copied over from the python version
144 0           $self->_read_frame();
145 0           return $self->{_transport}->read(@passthru_args);
146             }
147              
148             # completely unsure about this, taken from the python version when trying to
149             # make the whole thing work, turned out my problem was with the BinaryProtocol
150             # needing a 4-byte offset on readMessageBegin as kerberos auth adds a 4 bytes
151             # header to replies, which was mistakenly used as the expected thrift header.
152             # leaving this in place in case it is actually needed (probably not working
153             # in the current state though)
154             sub _read_frame {
155 0     0     my $self = shift;
156 0           my $header = $self->{_transport}->readAll(4);
157 0           my $length = unpack( "N", $header );
158 0           my $decoded;
159 0 0         if ( $self->{_sasl_encode} ) {
160 0           my $encoded = $header . $self->{_transport}->readAll($length);
161 0 0         my $decoded = $self->{_sasl_client}->decode($encoded)
162             or die 'SASL decode returned nothing';
163              
164             # TODO throw a real per TTransportException like the python version
165             #die "SASL decode error: " .
166             # raise TTransportException(type=TTransportException.UNKNOWN,
167             # message=self.sasl.getError())
168             }
169             else {
170 0           $decoded = $self->{_transport}->readAll($length);
171             }
172 0           $self->{_transport}{rBuf} = $decoded;
173             }
174              
175             sub flush {
176 0     0 0   my $self = shift;
177              
178             print STDERR "<<< flush " . bytes::length( $self->{_out_buffer} ) . " bytes \n"
179 0 0         if $self->{_debug};
180              
181 0 0         if ( $self->{_sasl_encode_check} ) {
182 0           my $encoded = $self->{_sasl_client}->encode( $self->{_out_buffer} );
183 0 0 0       if ( bytes::length($encoded)
184             && bytes::length($encoded) != bytes::length( $self->{_out_buffer} ) )
185             {
186 0           $self->{_sasl_encode} = 1;
187 0 0         print STDERR "GSSAPI Will encode from now on\n" if $self->{_debug};
188             }
189             else {
190 0           $self->{_sasl_encode} = 0;
191 0 0         print STDERR "GSSAPI Will *not* encode from now on\n" if $self->{_debug};
192             }
193 0           $self->{_sasl_encode_check} = undef;
194             }
195 0 0         if ( $self->{_sasl_encode} ) {
196 0           $self->{_out_buffer} = $self->{_sasl_client}->encode( $self->{_out_buffer} );
197             }
198             else {
199             $self->{_out_buffer}
200 0           = pack( "N", bytes::length( $self->{_out_buffer} ) ) . $self->{_out_buffer};
201             }
202 0           $self->{_transport}->write( $self->{_out_buffer} );
203 0           $self->{_transport}->flush();
204 0           $self->{_out_buffer} = '';
205              
206             #print STDERR Dumper $self;
207             }
208              
209             sub isOpen {
210 0     0 0   shift->{_transport}->isOpen(@_);
211             }
212              
213             sub readAll {
214 0     0 0   my $self = shift;
215             print STDERR ">>> readAll $_[0] bytes\n"
216 0 0         if $self->{_debug} > 1;
217 0           my $ret = $self->{_transport}->readAll(@_);
218 0           return $ret;
219             }
220              
221             1;
222              
223             #ABSTRACT: Thrift Transport allowing Kerberos auth/encryption through GSSAPI
224              
225             __END__