[BACK]Return to update_trango.pl CVS log [TXT][DIR] Up to [local] / trango / Net-Telnet-Trango / scripts

File: [local] / trango / Net-Telnet-Trango / scripts / update_trango.pl (download)

Revision 1.10, Wed Dec 21 01:17:06 2005 UTC (18 years, 6 months ago) by andrew
Branch: MAIN
Changes since 1.9: +24 -11 lines

The Welcome banner is different on AP's.  tsk tsk dave.

clean up some error logging cuZ we need to know what didn't match, not what did.

make the config parser get rid of extraneous whitespace in the config file

#!/usr/bin/perl
# $RedRiver: update_trango.pl,v 1.9 2005/11/18 19:17:12 andrew Exp $
########################################################################
# update_trango.pl *** Updates trango foxes with a new firmware
# 
# 2005.11.15 #*#*# andrew fresh <andrew@mad-techies.org>
########################################################################
use strict;
use warnings;

use Net::TFTP;

my $config_file = shift || 'update_trango.conf';
my $max_tries = 3;



my $l = Mylogger->new( { log_prefix => 'UT' } );


$l->sp("Reading config file '$config_file'");
my $conf = read_conf($config_file);

$l->sp("  Hardware Type: $conf->{'type'}");
$l->sp("  File Name:     $conf->{'file_name'}");
$l->sp("  File Size:     $conf->{'file_size'}");
$l->sp("  File Checksum: $conf->{'file_cksum'}");
$l->sp("  FW Version:    $conf->{'ver'}");
$l->sp("  FW Checksum:   $conf->{'cksum'}");
$l->sp("");






foreach my $fox (@{ $conf->{'ips'} }) {
  $l->sp("Updating: $fox");

  ## Connect and login.
  my $t = new Trango::Telnet ({
    Host    => $fox,
    Timeout => 5,
  });

  $l->p("Connecting to $fox");
  my ($type, $version) = $t->connect();

  unless (defined $type && defined $version) {
    $l->sp("Error connecting!");
    next;
  }

  if ($type ne $conf->{'type'}) {
    $l->sp("Wrong type of unit ('$type' should be '$conf->{'type'}')");
    $t->close;
    next;
  }

  if ($version eq $conf->{'ver'}) {
    $l->sp("Already up to date with firmware version '$version'");
    $t->close;
    next;
  }

  $l->p("Logging in");
  $t->login($conf->{'password'}) || die "Couldn't login: $!";

  $l->p("Sending commands");
  ## Send commands
  if ( upload($t, $conf->{'file_name'}) ) {
    $l->p("Rebooting");
    $t->reboot;
  } else {
    $l->p("Exiting");
    $t->exit;
  }
  $t->close;
  $l->sp("");
}

