Permalink
Cannot retrieve contributors at this time
Fetching contributors…
| #! @EXPECT_PATH@ -- | |
| ## | |
| ## $Id: fnlogin.in,v 1.51 2009/04/16 21:22:58 heas Exp $ | |
| ## patched to accomplish fortinet from nlogin | |
| ## by: Daniel G. Epstein <dan at rootlike.com> | |
| ## adapted by: Diego Ercolani <diego.ercolani at ssis.sm> | |
| ## | |
| ## @PACKAGE@ @VERSION@ | |
| ## Copyright (c) @COPYYEARS@ by Terrapin Communications, Inc. | |
| ## All rights reserved. | |
| ## | |
| ## This code is derived from software contributed to and maintained by | |
| ## Terrapin Communications, Inc. by Henry Kilmer, John Heasley, Andrew Partan, | |
| ## Pete Whiting, Austin Schutz, and Andrew Fort. | |
| ## | |
| ## Redistribution and use in source and binary forms, with or without | |
| ## modification, are permitted provided that the following conditions | |
| ## are met: | |
| ## 1. Redistributions of source code must retain the above copyright | |
| ## notice, this list of conditions and the following disclaimer. | |
| ## 2. Redistributions in binary form must reproduce the above copyright | |
| ## notice, this list of conditions and the following disclaimer in the | |
| ## documentation and/or other materials provided with the distribution. | |
| ## 3. All advertising materials mentioning features or use of this software | |
| ## must display the following acknowledgement: | |
| ## This product includes software developed by Terrapin Communications, | |
| ## Inc. and its contributors for RANCID. | |
| ## 4. Neither the name of Terrapin Communications, Inc. nor the names of its | |
| ## contributors may be used to endorse or promote products derived from | |
| ## this software without specific prior written permission. | |
| ## 5. It is requested that non-binding fixes and modifications be contributed | |
| ## back to Terrapin Communications, Inc. | |
| ## | |
| ## THIS SOFTWARE IS PROVIDED BY Terrapin Communications, INC. AND CONTRIBUTORS | |
| ## ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED | |
| ## TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR | |
| ## PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COMPANY OR CONTRIBUTORS | |
| ## BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR | |
| ## CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF | |
| ## SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS | |
| ## INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN | |
| ## CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) | |
| ## ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | |
| ## POSSIBILITY OF SUCH DAMAGE. | |
| # | |
| # The expect login scripts were based on Erik Sherk's gwtn, by permission. | |
| # | |
| # Netscreen hacks implemented by Stephen Gill <gillsr@yahoo.com>. | |
| # Fortinet hacks by Daniel G. Epstein <dan at rootlike.com> | |
| # | |
| # fnlogin - fortinet login | |
| # | |
| # Most options are intuitive for logging into a netscreen firewall. | |
| # | |
| # Misc notes | |
| # netscreen does not have the concept of "enable", once logged in, a | |
| # users permissions can not change. | |
| # Usage line | |
| set usage "Usage: $argv0 \[-dSV\] \[-c command\] \[-Evar=x\] \ | |
| \[-f cloginrc-file\] \[-p user-password\] \ | |
| \[-s script-file\] \[-t timeout\] \[-u username\] \ | |
| \[-v vty-password\] \[-x command-file\] \ | |
| \[-y ssh_cypher_type\] router \[router...\]\n" | |
| # env(CLOGIN) may contain: | |
| # x == do not set xterm banner or name | |
| # Password file | |
| set password_file $env(HOME)/.cloginrc | |
| # Default is to login to the firewall | |
| set do_command 0 | |
| set do_script 0 | |
| # The default is to look in the password file to find the passwords. This | |
| # tracks if we receive them on the command line. | |
| set do_passwd 1 | |
| set do_enapasswd 1 | |
| # Sometimes routers take awhile to answer (the default is 10 sec) | |
| set timeoutdflt 45 | |
| # Save config, if prompted | |
| set do_saveconfig 0 | |
| # Find the user in the ENV, or use the unix userid. | |
| if {[ info exists env(CISCO_USER) ]} { | |
| set default_user $env(CISCO_USER) | |
| } elseif {[ info exists env(USER) ]} { | |
| set default_user $env(USER) | |
| } elseif {[ info exists env(LOGNAME) ]} { | |
| set default_user $env(LOGNAME) | |
| } else { | |
| # This uses "id" which I think is portable. At least it has existed | |
| # (without options) on all machines/OSes I've been on recently - | |
| # unlike whoami or id -nu. | |
| if [ catch {exec id} reason ] { | |
| send_error "\nError: could not exec id: $reason\n" | |
| exit 1 | |
| } | |
| regexp {\(([^)]*)} "$reason" junk default_user | |
| } | |
| if {[ info exists env(CLOGINRC) ]} { | |
| set password_file $env(CLOGINRC) | |
| } | |
| # Sometimes firewall take awhile to answer (the default is 10 sec) | |
| set timeout 45 | |
| # Process the command line | |
| for {set i 0} {$i < $argc} {incr i} { | |
| set arg [lindex $argv $i] | |
| switch -glob -- $arg { | |
| # Expect debug mode | |
| -d* { | |
| exp_internal 1 | |
| # Username | |
| } -u* { | |
| if {! [ regexp .\[uU\](.+) $arg ignore user]} { | |
| incr i | |
| set username [ lindex $argv $i ] | |
| } | |
| # VTY Password | |
| } -p* { | |
| if {! [ regexp .\[pP\](.+) $arg ignore userpasswd]} { | |
| incr i | |
| set userpasswd [ lindex $argv $i ] | |
| } | |
| set do_passwd 0 | |
| # Environment variable to pass to -s scripts | |
| } -E* { | |
| if {[ regexp .\[E\](.+)=(.+) $arg ignore varname varvalue]} { | |
| set E$varname $varvalue | |
| } else { | |
| send_user "\nError: invalid format for -E in $arg\n" | |
| exit 1 | |
| } | |
| # Command to run. | |
| } -c* { | |
| if {! [ regexp .\[cC\](.+) $arg ignore command]} { | |
| incr i | |
| set command [ lindex $argv $i ] | |
| } | |
| set do_command 1 | |
| # Expect script to run. | |
| } -s* { | |
| if {! [ regexp .\[sS\](.+) $arg ignore sfile]} { | |
| incr i | |
| set sfile [ lindex $argv $i ] | |
| } | |
| if { ! [ file readable $sfile ] } { | |
| send_user "\nError: Can't read $sfile\n" | |
| exit 1 | |
| } | |
| set do_script 1 | |
| # save config on exit | |
| } -S* { | |
| set do_saveconfig 1 | |
| # cypher type | |
| } -y* { | |
| if {! [ regexp .\[eE\](.+) $arg ignore cypher]} { | |
| incr i | |
| set cypher [ lindex $argv $i ] | |
| } | |
| # alternate cloginrc file | |
| } -f* { | |
| if {! [ regexp .\[fF\](.+) $arg ignore password_file]} { | |
| incr i | |
| set password_file [ lindex $argv $i ] | |
| } | |
| } -t* { | |
| if {! [regexp .\[tT\](.+) $arg ignore timeout]} { | |
| incr i | |
| set timeoutdflt [ lindex $argv $i ] | |
| } | |
| # command file | |
| } -x* { | |
| if {! [ regexp .\[xX\](.+) $arg ignore cmd_file]} { | |
| incr i | |
| set cmd_file [ lindex $argv $i ] | |
| } | |
| if [ catch {set cmd_fd [open $cmd_file r]} reason ] { | |
| send_user "\nError: $reason\n" | |
| exit 1 | |
| } | |
| set cmd_text [read $cmd_fd] | |
| close $cmd_fd | |
| set command [join [split $cmd_text \n] \;] | |
| set do_command 1 | |
| # Version string | |
| } -V* { | |
| send_user "@PACKAGE@ @VERSION@\n" | |
| exit 0 | |
| # Does tacacs automatically enable us? | |
| } -autoenable { | |
| # ignore autoenable | |
| } -* { | |
| send_user "\nError: Unknown argument! $arg\n" | |
| send_user $usage | |
| exit 1 | |
| } default { | |
| break | |
| } | |
| } | |
| } | |
| # Process firewalls...no firewalls listed is an error. | |
| if { $i == $argc } { | |
| send_user "\nError: $usage" | |
| } | |
| # Only be quiet if we are running a script (it can log its output | |
| # on its own) | |
| if { $do_script } { | |
| log_user 0 | |
| } else { | |
| log_user 1 | |
| } | |
| # | |
| # Done configuration/variable setting. Now run with it... | |
| # | |
| # Sets Xterm title if interactive...if its an xterm and the user cares | |
| proc label { host } { | |
| global env | |
| # if CLOGIN has an 'x' in it, don't set the xterm name/banner | |
| if [info exists env(CLOGIN)] { | |
| if {[string first "x" $env(CLOGIN)] != -1} { return } | |
| } | |
| # take host from ENV(TERM) | |
| if [info exists env(TERM)] { | |
| if [regexp \^(xterm|vs) $env(TERM) ignore ] { | |
| send_user "\033]1;[lindex [split $host "."] 0]\a" | |
| send_user "\033]2;$host\a" | |
| } | |
| } | |
| } | |
| # This is a helper function to make the password file easier to | |
| # maintain. Using this the password file has the form: | |
| # add password sl* pete cow | |
| # add password at* steve | |
| # add password * hanky-pie | |
| proc add {var args} { global int_$var ; lappend int_$var $args} | |
| proc include {args} { | |
| global env | |
| regsub -all "(^{|}$)" $args {} args | |
| if { [ regexp "^/" $args ignore ] == 0 } { | |
| set args $env(HOME)/$args | |
| } | |
| source_password_file $args | |
| } | |
| proc find {var router} { | |
| upvar int_$var list | |
| if { [info exists list] } { | |
| foreach line $list { | |
| if { [string match [lindex $line 0] $router ] } { | |
| return [lrange $line 1 end] | |
| } | |
| } | |
| } | |
| return {} | |
| } | |
| # Loads the password file. Note that as this file is tcl, and that | |
| # it is sourced, the user better know what to put in there, as it | |
| # could install more than just password info... I will assume however, | |
| # that a "bad guy" could just as easy put such code in the clogin | |
| # script, so I will leave .cloginrc as just an extention of that script | |
| proc source_password_file { password_file } { | |
| global env | |
| if { ! [file exists $password_file] } { | |
| send_user "\nError: password file ($password_file) does not exist\n" | |
| exit 1 | |
| } | |
| file stat $password_file fileinfo | |
| if { [expr ($fileinfo(mode) & 007)] != 0000 } { | |
| send_user "\nError: $password_file must not be world readable/writable\n" | |
| exit 1 | |
| } | |
| if [ catch {source $password_file} reason ] { | |
| send_user "\nError: $reason\n" | |
| exit 1 | |
| } | |
| } | |
| # Log into the firewall. | |
| # returns: 0 on success, 1 on failure | |
| proc login { router user userpswd passwd enapasswd prompt cmethod cyphertype } { | |
| global spawn_id in_proc do_command do_script sshcmd | |
| set in_proc 1 | |
| set uprompt_seen 0 | |
| # Telnet to the firewall & try to login. | |
| set progs [llength $cmethod] | |
| foreach prog [lrange $cmethod 0 end] { | |
| incr progs -1 | |
| if [string match "telnet*" $prog] { | |
| regexp {telnet(:([^[:space:]]+))*} $prog command suffix port | |
| if {"$port" == ""} { | |
| set retval [ catch {spawn telnet $router} reason ] | |
| } else { | |
| set retval [ catch {spawn telnet $router $port} reason ] | |
| } | |
| if { $retval } { | |
| send_user "\nError: telnet failed: $reason\n" | |
| return 1 | |
| } | |
| } elseif [string match "ssh*" $prog] { | |
| regexp {ssh(:([^[:space:]]+))*} $prog methcmd suffix port | |
| set cmd $sshcmd | |
| if {"$port" != ""} { | |
| set cmd "$cmd -p $port" | |
| } | |
| set retval [ catch {eval spawn [split "$cmd -c $cyphertype -x -l $user $router" { }]} reason ] | |
| if { $retval } { | |
| send_user "\nError: $cmd failed: $reason\n" | |
| return 1 | |
| } | |
| } elseif ![string compare $prog "rsh"] { | |
| send_error "\nError: unsupported method: rsh\n" | |
| if { $progs == 0 } { | |
| return 1 | |
| } | |
| continue; | |
| } else { | |
| send_user "\nError: unknown connection method: $prog\n" | |
| return 1 | |
| } | |
| sleep 0.3 | |
| # This helps cleanup each expect clause. | |
| expect_after { | |
| timeout { | |
| send_user "\nError: TIMEOUT reached\n" | |
| catch {close}; catch {wait}; | |
| if { $in_proc} { | |
| return 1 | |
| } else { | |
| continue | |
| } | |
| } eof { | |
| send_user "\nError: EOF received\n" | |
| catch {close}; catch {wait}; | |
| if { $in_proc} { | |
| return 1 | |
| } else { | |
| continue | |
| } | |
| } | |
| } | |
| # Here we get a little tricky. There are several possibilities: | |
| # the firewall can ask for a username and passwd and then | |
| # talk to the TACACS server to authenticate you, or if the | |
| # TACACS server is not working, then it will use the enable | |
| # passwd. Or, the firewall might not have TACACS turned on, | |
| # then it will just send the passwd. | |
| # if telnet fails with connection refused, try ssh | |
| expect { | |
| -re "(Connection refused|Secure connection \[^\n\r]+ refused)" { | |
| catch {close}; catch {wait}; | |
| if !$progs { | |
| send_user "\nError: Connection Refused ($prog): $router\n" | |
| return 1 | |
| } | |
| } | |
| -re "(Connection closed by|Connection to \[^\n\r]+ closed)" { | |
| catch {close}; catch {wait}; | |
| if !$progs { | |
| send_user "\nError: Connection closed ($prog): $router\n" | |
| return 1 | |
| } | |
| } | |
| eof { send_user "\nError: Couldn't login: $router\n"; wait; return 1 } | |
| -nocase "unknown host\r" { | |
| send_user "\nError: Unknown host $router\n"; | |
| catch {close}; catch {wait}; | |
| return 1 | |
| } | |
| "Host is unreachable" { | |
| send_user "\nError: Host Unreachable: $router\n"; | |
| catch {close}; catch {wait}; | |
| return 1 | |
| } | |
| "No address associated with name" { | |
| send_user "\nError: Unknown host $router\n"; | |
| catch {close}; catch {wait}; | |
| return 1 | |
| } | |
| -re "(Host key not found |The authenticity of host .* be established).* \\(yes/no\\)\\?" { | |
| send "yes\r" | |
| send_user "\nHost $router added to the list of known hosts.\n" | |
| exp_continue } | |
| -re "HOST IDENTIFICATION HAS CHANGED.* \\(yes/no\\)\\?" { | |
| send "no\r" | |
| send_user "\nError: The host key for $router has changed. Update the SSH known_hosts file accordingly.\n" | |
| catch {close}; catch {wait}; | |
| return 1 | |
| } | |
| -re "HOST IDENTIFICATION HAS CHANGED\[^\n\r]+" { | |
| send_user "\nError: The host key for $router has changed. Update the SSH known_hosts file accordingly.\n" | |
| return 1 | |
| } | |
| -re "Offending key for .* \\(yes/no\\)\\?" { | |
| send "no\r" | |
| send_user "\nError: host key mismatch for $router. Update the SSH known_hosts file accordingly.\n" | |
| catch {close}; catch {wait}; | |
| return 1 | |
| } | |
| -re "(denied|Sorry)" { | |
| send_user "\nError: Check your passwd for $router\n" | |
| catch {close}; catch {wait}; return 1 | |
| } | |
| "Login failed" { | |
| send_user "\nError: Check your passwd for $router\n"; | |
| catch {close}; catch {wait}; return 1 | |
| } | |
| -re "(login:)" { | |
| sleep 1; | |
| send -- "$user\r" | |
| set uprompt_seen 1 | |
| exp_continue | |
| } | |
| -re "@\[^\r\n]+\[Pp]assword:" { | |
| # ssh pwd prompt | |
| sleep 1 | |
| send -- "$userpswd\r" | |
| exp_continue | |
| } | |
| "\[Pp]assword:" { | |
| sleep 1; | |
| if {$uprompt_seen == 1} { | |
| send -- "$userpswd\r" | |
| } else { | |
| send -- "$passwd\r" | |
| } | |
| exp_continue | |
| } | |
| -- "$prompt" { break; } | |
| } | |
| } | |
| set in_proc 0 | |
| return 0 | |
| } | |
| # Run commands given on the command line. | |
| proc run_commands { prompt command } { | |
| global in_proc | |
| set in_proc 1 | |
| # Disable output paging. | |
| send -- "config system console\r" | |
| expect -re $prompt; send -- "set output standard\r" | |
| expect -re $prompt; send -- "end\r" | |
| expect -re $prompt; | |
| set sep "\\1\u001" | |
| regsub -all {([^\\])\;} $command "$sep" esccommand | |
| set sep "\u001" | |
| set commands [split $esccommand $sep] | |
| set num_commands [llength $commands] | |
| for {set i 0} {$i < $num_commands} { incr i} { | |
| send -- "[subst -nocommands [lindex $commands $i]]\r" | |
| expect { | |
| -re "$prompt" { send "\r" | |
| sleep 0.5 | |
| } | |
| -gl "--More--" { send " " | |
| exp_continue | |
| -re "\[\n\r]+" { exp_continue } | |
| } | |
| } | |
| } | |
| expect { | |
| -re "$prompt$" { | |
| send "exit\r" | |
| sleep 0.5 | |
| exp_continue | |
| } | |
| -re "\[\n\r]+" { exp_continue } | |
| -gl "Configuration modified, save?" { | |
| send "n\r" | |
| exp_continue | |
| } | |
| timeout { catch {close}; catch {wait}; | |
| return 0 | |
| } | |
| eof { return 0 } | |
| } | |
| set in_proc 0 | |
| } | |
| # | |
| # For each firewall... (this is main loop) | |
| # | |
| source_password_file $password_file | |
| set in_proc 0 | |
| set exitval 0 | |
| foreach router [lrange $argv $i end] { | |
| set router [string tolower $router] | |
| send_user "$router\n" | |
| # FortiOS 2.x prompts can end in either '#' or '$' | |
| set prompt "\[#\\$] " | |
| # device timeout | |
| set timeout [find timeout $router] | |
| if { [llength $timeout] == 0 } { | |
| set timeout $timeoutdflt | |
| } | |
| # Figure out passwords | |
| if { $do_passwd || $do_enapasswd } { | |
| set pswd [find password $router] | |
| if { [llength $pswd] == 0 } { | |
| send_user "\nError: no password for $router in $password_file.\n" | |
| continue | |
| } | |
| set passwd [join [lindex $pswd 0] ""] | |
| set enapasswd [join [lindex $pswd 1] ""] | |
| } else { | |
| set passwd $userpasswd | |
| set enapasswd $enapasswd | |
| } | |
| # Figure out username | |
| if {[info exists username]} { | |
| # command line username | |
| set ruser $username | |
| } else { | |
| set ruser [join [find user $router] ""] | |
| if { "$ruser" == "" } { set ruser $default_user } | |
| } | |
| # Figure out username's password (if different from the vty password) | |
| if {[info exists userpasswd]} { | |
| # command line username | |
| set userpswd $userpasswd | |
| } else { | |
| set userpswd [join [find userpassword $router] ""] | |
| if { "$userpswd" == "" } { set userpswd $passwd } | |
| } | |
| # Figure out cypher type | |
| if {[info exists cypher]} { | |
| # command line cypher type | |
| set cyphertype $cypher | |
| } else { | |
| set cyphertype [find cyphertype $router] | |
| if { "$cyphertype" == "" } { set cyphertype "3des" } | |
| } | |
| # Figure out connection method | |
| set cmethod [find method $router] | |
| if { "$cmethod" == "" } { set cmethod {{telnet} {ssh}} } | |
| # Figure out the SSH executable name | |
| set sshcmd [join [lindex [find sshcmd $router] 0] ""] | |
| if { "$sshcmd" == "" } { set sshcmd {ssh} } | |
| # Login to the router | |
| if {[login $router $ruser $userpswd $passwd $enapasswd $prompt $cmethod $cyphertype]} { | |
| incr exitval | |
| continue | |
| } | |
| # we are logged in, now figure out the full prompt based on what the | |
| # device sends us. | |
| send "\r" | |
| expect { | |
| -re "\[\r\n]+" { exp_continue; } | |
| -re "^(.+$prompt)" { set junk $expect_out(0,string); } | |
| if {[$junk = "(^\\$ $)"]} { | |
| set prompt $junk; | |
| } else { | |
| if {[$junk = "(^# $)"]} { set prompt $junk ; } | |
| }; | |
| } | |
| if { $do_command } { | |
| if {[run_commands $prompt $command]} { | |
| incr exitval | |
| continue | |
| } | |
| } elseif { $do_script } { | |
| # Disable output paging. | |
| send "config system console\r" | |
| send "set output standard\r" | |
| send "end\r" | |
| expect -re $prompt {} | |
| source $sfile | |
| catch {close}; | |
| } else { | |
| label $router | |
| log_user 1 | |
| interact | |
| } | |
| # End of for each firewall | |
| catch {wait}; | |
| sleep 0.3 | |
| } | |
| exit $exitval |