1#! @EXPECT_PATH@ --
2##
3## @PACKAGE@ @VERSION@
4@copyright@
5#
6# flogin - foundry login
7#
8# Most options are intuitive for logging into a foundry switch.
9# this should be the clogin, but foundry can't seem to dislodge
10# their heads and make the UI consistent.  i think the UI
11# development has been outsourced to Fisher Price.
12# The default is to enable (thus -noenable).  Some folks have
13# setup tacacs to have a user login at priv-lvl = 15 (enabled)
14# so the -autoenable flag was added for this case (don't go through
15# the process of enabling and the prompt will be the "#" prompt.
16# The default username password is the same as the vty password.
17#
18
19# Sometimes routers take awhile to answer (the default is 10 sec)
20set timeoutdflt 45
21# Some CLIs having problems if we write too fast (Extreme, PIX, Cat)
22set send_human {.2 .1 .4 .2 1}
23
24@login_top@
25
26# Log into the router.
27# returns: 0 on success, 1 on failure
28proc login { router user userpswd passwd enapasswd cmethod cyphertype identfile } {
29    global spawn_id in_proc do_command do_script passphrase
30    global prompt u_prompt p_prompt e_prompt sshcmd telnetcmd
31    set in_proc 1
32    set uprompt_seen 0
33
34    # try each of the connection methods in $cmethod until one is successful
35    set progs [llength $cmethod]
36    foreach prog [lrange $cmethod 0 end] {
37	incr progs -1
38	if [string match "telnet*" $prog] {
39	    regexp {telnet(:([^[:space:]]+))*} $prog command suffix port
40	    if {"$port" == ""} {
41		set retval [ catch {eval spawn [split "$telnetcmd $router"]} reason ]
42	    } else {
43		set retval [ catch {eval spawn [split "$telnetcmd $router $port"]} reason ]
44	    }
45	    if { $retval } {
46		send_user "\nError: telnet failed: $reason\n"
47		return 1
48	    }
49	} elseif [string match "ssh*" $prog] {
50	    # ssh to the router & try to login with or without an identfile.
51	    regexp {ssh(:([^[:space:]]+))*} $prog methcmd suffix port
52	    set cmd $sshcmd
53	    if {"$port" != ""} {
54		set cmd "$cmd -p $port"
55	    }
56	    if {"$cyphertype" != ""} {
57		set cmd "$cmd -c $cyphertype"
58	    }
59	    if {"$identfile" != ""} {
60		set cmd "$cmd -i $identfile"
61	    }
62	    set retval [ catch {eval spawn [split "$cmd -x -l $user $router" { }]} reason ]
63	    if { $retval } {
64		send_user "\nError: $cmd failed: $reason\n"
65		return 1
66	    }
67	} elseif ![string compare $prog "rsh"] {
68	    send_error "\nError: unsupported method: rsh\n"
69	    if { $progs == 0 } {
70		return 1
71	    }
72	    continue;
73	} else {
74	    send_user "\nError: unknown connection method: $prog\n"
75	    return 1
76	}
77	sleep 0.3
78
79	# This helps cleanup each expect clause.
80	expect_after {
81	    timeout {
82		global in_proc
83		send_user "\nError: TIMEOUT reached\n"
84		catch {close}; catch {wait};
85		if {$in_proc} {
86		    return 1
87		} else {
88		    continue
89		}
90	    } eof {
91		global in_proc
92		send_user "\nError: EOF received\n"
93		catch {close}; catch {wait};
94		if {$in_proc} {
95		    return 1
96		} else {
97		    continue
98		}
99	    }
100	}
101
102    # Here we get a little tricky.  There are several possibilities:
103    # the router can ask for a username and passwd and then
104    # talk to the TACACS server to authenticate you, or if the
105    # TACACS server is not working, then it will use the enable
106    # passwd.  Or, the router might not have TACACS turned on,
107    # then it will just send the passwd.
108    # if telnet fails with connection refused, try ssh
109    expect {
110	-re "(Connection refused|Secure connection \[^\n\r]+ refused)" {
111	    catch {close}; catch {wait};
112	    if !$progs {
113		send_user "\nError: Connection Refused ($prog): $router\n"
114		return 1
115	    }
116	}
117	-re "(Connection closed by|Connection to \[^\n\r]+ closed)" {
118	    catch {close}; catch {wait};
119	    if !$progs {
120		send_user "\nError: Connection closed ($prog): $router\n"
121		return 1
122	    }
123	}
124	-re "Telnet server disabled" {
125	    catch {close}; catch {wait};
126	    if !$progs {
127		send_user "\nError: Connection Refused ($prog): $router\n"
128		return 1
129	    }
130	}
131	eof { send_user "\nError: Couldn't login\n"; wait; return 1 }
132	-nocase "unknown host\r" {
133	    catch {close}; catch {wait};
134	    send_user "\nError: Unknown host $router\n"; wait; return 1
135	}
136	"Host is unreachable" {
137	    catch {close}; catch {wait};
138	    send_user "\nError: Host Unreachable: $router\n"; wait; return 1
139	}
140	"No address associated with name" {
141	    catch {close}; catch {wait};
142	    send_user "\nError: Unknown host $router\n"; wait; return 1
143	}
144	-re "(Host key not found |The authenticity of host .* be established)" {
145	    expect {
146		-re "\\(yes\/no\[^\\)]*\\)\\?" {
147					  send "yes\r";
148					  send_user "\nHost $router added to the list of known hosts.\n"
149					 }
150		-re "\[^\r\n]*\[\r\n]+"	{ exp_continue; }
151	    }
152	    exp_continue
153	}
154	-re "HOST IDENTIFICATION HAS CHANGED" {
155	    send_user "\nError: The host key for $router has changed.  Update the SSH known_hosts file accordingly.\n"
156	    expect {
157		-re "\\(yes\/no\\)\\?"	{ send "no\r" }
158		-re " strict checking\.\[\r\n]+" { }
159		-re "\[^\r\n]*\[\r\n]+"	{ exp_continue; }
160	    }
161	    catch {close}; catch {wait};
162	    return 1
163	}
164	-re "Offending key for " {
165	    send_user "\nError: host key mismatch for $router.  Update the SSH known_hosts file accordingly.\n"
166	    expect {
167		-re "\\(yes\/no\\)\\?"	{ send "no\r" }
168		-re "\[^\r\n]*\[\r\n]+"	{ exp_continue; }
169	    }
170	    catch {close}; catch {wait};
171	    return 1
172	}
173	-nocase -re "^warning: remote host denied authentication agent forwarding." {
174	    exp_continue;
175	}
176	-re "(denied|Sorry)"	{
177				  send_user "\nError: Check your passwd for $router\n"
178				  catch {close}; catch {wait}; return 1
179				}
180	"Login failed"		{
181				  send_user "\nError: Check your passwd for $router\n"
182				  return 1
183				}
184	-re "% (Bad passwords|Authentication failed)"	{
185				  send_user "\nError: Check your passwd for $router\n"
186				  return 1
187				}
188	-re "@\[^\r\n]+ $p_prompt"	{
189					  # ssh pwd prompt
190					  send -h -- "$userpswd\r"
191					  exp_continue
192				}
193	-re "Enter passphrase.*: " {
194				  # sleep briefly to allow time for stty -echo
195				  sleep .3
196				  send -- "$passphrase\r"
197				  exp_continue
198				}
199	-re "$u_prompt"		{
200				  send -h -- "$user\r"
201				  set uprompt_seen 1
202				  exp_continue
203				}
204	-re "$p_prompt"		{
205				  if {$uprompt_seen == 1} {
206					send -h -- "$userpswd\r"
207				  } else {
208					send -h -- "$passwd\r"
209				  }
210				  exp_continue
211				}
212	"$prompt"		{ break; }
213	"Login invalid"		{
214				  send_user "\nError: Invalid login: $router\n";
215				  catch {close}; catch {wait}; return 1
216				}
217     }
218    }
219
220    set in_proc 0
221    return 0
222}
223
224# Enable
225proc do_enable { enauser enapasswd } {
226    global prompt in_proc
227    global u_prompt e_prompt
228    set in_proc 1
229
230    send -h "enable\r"
231    expect {
232	-re "$u_prompt"	{ send -h -- "$enauser\r"; exp_continue}
233	-re "$e_prompt"	{ send -h -- "$enapasswd\r"; exp_continue}
234	"#"		{ set prompt "#" }
235	"(enable)"	{ set prompt "> (enable) " }
236	denied		{ send_user "\nError: Check your Enable passwd\n";
237			  return 1
238			}
239	"Incorrect username or password" {
240			  send_user "\nError: Check your Enable passwd\n";
241			  return 1
242			}
243	"% Bad passwords" { send_user "\nError: Check your Enable passwd\n";
244			  return 1
245			}
246    }
247    # We set the prompt variable (above) so script files don't need
248    # to know what it is.
249    set in_proc 0
250    return 0
251}
252
253# Run commands given on the command line.
254proc run_commands { prompt command } {
255    global do_interact do_saveconfig in_proc
256    set in_proc 1
257
258    send -h "skip-page-display\r"
259    expect -re "$prompt" {}
260    # If logging in as an unpriv user (i.e., level 5 read-only), then
261    # "skip-page-display" isn't available, unless you also configure:
262    # privilege exec level 5 skip-page-display
263    #
264    # On some platforms, like the MLX, you can work around that
265    # with this:
266    send "terminal length 0\r"
267    expect -re "$prompt" {}
268
269    # match foundry config mode prompts too, such as ssh@router(config-if)#
270    # or ssh@truncatedr(config-if)#.  NOTE that, at least, as of v6.0.0
271    # (2018?), Foundry NetIron does not truncate, but this shouldnt hurt.
272    regsub -lineanchor -- {^(.{1,11}).*([#>])$} $prompt {\1} reprompt
273    regsub -all -- {[\\]$} $reprompt {} reprompt
274    append reprompt {([^#>\r\n]+)?[#>]([\\r\\n]+)?}
275
276    # handle escaped ;s in commands, and ;; and ^;
277    regsub -all {([^\\]);} $command "\\1\u0002;" esccommand
278    regsub -all {([^\\]);;} $esccommand "\\1;\u0002;" command
279    regsub {^;} $command "\u0002;" esccommand
280    regsub -all {[\\];} $esccommand ";" command
281    regsub -all {\u0002;} $command "\u0002" esccommand
282    set sep "\u0002"
283    set commands [split $esccommand $sep]
284    set num_commands [llength $commands]
285    for {set i 0} {$i < $num_commands} { incr i} {
286	send -h -- "[subst -nocommands [lindex $commands $i]]\r"
287	expect {
288	    -re " *\b+"			{ exp_continue }
289	    -re "^\[^\n\r]*$reprompt."	{ exp_continue }
290	    -re "^\[^\n\r *]*$reprompt"	{}
291	    -re "\[\n\r]"		{ exp_continue }
292	    -- "---More---"		{ # EdgeIron cant disable pager
293					  send " "
294					  exp_continue
295					}
296	}
297    }
298
299    if { $do_interact == 1 } {
300	interact
301	return 0
302    }
303
304    send -h "exit\r"
305    expect {
306	"\n"					{ exp_continue }
307	-re "^\[^ ]+>"				{ send -h "exit\r"
308						  exp_continue
309						}
310	timeout					{ catch {close}; catch {wait};
311						  return 0
312						}
313	eof					{ return 0 }
314	-re "Configuration modified, save\? \[\r\n]*" {
315						  if {$do_saveconfig} {
316						    catch {send "y\r"}
317						  } else {
318						    catch {send "n\r"}
319						  }
320						  exp_continue
321						}
322    }
323    set in_proc 0
324}
325
326#
327# For each router... (this is main loop)
328#
329source_password_file $password_file
330set in_proc 0
331set exitval 0
332foreach router [lrange $argv $i end] {
333    set router [string tolower $router]
334    send_user "$router\n"
335
336    # device timeout
337    set timeout [find timeout $router]
338    if { [llength $timeout] == 0 } {
339	set timeout $timeoutdflt
340    }
341
342    # Figure out prompt.
343    # Since autoenable is off by default, if we have it defined, it
344    # was done on the command line. If it is not specifically set on the
345    # command line, check the password file.
346    if $avautoenable {
347	set autoenable 1
348	set enable 0
349	set prompt "#"
350    } else {
351	set ae [find autoenable $router]
352	if { "$ae" == "1" } {
353	    set autoenable 1
354	    set enable 0
355	    set prompt "#"
356	} else {
357	    set autoenable 0
358	    set enable $avenable
359	    set prompt ">"
360	}
361    }
362
363    # Figure out passwords
364    if { $do_passwd || $do_enapasswd } {
365	set pswd [find password $router]
366	if { [llength $pswd] == 0 } {
367	    send_user -- "\nError: no password for $router in $password_file.\n"
368	    continue
369	}
370	if { $enable && $do_enapasswd && $autoenable == 0 && [llength $pswd] < 2 } {
371	    send_user -- "\nError: no enable password for $router in $password_file.\n"
372            continue
373	}
374	if { $do_passwd } {
375	    set passwd [join [lindex $pswd 0] ""]
376	} else {
377	    set passwd $userpasswd
378	}
379	if { $do_enapasswd } {
380	    set enapasswd [join [lindex $pswd 1] ""]
381	} else {
382	    set enapasswd $enapasswd
383	}
384    } else {
385	set passwd $userpasswd
386	set enapasswd $enapasswd
387    }
388
389    # Figure out username
390    if {[info exists username]} {
391      # command line username
392      set ruser $username
393    } else {
394      set ruser [join [find user $router] ""]
395      if { "$ruser" == "" } { set ruser $default_user }
396    }
397
398    # Figure out username's password (if different from the vty password)
399    if {[info exists userpasswd]} {
400      # command line username
401      set userpswd $userpasswd
402    } else {
403      set userpswd [join [find userpassword $router] ""]
404      if { "$userpswd" == "" } { set userpswd $passwd }
405    }
406
407    # Figure out enable username
408    if {[info exists enausername]} {
409      # command line enausername
410      set enauser $enausername
411    } else {
412      set enauser [join [find enauser $router] ""]
413      if { "$enauser" == "" } { set enauser $ruser }
414    }
415
416    # Figure out prompts
417    set u_prompt [find userprompt $router]
418    if { "$u_prompt" == "" } {
419	set u_prompt "(Username|login|Name|User Name) *:"
420    } else {
421	set u_prompt [join [lindex $u_prompt 0] ""]
422    }
423    set p_prompt [find passprompt $router]
424    if { "$p_prompt" == "" } {
425	set p_prompt "(\[Pp]assword):"
426    } else {
427	set p_prompt [join [lindex $p_prompt 0] ""]
428    }
429    set e_prompt [find enableprompt $router]
430    if { "$e_prompt" == "" } {
431	set e_prompt "\[Pp]assword:"
432    } else {
433	set e_prompt [join [lindex $e_prompt 0] ""]
434    }
435
436    # Figure out identity file to use
437    set identfile [join [lindex [find identity $router] 0] ""]
438
439    # Figure out passphrase to use
440    if {[info exists avpassphrase]} {
441	set passphrase $avpassphrase
442    } else {
443	set passphrase [join [lindex [find passphrase $router] 0] ""]
444    }
445    if { ! [string length "$passphrase"]} {
446	set passphrase $passwd
447    }
448
449    # Figure out cypher tpye
450    if {[info exists cypher]} {
451      # command line cypher type
452      set cyphertype $cypher
453    } else {
454      set cyphertype [find cyphertype $router]
455    }
456
457    # Figure out connection method
458    set cmethod [find method $router]
459    if { "$cmethod" == "" } { set cmethod {{telnet} {ssh}} }
460
461    # Figure out the SSH executable name
462    set sshcmd [join [lindex [find sshcmd $router] 0] ""]
463    if { "$sshcmd" == "" } { set sshcmd {ssh} }
464
465    # Figure out the telnet executable name
466    set telnetcmd [join [lindex [find telnetcmd $router] 0] ""]
467    if { "$telnetcmd" == "" } { set telnetcmd "@TELNET_CMD@" }
468
469    # if [-mM], skip do not login
470    if { $do_cloginrcdbg > 0 } { continue; }
471
472    # Login to the router
473    if {[login $router $ruser $userpswd $passwd $enapasswd $cmethod $cyphertype $identfile ]} {
474	incr exitval
475	continue
476    }
477    if { $enable } {
478	if {[do_enable $enauser $enapasswd]} {
479	    if { $do_command || $do_script } {
480		incr exitval
481		catch {close}; catch {wait};
482		continue
483	    }
484	}
485    }
486    # we are logged in, now figure out the full prompt
487    send -h "\r"
488    expect {
489	-re "\[\r\n]+"		{ exp_continue; }
490	-re "^.+$prompt"	{ set junk $expect_out(0,string);
491				  regsub -all "\[\]\[ ]" $junk {\\&} prompt;
492				}
493    }
494
495
496    if { $do_command } {
497	if {[run_commands $prompt $command]} {
498	    incr exitval
499	    continue
500	}
501    } elseif { $do_script } {
502	# fucking foundry
503	send -h "skip-page-display\r"
504	expect -re $prompt	{}
505	# If logging in as an unpriv user (i.e., level 5 read-only), then
506	# "skip-page-display" isn't available, unless you also configure:
507	# privilege exec level 5 skip-page-display
508	#
509	# On some platforms, like the MLX, you can work around that
510	# with this:
511	send "terminal length 0\r"
512	expect -re "$prompt" {}
513	source $sfile
514	catch {close};
515    } else {
516	label $router
517	log_user 1
518	interact
519    }
520
521    # End of for each router
522    catch {wait};
523    sleep 0.3
524}
525exit $exitval
526