sub upload
{
  my $t    = shift;
  my $file = shift;

  $l->p("Getting current version");
  my $ver = $t->ver;

  if (
    $ver->{'Firmware Version'}  eq $conf->{'ver'} && 
    $ver->{'Firmware Checksum'} eq $conf->{'cksum'}
  ) {
    $l->sp("Already updated!");
    return 1;
  }

  my $try = 0;
  while (1) {
    if ($try >= $max_tries) {
      $l->sp("Couldn't update in $max_tries tries!");
      return undef;
    }
    $try++;

    #sysinfo($self->{'_host'});
    
    $l->p("Enabling TFTPd");
    $t->enable_tftpd || die "Couldn't enable tftpd";

    $l->p("Uploading file ($file)");
    # use tftp to push the file up
    my $tftp = Net::TFTP->new($t->Host, Mode => 'octet');

    $tftp->put($file, $file) 
      or die "Error uploading: " . $tftp->error;

    # waitfor some sort of output
    # make sure it says 'Success.' otherwise error
    #print "LAST: " . $self->lastline;
    #my @lines = $self->getlines;
    #print Dump \@lines;
    
    $l->p("Checking upload ($conf->{'file_cksum'})");
    my $results = $t->tftpd;
    # check the 'File Length' against ???
    if ( $results->{'File Checksum'} ne $conf->{'file_cksum'}) {
      $l->sp(
        "File checksum '" . $results->{'File Checksum'} .
        "does not match config file '" . $conf->{'file_cksum'} . "'!"
      );
      next;
    } 
    $l->p("File checksum matches . . . ");

    if ($results->{'File Length'}   !~ /^$conf->{'file_size'} bytes/) {
      $l->sp(
        "File length '" . $results->{'File Length'} .
        "does not match config file '" . $conf->{'file_size'} . " bytes'!"
      );
      next;
    }
    $l->p("File length matches . . . ");

    if ( uc($results->{'File Name'}) ne uc($conf->{'file_name'}) ) {
      $l->sp(
        "File name '" . $results->{'File Name'} .
        "' does not match config file '" . $conf->{'file_name'} . "'!"
      );
      next;
    }
    $l->p("File name  matches . . . ");

    $l->p("Updating flash (new checksum '$conf->{'cksum'}')");
    unless ($results = $t->updateflash(
      $ver->{'Firmware Checksum'}, $conf->{'cksum'}
    ) ) {
      $l->sp("Couldn't update flash: $!");
      next;
    }

    unless (
      defined $results->{'Checksum'} && 
      $results->{'Checksum'} eq $conf->{'cksum'}
    ) {
      $l->sp("Saved checksum does not match config file!");
      next;
    }
    $l->p("Uploaded checksum ($results->{'Checksum'}) " . 
          "matches ($conf->{'cksum'})");
    
    $l->sp("Successfully updated!");
    return 1;
  }
}

sub read_conf
{
  my $file = shift;
  my %conf;
  my $in_ip_list = 0;
  open my $fh, $file or die "Couldn't open file $file: $!";
  while (<$fh>) {
    chomp;
    next if /^#/;
    next if /^$/;
    if ($in_ip_list) {
      s/\s+//g; # Whitespace is a no no

      if (/^(\d{1,3}\.\d{1,3}\.\d{1,3}\.)(\d{1,3})-(\d{1,3})/) {
        push @{ $conf{'ips'} }, $1 . $_ for ($2..$3);
      } else {
        push @{ $conf{'ips'} }, $_;
      }
    } else {
      my ($key, $val) = split /\s+/, $_, 2;

      if (lc($key) eq 'ips') {
        $in_ip_list = 1;
        next;
      }

      $key =~ s/^\s+//;
      $key =~ s/\s+$//;
      $val =~ s/^\s+//;
      $val =~ s/\s+$//;

      $conf{ lc($key) } = $val;
    }
  }
  close $fh;

  #print Dump \%conf;
  foreach (
    'password', 
    'file_name', 'file_size', 'file_cksum', 
    'ver', 'cksum', 'ips',
  ) {
    die "No $_ specified in config file!"
      if (not exists $conf{$_});
  }

  #print Dump \%conf;
  #exit;
  return \%conf;
}

package Trango::Telnet;
use base 'Net::Telnet';

my %PRIVATE = (
  connected => 0,
  logged_in => 0,
);

sub new {
  my $class = shift;
  my $args = shift || {};

  $args->{'Timeout'} ||= 5;
  $args->{'Prompt'}  ||= '/#> *$/';

  foreach my $key (keys %{ $args }) {
    $PRIVATE{$key} = $args->{$key};
  }

  my $self = $class->SUPER::new(%{ $args });
  bless $self;

  #bless $self, $package;
  return $self;
}

