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   14230 use warnings;
  1         1  
  1         30  
6 1     1   4 use strict;
  1         1  
  1         32  
7             our $VERSION = '0.06';
8              
9 1     1   7 use Carp qw/carp croak/;
  1         6  
  1         80  
10             # DEPENDS
11 1     1   883 use Net::Telnet;
  1         32493  
  1         45  
12 1     1   451 use Net::TFTP;
  1         7467  
  1         1753  
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             }
283             else {
284 0           my $ip = $2;
285 0           _check_range ($scope_hash->{range}, $ip);
286 0           my $mac = $4;
287 0           _add_mac ($config, $mac, $ip);
288             # Add the data to scope_hash
289 0           $scope_hash->{$ip} = {mac => $mac};
290             }
291 0           next;
292             }
293 0 0         next if $line =~ /^#/;
294 0           $line =~ s/\s+$//;
295 0           print "Unrecognized line '$line'\n";
296             }
297 0 0         close $input or die $!;
298 0           $rtx->{config} = $config;
299             }
300              
301             # Do one command
302              
303             sub _command
304             {
305 0     0     my ($rtx, $command) = @_;
306 0           _check ($rtx);
307 0 0         $rtx->connect() unless $rtx->{telnet_connection};
308 0           $rtx->_admin_login();
309 0           my @lines = $rtx->{telnet_connection}->cmd ("$command\n");
310 0           my $retval = join ("", @lines);
311 0 0         die "Error doing '$command': $retval" if $retval =~ /Error:/;
312 0           return @lines;
313             }
314              
315             # Save the configuration to permanent memory
316              
317             sub save
318             {
319 0     0 1   my ($rtx) = @_;
320 0           _check ($rtx);
321 0           my @lines = $rtx->_command ("save\n");
322 0           my $retval = join ("", @lines);
323 0 0         die "Save failed: $retval" unless $retval =~ /Saving.*Done/;
324             }
325              
326             sub _admin_login
327             {
328 0     0     my ($rtx) = @_;
329 0           _check ($rtx);
330 0 0         return if $rtx->{admin};
331 0           my $admin_login_cmd = "administrator\n$rtx->{admin_password}\n";
332 0           my @reply = $rtx->{telnet_connection}->cmd($admin_login_cmd);
333 0           $rtx->{admin} = 1;
334             }
335              
336             sub command
337             {
338 0     0 1   my ($rtx, $command) = @_;
339 0           _check ($rtx);
340 0           $rtx->_command ("$command\n");
341 0           $rtx->save ("$command\n");
342             }
343              
344             sub _check_mac
345             {
346 0     0     my ($mac) = @_;
347 0 0         die "Bad MAC address '$mac'" unless $mac =~ /$mac_re/i;
348             }
349              
350             sub wake
351             {
352 0     0 1   my ($rtx, $lan, $mac) = @_;
353 0           _check ($rtx);
354 0           _check_mac ($mac);
355             # Reference: Cmdrefs.pdf, page 305
356 0           my @output = $rtx->_command ("wol send lan$lan $mac");
357 0           print "@output\n";
358             }
359              
360             sub arp
361             {
362 0     0 1   my ($rtx) = @_;
363 0           _check ($rtx);
364 0           my @output = $rtx->_command("show arp");
365 0           my @arp;
366 0           for my $line (@output) {
367 0 0         if ($line =~ /LAN(\d)\s+($ip_address)\s+($mac_re)\s+(\d+)/) {
368 0           my %arp_data;
369 0           $arp_data{lan} = $1;
370 0           $arp_data{ip} = $2;
371 0           $arp_data{mac} = $3;
372 0           $arp_data{ttl} = $4;
373 0           push @arp, \%arp_data;
374             }
375             }
376 0 0         if (@arp) {
377 0           return \@arp;
378             }
379 0           return;
380             }
381              
382             1;