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