sub connect
{
  my $self = shift;

  unless ( $self->open( 
      Host => $PRIVATE{'Host'},
      Errmode => 'return',
  ) ) {
    $! = "Couldn't connect to $self->{'Host'}.  Connection timed out.";
    return undef, undef;
  }
  #$self->dump_log('dump.log');

  ## Login to remote host.
  unless ($self->waitfor(
    -match => '/password: ?$/i',
    -errmode => "return",
  ) ) {
    $! = "problem connecting to host ($self->{'Host'}): " . $self->lastline;
    return undef;
  }

  $self->login_banner($self->lastline);

  $PRIVATE{'connected'} = 1;

  return ($self->host_type, $self->firmware_version);
}

sub login
{
  my $self = shift;

  my $password = shift;

  $self->print($password);
  unless ($self->waitfor(
    -match => $self->prompt,
    -errmode => "return",
  ) ) {
    $! = "login ($self->{'Host'}) failed: " . $self->lastline;
    return undef;
  }

  $PRIVATE{'logged_in'} = 1;

  return 1;
}

sub login_banner
{
  my $self = shift;

  my $banner = shift || $PRIVATE{'login_banner'};

  my ($type, $ver) = $banner =~ 
    /Welcome to Trango Broadband Wireless (.+)[\s-](\w+)$/i;

  $self->host_type($type);
  $self->firmware_version($ver); 

  return $banner;
}

sub host_type 
{
  my $self = shift;

  my $type = shift || $PRIVATE{'host_type'};
  $PRIVATE{'host_type'} = $type;

  return $type;
}

sub firmware_version
{
  my $self = shift;

  my $ver = shift || $PRIVATE{'firmware_version'};
  $PRIVATE{'firmware_version'} = $ver;
  
  return $ver
}

sub Host
{
  my $self = shift;
  
  my $host = shift || $PRIVATE{'Host'};
  $PRIVATE{'HOST'} = $host;

  return $host;
}


sub reboot
{
  my $self = shift;

  $self->print("reboot\n");
  $self->getline;

  return 1;
}

sub exit
{
  my $self = shift;

  $self->print("exit\n");
  $self->getline;

  return 1;
}

sub ver
{
  my $self = shift;

  return $self->cmd('ver');
}

sub tftpd
{
  my $self = shift;

  return $self->cmd('tftpd', 'Success.');
}

sub sysinfo
{
  my $self = shift;

  return $self->cmd('sysinfo', 'Success.');
}

sub enable_tftpd
{
  my $self = shift;

  my $vals = $self->cmd('tftpd on', 'Success.');

  if ($vals->{'Tftpd'} eq 'listen') {
    return $vals;
  } else {
    return undef;
  }
}

sub updateflash
{
  my $self = shift;

  my $old = shift;
  my $new = shift;

  return undef unless $new;

  return $self->cmd("updateflash mainimage $old $new", 'Success.', 90);
}

sub cmd
{
  my $self = shift;

  my $string = shift;
  my $expect_last = shift;
  my $timeout = shift || $PRIVATE{'Timeout'};

  unless (defined $string) {
    $! = "No command passed";
    return undef;
  }

  unless ($PRIVATE{'connected'}) {
    $! = "Not connected";
    return undef;
  }

  unless ($PRIVATE{'logged_in'}) {
    $! = "Not logged in";
    return undef;
  }

  my @lines = $self->SUPER::cmd(String => $string, Timeout => $timeout);

  my $vals = _decode_lines(@lines);

  unless ($expect_last) {
    return $vals;
  }

  my $last = $self->lastline;

  if ($last =~ /$expect_last$/) {
    return $vals;
  } else {
    warn "Error with command ($string): $last";
    return undef;
  }
}

