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