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