Problems with RE matching in .cloginrc

Dylan Hall dylan.hall at tsnz.net
Mon Oct 15 22:59:36 UTC 2001


We're having some problems getting the .cloginrc file to do what we want. Below is an extract from the file (passwords changed). The idea is that we have some wildcard entries to catch most of our switches, but a couple of more specific matches where they differ. 

#
# Edge Switches..... 
#
add method s3-BNZW01-r01.engops.tsnz.net	{ssh}
add user s3-BNZW01-r01.engops.tsnz.net		rancid
add password s3-BNZW01-r01.engops.tsnz.net	12345


add method *-r01.engops.tsnz.net		{ssh}
add user *-r01.engops.tsnz.net			admin
add password *-r01.engops.tsnz.net		abcde


When we use xlogin (a hack of clogin or jlogin to work with Extremes - I can't remember which) the following happens

% ./xlogin -c "show version" s3-BNZW01-r01.engops.tsnz.net
s3-bnzw01-r01.engops.tsnz.net
spawn ssh -c 3des -x -l admin s3-bnzw01-r01.engops.tsnz.net

xlogin seems to be getting the wrong username.

if I change the case of entries in .cloginrc so everything is lowercase as follows:

#
# Edge Switches..... 
#
add method s3-bnzw01-r01.engops.tsnz.net	{ssh}
add user s3-bnzw01-r01.engops.tsnz.net		rancid
add password s3-bnzw01-r01.engops.tsnz.net	12345


add method *-r01.engops.tsnz.net		{ssh}
add user *-r01.engops.tsnz.net			admin
add password *-r01.engops.tsnz.net		abcde


% ./xlogin -c "show version" s3-bnzw01-r01.engops.tsnz.net
s3-bnzw01-r01.engops.tsnz.net
spawn ssh -c 3des -x -l rancid s3-bnzw01-r01.engops.tsnz.net
rancid at s3-bnzw01-r01.engops.tsnz.net's password: 
Permission denied, please try again.

xlogin seems to get the right username now, but is still getting the wrong password.

Can anyone explain/fix this? Are we using wildcards in the wrong way?

I have attached xlogin, although the functions that match passwords are unaltered.

This problems exists on my linux box with expect version 5.31.8 and on my freebsd box with expect version 5.32.1

Thanks for your help,

-- 
Dylan Hall
IP Engineer
TelstraSaturn Ltd
Ph: +64 4 9395000 Fax: +64 4 9228555
-------------- next part --------------
#!/usr/local/bin/expect --
##
##
## Copyright (C) 1997 by Henry Kilmer, Erik Sherk and Pete Whiting.
## All rights reserved.
##
## This software may be freely copied, modified and redistributed without
## fee for non-commerical purposes provided that this copyright notice is
## preserved intact on all copies and modified copies.
##
## There is no warranty or other guarantee of fitness of this software.
## It is provided solely "as is". The author(s) disclaim(s) all
## responsibility and liability with respect to this software's usage
## or its effect upon hardware, computer systems, other software, or
## anything else.
##
##
#
# xlogin - extreme ssh login
#
## Most options are intuitive for logging into a Cisco router.
## The default username password is the same as the vty password.
#

# Usage line
set usage "Usage: $argv0 \[-c command\] \[-f cloginrc-file\] \
\[-p user-password\] \[-r passphrase\] \[-s script-file\] \[-v save\] \
\[-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 enable 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
# No passphrase by default
set passphrase ""
# Don't save changes by default
set saveyn "n"

# 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)
} 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 "Error: could not exec id: $reason\n"
	exit 1
    }
    regexp {\(([^)]*)} "$reason" junk default_user
}

# Sometimes routers take awhile to answer (the default is 10 sec)
set timeout 120

