I'm trying to write a script to update the password of an OS X account to a rotating, centrally-stored value. As a prelude to learning to use tclcurl, I just want to get this prototype script working:
#!/usr/bin/expect
set mgrpassword "newpassword" # this will become a tclcurl command later
spawn passwd manager
expect "New password:"
send "$mgrpassword\n"
expect "Retype new password:"
send "$mgrpassword\n"
puts "\nManager password changed."
exit 0
It runs without errors, but it does nothing; the password for the manager account remains unchanged. I've tried it with both \r and \n but that didn't make any difference. Can anyone see what I'm doing wrong or what steps I'm omitting?
(It will always run with admin rights; that's why there is no 'expect "Old password:"' line.)
Just add one more expect statement at the end, like as follows,
send "$mgrpassword\r"
expect eof
Basically, Expect will work with two feasible commands such as send and expect. If send is used, then it is mandatory to have expect (in most of the cases) afterwards. (while the vice-versa is not required to be mandatory)
This is because without that we will be missing out what is happening in the spawned process as Expect will assume that you simply need to send one string value and not expecting anything else from the session.
Your script can be written in the following way as well which makes it robust with the use of exp_continue. It will make the Expect to run again.
set mgrpassword "newpassword"
spawn passwd manager
expect {
timeout { puts "Timeout happened";exit 0}
"password:" {send "$mgrpassword \r";exp_continue}
eof {puts "Manager password changed"; exit 1}
}
So it turns out that
dscl . passwd /Users/manager [passwordstring]
works a lot better/easier than trying to combine passwd with expect. Hope this helps someone else.
Related
I'm working on a small project for school. I'm using 15 or so tuners to emulate a Cell network. I'm by no means well versed in scripting yet. I'm an EE who usually googles until I have some frankencode capable of my purposes.
The goal is the set up all the modules quickly so I thought to automate the process with a script. This requires ssh, and so far I have to manually type in the password each time. This morning I set up a basic test with both Expect and sshpass. In either case I can correctly log in, but not give instructions to the remote machine.
I was reading that sshpass has difficulty with sending remote instruction, correct me if I'm wrong.
/usr/bin/expect << EOF
spawn ssh root#<IP>
expect "(yes/no)?" #Are you sure you want to connect nonsense
send "yes\r"
expect "password"
send "$pass\r"
I tried a few things here to get the device to receive instruction
interact
cat /pathto/config.txt
#or
send "cat /pathto/config.txt
#the real goal is to send this instruction
sqlite3 /database.db "update table set param=X"
EOF
You might as well make it an expect script, not a shell script
#!/usr/bin/expect -f
and then pass the IP address to the script as a command line argument
expect myloginscript.exp 128.0.0.1 the_password
In the expect script, you'll grab that IP address from the arguments list
set ip [lindex $argv 0]
set pass [lindex $argv 1]
(Putting the password on the command line is not good security practice. You can research better methods of passing the password to your expect script.)
To use ssh, you'll be asked "are you sure" only the first time to connect, so let's make that conditional. That is done by letting the expect command wait for several patterns:
spawn ssh root#$ip
expect {
"(yes/no)?" {
send "yes\r"
# continue to wait for the password prompt
exp_continue
}
"password" {
send "$pass\r"
}
}
Once that is sent, you should expect to see your shell prompt. The pattern for this is up to your own configuration but it typically ends with a hash and a space.
expect -re {# $}
Now you can automate the rest of the commands:
send "cat /pathto/config.txt\r"
expect -re {# $}
# note the quoting
send "sqlite3 /database.db \"update table set param='X'\"\r"
expect -re {# $}
At this point, you'll want to log off:
send "exit\r"
expect eof
On the other hand, if you set up ssh private key authentication (see ssh-keygen and ssh-copy-id), you can just do this:
ssh root#IP sqlite3 /database.db "update table set param='$X'"
and not have to get into expect at all.
Not sure if this is more of a scripting/unix question or a programming one, but I tried on the unix stackexchange and got no responses, so:
The following expect code seems to work; that is, it appears to enter text in answer to the password prompt. However, the device never actually mounts.
But if I simply enter the command into a shell and type the password in by hand the device mounts successfully.
So I'm curious where the input is actually ending up, as it never seems to 'catch' the password yet doesn't present an error message either? In fact the output looks exactly the same in both instances, but only in the case of running the command and typing the password manually do I see my files appear over the network.
Here is the code:
#!/usr/bin/expect
spawn sudo mount.cifs "//WinPC/My Pictures" /home/LinPC/Desktop/Pictures -o user=Me
expect "Password: " {
set send_slow {1 .1}
send -s "a_password"
}
UPDATE: Got the help I needed to make it work: had to insert 'expect eof' after sending the password so that it doesn't end prematurely. However I now wish to progress to changing it to 'expect_background' so that I can have the same trigger response to issuing multiple mount commands. The following ends prematurely, and 'expect eof' at the end causes an error:
expect_background "Password:" {
send "a_password\r"
expect eof
}
spawn sudo mount.cifs "//WinPC/My Pictures" /home/LinPC/Desktop/Pictures -o user=Me
expect eof
What should it look like?
UPDATE: the following code block illustrates the current problem:
expect_background "Password: " {
send "a_password\r"
expect eof
}
spawn sudo mount.cifs "//WinPC/My Pictures" /home/LinPC/Desktop/Pictures -o user=someone
expect "Password: " {
send "a_password\r"
expect eof
}
#The password prompt gets answered by 'expect' but not 'expect_background'.
#If I delete the last 'expect' and insert 'expect eof' it hangs for a short
#while at the password prompt (around 3 seconds) then exits.
#Why?
Add one more expect statement after sending the password.
send -s "a_password\r"
expect eof
The eof will make the Expect to wait till the end of program.
Can anyone help me in creating an expect script to just do an SSH on a list of servers and check if it was fine. I do not need to interact, do not need to fire any command on each server, I just want to do an ssh and come out, with a return code mentioning whether it was successful or not.
Expect is important here, as I do not have an option to setup a passwordless connection. Also there is a possibility that passwordless connection is setup on some of those servers.
I tried something like:
#!/usr/local/bin/expect
set timeout 10
set ip [lindex $argv 0]
set user [lindex $argv 1]
set password [lindex $argv 2]
set prompt "(>|%|\\\\\\\$|#|]|) \$"
spawn ssh "$user\#$ip"
expect "Password:"
send "$password\r"
send "echo hello\r"
expect "hello"
send "exit\r"
But this gets stuck on the first server, and does nothing after that.
Thanks,
Piyush
A generalized idea can be having a procedure to spawn the ssh and close the connection which will maintain the connections local to the scope, so that global spawn_id won't get affected at all.
#!/usr/bin/expect
proc isHostAlive {host user pwd} {
# We escaped the `$` symbol with backslash to match literal '$'
# Commonly used prompt types
set prompt "#|%|>|\\\$"
set timeout 60
spawn ssh $user#$host
expect {
timeout {return FAIL}
"(yes/no)" {send "yes\r";exp_continue}
"password:" {send "$pwd\r";exp_continue}
-re $prompt
}
set msg "Hello World"
send "echo $msg\r"
expect {
timeout {return FAIL}
"\r\n$msg"
}
send "exit\r"
expect {
timeout {return FAIL}
eof
}
return PASS
}
# Lists to maintain the each host's information
set serverList {server1 server2 server3}
set userList {user1 user2 user3}
set pwdList {pwd1 pwd2 pwd3}
# Looping through all the servers and checking it's availability
foreach server $serverList user $userList pwd $pwdList {
puts "\n==>Status of $server : [isHostAlive $server $user $pwd]\n"
}
With exp_continue, we can handle even if any host does not have password. Basically exp_continue will cause the expect to run again. So, among the mentioned phrase whichever comes, it will be handled. i.e. if expect sees (yes/no), it will send yes, if expect sees password, it will send the password value and so on. Then expect will continue to wait for the whole set of phrases again.
The reason why I have added yes/no is because if suppose the host's RSA fingerprint needs to be saved.
After successful login, I am echoing Hello World and expecting for the echoed message. If you have noticed, I have used \r\n$msg in the expect statement. Why do we need \r\n here ?
Here is why. Whenever we send command that will be seen by the expect also and it will try to match against that too. If it matched, it will proceed as such.
Sample echo command output
dinesh#dinesh-VirtualBox:~/stackoverflow$ echo Hello World
Hello World
dinesh#dinesh-VirtualBox:~/stackoverflow$
The string we want to expect is already available in the send command. So, to make sure the matching expect string is only from the actual echoed response, I have added \r\n which will help us in matching what is necessary.
Then at last of the proc, I am sending exit command which will close the ssh connection and to match the same eof (End Of File) is used. In all sort of failure cases, the procedure will return FAIL.
I am trying to write a bash script that uses expect to scp a file to remote systems. The expect block that I have so far looks like this
expect -c "
set timeout -1
spawn scp $file user#host:$file
expect "\Are you sure you want to continue connection (yes/no)\"
send -- \"$password\r\"
expect eof
"
The problem is that this handles the case in which the host is not a known host and it asks if I want to continue connecting. I would like to add a an option for the case in which the host is already known and it simply wants the password.
The other issue is that I would like to handle the event in which the password the user entered is not correct. In that case, I would like to have the user reenter the password.
What would be the best way of accomplishing this using bash and expect?
Many thanks in advance!
host is not a known host and it asks if I want to continue connecting
Use: scp -o "StrictHostKeyChecking no" <...>
event in which the password the user entered is not correct
If your script is considered to be interactive, why you are using expect at all? scp can ask and re-ask a password by itself.
Like this:
expect -c "
set timeout -1
spawn scp $file user#host:$file
expect
{Are you sure you want to continue connection (yes/no)} {
send \"yes\r\"
exp_continue
}
{Password: } {
send -- \"$password\r\"
}
}
expect eof
"
The exp_continue command loops back to the containing expect command so that other patterns have a change to be matched.
I am using expect script inside bash script.
I tried to copy a file from remote host to local. The error I face is,
open(slave pty): bad file number + parent: sync byte write: broken pipe
The code:
#!/bin/bash
read -p "Enter username: " username
read -s -p "Enter password: " password
#Expect script
/bin/expect -<<EOD
set SERVERS {100 101 102}
foreach SERVER \$SERVERS {
set timeout -1
spawn scp ${username}#plsa\${SERVER}.corp.com:/log.2011-11-24 log.2011-11-24
expect "*password:"; send "$password\r"
expect eof }
EOD
Thanks
This doesn't exactly answer your question, but why are you wrapping the expect script with bash? If the only thing you're doing in bash is reading a couple of lines of input, you might as well just move that into expect:
#!/usr/bin/env expect
send_user "Enter username: "
expect_user -re "(.*)\n" {set username $expect_out(1,string)}
send_user "Enter password: "
stty -echo
expect_user -re "(.*)\n" {set password $expect_out(1,string)}
stty echo
set SERVERS {100 101 102}
foreach SERVER \$SERVERS {
set timeout -1
spawn scp ${username}#plsa\${SERVER}.corp.com:/log.2011-11-24 log.2011-11-24
expect "*assword:"; send "$password\r"
expect eof }
You've now removed a whole layer of complexity, which might make the actual error easier to debug. Also, note that scp prompts "Password", not "password", so you need to modify your match string as I've done here.
An additional question: can you use ssh key-based authentication for these connections instead of password authentication? This would simplify the entire process -- you wouldn't need to muck about with expect at all in this case. You could just do:
for server in 100 101 102; do
scp -i mykey ${username}#plsa${SERVER}.corp.com:/log.2011-11-24 log.2011-11-24
done
See http://expect.sourceforge.net/FAQ.html#q68
This is one of these "should not happen" errors. For example, the following question in this FAQ mentions that it could be the fault of the C library. Another possibility is that you've run out of some system resource (file descriptors). The most likely reason is that you're calling spawn in a loop and have neglected to call close and wait.
I know that this post is old that re-opening old posts is considered bad fu, but a search for this error message keeps sending Google here, so I think this probably a good place to actually answer it.
In my experience, I found that one of the many possible causes for this has to do with the fact that Expect is making library calls to other programs on the system and, in my case, I was unable to run those secondary programs with my user permissions. I have been unable to find out which library call is causing the issue, but a
username All=(ALL) /path/to/your/script
entered into visudo fixed it for me. Obviously, you don't have to issue blanket permissions, but allowing the user to execute the script with sudo root permissions gave Expect the access it needed. Just thought I would add this with the hope that it would help someone else in the future.