1#! @EXPECT_PATH@ --
2##
3## @PACKAGE@ @VERSION@
4@copyright@
5#
6# rblogin - Riverbed Steelhead login; this is starting from clogin r3115, with
7#	    a few changes for UI bugs.
8#
9# Most options are intuitive for logging into a Cisco router.
10# The default is to enable (thus -noenable).  Some folks have
11# setup tacacs to have a user login at priv-lvl = 15 (enabled)
12# so the -autoenable flag was added for this case (don't go through
13# the process of enabling and the prompt will be the "#" prompt.
14# The default username password is the same as the vty password.
15#
16
17# Sometimes routers take awhile to answer (the default is 10 sec)
18set timeoutdflt 45
19# Some CLIs having problems if we write too fast (Extreme, PIX, Cat)
20set send_human {.2 .1 .4 .2 1}
21
22@login_top@
23
24# Log into the router.
25# returns: 0 on success, 1 on failure, -1 if rsh was used successfully
26proc login { router user userpswd passwd enapasswd cmethod cyphertype identfile } {
27    global command spawn_id in_proc do_command do_script platform passphrase
28    global prompt prompt_match u_prompt p_prompt e_prompt sshcmd telnetcmd
29    set in_proc 1
30    set uprompt_seen 0
31
32    # try each of the connection methods in $cmethod until one is successful
33    set progs [llength $cmethod]
34    foreach prog [lrange $cmethod 0 end] {
35	incr progs -1
36	if [string match "telnet*" $prog] {
37	    regexp {telnet(:([^[:space:]]+))*} $prog methcmd suffix port
38	    if {"$port" == ""} {
39		set retval [catch {eval spawn [split "$telnetcmd $router"]} reason]
40	    } else {
41		set retval [catch {eval spawn [split "$telnetcmd $router $port"]} reason]
42	    }
43	    if { $retval } {
44		send_user "\nError: telnet failed: $reason\n"
45		return 1
46	    }
47	} elseif [string match "ssh*" $prog] {
48	    # ssh to the router & try to login with or without an identfile.
49	    regexp {ssh(:([^[:space:]]+))*} $prog methcmd suffix port
50	    set cmd $sshcmd
51	    if {"$port" != ""} {
52		set cmd "$cmd -p $port"
53	    }
54	    if {"$cyphertype" != ""} {
55		set cmd "$cmd -c $cyphertype"
56	    }
57	    if {"$identfile" != ""} {
58		set cmd "$cmd -i $identfile"
59	    }
60	    set retval [catch {eval spawn [split "$cmd -x -l $user $router" { }]} reason]
61	    if { $retval } {
62		send_user "\nError: $cmd failed: $reason\n"
63		return 1
64	    }
65	} elseif ![string compare $prog "rsh"] {
66	    if { ! $do_command } {
67		if { [llength $cmethod] == 1 } {
68		    send_user "\nError: rsh is an invalid method for -x and "
69		    send_user "interactive logins\n"
70		}
71		if { $progs == 0 } {
72		    return 1
73		}
74		continue;
75	    }
76
77	    # handle escaped ;s in commands, and ;; and ^;
78	    regsub -all {([^\\]);} $command "\\1\u0002;" esccommand
79	    regsub -all {([^\\]);;} $esccommand "\\1;\u0002;" command
80	    regsub {^;} $command "\u0002;" esccommand
81	    regsub -all {[\\];} $esccommand ";" command
82	    regsub -all {\u0002;} $command "\u0002" esccommand
83	    set sep "\u0002"
84	    set commands [split $esccommand $sep]
85	    set num_commands [llength $commands]
86	    set rshfail 0
87	    for {set i 0} {$i < $num_commands && !$rshfail} { incr i} {
88		log_user 0
89		set retval [catch {spawn rsh $user@$router [lindex $commands $i] } reason]
90		if { $retval } {
91		    send_user "\nError: rsh failed: $reason\n"
92		    log_user 1; return 1
93		}
94		send_user "$router# [lindex $commands $i]\n"
95
96		# rcmd does not get a pager and no prompts, so we just have to
97		# look for failures & lines.
98		expect {
99		  "Connection refused"	{ catch {close}; catch {wait};
100					  send_user "\nError: Connection\
101						    Refused ($prog): $router\n"
102					  set rshfail 1
103					}
104		  -re "(Connection closed by|Connection to \[^\n\r]+ closed)" {
105					  catch {close}; catch {wait};
106					  send_user "\nError: Connection\
107						    closed ($prog): $router\n"
108					  set rshfail 1
109					}
110		  "Host is unreachable"	{ catch {close}; catch {wait};
111					  send_user "\nError: Host Unreachable:\
112						    $router\n"
113					  set rshfail 1
114					}
115		  "No address associated with" {
116					  catch {close}; catch {wait};
117					  send_user "\nError: Unknown host\
118						    $router\n"
119					  set rshfail 1
120					}
121		  -re "\b+"		{ exp_continue }
122		  -re "\[\n\r]+"	{ send_user -- "$expect_out(buffer)"
123					  exp_continue
124					}
125		  timeout		{ catch {close}; catch {wait};
126					  send_user "\nError: TIMEOUT reached\n"
127					  set rshfail 1
128					}
129		  eof			{ catch {close}; catch {wait}; }
130		}
131		log_user 1
132	    }
133	    if { $rshfail } {
134		if { !$progs } {
135		    return 1
136		} else {
137		    continue
138		}
139	    }
140	    # fake the end of the session for rancid.
141	    send_user "$router# exit\n"
142	    # return rsh "success"
143	    return -1
144	} else {
145	    send_user "\nError: unknown connection method: $prog\n"
146	    return 1
147	}
148	sleep 0.3
149
150	# This helps cleanup each expect clause.
151	expect_after {
152	    timeout {
153		global in_proc
154		send_user "\nError: TIMEOUT reached\n"
155		catch {close}; catch {wait};
156		if {$in_proc} {
157		    return 1
158		} else {
159		    continue
160		}
161	    } eof {
162		global in_proc
163		send_user "\nError: EOF received\n"
164		catch {close}; catch {wait};
165		if {$in_proc} {
166		    return 1
167		} else {
168		    continue
169		}
170	    }
171	}
172
173    # Here we get a little tricky.  There are several possibilities:
174    # the router can ask for a username and passwd and then
175    # talk to the TACACS server to authenticate you, or if the
176    # TACACS server is not working, then it will use the enable
177    # passwd.  Or, the router might not have TACACS turned on,
178    # then it will just send the passwd.
179    # if telnet fails with connection refused, try ssh
180    expect {
181	-re "^<-+ More -+>\[^\n\r]*" {
182	    # ASA will use the pager for long banners
183	    send " ";
184	    exp_continue
185	}
186	-re "(Connection refused|Secure connection \[^\n\r]+ refused)" {
187	    catch {close}; catch {wait};
188	    if !$progs {
189		send_user "\nError: Connection Refused ($prog): $router\n"
190		return 1
191	    }
192	}
193	-re "(Connection closed by|Connection to \[^\n\r]+ closed)" {
194	    catch {close}; catch {wait};
195	    if !$progs {
196		send_user "\nError: Connection closed ($prog): $router\n"
197		return 1
198	    }
199	}
200	eof { send_user "\nError: Couldn't login: $router\n"; wait; return 1 }
201	-nocase "unknown host\r" {
202	    send_user "\nError: Unknown host $router\n";
203	    catch {close}; catch {wait};
204	    return 1
205	}
206	"Host is unreachable" {
207	    send_user "\nError: Host Unreachable: $router\n";
208	    catch {close}; catch {wait};
209	    return 1
210	}
211	"No address associated with name" {
212	    send_user "\nError: Unknown host $router\n";
213	    catch {close}; catch {wait};
214	    return 1
215	}
216	-re "(Host key not found |The authenticity of host .* be established)" {
217	    expect {
218		-re "\\(yes\/no\[^\\)]*\\)\\?" {
219					  send "yes\r";
220					  send_user "\nHost $router added to the list of known hosts.\n"
221					 }
222		-re "\[^\r\n]*\[\r\n]+"	{ exp_continue; }
223	    }
224	    exp_continue
225	}
226	-re "HOST IDENTIFICATION HAS CHANGED" {
227	    send_user "\nError: The host key for $router has changed.  Update the SSH known_hosts file accordingly.\n"
228	    expect {
229		-re "\\(yes\/no\\)\\?"	{ send "no\r" }
230		-re " strict checking\.\[\r\n]+" { }
231		-re "\[^\r\n]*\[\r\n]+"	{ exp_continue; }
232	    }
233	    catch {close}; catch {wait};
234	    return 1
235	}
236	-re "Offending key for " {
237	    send_user "\nError: host key mismatch for $router.  Update the SSH known_hosts file accordingly.\n"
238	    expect {
239		-re "\\(yes\/no\\)\\?"	{ send "no\r" }
240		-re "\[^\r\n]*\[\r\n]+"	{ exp_continue; }
241	    }
242	    catch {close}; catch {wait};
243	    return 1
244	}
245	-nocase -re "^warning: remote host denied authentication agent forwarding." {
246	    exp_continue;
247	}
248	-re "(denied|Sorry)"	{
249				  send_user "\nError: Check your passwd for $router\n"
250				  catch {close}; catch {wait}; return 1
251				}
252	"Login failed"		{
253				  send_user "\nError: Check your passwd for $router\n"
254				  catch {close}; catch {wait}; return 1
255				}
256	-re "% (Bad passwords|Authentication failed)"	{
257				  send_user "\nError: Check your passwd for $router\n"
258				  catch {close}; catch {wait}; return 1
259				}
260	"Press any key to continue" {
261				  # send_user "Pressing the ANY key\n"
262				  send "\r"
263				  exp_continue
264				}
265	-re "Enter Selection: " {
266				  # Catalyst 1900s have some lame menu.  Enter
267				  # K to reach a command-line.
268				  send "K\r"
269				  exp_continue
270				}
271	-re "Last login:"	{
272				  exp_continue
273				}
274	-re "Press the <tab> key \[^\r\n]+\[\r\n]+"	{
275				  exp_continue
276				}
277	-re "@\[^\r\n]+ $p_prompt"	{
278				  # ssh pwd prompt
279				  sleep 1
280				  send -- "$userpswd\r"
281				  exp_continue
282				}
283	-re "Enter passphrase.*: " {
284				  # sleep briefly to allow time for stty -echo
285				  sleep .3
286				  send -- "$passphrase\r"
287				  exp_continue
288				}
289	-re "$u_prompt"		{
290				  send -- "$user\r"
291				  set uprompt_seen 1
292				  exp_continue
293				}
294	-re "$p_prompt"		{
295				  sleep 1
296				  if {$uprompt_seen == 1} {
297					send -- "$userpswd\r"
298				  } else {
299					send -- "$passwd\r"
300				  }
301				  exp_continue
302				}
303	-re "$prompt"		{
304				  set prompt_match $expect_out(0,string);
305				  break;
306				}
307	"Login invalid"		{
308				  send_user "\nError: Invalid login: $router\n";
309				  catch {close}; catch {wait}; return 1
310				}
311	-re "\[^\r\n]*\[\r\n]+"	{ exp_continue; }
312     }
313    }
314
315    set in_proc 0
316    return 0
317}
318
319# Enable
320proc do_enable { enauser enapasswd } {
321    global do_saveconfig in_proc
322    global prompt u_prompt e_prompt enacmd
323    set in_proc 1
324
325    send "$enacmd\r"
326    expect {
327	-re "$u_prompt"	{ send -- "$enauser\r"; exp_continue}
328	-re "$e_prompt"	{ send -- "$enapasswd\r"; exp_continue}
329	"#"		{ set prompt "#" }
330	"(enable)"	{ set prompt "> \\(enable\\) " }
331	"% Invalid input" {
332			  send_user "\nError: Unrecognized command, check your enable command\n";
333			  return 1
334			}
335	-re "(denied|Sorry|Incorrect)"	{
336			  # % Access denied - from local auth and poss. others
337			  send_user "\nError: Check your Enable passwd\n";
338			  return 1
339			}
340	"% Error in authentication" {
341			  send_user "\nError: Check your Enable passwd\n"
342			  return 1
343			}
344	"% Bad passwords" {
345			  send_user "\nError: Check your Enable passwd\n"
346			  return 1
347			}
348    }
349    # We set the prompt variable (above) so script files don't need
350    # to know what it is.
351    set in_proc 0
352    return 0
353}
354
355# Run commands given on the command line.
356proc run_commands { prompt command } {
357    global do_interact do_saveconfig in_proc platform
358    set in_proc 1
359
360    if { [string compare "extreme" "$platform"] } {
361	# match cisco config mode prompts too, such as router(config-if)#,
362	# but catalyst does not change in this fashion.
363	regsub -lineanchor -- {^(.{1,11}).*([#>])$} $prompt {\1} reprompt
364	regsub -all -- {[\\]$} $reprompt {} reprompt
365	append reprompt {([^#>\r\n]+)?[#>](\\([^)\\r\\n]+\\))?}
366    } else {
367	set reprompt $prompt
368    }
369
370    # this is the only way i see to get rid of more prompts in o/p..grrrrr
371    log_user 0
372
373    # handle escaped ;s in commands, and ;; and ^;
374    regsub -all {([^\\]);} $command "\\1\u0002;" esccommand
375    regsub -all {([^\\]);;} $esccommand "\\1;\u0002;" command
376    regsub {^;} $command "\u0002;" esccommand
377    regsub -all {[\\];} $esccommand ";" command
378    regsub -all {\u0002;} $command "\u0002" esccommand
379    set sep "\u0002"
380    set commands [split $esccommand $sep]
381    set num_commands [llength $commands]
382    # the pager can not be turned off on the PIX, so we have to look
383    # for the "More" prompt.  the extreme is equally obnoxious in pre-12.3 XOS,
384    # with a global switch in the config.
385    for {set i 0} {$i < $num_commands} { incr i} {
386	send -- "[subst -nocommands [lindex $commands $i]]\r"
387	expect {
388	    -re "\b+"				{ exp_continue }
389	    -re "^<<Service needs \[^>]+>>" {
390				  # for whatever reason, the riverbed prints
391				  # this warning in show peers o/p, but doesnt
392				  # follow it with a CR, so the following
393				  # prompt is not at the BOL, confusing the
394				  # parsing script.  so, filter it.  XXX
395				  exp_continue;
396				}
397	    -re "^\[^\n\r *]*$reprompt"		{ send_user -- "$expect_out(buffer)"
398						}
399	    -re "^\[^\n\r]*$reprompt."		{ send_user -- "$expect_out(buffer)"
400						  exp_continue
401						}
402	    -re "^--More--\[\r\n]+"		{ # specific match c1900 pager
403						  send " "
404						  exp_continue
405						}
406	    -re "\[^\r\n]*\[\n\r]+"		{ send_user -- "$expect_out(buffer)"
407						  exp_continue
408						}
409	    -re "\[^\r\n]*Press <SPACE> to cont\[^\r\n]*"	{
410						  send " "
411						  # bloody ^[[2K after " "
412						  expect {
413							-re "^\[^\r\n]*\r" {}
414							}
415						  exp_continue
416						}
417	    -re "^ *--More--\[^\n\r]*"		{
418						  send " "
419						  exp_continue }
420	    -re "^<-+ More -+>\[^\n\r]*"	{
421						  send_user -- "$expect_out(buffer)"
422						  send " "
423						  exp_continue }
424	}
425    }
426    log_user 1
427
428    if { $do_interact == 1 } {
429	interact
430	return 0
431    }
432
433    if { [string compare "extreme" "$platform"] } {
434	send -h "exit\r"
435    } else {
436	send -h "quit\r"
437    }
438    expect {
439	-re "^\[^\n\r *]*$reprompt"		{
440						  # the Cisco CE and Jnx ERX
441						  # return to non-enabled mode
442						  # on exit in enabled mode.
443						  send -h "exit\r"
444						  exp_continue;
445						}
446	"The system has unsaved changes"	{ # Force10 SFTOS
447						  if {$do_saveconfig} {
448						    catch {send "y\r"}
449						  } else {
450						    catch {send "n\r"}
451						  }
452						  exp_continue
453						}
454	"Would you like to save them now"	{ # Force10
455						  if {$do_saveconfig} {
456						    catch {send "y\r"}
457						  } else {
458						    catch {send "n\r"}
459						  }
460						  exp_continue
461						}
462	-re "(Profile|Configuration) changes have occurred.*"	{
463						  # Cisco CSS
464						  if {$do_saveconfig} {
465						    catch {send "y\r"}
466						  } else {
467						    catch {send "n\r"}
468						  }
469						  exp_continue
470						}
471	"Do you wish to save your configuration changes" {
472						  if {$do_saveconfig} {
473						    catch {send "y\r"}
474						  } else {
475						    catch {send "n\r"}
476						  }
477						  exp_continue
478						}
479	-re "\[\n\r]+"				{ exp_continue }
480	timeout					{ catch {close}; catch {wait};
481						  return 1
482						}
483	eof					{ return 0 }
484    }
485    set in_proc 0
486}
487
488#
489# For each router... (this is main loop)
490#
491source_password_file $password_file
492set in_proc 0
493set exitval 0
494set prompt_match ""
495foreach router [lrange $argv $i end] {
496    set router [string tolower $router]
497    # attempt at platform switching.
498    set platform ""
499    send_user -- "$router\n"
500
501    # device timeout
502    set timeout [find timeout $router]
503    if { [llength $timeout] == 0 } {
504	set timeout $timeoutdflt
505    }
506
507    # Default prompt.
508    set prompt [join [find prompt $router] ""]
509    if { [llength $prompt] == 0 } {
510	set prompt "(>|#| \\(enable\\))"
511    }
512
513    # look for autoenable option in .cloginrc & cmd-line
514    set ae [find autoenable $router]
515    if { "$ae" == "1" || $avautoenable } {
516	set autoenable 1
517    } else {
518	set autoenable 0
519    }
520    # look for enable options in .cloginrc & cmd-line
521    if { $avenable == 0 } {
522	set enable 0
523    } else {
524	set ne [find noenable $router]
525	if { "$ne" == "1" || "$autoenable" == "1" } {
526	    set enable 0
527	} else {
528	    set enable 1
529	}
530    }
531
532    # Figure out passwords
533    if { $do_passwd || $do_enapasswd } {
534	set pswd [find password $router]
535	if { [llength $pswd] == 0 } {
536	    send_user -- "\nError: no password for $router in $password_file.\n"
537	    continue
538	}
539	if { $enable && $do_enapasswd && $autoenable == 0 && [llength $pswd] < 2 } {
540	    send_user -- "\nError: no enable password for $router in $password_file.\n"
541	    continue
542	}
543	if { $do_passwd } {
544	    set passwd [join [lindex $pswd 0] ""]
545	} else {
546	    set passwd $userpasswd
547	}
548	if { $do_enapasswd } {
549	    set enapasswd [join [lindex $pswd 1] ""]
550	} else {
551	    set enapasswd $enapasswd
552	}
553    } else {
554	set passwd $userpasswd
555	set enapasswd $enapasswd
556    }
557
558    # Figure out username
559    if {[info exists username]} {
560      # command line username
561      set ruser $username
562    } else {
563      set ruser [join [find user $router] ""]
564      if { "$ruser" == "" } { set ruser $default_user }
565    }
566
567    # Figure out username's password (if different from the vty password)
568    if {[info exists userpasswd]} {
569      # command line username
570      set userpswd $userpasswd
571    } else {
572      set userpswd [join [find userpassword $router] ""]
573      if { "$userpswd" == "" } { set userpswd $passwd }
574    }
575
576    # Figure out enable username
577    if {[info exists enausername]} {
578      # command line enausername
579      set enauser $enausername
580    } else {
581      set enauser [join [find enauser $router] ""]
582      if { "$enauser" == "" } { set enauser $ruser }
583    }
584
585    # Figure out enable command
586    set enacmd [join [find enablecmd $router] ""]
587    if { "$enacmd" == "" } { set enacmd "enable" }
588
589    # Figure out prompts
590    set u_prompt [find userprompt $router]
591    if { "$u_prompt" == "" } {
592	set u_prompt "(\[Uu]sername|Login|login|user name|User):"
593    } else {
594	set u_prompt [join [lindex $u_prompt 0] ""]
595    }
596    set p_prompt [find passprompt $router]
597    if { "$p_prompt" == "" } {
598	set p_prompt "(\[Pp]assword|passwd|Enter password for \[^ :]+):"
599    } else {
600	set p_prompt [join [lindex $p_prompt 0] ""]
601    }
602    set e_prompt [find enableprompt $router]
603    if { "$e_prompt" == "" } {
604	set e_prompt "\[Pp]assword:"
605    } else {
606	set e_prompt [join [lindex $e_prompt 0] ""]
607    }
608
609    # Figure out identity file to use
610    set identfile [join [lindex [find identity $router] 0] ""]
611
612    # Figure out passphrase to use
613    if {[info exists avpassphrase]} {
614	set passphrase $avpassphrase
615    } else {
616	set passphrase [join [lindex [find passphrase $router] 0] ""]
617    }
618    if { ! [string length "$passphrase"]} {
619	set passphrase $passwd
620    }
621
622    # Figure out cypher type
623    if {[info exists cypher]} {
624        # command line cypher type
625        set cyphertype $cypher
626    } else {
627        set cyphertype [find cyphertype $router]
628    }
629
630    # Figure out connection method
631    set cmethod [find method $router]
632    if { "$cmethod" == "" } { set cmethod {{telnet} {ssh}} }
633
634    # Figure out the SSH executable name
635    set sshcmd [join [lindex [find sshcmd $router] 0] ""]
636    if { "$sshcmd" == "" } { set sshcmd {ssh} }
637
638    # Figure out the telnet executable name
639    set telnetcmd [join [lindex [find telnetcmd $router] 0] ""]
640    if { "$telnetcmd" == "" } { set telnetcmd "@TELNET_CMD@" }
641
642    # if [-mM], skip do not login
643    if { $do_cloginrcdbg > 0 } { continue; }
644
645    # Login to the router
646    if {[login $router $ruser $userpswd $passwd $enapasswd $cmethod $cyphertype $identfile]} {
647	incr exitval
648	# if login failed or rsh was unsuccessful, move on to the next device
649	continue
650    }
651    # Figure out the prompt.
652    if { [regexp -- "(#| \\(enable\\))" $prompt_match junk] == 1 } {
653	set enable 0
654    }
655    if { $enable } {
656	if {[do_enable $enauser $enapasswd]} {
657	    if { $do_command || $do_script } {
658		incr exitval
659		catch {close}; catch {wait};
660		continue
661	    }
662	}
663    }
664    # we are logged in, now figure out the full prompt
665    send "\r"
666    regsub -all {^(\^*)(.*)} $prompt {\2} reprompt
667    expect {
668	-re "\[\r\n]+"		{ exp_continue; }
669	-re "^(.+\[:.])1 ($reprompt)" { # stoopid extreme cmd-line numbers and
670				  # prompt based on state of config changes,
671				  # which may have an * at the beginning.
672				  set junk $expect_out(1,string)
673				  regsub -all "^\\\* " $expect_out(1,string) {} junk
674				  regsub -all "\[\]\[\(\)]" $junk {\\&} junk;
675				  set prompt ".? ?$junk\[0-9]+ $expect_out(2,string)";
676				  set platform "extreme"
677				}
678	-re "^.+$reprompt"	{ set junk $expect_out(0,string);
679				  regsub -all "\[\]\[\(\)+]" $junk {\\&} prompt;
680				}
681    }
682    if { $do_command || $do_script } {
683	if { [string compare "extreme" "$platform"] } {
684	    # If the prompt is (enable), then we are on a switch and the
685	    # command is "set length 0"; otherwise its "terminal length 0".
686	    if [regexp -- ".*> .*enable" "$prompt"] {
687		send "set length 0\r"
688		expect -re $prompt  	{}
689		# XXX This causes the riverbed to reprint the prompt after the
690		#     existing prompt, which confuses the expect script.
691		# send "set width 132\r"
692		# expect -re $prompt	{}
693		send "set logging session disable\r"
694	    } else {
695		send "terminal length 0\r"
696		# XXX This causes the riverbed to reprint the prompt after the
697		#     existing prompt, which confuses the expect script.
698		# expect -re $prompt  	{}
699		# send "terminal width 132\r"
700	    }
701	    expect -re $prompt		{}
702	} else {
703	    send "disable clipaging\r"
704	    expect -re $prompt		{}
705	}
706    }
707    if { $do_command } {
708	if {[run_commands $prompt $command]} {
709	    incr exitval
710	    continue
711	}
712    } elseif { $do_script } {
713	source $sfile
714	catch {close};
715    } else {
716	label $router
717	log_user 1
718	interact
719    }
720
721    # End of for each router
722    catch {wait};
723    sleep 0.3
724}
725exit $exitval
726