1#!/usr/local/bin/perl
2##
3## @PACKAGE@ @VERSION@
4## Copyright (c) @COPYYEARS@ by Henry Kilmer and John Heasley
5## All rights reserved.
6##
7## This code is derived from software contributed to and maintained by
8## Henry Kilmer, John Heasley, Andrew Partan,
9## Pete Whiting, Austin Schutz, and Andrew Fort.
10##
11## Redistribution and use in source and binary forms, with or without
12## modification, are permitted provided that the following conditions
13## are met:
14## 1. Redistributions of source code must retain the above copyright
15##    notice, this list of conditions and the following disclaimer.
16## 2. Redistributions in binary form must reproduce the above copyright
17##    notice, this list of conditions and the following disclaimer in the
18##    documentation and/or other materials provided with the distribution.
19## 3. Neither the name of RANCID nor the names of its
20##    contributors may be used to endorse or promote products derived from
21##    this software without specific prior written permission.
22##
23## THIS SOFTWARE IS PROVIDED BY Henry Kilmer, John Heasley AND CONTRIBUTORS
24## ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
25## TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
26## PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COMPANY OR CONTRIBUTORS
27## BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
28## CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
29## SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
30## INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
31## CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
32## ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
33## POSSIBILITY OF SUCH DAMAGE.
34##
35## It is the request of the authors, but not a condition of license, that
36## parties packaging or redistributing RANCID NOT distribute altered versions
37## of the etc/rancid.types.base file nor alter how this file is processed nor
38## when in relation to etc/rancid.types.conf.  The goal of this is to help
39## suppress our support costs.  If it becomes a problem, this could become a
40## condition of license.
41#
42#  The expect login scripts were based on Erik Sherk's gwtn, by permission.
43#
44#  The original looking glass software was written by Ed Kern, provided by
45#  permission and modified beyond recognition.
46#
47# hacked version of Hank's rancid - this one tries to deal with Netscalers.
48# Hacks from Anshuman Kanwar.
49#
50#  RANCID - Really Awesome New Cisco confIg Differ
51#
52# usage: nsrancid [-dltCV] [-f filename | hostname]
53#
54use Getopt::Std;
55getopts('dflt:CV');
56if ($opt_V) {
57    print "@PACKAGE@ @VERSION@\n";
58    exit(0);
59}
60$log = $opt_l;
61$debug = $opt_d;
62$file = $opt_f;
63$host = $ARGV[0];
64$clean_run = 0;
65$found_end = 0;
66$timeo = 90;				# nslogin timeout in seconds
67
68my(@commandtable, %commands, @commands);# command lists
69my($aclsort) = ("ipsort");		# ACL sorting mode
70my($filter_commstr);			# SNMP community string filtering
71my($filter_osc);			# oscillating data filtering mode
72my($filter_pwds);			# password filtering mode
73
74$prompt = ">";
75
76# This routine is used to print out the router configuration
77sub ProcessHistory {
78    my($new_hist_tag,$new_command,$command_string,@string) = (@_);
79    if ((($new_hist_tag ne $hist_tag) || ($new_command ne $command))
80	&& scalar(%history)) {
81	print eval "$command \%history";
82	undef %history;
83    }
84    if (($new_hist_tag) && ($new_command) && ($command_string)) {
85	if ($history{$command_string}) {
86	    $history{$command_string} = "$history{$command_string}@string";
87	} else {
88	    $history{$command_string} = "@string";
89	}
90    } elsif (($new_hist_tag) && ($new_command)) {
91	$history{++$#history} = "@string";
92    } else {
93	print "@string";
94    }
95    $hist_tag = $new_hist_tag;
96    $command = $new_command;
97    1;
98}
99
100sub numerically { $a <=> $b; }
101
102# This is a sort routine that will sort numerically on the
103# keys of a hash as if it were a normal array.
104sub keynsort {
105    local(%lines) = @_;
106    local($i) = 0;
107    local(@sorted_lines);
108    foreach $key (sort numerically keys(%lines)) {
109	$sorted_lines[$i] = $lines{$key};
110	$i++;
111    }
112    @sorted_lines;
113}
114
115# This is a sort routine that will sort on the
116# keys of a hash as if it were a normal array.
117sub keysort {
118    local(%lines) = @_;
119    local($i) = 0;
120    local(@sorted_lines);
121    foreach $key (sort keys(%lines)) {
122	$sorted_lines[$i] = $lines{$key};
123	$i++;
124    }
125    @sorted_lines;
126}
127
128# This is a sort routine that will sort on the
129# values of a hash as if it were a normal array.
130sub valsort{
131    local(%lines) = @_;
132    local($i) = 0;
133    local(@sorted_lines);
134    foreach $key (sort values %lines) {
135	$sorted_lines[$i] = $key;
136	$i++;
137    }
138    @sorted_lines;
139}
140
141# This is a numerical sort routine (ascending).
142sub numsort {
143    local(%lines) = @_;
144    local($i) = 0;
145    local(@sorted_lines);
146    foreach $num (sort {$a <=> $b} keys %lines) {
147	$sorted_lines[$i] = $lines{$num};
148	$i++;
149    }
150    @sorted_lines;
151}
152
153# This is a sort routine that will sort on the
154# ip address when the ip address is anywhere in
155# the strings.
156sub ipsort {
157    local(%lines) = @_;
158    local($i) = 0;
159    local(@sorted_lines);
160    foreach $addr (sort sortbyipaddr keys %lines) {
161	$sorted_lines[$i] = $lines{$addr};
162	$i++;
163    }
164    @sorted_lines;
165}
166
167# These two routines will sort based upon IP addresses
168sub ipaddrval {
169    my(@a) = ($_[0] =~ m#^(\d+)\.(\d+)\.(\d+)\.(\d+)$#);
170    $a[3] + 256 * ($a[2] + 256 * ($a[1] +256 * $a[0]));
171}
172sub sortbyipaddr {
173    &ipaddrval($a) <=> &ipaddrval($b);
174}
175
176# This routine parses "show ns ns.conf"
177sub ShowConfig {
178    print STDERR "    In ShowConfig: $_" if ($debug);
179
180    while (<INPUT>) {
181	tr/\015//d;
182	last if (/ Done$/);
183	last if (/^$prompt/);
184	next if (/^(\s*|\s*$cmd\s*)$/);
185	next if (/^Reading configuration information/);
186	next if (/^Can\'t find object or class named \"\-all\"\s*$/);
187	next if (/lock-address .*$/);
188	next if (/^\# *uptime +\d+\s*$/);
189	next if (/^\# last modified/i);
190	if (/community label /) {
191	    if ($filter_commstr) {
192		$_ =~ s/community label .*$/community label <removed>/;
193	    }
194	}
195	return(1) if /(invalid command name)/;
196	ProcessHistory("","","","$_");
197    }
198
199    if (/ Done$/) {
200	$found_end = 1;
201	$clean_run = 1;
202	return(1);
203    }
204    return(0);
205}
206
207# This routine parses single command's that return no required info
208sub RunCommand {
209    print STDERR "    In RunCommand: $_" if ($debug);
210
211    while (<INPUT>) {
212	tr/\015//d;
213	last if (/^$prompt/);
214	next if (/^(\s*|\s*$cmd\s*)$/);
215    }
216    return(0)
217}
218
219# Main
220@commandtable = (
221	{'show ns ns.conf'	=> 'ShowConfig'},
222);
223# Use an array to preserve the order of the commands and a hash for mapping
224# commands to the subroutine and track commands that have been completed.
225@commands = map(keys(%$_), @commandtable);
226%commands = map(%$_, @commandtable);
227$commandcnt = scalar(keys %commands);
228
229$commandstr=join(";",@commands);
230$cmds_regexp = join("|", map quotemeta($_), @commands);
231
232if (length($host) == 0) {
233    if ($file) {
234	print(STDERR "Too few arguments: file name required\n");
235	exit(1);
236    } else {
237	print(STDERR "Too few arguments: host name required\n");
238	exit(1);
239    }
240}
241if ($opt_C) {
242    print "nslogin -t $timeo -c\'$commandstr\' $host\n";
243    exit(0);
244}
245open(OUTPUT,">$host.new") || die "Can't open $host.new for writing: $!\n";
246select(OUTPUT);
247# make OUTPUT unbuffered if debugging
248if ($debug) { $| = 1; }
249
250if ($file) {
251    print(STDERR "opening file $host\n") if ($debug || $log);
252    open(INPUT,"<$host") || die "open failed for $host: $!\n";
253} else {
254    print(STDERR "executing nslogin -t $timeo -c\"$commandstr\" $host\n") if ($debug || $log);
255    system "nslogin -t $timeo -c \"$commandstr\" $host </dev/null > $host.raw 2>&1" || die "nslogin failed for $host: $!\n";
256    open(INPUT, "< $host.raw") || die "nslogin failed for $host: $!\n";
257}
258
259# determine ACL sorting mode
260if ($ENV{"ACLSORT"} =~ /no/i) {
261    $aclsort = "";
262}
263# determine community string filtering mode
264if (defined($ENV{"NOCOMMSTR"}) &&
265    ($ENV{"NOCOMMSTR"} =~ /yes/i || $ENV{"NOCOMMSTR"} =~ /^$/)) {
266    $filter_commstr = 1;
267} else {
268    $filter_commstr = 0;
269}
270# determine oscillating data filtering mode
271if (defined($ENV{"FILTER_OSC"}) && $ENV{"FILTER_OSC"} =~ /no/i) {
272    $filter_osc = 0;
273} else {
274    $filter_osc = 1;
275}
276# determine password filtering mode
277if ($ENV{"FILTER_PWDS"} =~ /no/i) {
278	$filter_pwds = 0;
279} elsif ($ENV{"FILTER_PWDS"} =~ /all/i) {
280	$filter_pwds = 2;
281} else {
282	$filter_pwds = 1;
283}
284
285#print STDOUT "$prompt \n";
286ProcessHistory("","","","!RANCID-CONTENT-TYPE: netscaler\n!\n");
287TOP: while(<INPUT>) {
288    tr/\015//d;
289
290    if (/^Error:/) {
291	print STDOUT ("$host nslogin error: $_");
292	print STDERR ("$host nslogin error: $_") if ($debug);
293	$clean_run = 0;
294	last;
295    }
296    while (/$prompt\s*($cmds_regexp)\s*$/) {
297	$cmd = $1;
298	print STDERR ("HIT COMMAND:$_") if ($debug);
299	if (! defined($commands{$cmd})) {
300	    print STDERR "$host: found unexpected command - \"$cmd\"\n";
301	    $clean_run = 0;
302	    last TOP;
303	}
304	$rval = &{$commands{$cmd}}(*INPUT, *OUTPUT, $cmd);
305	delete($commands{$cmd});
306	if ($rval == -1) {
307	    $clean_run = 0;
308	    last TOP;
309	}
310    }
311}
312print STDOUT "Done $logincmd: $_\n" if ($log);
313# Flush History
314ProcessHistory("","","","");
315# Cleanup
316close(INPUT);
317close(OUTPUT);
318
319unlink("$host.raw") if (! $debug);
320
321# check for completeness
322if (scalar(%commands) || !$clean_run || !$found_end) {
323    if (scalar(keys %commands) eq $commandcnt) {
324	printf(STDERR "$host: missed cmd(s): all commands\n");
325    } elsif (scalar(%commands)) {
326	my($count, $i) = 0;
327	for ($i = 0; $i < $#commands; $i++) {
328	    if ($commands{$commands[$i]}) {
329		if (!$count) {
330		    printf(STDERR "$host: missed cmd(s): %s", $commands[$i]);
331		} else {
332		    printf(STDERR ", %s", $commands[$i]);
333		}
334		$count++;
335	    }
336	}
337	if ($count) {
338	    printf(STDERR "\n");
339	}
340    }
341    if (!$clean_run || !$found_end) {
342	print(STDERR "$host: End of run not found\n");
343	if ($debug) {
344	    print(STDERR "$host: clean_run is false\n") if (!$clean_run);
345	    print(STDERR "$host: found_end is false\n") if (!$found_end);
346	}
347	system("/usr/bin/tail -1 $host.new");
348    }
349    unlink "$host.new" if (! $debug);
350}
351