#
# Dump ethernet port detail (duplex, autoneg settings, etc) from
# 3Com SuperStack II switches
#
# $Id: EtherDetail.pm,v 1.10 1999/06/03 15:09:11 trockij Exp $
#
# Copyright (C) 1998 Jim Trocki
#
#    This program is free software; you can redistribute it and/or modify
#    it under the terms of the GNU General Public License as published by
#    the Free Software Foundation; either version 2 of the License, or
#    (at your option) any later version.
#
#    This program is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU General Public License for more details.
#
#    You should have received a copy of the GNU General Public License
#    along with this program; if not, write to the Free Software
#    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#

package A3Com::EtherDetail;

use strict;
use vars qw($VERSION);
use A3Com::Base;
use Expect;

$VERSION = "0.01";

sub _convert_etherdetail;

sub new {
    my $proto = shift;
    my $class = ref($proto) || $proto;
    my %vars = @_;

    my $self  = {};
    $self->{"PORTS"} = {};
    $self->{"ERROR"} = "";
    $self->{"SWITCH"} = "";
    $self->{"READ_METHOD"} = \&telnet_read;
    $self->{"LOGIN"} = "read";
    $self->{"PASSWORD"} = "";
    $self->{"LOG"} = 0;
    $self->{"CACHE"} = 1;
    $self->{"GLOBALCACHE"} = 1;
    $self->{"CACHETIME"} = $A3Com::Base::DEFAULT_CACHETIME;
    $self->{"FILE"} = "";

    $self->{"GLOBALCACHEDIR"} = "/usr/local/share/a3com";
    if (defined $ENV{"HOME"}) {
	$self->{CACHEDIR} = $ENV{"HOME"} . "/.a3com";
    } else {
	$self->{"CACHEDIR"} = ".a3com";
    }

    my %c = &A3Com::Base::_read_conf ($self, $ENV{"A3COM_CONF"});
    for (keys %c) {
        $self->{$_} = $c{$_};
    }

    if (defined $ENV{"A3COM_GLOBALCACHEDIR"}) {
    	$self->{"GLOBALCACHEDIR"} = $ENV{"A3COM_GLOBALCACHEDIR"};
    }

    if (defined $ENV{"A3COM_CACHEDIR"}) {
        $self->{"CACHEDIR"} = $ENV{"A3COM_CACHEDIR"};
    }
    $self->{"_LOADED"} = 0;

    for my $k (keys %vars) {
    	$self->{$k} = $vars{$k} if ($vars{$k} ne "");
    }

    bless ($self, $class);
    return $self;
}


sub cachedir {
    my $self = shift;
    if (@_) { $self->{"CACHEDIR"} = shift }
    return $self->{"CACHEDIR"};
}


sub globalcachedir {
    my $self = shift;
    if (@_) { $self->{"GLOBALCACHEDIR"} = shift }
    return $self->{"GLOBALCACHEDIR"};
}


sub cachetime {
    my $self = shift;
    if (@_) { $self->{"CACHETIME"} = shift }
    return $self->{"CACHETIME"};
}


sub switch {
    my $self = shift;
    if (@_) { $self->{"SWITCH"} = shift }
    return $self->{"SWITCH"};
}


sub error {
    my $self = shift;
    if (@_) { $self->{"ERROR"} = shift }
    return $self->{"ERROR"};
}


sub log {
    my $self = shift;
    if (@_) { $self->{"LOG"} = shift }
    return $self->{"LOG"};
}


sub cache {
    my $self = shift;
    if (@_) { $self->{"CACHE"} = shift }
    return $self->{"CACHE"};
}


sub globalcache {
    my $self = shift;
    if (@_) { $self->{"GLOBALCACHE"} = shift }
    return $self->{"GLOBALCACHE"};
}


sub port_val {
    my $self = shift;
    my ($portnum, $var) = @_;

    $self->{"ERROR"} = "";

    return undef if (!defined $self->_load_cache ($self));

    if (!defined $self->{"PORTS"}->{$portnum}->{$var}) {
    	$self->{"ERROR"} = "value $portnum/$var not found";
	return undef;
    }
    return $self->{"PORTS"}->{$portnum}->{$var};
}


sub ports {
    my $self = shift;

    $self->{"ERROR"} = "";

    return undef if (!defined $self->_load_cache ($self));

    return sort {$a <=> $b} keys %{$self->{"PORTS"}};
}


