From owner-svn-ports-all@freebsd.org Wed Aug 17 18:13:38 2016 Return-Path: Delivered-To: svn-ports-all@mailman.ysv.freebsd.org Received: from mx1.freebsd.org (mx1.freebsd.org [IPv6:2001:1900:2254:206a::19:1]) by mailman.ysv.freebsd.org (Postfix) with ESMTP id 4D439BB5088; Wed, 17 Aug 2016 18:13:38 +0000 (UTC) (envelope-from feld@FreeBSD.org) Received: from repo.freebsd.org (repo.freebsd.org [IPv6:2610:1c1:1:6068::e6a:0]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (Client did not present a certificate) by mx1.freebsd.org (Postfix) with ESMTPS id E166F15B1; Wed, 17 Aug 2016 18:13:37 +0000 (UTC) (envelope-from feld@FreeBSD.org) Received: from repo.freebsd.org ([127.0.1.37]) by repo.freebsd.org (8.15.2/8.15.2) with ESMTP id u7HIDbd7052477; Wed, 17 Aug 2016 18:13:37 GMT (envelope-from feld@FreeBSD.org) Received: (from feld@localhost) by repo.freebsd.org (8.15.2/8.15.2/Submit) id u7HIDbqJ052473; Wed, 17 Aug 2016 18:13:37 GMT (envelope-from feld@FreeBSD.org) Message-Id: <201608171813.u7HIDbqJ052473@repo.freebsd.org> X-Authentication-Warning: repo.freebsd.org: feld set sender to feld@FreeBSD.org using -f From: Mark Felder Date: Wed, 17 Aug 2016 18:13:37 +0000 (UTC) To: ports-committers@freebsd.org, svn-ports-all@freebsd.org, svn-ports-head@freebsd.org Subject: svn commit: r420361 - in head/net-mgmt/rancid3: . files X-SVN-Group: ports-head MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit X-BeenThere: svn-ports-all@freebsd.org X-Mailman-Version: 2.1.22 Precedence: list List-Id: SVN commit messages for the ports tree List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Wed, 17 Aug 2016 18:13:38 -0000 Author: feld Date: Wed Aug 17 18:13:36 2016 New Revision: 420361 URL: https://svnweb.freebsd.org/changeset/ports/420361 Log: net-mgmt/rancid3: Add experimental support for Comware devices Comware support has been added via a port option. It is not enabled by default as upstream has not been able to validate the reliability of this enhancement. PR: 209361 Added: head/net-mgmt/rancid3/files/cmw.pm (contents, props changed) head/net-mgmt/rancid3/files/cmwlogin (contents, props changed) head/net-mgmt/rancid3/files/comware_types.conf (contents, props changed) Modified: head/net-mgmt/rancid3/Makefile head/net-mgmt/rancid3/pkg-plist Modified: head/net-mgmt/rancid3/Makefile ============================================================================== --- head/net-mgmt/rancid3/Makefile Wed Aug 17 18:10:01 2016 (r420360) +++ head/net-mgmt/rancid3/Makefile Wed Aug 17 18:13:36 2016 (r420361) @@ -34,15 +34,22 @@ NEWERCONFIG_FILES1= etc/rancid.conf NEWERCONFIG_FILES2= etc/lg.conf NEWCONFIG_FILES= rancid.conf lg.conf -OPTIONS_DEFINE= SVN +OPTIONS_DEFINE= COMWARE SVN OPTIONS_DEFAULT= +COMWARE_DESC= Add experimental support for Comware devices SVN_DESC= Use Subversion instead of CVS CONFLICTS= rancid-[0-9]* .include +.if ${PORT_OPTIONS:MCOMWARE} +PLIST_SUB+= COMWARE="" +.else +PLIST_SUB+= COMWARE="@comment " +.endif + .if ${PORT_OPTIONS:MSVN} CONFIGURE_ARGS+= --with-svn BUILD_DEPENDS+= svn:devel/subversion @@ -57,6 +64,9 @@ pre-everything:: @ ${ECHO} "Make sure your rancid repository is quiet before upgrading; disable rancid cron jobs" post-patch: +.if ${PORT_OPTIONS:MCOMWARE} + ${CAT} ${FILESDIR}/comware_types.conf >> ${WRKSRC}/etc/rancid.types.base +.endif ${REINPLACE_CMD} "s|par.1|rancid-par.1|" ${WRKSRC}/man/Makefile.in ${MV} ${WRKSRC}/man/par.1 ${WRKSRC}/man/rancid-par.1 ${REINPLACE_CMD} "s|3des|3des-cbc|g" ${WRKSRC}/bin/*.in ${WRKSRC}/cloginrc.sample \ @@ -65,6 +75,10 @@ post-patch: post-install: ${LN} -s ${PREFIX}/libexec/${PORTNAME}/rancid-cvs ${STAGEDIR}${PREFIX}/bin ${LN} -s ${PREFIX}/libexec/${PORTNAME}/rancid-run ${STAGEDIR}${PREFIX}/bin +.if ${PORT_OPTIONS:MCOMWARE} + ${INSTALL_DATA} ${FILESDIR}/cmw.pm ${STAGEDIR}/${PREFIX}/lib/rancid + ${INSTALL_SCRIPT} ${FILESDIR}/cmwlogin ${STAGEDIR}/${PREFIX}/libexec/rancid +.endif .for file in ${NEWCONFIG_FILES} @ if [ -f ${PREFIX}/etc/${PORTNAME}/${file} ] ; then \ ${ECHO} "WARNING: *** new ${file} file is installed as ${PREFIX}/rancid/${file}.sample"; \ Added: head/net-mgmt/rancid3/files/cmw.pm ============================================================================== --- /dev/null 00:00:00 1970 (empty, because file is newly added) +++ head/net-mgmt/rancid3/files/cmw.pm Wed Aug 17 18:13:36 2016 (r420361) @@ -0,0 +1,525 @@ +package cmw; +## +## $Id: cmw.pm.in 3000 2015-01-06 18:47:49Z heas $ +## +## rancid 3.1.99 +## Copyright (c) 1997-2015 by Terrapin Communications, Inc. +## All rights reserved. +## +## This code is derived from software contributed to and maintained by +## Terrapin Communications, Inc. 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. All advertising materials mentioning features or use of this software +## must display the following acknowledgement: +## This product includes software developed by Terrapin Communications, +## Inc. and its contributors for RANCID. +## 4. Neither the name of Terrapin Communications, Inc. nor the names of its +## contributors may be used to endorse or promote products derived from +## this software without specific prior written permission. +## 5. It is requested that non-binding fixes and modifications be contributed +## back to Terrapin Communications, Inc. +## +## THIS SOFTWARE IS PROVIDED BY Terrapin Communications, INC. 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. +# +# RANCID - Really Awesome New Cisco confIg Differ +# +# cmw.pm - Comware (Huawei/H3C/3com/HP) rancid procedures +# +# https://sites.google.com/site/jrbinks/code/rancid/cmwrancid + +use 5.010; +use strict 'vars'; +use warnings; +no warnings 'uninitialized'; +require(Exporter); +our @ISA = qw(Exporter); + +use rancid 3.1.99; + +our $login; + +@ISA = qw(Exporter rancid main); +#XXX @Exporter::EXPORT = qw($VERSION @commandtable %commands @commands); + +# XXX +#our @EXPORT = qw(iproutesort iprouteval); + +# load-time initialization +sub import { + 0; +} + +# post-open(collection file) initialization +sub init { + $login = "cmwlogin"; + # add content lines and separators + ProcessHistory("","","","!RANCID-CONTENT-TYPE: $devtype\n!\n"); + + 0; +} + +# main loop of input of device output +sub inloop { + my($INPUT, $OUTPUT) = @_; + my($cmd, $rval); + +TOP: while(<$INPUT>) { + tr/\015//d; + if (/[\]>#]\a?\s*quit/) { + #if (/[>#]\s?exit$/) { + $clean_run = 1; + last; + } + if (/^Error:/) { + print STDOUT ("$host $login error: $_"); + print STDERR ("$host $login error: $_") if ($debug); + $clean_run = 0; + last; + } + while (/[\]>#]\a?\s*($cmds_regexp)\s*$/) { + $cmd = $1; + + if (!defined($prompt)) { + # Extract the prompt: look for something not [ or < at the start + # of the line, until either ] or > or # is reached: + $prompt = ($_ =~ /^([^\]>#]+[\]>]\a?)/)[0]; + $prompt =~ s/([][}{)(\\])/\\$1/g; + print STDERR ("PROMPT MATCH: $prompt\n") if ($debug); + } + print STDERR ("HIT COMMAND:$_") if ($debug); + if (! defined($commands{$cmd})) { + print STDERR "$host: found unexpected command - \"$cmd\"\n"; + $clean_run = 0; + last TOP; + } + $rval = &{$commands{$cmd}}($INPUT, $OUTPUT, $cmd); + delete($commands{$cmd}); + if ($rval == -1) { + $clean_run = 0; + last TOP; + } + } + } +} + +# dummy function +sub DoNothing {print STDOUT;} + +# This is a sort routine that will sort on the +# ip route when the ip route is anywhere in +# the strings. +sub iproutesort { + my(%lines) = @_; + my($i) = 0; + my(@sorted_lines); + foreach my $iproute (sort sortbyiproute keys %lines) { + $sorted_lines[$i] = $lines{$iproute}; + $i++; + } + @sorted_lines; +} + +## XXX Re-evaluate based on new routines, and consider IPv6: +# These two routines will sort based upon IP route +sub iprouteval { + my(@a) = ($_[0] =~ m#^(\d+)\.(\d+)\.(\d+)\.(\d+)/(\d+)$#); + $a[4] + ($a[3] + 256 * ($a[2] + 256 * ($a[1] + 256 * $a[0]))); +} + +sub sortbyiproute { + &iprouteval($a) <=> &iprouteval($b); +} + +# Clean up lines on input, particularly ANSI characters as a result +# of us not being able to turn off per-session terminal paging +sub filter_lines { + my ($l) = (@_); + + #s/^\033\[42D +\033\[42D(.+)$/$1/; + #s/\033\133\064\062\104\s*\033\133\064\062\104//g; + $l =~ s/\033\133\064\062\104\s+\033\133\064\062\104//g; + $l =~ s/\033\133\061\066\104\s+\033\133\061\066\104//g; + $l =~ s/\033\133\064\062\104//g; + $l =~ s/\033\133\061\062\104//g; + $l =~ s/.*\[37D(.*)/$1/g; # MA5600 + # Probably not needed: + $l =~ s/\s*---- More ----\s*//; + $l =~ s/^ //; # Comware7 + $l =~ s/Synchronization is finished.//g; + return $l; +} + +# Some commands are not supported on some models or versions +# of code. +# Remove the associated error messages, and rancid will ensure that +# these are not treated as "missed" commands +sub command_not_valid { + my ($l) = (@_); + + if ( $l =~ + /% Too many parameters found at '\^' position/ || + /% Unrecognized command found at '\^' position/ || + /% Incomplete command found at '\^' position./ || + /(% )?Wrong parameter found at '\^' position/ || + /% Wrong device .+/ + ) { + return(1); + } else { + return(0); + } +} + +# Some commands are not authorized under the current +# user's permissions +sub command_not_auth { + my ($l) = (@_); + + if ( $l =~ + /Permission denied\./ + ) { + return(1); + } else { + return(0); + } +} + +# Some output lines are always skipped +sub skip_pattern { + my ($l) = (@_); + + if ( $l =~ + /^\s+\^$/ || + /^$/ + ) { + return(1); + } else { + return(0); + } +} + +sub DisplayFib { + + my($INPUT, $OUTPUT, $cmd) = @_; + my($dest, $nexthop, $flag, $outint, $label); + print STDERR " In DisplayFib: $_" if ($debug); + + chomp; + + # Display the command we're processing in the output: + ProcessHistory("FIB","","","!\n! '$cmd':\n!\n"); + + while (<$INPUT>) { + tr/\015//d; + last if(/^\s*$prompt/); + chomp; + $_ = filter_lines($_); + + return(1) if command_not_valid($_); + return(-1) if command_not_auth($_); + next if skip_pattern($_); + + next if /^Destination count: \d+ FIB entry count: \d+/; + + # Chop out some detail that changes over time (Comware 3): + s/(\s+)TimeStamp\s+/$1/; # TimeStamp column heading + + ProcessHistory("FIB","","","! $_\n"); + + if ( m,Destination/Mask, ) { + while (<$INPUT>) { + tr/\015//d; + last if(/^\s*$prompt/); + chomp; + $_ = filter_lines($_); + + # Chop out some detail that changes over time (Comware 3): + s/(\s+)t\[\d+\]\s+/$1/; # TimeStamp data + + # "display fib" on comware7 shows host entries for things + # learned via arp too. For a distribution router, that's all + # the devices on subnets routed by it! + # If we filter out all "UH" entries that are NOT InLoop, we + # get acceptable output. + # + # So we want to keep: + # + # 0.0.0.0/32 127.0.0.1 UH InLoop0 Null + # + # but reject: + # + # 130.159.44.161/32 130.159.44.161 UH Vlan44 Null + # + # However I've a feeling that this is a problematic + # solution, and some object to the notion that rancid + # should be representing such potentially dynamic data in + # the first place, which is why we created the + # $display_fib flag for rancid 2, and in rancid 3 one + # can modify the command table in rancid.types.conf + + ($dest, $nexthop, $flag, $outint, $label) = split; + next if ( $flag eq 'UH' && $outint !~ /InLoop/ ); + #ProcessHistory("FIB", "cmw::iproutesort", "$dest", "! $_\n"); + ProcessHistory("FIB", "ipsort", "$dest", "! $_\n"); + } + + ProcessHistory("FIB", "", "", "!\n"); + + # return here to ensure that we don't keep swallowing the + # next command's output by returning to the surrounding + # while loop + return(0); + } + } + return(0); +} + +sub DisplayIPRoutes { + my($INPUT, $OUTPUT, $cmd) = @_; + my($key,$line,$spaces); + print STDERR " In DisplayIPRoutes: $_" if ($debug); + + chomp; + + # Display the command we're processing in the output: + ProcessHistory("IPR","","","!\n! '$cmd':\n!\n"); + + while (<$INPUT>) { + tr/\015//d; + last if(/^\s*$prompt/); + chomp; + + $_ = filter_lines($_); + return(1) if command_not_valid($_); + return(-1) if command_not_auth($_); + next if skip_pattern($_); + + ProcessHistory("IPR","","","! $_\n"); + + if ( m,Destination/Mask, ) { + my $lastkey = ""; + my $lastspaces = ""; + while (<$INPUT>) { + tr/\015//d; + last if(/^\s*$prompt/); + chomp; + $_ = filter_lines($_); + + # If the key is blank, indicating multiple nexthops for + # a particular route, then we use the previous one + if ( m/^\s+(.+)/ ) { + $key = $lastkey; + $line = $key . $lastspaces . $1; + #ProcessHistory("IPR", "cmw::iproutesort", "$key", "! $line\n"); + ProcessHistory("IPR", "ipsort", "$key", "! $line\n"); + } + if ( m/^(\S+)(\s+).+/ ) { + $key = $1; + $line = $_; + $spaces = $2; + #ProcessHistory("IPR", "cmw::iproutesort", "$key", "! $line\n"); + ProcessHistory("IPR", "ipsort", "$key", "! $line\n"); + + # Remember these, we may need them on the next pass + $lastkey = $key; + $lastspaces = $spaces; + } + } + + ProcessHistory("IPR", "", "", "!\n"); + + # return here to ensure that we don't keep swallowing the + # next command's output by returning to the surrounding + # while loop + return(0); + } + } + return(0); +} + +## This routine processes general output of "display" commands +sub CommentOutput { + my($INPUT, $OUTPUT, $cmd) = @_; + print STDERR " In CommentOutput: $_" if ($debug); + + chomp; + + # Display the command we're processing in the output: + ProcessHistory("COMMENTS", "", "", "!\n! '$cmd':\n!\n"); + + while (<$INPUT>) { + tr/\015//d; + + # If we find the prompt, we're done + # Ordinarily this matches from the start of the line, however + # we've seen circumstances at least in Comware7 where the + # prompt is preceded by whitespace, like so: + # ^M^M ^Mdisplay boot-loader^M + last if(/^\s*$prompt/); + chomp; + + # filter out some junk + $_ = filter_lines($_); + return(1) if command_not_valid($_); + return(-1) if command_not_auth($_); + next if skip_pattern($_); + + # Now we skip or modify some lines from various commands to + # remove irrelevant content, or to avoid insignificant diffs + + # 'display local-user': + s/\s+Current AccessNum:.+$//; + + # 'display version': + next if (/^(Uptime is \d|.+ [Uu]ptime is \d).+$/); + # No longer necessary since skipping the whole Uptime line: + # Mangle these lines: + #s/(.*)[Uu]ptime.*.weeks.*.days*.*hours*.*minutes*(.*)/$1 $2/; + #s/(.*)[Uu]ptime.*days*.*hours*.*minutes*(.*)/$1 $2/; + + # MSRs display a 'last reboot' time, but sometimes the seconds + # vary by one or two (presumably internal rounding), so simply make + # the last digit a fixed '0'. It would probably be safer to make + # the last two digits a fixed '00'. + # (Thx Alexander Belokopytov) + s/(^Last reboot.+)\d$/${1}0/; + + # 'dir ' commands + if ( $cmd =~ /^dir / ) { + # First field is just an index number, chop it out + s/^\s+\d+\s+(.+)/ $1/; + # Remove filenames that are updated frequently + next if ( + /logfile\.log$/ || + /lauth\.dat$/ || + /ifindex\.dat$/ || + /startup\.mdb$/ || + /private-data\.txt$/ || + /.+ KB total \(.+ KB free/ || + /.+ KB total \(.+ KB free/ || + /\.trash/ + ); + } + + # 'display ospf brief'/'display ospf' + if ( $cmd =~ 'display ospf( brief)?' ) { + #next if (/^(Ospf is not enabled yet|Info: OSPF routing process is not enabled|The feature OSPF has not been enabled.).+$/); + next if (/^\s+SPF (Computation|Scheduled|calculation) Count:.+$/i); + } + + if ( $cmd eq 'display power' ) { + next if (/^(\s+Input Power).+$/); + } + + if ( $cmd eq 'display poe powersupply' ) { + next if (/^(PSE Total Power Consumption|PSE Available Power|PSE Peak Value|PSE Average Value).+$/); + } + + if ( $cmd eq 'display ntp-service status' ) { + next unless m/(Clock status|Clock stratum|Reference clock ID)/i; + } + + if ( $cmd eq 'display transceiver interface' ) { + s/^(\S+ transceiver information:).+$/$1/; # filter random garbage + s/^Error: The transceiver is absent.$/ No transceiver present./; + s/^Error: The combo port is inactive.$/ Inactive combo port./; + } + + # Add the processed lines to the output buffer: + ProcessHistory("COMMENTS","","","! $_\n"); + } + + # Add a blank comment line to the output buffer + ProcessHistory("COMMENTS", "", "", "!\n"); + return(0); +} + +## This routine processes a "display current" +sub DisplayCurrent { + my($INPUT, $OUTPUT, $cmd) = @_; + print STDERR " In DisplayCurrent: $_" if ($debug); + + + while (<$INPUT>) { + tr/\015//d; + last if(/^\s*$prompt/); + chomp; + + $_ = filter_lines($_); + return(1) if command_not_valid($_); + return(-1) if command_not_auth($_); + next if skip_pattern($_); + + return(0) if ($found_end); + + # Filter out some sensitive data: + if ( $filter_commstr && + /^ ?(snmp-agent (target-host.+securityname|usm-user|community (read|write)) )(\S+)/ + ) { + ProcessHistory("","","","! $1$'\n"); + next; + } + if ( $filter_pwds >= 1 && + /^ ?(password (?:simple|cipher|hash) )(\S+)/ || + /^ ?(super password( role level-\d)( level \d)? (cipher|simple|hash) )(\S+)/ || + /^ ?(set authentication password (cipher|simple|hash) )(\S+)/ || + /^ ?(key (?:authentication|accounting) )(\S+)/ + ) { + ProcessHistory("","","","! $1$'\n"); + next; + } + + # filter ssh public keys of devices connected to from this device + if (/^ ?(public-key-code begin)/ && + $filter_pwds >= 2) { + ProcessHistory("","","","!$1\n"); + ProcessHistory("","","","! \n"); + while (<$INPUT>) { + tr/\015//d; + next if /^$/; + next if /^\s+[[:xdigit:]]$/; + if (/(^ public-key-code end)/) { + ProcessHistory("","","","!$1\n"); + last; + } + } + next; + } + + # Filter mac addresses dynamically added to config + next if (/^ ?mac-address security.+$/); + + ProcessHistory("", "", "", "$_\n"); + + # end of config + if (/^return/) { + $found_end = 1; + return(0); + } + } + return(0); +} + +1; + +__END__ + + Added: head/net-mgmt/rancid3/files/cmwlogin ============================================================================== --- /dev/null 00:00:00 1970 (empty, because file is newly added) +++ head/net-mgmt/rancid3/files/cmwlogin Wed Aug 17 18:13:36 2016 (r420361) @@ -0,0 +1,1043 @@ +#! /usr/local/bin/expect -- +## +## $Id: cmwlogin 3022 2015-01-13 20:00:00Z heas $ +## +## rancid 3.1.99 +## Copyright (c) 1997-2015 by Terrapin Communications, Inc. +## All rights reserved. +## +## This code is derived from software contributed to and maintained by +## Terrapin Communications, Inc. 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. All advertising materials mentioning features or use of this software +## must display the following acknowledgement: +## This product includes software developed by Terrapin Communications, +## Inc. and its contributors for RANCID. +## 4. Neither the name of Terrapin Communications, Inc. nor the names of its +## contributors may be used to endorse or promote products derived from +## this software without specific prior written permission. +## 5. It is requested that non-binding fixes and modifications be contributed +## back to Terrapin Communications, Inc. +## +## THIS SOFTWARE IS PROVIDED BY Terrapin Communications, INC. 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. +# +# The expect login scripts were based on Erik Sherk's gwtn, by permission. +# +# cwlogin - Comware login +# +# Most options are intuitive for logging into a Comware device. +# The default is to enable (thus -noenable). Some folks have +# setup tacacs to have a user login at priv-lvl = 3 (enabled) +# so the -autoenable flag was added for this case (don't go through +# the process of enabling and the prompt will be the "#" prompt. +# The default username password is the same as the vty password. +# + +# Usage line +set usage "Usage: $argv0 \[-dSV\] \[-autoenable\] \[-noenable\] \[-c command\] \ +\[-Evar=x\] \[-e enable-password\] \[-f cloginrc-file\] \[-p user-password\] \ +\[-r passphrase\] \[-s script-file\] \[-t timeout\] \[-u username\] \ +\[-v vty-password\] \[-w enable-username\] \[-x command-file\] \[-P platform\] \ +\[-y ssh_cypher_type\] router \[router...\]\n" + +# env(CLOGIN) may contain: +# x == do not set xterm banner or name + +# Password file +set password_file $env(HOME)/.cloginrc +# Default is to login to the router +set do_command 0 +set do_script 0 +# The default is to automatically enable +set avenable 1 +# The default is that you login non-enabled (tacacs can have you login already +# enabled) +set avautoenable 0 +# The default is to look in the password file to find the passwords. This +# tracks if we receive them on the command line. +set do_passwd 1 +set do_enapasswd 1 +# Save config, if prompted +set do_saveconfig 0 +# Sometimes routers take awhile to answer (the default is 10 sec) +set timeoutdflt 45 +# Some CLIs having problems if we write too fast (Extreme, PIX, Cat) +set send_human {.2 .1 .4 .2 1} + +# Find the user in the ENV, or use the unix userid. +if {[info exists env(CISCO_USER)]} { + set default_user $env(CISCO_USER) +} elseif {[info exists env(USER)]} { + set default_user $env(USER) +} elseif {[info exists env(LOGNAME)]} { + set default_user $env(LOGNAME) +} else { + # This uses "id" which I think is portable. At least it has existed + # (without options) on all machines/OSes I've been on recently - + # unlike whoami or id -nu. + if [catch {exec id} reason] { + send_error "\nError: could not exec id: $reason\n" + exit 1 + } + regexp {\(([^)]*)} "$reason" junk default_user +} +if {[info exists env(CLOGINRC)]} { + set password_file $env(CLOGINRC) +} + +# Process the command line +for {set i 0} {$i < $argc} {incr i} { + set arg [lindex $argv $i] + + switch -glob -- $arg { + # Expect debug mode + -d* { + exp_internal 1 + # Username + } -u* { + if {! [regexp .\[uU\](.+) $arg ignore user]} { + incr i + set username [lindex $argv $i] + } + # VTY Password + } -p* { + if {! [regexp .\[pP\](.+) $arg ignore userpasswd]} { + incr i + set userpasswd [lindex $argv $i] + } + set do_passwd 0 + # ssh passphrase + } -r* { + if {! [regexp .\[rR\](.+) $arg ignore passphrase]} { + incr i + set vapassphrase [lindex $argv $i] + } + # VTY Password + } -v* { + if {! [regexp .\[vV\](.+) $arg ignore passwd]} { + incr i + set passwd [lindex $argv $i] + } + set do_passwd 0 + # Version string + } -V* { + send_user "rancid 3.1.99\n" + exit 0 + # Enable Username + } -w* { + if {! [regexp .\[wW\](.+) $arg ignore enauser]} { + incr i + set enausername [lindex $argv $i] + } + # Environment variable to pass to -s scripts + } -E* { + if {[regexp .\[E\](.+)=(.+) $arg ignore varname varvalue]} { + set E$varname $varvalue + } else { + send_user "\nError: invalid format for -E in $arg\n" + exit 1 + } + # Enable Password + } -e* { + if {! [regexp .\[e\](.+) $arg ignore enapasswd]} { + incr i + set enapasswd [lindex $argv $i] + } + set do_enapasswd 0 + # Platform + } -P* { + if {! [regexp .\[P\](.+) $arg ignore platform]} { + incr i + set plat [lindex $argv $P] + } + # Command to run. + } -c* { + if {! [regexp .\[cC\](.+) $arg ignore command]} { + incr i + set command [lindex $argv $i] + } + set do_command 1 + # Expect script to run. + } -s* { + if {! [regexp .\[sS\](.+) $arg ignore sfile]} { + incr i + set sfile [lindex $argv $i] + } + if { ! [file readable $sfile] } { + send_user "\nError: Can't read $sfile\n" + exit 1 + } + set do_script 1 + # save config on exit + } -S* { + set do_saveconfig 1 + # 'ssh -c' cypher type + } -y* { + if {! [regexp .\[eE\](.+) $arg ignore cypher]} { + incr i + set cypher [lindex $argv $i] + } + # alternate cloginrc file + } -f* { + if {! [regexp .\[fF\](.+) $arg ignore password_file]} { + incr i + set password_file [lindex $argv $i] + } + # Timeout + } -t* { + if {! [regexp .\[tT\](.+) $arg ignore timeout]} { + incr i + set timeoutdflt [lindex $argv $i] + } + # Command file + } -x* { + if {! [regexp .\[xX\](.+) $arg ignore cmd_file]} { + incr i + set cmd_file [lindex $argv $i] + } + if [catch {set cmd_fd [open $cmd_file r]} reason] { + send_user "\nError: $reason\n" + exit 1 + } + set cmd_text [read $cmd_fd] + close $cmd_fd + set command [join [split $cmd_text \n] \;] + set do_command 1 + # Do we enable? + } -noenable { + set avenable 0 + # Does tacacs automatically enable us? + } -autoenable { + set avautoenable 1 + set avenable 0 + } -* { + send_user "\nError: Unknown argument! $arg\n" + send_user $usage + exit 1 + } default { + break + } + } +} +# Process routers...no routers listed is an error. +if { $i == $argc } { + send_user "\nError: $usage" +} + +# Only be quiet if we are running a script (it can log its output +# on its own) +if { $do_script } { + log_user 0 +} else { + log_user 1 +} + +# +# Done configuration/variable setting. Now run with it... +# + +# Sets Xterm title if interactive...if its an xterm and the user cares +proc label { host } { + global env + # if CLOGIN has an 'x' in it, don't set the xterm name/banner + if [info exists env(CLOGIN)] { + if {[string first "x" $env(CLOGIN)] != -1} { return } + } + # take host from ENV(TERM) + if [info exists env(TERM)] { + if [regexp \^(xterm|vs) $env(TERM) ignore] { + send_user "\033]1;[lindex [split $host "."] 0]\a" + send_user "\033]2;$host\a" + } + } +} + +# This is a helper function to make the password file easier to +# maintain. Using this the password file has the form: +# add password sl* pete cow +# add password at* steve +# add password * hanky-pie +proc add {var args} { global int_$var ; lappend int_$var $args} +proc include {args} { + global env + regsub -all "(^{|}$)" $args {} args + if { [regexp "^/" $args ignore] == 0 } { + set args $env(HOME)/$args + } + source_password_file $args +} + +proc find {var router} { + upvar int_$var list + if { [info exists list] } { + foreach line $list { + if { [string match -nocase [lindex $line 0] $router] } { + return [lrange $line 1 end] + } + } + } + return {} +} + +# Loads the password file. Note that as this file is tcl, and that +# it is sourced, the user better know what to put in there, as it +# could install more than just password info... I will assume however, +# that a "bad guy" could just as easy put such code in the clogin +# script, so I will leave .cloginrc as just an extention of that script +proc source_password_file { password_file } { + global env + if { ! [file exists $password_file] } { + send_user "\nError: password file ($password_file) does not exist\n" + exit 1 + } + file stat $password_file fileinfo + if { [expr ($fileinfo(mode) & 007)] != 0000 } { + send_user "\nError: $password_file must not be world readable/writable\n" + exit 1 + } + if [catch {source $password_file} reason] { + send_user "\nError: $reason\n" + exit 1 + } +} + +# Log into the router. +# returns: 0 on success, 1 on failure, -1 if rsh was used successfully +proc login { router user userpswd passwd enapasswd cmethod cyphertype identfile } { + global command spawn_id in_proc do_command do_script platform passphrase + global prompt prompt_match u_prompt p_prompt e_prompt sshcmd + set in_proc 1 + set uprompt_seen 0 + + # try each of the connection methods in $cmethod until one is successful + set progs [llength $cmethod] + foreach prog [lrange $cmethod 0 end] { + incr progs -1 + if [string match "telnet*" $prog] { + regexp {telnet(:([^[:space:]]+))*} $prog methcmd suffix port + if {"$port" == ""} { + set retval [catch {spawn telnet $router} reason] + } else { + set retval [catch {spawn telnet $router $port} reason] + } + if { $retval } { + send_user "\nError: telnet failed: $reason\n" + return 1 + } + } elseif [string match "ssh*" $prog] { + # ssh to the router & try to login with or without an identfile. + regexp {ssh(:([^[:space:]]+))*} $prog methcmd suffix port + set cmd $sshcmd + if {"$port" != ""} { + set cmd "$cmd -p $port" + } + if {"$identfile" != ""} { + set cmd "$cmd -i $identfile" + } + set retval [catch {eval spawn [split "$cmd -c $cyphertype -x -l $user $router" { }]} reason] + if { $retval } { + send_user "\nError: $cmd failed: $reason\n" + return 1 + } + } elseif ![string compare $prog "rsh"] { + if { ! $do_command } { + if { [llength $cmethod] == 1 } { + send_user "\nError: rsh is an invalid method for -x and " + send_user "interactive logins\n" + } + if { $progs == 0 } { + return 1 + } + continue; + } + + # handle escaped ;s in commands, and ;; and ^; + regsub -all {([^\\]);;} $command "\\1;\u002;" esccommand + regsub {^;} $esccommand "\u002;" command + set sep "\\1\u001" + regsub -all {([^\\])\;} $command "$sep" esccommand + set sep "\u001" + set commands [split $esccommand $sep] + set num_commands [llength $commands] + set rshfail 0 + for {set i 0} {$i < $num_commands && !$rshfail} { incr i} { + log_user 0 + set retval [catch {spawn rsh $user@$router [lindex $commands $i] } reason] + if { $retval } { + send_user "\nError: rsh failed: $reason\n" + log_user 1; return 1 + } + send_user "$router# [lindex $commands $i]\n" + + # rcmd does not get a pager and no prompts, so we just have to + # look for failures & lines. + expect { + "Connection refused" { catch {close}; catch {wait}; + send_user "\nError: Connection\ + Refused ($prog): $router\n" + set rshfail 1 + } + -re "(Connection closed by|Connection to \[^\n\r]+ closed)" { + catch {close}; catch {wait}; + send_user "\nError: Connection\ + closed ($prog): $router\n" + set rshfail 1 + } + "Host is unreachable" { catch {close}; catch {wait}; + send_user "\nError: Host Unreachable:\ + $router\n" + set rshfail 1 + } + "No address associated with" { + catch {close}; catch {wait}; + send_user "\nError: Unknown host\ + $router\n" + set rshfail 1 + } + -re "\b+" { exp_continue } + -re "\[\n\r]+" { send_user -- "$expect_out(buffer)" + exp_continue + } + timeout { catch {close}; catch {wait}; + send_user "\nError: TIMEOUT reached\n" + set rshfail 1 *** DIFF OUTPUT TRUNCATED AT 1000 LINES ***