I need to run this cmd with an expect script:
echo users.1.password=`grep %user% /etc/passwd | awk -F: '{print $2}'` >> /tmp/system.cfg.new
But it errors out because of the $2 in it. How do I fix this? I need the variable to only be visible to the device I am sending the cmd to.
Here is the full script for password change on UBNT equipment via script (works via ssh, but not as script because of $2):
#!/usr/bin/expect
set timeout 30
#Edit for User
set user user
#Edit for Old Password
set old oldpassword
#Edit for New Password
set new newpassword
#get IP List from iplist.txt
set f [open "/iplist.txt"]
set data [read $f]
close $f
foreach line [split $data \n] {
if {$line eq {}} continue
spawn ssh $user#$line
expect {
"assword:" {
send "$old\r"
expect {
"assword:" {
close
continue
}}
expect {
"*" {
send "passwd $user\r"
expect "assword:"
send "$new\r"
expect "assword:"
send "$new\r"
expect "*"
send "grep -v users.1.password= /tmp/system.cfg > /tmp/system.cfg.new\r"
expect "*"
send "echo users.1.password=`grep $user /etc/passwd | awk -F: '{print $2}'` >> /tmp/system.cfg.new\r"
expect "*"
send "cp /tmp/system.cfg.new /tmp/system.cfg\r"
expect "*"
send "save && reboot\r"
close
continue
}}}}
expect {
"*" {
close
continue
}}
expect eof
}
You just have to escape the dollar sign with backslash.
send "echo users.1.password=`grep $user /etc/passwd | awk -F: '{print \$2}'` >> /tmp/system.cfg.new\r"
By the way, using expect * might be dangerous and not a recommended one unless a read need of it.
You could use the a generalized approach for the matching the prompt as,
set prompt "#|%|>|\\\$ $"
Here, we have escaped the literal dollar sign with backslash and the last dollar is meant for the end-of-line regular expression.
After sending any commands to the remote shell, you can expect for the pattern as
expect -re $prompt
Related
I keep getting an error on this after sending the new password. It will change the password, but not do the rest of the script. Any ideas why it keeps erroring out. I wrote a very similar script for a different device and it works prefect, but the stuff after changing the password is not needed on that device. This device will not save the password after reboot if the rest is not completed. Doing it manually through ssh works just fine so its not the cmds that is the problem, it's something with the script.
#!/usr/bin/expect
set timeout -1
#Edit for User
set user user
#Edit for Old Password
set old oldpassword
#Edit for New Password
set new newpassword
#get IP List from iplist.txt
set f [open "/iplist.txt"]
set data [read $f]
close $f
foreach line [split $data \n] {
if {$line eq {}} continue
spawn ssh $user#$line
expect "assword:"
send "$old\r"
sleep 10
send "passwd $user\r"
expect "assword:"
send "$new\r"
expect "assword:"
send "$new\r"
sleep 10
send "grep -v users.1.password= /tmp/system.cfg > /tmp/system.cfg.new\r"
sleep 10
send "echo users.1.password=`grep $user /etc/passwd | awk -F: '{print \$2}'` >> /tmp/system.cfg.new\r"
sleep 10
send "cp /tmp/system.cfg.new /tmp/system.cfg\r"
sleep 10
send "save && reboot\r"
close
expect eof
}
The full script is alot bigger with with fail-safes if the device does not respond to ssh or the original password is wrong. That one won't work though until I figure out what is wrong with this portion. I just slimmed it down to figure out where the problem is happening. Also this line seems to be the issue as it does create the system.config.new on the line before it:
send "echo users.1.password=`grep $user /etc/passwd | awk -F: '{print \$2}'` >> /tmp/system.cfg.new\r"
It was and works in ssh as this:
send "echo users.1.password=`grep $user /etc/passwd | awk -F: '{print $2}'` >> /tmp/system.cfg.new\r"
But sends an error because of the $2 then is view-able by expect. I was told that putting a \$2 would make it only view-able to the remote shell. Any help would be great.
Originally it had expect "star" instead of the sleep cmds. I have been trying tons of stuff out on this script and once I get it to run incorporate it in my full script. The reason I am using sleep is because "star" doesn't seem to match output and fails on the second send $new/r. With sleep it has made it alot farther. I do have a correction though. It is actually making it right up to send "save && reboot\r". I am going to eventually use your $prompt suggestion from my other question in place of sleep or expect "star". With debug on this is where it throws the error:
send: sending "save && reboot\r" to { exp4 }
expect: spawn id exp4 not open
while executing
"expect eof"
("foreach" body line 21)
invoked from within
"foreach line [split $data \n] {
if {$line eq {}} continue
spawn ssh $user#$line
expect "assword:"
send "$old\r"
sleep 3
send "passwd $user\r"
e..."
(file "./ubnt.sh" line 15)
The "save && reboot\r" will kick out the ssh connection after it saves the settings, but it doesn't seem to be getting that far. Is there an issue with &&, maybe I need to /&&.
Interact instead of close and expect eof is the answer I will update with full script when it's done. Here is the working UBNT password change script:
#!/usr/bin/expect
set timeout 30
#Edit for User
set user user
#Edit for Old Password
set old oldpassword
#Edit for New Password
set new newpassword
#get IP List from iplist.txt
set f [open "/iplist.txt"]
set data [read $f]
close $f
foreach line [split $data \n] {
if {$line eq {}} continue
spawn ssh $user#$line
expect {
"assword:" {
send "$old\r"
expect {
"assword:" {
close
continue
}}
expect {
"*" {
send "passwd $user\r"
expect "*assword:*"
send "$new\r"
expect "*assword:*"
send "$new\r"
expect "*"
send "grep -v users.1.password= /tmp/system.cfg > /tmp/system.cfg.new\r"
expect "*"
send "echo users.1.password=`grep $user /etc/passwd | awk -F: '{print \$2}'` >> /tmp/system.cfg.new\r"
expect "*"
send "cp /tmp/system.cfg.new /tmp/system.cfg\r"
expect "*"
send "save && reboot\r"
interact
continue
}}}}
expect {
"*" {
close
continue
}}
expect eof
}
Here is the script for Mikrotik passwords:
#!/usr/bin/expect
set timeout 30
#Edit for User
set user user
#Edit for Old Password
set old oldpassword
#Edit for New Password
set new newpassword
#get IP List from iplist.txt
set f [open "/iplist.txt"]
set data [read $f]
close $f
foreach line [split $data \n] {
if {$line eq {}} continue
spawn -noecho ssh -q -o "StrictHostKeyChecking=no" $user#$line
expect {
"assword:" {
send "$old\r"
expect {
"assword:" {
close
continue
}}
expect {
"*" {
send "user set $user password=$new\r"
expect ">"
send "quit\r"
close
continue
}}}}
expect {
"*" {
close
continue
}}
expect eof
}
Dinesh mentioned that I should not use "star" in my expect commands and said that instead use:
set prompt "#|%|>|\\\$ $"
And:
expect -re $prompt
Which I will probably change. The scripts also move onto next IP in list if the device cannot be reached via ssh or if the oldpassword does not work for the device. The only other change I plan to make on both is to have it create 3 log files listing successful ips, wrong password ips, and cant reach ips. This should only be one more command before each continue, but haven't yet researched how to output a variable to a local log file.
Thanks for your help on this Dinesh.
I am trying to change the password for a user on a bunch of linux based routers pulling IPs from a list. The iplist.txt is just a list of ips (one per line). This is the code I am trying to use:
#!/usr/bin/expect
set timeout 20
#Edit for User
set user username
#Edit for Old Password
set old oldpassword
#Edit for New Password
set new newpassword
#get IP List from iplist.txt
set f [open "/iplist.txt"]
set hosts [split [read $f] "\n"]
close $f
foreach host $hosts {
spawn -noecho ssh -q -o "StrictHostKeyChecking=no" $user#$host
expect "assword:"
send "$old\r"
expect ">"
send "user set $user password=$new\r"
expect ">"
send "quit\r"
expect eof
close
}
Which works for the first ip on the list but send this error on the second:
spawn_id: spawn id exp4 not open
I got it to work this way as I wanted:
#!/usr/bin/expect
set timeout 30
#Edit for User
set user user
#Edit for Old Password
set old oldpassword
#Edit for New Password
set new newpassword
#get IP List from iplist.txt
set f [open "/iplist.txt"]
set data [read $f]
close $f
foreach line [split $data \n] {
if {$line eq {}} continue
spawn -noecho ssh -q -o "StrictHostKeyChecking=no" $user#$line
expect "assword:"
send "$old\r"
expect ">"
send "user set $user password=$new\r"
expect ">"
send "\r"
expect ">"
send "quit\r"
send "\r"
expect eof
}
The next issue I run into is if the device doesn't have the old password or if the linux box cannot reach the device via ssh, it will error out with the same spawn id exp* not open when it gets to that IP and will not continue onto the next IP in the list. Is there anyway I can a statement that says if "assword:" comes up a second time to move onto next IP and if ">" comes up like its supposed to keep going with the script, then add a line after the spawn command that will move to the next IP in list if it doesn't receive the first expect "assword:"?
Any help would be appreciated. I am new to expect, but seems to be a really good tool for mass ssh processes in a script. Just having trouble tweaking it to not error out on 1 job instead of moving to next job upon error.
#!/usr/bin/expect
set timeout 30
#Edit for User
set user user
#Edit for Old Password
set old oldpassword
#Edit for New Password
set new newpassword
#get IP List from iplist.txt
set f [open "/iplist.txt"]
set data [read $f]
close $f
foreach line [split $data \n] {
if {$line eq {}} continue
spawn -noecho ssh -q -o "StrictHostKeyChecking=no" $user#$line
expect {
"assword:" {
send "$old\r"
expect {
"assword:" {
close
continue
}}
expect {
"*" {
send "user set $user password=$new\r"
expect ">"
send "quit\r"
close
continue
}}}}
expect {
"*" {
close
continue
}}
expect eof
}
Probably a bit dirty scripting, but it works. Now if I can figure out how to export successful, wrong password, and timeout logs so I know if any error-ed out.
In a another question I was kindly provided with a solution to a dilemma I was having with expect and if/else statements (basically, my lack of knowledge in how they're written). The script I was given works with one exception: If I cleanly connect to a remote host the exit command is not sent which will close that connection and move onto the next host in the list.
This is the functional part of the script:
while {[gets $file1 host] != -1} {
puts $host
spawn -noecho ssh $host
expect {
"continue connecting" {
send "yes\r"
expect {
"current" {
send -- $pw2\r
exp_continue
}
"New password" {
send -- $pw1\r
exp_continue
}
"Retype new password" {
send -- $pw1\r
exp_continue
}
msnyder
}
send "exit\r"
}
}
interact
}
When I'm connected to the host I expect to see the standard Linux prompt: [username#host ~]$. At least, that's our standard prompt. The prompt is preceded by a banner. Might that be throwing a kink into this? I wouldn't think so since I'm only looking for "continue connecting" from the RSA fingerprint prompt and that is within text spread across multiple lines. I would think that expect is intelligent enough to ignore any scrolling text and only look at text in prompts that are looking for input.
I considered passing the exit command as an argument for the ssh $host command, but expect doesn't seem to like that.
Can someone now assist me in getting the exit command to be sent properly?
UPDATE: I've added the exp_internal 1 option to the script to see what it is doing and it seems to only ever be matching "continue connecting" even at the user prompt once an SSH connection is established. It doesn't appear to be executing the next comparison for "msnyder". http://pastebin.com/kEGH3yUY
UPDATE2: I'm making progress. Based on Glenn Jackman's script below, I was able to get this working until the password prompt appears:
set prompt {\$ $}
set file1 [open [lindex $argv 0] r]
set pw1 [exec cat /home/msnyder/bin/.pw1.txt]
set pw2 [exec cat /home/msnyder/bin/.pw2.txt]
while {[gets $file1 host] != -1} {
puts $host
spawn -noecho ssh -q $host
expect {
"continue connecting" {
send "yes\r"
expect {
"current" {
send -- $pw2\r
exp_continue
}
"New password" {
send -- $pw1\r
exp_continue
}
"Retype new password" {
send -- $pw1\r
exp_continue
}
-re $prompt {
send -- exit\r
expect eof
}
}
} -re $prompt {
send -- exit\r
expect eof
}
}
}
I have explicitly told it to send the exit command twice rather than trying the short-circuit method that Glenn used (it didn't seem to work per my comment under his answer).
What is now happening, is that it will loop through the list of hosts, send 'yes' to the RSA fingerprint prompt and then send 'exit' to the command prompt. However, when it hits a server that needs to have the password set it stops. It seems to eventually time out and then restarts with the next host. The current password is not being sent.
# a regular expression that matches a dollar sign followed by a space
# anchored at the end of the string
set prompt {\$ $}
while {[gets $file1 host] != -1} {
puts $host
spawn -noecho ssh $host
expect {
"continue connecting" {
send "yes\r"
expect {
# ...snipped...
-re $prompt
}
}
-re $prompt
}
send -- exit\r
expect eof
}
We've moved the "exit" to a place where it will be executed if you match either "comtinue connecting" or if you log in directly to your prompt.
Nice answer but I hate seeing code cut and pasted. One comment and a bit of refactoring: When you hit the "current" block, you change the password but never exit. It occurs to be that each scenario should end with you seeing the prompt and exiting:
while {[gets $file1 host] != -1} {
puts $host
spawn -noecho ssh -q $host
expect {
"continue connecting" {
send "yes\r"
exp_continue
}
"current" {
send -- $pw2\r
expect "New password"
send -- $pw1\r
expect "Retype new password"
send -- $pw1\r
exp_continue
}
-re $prompt
}
send -- exit\r
expect eof
}
If you see "continue connecting", you send yes and then wait to see "current" or the prompt.
If you see "current" you change the password and then wait to see the prompt.
If you see the prompt, the expect comment ends and the next thing you do is exit.
I've sorted out the answer. After looking at Glenn's script below it occurred to me that each block is a separate if/else when instead I need the password section to act as a single block and the exit to act as the else. This is what I now have:
set prompt {\$ $}
set file1 [open [lindex $argv 0] r]
set pw1 [exec cat /home/msnyder/bin/.pw1.txt]
set pw2 [exec cat /home/msnyder/bin/.pw2.txt]
while {[gets $file1 host] != -1} {
puts $host
spawn -noecho ssh -q $host
expect {
-re $prompt {
send -- exit\r
expect eof
}
"current" {
send -- $pw2\r
expect "New password"
send -- $pw1\r
expect "Retype new password"
send -- $pw1\r
}
"continue connecting" {
send "yes\r"
expect {
"current" {
send -- $pw2\r
expect "New password"
send -- $pw1\r
expect "Retype new password"
send -- $pw1\r
}
-re $prompt {
send -- exit\r
expect eof
}
}
}
}
}
It addresses four situations:
I do NOT have to accept the RSA fingerprint OR change my password.
I do NOT have to accept the RSA fingerprint but DO have to change my password.
I DO have to accept the RSA fingerprint AND change my password.
I DO have to accept the RSA fingerprint but NOT change my password.
I'm running a script on a remote server like using this command:
ssh root#host 'bash -s' < script.sh
Now I'm trying to use expect to handle the password prompt. This is the script:
#!/usr/bin/expect
set cmd [lindex $argv 0]
spawn -noecho ssh root#host $cmd
expect {
"password:" {
send "password\r"
}
}
If I run the script, it gives no output:
./ssh.exp 'bash -s' < script.sh
I know that's not the way to use ssh without password, but this is not the question right here.
UPDATE I tried the idea of glenn jackman with a simple script but it's not working. This is the script I'm using:
#!/usr/bin/expect
spawn ssh xxx#xxx
expect "*?assword:*"
send "pwd\r"
send "echo hello world"
This is the output I get:
[xxx#xxx bin]$ expect -d my.exp
expect version 5.43.0
argv[0] = expect argv[1] = -d argv[2] = my.exp
set argc 0
set argv0 "my.exp"
set argv ""
executing commands from command file my.exp
spawn ssh xxx#xxx
parent: waiting for sync byte
parent: telling child to go ahead
parent: now unsynchronized from child
spawn: returns {7599}
expect: does "" (spawn_id exp6) match glob pattern "*?assword:*"? no
xxx#xxx's password:
expect: does "xxx#xxx's password: " (spawn_id exp6) match glob pattern "*?assword:*"? yes
expect: set expect_out(0,string) "xxx#xxx's password: "
expect: set expect_out(spawn_id) "exp6"
expect: set expect_out(buffer) "xxx#xxx's password: "
send: sending "pwd" to { exp6 }
send: sending "echo hello world" to { exp6 }
write() failed to write anything - will sleep(1) and retry...
UPDATE I managed it to get my script to run. This is the result which works:
#!/usr/bin/expect
set user [lindex $argv 0]
set host [lindex $argv 1]
set pwd [lindex $argv 2]
spawn ssh $user#$host bash -s
expect {
"?asswor?: " {
send "$pwd\n"
}
}
while {[gets stdin line] != -1} {
send "$line\n"
}
send \004
expect {
"END_TOKEN_OF_SCRIPT" {
exit 0
}
default {
exit 1
}
}
You need to send the script you read on stdin to the remote host:
while {[gets stdin line] != -1} {
send "$line\r"
}
# then you may have to send ctrl-D to signal end of stdin
send \004
Use expect_user, as shown in the man page:
The following script reads a password, and then runs a program every hour that demands a password each time it is run. The script supplies the password so that you only have to type it once. (See the stty command which demonstrates how to turn off password echoing.)
send_user "password?\ "
expect_user -re "(.*)\n"
for {} 1 {} {
if {[fork]!=0} {sleep 3600;continue}
disconnect
spawn priv_prog
expect Password:
send "$expect_out(1,string)\r"
. . .
exit
}
Here is what I currently have, still improving it though:
#!/usr/local/bin/expect
# For debugging make the following to be line 1:
#!/usr/local/bin/expect -D 1
set timeout 20
send_user "Username?\ "
expect_user -re "(.*)\n"
set user $expect_out(1,string)
send_user "password?\ "
stty -echo
expect_user -re "(.*)\n"
stty echo
set password $expect_out(1,string)
spawn su
expect {
"Password" {send "$password\r"}
"#" {interact + return}
}
My Expect script shows password/user in clear text and I want to hide it.
#!/usr/local/bin/expect
########################################################################################### ############
# Input: It will handle two arguments -> a device and a show command.
########################################################################################### ############
# ######### Start of Script ######################
# #### Set up Timeouts - Debugging Variables
log_user 0
set timeout 10
set userid "USER"
set password "PASS"
# ############## Get two arguments - (1) Device (2) Command to be executed
set device [lindex $argv 0]
set command [lindex $argv 1]
spawn /usr/local/bin/ssh -l $userid $device
match_max [expr 32 * 1024]
expect {
-re "RSA key fingerprint" {send "yes\r"}
timeout {puts "Host is known"}
}
expect {
-re "username: " {send "$userid\r"}
-re "(P|p)assword: " {send "$password\r"}
-re "Warning:" {send "$password\r"}
-re "Connection refused" {puts "Host error -> $expect_out(buffer)";exit}
-re "Connection closed" {puts "Host error -> $expect_out(buffer)";exit}
-re "no address.*" {puts "Host error -> $expect_out(buffer)";exit}
timeout {puts "Timeout error. Is device down or unreachable?? ssh_expect";exit}
}
expect {
-re "\[#>]$" {send "term len 0\r"}
timeout {puts "Error reading prompt -> $expect_out(buffer)";exit}
}
expect {
-re "\[#>]$" {send "$command\r"}
timeout {puts "Error reading prompt -> $expect_out(buffer)";exit}
}
expect -re "\[#>]$"
set output $expect_out(buffer)
send "exit\r"
puts "$output\r\n"
... and add -OUseBatchMode=Yes so that if there is a problem with your keys, ssh will fail immediately (you can verify exit code) rather than just falling back to password mode and hanging (as you are running interactively)
The same issue exists in any scripting language. The script can't type in your password if it doesn't know it... the easiest solution is to use passwordless ssh, using keys.
Have a look at Glenn's answer here: How can I make an expect script prompt for a password?
I was trying to do the same sort of thing and found this useful.
The following is expect code that prompts for the username and password:
send_user "Username?\ "
expect_user -re "(.*)\n"
set user $expect_out(1,string)
send_user "password?\ "
stty -echo
expect_user -re "(.*)\n"
stty echo
set password $expect_out(1,string)
Alternatively if you need multiple runs and don't want to re-enter your password each time then you could store the password in a file in your home directory with restricted permissions (0400) and read in the password from there. Then delete the password file when no longer needed.
When you send password through expect, if you do a 'ps', you can view the entire command with the password. To avoid this, you can call expect script from another bash script sending at about 500 character random string and then the password. Then on expect script you can call password as $1 variable.
Bash script example:
#!/bin/bash
string="6e2884f8bf8418bac1ca224472c5d8206e2884f8bf8418bac1ca224472c5d8206e2884f8bf8418bac1ca224472c5d8206e2884f8bf8418bac1ca224472c5d8206e2884f8bf8418bac1ca224472c5d8206e2884f8bf8418bac1ca224472c5d8206e2884f8bf8418bac1ca224472c5d8206e2884f8bf8418bac1ca224472c5d8206e2884f8bf8418bac1ca224472c5d8206e2884f8bf8418bac1ca224472c5d8206e2884f8bf8418bac1ca224472c5d8206e2884f8bf8418bac1ca224472c5d8206e2884f8bf8418bac1ca224472c5d8206e2884f8bf8418bac1ca224472c5d8206e2884f8bf8418bac1ca224472c5d820"
./expectscript $string "MySuperSecretPassword"
And expect script example:
#!/usr/bin/expect -f
set timeout 20
set string [lindex $argv 0]
set password [lindex $argv 1]
spawn ssh "user#192.168.1.1"
expect "assword"
send "$password\r"
Hope this solve your problem.