#
# write ether detail
#
sub file_write {
    my $self = shift;
    my $file = shift;

    my ($port, $key);

    $self->{"ERROR"} = "";

    if (!defined $file) {
	my $cd;

	if ($self->{"GLOBALCACHE"}) {
	    $cd = "GLOBALCACHEDIR";
	} else {
	    $cd = "CACHEDIR";
	}

	if (! -d $self->{$cd}) {
	    $self->{"ERROR"} = "$cd is not a directory";
	    return undef;
	}
	$file = "$self->{$cd}/$self->{SWITCH}.ed";
    }

    if (!open (O, ">$file")) {
    	$self->{"ERROR"} = "$!";
	return undef;
    }

    print O "#\n# ethernet detail table for $self->{SWITCH} at " . time . "\n#\n";
    foreach $port (sort {$a <=> $b} keys %{$self->{"PORTS"}}) {
	foreach $key (sort keys %{$self->{"PORTS"}->{$port}}) {
	    print O "$port $key $self->{PORTS}->{$port}->{$key}\n";
	}
    }
    close (O);
}


#
# read bridge table
#
sub file_read {
    my $self = shift;
    my $file = shift;
    my ($port, $key, $val);

    $self->{"ERROR"} = "";

    if (!defined $file) {
        if (! -d $self->{"CACHEDIR"}) {
            $self->{"ERROR"} = "CACHEDIR is not a directory";
            return undef;
        }
        $file = "$self->{CACHEDIR}/$self->{SWITCH}.ed";
    }

    if (!open (I, $file)) {
    	$self->{"ERROR"} = "$!";
	return undef;
    }

    $self->{"PORTS"} = {};

    while (<I>) {
	chomp;
	next if (/^\s*#/ || /^\s*$/);
    	($port, $key, $val) = split (/\s+/, $_, 3);
	$self->{"PORTS"}->{$port}->{$key} = $val;
    }
    close (I);
}


sub telnet_read {
    my $self = shift;

    my ($ADDRS, $s, @n);

    $self->{"ERROR"} = "";

    $s = Expect->spawn ("telnet $self->{SWITCH}");
    if (!$s) {
	$self->{"ERROR"} = "could not telnet to $self->{SWITCH}";
    	return undef;
    }
    $s->log_stdout($self->{"LOG"});

    #
    # log in
    #
    if (!$s->expect (15, ("Select access level (read, write, administer): "))) {
	$self->{"ERROR"} = "did not get login prompt in time";
	return undef;
    }
    $s->clear_accum;

    print $s "$self->{LOGIN}\r";

    if (!$s->expect (8, ("Password:"))) {
	$self->{"ERROR"} = "did not get password prompt";
	return undef;
    }
    $s->clear_accum;

    print $s "$self->{PASSWORD}\r";

    @n = $s->expect (8, "Incorrect password", "Select menu option: ");
    if ($n[0] == 1) {
	$s->hard_close();
	$self->{"ERROR"} = "incorrect password for $self->{SWITCH}";
	return undef;
    } elsif (!defined $n[0] && $n[1] eq "1:TIMEOUT") {
	$s->hard_close();
	$self->{"ERROR"} = "timeout logging in to $self->{SWITCH}, it says {$n[3]}";
	return undef;
    }
    $s->clear_accum;

    print $s "ether detail all\r";

    @n = $s->expect (30, "Select menu option:");
    $ADDRS = $n[3];

    if (!defined ($n[0])) {
	$s->hard_close();
	$self->{"ERROR"} = "error ($n[1]) during ethernet data gather for $self->{SWITCH}\n$n[3]";
	return undef;

    }

    $s->clear_accum;

    #
    # log out politely
    #
    print $s "logout\r";
    if ($s->expect (8, "Exiting") != 1) {
	$s->hard_close();
	$self->{"ERROR"} = "did not get logout confirmation";
	return undef;
    }

    $s->hard_close();

    _convert_etherdetail $self, $ADDRS;
}


sub _convert_etherdetail {
    my $self = shift;
    my $ADDRS = shift;
    my ($l);
    my ($port, @fields, @vals);

    $self->{"PORTS"} = {};

    while ($ADDRS =~ /([^\n\r]+\n\r)/sg) {
	$l = $1;
	$l =~ s/[\r\n]*$//;
	
	next if ($l =~ /^\s*$/);

	if ($l =~ /^port/) {
	    ($port, @fields) = split (/\s+/, $l);
	    for (@fields) { s/^\s*//; s/\s*$//; }
	    next;

	} elsif ($l =~ /^(\d+)/) {
	    $port = $1;
	    $l =~ s{(\([^)]+\))} {my $t = $1; $t =~ s/\s+//g; $t}e;
	    my $junk;
	    ($junk, @vals) = split (/\s+/, $l, 1 + @fields);
	    for (my $i=0; $i<@fields; $i++) {
	    	$self->{"PORTS"}->{$port}->{$fields[$i]} = $vals[$i];
	    }
	    next;
	}
    }
}


sub _load_cache {
    my $self = shift;

    if (!$self->{"_LOADED"}) {
	if ($self->{"GLOBALCACHE"}) {
	    return undef if (!defined (A3Com::Base::_load_global ($self, ".ed")));
	} else {
	    return undef if (!defined (A3Com::Base::_load ($self, ".ed")));
	}
	$self->{"_LOADED"} = 1;
    }
}
