[BACK]Return to Trango.pm CVS log [TXT][DIR] Up to [local] / trango / Net-Telnet-Trango / lib / Net / Telnet

Annotation of trango/Net-Telnet-Trango/lib/Net/Telnet/Trango.pm, Revision 1.25

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

FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>