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   37 use strict;
  5         9  
  5         162  
4 5     5   26 use Carp qw( croak );
  5         9  
  5         317  
5 5     5   2458 use IO::Select;
  5         8879  
  5         260  
6 5     5   1045 use POSIX qw( :errno_h );
  5         15986  
  5         37  
7 5     5   12600 use Crypt::PRNG qw( random_bytes );
  5         17782  
  5         299  
8              
9 5     5   40 use Net::SSH::Perl;
  5         9  
  5         183  
10 5         86 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   2363 MAX_PACKET_SIZE );
  5         16  
20 5     5   2380 use Net::SSH::Perl::Buffer;
  5         12  
  5         165  
21              
22 5     5   37 use Scalar::Util qw(weaken);
  5         18  
  5         12395  
23              
24             sub new {
25 10     10 1 15 my $class = shift;
26 10         14 my $ssh = shift;
27 10         34 my $pack = bless { ssh => $ssh, @_ }, $class;
28 10         40 weaken $pack->{ssh};
29 10 100       26 unless ($pack->{data}) {
30 5 50       18 $pack->{data} = Net::SSH::Perl::Buffer->new(
31             MP => $ssh->protocol == PROTOCOL_SSH2 ? 'SSH2' : 'SSH1');
32 5 50       15 if ($pack->{type}) {
33 5         15 $pack->{data}->put_int8($pack->{type});
34             }
35             }
36 10         30 $pack;
37             }
38              
39             sub read {
40 5     5 1 736 my $class = shift;
41 5         9 my $ssh = shift;
42 5         25 my $sock = $ssh->sock;
43              
44 5         10 while (1) {
45 9 100       24 if (my $packet = $class->read_poll($ssh)) {
46 4         25 return $packet;
47             }
48 4         17 my $s = IO::Select->new( $sock );
49 4         242 my @ready = $s->can_read;
50 4         72 my $buf;
51 4         21 my $len = sysread $sock, $buf, 8192;
52 4 50       53 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         19 ($buf) = $buf =~ /(.*)/s;
61 4         13 $ssh->incoming_data->append($buf);
62             }
63             }
64              
65             sub read_poll {
66 9     9 1 17 my $class = shift;
67 9         11 my $ssh = shift;
68              
69 9         12 my($packet, $debug, $ignore);
70 9 50       21 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         23 ($debug, $ignore) = (SSH_MSG_DEBUG, SSH_MSG_IGNORE);
77             }
78 9 100       24 return unless $packet;
79              
80 5         19 my $type = $packet->type;
81 5 50       13 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       13 if ($type == SSH_MSG_DISCONNECT) {
89 1         7 croak "Received disconnect message: ", $packet->get_str, "\n";
90             }
91             }
92              
93 4 50       11 if ($type == $debug) {
    50          
94 0         0 $ssh->debug("Remote: " . $packet->get_str);
95             }
96             elsif ($type == $ignore) { }
97             else {
98 4         13 return $packet;
99             }
100 0         0 return $class->read_poll($ssh);
101             }
102              
103             sub read_poll_ssh1 {
104 9     9 0 16 my $class = shift;
105 9         10 my $ssh = shift;
106              
107 9 50       21 unless (defined &_crc32) {
108 0         0 eval "use Net::SSH::Perl::Util qw( _crc32 );";
109 0 0       0 die $@ if $@;
110             }
111              
112 9         33 my $incoming = $ssh->incoming_data;
113 9 100       25 return if $incoming->length < 4 + 8;
114              
115 5         12 my $len = unpack "N", $incoming->bytes(0, 4);
116 5 50       12 $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         22 $buffer->append($incoming->bytes(0, $pad_len+4, ''));
122              
123 5         17 $buffer->bytes(0, 4, "");
124              
125 5 50       17 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         11 my $crc = _crc32($buffer->bytes(0, -4));
132 5         18 $buffer->bytes(0, 8 - $len % 8, "");
133              
134 5         15 my $stored_crc = unpack "N", $buffer->bytes(-4, 4);
135 5 50       13 $ssh->fatal_disconnect("Corrupted check bytes on input")
136             unless $crc == $stored_crc;
137              
138 5         18 $buffer->bytes(-4, 4, ""); ## Cut off checksum.
139              
140 5 50       22 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         12 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 3 my $class = shift;
261 1         3 my($ssh, $type) = @_;
262 1         6 my $pack = $class->read($ssh);
263 1 50       3 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         4 $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       16 if ($pack->{ssh}->protocol == PROTOCOL_SSH2) {
280 0         0 $pack->send_ssh2(@_);
281             }
282             else {
283 5         13 $pack->send_ssh1(@_);
284             }
285             }
286              
287             sub send_ssh1 {
288 5     5 0 8 my $pack = shift;
289 5   33     34 my $buffer = shift || $pack->{data};
290 5         12 my $ssh = $pack->{ssh};
291              
292 5 100       15 unless (defined &_crc32) {
293 1     1   118 eval "use Net::SSH::Perl::Util qw( _crc32 );";
  1         7  
  1         12  
  1         8  
294             }
295              
296 5 50       19 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         12 my $cipher = $ssh->send_cipher;
311             #if ($cipher) {
312 5         14 $buffer->insert_padding;
313             #}
314              
315 5         12 my $crc = _crc32($buffer->bytes);
316 5         17 $buffer->put_int32($crc);
317              
318 5         15 my $output = Net::SSH::Perl::Buffer->new( MP => 'SSH1' );
319 5         16 $output->put_int32($len);
320 5 50       19 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         16 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 404 my $pack = shift;
381 11 50       22 $pack->{type} = shift if @_;
382 11         50 $pack->{type};
383             }
384              
385 0     0 1 0 sub data { $_[0]->{data} }
386              
387 5     5   43 use vars qw( $AUTOLOAD );
  5         9  
  5         895  
388             sub AUTOLOAD {
389 14     14   2091 my $pack = shift;
390 14         77 (my $meth = $AUTOLOAD) =~ s/.*://;
391 14 100       97 return if $meth eq "DESTROY";
392              
393 4 50       22 if ( $pack->{data}->can($meth) ) {
394 4         16 $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__