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#  RANCID - Really Awesome New Cisco confIg Differ
48#
49# arancid - Alteon WebOS plugin for rancid
50#
51# usage: arancid [-dltCV] [-f filename | hostname]
52#
53use Getopt::Std;
54getopts('dflt:CV');
55if ($opt_V) {
56    print "@PACKAGE@ @VERSION@\n";
57    exit(0);
58}
59$log = $opt_l;
60$debug = $opt_d;
61$file = $opt_f;
62$host = $ARGV[0];
63$clean_run = 0;
64$found_end = 0;
65$prompt = "#";
66$timeo = 90;				# alogin 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# This routine is used to print out the router configuration
75sub ProcessHistory {
76    my($new_hist_tag,$new_command,$command_string,@string) = (@_);
77    if ((($new_hist_tag ne $hist_tag) || ($new_command ne $command))
78	&& scalar(%history)) {
79	print eval "$command \%history";
80	undef %history;
81    }
82    if (($new_hist_tag) && ($new_command) && ($command_string)) {
83	if ($history{$command_string}) {
84	    $history{$command_string} = "$history{$command_string}@string";
85	} else {
86	    $history{$command_string} = "@string";
87	}
88    } elsif (($new_hist_tag) && ($new_command)) {
89	$history{++$#history} = "@string";
90    } else {
91	print "@string";
92    }
93    $hist_tag = $new_hist_tag;
94    $command = $new_command;
95    1;
96}
97
98sub numerically { $a <=> $b; }
99
100# This is a sort routine that will sort numerically on the
101# keys of a hash as if it were a normal array.
102sub keynsort {
103    local(%lines) = @_;
104    local($i) = 0;
105    local(@sorted_lines);
106    foreach $key (sort numerically keys(%lines)) {
107	$sorted_lines[$i] = $lines{$key};
108	$i++;
109    }
110    @sorted_lines;
111}
112
113# This is a sort routine that will sort on the
114# keys of a hash as if it were a normal array.
115sub keysort {
116    local(%lines) = @_;
117    local($i) = 0;
118    local(@sorted_lines);
119    foreach $key (sort keys(%lines)) {
120	$sorted_lines[$i] = $lines{$key};
121	$i++;
122    }
123    @sorted_lines;
124}
125
126# This is a sort routine that will sort on the
127# values of a hash as if it were a normal array.
128sub valsort{
129    local(%lines) = @_;
130    local($i) = 0;
131    local(@sorted_lines);
132    foreach $key (sort values %lines) {
133	$sorted_lines[$i] = $key;
134	$i++;
135    }
136    @sorted_lines;
137}
138
139# This is a numerical sort routine (ascending).
140sub numsort {
141    local(%lines) = @_;
142    local($i) = 0;
143    local(@sorted_lines);
144    foreach $num (sort {$a <=> $b} keys %lines) {
145	$sorted_lines[$i] = $lines{$num};
146	$i++;
147    }
148    @sorted_lines;
149}
150
151# This is a sort routine that will sort on the
152# ip address when the ip address is anywhere in
153# the strings.
154sub ipsort {
155    local(%lines) = @_;
156    local($i) = 0;
157    local(@sorted_lines);
158    foreach $addr (sort sortbyipaddr keys %lines) {
159	$sorted_lines[$i] = $lines{$addr};
160	$i++;
161    }
162    @sorted_lines;
163}
164
165# These two routines will sort based upon IP addresses
166sub ipaddrval {
167    my(@a) = ($_[0] =~ m#^(\d+)\.(\d+)\.(\d+)\.(\d+)$#);
168    $a[3] + 256 * ($a[2] + 256 * ($a[1] +256 * $a[0]));
169}
170sub sortbyipaddr {
171    &ipaddrval($a) <=> &ipaddrval($b);
172}
173
174# This routine parses "/info/sys" (cf. show version)
175sub ShowVersion {
176    print STDERR "    In ShowVersion: $_" if ($debug);
177
178    while (<INPUT>)  {
179	tr/\015//d;
180	last if (/^>>.*$prompt/);
181	next if(/^(\s*|\s*$cmd\s*)$/);
182
183	/^(ACEdirector.*|ACEswitch.*|Alteon.*)/i &&
184	  ProcessHistory("COMMENTS","keysort","A1", "\/\*Model: $1\n") && next;
185	/^Software Version\s+(.*?)\s\((.*)\)/i &&
186	  ProcessHistory("COMMENTS","keysort","B1",
187			 "\/\*Image: Software: $1 ($2)\n") && next;
188	/^Hardware Part No:\s+(.*?)\s+/i &&
189	  ProcessHistory("COMMENTS","keysort","A2",
190			 "\/\*Hardware part no: $1\n") && next;
191	/^MAC address:\s+([0-9a-f]{2}:[0-9a-f]{2}:[0-9a-f]{2}:[0-9a-f]{2}:[0-9a-f]{2}:[0-9a-f]{2})/i &&
192	  ProcessHistory("COMMENTS","keysort","C1",
193			 "\/\*Base MAC address: $1\n") && next;
194    }
195    return(0);
196}
197
198# This routine processes a "/cfg/dump"
199sub WriteTerm {
200    print STDERR "    In WriteTerm: $_" if ($debug);
201
202    # eat the header line
203    #$junk = <INPUT>;
204
205    # now just copy it verbatim to the history file
206    while (<INPUT>) {
207	tr/\015//d;
208	last if(/^>>.*$prompt/);
209	chop;
210	if (/(rcomm|wcomm|t1com|t2com)(\s+)(.*)/ && $filter_commstr) {
211	    ProcessHistory("","","","\/\*\t$1$2\"<removed>\"\n") && next;
212	}
213	/^(\s+(.{1,3}pw|pswd) )\S+/ &&
214	    ProcessHistory("","","","\/\*$1<removed>\n") && next;
215
216	next if (/^\/\* Configuration dump taken/i);
217	next if (/^\/\* Version.*Base MAC.*/i);
218	next if (/^ *esecret /);
219
220	if (/^\/?script end/) {
221	    $found_end = 1;
222	    ProcessHistory("","","","$_\n");
223	    return(1);
224	}
225
226	ProcessHistory("","","","$_\n");
227    }
228    return(0);
229}
230
231# Main
232@commandtable = (
233	{'/info/sys/dump'	=> 'ShowVersion'},
234	{'/cfg/dump'		=> 'WriteTerm'}
235);
236# Use an array to preserve the order of the commands and a hash for mapping
237# commands to the subroutine and track commands that have been completed.
238@commands = map(keys(%$_), @commandtable);
239%commands = map(%$_, @commandtable);
240$commandcnt = scalar(keys %commands);
241
242$commandstr = join(";",@commands);
243$cmds_regexp = join("|", map quotemeta($_), @commands);
244
245if (length($host) == 0) {
246    if ($file) {
247	print(STDERR "Too few arguments: file name required\n");
248	exit(1);
249    } else {
250	print(STDERR "Too few arguments: host name required\n");
251	exit(1);
252    }
253}
254if ($opt_C) {
255    print "alogin -t $timeo -c\'$commandstr\' $host\n";
256    exit(0);
257}
258open(OUTPUT,">$host.new") || die "Can't open $host.new for writing: $!\n";
259select(OUTPUT);
260# make OUTPUT unbuffered if debugging
261if ($debug) { $| = 1; }
262
263if ($file) {
264    print(STDERR "opening file $host\n") if ($debug || $log);
265    open(INPUT,"<$host") || die "open failed for $host: $!\n";
266} else {
267    print(STDERR "executing alogin -t $timeo -c\"$commandstr\" $host\n") if ($debug || $log);
268    system "alogin -t $timeo -c \"$commandstr\" $host </dev/null > $host.raw 2>&1" || die "alogin failed for $host: $!\n";
269    open(INPUT, "< $host.raw") || die "alogin failed for $host: $!\n";
270}
271
272# determine ACL sorting mode
273if ($ENV{"ACLSORT"} =~ /no/i) {
274    $aclsort = "";
275}
276# determine community string filtering mode
277if (defined($ENV{"NOCOMMSTR"}) &&
278    ($ENV{"NOCOMMSTR"} =~ /yes/i || $ENV{"NOCOMMSTR"} =~ /^$/)) {
279    $filter_commstr = 1;
280} else {
281    $filter_commstr = 0;
282}
283# determine oscillating data filtering mode
284if (defined($ENV{"FILTER_OSC"}) && $ENV{"FILTER_OSC"} =~ /no/i) {
285    $filter_osc = 0;
286} else {
287    $filter_osc = 1;
288}
289# determine password filtering mode
290if ($ENV{"FILTER_PWDS"} =~ /no/i) {
291    $filter_pwds = 0;
292} elsif ($ENV{"FILTER_PWDS"} =~ /all/i) {
293    $filter_pwds = 2;
294} else {
295    $filter_pwds = 1;
296}
297ProcessHistory("","","","\/\*RANCID-CONTENT-TYPE: alteon\n\/\*\n");
298ProcessHistory("COMMENTS","keysort","B0","\/\*\n");
299ProcessHistory("COMMENTS","keysort","F0","\/\*\n");
300TOP: while(<INPUT>) {
301    tr/\015//d;
302    if (/^>>.*$prompt exit/) {
303	$clean_run = 1;
304	last;
305    }
306
307    while (/>>.*$prompt\s*($cmds_regexp)\s*$/) {
308	$cmd = $1;
309	if (!defined($prompt)) {
310	    $prompt = ($_ =~ /^([^#]+#)/)[0];
311	    $prompt =~ s/([][}{)(\\])/\\$1/g;
312	    print STDERR ("PROMPT MATCH: $prompt\n") if ($debug);
313	}
314	print STDERR ("HIT COMMAND:$_") if ($debug);
315	if (!defined($commands{$cmd})) {
316	    print STDERR "$host: found unexpected command - \"$cmd\"\n";
317	    $clean_run = 0;
318	    last TOP;
319	}
320	$rval = &{$commands{$cmd}}(*INPUT, *OUTPUT, $cmd);
321	delete($commands{$cmd});
322	if ($rval == -1) {
323	    $clean_run = 0;
324	    last TOP;
325	}
326    }
327}
328print STDOUT "Done $logincmd: $_\n" if ($log);
329# Flush History
330ProcessHistory("","","","");
331# Cleanup
332close(INPUT);
333close(OUTPUT);
334
335unlink("$host.raw") if (! $debug);
336
337# check for completeness
338if (scalar(%commands) || !$clean_run || !$found_end) {
339    if (scalar(keys %commands) eq $commandcnt) {
340	printf(STDERR "$host: missed cmd(s): all commands\n");
341    } elsif (scalar(%commands)) {
342	my($count, $i) = 0;
343	for ($i = 0; $i < $#commands; $i++) {
344	    if ($commands{$commands[$i]}) {
345		if (!$count) {
346		    printf(STDERR "$host: missed cmd(s): %s", $commands[$i]);
347		} else {
348		    printf(STDERR ", %s", $commands[$i]);
349		}
350		$count++;
351	    }
352	}
353	if ($count) {
354	    printf(STDERR "\n");
355	}
356    }
357    if (!$clean_run || !$found_end) {
358	print(STDERR "$host: End of run not found\n");
359	if ($debug) {
360	    print(STDERR "$host: clean_run is false\n") if (!$clean_run);
361	    print(STDERR "$host: found_end is false\n") if (!$found_end);
362	}
363	system("/usr/bin/tail -1 $host.new");
364    }
365    unlink "$host.new" if (! $debug);
366}
367