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