# 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
	# alternate cloginrc file
	} -f* -
	-F* {
	    if {! [ regexp .\[fF\](.+) $arg ignore password_file]} {
		incr i
		set password_file [ lindex $argv $i ]
	    }
	# user Password
	} -p* -
	-P* {
	    if {! [  regexp .\[pP\](.+) $arg ignore userpswd]} {
		incr i
		set userpswd [ lindex $argv $i ]
	    }
	    set do_passwd 0
	# passphrase
	} -r* -
	-R* {
	    if {! [  regexp .\[rR\](.+) $arg ignore passphrase]} {
		incr i
		set passphrase [ lindex $argv $i ]
	    }
	# Expect script to run.
	} -s* -
	-S* {
	    if {! [  regexp .\[sS\](.+) $arg ignore sfile]} {
		incr i
		set sfile [ lindex $argv $i ]
	    }
	    if { ! [ file readable $sfile ] } {
		send_user "Error: Can't read $sfile\n"
		exit 1
	    }
	    set do_script 1
	# Timeout
	} -t* -
	-T* {
	    if {! [ regexp .\[tT\](.+) $arg ignore timeout]} {
		incr i
		set timeout [ lindex $argv $i ]
	    }
	# Username
	} -u* -
	-U* {
	    if {! [  regexp .\[uU\](.+) $arg ignore user]} {
		incr i
		set username [ lindex $argv $i ]
	    }
	# Save changes
	} -v* -
	-V* {
	    if {! [  regexp .\[vV\](.+) $arg ignore saveyn]} {
		incr i
		set saveyn [ lindex $argv $i ]
	    }
	# command file
	} -x* -
	-X* {
	    if {! [  regexp .\[xX\](.+) $arg ignore cmd_file]} {
		incr i
		set cmd_file [ lindex $argv $i ]
	    }
            set cmd_fd [open $cmd_file r]
            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* -
	-Y* {
	    if {! [  regexp .\[yY\](.+) $arg ignore cypher]} {
		incr i
		set cypher [ lindex $argv $i ]
	    }
	} -* {
	    send_user "Error: Unknown argument! $arg\n"
	    send_user $usage
	    exit 1
	} default {
	    break
	}
    }
}
# Process routers...no routers listed is an error.
if { $i == $argc } {
    send_user "Error: $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 [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 "Error: password file ($password_file) does not exist\n"
	exit 1
    }
    file stat $password_file fileinfo
    if { [expr ($fileinfo(mode) & 007)] != 0000 } {
	send_user "Error: $password_file must not be world readable/writable\n"
	exit 1
    }
    if [ catch {source $password_file} reason ] {
	send_user "Error: $reason\n"
	exit 1
    }
}

# Log into the router.
proc login { router user passwd prompt cmethod cyphertype identfile} {
    global spawn_id in_proc do_command do_script passphrase
    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] {
	if ![string compare $prog "telnet"] {
	    if [ catch {spawn telnet $router} reason ] {
		send_user "Error: telnet failed: $reason\n"
		exit 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.
	    if {$identfile != ""} {
		if [ catch {spawn ssh -c $cyphertype -x -l $user -i $identfile $router} reason ] {
		    send_user "Error: failed to ssh: $reason\n"
		    exit 1
		}
	    } else {
		if [ catch {spawn ssh -c $cyphertype -x -l $user $router} reason ] {
		    send_user "Error: failed to ssh: $reason\n"
		    exit 1
		}
	    }
	} elseif ![string compare $prog "rsh"] {
	    if [ catch {spawn rsh -l $user $router} reason ] {
		send_user "Error: rsh failed: $reason\n"
		exit 1
	    }
	} else {
	    puts "ERROR: unknown connection method: $prog"
	    return 1
	}
	incr progs -1
	sleep 0.3

	# This helps cleanup each expect clause.
	expect_after {
	    timeout {
		send_user "\nError: TIMEOUT reached\n"
		catch {close}; wait
		if { $in_proc} {
	            return 1
		} else {
	            continue
		}
            } eof {
		send_user "\nError: EOF received\n"
		catch {close}; 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|Connectionclosed by)" {
		catch {close}; wait
		if !$progs {
		   send_user "\nError: Connection Refused ($prog)\n"; return 1
		}
	    }
            eof { send_user "Error: Couldn't login\n"; wait; return 1
	    } -nocase "unknown host\r\n" {
		catch {close};
		send_user "Error: Unknown host\n"; wait; return 1
	    } "Host is unreachable" {
		catch {close};
		send_user "Error: Host Unreachable!\n"; wait; return 1
	    } "No address associated with name" {
		catch {close};
		send_user "Error: Unknown host\n"; wait; return 1
	    }
	    -re "Enter passphrase for RSA key '\[^'\]*': " {
		send_user "\nKey has passphrase!\n"
		send "$passphrase\r"
		exp_continue }
	    -re "Host key not found .* \(yes\/no\)\?"	{
		send "yes\r"
		send_user "Host $router added to the list of known hosts.\n"
		exp_continue }
	    -re "HOST IDENTIFICATION HAS CHANGED.* \(yes\/no\)\?"	{
		send "no\r"
		send_user "Error: The host key for $router has changed.  update the known_hosts file accordingly.\n"
		return 1 }
	    -re "(Username|\[\r\n]login):"	{ send "$user\r"
		expect {
		    eof             	{ send_user "Error: Couldn't login\n";
					  wait; return 1 }
		    -re "\[Pp]assword:"	{ send "$passwd\r" }
		    "$prompt"		{ set in_proc 0; return 0 }
		}
		exp_continue
	    }
	    "\[Pp]assword:"	{ send "$passwd\r"
	    
		expect {
		eof             { send_user "Error: Couldn't login\n"; wait; return 1 }
		"$prompt"       { set in_proc 0; return 0 }
	    }
	    exp_continue
	}
	"$prompt"       { break; }
	denied		{ send_user "Error: Check your passwd for $router\n"
	                  if { $do_command || $do_script } {
			      send "quit"
			      wait
			      return 1
			  } else {
			      return 1
			  }
	                }
	"% Bad passwords" {send_user "Error: Check your passwd for $router\n"; return 1 }
    }
  }
    set in_proc 0
    return 0
}

