File Coverage

blib/lib/Net/SSH/Perl/Packet.pm
Criterion Covered Total %
statement 125 250 50.0
branch 36 142 25.3
condition 1 81 1.2
subroutine 20 23 86.9
pod 7 11 63.6
total 189 507 37.2


line stmt bran cond sub pod time code
1             package Net::SSH::Perl::Packet;
2              
3 5     5   34 use strict;
  5         9  
  5         158  
4 5     5   26 use Carp qw( croak );
  5         10  
  5         321  
5 5     5   2444 use IO::Select;
  5         8864  
  5         250  
6 5     5   1067 use POSIX qw( :errno_h );
  5         16236  
  5         35  
7 5     5   12035 use Crypt::PRNG qw( random_bytes );
  5         17648  
  5         321  
8              
9 5     5   37 use Net::SSH::Perl;
  5         10  
  5         169  
10 5         35 use Net::SSH::Perl::Constants qw(
11             :protocol
12             SSH_MSG_DISCONNECT
13             SSH_MSG_DEBUG
14             SSH_MSG_IGNORE
15             SSH2_MSG_GLOBAL_REQUEST
16             SSH2_MSG_DISCONNECT
17             SSH2_MSG_DEBUG
18             SSH2_MSG_IGNORE
19 5     5   2434 MAX_PACKET_SIZE );
  5         15  
20 5     5   2346 use Net::SSH::Perl::Buffer;
  5         13  
  5         160  
21              
22 5     5   34 use Scalar::Util qw(weaken);
  5         9  
  5         12343  
