#!/usr/local/bin/perl

# backup cisco configs 20160522
# changes since previous: Override file is now spelled Credentials

use strict;
use Getopt::Std;
use Net::Telnet::Cisco;

my %GENERIC = ( un => 'username',
                pw => 'password',
                en => 'enablepw',
              );

# make subdirectories in $dir called the FQDN of the routers you want to back up
# if you need seperate credentials for this router,
#  inside those subdirectories, in a file called Credentials put:
#un: username
#pw: password
#en: enable_password


my %opt;
getopts('Dd:r:', \%opt);
# Debug
# dir
# regex

my $dir = $opt{d} ? $opt{d} : '/usr/sys/routers';

-d $dir || die "$dir does not exist - can not continue.";

my $login = (-f "$dir/Credentials") ? build_cred($dir) : \%GENERIC;

my $changed = 0;
my $time = time();
opendir(my $D, $dir) || die "cannot opendir $dir: $!\n";
for my $host (grep {!/^\./} readdir $D){
    next if $host eq 'Credentials'; # special
    if( -f '$dir/$host/Credentials' ){
        $login = build_cred("$dir/$host");
        next;
    }

    unless( $host =~ /$opt{r}/ ){
        debug( "$host: skipping as per regex: $opt{r}" );
        next;
    }
    debug( "$host: working" );

    opendir(my $sdir, "$dir/$host") || die "cannot opendir $dir/$host: $!\n";
    my $last_file;
    for my $file (sort grep {/^\d/} readdir $sdir){
        if( (stat("$dir/$host/$file"))[9] < ($time-5184000) ){
            # file is older than 60 days
            verbose( "$host/$file is older than 60 days" );
            #unlink($dir/$host/$file) || verbose( "could not unlink $host/$file: $!" );
        }
        $last_file = $file;
    }
    closedir $sdir;
    my $socket = Net::Telnet::Cisco->new(Host    => $host,
                                         Prompt  => '/[\$%#>]\s*$/',
                                         Timeout => 5,
                                         Errmode => 'return',
                                        );

    unless( $socket ){
        verbose( "$host: could not connect" );
        next;
    }

    $socket->login($login->{un},$login->{pw});
    unless($socket->is_enabled){
        unless($socket->enable($login->{en})){
            die "Cant enable: " . $socket->errmsg;
        }
    }
    debug( "$host: enabled" );
    $socket->cmd("term len 0");
    my @result = $socket->cmd("sho run");
    $socket->cmd("");
    $socket->print("exit");

    die "bad config?" unless @result > 25;

    for(1..3){ shift @result; }; # garbage
    for(1..2){ pop @result; }; # more garbage

    my $diff;
    if( ($last_file) && (open(my $last, "$dir/$host/$last_file")) ){
        my $n = -1;  # @result is 0 based
        while(<$last>){
            $n++;
            next if /^ntp clock-period/ || /^\!/;
            next if $_ eq $result[$n];

            $diff = 1;
            last;
        }
        close $last;
    }else{
        $diff = 2;
    }

    next unless $diff;

    debug( "$host: configs different via mode $diff" );

    $changed++;

    my($mday,$mon,$year) = (localtime)[3,4,5];
    $mon++; $year+=1900;
    my $date = sprintf('%04d%02d%02d',$year, $mon, $mday);
    open(my $cfg, ">$dir/$host/${date}.config") || die "cannot write to $dir/$host/${date}.config: $!\n";
    for(@result){
        print $cfg $_;
    }
    close $cfg;
}
closedir $D;

verbose( "$changed configs updated." );

sub build_cred {
    my $dir = shift;
   
    debug( "caught $dir/Credentials file" );

    my %custom;
    open(my $cred, "$dir/Credentials");
    while(<$cred>){
        chop;
        if( /^(un|pw|en)\s*:\s*(\S+)/ ){
            $custom{$1} = $2;
            debug( "custom{$1} = $custom{$1}" );
        }else{
            verbose( "unknown syntax in $dir/Credentials" );
        }
    }
    close $cred;

    return \%custom;
}

sub verbose {
    my ($msg) = join('', @_);

    warn "$msg\n";
}

sub debug {
    verbose @_ if $opt{D};
}
