#! /usr/bin/perl
##
## rancid 3.13
## Copyright (c) 1997-2019 by Henry Kilmer and John Heasley
## All rights reserved.
##
## This code is derived from software contributed to and maintained by
## Henry Kilmer, John Heasley, Andrew Partan,
## Pete Whiting, Austin Schutz, and Andrew Fort.
##
## Redistribution and use in source and binary forms, with or without
## modification, are permitted provided that the following conditions
## are met:
## 1. Redistributions of source code must retain the above copyright
##    notice, this list of conditions and the following disclaimer.
## 2. Redistributions in binary form must reproduce the above copyright
##    notice, this list of conditions and the following disclaimer in the
##    documentation and/or other materials provided with the distribution.
## 3. Neither the name of RANCID nor the names of its
##    contributors may be used to endorse or promote products derived from
##    this software without specific prior written permission.
##
## THIS SOFTWARE IS PROVIDED BY Henry Kilmer, John Heasley AND CONTRIBUTORS
## ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
## TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
## PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COMPANY OR CONTRIBUTORS
## BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
## CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
## SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
## INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
## CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
## ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
## POSSIBILITY OF SUCH DAMAGE.
##
## It is the request of the authors, but not a condition of license, that
## parties packaging or redistributing RANCID NOT distribute altered versions
## of the etc/rancid.types.base file nor alter how this file is processed nor
## when in relation to etc/rancid.types.conf.  The goal of this is to help
## suppress our support costs.  If it becomes a problem, this could become a
## condition of license.
# 
#  The expect login scripts were based on Erik Sherk's gwtn, by permission.
# 
#  The original looking glass software was written by Ed Kern, provided by
#  permission and modified beyond recognition.
#
#  RANCID - Really Awesome New Cisco confIg Differ
#
#  rancid - generalized rancid module; command schedule is derived from the
#	    rancid.types.{base,conf} configurations.
#
# usage: rancid [-dhltCV] -t device_type [-f filename | hostname]
#
use 5.010;
use strict 'vars';
use warnings;
no warnings 'uninitialized';
use Exporter;
use Getopt::Std;
our($opt_d, $opt_f, $opt_h, $opt_l, $opt_t, $opt_C, $opt_V);
getopts('dfhlt:CV');
BEGIN {
    push(@INC, "/usr/local/lib/rancid");
}
use rancid;
our @ISA = qw(Exporter rancid);

sub usage()
{
    print STDERR "rancid [-dhlCV] -t device_type [-f filename | hostname]\n";
    exit 64;
}

if ($opt_h) {
    usage();
}

# basic initialization
rancidinit();

# load device type spec, build @commandtable and load modules
if (loadtype($devtype)) {
    die "Couldn't load device type spec for $rancid::devtype\n";
}
if (! defined($lscript)) {
    die "login script not defined for device type $rancid::devtype\n";
}
# if the first word of $script is not us (this script), exec the given
# script.
my(@script) = split(/\s+/, $script);
if (which($script[0]) ne which($0)) {
    # -[hCV] are not handled; they will have already been handled earlier.
    push(@script, "-d") if $opt_d;
    push(@script, "-l") if $opt_l;
    push(@script, "-f") if $opt_f;
    push(@script, $host);
    if ($debug) {
	print(STDERR "device script ($script[0]) does not appear to be me ($0)".
		     ": exec(".  join(" ", @script) .")\n");
    }
    # there is no way to prevent an error msg from exec such that the die error
    # is the only one displayed, without also leaving the child without a
    # working STDERR.
    exec(join(" ", @script)) || die "exec($script[0]) failed: $!\n";
}

# check that inloop, the input/main loop, is defined
if (!defined($inloop) || length($inloop) < 1) {
    die "inloop is not configured for device type $devtype";
}

# open the temporary file for the digested output
open(OUTPUT,">$host.new") || die "Can't open $host.new for writing: $!\n";
select(OUTPUT);
if (length($#modules)) {
    my($module);

    foreach $module (@modules) {
	(my $file = $module) =~ s/::/\//g;
	my($err) = 0;

	# call module->init(); we expect 0 as success, as god intended it
	eval "\$err = ". $module ."::init();";
	if ($@) {
	    printf(STDERR "loadtype: initializing $module failed: %s\n", $@);
	    exit 1;
	} elsif ($err) {
	    printf(STDERR "loadtype: %s::init() returned failure\n", $module);
	    exit 1;
	}
    }
}

# open the input; a pre-collected file or start a login for a login stream or
# temporary file
if ($file) {
    print(STDERR "opening file $host\n") if ($debug || $log);
    open(INPUT,"<$host") || die "open failed for $host: $!\n";
} else {
    my $cstr = $commandstr;
    $cstr =~ s/\"/\\\"/g;
    print(STDERR "executing $lscript -t $timeo -c\"$cstr\" $host\n") if ($debug || $log);
    system "$lscript -t $timeo -c \"$cstr\" $host </dev/null > $host.raw 2>&1" || die "clogin failed for $host: $!\n";
    open(INPUT, "< $host.raw") || die "clogin failed for $host: $!\n";
}

# loop over the input using the provided input/main loop
eval($inloop ."(*INPUT, *OUTPUT);") && die "${inloop} failed: $@\n";

print STDOUT "Done $lscript: $_\n" if ($log);
# Flush History
ProcessHistory("","","","");
# Cleanup
close(INPUT);
close(OUTPUT);

unlink("$host.raw") if (! $debug);

# check for completeness
if (scalar(%commands) || !$clean_run || !$found_end) {
    if (scalar(keys %commands) eq $commandcnt) {
	printf(STDERR "$host: missed cmd(s): all commands\n");
    } elsif (scalar(%commands)) {
	my($count, $i) = 0;
	for ($i = 0; $i < $#commands; $i++) {
	    if ($commands{$commands[$i]}) {
		if (!$count) {
		    printf(STDERR "$host: missed cmd(s): %s", $commands[$i]);
		} else {
		    printf(STDERR ", %s", $commands[$i]);
		}
		$count++;
	    }
	}
	if ($count) {
	    printf(STDERR "\n");
	}
    }
    if (!$clean_run || !$found_end) {
	print(STDERR "$host: End of run not found\n");
	if ($debug) {
	    print(STDERR "$host: clean_run is false\n") if (!$clean_run);
	    print(STDERR "$host: found_end is false\n") if (!$found_end);
	}
	system("/usr/bin/tail -1 $host.new");
    }
    unlink "$host.new" if (! $debug);
}
