File Coverage

blib/lib/Net/SSH/Perl/Packet.pm
Criterion Covered Total %
statement 30 247 12.1
branch 0 142 0.0
condition 0 81 0.0
subroutine 10 22 45.4
pod 7 11 63.6
total 47 503 9.3


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