File Coverage

blib/lib/Device/Router/RTX.pm
Criterion Covered Total %
statement 15 202 7.4
branch 0 94 0.0
condition 0 3 0.0
subroutine 5 20 25.0
pod 8 8 100.0
total 28 327 8.5


line stmt bran cond sub pod time code
1             package Device::Router::RTX;
2             require Exporter;
3             @ISA = qw(Exporter);
4             @EXPORT_OK = qw//;
5 1     1   55950 use warnings;
  1         2  
  1         48  
6 1     1   6 use strict;
  1         3  
  1         60  
7             our $VERSION = 0.05;
8              
9 1     1   105 use Carp qw/carp croak/;
  1         7  
  1         112  
10             # DEPENDS
11 1     1   1673 use Net::Telnet;
  1         74391  
  1         50  
12 1     1   1004 use Net::TFTP;
  1         19431  
  1         4668  
13             # use Net::IP;
14             # END DEPENDS
15              
16             sub _read_config
17             {
18 0     0     my ($configfile, $args) = @_;
19 0 0         if (!-f $configfile) {
20 0           print <
21             I was told that there was a configuration file in '$configfile' but
22             I can't seem to find it.
23             EOF
24 0           return;
25             }
26 0 0         open my $input, "<", $configfile or die $!;
27 0           while (my $line = <$input>) {
28 0 0         next if $line =~ /^#/;
29 0 0         if ($line =~ /^(\w+):\s*(.*?)\s*$/) {
30 0           my $parameter = $1;
31 0           $parameter = lc $parameter;
32 0           my $value = $2;
33 0           $args->{$parameter} = $value;
34             } else {
35 0           die "$configfile:$.: parse error at '$line'";
36             }
37             }
38 0 0         close $input or die $!;
39             }
40              
41             sub new
42             {
43 0     0 1   my ($class, %args) = @_;
44 0           my $rtx = {};
45 0           for (keys %args) {
46 0           $_ = lc $_;
47             }
48 0 0         if ($args{config}) {
49 0           _read_config ($args{config}, \%args);
50             }
51 0 0         if (!$args{store}) {
    0          
52             # carp "No file store specified";
53             }
54             elsif (! -d $args{store}) {
55             # carp "File store '$args{store}' is not a valid directory";
56             }
57             else {
58 0           $rtx->{store} = $args{store};
59             }
60 0 0         if (!$args{address}) {
61 0           croak "No internet protocol address for router";
62             }
63             else {
64 0           $rtx->{address} = $args{address};
65             }
66 0 0         if (defined($args{password})) {
67 0           $rtx->{password} = $args{password};
68             }
69             else {
70 0           $rtx->{password} = "";
71             }
72 0 0         if (defined($args{admin_password})) {
73 0           $rtx->{admin_password} = $args{admin_password};
74             }
75             else {
76 0           $rtx->{admin_password} = "";
77             }
78 0           $rtx->{verbose} = $args{verbose};
79 0           bless $rtx;
80 0           return $rtx;
81             }
82              
83             sub _check
84             {
85 0     0     my ($rtx) = @_;
86 0 0 0       if (!$rtx || ref $rtx ne __PACKAGE__) {
87 0           die "Bad object passed";
88             }
89 0 0         if (!$rtx->{address}) {
90 0           die "No internet protocol address for router in object";
91             }
92             }
93              
94             sub connect
95             {
96 0     0 1   my ($rtx) = @_;
97 0           _check ($rtx);
98             # my $telnet_connection = new Net::Telnet (Dump_Log => "stuff.txt");
99 0           my $telnet_connection = new Net::Telnet();
100             # $telnet_connection->option_log (*STDERR);
101             # See documentation for Net::Telnet
102 0           $telnet_connection->open ($rtx->{address});
103 0 0         if ($rtx->{password}) {
104             # $telnet_connection->print ();
105 0           my $stuff = $telnet_connection->get ();
106             # print "stuff is: $stuff\n";
107 0 0         if ($stuff =~ m'Error: Other user logged in by telnet.') {
108 0           die "Someone else is already logged in.\n";
109             }
110             else {
111 0           $telnet_connection->print ($rtx->{password});
112             }
113 0 0         if ($telnet_connection->eof()) {
114 0           die "Telnet connection cut off for some reason.\n";
115             }
116 0           my $response = $telnet_connection->get();
117             # print "Response is: $stuff\n";
118             }
119 0           $rtx->{telnet_connection} = $telnet_connection;
120             }
121              
122             my $_config_desc = "RTX1000 configuration file";
123              
124             sub get_config
125             {
126 0     0 1   my ($rtx, $filename) = @_;
127 0 0         $filename = "config" unless $filename;
128 0           _check ($rtx);
129 0           my $tftp = Net::TFTP->new ($rtx->{address});
130 0           $tftp->ascii;
131 0 0         if (-f $filename) {
132 0           warn "$_config_desc '$filename' already exists.\n";
133 0           return;
134             }
135 0           my $remotefile = "config";
136 0 0         if ($rtx->{admin_password}) {
    0          
137 0           $remotefile .= "/$rtx->{admin_password}";
138             }
139             elsif ($rtx->{password}) {
140 0           $remotefile .= "/$rtx->{password}";
141 0           carp "Admin password is not set";
142             }
143             else {
144 0           carp "Neither admin nor user passwords are set";
145             }
146 0 0         if ($rtx->{verbose}) {
147 0           print "Getting $remotefile\n";
148             }
149 0           $tftp->get ($remotefile, $filename);
150 0 0         if ($tftp->error ()) {
151 0           die "tftp get '$remotefile failed': ",$tftp->error ();
152             }
153 0 0         die "TFTP failed" unless -f $filename;
154             # open my $input, "<", $filename or die $!;
155             # while (<$input>) { print }
156             # close $input or die $!;
157             }
158              
159             my $ip_address = qr/(?:(?:\d+\.){3}(?:\d+))/;
160             # The following does not contain the full possibilities with "except" etc.
161             my $ip_range_re = qr:(($ip_address)-($ip_address)/(\d+)):;
162             my $mac_re = qr/(?:[0-9a-f]{2}:){5}[0-9a-f]{2}/;
163             # Reference: Cmdref.pdf page 211
164             my $protocol_re = qr/(?:esp|tcp|udp|icmp|\d+)/;
165             # Reference: Cmdref.pdf page 56
166             my %aliases = (qw/
167             ftp 20&21
168             ftpdata 20
169             telnet 23
170             smtp 25
171             domain 53
172             gopher 70
173             finger 73
174             www 80
175             pop3 110
176             sunrpc 111
177             ident 113
178             ntp 123
179             nntp 119
180             snmp 161
181             syslog 514
182             printer 515
183             talk 517
184             route 520
185             uucp 540/
186             );
187             my @alias_keys = sort {length $b <=> length $a} keys %aliases;
188             my $port_re = '(?:'.join ('|', @alias_keys).'|\\d+)';
189              
190             sub _add_mac
191             {
192 0     0     my ($config, $mac, $what) = @_;
193 0           $config->{mac}->{$mac} = $what;
194 0           $config->{mac_map}->{$what} = $mac;
195             }
196              
197             sub _check_range
198             {
199 0     0     my ($range, $ip) = @_;
200             }
201              
202             sub read_config
203             {
204 0     0 1   my ($rtx, $filename) = @_;
205 0 0         $filename = "config" unless $filename;
206 0 0         die "Cannot find $_config_desc '$filename'" unless -f $filename;
207 0 0         open my $input, "<", $filename or die $!;
208 0           my $config = {};
209             # Default value is to disallow tftp.
210             # Reference: Cmdrefs.pdf, page 57
211 0           $config->{tftp_host} = "none";
212 0           while (my $line = <$input>) {
213 0 0         next if $line =~ /^\s*$/;
214 0 0         if ($line =~ /#\s+RTX1000\s+Rev\.([\d.]+)/) {
215 0           $config->{firmware} = $1;
216 0           next;
217             }
218 0 0         if ($line =~ /mac address\s*:\s*($mac_re)\s*,\s*($mac_re)\s*,\s*($mac_re)/i) {
219 0           my @macs = ($1, $2, $3);
220 0           _add_mac ($config, $macs[0], "lan1");
221 0           _add_mac ($config, $macs[1], "lan2");
222 0           _add_mac ($config, $macs[2], "lan3");
223 0           next;
224             }
225 0 0         if ($line =~ /tftp\s+host\s+($ip_address|any|none)/) {
226 0           $config->{tftp_host} = $1;
227 0           next;
228             }
229 0 0         if ($line =~ /no\s+tftp\s+host/) {
230 0           $config->{tftp_host} = "none";
231 0           next;
232             }
233             # Login password can be printable ascii characters
234             # Reference: Cmdref.pdf, page 43.
235             # Reference does not mention whether the space character is OK
236             # or not. Here I have assumed "not".
237 0 0         if ($line =~ /(administrator|login)\s+password\s+([[:graph:]]+)/) {
238 0           $config->{$1."_password"} = $2;
239 0           next;
240             }
241 0 0         if ($line =~ m:ip\s+lan(\d)\s+address\s+($ip_address)/(\d+):) {
242 0           my $lan = "lan$1";
243 0           $config->{$lan}->{address} = $2;
244 0           $config->{$lan}->{mask} = $3;
245 0           next;
246             }
247 0 0         if ($line =~ /nat\s+descriptor\s+type\s+(\d+)\s+(masquerade)/) {
248 0           my $nat_descriptor = $1;
249 0           my $type = $2;
250 0           $config->{nat}->{$nat_descriptor}->{type} = $type;
251 0           next;
252             }
253             # Reference: Cmdref.pdf page 211
254 0 0         if ($line =~ /nat\s+descriptor\s+masquerade\s+(static)\s+(\d+)\s+(\d+)\s+($ip_address)\s+($protocol_re)\s+(?:($port_re)=)?($port_re)/) {
255 0           my $nat_descriptor = $2;
256 0           my $id = $3;
257 0           my $ip = $4;
258 0           my $protocol = $5;
259 0           my $outer_port = $6;
260 0           my $inner_port = $7;
261 0           $config->{nat}->{$nat_descriptor}->{$id} =
262             {
263             id => $id,
264             ip => $ip,
265             protocol => $protocol,
266             outer_port => $outer_port,
267             inner_port => $inner_port
268             };
269 0           next;
270             }
271 0 0         if ($line =~ /dhcp\s+scope\s+(\d+)\s+$ip_range_re/) {
272 0           my $scope = $1;
273 0           my $range = $2;
274 0           $config->{dhcp}->{$scope}->{range} = $2;
275 0           next;
276             }
277 0 0         if ($line =~ /dhcp\s+scope\s+bind\s+(\d+)\s+($ip_address)\s+(ethernet\s+)?($mac_re)/i) {
278 0           my $scope = $1;
279 0           my $scope_hash = $config->{dhcp}->{$scope};
280 0 0         if (!$scope_hash) {
281 0           print "Warning: unknown scope $scope\n";
282             } else {
283 0           my $ip = $2;
284 0           _check_range ($scope_hash->{range}, $ip);
285 0           my $mac = $4;
286 0           _add_mac ($config, $mac, $ip);
287             # Add the data to scope_hash
288 0           $scope_hash->{$ip} = {mac => $mac};
289             }
290 0           next;
291             }
292 0 0         next if $line =~ /^#/;
293 0           $line =~ s/\s+$//;
294 0           print "Unrecognized line '$line'\n";
295             }
296 0 0         close $input or die $!;
297 0           $rtx->{config} = $config;
298             }
299              
300             # Do one command
301              
302             sub _command
303             {
304 0     0     my ($rtx, $command) = @_;
305 0           _check ($rtx);
306 0 0         $rtx->connect() unless $rtx->{telnet_connection};
307 0           $rtx->_admin_login();
308 0           my @lines = $rtx->{telnet_connection}->cmd ("$command\n");
309 0           my $retval = join ("", @lines);
310 0 0         die "Error doing '$command': $retval" if $retval =~ /Error:/;
311 0           return @lines;
312             }
313              
314             # Save the configuration to permanent memory
315              
316             sub save
317             {
318 0     0 1   my ($rtx) = @_;
319 0           _check ($rtx);
320 0           my @lines = $rtx->_command ("save\n");
321 0           my $retval = join ("", @lines);
322 0 0         die "Save failed: $retval" unless $retval =~ /Saving.*Done/;
323             }
324              
325             sub _admin_login
326             {
327 0     0     my ($rtx) = @_;
328 0           _check ($rtx);
329 0 0         return if $rtx->{admin};
330 0           my $admin_login_cmd = "administrator\n$rtx->{admin_password}\n";
331 0           my @reply = $rtx->{telnet_connection}->cmd($admin_login_cmd);
332 0           $rtx->{admin} = 1;
333             }
334              
335             sub command
336             {
337 0     0 1   my ($rtx, $command) = @_;
338 0           _check ($rtx);
339 0           $rtx->_command ("$command\n");
340 0           $rtx->save ("$command\n");
341             }
342              
343             sub _check_mac
344             {
345 0     0     my ($mac) = @_;
346 0 0         die "Bad MAC address '$mac'" unless $mac =~ /$mac_re/i;
347             }
348              
349             sub wake
350             {
351 0     0 1   my ($rtx, $lan, $mac) = @_;
352 0           _check ($rtx);
353 0           _check_mac ($mac);
354             # Reference: Cmdrefs.pdf, page 305
355 0           my @output = $rtx->_command ("wol send lan$lan $mac");
356 0           print "@output\n";
357             }
358              
359             sub arp
360             {
361 0     0 1   my ($rtx) = @_;
362 0           _check ($rtx);
363 0           my @output = $rtx->_command("show arp");
364 0           my @arp;
365 0           for my $line (@output) {
366 0 0         if ($line =~ /LAN(\d)\s+($ip_address)\s+($mac_re)\s+(\d+)/) {
367 0           my %arp_data;
368 0           $arp_data{lan} = $1;
369 0           $arp_data{ip} = $2;
370 0           $arp_data{mac} = $3;
371 0           $arp_data{ttl} = $4;
372 0           push @arp, \%arp_data;
373             }
374             }
375 0 0         if (@arp) {
376 0           return \@arp;
377             }
378 0           return;
379             }
380              
381             1;