File Coverage

blib/lib/Net/Telnet/Trango.pm
Criterion Covered Total %
statement 109 410 26.5
branch 41 204 20.1
condition 8 69 11.5
subroutine 8 25 32.0
pod 15 15 100.0
total 181 723 25.0


line stmt bran cond sub pod time code
1             package Net::Telnet::Trango;
2              
3             # $RedRiver: Trango.pm,v 1.60 2009/07/31 21:46:07 andrew Exp $
4 11     11   272354 use strict;
  11         30  
  11         453  
5 11     11   71 use warnings;
  11         26  
  11         327  
6 11     11   64 use base 'Net::Telnet';
  11         27  
  11         19148  
7              
8             =pod
9              
10             =head1 NAME
11              
12             Net::Telnet::Trango
13             - Perl extension for accessing the Trango telnet interface
14              
15             =head1 SYNOPSIS
16              
17             use Net::Telnet::Trango;
18             my $t = new Net::Telnet::Trango ( Timeout => 5 );
19              
20             $t->open( Host => $ap ) or die "Error connecting: $!";
21              
22             $t->login('password') or die "Couldn't log in: $!";
23              
24             # Do whatever
25              
26             $t->exit;
27             $t->close;
28              
29             =head1 DESCRIPTION
30              
31             Perl access to the telnet interface on Trango APs and SUs.
32              
33             A handy feature is that it will parse the output from certain commands that is
34             in the format "[key1] value1 [key2] value2" and put those in a hashref that is
35             returned. This makes using the output from things like sysinfo very easy to
36             do.
37              
38             =head2 EXPORT
39              
40             None
41              
42             =head1 METHODS
43              
44             =cut
45              
46             our $VERSION = '0.05';
47              
48             my $EMPTY = q{};
49             my $SPACE = q{ };
50              
51             =pod
52              
53             =head2 B - Creates a new Net::Telnet::Trango object.
54              
55             new([Options from Net::Telnet,]
56             [Decode => 0,]);
57              
58             Same as new from L but sets the default Trango Prompt:
59             '/[\$#]>\s*\Z/'
60              
61             It also takes an optional parameter 'Decode'. If not defined it
62             defaults to 1, if it is set to 0, it will not decode the output and
63             instead return a reference to an array of the lines that were returned
64             from the command.
65              
66             =cut
67              
68             sub new {
69 6     6 1 3134 my $class = shift;
70              
71 6         13 my %args = ();
72 6 50       16 if ( @_ == 1 ) {
73 0         0 $args{'Host'} = shift;
74             }
75             else {
76 6         12 %args = @_;
77             }
78              
79 6   50     36 $args{'Prompt'} ||= '/[\$#]>\s*\r?\n?$/';
80              
81 6         10 my $decode = $args{'Decode'};
82 6         10 delete $args{'Decode'};
83              
84 6         41 my $self = $class->SUPER::new(%args);
85 6 50       1440 bless $self if ref $self;
86              
87 6 50       19 $args{Decode} = defined $decode ? $decode : 1;
88 6         13 $args{is_connected} = 0;
89 6         11 $args{logged_in} = 0;
90              
91 6         13 *$self->{net_telnet_trango} = \%args;
92              
93 6         29 return $self;
94             }
95              
96             # _password
97             # ? [command]
98             # apsearch [ ]...
99             # arp -bcast
100             # bcastscant [ ...
101             # bye
102             # cf2cf ap [default|]
103             # date
104             # date
105             # freq scantable
106             # freq channeltable
107             # freq writescan [ ]
108             # freq writechannel [ ] ...
109             # freq
110             # help [command]
111             # heater [ ]
112             # ipconfig [ ]
113             # linktest [ [<# of pkts> [<# of cycle>]]]
114             # log [<# of entries, 1..179>]
115             # log <# of entries, 1..179>
116             # logout
117             # opmode [ap [y]]
118             # password
119             # ping
120             # polar
121             # power >
122             # reboot
123             # restart
124             # remarks []
125             # rfrxthreshold [ <-90|-85|-80|-75|-70|-65>]
126             # rfrxth [ <-90|-85|-80|-75|-70|-65>]
127             # sysinfo
128             # set suid
129             # set apid
130             # set baseid
131             # set defaultopmode [ ]
132             # set defaultopmode off
133             # set snmpcomm [ ]
134             # set mir [on|off]
135             # set mir threshold
136             # set rssitarget [ ]
137             # set serviceradius [ ]
138             # ssrssi
139             # su [|all]
140             # su changechannel
141             # su ipconfig
142             # su [live|poweroff|priority]
143             # su
144             # su powerleveling
145             # su reboot
146             # su restart
147             # su testrflink [r]
148             # su testrflink [64..1600]
149             # su testrflink [20..100]
150             # su sw
151             # sudb [dload | view]
152             # sudb add pr
153             # sudb add reg
154             # sudb delete >
155             # sudb modify
156             # sudb modify
157             # sudb view
158             # sulog [lastmins | sampleperiod <1..60>]
159             # sulog [<# of entry,1..18>]
160             # survey
161             # sw [ ]
162             # temp
163             # tftpd [on|off]
164             # time
165             # time
166             # save
167             # save
168             # updateflash
169             # updateflash
170              
171             =pod
172              
173             =head1 ACCESSORS
174              
175             These are usually only set internally.
176              
177             =head2 B - returns the firmware version
178              
179             Returns the firmware version if available, otherwise undef.
180              
181             It should be available after a successful open().
182              
183             =head2 B - return the type of host you are connected to.
184              
185             returns the type of host from the login banner for example M5830S or M5300S.
186              
187             Should be available after a successful open().
188              
189             =head2 B - Status of the connection to host.
190              
191             returns 1 when connected, undef otherwise.
192              
193             =head2 B - Status of being logged in to the host.
194              
195             returns 1 after a successful login(), 0 if it failed and undef if
196             login() was never called.
197              
198             =head2 B - The banner when first connecting to the host.
199              
200             returns the banner that is displayed when first connected at login.
201             Only set after a successful open().
202              
203             =head2 B - The last lines of output from the last cmd().
204              
205             returns, as an array ref, the output from the last cmd() that was run.
206              
207             =head2 B - A text output of the last error that was encountered.
208              
209             returns the last error reported. Probably contains the last entry in
210             last_lines.
211              
212             =head1 ALIASES
213              
214             =head2 B - alias of exit()
215              
216             Does the same as exit()
217              
218             =head2 B - alias of reboot()
219              
220             Does the same as reboot()
221              
222             =head2 B - alias of save_ss()
223              
224             Does the same as save_ss()
225              
226             =head1 COMMANDS
227              
228             Most of these are just shortcuts to C METHOD)>,
229             as such they accept the same options as C.
230             Specifically they take a named paramater "args", for example:
231             C 'on')> would enable tftpd
232              
233             =head2 B - The output from the tftpd command
234              
235             Returns a hash ref of the decoded output from the
236             command.
237              
238             Also see enable_tftpd() and disable_tftpd() as those check that it was
239             successfully changed.
240              
241             =head2 B - The output from the ver command
242              
243             Returns a hash ref of the decoded output from the
244             command.
245              
246             =head2 B - The output from the sysinfo command
247              
248             Returns a hash ref of the decoded output from the
249             command.
250              
251             =head2 B - Exits the connection
252              
253             exits the command session with the Trango and closes
254             the connection
255              
256             =head2 B - Sends a reboot command
257              
258             reboots the Trango and closes the connection
259              
260             =head2 B - Sends a reset command
261              
262             resets settings to default
263              
264             =head2 B - Set or retrieve the remarks.
265              
266             Takes an optional argument, which sets the remarks.
267             If there is no argument, returns the current remarks.
268              
269             my $old_remarks = $t->remarks();
270             $t->remarks($new_remarks);
271              
272             =head2 B - The output from the sulog command
273              
274             Returns an array ref of hashes containing each log
275             line.
276              
277             =head2 B - saves the sudb
278              
279             Returns true on success, undef on failure
280              
281             =head2 B - The output from the sulog command
282              
283             Returns a hashref of the output from the syslog command
284              
285             =head2 B - the pipe command
286              
287             Returns the output from the pipe command
288              
289             =head2 B - retrieves the maclist
290              
291             Returns the output from the maclist command
292              
293             =head2 B - resets the maclist.
294              
295             No useful output.
296              
297             =head2 B - eth link command
298              
299             Returns the output from the eth link command
300              
301             This command seems to cause some weird issues. It often will cause the
302             command after it to appear to fail. I am not sure why.
303              
304             =head2 B - gets the su info
305              
306             Returns information about the SU.
307              
308             You need to pass in the $suid and it will return the info for that suid.
309              
310             $t->su_info($suid);
311              
312             =head2 B - tests the RF Link to an su
313              
314             $t->su_testrflink($suid|'all');
315              
316             =head2 B - saves the config.
317              
318             Returns 1 on success, undef on failure.
319              
320             =head2 B - sets baseid
321              
322             $t->set_baseid($baseid);
323              
324             =head2 B - sets baseid
325              
326             $t->set_suid($baseid);
327              
328             =head2 B - sets default opmode
329              
330             $t->set_defaultopmode(ap|su);
331              
332             =head2 B - sets or returns the opmode
333              
334             $t->opmode([ap y|su y]);
335              
336             =head2 B - sets or returns the freq
337              
338             $channel = '11 v';
339             $t->freq([$channel]);
340              
341             =head2 B - sets the freq writescan
342              
343             $channels = '11 v 11 h 12 v 12 h';
344             $t->freq_writescan($channels);
345              
346             =head2 B - returns the freq scantable
347              
348             $channels = $t->freq_scantable();
349             # now $channels eq '11 v 11 h 12 v 12 h';
350              
351              
352             =cut
353              
354             my $success = 'Success\\.';
355             my %COMMANDS = (
356             _clear => { String => "\n" },
357             tftpd => { decode => 'all', expect => $success },
358             ver => { decode => 'all' },
359             sysinfo => { decode => 'all', expect => $success },
360             updateflash => { decode => 'all', expect => $success },
361             sulog => { decode => 'sulog', expect => $success },
362             'exit' => { no_prompt => 1, cmd_disconnects => 1 },
363             reboot => { no_prompt => 1, cmd_disconnects => 1 },
364             'reset' => {},
365             remarks => { decode => 'all', expect => $success },
366             save_sudb => { String => 'save sudb', expect => $success },
367             syslog => { expect => $success },
368             'pipe' => {}, # XXX needs a special decode
369             maclist => { decode => 'maclist' },
370             maclist_reset => { String => 'maclist reset', expect => 'done' },
371             eth_link => { String => 'eth link', expect => $success },
372             su_info => { String => 'su info', decode => 'all', expect => $success },
373             su_testrflink =>
374             { String => 'su testrflink', decode => 'each', expect => $success },
375             save_ss => { String => 'save ss', expect => $success },
376             set_baseid => {
377             String => 'set baseid',
378             decode => 'all',
379             expect => $success
380             },
381             set_suid => {
382             String => 'set suid',
383             decode => 'all',
384             expect => $success
385             },
386             set_defaultopmode => {
387             String => 'set defaultopmode',
388             decode => 'all',
389             expect => $success
390             },
391             opmode => { decode => 'all', expect => $success },
392             freq => { decode => 'freq', expect => $success },
393             freq_writescan =>
394             { String => 'freq writescan', decode => 'all', expect => $success },
395             freq_scantable =>
396             { String => 'freq scantable', decode => 'all', expect => $success },
397             arq => { decode => 'all' },
398             );
399              
400             my %ALIASES = (
401             bye => 'exit',
402             restart => 'reboot',
403             Host => 'host',
404             save_systemseting => 'save_ss',
405             );
406              
407             my %ACCESS = map { $_ => 1 } qw(
408             firmware_version
409             host_type
410             is_connected
411             logged_in
412             login_banner
413             Timeout
414             last_lines
415             last_vals
416             last_error
417             Decode
418             );
419              
420             sub AUTOLOAD {
421 42     42   66 my $self = shift;
422              
423 42 50       252 my ($method) = ( our $AUTOLOAD ) =~ /^.*::(\w+)$/
424             or die "Weird: $AUTOLOAD";
425              
426 42 50       111 if ( exists $ALIASES{$method} ) {
427 0         0 $method = $ALIASES{$method};
428 0         0 return $self->$method(@_);
429             }
430              
431 42 50       101 if ( exists $COMMANDS{$method} ) {
432 0         0 my %cmd;
433 0         0 foreach my $k ( keys %{ $COMMANDS{$method} } ) {
  0         0  
434 0         0 $cmd{$k} = $COMMANDS{$method}{$k};
435             }
436 0   0     0 $cmd{'String'} ||= $method;
437 0 0       0 $cmd{'args'} .= $SPACE . shift if ( @_ == 1 );
438 0         0 return $self->cmd( %cmd, @_ );
439             }
440              
441 42 50       103 if ( exists $ACCESS{$method} ) {
442 42         67 my $s = *$self->{net_telnet_trango};
443 42         67 my $prev = $s->{$method};
444 42 100       122 ( $s->{$method} ) = @_ if @_;
445 42         130 return $prev;
446             }
447              
448 0         0 $method = "SUPER::$method";
449 0         0 return $self->$method(@_);
450             }
451              
452             =pod
453              
454             =head2 B - Open a connection to a Trango AP.
455              
456             Calls Net::Telnet::open() then makes sure you get a password prompt so
457             you are ready to login() and parses the login banner so you can get
458             host_type() and firmware_version()
459              
460             =cut
461              
462             sub open {
463 0     0 1 0 my $self = shift;
464              
465 0 0       0 unless ( $self->SUPER::open(@_) ) {
466 0         0 $self->last_error( "Couldn't connect to " . $self->host . ": $!" );
467 0         0 return;
468             }
469              
470             ## Get to login prompt
471 0 0       0 unless (
472             $self->waitfor(
473             -match => '/password: ?$/i',
474             -errmode => "return",
475             )
476             )
477             {
478 0         0 $self->last_error( "problem connecting to host ("
479             . $self->host . "): "
480             . $self->lastline );
481 0         0 return;
482             }
483              
484 0         0 $self->parse_login_banner( $self->lastline );
485              
486 0         0 $self->is_connected(1);
487              
488 0         0 return $self->is_connected;
489             }
490              
491             =pod
492              
493             =head2 B - Login to the AP.
494              
495             Calls open() if not already connected, then sends the password and sets
496             logged_in() if successful
497              
498             =cut
499              
500             sub login {
501 0     0 1 0 my $self = shift;
502              
503 0 0       0 unless ( $self->is_connected ) {
504 0 0       0 $self->open or return;
505             }
506              
507 0         0 my $password = shift;
508              
509 0         0 $self->print($password);
510 0 0       0 unless (
511             $self->waitfor(
512             -match => $self->prompt,
513             -errmode => "return",
514             )
515             )
516             {
517 0         0 $self->last_error( "login ($self->host) failed: " . $self->lastline );
518 0         0 return;
519             }
520              
521 0         0 $self->logged_in(1);
522              
523 0         0 return $self->logged_in;
524             }
525              
526             =pod
527              
528             =head2 B - Converts the login_banner to something useful.
529              
530             Takes a login banner (what you get when you first connect to the Trango)
531             or reads what is already in login_banner() then parses it and sets
532             host_type() and firmware_version() as well as login_banner()
533              
534             =cut
535              
536             sub parse_login_banner {
537 6     6 1 38 my $self = shift;
538              
539 6 50       20 if (@_) {
540 6         41 $self->login_banner(@_);
541             }
542              
543 6         25 my $banner = $self->login_banner;
544              
545 6         51 my ( $type, $sep1, $subtype, $sep2, $ver )
546             = $banner
547             =~ /Welcome to Trango Broadband Wireless,? (\S+)([\s-]+)(\S+)([\s-]+)(.+)$/i;
548              
549 6         13 $type .= $sep1 . $subtype;
550 6         14 $ver = $subtype . $sep2 . $ver;
551              
552 6         23 $self->login_banner($banner);
553 6         36 $self->host_type($type);
554 6         33 $self->firmware_version($ver);
555              
556 6         29 return 1;
557             }
558              
559             =pod
560              
561             =head2 B - Link test to SU
562              
563             linktest('suid'[, 'pkt len, bytes'[, '# of pkts'[, '# of cycles']]]);
564              
565             Returns a hash reference to the results of the test
566              
567             =cut
568              
569             sub linktest {
570 0     0 1 0 my $self = shift;
571 0         0 my $suid = shift;
572              
573             # These numbers are what I found as defaults when running the command
574 0   0     0 my $pkt_len = shift || 1600;
575 0   0     0 my $pkt_cnt = shift || 500;
576 0   0     0 my $cycles = shift || 10;
577              
578 0         0 my %config = @_;
579              
580             # * 2, one for the FromAP, one FromSU. Then / 1000 to get to ms.
581             # XXX This might need to be changed, this makes the default timeout the
582             # same as $pkt_len, and that might not be enough at slower speeds.
583 0   0     0 $config{Timeout} ||= int( ( $pkt_len * $pkt_cnt * $cycles * 2 ) / 1000 );
584              
585 0         0 my $string = join $SPACE, 'linktest', $suid, $pkt_len, $pkt_cnt, $cycles;
586 0         0 return $self->cmd(
587             %config,
588             String => $string,
589             decode => 'linktest',
590             );
591              
592             }
593              
594             =pod
595              
596             =head2 B - Set the password on SUs connected to the AP.
597              
598             su_password('new_password'[, 'suid']) If no suid is specified,
599             the default is "all".
600              
601             $t->su_password('good_pass', 5);
602              
603             =cut
604              
605             sub su_password {
606 0     0 1 0 my $self = shift;
607 0   0     0 my $new_pass = shift || $EMPTY;
608 0   0     0 my $su = shift || 'all';
609              
610 0 0       0 unless ( defined $new_pass ) {
611 0         0 $self->last_error("No new password");
612              
613             #return;
614             }
615              
616 0         0 return $self->cmd(
617             String => 'su password '
618             . $su
619             . $SPACE
620             . $new_pass
621             . $SPACE
622             . $new_pass,
623             expect => $success,
624             );
625             }
626              
627             =pod
628              
629             =head2 B - Change IP configuration
630              
631             ipconfig( 'new_ip', 'new_subnet', 'new_gateway' )
632              
633             $t->ipconfig( '10.0.1.5', '255.255.255.0', '10.0.1.1' );
634              
635             =cut
636              
637             sub ipconfig {
638 0     0 1 0 my $self = shift;
639              
640 0         0 my $string = join $SPACE, 'ipconfig', @_;
641              
642 0 0       0 if ( @_ == 3 ) {
643 0         0 $self->print($string);
644 0         0 my @lines = $self->waitfor( Match => '/save\s+and\s+activate/', );
645 0         0 $self->print('y');
646              
647 0         0 $self->logged_in(0);
648 0         0 $self->is_connected(0);
649              
650 0         0 foreach my $line (@lines) {
651 0 0       0 if ( $line =~ s/New \s configuration:\s+//xms ) {
652 0         0 return _decode_lines($line);
653             }
654             }
655              
656 0         0 return {};
657             }
658              
659             # ipconfig [ ]
660 0         0 return $self->cmd( String => $string, expect => $success );
661             }
662              
663             =pod
664              
665             =head2 B - Change IP configuration on SUs connected to the AP.
666              
667             su_ipconfig( 'suid', 'new_ip', 'new_subnet', 'new_gateway' )
668              
669             $t->su_ipconfig( 5, '10.0.1.5', '255.255.255.0', '10.0.1.1' );
670              
671             =cut
672              
673             sub su_ipconfig {
674 0     0 1 0 my $self = shift;
675              
676 0         0 my $suid = shift;
677 0         0 my $new_ip = shift;
678 0         0 my $new_subnet = shift;
679 0         0 my $new_gateway = shift;
680              
681 0 0       0 if ( $suid =~ /\D/ ) {
682 0         0 $self->last_error("Invalid suid '$suid'");
683 0         0 return;
684             }
685 0 0       0 unless ($new_ip) {
686 0         0 $self->last_error("no new_ip passed");
687 0         0 return;
688             }
689 0 0       0 unless ($new_subnet) {
690 0         0 $self->last_error("no new_subnet passed");
691 0         0 return;
692             }
693 0 0       0 unless ($new_gateway) {
694 0         0 $self->last_error("no new_gateway passed");
695 0         0 return;
696             }
697              
698             # su ipconfig
699 0         0 return $self->cmd(
700             String => 'su ipconfig '
701             . $suid
702             . $SPACE
703             . $new_ip
704             . $SPACE
705             . $new_subnet
706             . $SPACE
707             . $new_gateway,
708             expect => $success,
709             );
710             }
711              
712             =pod
713              
714             =head2 B - Returns the output from the sudb view command
715              
716             returns a reference to an array of hashes each containing these keys
717             'suid', 'su2su', 'type', 'cir', 'mir' and 'mac'
718              
719             =cut
720              
721             sub sudb_view {
722 0     0 1 0 my $self = shift;
723              
724 0   0     0 my $lines = $self->cmd( String => 'sudb view', expect => $success ) || [];
725              
726 0 0       0 return unless @{$lines};
  0         0  
727              
728 0         0 my $s = *$self->{net_telnet_trango};
729 0 0       0 return $lines if !$s->{'Decode'};
730              
731 0         0 my @sus;
732 0         0 foreach ( @{$lines} ) {
  0         0  
733 0 0       0 next unless $_;
734 0 0       0 if (/^
735             \[(\d+)\]
736             \s+
737             [[:xdigit:]]{2}
738             ([[:xdigit:]])
739             ([[:xdigit:]])
740             \s+
741             (\d+)
742             \s+
743             (\d+)
744             \s+
745             ([[:xdigit:]\s]+)
746             $/ixms
747             )
748             {
749 0 0       0 my %s = (
    0          
    0          
750             suid => $1,
751             su2su => $2 ? $2 : undef,
752             type => $3 == 1 ? 'reg' : $3 == 5 ? 'pri' : $3,
753             cir => $4,
754             mir => $5,
755             mac => $6,
756             );
757              
758 0         0 $s{'mac'} =~ s/\s//gxms;
759 0         0 $s{'mac'} = uc( $s{'mac'} );
760              
761 0         0 push @sus, \%s;
762             }
763             }
764              
765 0         0 return \@sus;
766             }
767              
768             =pod
769              
770             =head2 B - Adds an su to the sudb
771              
772             Takes the following paramaters
773              
774             suid : numeric,
775             type : (reg|pr)
776             cir : numeric,
777             mir : numeric,
778             mac : Almost any format, it will be reformatted,
779              
780             and returns true on success or undef otherwise.
781              
782             $t->sudb_add($suid, 'reg', $cir, $mir, $mac);
783              
784             You should save_sudb() after calling this, or your changes will be lost
785             when the AP is rebooted.
786              
787             =cut
788              
789             sub sudb_add {
790 0     0 1 0 my $self = shift;
791 0         0 my $suid = shift;
792 0         0 my $type = shift;
793 0         0 my $cir = shift;
794 0         0 my $mir = shift;
795 0         0 my $mac = shift;
796              
797 0 0       0 if ( $suid =~ /\D/ ) {
798 0         0 $self->last_error("Invalid suid '$suid'");
799 0         0 return;
800             }
801              
802 0 0 0     0 unless ( lc($type) eq 'reg' || lc($type) eq 'pr' ) {
803 0         0 $self->last_error("Invalid type '$type'");
804 0         0 return;
805             }
806              
807 0 0       0 if ( $cir =~ /\D/ ) {
808 0         0 $self->last_error("Invalid CIR '$cir'");
809 0         0 return;
810             }
811              
812 0 0       0 if ( $mir =~ /\D/ ) {
813 0         0 $self->last_error("Invalid MIR '$mir'");
814 0         0 return;
815             }
816              
817 0         0 my $new_mac = $mac;
818 0         0 $new_mac =~ s/[^0-9A-Fa-f]//g;
819 0 0       0 unless ( length $new_mac == 12 ) {
820 0         0 $self->last_error("Invalid MAC '$mac'");
821 0         0 return;
822             }
823 0         0 $new_mac = join $SPACE, $new_mac =~ /../g;
824              
825 0         0 my $string
826             = 'sudb add '
827             . $suid
828             . $SPACE
829             . $type
830             . $SPACE
831             . $cir
832             . $SPACE
833             . $mir
834             . $SPACE
835             . $new_mac;
836              
837 0         0 return $self->cmd( String => $string, expect => $success );
838             }
839              
840             =pod
841              
842             =head2 B - removes an su from the sudb
843              
844             Takes either 'all' or the suid of the su to delete
845             and returns true on success or undef otherwise.
846              
847             $t->sudb_delete($suid);
848              
849             You should save_sudb() after calling this, or your changes will be lost
850             when the AP is rebooted.
851              
852             =cut
853              
854             sub sudb_delete {
855 0     0 1 0 my $self = shift;
856 0         0 my $suid = shift;
857              
858             #if (lc($suid) ne 'all' || $suid =~ /\D/) {
859 0 0       0 if ( $suid =~ /\D/ ) {
860 0         0 $self->last_error("Invalid suid '$suid'");
861 0         0 return;
862             }
863              
864 0         0 return $self->cmd( String => 'sudb delete ' . $suid, expect => $success );
865             }
866              
867             =pod
868              
869             =head2 B - changes the su information in the sudb
870              
871             Takes either the suid of the su to change
872             as well as what you are changing, either "cir, mir or su2su"
873             and returns true on success or undef otherwise.
874              
875             cir and mir also take a value to set the cir/mir to.
876              
877             su2su takes a group id parameter that is in hex.
878              
879             $t->sudb_modify($suid, 'cir', 512);
880              
881             You should save_sudb() after calling this, or your changes will be lost
882             when the AP is rebooted.
883              
884             =cut
885              
886             sub sudb_modify {
887 0     0 1 0 my $self = shift;
888 0         0 my $suid = shift;
889 0         0 my $opt = shift;
890 0         0 my $value = shift;
891              
892 0 0       0 if ( $suid =~ /\D/ ) {
893 0         0 $self->last_error("Invalid suid '$suid'");
894 0         0 return;
895             }
896              
897 0 0 0     0 if ( lc($opt) eq 'cir' or lc($opt) eq 'mir' ) {
    0          
898 0 0       0 if ( $value =~ /\D/ ) {
899 0         0 $self->last_error("Invalid $opt '$value'");
900 0         0 return;
901             }
902             }
903             elsif ( lc($opt) eq 'su2su' ) {
904 0 0       0 if ( $value =~ /[^0-9A-Za-f]/ ) {
905 0         0 $self->last_error("Invalid MAC '$value'");
906 0         0 return;
907             }
908             }
909             else {
910 0         0 $self->last_error("Invalid option '$opt'");
911 0         0 return;
912             }
913              
914 0         0 my $string = 'sudb modify ' . $suid . $SPACE . $opt . $SPACE . $value;
915              
916 0         0 return $self->cmd( String => $string, expect => $success );
917             }
918              
919             =pod
920              
921             =head2 B - enable the TFTP server
922              
923             runs C 'on')> and makes sure that Tftpd is now 'listen'ing
924              
925             =cut
926              
927             sub enable_tftpd {
928 0     0 1 0 my $self = shift;
929              
930 0         0 my $vals = $self->tftpd( args => 'on' );
931              
932 0 0 0     0 if ( ref $vals eq 'HASH' && $vals->{'Tftpd'} eq 'listen' ) {
933 0         0 return $vals;
934             }
935             else {
936 0         0 return;
937             }
938             }
939              
940             =pod
941              
942             =head2 B - disable the TFTP server
943              
944             runs C 'off')> and makes sure that Tftpd is now 'disabled'
945              
946             =cut
947              
948             sub disable_tftpd {
949 0     0 1 0 my $self = shift;
950              
951 0         0 my $vals = $self->tftpd( args => 'off' );
952              
953 0 0 0     0 if ( ref $vals eq 'HASH' && $vals->{'Tftpd'} eq 'disabled' ) {
954 0         0 return $vals;
955             }
956             else {
957 0         0 return;
958             }
959             }
960              
961             =pod
962              
963             =head2 B - runs a command on the AP.
964              
965             This does most of the work. At the heart, it calls Net::Telnet::cmd()
966             but it also does some special stuff for Trango.
967              
968             Normally returns the last lines from from the command
969              
970             If you are using this, rather than one of the "easy" methods above,
971             you probably want to read through the source of this module to see how
972             some of the other commands are called.
973              
974             In addition to the Net::Telnet::cmd() options, it also accepts these:
975              
976             I
977             - if this is true, then it will send the output lines to _decode_lines()
978             and then returns the decoded output
979              
980             I
981             - if this is true, it does not wait for a prompt, so you are not stuck
982             waiting for something that will never happen.
983              
984             I
985             - if this is true, it then sets logged_in() to false, then it will
986             close() the connection and set is_connected() to false
987              
988             I
989             - if this is set (usually to 'Success.') it will check for that in the
990             last line of output and if it does not, will return undef because the
991             command probably failed
992              
993             I
994             - a string containing the command line options that are passed to the
995             command
996              
997             $t->cmd( String => 'exit', no_prompt => 1, cmd_disconnects => 1 );
998              
999             =cut
1000              
1001             sub cmd {
1002 0     0 1 0 my $self = shift;
1003 0         0 my $s = *$self->{net_telnet_trango};
1004              
1005 0         0 my @valid_net_telnet_opts = qw(
1006             String
1007             Output
1008             Cmd_remove_mode
1009             Errmode
1010             Input_record_separator
1011             Ors
1012             Output_record_separator
1013             Prompt
1014             Rs
1015             Timeout
1016             );
1017              
1018 0         0 my %cfg;
1019 0 0       0 if ( @_ == 1 ) {
    0          
1020 0         0 $cfg{'String'} = shift;
1021             }
1022             elsif ( @_ > 1 ) {
1023 0         0 %cfg = @_;
1024             }
1025              
1026 0   0     0 $cfg{'Timeout'} ||= $self->Timeout;
1027              
1028 0 0       0 unless ( $cfg{'String'} ) {
1029 0         0 $self->last_error("No command passed");
1030 0         0 return;
1031             }
1032              
1033 0 0       0 unless ( $self->is_connected ) {
1034 0         0 $self->last_error("Not connected");
1035 0         0 return;
1036             }
1037              
1038 0 0       0 unless ( $self->logged_in ) {
1039 0         0 $self->last_error("Not logged in");
1040 0         0 return;
1041             }
1042              
1043 0         0 my %cmd;
1044 0         0 foreach (@valid_net_telnet_opts) {
1045 0 0       0 if ( exists $cfg{$_} ) {
1046 0         0 $cmd{$_} = $cfg{$_};
1047             }
1048             }
1049 0 0       0 if ( $cfg{'args'} ) {
1050 0         0 $cmd{'String'} .= $SPACE . $cfg{'args'};
1051             }
1052              
1053             #print "Running cmd $cmd{String}\n";
1054 0         0 my @lines;
1055 0 0       0 if ( $cfg{'no_prompt'} ) {
1056 0         0 $self->print( $cmd{'String'} );
1057 0         0 @lines = $self->lastline;
1058             }
1059             else {
1060 0         0 @lines = $self->SUPER::cmd(%cmd);
1061             }
1062              
1063 0         0 $self->last_lines( \@lines );
1064              
1065 0         0 my $last = $self->lastline;
1066 0         0 my $prompt = $self->prompt;
1067 0         0 $prompt =~ s{^/}{}xms;
1068 0         0 $prompt =~ s{/[gixms]*$}{}xms;
1069 0   0     0 while ( @lines && $last =~ qr($prompt) ) {
1070 0         0 pop @lines;
1071 0         0 $last = $lines[-1];
1072             }
1073 0         0 $self->last_error($EMPTY);
1074              
1075 0         0 my $vals = 1;
1076 0 0 0     0 if ( $s->{'Decode'} && $cfg{'decode'} ) {
1077 0 0       0 if ( $cfg{'decode'} eq 'each' ) {
    0          
    0          
    0          
    0          
1078 0         0 $vals = _decode_each_line(@lines);
1079             }
1080             elsif ( $cfg{'decode'} eq 'sulog' ) {
1081 0         0 $vals = _decode_sulog(@lines);
1082             }
1083             elsif ( $cfg{'decode'} eq 'maclist' ) {
1084 0         0 $vals = _decode_maclist(@lines);
1085 0 0       0 if ( !$vals ) {
1086 0         0 $self->last_error("Error decoding maclist");
1087             }
1088             }
1089             elsif ( $cfg{'decode'} eq 'linktest' ) {
1090 0         0 $vals = _decode_linktest(@lines);
1091 0 0       0 if ( !$vals ) {
1092 0         0 $self->last_error("Error decoding linktest");
1093             }
1094             }
1095             elsif ( $cfg{'decode'} eq 'freq' ) {
1096 0         0 $vals = _decode_freq(@lines);
1097             }
1098             else {
1099 0         0 $vals = _decode_lines(@lines);
1100             }
1101             }
1102 0 0       0 if ( ref $vals eq 'HASH' ) {
1103 0         0 $vals->{_raw} = join q{}, @lines;
1104             }
1105 0         0 $self->last_vals($vals);
1106              
1107 0 0 0     0 if ( ( not $cfg{'expect'} ) || $last =~ /$cfg{'expect'}$/ ) {
1108 0 0       0 if ( $cfg{'cmd_disconnects'} ) {
1109 0         0 $self->logged_in(0);
1110 0         0 $self->close;
1111 0         0 $self->is_connected(0);
1112             }
1113              
1114 0 0 0     0 if ( $s->{'Decode'} && $cfg{'decode'} ) {
1115 0         0 return $vals;
1116             }
1117             else {
1118 0         0 return \@lines;
1119             }
1120             }
1121             else {
1122 0         0 my $err;
1123 0 0       0 if ( grep {/\[ERR\]/} @lines ) {
  0         0  
1124 0         0 $err = _decode_lines(@lines);
1125             }
1126              
1127 0 0 0     0 if ( ref $err eq 'HASH' && $err->{ERR} ) {
1128 0         0 $self->last_error( $err->{ERR} );
1129             }
1130             else {
1131 0         0 $self->last_error("Error with command ($cmd{'String'}): $last");
1132             }
1133 0         0 return;
1134             }
1135             }
1136              
1137             #=item _decode_lines
1138              
1139             sub _decode_lines {
1140 20     20   44 my @lines = @_;
1141              
1142 20         27 my %conf;
1143              
1144 20         30 my $key = $EMPTY;
1145 20         26 my $val = undef;
1146 20         23 my @vals;
1147 20         26 my $in_key = 0;
1148 20         22 my $in_val = 1;
1149              
1150 20         55 LINE: while ( my $line = shift @lines ) {
1151 20 50       102 next LINE if $line =~ /$success\Z/;
1152 20 50       52 next LINE if $line =~ /^ \*+ \s+ \d+ \s+ \*+ \Z/xms;
1153              
1154             # Special decode for sysinfo on a TrangoLink 45
1155 20 50       70 if ( $line =~ /^(.* Channel \s+ Table):\s*(.*)\Z/xms ) {
    50          
1156 0         0 my $key = $1;
1157 0         0 my $note = $2;
1158              
1159 0         0 my %vals;
1160 0         0 while ( $line = shift @lines ) {
1161 0 0       0 if ( $line =~ /^\Z/ ) {
1162 0         0 $conf{$key} = \%vals;
1163 0         0 $conf{$key}{note} = $note;
1164 0         0 next LINE;
1165             }
1166              
1167 0         0 my $decoded = _decode_lines($line);
1168 0 0       0 if ($decoded) {
1169 0         0 %vals = ( %vals, %{$decoded} );
  0         0  
1170             }
1171             }
1172             }
1173              
1174             # Another special decode for the TrangoLink
1175             elsif (
1176             $line =~ /^
1177             RF \s Band \s \#
1178             (\d+) \s+
1179             \( ([^\)]+) \) \s*
1180             (.*)$
1181             /xms
1182             )
1183             {
1184 0         0 my $num = $1;
1185 0         0 my $band = $2;
1186 0         0 my $extra = $3;
1187              
1188 0 0       0 if ( $extra =~ /\[/ ) {
1189 0         0 my $decoded = _decode_lines($extra);
1190 0         0 $conf{'RF Band'}{$num} = $decoded;
1191             }
1192             else {
1193 0         0 $conf{'RF Band'}{$num}{$extra} = 1;
1194             }
1195 0         0 next LINE;
1196             }
1197              
1198 20         270 my @chars = split //, $line;
1199              
1200 20         51 my $last_key = $EMPTY;
1201 20         38 foreach my $c (@chars) {
1202              
1203 655 100 66     4785 if ( $c eq '[' || $c eq "\r" || $c eq "\n" ) {
    100 100        
    100          
    50          
1204 63 100       112 if ( $c eq '[' ) {
1205 43         56 $in_key = 1;
1206 43         59 $in_val = 0;
1207             }
1208             else {
1209 20         26 $in_key = 0;
1210 20         51 $in_val = 1;
1211             }
1212              
1213 63 100       129 if ($key) {
    50          
1214 43         96 $key =~ s/^\s+//;
1215 43         115 $key =~ s/\s+$//;
1216              
1217 43 100       99 if ($val) {
1218 38         73 $val =~ s/^\s+//;
1219 38         136 $val =~ s/\s+\.*$//;
1220             }
1221              
1222 43 50 33     122 if ( $key eq 'Checksum' && $last_key ) {
1223              
1224             # Special case for these bastids.
1225 0         0 my $new = $last_key;
1226 0         0 $new =~ s/\s+\S+$//;
1227 0         0 $key = $new . $SPACE . $key;
1228             }
1229              
1230 43         121 $conf{$key} = $val;
1231 43         66 $last_key = $key;
1232 43         64 $key = $EMPTY;
1233             }
1234             elsif ($val) {
1235 0         0 push @vals, $val;
1236             }
1237 63         243 $val = $EMPTY;
1238              
1239             }
1240             elsif ( $c eq ']' ) {
1241 43         58 $in_val = 1;
1242 43         46 $in_key = 0;
1243 43         97 $c = shift @chars;
1244              
1245             }
1246             elsif ($in_key) {
1247 348         641 $key .= $c;
1248              
1249             }
1250             elsif ($in_val) {
1251 201         419 $val .= $c;
1252             }
1253             }
1254             }
1255              
1256 20 50       42 unless ($key) {
1257 20         39 push @vals, $val;
1258             }
1259              
1260 20         35 foreach my $val (@vals) {
1261 20 50 33     146 if ( defined $val && length $val ) {
1262 0         0 $val =~ s/^\s+//;
1263 0         0 $val =~ s/\s+\.*$//;
1264             }
1265             }
1266              
1267 20 50       51 if ( @vals == 1 ) {
    0          
1268 20         33 $val = $vals[0];
1269             }
1270             elsif (@vals) {
1271 0         0 $val = \@vals;
1272             }
1273             else {
1274 0         0 $val = undef;
1275             }
1276              
1277 20 100       40 if (%conf) {
1278 15 50       33 $conf{_pre} = $val if $val;
1279 15         54 return \%conf;
1280             }
1281             else {
1282 5         18 return $val;
1283             }
1284             }
1285              
1286             #=item _decode_each_line
1287              
1288             sub _decode_each_line {
1289 0     0   0 my @lines = @_;
1290 0         0 my @decoded;
1291 0         0 foreach my $line (@lines) {
1292 0         0 my $decoded = _decode_lines($line);
1293 0 0 0     0 push @decoded, $decoded if defined $decoded && length $decoded;
1294             }
1295 0         0 return \@decoded;
1296             }
1297              
1298             #=item _decode_linktest
1299              
1300             sub _decode_linktest {
1301 1     1   438 my @lines = @_;
1302 1         3 my %decoded;
1303 1         3 foreach my $line (@lines) {
1304              
1305 20 100       83 if ( $line =~ s/^(\d+) \s+ //xms ) {
1306 5         13 my $line_id = $1;
1307 5         7 my ( $tm, $rt );
1308 5 50       81 if ( $line =~ s/\s+ (\d+ \s+ \w+) \s* $//xms ) {
1309 5         12 $rt = $1;
1310             }
1311 5 50       71 if ( $line =~ s/\s+ (\d+ \s+ \w+) \s* $//xms ) {
1312 5         12 $tm = $1;
1313             }
1314              
1315 5         16 my $d = _decode_lines( $line . "\n" );
1316 5         18 $decoded{tests}[$line_id] = $d;
1317 5         11 $decoded{tests}[$line_id]{'time'} = $tm;
1318 5         25 $decoded{tests}[$line_id]{rate} = $rt;
1319             }
1320              
1321             else {
1322 15         48 my $d = _decode_lines( $line . "\n" );
1323 15 100       49 if ($d) {
1324 10         14 while ( my ( $k, $v ) = each %{$d} ) {
  23         102  
1325 13         47 $decoded{$k} = $v;
1326             }
1327             }
1328             }
1329              
1330             }
1331 1         14 return \%decoded;
1332             }
1333              
1334             #=item _decode_sulog
1335              
1336             sub _decode_sulog {
1337 0     0     my @lines = @_;
1338 0           my @decoded;
1339             my $last_tm;
1340 0           foreach my $line (@lines) {
1341 0           my $decoded = _decode_lines($line);
1342              
1343 0 0         if ( defined $decoded ) {
1344 0 0         if ( $decoded->{'tm'} ) {
1345 0           $last_tm = $decoded->{'tm'};
1346 0           next;
1347             }
1348             else {
1349 0           $decoded->{'tm'} = $last_tm;
1350             }
1351 0 0         next unless $last_tm;
1352              
1353 0 0         push @decoded, $decoded if defined $decoded;
1354             }
1355             }
1356 0           return \@decoded;
1357             }
1358              
1359             #=item _decode_maclist
1360              
1361             sub _decode_maclist {
1362 0     0     my @lines = @_;
1363 0           my @decoded;
1364 0           my $total_entries = 0;
1365 0           my $current_tm = 0;
1366 0           foreach my $line (@lines) {
1367 0           $line =~ s/\r?\n$//;
1368 0           my ( $mac, $loc, $tm ) = $line =~ /
1369             ([0-9a-fA-F ]{17})\s+
1370             (.*)\s+
1371             tm\s+
1372             (\d+)
1373             /x;
1374              
1375 0 0         if ($mac) {
    0          
    0          
1376 0           $mac =~ s/\s+//g;
1377 0           $loc =~ s/^\s+//;
1378 0           $loc =~ s/\s+$//;
1379              
1380 0           my $suid = undef;
1381 0 0         if ( $loc =~ /suid\s+=\s+(\d+)/ ) {
1382 0           $suid = $1;
1383 0           $loc = undef;
1384             }
1385              
1386 0           push @decoded,
1387             {
1388             mac => $mac,
1389             loc => $loc,
1390             tm => $tm,
1391             suid => $suid,
1392             };
1393             }
1394             elsif ( $line =~ /(\d+)\s+entries/ ) {
1395 0           $total_entries = $1;
1396             }
1397             elsif ( $line =~ /current tm = (\d+)\s+sec/ ) {
1398 0           $current_tm = $1;
1399             }
1400             }
1401              
1402 0           map { $_->{'cur_tm'} = $current_tm } @decoded;
  0            
1403              
1404 0 0         if ( scalar @decoded == $total_entries ) {
1405 0           return \@decoded;
1406             }
1407             else {
1408 0           return;
1409             }
1410             }
1411              
1412             #=item _decode_freq
1413              
1414             sub _decode_freq {
1415 0     0     my @lines = @_;
1416 0           my $decoded = _decode_lines(@lines);
1417              
1418 0 0 0       if ( $decoded && $decoded->{ERR} ) {
1419 0           return $decoded;
1420             }
1421              
1422 0           LINE: foreach my $line (@lines) {
1423 0 0         if (my ( $channel, $polarity, $freq )
1424             = $line =~ /
1425             Ch \s+ \#(\d+)
1426             \s+
1427             (\w+)
1428             \s+
1429             \[ (\d+) \s+ MHz\]
1430             /ixms
1431             )
1432             {
1433 0           $decoded = {
1434             channel => $channel,
1435             polarity => $polarity,
1436             frequency => $freq,
1437             };
1438 0           last LINE;
1439             }
1440             }
1441 0           return $decoded;
1442             }
1443              
1444             1; # End of Net::Telnet::Trango
1445             __END__