#!/usr/bin/perl -w

use strict;
use Socket qw(getaddrinfo getnameinfo AF_UNSPEC AF_INET AF_INET6 SOCK_STREAM NI_NUMERICHOST);
use Getopt::Std;
use File::Basename;
use Cisco::CopyConfig ();

my $scriptname = basename($0);
my %opts;
$Getopt::Std::STANDARD_HELP_VERSION = 1;
$Getopt::Std::OUTPUT_HELP_VERSION = \*STDERR;
$main::VERSION = "1.0";

sub HELP_MESSAGE() {
	print STDERR <<EOF;
Usage: $scriptname [OPTION]... <hostname>...

	-m <tftpfile>	copy <tftpfile> from tftp server to running-config
	-w <tftpfile>	copy <tftpfile> from tftp server to startup-config
	-s		copy running-config to startup-config
	-c		copy running-config to tftp server
	-b		copy startup-config to tftp server
	-p <tftppath>	destination path on tftp server for -c option, overriding
			configuration file entry
	-q		suppress all normal output
	-h		help
EOF
	exit 0;

}

sub err($) {
	print STDERR @_;
	exit 1;
}

getopts('m:w:scbp:qh', \%opts);
HELP_MESSAGE() if $opts{h};

use vars qw($config);
require '/etc/ciscocopy.conf';

sub call_getaddrinfo($) {
        my ($arg) = @_;
        my ($err, @res) = getaddrinfo($arg, 0, {socktype => SOCK_STREAM});
        if ($err != 0) {
                err "can't resolv '$arg': $err(".(0+$err).")\n";
        }
        my $results;
        foreach my $ent (@res) {
                my $ip = (getnameinfo($ent->{addr}, NI_NUMERICHOST))[1];
                if ($ent->{family} == AF_INET6) {
                        push @{$results->{IPv6}}, $ip;
                } elsif ($ent->{family} == AF_INET) {
                        push @{$results->{IPv4}}, $ip;
                } else {
                        # ignore non-IPv4/IPv6 addresses
                }
        }
        return $results;
}

sub copyconfig($) {
	my $host = shift;
	my $ciscocopy = undef;
	my ($domain, $addr);
	my $addrinfo = call_getaddrinfo($host);
	
	if (0) {
	} elsif (exists $addrinfo->{IPv4}) {
		$domain = 'udp4';
		$addr = $addrinfo->{IPv4}[0];
		$config->{tftpaddr} = $config->{tftpaddr4};
	} elsif (exists $addrinfo->{IPv6}) {
		$domain = 'udp6';
		$addr = $addrinfo->{IPv6}[0];
		$config->{tftpaddr} = $config->{tftpaddr6};
	} else {
		die;
	}
	
	$ciscocopy = Cisco::CopyConfig->new(
		host		=> $addr,
		domain		=> $domain,
		( (defined($config->{community}) and length($config->{community}) > 0) ? (
			ver => 1,
			comm => $config->{community},
		) : (
			ver          => 3,
			username     => $config->{username},
			authprotocol => $config->{authproto},
			privprotocol => $config->{privproto},
			authpassword => $config->{authpw},
			privpassword => $config->{privpw},
		))
	);
	if (length $ciscocopy->{'err'}) {
		err $host . ": " . $ciscocopy->error() . "\n";
	}
	return $ciscocopy;
}

sub run2tftp($$) {
	my $tftppath = shift;
	my $host = shift;
	my $ciscocopy = copyconfig($host);

	printf "%s#copy running-config tftp://%s/%s/%s%s\n",
		$host, $config->{tftpaddr}, $tftppath, $host, $config->{extension} unless $opts{q};
	$ciscocopy->copy($config->{tftpaddr}, $tftppath."/".$host.$config->{extension})
		or print STDERR $host . ": " . $ciscocopy->error() . "\n";
}

sub tftp2run($$) {
	my $tftpfile = shift;
	my $host = shift;
	my $ciscocopy = copyconfig($host);

	printf "%s#copy tftp://%s/%s running-config\n",
		$host, $config->{tftpaddr}, $tftpfile unless $opts{q};
	$ciscocopy->merge($config->{tftpaddr}, $tftpfile)
		or print STDERR $host . ": " . $ciscocopy->error() . "\n";

}

sub tftp2start($$) {
	my $tftpfile = shift;
	my $host = shift;
	my $ciscocopy = copyconfig($host);

	printf "%s#copy tftp://%s/%s startup-config\n",
		$host, $config->{tftpaddr}, $tftpfile unless $opts{q};
	$ciscocopy->writestart($config->{tftpaddr}, $tftpfile)
		or print STDERR $host . ": " . $ciscocopy->error() . "\n";

}

sub run2start($) {
	my $host = shift;
	my $ciscocopy = copyconfig($host);

	printf "%s#copy running-config startup-config\n", $host unless $opts{q};
	$ciscocopy->save()
		or print STDERR $host . ": " . $ciscocopy->error() . "\n";

}

sub start2tftp($$) {
	my $tftppath = shift;
	my $host = shift;
	my $ciscocopy = copyconfig($host);

	printf "%s#copy startup-config tftp://%s/%s/%s%s\n",
		$host, $config->{tftpaddr}, $tftppath, $host, $config->{extension} unless $opts{q};
	$ciscocopy->copystart($config->{tftpaddr}, $tftppath."/".$host.$config->{extension})
		or print STDERR $host . ": " . $ciscocopy->error() . "\n";
}

# old script names for backwards compatibility
if ($scriptname eq "ciscomerge") {
	err "Usage: $0 <filename> <net_device_hostname>...\n" if (scalar @ARGV < 2);
	my $filename = shift;
	foreach my $host (@ARGV) {
		tftp2run($filename,$host);
	}
	exit 0;
} elsif ($scriptname eq "ciscosave") {
	err "Usage: $0 <net_device_hostname>...\n" if (scalar @ARGV < 1);
	foreach my $host (@ARGV) {
		run2start($host);
	}
	exit 0;
} elsif ($scriptname eq "ciscocopy") {
	err "Usage: $0 <tftp-dir> <net_device_hostname>...\n" if (scalar @ARGV < 2);
	my $dirname = shift;
	foreach my $host (@ARGV) {
		run2tftp($dirname,$host);
	}
	exit 0;
}

# current syntax

if ((!$opts{m} && !$opts{w} && !$opts{s} && !$opts{c} && !$opts{b}) || (scalar @ARGV < 1)) {
	HELP_MESSAGE();
}

my $tftppath;

if ($opts{c} || $opts{b}) {
	if ($opts{p}) {
		$tftppath = $opts{p};
	} elsif ($config->{tftppath}) {
		$tftppath = $config->{tftppath};
	} else {
		err "tftppath argument or configuration variable missing\n";
	}
}

{
	my $tftp = call_getaddrinfo($config->{tftpserver});
	$config->{tftpaddr6} = $tftp->{IPv6}[0];
	$config->{tftpaddr4} = $tftp->{IPv4}[0];
}

foreach my $host (@ARGV) {
	tftp2run($opts{m},$host) if $opts{m};
	tftp2start($opts{w},$host) if $opts{w};
	run2start($host) if $opts{s};
	run2tftp($tftppath,$host) if $opts{c};
	start2tftp($tftppath,$host) if $opts{b};
}	

exit 0;
