File Coverage

blib/lib/Net/Telnet/Netgear/Packet/Native.pm
Criterion Covered Total %
statement 42 42 100.0
branch 9 10 90.0
condition 8 9 88.8
subroutine 10 10 100.0
pod 2 2 100.0
total 71 73 97.2


line stmt bran cond sub pod time code
1             package Net::Telnet::Netgear::Packet::Native;
2 3     3   1182 use strict;
  3         4  
  3         107  
3 3     3   11 use warnings;
  3         3  
  3         76  
4 3     3   11 use parent "Net::Telnet::Netgear::Packet";
  3         3  
  3         14  
5 3     3   146 use Carp;
  3         5  
  3         166  
6 3     3   1452 use Crypt::ECB;
  3         5261  
  3         144  
7 3     3   15 use Digest::MD5;
  3         4  
  3         1056  
8            
9             our @CARP_NOT = qw ( Net::Telnet::Netgear::Packet );
10            
11             sub new
12             {
13 11     11 1 31 my ($self, %opts) = @_;
14 11 100       145 Carp::croak "Missing required parameter 'mac'"
15             unless exists $opts{mac};
16 10         45 $opts{mac} =~ s/\W//g;
17             # WARNING: "Gearguy" and "Geardog" are no longer valid since the new firmwares of Netgear
18             # routers. You need to specify the username/password combination you use to login in the
19             # control panel. (default: admin/password)
20 10   100     37 $opts{username} ||= "Gearguy";
21 10   100     32 $opts{password} ||= "Geardog";
22 10 100 100     245 Carp::croak "The MAC address and the username have to be shorter than 16 characters"
23             unless length $opts{mac} < 16
24             and length $opts{username} < 16;
25             # Increase the maximum password length to 33 characters.
26             # See the discussion here: https://github.com/insanid/netgear-telenetenable/commit/445c972
27             # Long story short: the original packet structure specifies 5 fields: username, password, mac,
28             # signature, reserved. Each field excluding 'reserved' is 0x10 (16) characters. However,
29             # it looks like the length of the 'password' field can be increased up to 33 characters, which
30             # is the maximum length allowed for the password by the Netgear interface. The analysis of what
31             # happens is in the GitHub link written before.
32 8 100       116 Carp::croak "The password must have a maximum length of 33 characters."
33             unless length $opts{password} <= 33;
34 7         29 bless \%opts, $self;
35             }
36            
37             # Generates the packet. Here's some documentation:
38             # http://wiki.openwrt.org/toh/netgear/telnet.console
39             # Special thanks to insanid@github, he's a nice guy.
40             sub get_packet
41             {
42 6     6 1 15 my $self = shift;
43 6         18 my ($mac, $usr, $pwd) = (
44             _left_justify ($self->{mac}, 0x10, "\x00"),
45             _left_justify ($self->{username}, 0x10, "\x00"),
46             _left_justify ($self->{password}, 0x21, "\x00")
47             );
48 6         14 my $text = _left_justify ($mac . $usr . $pwd, 0x70, "\x00");
49 6         36 my $payload = _swap_bytes (
50             _left_justify (Digest::MD5::md5 ($text) . $text, 0x80, "\x00")
51             );
52 6         27 my $cipher = Crypt::ECB->new;
53 6         73 $cipher->padding (PADDING_NONE);
54 6 50       31 $cipher->cipher ("Blowfish")
55             || die "Blowfish not available: ", $cipher->errstring;
56 6         1819 $cipher->key ("AMBIT_TELNET_ENABLE+" . $self->{password});
57 6         26 _swap_bytes ($cipher->encrypt ($payload));
58             }
59            
60             # https://goo.gl/qYA1u5
61             # TL;DR: The Blowfish implementation of Netgear expects data in big endian
62             # order, but we are using the little endian order. This fixes the problem.
63             # Also, from perldoc -f pack:
64             # - V An unsigned long (32-bit) in "VAX" (little-endian) order.
65             # - N An unsigned long (32-bit) in "network" (big-endian) order.
66             sub _swap_bytes
67             {
68 12     12   1360 pack 'V*', unpack 'N*', shift;
69             }
70            
71             # See https://docs.python.org/2/library/string.html#string.ljust
72             # Left-justifies $str to $length characters, filling with $filler if necessary.
73             # Example:
74             # _left_justify 'meow', 6, 'A' -> 'meowAA'
75             sub _left_justify
76             {
77 30     30   32 my ($str, $length, $filler) = @_;
78 30   50     39 $filler ||= " ";
79 30         26 my $i = length $str;
80 30 100       48 return $str if $i >= $length;
81 22         324 $str .= $filler while ($i++ < $length);
82 22         40 $str;
83             }
84            
85             1;