# Run commands given on the command line.
proc run_commands { prompt command saveyn } {
    global in_proc
    set in_proc 1

    send "disable clipaging\r"
    expect $prompt	{}
#    send "set cli screen-length 0\r"
#    expect $prompt {}

    # Is this a multi-command?
    if [ string match "*\;*" "$command" ] {
	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."	{ exp_continue }
		-re "(\r\n|\n)"			{ exp_continue }
            }
	}
    } else {
	send "$command\r"
	expect {
		-re "^\[^\n\r]*$prompt $"	{}
		-re "^\[^\n\r]*$prompt."	{ exp_continue }
		-re "(\r\n|\n)"			{ exp_continue }
	}
    }
    send "enable clipaging\r"
    expect $prompt {}

    send "exit\r"
    expect {
	"Do you wish to save your configuration changes? (y/n)" {
	    send "$saveyn\r"
	    exp_continue
	}


	"\n" { exp_continue }
	timeout { 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
foreach router [lrange $argv $i end] {
    set router [string tolower $router]
    send_user "$router\n"

    set prompt ">"

    # Figure out username
    if {[info exists username]} {
      # command line username
      set loginname $username
    } else {
      set loginname [find user $router]
      if { "$loginname" == "" } { set loginname $default_user }
    }

    # Figure out loginname's password (if different from the vty password)
    if {[info exists userpswd]} {
      # command line passwd
      set passwd $userpswd
    } else {
      set passwd [lindex [find password $loginname@$router] 0]
      if { "$passwd" == "" } { set passwd [lindex [find password $router] 0] }
    }

    # figure out identity file to use
    set identfile ""
    if {[info exists identity]} {
	set identfile [lindex [find identity $router] 0]
    }

    # 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}} }

    # Login to the router
    if {[login $router $loginname $passwd $prompt $cmethod $cyphertype $identfile]} {
	continue
    }

    if { $do_command } {
	if {[run_commands $prompt $command $saveyn]} {
	    continue
	}
    } elseif { $do_script } {
	send "disable clipaging\r"
	expect $prompt	{}
#	send "set cli screen-length 0\r"
#	expect $prompt	{}
	source $sfile
	close
    } else {
	label $router
	log_user 1
	interact
    }

    # End of for each router
    wait
    sleep 0.3
}
exit 0


More information about the Rancid-discuss mailing list