How to pass a parameter form perl in linux expect script? - linux

I have to pass a path at runtime as the abc.sh script will prompt for it .. I am trying following expect script to accomplish this,
#!/usr/bin/expect -f
set force_conservative 0
if {$force_conservative} {
set send_slow {1 .1}
proc send {ignore arg} {
sleep .1
exp_send -s -- $arg
}
}
spawn abc.sh
match_max 100000
expect -exact "Please enter path : "
send -- $path
expect -exact "\r
expect eof
Need your help – how I can pass $path values at run time calling like this ?
expect script.exp $path
Thanks,

Expect Answer
Use send -- $argv in your expect script because $argv holds the command line arguments.
As an example, I've this handy expect script that interacts with ssh commands when admins stupidly disable public key authentication :
#!/usr/bin/expect -f
set timeout -1
set send_human {.6 .1 1.6 .1}
eval spawn $argv
match_max 100000
expect {
-re "LOGIN#(\[0-9A-Za-z_\\-\\.\]+)'s password: "
{ sleep 0.6 ; send -- "PASSWORD" ; sleep 0.1 }
interact
Use constructs like [lrange $argv 0 $argc] if you need only part of the $argv
Perl Answer
Use system 'script.exp' $path to call script.exp from a perl script, assuming use chmod, etc., roughly like DeVadder said sans the quotes. Or use exec if your perl script wishes to terminate. Run man perlfunc to read about perl commands.

Related

expect command is not passing env variable in ubuntu focal

I'm using expect (v5.45.4-2build1) to automate generation of client certs using easy-rsa in ubuntu focal.
As per easy-rsa, to customize cert validity we need to pass EASYRSA_CERT_EXPIRE variable.
But for some reason, my expect script is not able to fetch this variable.
Here's the code for script.exp:
#!/usr/bin/expect -f
set timeout -1
#exp_internal 1
set MY_PASSPHRASE [lindex $argv 0];
set USERNAME [lindex $argv 1];
set ttl [lindex $argv 2];
send "export EASYRSA_CERT_EXPIRE=$ttl\r"
spawn /bin/bash
set MSG "Executing script.exp for $USERNAME and $ttl";
send "echo 'INFO $MSG'>> /var/log/app/my_app.log\r"
send "cd /etc/openvpn/easy-rsa\r"
send "export EASYRSA_CERT_EXPIRE=$ttl\r"
send "./easyrsa gen-req $USERNAME\r"
expect "*Enter PEM pass phrase:"
send -- "$MY_PASSPHRASE\r"
expect "*Verifying - Enter PEM pass phrase:"
send -- "$MY_PASSPHRASE\r"
expect "*Common Name *$USERNAME*"
send -- "\r"
expect "*Keypair and certificate request completed*"
send -- "\r"
exit 0
Strangely, this works fine if i run the script via expect script.exp.
But I want to use this in my perl script and it is not working from the perl script.
This is how I'm calling my expect script:
my $cmd_out_csr = system("expect script.exp $my_passphrase $username $ttl");
Just running, expect script.exp $my_passphrase $username $ttl from cmd works fine.
Any help is appreciated.
Thanks.
use the perl system command with a list of arguments:
system('expect', 'script.exp', $my_passphrase, $username, $ttl);
when creating the variable in the remote shell, send quotes around the value:
send "export EASYRSA_CERT_EXPIRE=\"$ttl\"\r"
These two things will protect the value of $ttl if it contains whitespace.
Another thought: expect (via Tcl) can cd and set environment variables. Instead of spawning a bash shell, spawn easyrsa directly:
#!/usr/bin/expect -f
set timeout -1
#exp_internal 1
lassign $argv MY_PASSPHRASE USERNAME ttl
set f [open /var/log/app/my_app.log a]
puts $f "INFO Executing [info script] for $USERNAME and $ttl"
close $f
cd /etc/openvpn/easy-rsa
set env(EASYRSA_CERT_EXPIRE) $ttl
spawn ./easyrsa gen-req $USERNAME
expect "*Enter PEM pass phrase:"
send -- "$MY_PASSPHRASE\r"
expect "*Verifying - Enter PEM pass phrase:"
send -- "$MY_PASSPHRASE\r"
expect "*Common Name *$USERNAME*"
send -- "\r"
expect "*Keypair and certificate request completed*"
send -- "\r"
expect eof
puts "Done."

Use of expect to run scripts on remote machine

I am working on a project that requires some assistance.
I have automated most of the information required for the completion of this project but the only thing that is lagging is the running of local shell scripts on the remote machine.
As we are aware that no Linux command is recognized by the script that uses the 'expect' library.
Herein we have two use cases that I have tried:
1) Running the desired list of commands on the remote server using only one expect script which has both the script execution as well as pushing of output using scp to the local machine, here is a snippet of this code:
`chmod 777 localscript.sh
cat > script1.sh <<- "ALL"`
`#!/usr/bin/expect
set password [lindex $argv 0];
set ipaddress [lindex $argv 1];
set timevalue [lindex $argv 2];
set timeout $timevalue
spawn /usr/bin/ssh username#$ipaddress /bin/bash < ./localscript.sh
expect "assword:"
send "$password\r"
set timeout $timevalue
spawn /usr/bin/scp username#$2:"/path/from/source/*" /path/to/destination/folder/
expect "assword:"
send "$password\r"
interact
ALL
chmod 777 script1.sh
./script1.sh $password $2 $timevalue`
2) Running the desired list of commands on the remote server in a separate expect script and using scp to get files in a different script:
`cat > script1.sh <<- "ALL" `
`#!/usr/bin/expect
set password [lindex $argv 0];
set ipaddress [lindex $argv 1];
set timevalue [lindex $argv 2];
set timeout $timevalue
spawn /usr/bin/ssh username#$ipaddress /bin/bash < ./localscript.sh
expect "assword:"
send "$password\r"
interact
ALL
cat > script2.sh <<- "ALL2"`
`#!/usr/bin/expect
set password [lindex $argv 0];
set ipaddress [lindex $argv 1];
set timevalue [lindex $argv 2];
set timeout $timevalue
spawn /usr/bin/scp username#ipaddress:"/path/from/source/*" /path/to/destination/folder/
expect "assword:"
send "$password\r"
interact
ALL2
chmod 777 localscript.sh script1.sh script2.sh
./script1.sh $password $2 $timevalue
sleep 5
./script2.sh $password $2 $timevalue`
I believe the above codes should both be valid in their own respect however, the output for the same seem to be quite unexpected:
1) Both the commands ssh and scp are being executed almost simultaneously after password is entered hence, it is not giving localscript enough time to do its job, here's the output I see:
spawn /usr/bin/ssh username#1.2.3.4 /bin/bash < ./localscript.sh
Warning private system unauthorized users will be prosecuted.
username#1.2.3.4's password: spawn /usr/bin/scp
username#1.2.3.4:"/home/some/file/*" /another/file/
Warning private system unauthorized users will be prosecuted.
username#1.2.3.4's password:
scp: /home/some/file/*: No such file or directory
Please note: This functionality is working fine without the involvement of expect
2) Here we are executing ssh and scp separately, however, it seems like it is unable to recognize that the file localscript.sh exists:
spawn /usr/bin/ssh username#1.2.3.4 /bin/bash < ./localscript.sh
Warning private system unauthorized users will be prosecuted.
username#1.2.3.4's password:
bash: localscript.sh: No such file or directory
Warning private system unauthorized users will be prosecuted.
username#1.2.3.4's password:
scp: /home/some/file/*: No such file or directory
Any feedback on the same would be appreciated, I think the first approach might be a feasible solution, except the fact that spawn is too fast and none of the 'sleep' or 'after' commands are helping/working. I think the second approach is also valid however it seems like there is a different way of running a local script on a remote server than the usual way we do on Linux when using 'expect'.
Sorry for so much elaboration, I am hoping to be out of my misery soon :)
Indeed the timeout you are setting is not working as you expect it to. Both scripts are spawned, and the expect "assword:" after each spawn is actually catching and reacting to the same password prompt.
expect is actually more sophisticated than a cursory glance would lead you to believe. Each spawn should return a PID, which you can use with your expect to look for output from a specific process.
expect can also be broken down into multiple parts, and have the ability to define subroutines. Here are some more advanced use examples https://wiki.tcl-lang.org/10045
In this specific case I would suggest waiting for the scp to complete before spawning the next process.
expect {
"assword:" {
send "$password\r"
exp_continue # Keep expecting
}
eof {
puts -nonewline "$expect_out(buffer)"
# eof so the process should be done
# It is safe to execute the next spawn
# without exp_continue this expect will
# break and continue to the next line
}
}

What's the best way to mix remote expect scripts and local bash commands?

I'm automating tasks on a local and remote machine (behind a firewall). Once I'm done with tasks on the remote machine, I'd like the script to return to executing commands on the local machine.
#!/usr/bin/expect -f
set timeout -1
spawn ssh username#host
expect "Password: "
send "mypassword\r"
expect "username#host:~$"
...do some stuff...
send "exit\r"
expect eof
[then, once on the local machine, change directories and do other things]
What's the best way to append bash commands? I suppose I could start with bash, call expect within it, then simply return to bash once expect is done.
Expect is based on Tcl, so it can run the same commands. But if your goal is to run bash commands, the best bet is to run them from bash as a separate script, exactly as you propose in your last sentence.
It really depends on what your idea of ...do some stuff... is. Here's an example of something I recently did from my OSX w/s to an AWS instance
export all_status
init_scripts=($(ssh -q me#somehost 'ls /etc/init.d'))
for this_init in ${init_scripts[#]};do
all_status="${all_status}"$'\n\n'"${this_init}"$'\n'"$(ssh -q somehost \'sudo /etc/init.d/${this_init} status\')"
done
echo "$all_status" > ~/somehost_StatusReport.txt
unset all_status
Passing a command at the end of the ssh command will cause the command to be run on the remote host. Or you can scp a script to the remote host and run it with
ssh somehost '/home/me/myscript'
I met this situation recently too. I make a shell supexpect.sh which could login and execute command automatically. It will return to your local shell at the end.
#!/usr/bin/expect
#Usage:supexpect <host ip> <ssh username> <ssh password> <commands>
set timeout 60
spawn ssh [lindex $argv 1]#[lindex $argv 0] [lindex $argv 3]
expect "yes/no" {
send "yes\r"
expect "*?assword" { send "[lindex $argv 2]\r" }
} "*?assword" { send "[lindex $argv 2]\r" }
send "exit\r"
expect eof
To execute:
./supexpect.sh 10.89.114.132 username password "ls -a;pwd;your_stuff_on_remote_host"
Note:
The prompt might need to adapt to your own system, and of course you need to pass execute permission to it.

Linux Shell script, kill as another user

Recently I've been trying to get this script working:
#!/usr/bin/expect -f
set pssword [lrange $argv 0 0]
spawn su - kod -c cd cod4 -c "nohup kill 7938" > /dev/null 2>&1 &
expect "Password:" { send "$pssword\r" }
expect "# " { send "q" }
exit
It should login as user called "kod" and kill the process by certain pid
Here is the starting script and it works just fine...
#!/usr/bin/expect -f
set pssword [lrange $argv 0 0]
set port [lrange $argv 1 1]
set mod [lrange $argv 2 2]
set map [lrange $argv 3 3]
set num [lrange $argv 4 4]
set hostname [lrange $argv 5 5]
set rcon [lrange $argv 6 6]
set password [lrange $argv 7 7]
spawn su - kod -c cd cod4 -c "nohup ./cod4_lnxded +set s_num=$num +set net_port $port +set dedicated 2 +set fs_game mods/$mod +set sv_punkbuster 1 +set sv_hostname $hostname +set rcon_password $rcon +set g_password $password +set promod_mode match_mr10 +set g_gametype sd +map $map" > /dev/null 2>&1 &
expect "Password:" { send "$pssword\r" }
expect "# " { send "q" }
exit
Please don't tell me to "login as root" or either "just use sudo" because that's not the case...
Thanks !
I think the user who ran the process and the user that wanted to kill it should be in a common group. The process permission should be associated with that group too.
The real problem is that your code is dropping any error messages that might be coming out of that (rather complex) command; it would be far better if you were to either print that information out immediately or log it to a file. Like that, you can diagnose problems rather than having to take wild guesses…
There are other problems too.
You wrap the kill with nohup, but that's truly unnecessary as sending a signal is almost instantaneous and you don't want the extra complexities.
You're passing passwords as command line arguments. That's insecure because any process can read the entire command line passed to any program. It's better to pull the password from a file that is named on the command line; then only you (and root) can read the password (which is OK) if you set the permissions right.
You use lrange when extracting command line arguments; that's… almost certainly wrong (the lindex command is a better choice, or possibly even lassign to extract many values at once, provided it's running in Tcl 8.5 or later).
You're hard-coding the process ID. That's almost certainly not what you want to do, as it's totally likely to be different over time.
Sorting through that little lot, I get this possible improved script:
#!/usr/bin/expect -f
# A more robust method for handling arguments than you had...
if {$argc == 0} {
error "usage: $argv0 pid ?passFile?"
}
set pid [lindex $argv 0]
if {$argc > 1} {
set passwordFile [lindex $argv 1]
} else {
# Sensible default
set passwordFile ~/.codPassword
}
# Read the password from the file
set f [open $passwordFile]
gets $f pssword
close $f
# Run the program (doesn't need the 'cd') with trap to supply password,
# and connect to user for error passthrough
spawn su - kod -c kill $pid
expect_background {
"Password:" {exp_send "$pssword\r"}
}
interact {eof close}
Thinking about this further, a long time after the question was posed, the other problem is this:
spawn su - kod -c cd cod4 -c "nohup kill 7938" > /dev/null 2>&1 &
That's extremely likely to be doing something strange and unexpected. Instead, you probably need this:
spawn su -c "cd cod4; nohup kill 7938 > /dev/null 2>&1 &" - kod
That's because you want to pass all that lot as a single shell script to run inside the shell that su runs.

Automating telnet session using Bash scripts

I am working on automating some telnet related tasks, using Bash scripts.
Once automated, there will be no interaction of the user with telnet (that is, the script will be totally automated).
The scripts looks something like this:
# execute some commands on the local system
# access a remote system with an IP address: 10.1.1.1 (for example)
telnet 10.1.1.1
# execute some commands on the remote system
# log all the activity (in a file) on the local system
# exit telnet
# continue with executing the rest of the script
There are two problems I am facing here:
How to execute the commands on the remote system from the script (without human interaction)?
From my experience with some test code, I was able to deduce that when telnet 10.1.1.1 is executed, telnet goes into an interactive session and the subsequent lines of code in the script are executed on the local system. How can I run the lines of code on the remote system rather than on the local one?
I am unable to get a log file for the activity in the telnet session on the local system. The stdout redirect I used makes a copy on the remote system (I do not want to perform a copy operation to copy the log to the local system). How can I achieve this functionality?
While I'd suggest using expect, too, for non-interactive use the normal shell commands might suffice. telnet accepts its command on stdin, so you just need to pipe or write the commands into it through heredoc:
telnet 10.1.1.1 <<EOF
remotecommand 1
remotecommand 2
EOF
(Edit: Judging from the comments, the remote command needs some time to process the inputs or the early SIGHUP is not taken gracefully by telnet. In these cases, you might try a short sleep on the input:)
{ echo "remotecommand 1"; echo "remotecommand 2"; sleep 1; } | telnet 10.1.1.1
In any case, if it's getting interactive or anything, use expect.
Write an expect script.
Here is an example:
#!/usr/bin/expect
#If it all goes pear shaped the script will timeout after 20 seconds.
set timeout 20
#First argument is assigned to the variable name
set name [lindex $argv 0]
#Second argument is assigned to the variable user
set user [lindex $argv 1]
#Third argument is assigned to the variable password
set password [lindex $argv 2]
#This spawns the telnet program and connects it to the variable name
spawn telnet $name
#The script expects login
expect "login:"
#The script sends the user variable
send "$user "
#The script expects Password
expect "Password:"
#The script sends the password variable
send "$password "
#This hands control of the keyboard over to you (Nice expect feature!)
interact
To run:
./myscript.expect name user password
Telnet is often used when you learn the HTTP protocol. I used to use that script as a part of my web scraper:
echo "open www.example.com 80"
sleep 2
echo "GET /index.html HTTP/1.1"
echo "Host: www.example.com"
echo
echo
sleep 2
Let's say the name of the script is get-page.sh, then this will give you an HTML document:
get-page.sh | telnet
I hope this will be helpful to someone ;)
This worked for me..
I was trying to automate multiple telnet logins which require a username and password. The telnet session needs to run in the background indefinitely since I am saving logs from different servers to my machine.
telnet.sh automates telnet login using the 'expect' command. More info can be found here: http://osix.net/modules/article/?id=30
telnet.sh
#!/usr/bin/expect
set timeout 20
set hostName [lindex $argv 0]
set userName [lindex $argv 1]
set password [lindex $argv 2]
spawn telnet $hostName
expect "User Access Verification"
expect "Username:"
send "$userName\r"
expect "Password:"
send "$password\r";
interact
sample_script.sh is used to create a background process for each of the telnet sessions by running telnet.sh. More information can be found in the comments section of the code.
sample_script.sh
#!/bin/bash
#start screen in detached mode with session-name 'default_session'
screen -dmS default_session -t screen_name
#save the generated logs in a log file 'abc.log'
screen -S default_session -p screen_name -X stuff "script -f /tmp/abc.log $(printf \\r)"
#start the telnet session and generate logs
screen -S default_session -p screen_name -X stuff "expect telnet.sh hostname username password $(printf \\r)"
Make sure there is no screen running in the backgroud by using the
command 'screen -ls'.
Read
http://www.gnu.org/software/screen/manual/screen.html#Stuff to read
more about screen and its options.
'-p' option in sample_script.sh
preselects and reattaches to a specific window to send a command via
the ‘-X’ option otherwise you get a 'No screen session found' error.
You can use expect scripts instaed of bash.
Below example show how to telnex into an embedded board having no password
#!/usr/bin/expect
set ip "<ip>"
spawn "/bin/bash"
send "telnet $ip\r"
expect "'^]'."
send "\r"
expect "#"
sleep 2
send "ls\r"
expect "#"
sleep 2
send -- "^]\r"
expect "telnet>"
send "quit\r"
expect eof
The answer by #thiton was helpful but I wanted to avoid the sleep command. Also telnet didn't exit the interactive mode, so my script got stuck.
I solved that by sending telnet command with curl (which seems to wait for the response) and by explicitly telling telnet to quit like this:
curl telnet://10.1.1.1:23 <<EOF
remotecommand 1
remotecommand 2
quit
EOF
Following is working for me...
put all of your IPs you want to telnet in IP_sheet.txt
while true
read a
do
{
sleep 3
echo df -kh
sleep 3
echo exit
} | telnet $a
done<IP_sheet.txt
#!/bin/bash
ping_count="4"
avg_max_limit="1500"
router="sagemcom-fast-2804-v2"
adress="192.168.1.1"
user="admin"
pass="admin"
VAR=$(
expect -c "
set timeout 3
spawn telnet "$adress"
expect \"Login:\"
send \"$user\n\"
expect \"Password:\"
send \"$pass\n\"
expect \"commands.\"
send \"ping ya.ru -c $ping_count\n\"
set timeout 9
expect \"transmitted\"
send \"exit\"
")
count_ping=$(echo "$VAR" | grep packets | cut -c 1)
avg_ms=$(echo "$VAR" | grep round-trip | cut -d '/' -f 4 | cut -d '.' -f 1)
echo "1_____ping___$count_ping|||____$avg_ms"
echo "$VAR"
Use ssh for that purpose. Generate keys without using a password and place it to .authorized_keys at the remote machine. Create the script to be run remotely, copy it to the other machine and then just run it remotely using ssh.
I used this approach many times with a big success. Also note that it is much more secure than telnet.
Here is how to use telnet in bash shell/expect
#!/usr/bin/expect
# just do a chmod 755 one the script
# ./YOUR_SCRIPT_NAME.sh $YOUHOST $PORT
# if you get "Escape character is '^]'" as the output it means got connected otherwise it has failed
set ip [lindex $argv 0]
set port [lindex $argv 1]
set timeout 5
spawn telnet $ip $port
expect "'^]'."
Script for obtain version of CISCO-servers:
#!/bin/sh
servers='
192.168.34.1
192.168.34.3
192.168.34.2
192.168.34.3
'
user='cisco_login'
pass='cisco_password'
show_version() {
host=$1
expect << EOF
set timeout 20
set host $host
set user $user
set pass $pass
spawn telnet $host
expect "Username:"
send "$user\r"
expect "Password:"
send "$pass\r"
expect -re ".*#"
send "show version\r"
expect -re ".*-More-.*"
send " "
expect -re ".*#"
send "exit\r"
EOF
}
for ip in $servers; do
echo '---------------------------------------------'
echo "$ip"
show_version $ip | grep -A3 'SW Version'
done
Here is a solution that will work with a list of extenders. This only requires bash - some of the answers above require expect and you may not be able to count on expect being installed.
#!/bin/bash
declare -a Extenders=("192.168.1.48" "192.168.1.50" "192.168.1.51")
# "192.168.1.52" "192.168.1.56" "192.168.1.58" "192.168.1.59" "192.168.1.143")
sleep 5
# Iterate the string array using for loop
for val in ${Extenders[#]}; do
{ sleep 0.2; echo "root"; sleep 0.2; echo "ls"; sleep 0.2; } | telnet $val
done
Play with tcpdump or wireshark and see what commands are sent to the server itself
Try this
printf (printf "$username\r\n$password\r\nwhoami\r\nexit\r\n") | ncat $target 23
Some servers require a delay with the password as it does not hold lines on the stack
printf (printf "$username\r\n";sleep 1;printf "$password\r\nwhoami\r\nexit\r\n") | ncat $target 23**

Resources