sub _decode_lines
{
  my @lines = @_;

  my %conf;

  my $key = '';
  my $val = '';
  my $in_key = 0;
  my $in_val = 0;

  foreach my $line (@lines) {
    my @chars = split //, $line;

    my $last_key = '';
    foreach my $c (@chars) {

      if ($c eq '[' || $c eq "\r" || $c eq "\n") {
        if ($c eq '[') {
          $in_key = 1;
          $in_val = 0;
        } else {
          $in_key = 0;
          $in_val = 0;
        }

        if ($key) {
          $key =~ s/^\s+//;
          $key =~ s/\s+$//;

          $val =~ s/^\s+//;
          $val =~ s/\s+$//;

          if ($key eq 'Checksum' && $last_key) {
            # Special case for these bastids.
            my $new = $last_key;
            $new =~ s/\s+\S+$//;
            $key = $new . " " . $key;
          }

          $last_key = $key;
          $conf{$key} = $val;
          $key = '';
          $val = '';
        }

      } elsif ($c eq ']') {
        $in_val = 1;
        $in_key = 0;
        $c = shift @chars;

      } elsif ($in_key) {
        $key .= $c;

      } elsif ($in_val) {
        $val .= $c;
      }
    }
  }
  #print Dump \%conf;

  if (%conf) {
    return \%conf;
  } else {
    return \@lines;
  }
}



package Mylogger;

use Fcntl ':flock'; # import LOCK_* constants
#use YAML;
use constant LOG_PRINT => 128;
use constant LOG_SAVE  =>  64;

DESTROY {
  my $self = shift;
  if ($self->{'MYLOG'}) {
    $self->p("Closing log ($self->{'log_path'}/$self->{'log_file'})");
    close $self->{'MYLOG'};
  }
}

sub new {
  my $package = shift;
  my $self = shift || {};

  $self->{'base_path'}  ||= '.';
  $self->{'log_path'}   ||= $self->{'base_path'};
  $self->{'log_prefix'} ||= 'LOG';
  $self->{'log_file'}   ||= GetLogName(
    $self->{'log_prefix'}, 
    $self->{'log_path'}
  );
  bless $self, $package;
}

sub s
{
  my $self = shift;
  my $m = shift;
  return $self->mylog($m, LOG_SAVE);
}

sub p
{
  my $self = shift;
  my $m = shift;
  return $self->mylog($m, LOG_PRINT);
}

sub sp
{
  my $self = shift;
  my $m = shift;
  return $self->mylog($m, LOG_SAVE | LOG_PRINT);
}

sub mylog
{
  my $self = shift;

  my $thing = shift;
  chomp $thing;

  my $which = shift;

  my $MYLOG;
  if ($which & LOG_PRINT) {
    print $thing, "\n";
  }

  if ($which & LOG_SAVE) {
    if ($self->{'MYLOG'}) {
      $MYLOG = $self->{'MYLOG'};
    } else {
      unless ($MYLOG) {
		open ($MYLOG, '>>', $self->{'log_path'} . '/' . $self->{'log_file'}) 
          or die "Couldn't open logfile!\n";
        my $ofh = select $MYLOG;
        $|=1;
        select $ofh;
        $self->{'MYLOG'} = $MYLOG;

        $self->p("Opened log ($self->{'log_path'}/$self->{'log_file'})");
      }
    }
    flock($MYLOG, LOCK_EX);
    print $MYLOG (scalar gmtime), "\t", $thing, "\n" 
      or die "Couldn't print to MYLOG: $!";
    flock($MYLOG, LOCK_UN);
  }
}

sub GetLogName
{
  my $prefix  = shift || die "Invalid prefix passed for log";

  my $logdate = GetLogDate();
  my $logver  = 0;
  my $logname;

  do {
    $logname = $prefix . $logdate . sprintf("%02d", $logver) . '.log';
    $logver++;
  } until (not -e $logname);

  return $logname;
}

sub GetLogDate
{
  my ($sec,$min,$hour,$mday,$mon,$year,,,) = localtime();

  $mon++;
  $year += 1900;

  if ($min  < 10) { $min  = "0$min"  }
  if ($sec  < 10) { $sec  = "0$sec"  }
  if ($hour < 10) { $hour = "0$hour" }
  if ($mday < 10) { $mday = "0$mday" }
  if ($mon  < 10) { $mon  = "0$mon"  }

  my $time = $year . $mon . $mday;

  return $time;
}