From 3ed87c1ed7f1207d29f97429ec297cb43441e98f Mon Sep 17 00:00:00 2001 From: Mike Shoup Date: Fri, 6 Feb 2015 12:50:34 -0700 Subject: [PATCH] Added scripts --- README.md | 24 ++- uelogin | 522 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ uerancid | 374 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 918 insertions(+), 2 deletions(-) create mode 100755 uelogin create mode 100755 uerancid diff --git a/README.md b/README.md index d21112a..73ed010 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,22 @@ -# uerancid -A set of scripts to use Rancid with an Ubiquiti EdgeRouter +RANCID Scripts for Ubiquiti/UBNT EdgeRouters +====== + +Ubiquiti/UBNT Edgerouter scripts for Rancid. Forked from +https://bitbucket.org/aquerubin/rancid-vyatta + +Includes: + +* uelogin - basic login script +* vrancid - the Rancid wrapper + +To integrate into your RANCID install: + +* Copy uelogin and uerancid to your 'bin' directory +* Modify 'rancid.types.conf', and add uerancid.. IE: + +``` +ubnt-er;script;uerancid +``` +* Add a new device to your 'router.db', with the vendor of 'ubnt-er' +* Use it :) + diff --git a/uelogin b/uelogin new file mode 100755 index 0000000..f01e2e8 --- /dev/null +++ b/uelogin @@ -0,0 +1,522 @@ +#! /usr/bin/expect -- +## +## +## rancid 2.3.6 +## Copyright (c) 1997-2009 by Terrapin Communications, Inc. +## All rights reserved. +## + +# Usage line +set usage "Usage: $argv0 \[-dSV\] \[-c command\] \[-Evar=x\] \ +\[-f cloginrc-file\] \[-p user-password\] \[-r passphrase\] \[-s script-file\] \ +\[-u username\] \[-t timeout\] \[-x command-file\] \[-y ssh_cypher_type\] \ +router \[router...\]\n" + +# env(CLOGIN) may contain the following chars: +# 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 to look in the password file to find the passwords. This +# tracks if we receive them on the command line. +set do_passwd 1 +# Save config, if prompted +set do_saveconfig 1 +# Sometimes routers take awhile to answer (the default is 10 sec) +set timeoutdflt 120 + +# 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 { + # Command to run. + -c* - + -C* { + if {! [ regexp .\[cC\](.+) $arg ignore command]} { + incr i + set command [ lindex $argv $i ] + } + set do_command 1 + # Expect debug mode + } -d* { + exp_internal 1 + # 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 + } + # alternate cloginrc file + } -f* { + if {! [ regexp .\[fF\](.+) $arg ignore password_file]} { + incr i + set password_file [ lindex $argv $i ] + } + # user 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 avpassphrase [ lindex $argv $i ] + } + # VTY Password + } -v* { + # ignore -v + # Version string + } -V* { + send_user "rancid 2.3.6\n" + exit 0 + # 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 + # Timeout + } -t* { + if {! [ regexp .\[tT\](.+) $arg ignore timeout]} { + incr i + set timeoutdflt [ lindex $argv $i ] + } + # Username + } -u* { + if {! [ regexp .\[uU\](.+) $arg ignore user]} { + incr i + set username [ 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 + # 'ssh -c' cypher type + } -y* { + if {! [ regexp .\[yY\](.+) $arg ignore cypher]} { + incr i + set cypher [ lindex $argv $i ] + } + } -* { + 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 1 +} 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 [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 +proc login { router user passwd cmethod cyphertype identfile} { + global spawn_id in_proc do_command do_script passphrase prompt + global sshcmd + set in_proc 1 + + # 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 command 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 compare $prog "ssh"] { + # ssh to the router & try to login with or without an identfile. + # We use two calls to spawn since spawn does not seem to parse + # spaces correctly. + regexp {ssh(:([^[:space:]]+))*} $prog methcmd suffix port + set cmd [join [lindex $sshcmd 0] " "] + 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: $sshcmd failed: $reason\n" + return 1 + } + } elseif ![string compare $prog "rsh"] { + send_error "\nError: unsupported method: rsh\n" + if { $progs == 0 } { + return 1 + } + continue + } else { + send_user "\nError: unknown connection method: $prog\n" + return 1 + } + sleep 0.3 + + # This helps cleanup each expect clause. + expect_after { + timeout { + send_user "\nError: TIMEOUT reached\n" + catch {close}; catch {wait}; + if { $in_proc} { + return 1 + } else { + continue + } + } eof { + send_user "\nError: EOF received\n" + catch {close}; catch {wait}; + if { $in_proc} { + return 1 + } else { + continue + } + } + } + + # Here we get a little tricky. There are several possibilities: + # the router can ask for a username and passwd and then + # talk to the TACACS server to authenticate you, or if the + # TACACS server is not working, then it will use the enable + # passwd. Or, the router might not have TACACS turned on, + # then it will just send the passwd. + expect { + -re "(Connection refused|Secure connection \[^\n\r]+ refused|Connection closed by)" { + catch {close}; catch {wait}; + if !$progs { + send_user "\nError: Connection Refused ($prog)\n"; return 1 + } + } + eof { send_user "\nError: Couldn't login\n"; wait; return 1 + } -nocase "unknown host\r\n" { + catch {close}; catch {wait}; + send_user "\nError: Unknown host\n"; wait; return 1 + } "Host is unreachable" { + catch {close}; catch {wait}; + send_user "\nError: Host Unreachable!\n"; wait; return 1 + } "No address associated with name" { + catch {close}; catch {wait}; + send_user "\nError: Unknown host\n"; wait; return 1 + } + "Login incorrect" { + send_user "\nError: Check your password for $router\n" + catch {close}; catch {wait}; return 1 + } + -re "Enter passphrase.*: " { + # sleep briefly to allow time for stty -echo + sleep 1 + send -- "$passphrase\r" + exp_continue } + -re "(Host key not found |The authenticity of host .* be established).*\(yes\/no\)\?" { + send "yes\r" + send_user "\nHost $router added to the list of known hosts.\n" + exp_continue } + -re "HOST IDENTIFICATION HAS CHANGED.* \(yes\/no\)\?" { + send "no\r" + send_user "\nError: The host key for $router has changed. Update the SSH known_hosts file accordingly.\n" + return 1 } + -re "Offending key for .* \(yes\/no\)\?" { + send "no\r" + send_user "\nError: host key mismatch for $router. Update the SSH known_hosts file accordingly.\n" + return 1 } + -re "(Username|\[\r\n]login):" { + send -- "$user\r" + exp_continue + } + "\[Pp]assword:" { + sleep 1; send -- "$passwd\r" + exp_continue + } + -re "$prompt" { break; } + denied { send_user "\nError: Check your password for $router\n" + catch {close}; catch {wait}; return 1 + } + } + } + + # we are logged in, now figure out the full prompt + send "\r" + expect { + -re "(\r\n|\n)" { exp_continue; } + -re "^\[^ ]+$prompt" { + set prompt $expect_out(0,string); + regsub ">" $prompt "\[#>]" prompt; + } + } + + set in_proc 0 + return 0 +} + +# Run commands given on the command line. +proc run_commands { prompt command } { + global in_proc + set in_proc 1 + + # For some reason the full VyOS prompt is prefixed with an escape + # sequence \e]0; and the configure mode prompt does not contain the + # current path found in the normal prompt. Strip these out to form + # the config mode prompt. + set prompt [regsub "^.*;" $prompt ""] + # Remove the path starting with : + set prompt_config [regsub {:.*} $prompt ""] + + send "terminal length 0\r" + + set commands [split $command \;] + set num_commands [llength $commands] + for {set i 0} {$i < $num_commands} { incr i} { + send "[lindex $commands $i]\r" + + expect { + -re "^\[^\n\r *]*$prompt" {} + -re "^\[^\n\r *]*$prompt_config" {} + -re "^\[^\n\r]*$prompt $" { exp_continue } + -re "(\r\n|\n)" { exp_continue } + } + } + send "exit\r" + expect { + "\n" { exp_continue } + timeout { catch {close}; catch {wait}; + return 0 + } + eof { return 0 } + } + set in_proc 0 +} + +# +# For each router... (this is main loop) +# +source_password_file $password_file +set in_proc 0 +set exitval 0 +foreach router [lrange $argv $i end] { + set router [string tolower $router] + send_user "$router\n" + # device timeout + set timeout [find timeout $router] + if { [llength $timeout] == 0 } { + set timeout $timeoutdflt + } + + set prompt ":" + + # Figure out username + if {[info exists username]} { + # command line username + set loginname $username + } else { + set loginname [join [find user $router] ""] + if { "$loginname" == "" } { set loginname $default_user } + } + + # Figure out loginname's password (if different from the vty password) + if {[info exists userpasswd]} { + # command line passwd + set passwd $userpasswd + } else { + set passwd [join [lindex [find userpassword $router] 0] ""] + if { "$passwd" == "" } { + set passwd [join [lindex [find password $router] 0] ""] + if { "$passwd" == "" } { + send_user "\nError: no password for $router in $password_file.\n" + continue + } + } + } + + # Figure out identity file to use + set identfile [join [lindex [find identity $router] 0] ""] + + # Figure out passphrase to use + if {[info exists avpassphrase]} { + set passphrase $avpassphrase + } else { + set passphrase [join [lindex [find passphrase $router] 0] ""] + } + if { ! [string length "$passphrase"]} { + set passphrase $passwd + } + + # Figure out ssh cypher type + if {[info exists cypher]} { + # command line ssh cypher type + set cyphertype $cypher + } else { + set cyphertype [find cyphertype $router] + if { "$cyphertype" == "" } { set cyphertype "3des" } + } + + # Figure out connection method + set cmethod [find method $router] + if { "$cmethod" == "" } { set cmethod {{telnet} {ssh}} } + + # Figure out the SSH executable name + set sshcmd [find sshcmd $router] + if { "$sshcmd" == "" } { set sshcmd {ssh} } + + # Login to the router + if {[login $router $loginname $passwd $cmethod $cyphertype $identfile]} { + incr exitval + continue + } + + if { $do_command } { + if {[run_commands $prompt $command]} { + incr exitval + continue + } + } elseif { $do_script } { + send "set terminal length 0\r" + expect -re $prompt {} + source $sfile + catch {close}; + } else { + label $router + log_user 1 + interact + } + + # End of for each router + catch {wait}; + sleep 0.3 +} +exit $exitval diff --git a/uerancid b/uerancid new file mode 100755 index 0000000..149f2d6 --- /dev/null +++ b/uerancid @@ -0,0 +1,374 @@ +#! /usr/bin/perl +# By Damian Fantini +# usage: vrancid [-dV] [-l] [-f filename | hostname] +# +use Getopt::Std; +getopts('dflV'); +if ($opt_V) { + print "vrancid 3.0\n"; + exit(0); +} +$debug = $opt_d; +$log = $opt_l; +$file = $opt_f; +$host = $ARGV[0]; + +$clean_run = 0; +$found_end = 0; +$timeo = 120; # clogin timeout in seconds + +my(@commandtable, %commands, @commands);# command lists +my($aclsort) = ("ipsort"); # ACL sorting mode +my($filter_commstr); # SNMP community string filtering +my($filter_pwds); # password filtering mode +#my($ShowChassisSCB); # Only run ShowChassisSCB() once + +# This routine is used to print out the router configuration +sub ProcessHistory { + my($new_hist_tag,$new_command,$command_string,@string) = (@_); + if ((($new_hist_tag ne $hist_tag) || ($new_command ne $command)) + && scalar(%history)) { + print eval "$command \%history"; + undef %history; + } + if (($new_hist_tag) && ($new_command) && ($command_string)) { + if ($history{$command_string}) { + $history{$command_string} = "$history{$command_string}@string"; + } else { + $history{$command_string} = "@string"; + } + } elsif (($new_hist_tag) && ($new_command)) { + $history{++$#history} = "@string"; + } else { + print "@string"; + } + $hist_tag = $new_hist_tag; + $command = $new_command; + 1; +} + +sub numerically { $a <=> $b; } + +# This is a sort routine that will sort numerically on the +# keys of a hash as if it were a normal array. +sub keynsort { + local(%lines) = @_; + local($i) = 0; + local(@sorted_lines); + foreach $key (sort numerically keys(%lines)) { + $sorted_lines[$i] = $lines{$key}; + $i++; + } + @sorted_lines; +} + +# This is a sort routine that will sort on the +# keys of a hash as if it were a normal array. +sub keysort { + local(%lines) = @_; + local($i) = 0; + local(@sorted_lines); + foreach $key (sort keys(%lines)) { + $sorted_lines[$i] = $lines{$key}; + $i++; + } + @sorted_lines; +} + +# This is a sort routine that will sort on the +# values of a hash as if it were a normal array. +sub valsort{ + local(%lines) = @_; + local($i) = 0; + local(@sorted_lines); + foreach $key (sort values %lines) { + $sorted_lines[$i] = $key; + $i++; + } + @sorted_lines; +} + +# This is a numerical sort routine (ascending). +sub numsort { + local(%lines) = @_; + local($i) = 0; + local(@sorted_lines); + foreach $num (sort {$a <=> $b} keys %lines) { + $sorted_lines[$i] = $lines{$num}; + $i++; + } + @sorted_lines; +} + +# This is a sort routine that will sort on the +# ip address when the ip address is anywhere in +# the strings. +sub ipsort { + local(%lines) = @_; + local($i) = 0; + local(@sorted_lines); + foreach $addr (sort sortbyipaddr keys %lines) { + $sorted_lines[$i] = $lines{$addr}; + $i++; + } + @sorted_lines; +} + +# These two routines will sort based upon IP addresses +sub ipaddrval { + my(@a) = ($_[0] =~ m#^(\d+)\.(\d+)\.(\d+)\.(\d+)$#); + $a[3] + 256 * ($a[2] + 256 * ($a[1] +256 * $a[0])); +} +sub sortbyipaddr { + &ipaddrval($a) <=> &ipaddrval($b); +} + +### +### Start of real work +### + + + +# This routine parses "show configuration" +sub ShowConfiguration { + my($lines) = 0; + my($snmp) = 0; + print STDERR " In ShowConfiguration: $_" if ($debug); + + ProcessHistory("","","","# $_"); + while () { + tr/\015//d; + next if (/^\s*$/); + # end of config - hopefully. VyOS does not have a reliable + # end-of-config tag. appears to end with "\nPROMPT>", but not sure. + if (/$prompt/) { + $found_end++; + last; + } + next if (/^system (shutdown message from|going down )/i); + next if (/^\{(master|backup)(:\d+)?\}/); + + # Remove Uptime + next if (/^uptime/i); + # Sometimes the 'show' commands are echoed back + next if (/^show/); + next if (/^exit/); + + $lines++; + + /^database header mismatch: / && return(-1); + /^version .*;\d+$/ && return(-1); + + s/ # SECRET-DATA$//; + s/ ## SECRET-DATA$//; + # filter snmp community, when in snmp { stanza } + /^\s*snmp/ && $snmp++; + /^}/ && ($snmp = 0); + if ($snmp && /^(\s*)(community|trap-group) [^ ;]+(\s?[;{])$/) { + if ($filter_commstr) { + $_ = "$1$2 \"\"$3\n"; + } + } + + if (/^(.* snmp community) [^ ;]+(\s?.*)$/) { + if ($filter_commstr) { + $_ = "$1 \"\"$2\n"; + } + } + if (/^(.* snmp .*-key) [^ ;]+(\s?.*)$/) { + if ($filter_commstr) { + $_ = "$1 \"\"$2\n"; + } + } + if (/(\s*authentication-key )[^ ;]+/ && $filter_pwds >= 1) { + ProcessHistory("","","","#$1$'"); + next; + } + if (/(\s*md5 \d+ key )[^ ;]+/ && $filter_pwds >= 1) { + ProcessHistory("","","","#$1$'"); + next; + } + if (/(\s*hello-authentication-key )[^ ;]+/ && $filter_pwds >= 1) { + ProcessHistory("","","","#$1$'"); + next; + } + # don't filter this one - there is no secret here. + if (/^\s*permissions .* secret /) { + ProcessHistory("","","","$_"); + next; + } + if (/^(.*\ssecret )[^ ;]+/ && $filter_pwds >= 1) { + ProcessHistory("","","","#$1$'"); + next; + } + if (/(.* encrypted-password )[^ ;]+/ && $filter_pwds >= 2) { + ProcessHistory("","","","#$1\n"); + next; + } + if (/(\s+ssh-(rsa|dsa) )\"/ && $filter_pwds >= 2) { + ProcessHistory("","","","#$1;\n"); + next; + } + if (/^(\s+(pre-shared-|)key (ascii-text|hexadecimal) )[^ ;]+/ && $filter_pwds >= 1) { + ProcessHistory("","","","#$1$'"); + next; + } + ProcessHistory("","","","$_"); + } + + if ($lines < 1) { + printf(STDERR "ERROR: $host configuration appears truncated.\n"); + $found_end = 0; + return(-1); + } + + return(0); +} + +### +### End of real work +### + +# dummy function +sub DoNothing {print STDOUT;} + +# Main +@commandtable = ( + {'show version' => 'ShowConfiguration'}, + {'show system image' => 'ShowConfiguration'}, + {'show hardware cpu' => 'ShowConfiguration'}, + {'show configuration' => 'ShowConfiguration'}, + {'show system routing-daemons' => 'ShowConfiguration'}, +); +# Use an array to preserve the order of the commands and a hash for mapping +# commands to the subroutine and track commands that have been completed. +@commands = map(keys(%$_), @commandtable); +%commands = map(%$_, @commandtable); + +$jnx_commands=join(";",@commands); +$cmds_regexp = join("|", map quotemeta($_), @commands); + +if (length($host) == 0) { + if ($file) { + print(STDERR "Too few arguments: file name required\n"); + exit(1); + } else { + print(STDERR "Too few arguments: host name required\n"); + exit(1); + } +} +open(OUTPUT,">$host.new") || die "Can't open $host.new for writing: $!\n"; +select(OUTPUT); +# make OUTPUT unbuffered +if ($debug) { $| = 1; } + +if ($file) { + print STDERR "opening file $host\n" if ($debug); + print STDOUT "opening file $host\n" if ($log); + open(INPUT,"< $host") || die "open failed for $host: $!\n"; +} else { + print(STDERR "executing echo uelogin -t $timeo -c\"$jnx_commands\" $host\n") if ($debug); + + if (defined($ENV{NOPIPE})) { + system "uelogin -t $timeo -c \"$jnx_commands\" $host $host.raw" || die "uelogin failed for $host: $!\n"; + open(INPUT, "< $host.raw") || die "uelogin failed for $host: $!\n"; + } else { + open(INPUT,"uelogin -t $timeo -c \"$jnx_commands\" $host ) { + tr/\015//d; + if (/^Error:/) { + print STDOUT ("$host uelogin error: $_"); + print STDERR ("$host uelogin error: $_") if ($debug); + $clean_run=0; + last; + } + if (/System shutdown message/) { + print STDOUT ("$host shutdown msg: $_"); + print STDERR ("$host shutdown msg: $_") if ($debug); + $clean_run = 0; + last; + } + if (/error: cli version does not match Managment Daemon/i) { + print STDOUT ("$host mgd version mismatch: $_"); + print STDERR ("$host mgd version mismatch: $_") if ($debug); + $clean_run = 0; + last; + } + while (/\s*($cmds_regexp)\s*$/) { + $cmd = $1; + $prompt = ":~"; + + if (!defined($prompt)) { + $prompt = ($_ =~ /^([^>]+$)/)[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}}; + delete($commands{$cmd}); + if ($rval == -1) { + $clean_run = 0; + last TOP; + } + } + if (/\s*exit/) { + $clean_run=1; + last; + } +} +print STDOUT "Done uelogin: $_\n" if ($log); +# Flush History +ProcessHistory("","","",""); +# Cleanup +close(INPUT); +close(OUTPUT); + +if (defined($ENV{NOPIPE})) { + unlink("$host.raw") if (! $debug); +} + +# check for completeness +$commands = join(", ", keys(%commands)); +if (scalar(%commands) || !$clean_run || !$found_end) { + if (scalar(%commands)) { + printf(STDOUT "$host: missed cmd(s): %s\n", join(',', keys(%commands))); + printf(STDERR "$host: missed cmd(s): %s\n", join(',', keys(%commands))) if ($debug); + } + if (!$clean_run || !$found_end) { + print STDOUT "$host: End of run not found\n"; + print STDERR "$host: End of run not found\n" if ($debug); + system("/usr/bin/tail -1 $host.new"); + } + unlink "$host.new" if (! $debug); +}