23              
24             sub new {
25 10     10 1 19 my $class = shift;
26 10         15 my $ssh = shift;
27 10         32 my $pack = bless { ssh => $ssh, @_ }, $class;
28 10         38 weaken $pack->{ssh};
29 10 100       25 unless ($pack->{data}) {
30 5 50       15 $pack->{data} = Net::SSH::Perl::Buffer->new(
31             MP => $ssh->protocol == PROTOCOL_SSH2 ? 'SSH2' : 'SSH1');
32 5 50       15 if ($pack->{type}) {
33 5         17 $pack->{data}->put_int8($pack->{type});
34             }
35             }
36 10         29 $pack;
37             }
38              
39             sub read {
40 5     5 1 732 my $class = shift;
41 5         7 my $ssh = shift;
42 5         13 my $sock = $ssh->sock;
43              
44 5         9 while (1) {
45 9 100       22 if (my $packet = $class->read_poll($ssh)) {
46 4         16 return $packet;
47             }
48 4         16 my $s = IO::Select->new( $sock );
49 4         234 my @ready = $s->can_read;
50 4         71 my $buf;
51 4         21 my $len = sysread $sock, $buf, 8192;
52 4 50       50 croak "Connection closed by remote host." if $len == 0;
53 4 50       11 if (!defined $len) {
54 0 0 0     0 next if $! == EAGAIN || $! == EWOULDBLOCK;
55 0         0 croak "Read from socket failed: $!";
56             }
57              
58             ## Untaint data read from sshd. This is binary data,
59             ## so there's nothing to taint-check against/for.
60 4         21 ($buf) = $buf =~ /(.*)/s;
61 4         13 $ssh->incoming_data->append($buf);
62             }
63             }
64              
65             sub read_poll {
66 9     9 1 15 my $class = shift;
67 9         12 my $ssh = shift;
68              
69 9         13 my($packet, $debug, $ignore);
70 9 50       20 if ($ssh->protocol == PROTOCOL_SSH2) {
71 0         0 $packet = $class->read_poll_ssh2($ssh);
72 0         0 ($debug, $ignore) = (SSH2_MSG_DEBUG, SSH2_MSG_IGNORE);
73             }
74             else {
75 9         19 $packet = $class->read_poll_ssh1($ssh);
76 9         24 ($debug, $ignore) = (SSH_MSG_DEBUG, SSH_MSG_IGNORE);
77             }
78 9 100       24 return unless $packet;
79              
80 5         11 my $type = $packet->type;
81 5 50       14 if ($ssh->protocol == PROTOCOL_SSH2) { ## Handle DISCONNECT msg
82 0 0       0 if ($type == SSH2_MSG_DISCONNECT) {
83 0         0 $packet->get_int32; ## reason
84 0         0 croak "Received disconnect message: ", $packet->get_str, "\n";
85             }
86             }
87             else {
88 5 100       10 if ($type == SSH_MSG_DISCONNECT) {
89 1         5 croak "Received disconnect message: ", $packet->get_str, "\n";
90             }
91             }
92              
93 4 50       14 if ($type == $debug) {
    50          
94 0         0 $ssh->debug("Remote: " . $packet->get_str);
95             }
96             elsif ($type == $ignore) { }
97             else {
98 4         12 return $packet;
99             }
100 0         0 return $class->read_poll($ssh);
101             }
102              
103             sub read_poll_ssh1 {
104 9     9 0 14 my $class = shift;
105 9         12 my $ssh = shift;
106              
107 9 50       22 unless (defined &_crc32) {
108 0         0 eval "use Net::SSH::Perl::Util qw( _crc32 );";
109 0 0       0 die $@ if $@;
110             }
111              
112 9         34 my $incoming = $ssh->incoming_data;
113 9 100       21 return if $incoming->length < 4 + 8;
114              
115 5         16 my $len = unpack "N", $incoming->bytes(0, 4);
116 5 50       14 $len = 0 unless defined $len;
117 5         11 my $pad_len = ($len + 8) & ~7;
118 5 50       11 return if $incoming->length < 4 + $pad_len;
119              
120 5         12 my $buffer = Net::SSH::Perl::Buffer->new( MP => 'SSH1' );
121 5         16 $buffer->append($incoming->bytes(0, $pad_len+4, ''));
122              
123 5         19 $buffer->bytes(0, 4, "");
124              
125 5 50       19 if (my $cipher = $ssh->receive_cipher) {
126 0         0 my $decrypted = $cipher->decrypt($buffer->bytes);
127 0         0 $buffer->empty;
128 0         0 $buffer->append($decrypted);
129             }
130              
131 5         12 my $crc = _crc32($buffer->bytes(0, -4));
132 5         20 $buffer->bytes(0, 8 - $len % 8, "");
133              
134 5         12 my $stored_crc = unpack "N", $buffer->bytes(-4, 4);
135 5 50       14 $ssh->fatal_disconnect("Corrupted check bytes on input")
136             unless $crc == $stored_crc;
137              
138 5         23 $buffer->bytes(-4, 4, ""); ## Cut off checksum.
139              
140 5 50       13 if (my $comp = $ssh->compression) {
141 0         0 my $inflated = $comp->uncompress($buffer->bytes);
142 0         0 $buffer->empty;
143 0         0 $buffer->append($inflated);
144             }
145              
146 5         13 my $type = unpack "C", $buffer->bytes(0, 1, "");
147 5         19 $class->new($ssh,
148             type => $type,
149             data => $buffer);
150             }
151              
152             sub read_poll_ssh2 {
153 0     0 0 0 my $class = shift;
154 0         0 my $ssh = shift;
155 0         0 my $kex = $ssh->kex;
156              
157 0         0 my($ciph, $mac, $comp);
158 0         0 my $authlen = 0;
159 0 0       0 if ($kex) {
160 0         0 $ciph = $kex->receive_cipher;
161 0         0 $mac = $kex->receive_mac;
162 0         0 $comp = $kex->receive_comp;
163 0 0 0     0 undef $mac if $authlen = $ciph && $ciph->authlen;
164             }
165 0 0 0     0 my $maclen = $mac && $mac->enabled ? $mac->len : 0;
166 0 0 0     0 my $block_size = $ciph && $ciph->enabled ? $ciph->blocksize : 8;
167 0 0 0     0 my $aadlen = ($mac && $mac->enabled && $mac->etm) || $authlen ? 4 : 0;
168 0         0 my $seqnr = $ssh->{session}{seqnr_in};
169              
170 0         0 my $incoming = $ssh->incoming_data;
171 0 0       0 if (!$ssh->{session}{_last_packet_length}) {
172 0 0       0 return if $incoming->length < $block_size;
173 0         0 my $b = Net::SSH::Perl::Buffer->new( MP => 'SSH2' );
174              
175 0 0       0 if ($authlen) {
    0          
176 0         0 $b->append($ciph->get_length($incoming->bytes(0,$block_size),
177             $seqnr));
178             } elsif ($aadlen) {
179 0         0 $b->append($incoming->bytes(0, $block_size));
180             } else {
181 0 0 0     0 $b->append( $ciph && $ciph->enabled ?
182             $ciph->decrypt($incoming->bytes(0, $block_size)) :
183             $incoming->bytes(0, $block_size)
184             );
185             # replace first block of incoming buffer with decrypted contents
186 0         0 $incoming->bytes(0, $block_size, $b->bytes);
187             }
188 0         0 my $plen = $ssh->{session}{_last_packet_length} = $b->get_int32;
189 0 0 0     0 if ($plen < 1 + 4 || $plen > 256 * 1024) {
190 0         0 $ssh->fatal_disconnect("Bad packet length $plen");
191             }
192             }
193             my $need = $aadlen ? $ssh->{session}{_last_packet_length} :
194 0 0       0 4 + $ssh->{session}{_last_packet_length} - $block_size;
195 0 0       0 croak "padding error: need $need block $block_size"
196             if $need % $block_size;
197 0 0 0     0 return if ( $aadlen && ($incoming->length < $aadlen + $need + $maclen + $authlen)) ||
      0        
      0        
198             (!$aadlen && ($incoming->length < $need + $block_size + $maclen));
199              
200 0         0 my $buffer = Net::SSH::Perl::Buffer->new( MP => 'SSH2' );
201 0         0 my ($macbuf,$p_str);
202 0 0 0     0 if ($mac && $mac->enabled) {
    0          
203 0 0       0 if ($mac->etm) {
204 0         0 $p_str = $incoming->bytes(0, $aadlen + $need, '');
205 0         0 $macbuf = $mac->hmac(pack("N", $seqnr) . $p_str);
206             # remove packet length bytes
207 0         0 substr($p_str,0,$aadlen,'');
208             } else {
209             # not etm mac, append bytes already decrypted above
210 0         0 $buffer->append( $incoming->bytes(0, $block_size, '') );
211 0         0 $p_str = $incoming->bytes(0, $need, '');
212             }
213             } elsif ($authlen) {
214             # cipher with auth
215 0         0 $p_str = $incoming->bytes(0, $aadlen + $need + $authlen, '');
216             } else {
217 0         0 $buffer->append( $incoming->bytes(0, $block_size, '') );
218 0         0 $p_str = $incoming->bytes(0, $need, '');
219             }
220              
221 0 0 0     0 if ($mac && $mac->enabled) {
222 0 0       0 unless ($mac->etm) {
223 0 0 0     0 $buffer->append( $ciph && $ciph->enabled ?
224             $ciph->decrypt($p_str) : $p_str );
225 0         0 $macbuf = $mac->hmac(pack("N", $seqnr) . $buffer->bytes);
226             }
227 0         0 my $stored_mac = $incoming->bytes(0, $maclen, '');
228 0 0       0 $ssh->fatal_disconnect("Corrupted MAC on input")
229             unless $macbuf eq $stored_mac;
230             # with etm macs, do not decrypt until after mac verified
231 0 0 0     0 $buffer->append( $ciph && $ciph->enabled ?
    0          
232             $ciph->decrypt($p_str) : $p_str ) if $mac->etm;
233             } else {
234 0 0 0     0 $buffer->append( $ciph && $ciph->enabled ?
235             $ciph->decrypt($p_str,$seqnr,$aadlen) : $p_str );
236             }
237              
238 0         0 $ssh->{session}{seqnr_in}++;
239              
240 0         0 my $padlen = unpack "C", $buffer->bytes(4-$aadlen, 1);
241 0 0       0 $ssh->fatal_disconnect("Corrupted padlen $padlen on input")
242             unless $padlen >= 4;
243              
244             # Cut off packet size + padlen, discard padding
245 0         0 $buffer->bytes(0, 5-$aadlen, '');
246 0         0 $buffer->bytes(-$padlen, $padlen, '');
247              
248 0 0 0     0 if ($comp && $comp->enabled) {
249 0         0 my $inflated = $comp->uncompress($buffer->bytes);
250 0         0 $buffer->empty;
251 0         0 $buffer->append($inflated);
252             }
253              
254 0         0 my $type = unpack "C", $buffer->bytes(0, 1, '');
255 0         0 $ssh->{session}{_last_packet_length} = 0;
256 0         0 $class->new($ssh, type => $type, data => $buffer);
257             }
258              
259             sub read_expect {
260 1     1 1 2 my $class = shift;
261 1         4 my($ssh, $type) = @_;
262 1         3 my $pack = $class->read($ssh);
263 1 50       4 if ($pack->type != $type) {
264 1 50       3 if ($pack->type == SSH2_MSG_GLOBAL_REQUEST) {
265 0         0 $ssh->debug("Unprocessed global request encountered");
266             # now repeat read_expect for expected type
267 0         0 return $class->read_expect($ssh,$type);
268             } else {
269 1         3 $ssh->fatal_disconnect(sprintf
270             "Protocol error: expected packet type %d, got %d",
271             $type, $pack->type);
272             }
273             }
274 0         0 $pack;
275             }
276              
277             sub send {
278 5     5 1 10 my $pack = shift;
279 5 50       17 if ($pack->{ssh}->protocol == PROTOCOL_SSH2) {
280 0         0 $pack->send_ssh2(@_);
281             }
282             else {
283 5         14 $pack->send_ssh1(@_);
284             }
285             }
286              
287             sub send_ssh1 {
288 5     5 0 7 my $pack = shift;
289 5   33     37 my $buffer = shift || $pack->{data};
290 5         12 my $ssh = $pack->{ssh};
291              
292 5 100       12 unless (defined &_crc32) {
293 1     1   78 eval "use Net::SSH::Perl::Util qw( _crc32 );";
  1         17  
  1         3  
  1         7  
294             }
295              
296 5 50       14 if ($buffer->length >= MAX_PACKET_SIZE - 30) {
297 0         0 $ssh->fatal_disconnect(sprintf
298             "Sending too big a packet: size %d, limit %d",
299             $buffer->length, MAX_PACKET_SIZE);
300             }
301              
302 5 50       13 if (my $comp = $ssh->compression) {
303 0         0 my $compressed = $comp->compress($buffer->bytes);
304 0         0 $buffer->empty;
305 0         0 $buffer->append($compressed);
306             }
307              
308 5         11 my $len = $buffer->length + 4;
309              
310 5         11 my $cipher = $ssh->send_cipher;
311             #if ($cipher) {
312 5         11 $buffer->insert_padding;
313             #}
314              
315 5         51 my $crc = _crc32($buffer->bytes);
316 5         17 $buffer->put_int32($crc);
317              
318 5         18 my $output = Net::SSH::Perl::Buffer->new( MP => 'SSH1' );
319 5         15 $output->put_int32($len);
320 5 50       15 my $data = $cipher ? $cipher->encrypt($buffer->bytes) : $buffer->bytes;
321 5         17 $output->put_chars($data);
322              
323 5         19 my $sock = $ssh->sock;
324 5         17 syswrite $sock, $output->bytes, $output->length;
325             }
326              
327             sub send_ssh2 {
328 0     0 0 0 my $pack = shift;
329 0   0     0 my $buffer = shift || $pack->{data};
330 0         0 my $ssh = $pack->{ssh};
331              
332 0         0 my $kex = $ssh->kex;
333 0         0 my($ciph, $mac, $comp, $authlen);
334 0 0       0 if ($kex) {
335 0         0 $ciph = $kex->send_cipher;
336 0         0 $mac = $kex->send_mac;
337 0         0 $comp = $kex->send_comp;
338 0 0 0     0 undef $mac if $authlen = $ciph && $ciph->authlen;
339             }
340 0 0 0     0 my $block_size = $ciph && $ciph->enabled ? $ciph->blocksize : 8;
341 0 0 0     0 my $aadlen = ($mac && $mac->enabled && $mac->etm) || $authlen ? 4 : 0;
342 0         0 my $seqnr = $ssh->{session}{seqnr_out};
343              
344 0 0 0     0 if ($comp && $comp->enabled) {
345 0         0 my $compressed = $comp->compress($buffer->bytes);
346 0         0 $buffer->empty;
347 0         0 $buffer->append($compressed);
348             }
349              
350 0         0 my $len = $buffer->length + 4 + 1;
351 0         0 my $padlen = $block_size - (($len - $aadlen) % $block_size);
352 0 0       0 $padlen += $block_size if $padlen < 4;
353 0 0       0 my $junk = $ciph ? random_bytes($padlen) : ("\0" x $padlen);
354 0         0 $buffer->append($junk);
355              
356 0         0 my $packet_len = $buffer->length + 1;
357 0         0 $buffer->bytes(0, 0, pack("C", $padlen));
358 0 0 0     0 $buffer->bytes(0, 0, pack("N", $packet_len)) unless $mac && $mac->etm;
359              
360 0 0 0     0 my $out = $ciph && $ciph->enabled ? $ciph->encrypt($buffer->bytes,$seqnr,$aadlen) : $buffer->bytes;
361 0 0 0     0 substr($out,0,0,pack("N", $packet_len)) if $mac && $mac->etm;
362              
363 0         0 my($macbuf);
364 0 0 0     0 if ($mac && $mac->enabled) {
365 0 0       0 my $data = $mac->etm ? $out : $buffer->bytes;
366 0         0 $macbuf = $mac->hmac(pack("N", $seqnr) . $data);
367             }
368              
369 0         0 my $output = Net::SSH::Perl::Buffer->new( MP => 'SSH2' );
370 0         0 $output->append($out);
371 0 0 0     0 $output->append($macbuf) if $mac && $mac->enabled;
372              
373 0         0 $ssh->{session}{seqnr_out}++;
374              
375 0         0 my $sock = $ssh->sock;
376 0         0 syswrite $sock, $output->bytes, $output->length;
377             }
378              
379             sub type {
380 11     11 1 443 my $pack = shift;
381 11 50       21 $pack->{type} = shift if @_;
382 11         41 $pack->{type};
383             }
384              
385 0     0 1 0 sub data { $_[0]->{data} }
386              
387 5     5   44 use vars qw( $AUTOLOAD );
  5         9  
  5         907  
388             sub AUTOLOAD {
389 14     14   2016 my $pack = shift;
390 14         78 (my $meth = $AUTOLOAD) =~ s/.*://;
391 14 100       88 return if $meth eq "DESTROY";
392              
393 4 50       22 if ( $pack->{data}->can($meth) ) {
394 4         15 $pack->{data}->$meth(@_);
395             }
396             else {
397 0         0 croak "Can't dispatch method $meth to Net::SSH::Perl::Buffer object.";
398             }
399             }
400              
401             1;
402             __END__