1#! @EXPECT_PATH@ -- 2## 3## @PACKAGE@ @VERSION@ 4@copyright@ 5# 6# elogin - ADC EZT3 login 7# 8# Most options are intuitive for logging into an ADC EZT3 mux. 9# 10 11# XXX need to import login_top. 12 13# Usage line 14set usage "Usage: $argv0 \[-diSV\] \[-noenable\] \[-c command\] \ 15\[-Evar=x\] \[-f cloginrc-file\] \[-p user-password\] \ 16\[-s script-file\] \[-t timeout\] \[-u username\] \ 17\[-v vty-password\] \[-w enable-username\] \[-x command-file\] \ 18\[-y ssh_cypher_type\] router \[router...\]\n" 19 20# env(CLOGIN) may contain: 21# x == do not set xterm banner or name 22 23# Password file 24set password_file $env(HOME)/.cloginrc 25# Default is to login to the router 26set do_command 0 27set do_interact 0 28set do_script 0 29# The default is to automatically enable 30set avenable 1 31# The default is that you login non-enabled (tacacs can have you login already 32# enabled) 33set avautoenable 0 34# The default is to look in the password file to find the passwords. This 35# tracks if we receive them on the command line. 36set do_passwd 1 37# Save config, if prompted 38set do_saveconfig 0 39# cloginrc debugging knob 40set do_cloginrcdbg 0 41# Sometimes routers take awhile to answer (the default is 10 sec) 42set timeoutdflt 45 43# intialize cloginrc parsing stacks 44set int_file {} 45set int_lineno {} 46# Some CLIs having problems if we write too fast (Extreme, PIX, Cat) 47set send_human {.2 .1 .4 .2 1} 48 49# Find the user in the ENV, or use the unix userid. 50if {[ info exists env(CISCO_USER) ]} { 51 set default_user $env(CISCO_USER) 52} elseif {[ info exists env(USER) ]} { 53 set default_user $env(USER) 54} elseif {[ info exists env(LOGNAME) ]} { 55 set default_user $env(LOGNAME) 56} else { 57 # This uses "id" which I think is portable. At least it has existed 58 # (without options) on all machines/OSes I've been on recently - 59 # unlike whoami or id -nu. 60 if [ catch {exec id} reason ] { 61 send_error "\nError: could not exec id: $reason\n" 62 exit 1 63 } 64 regexp {\(([^)]*)} "$reason" junk default_user 65} 66if {[ info exists env(CLOGINRC) ]} { 67 set password_file $env(CLOGINRC) 68} 69 70# Process the command line 71for {set i 0} {$i < $argc} {incr i} { 72 set arg [lindex $argv $i] 73 74 switch -glob -- $arg { 75 # Expect debug mode 76 -d* { 77 exp_internal 1 78 # Username 79 } -u* { 80 if {! [ regexp .\[uU\](.+) $arg ignore user]} { 81 incr i 82 set username [ lindex $argv $i ] 83 } 84 # cloginrc debugging knobs 85 } -m* { 86 set do_cloginrcdbg 1 87 } -M* { 88 set do_cloginrcdbg 2 89 # interactive 90 } -i* { 91 set do_interact 1 92 # VTY Password 93 } -p* { 94 if {! [ regexp .\[pP\](.+) $arg ignore userpasswd]} { 95 incr i 96 set userpasswd [ lindex $argv $i ] 97 } 98 set do_passwd 0 99 # ssh passphrase 100 } -r* { 101 # ignore -r 102 # VTY Password 103 } -v* { 104 if {! [ regexp .\[vV\](.+) $arg ignore passwd]} { 105 incr i 106 set passwd [ lindex $argv $i ] 107 } 108 set do_passwd 0 109 # Version string 110 } -V* { 111 send_user "@PACKAGE@ @VERSION@\n" 112 exit 0 113 # Enable Username 114 } -w* { 115 # ignore -w 116 # Environment variable to pass to -s scripts 117 } -E* { 118 if {[ regexp .\[E\](.+)=(.+) $arg ignore varname varvalue]} { 119 set E$varname $varvalue 120 } else { 121 send_user "\nError: invalid format for -E in $arg\n" 122 exit 1 123 } 124 # Enable Password 125 } -e* { 126 # ignore -e 127 # Command to run. 128 } -c* { 129 if {! [ regexp .\[cC\](.+) $arg ignore command]} { 130 incr i 131 set command [ lindex $argv $i ] 132 } 133 set do_command 1 134 # Expect script to run. 135 } -s* { 136 if {! [ regexp .\[sS\](.+) $arg ignore sfile]} { 137 incr i 138 set sfile [ lindex $argv $i ] 139 } 140 if { ! [ file readable $sfile ] } { 141 send_user "\nError: Can't read $sfile\n" 142 exit 1 143 } 144 set do_script 1 145 # save config on exit 146 } -S* { 147 set do_saveconfig 1 148 # 'ssh -c' cypher type 149 } -y* { 150 if {! [ regexp .\[eE\](.+) $arg ignore cypher]} { 151 incr i 152 set cypher [ lindex $argv $i ] 153 } 154 # alternate cloginrc file 155 } -f* { 156 if {! [ regexp .\[fF\](.+) $arg ignore password_file]} { 157 incr i 158 set password_file [ lindex $argv $i ] 159 } 160 # Timeout 161 } -t* { 162 if {! [ regexp .\[tT\](.+) $arg ignore timeout]} { 163 incr i 164 set timeoutdflt [ lindex $argv $i ] 165 } 166 # Command file 167 } -x* { 168 if {! [ regexp .\[xX\](.+) $arg ignore cmd_file]} { 169 incr i 170 set cmd_file [ lindex $argv $i ] 171 } 172 if [ catch {set cmd_fd [open $cmd_file r]} reason ] { 173 send_user "\nError: $reason\n" 174 exit 1 175 } 176 set cmd_text [read $cmd_fd] 177 close $cmd_fd 178 set command [join [split $cmd_text \n] \;] 179 set do_command 1 180 # Do we enable? 181 } -noenable { 182 # ignore -noenable 183 # Does tacacs automatically enable us? 184 } -autoenable { 185 # ignore -autoenable 186 } -* { 187 send_user "\nError: Unknown argument! $arg\n" 188 send_user $usage 189 exit 1 190 } default { 191 break 192 } 193 } 194} 195# Process routers...no routers listed is an error. 196if { $i == $argc } { 197 send_user "\nError: $usage" 198} 199 200# Only be quiet if we are running a script (it can log its output 201# on its own) 202if { $do_script } { 203 log_user 0 204} else { 205 log_user 1 206} 207 208# 209# Done configuration/variable setting. Now run with it... 210# 211 212# Sets Xterm title if interactive...if its an xterm and the user cares 213proc label {host} { 214 global env 215 # if CLOGIN has an 'x' in it, don't set the xterm name/banner 216 if [info exists env(CLOGIN)] { 217 if {[string first "x" $env(CLOGIN)] != -1} { return } 218 } 219 # take host from ENV(TERM) 220 if [info exists env(TERM)] { 221 if [regexp \^(xterm|vs) $env(TERM) ignore] { 222 send_user "\033]1;[lindex [split $host "."] 0]\a" 223 send_user "\033]2;$host\a" 224 } 225 } 226} 227 228# This is a helper function to make the password file easier to 229# maintain. Using this the password file has the form: 230# add password sl* pete cow 231# add password at* steve 232# add password * hanky-pie 233proc add {var args} { 234 global int_file int_lineno int_$var 235 set file [lindex $int_file 0] 236 set lineno [lindex $int_lineno 0] 237 lappend int_$var "$var:$file:$lineno: $args" 238} 239proc include {args} { 240 global env 241 regsub -all "(^{|}$)" $args {} args 242 if {[regexp "^/" $args ignore] == 0} { 243 set args $env(HOME)/$args 244 } 245 source_password_file $args 246} 247 248proc find {var router} { 249 global do_cloginrcdbg 250 upvar int_$var list 251 if {[info exists list]} { 252 foreach line $list { 253 if {[string match -nocase [lindex $line 1] $router]} { 254 if {$do_cloginrcdbg > 0} { 255 send_error -- [join [list [lindex $line 0] [lrange $line 1 end] "\r\n"]] 256 } 257 if {$do_cloginrcdbg == 2} { 258 # save return value 259 if {! [info exists result]} { 260 set result [lrange $line 2 end] 261 } 262 } else { 263 return [lrange $line 2 end] 264 } 265 } 266 } 267 } 268 269 if {$do_cloginrcdbg == 2} { 270 if {[info exists result]} { 271 return $result 272 } 273 } 274 return {} 275} 276 277# Loads the password file. Note that as this file is tcl, and that 278# it is sourced, the user better know what to put in there, as it 279# could install more than just password info... I will assume however, 280# that a "bad guy" could just as easy put such code in the clogin 281# script, so I will leave .cloginrc as just an extention of that script 282proc source_password_file {file} { 283 global env int_file int_lineno 284 if {! [file exists $file]} { 285 send_user "\nError: password file ($file) does not exist\n" 286 exit 1 287 } 288 file stat $file fileinfo 289 if {[expr ($fileinfo(mode) & 007)] != 0000} { 290 send_user "\nError: $file must not be world readable/writable\n" 291 exit 1 292 } 293 if [catch {set fd [open $file "r"]} reason] { 294 send_user "\nError: $reason\n" 295 exit 1 296 } 297 set int_file [linsert $int_file 0 $file] 298 set int_lineno [linsert $int_lineno 0 0] 299 while {[gets $fd line] >= 0} { 300 set tmp [lindex $int_lineno 0]; incr tmp 301 lset int_lineno 0 $tmp 302 eval $line 303 } 304 set int_file [lrange $int_file 1 end] 305 set int_lineno [lrange $int_lineno 1 end] 306 close $fd 307} 308 309# Log into the router. 310# returns: 0 on success, 1 on failure 311proc login { router user userpswd passwd prompt cmethod cyphertype } { 312 global spawn_id in_proc do_command do_script 313 global u_prompt p_prompt telnetcmd 314 set in_proc 1 315 set uprompt_seen 0 316 317 # try each of the connection methods in $cmethod until one is successful 318 set progs [llength $cmethod] 319 foreach prog [lrange $cmethod 0 end] { 320 incr progs -1 321 if [string match "telnet*" $prog] { 322 regexp {telnet(:([^[:space:]]+))*} $prog command suffix port 323 if {"$port" == ""} { 324 set retval [ catch {eval spawn [split "$telnetcmd $router"]} reason ] 325 } else { 326 set retval [ catch {eval spawn [split "$telnetcmd $router $port"]} reason ] 327 } 328 if { $retval } { 329 send_user "\nError: telnet failed: $reason\n" 330 return 1 331 } 332 } elseif ![string compare $prog "ssh"] { 333 send_error "\nError: unsupported method: ssh\n" 334 if { $progs == 0 } { 335 return 1 336 } 337 continue 338 } elseif ![string compare $prog "rsh"] { 339 send_error "\nError: unsupported method: rsh\n" 340 if { $progs == 0 } { 341 return 1 342 } 343 continue 344 } else { 345 send_user "\nError: unknown connection method: $prog\n" 346 return 1 347 } 348 sleep 0.3 349 350 # This helps cleanup each expect clause. 351 expect_after { 352 timeout { 353 global in_proc 354 send_user "\nError: TIMEOUT reached\n" 355 catch {close}; catch {wait}; 356 if {$in_proc} { 357 return 1 358 } else { 359 continue 360 } 361 } eof { 362 global in_proc 363 send_user "\nError: EOF received\n" 364 catch {close}; catch {wait}; 365 if {$in_proc} { 366 return 1 367 } else { 368 continue 369 } 370 } 371 } 372 373 expect { 374 "Connection refused" { 375 catch {close}; catch {wait}; 376 sleep 0.3 377 expect eof 378 send_user "\nError: Connection Refused\n"; wait; return 1 379 } eof { send_user "\nError: Couldn't login\n"; wait; return 1 380 } "Unknown host\r\n" { 381 expect eof 382 send_user "\nError: Unknown host\n"; wait; return 1 383 } "Host is unreachable" { 384 expect eof 385 send_user "\nError: Host Unreachable!\n"; wait; return 1 386 } "No address associated with name" { 387 expect eof 388 send_user "\nError: Unknown host\n"; wait; return 1 389 } 390 -re "$u_prompt" { 391 send -- "$user\r" 392 set uprompt_seen 1 393 exp_continue 394 } 395 -re "$p_prompt" { 396 sleep 1 397 if {$uprompt_seen == 1} { 398 send -- "$userpswd\r" 399 } else { 400 send -- "$passwd\r" 401 } 402 exp_continue 403 } 404 "Password incorrect" { send_user "\nError: Check your password for $router\n"; 405 catch {close}; catch {wait}; return 1 406 } 407 "$prompt" { break; } 408 denied { send_user "\nError: Check your passwd for $router\n" 409 catch {close}; catch {wait}; return 1 410 } 411 "\r\n" { exp_continue; } 412 } 413 } 414 set in_proc 0 415 return 0 416} 417 418# Run commands given on the command line. 419proc run_commands { prompt command } { 420 global do_interact in_proc 421 set in_proc 1 422 423 send "screen 0\r" 424 expect $prompt {} 425 426 # escape any parens in the prompt, such as "(enable)" 427 regsub -all "\[)(]" $prompt {\\&} reprompt 428 429 # handle escaped ;s in commands, and ;; and ^; 430 regsub -all {([^\\]);} $command "\\1\u0002;" esccommand 431 regsub -all {([^\\]);;} $esccommand "\\1;\u0002;" command 432 regsub {^;} $command "\u0002;" esccommand 433 regsub -all {[\\];} $esccommand ";" command 434 regsub -all {\u0002;} $command "\u0002" esccommand 435 set sep "\u0002" 436 set commands [split $esccommand $sep] 437 set num_commands [llength $commands] 438 for {set i 0} {$i < $num_commands} { incr i} { 439 send -- "[subst -nocommands [lindex $commands $i]]\r" 440 expect { 441 -re "^\[^\n\r]*$reprompt." { exp_continue } 442 -re "^\[^\n\r *]*$reprompt" {} 443 -re "\[\n\r]" { exp_continue } 444 } 445 } 446 447 if { $do_interact == 1 } { 448 interact 449 return 0 450 } 451 452 send "exit\r" 453 expect { 454 "\n" { exp_continue } 455 timeout { catch {close}; catch {wait}; 456 return 0 457 } 458 eof { return 0 } 459 } 460 set in_proc 0 461} 462 463# 464# For each router... (this is main loop) 465# 466source_password_file $password_file 467set in_proc 0 468set exitval 0 469foreach router [lrange $argv $i end] { 470 set router [string tolower $router] 471 send_user "$router\n" 472 473 # device timeout 474 set timeout [find timeout $router] 475 if { [llength $timeout] == 0 } { 476 set timeout $timeoutdflt 477 } 478 479 # Figure out prompt. 480 set prompt "Active) > " 481 set autoenable 1 482 set enable 0 483 484 # Figure out passwords 485 if { $do_passwd } { 486 set pswd [find password $router] 487 if { [llength $pswd] == 0 } { 488 send_user -- "\nError: no password for $router in $password_file.\n" 489 continue 490 } 491 set passwd [join [lindex $pswd 0] ""] 492 } 493 494 # Figure out username 495 if {[info exists username]} { 496 # command line username 497 set ruser $username 498 } else { 499 set ruser [join [find user $router] ""] 500 if { "$ruser" == "" } { set ruser $default_user } 501 } 502 503 # Figure out username's password (if different from the vty password) 504 if {[info exists userpasswd]} { 505 # command line username 506 set userpswd $userpasswd 507 } else { 508 set userpswd [join [find userpassword $router] ""] 509 if { "$userpswd" == "" } { set userpswd $passwd } 510 } 511 512 # Figure out prompts 513 set u_prompt [find userprompt $router] 514 if { "$u_prompt" == "" } { 515 set u_prompt "(Username|login| Login):" 516 } else { 517 set u_prompt [join [lindex $u_prompt 0] ""] 518 } 519 set p_prompt [find passprompt $router] 520 if { "$p_prompt" == "" } { 521 set p_prompt "\[Pp]assword:" 522 } else { 523 set p_prompt [join [lindex $p_prompt 0] ""] 524 } 525 526 # Figure out cypher type 527 if {[info exists cypher]} { 528 # command line cypher type 529 set cyphertype $cypher 530 } else { 531 set cyphertype [find cyphertype $router] 532 } 533 534 # Figure out connection method 535 set cmethod [find method $router] 536 if { "$cmethod" == "" } { set cmethod {{telnet}} } 537 538 # Figure out the telnet executable name 539 set telnetcmd [join [lindex [find telnetcmd $router] 0] ""] 540 if { "$telnetcmd" == "" } { set telnetcmd "@TELNET_CMD@" } 541 542 # if [-mM], skip do not login 543 if { $do_cloginrcdbg > 0 } { continue; } 544 545 # Login to the router 546 if {[login $router $ruser $userpswd $passwd $prompt $cmethod $cyphertype]} { 547 incr exitval 548 continue 549 } 550 551 if { $do_command } { 552 if {[run_commands $prompt $command]} { 553 incr exitval 554 continue 555 } 556 } elseif { $do_script } { 557 send "screen 0\r" 558 expect $prompt {} 559 source $sfile 560 catch {close} 561 } else { 562 label $router 563 log_user 1 564 interact 565 } 566 567 # End of for each router 568 catch {wait}; 569 sleep 0.3 570} 571exit $exitval 572