Get value of variable passed to spawn_id in Expect - ssh

I've managed to write a simple expect script to backup multiple router configs. This works fine but takes some time slow when the list of devices grows.
I tried to find an a way to spawn SSH sessions simultaneously and came across an article that goes:
# spawn all connections
foreach conn $allconnections {
spawn telnet $conn
lappend spawn_id_list $spawn_id
}
# run expect script for all connections individually
foreach id $spawn_id_list {
# this is important - for unknown (to me) reasons
set spawn_id $id
send "your_send_message"
expect "your_expect_pattern"
}
This seems to work but I am now faced with another challenge in creating a backup file named after the actual device. When $conn is passed to spawn_id and I create the backup file based on $id_configs.txt, I only get "expN" where N is a number which increments as each line on the device list is read. I've been experimenting on how to get the actual $id value back to use as filename but have yet to find a solution.
Appreciate any input from you guys. Thanks in advance!

% set pid [spawn telnet xx.xx.xxx.xx]
spawn telnet xx.xx.xxx.xx
29353
% set pid
29353
The spawn returns process id of whatsoever program being spawned. You can save that in a variable and use it further.

Related

Not able to establish Oracle SQL session from within a BASH script

#!/bin/bash
#Oracle DB Info for NEXT
HOST="1.2.3.4"
PORT="5678"
SERVICE="MYDB"
DB_USER=$(whoami)
DB_PASS=$(base64 -d ~/.passwd)
DB_SCHEMA="my_db"
#Section for all of our functions.
function SQLConnection(){
sqlplus "$DB_USER"/"$DB_PASS"#"$HOST":"$PORT"/"$SERVICE"
}
function Connected(){
SQLConnection <<EOF
select sys_context('USERENV','SERVER_HOST') from dual;
EOF
}
function GetJMS(){
SQLConnection <<EOF
set echo on timing on lines 200 pages 100
select pd.destination from ${DB_SCHEMA}.pd_notification pd where pd.org_id = '$ORGID';
EOF
}
TODAY=$(date +"%A %B %d, %Y")
read -r -p $'\n\nWhat is the ORG ID? ' ORGID
read -r -p $'\n\nWhat is the REMOTE QUEUE MANAGER NAME? ' RQM
read -r -p $'\n\nWhat is the IP address of the REMOTE QUEUE MANAGER? ' CONN
read -r -p $'\n\nWhat is the PORT of the REMOTE QUEUE MANAGER? ' PORT
echo -en "* $(whoami)\n* $TODAY\n* MQ Setup $ORGID\n\nDEFINE +\n\tCHANNEL('$RQM.LQML') +\n\tCHLTYPE(SDR) +\n\tCONNAME('$CONN($PORT)') +\n\tXMITQ('BUF.2.$ORGID.XMQ')\n\tCHAUTH(TLS_RSA_WITH_AES_256_CBC_SHA256)\n\nDEFINE +\n\tCHANNEL('LQML.$RQM') +\n\tCHLTYPE(RCVR) +\n\tTRPTYPE(TCP)\n\nDEFINE +\n\tQLOCAL('$RQM') +\n\tTRIGDATA('LQML.$RQM') +\n\tINITQ('SYSTEM.CHANNEL.INITQ') +\n\tTRIGGER USAGE(XMITQ)\n\n" > ~/mqsetup.mqsc
CONNECTED=$(Connected | awk 'NR==16')
echo -en "\n\nHello From: $CONNECTED\n\n"
for JMSDESTINATION in $(GetJMS | awk 'NR>=16&&NR<=24{print $1}')
do
read -r -p $'\n\nWhich REMOTE QUEUE NAME matches with this ${JMSDESTINATION}?' RNAME
QDESC=$(echo "$JMSDESTINATION" | tr '.' ' ' | tr '[[:upper:]]' '[[:lower:]]')
echo -en "\n\nDEFINE +\n\tQR($JMSDESTINATION) +\n\t\tREPLACE DESCR('$ORGID $QDESC Queue') +\n\t\tREPLACE MAXDEPTH(5000) +\n\t\tXMITQ('BUF.2.$ORGID.XMQ') +\n\t\tRNAME('$RNAME') +\n\t\tRQMNAME('$RQM')" >> ~/mqsetup.mqsc
done
Here is the script I've built, hoping to automate the setup of IBM MQ Queues and Channels. My problem is that outside this script, I can establish an SQL Session without an issue, directly from the shell, provided I input the variables seen in the script. I can call the functions and everything returns just as I'd hope it would. When I run the exact same things from within the script, I get timeout errors ... the "Hello From" is blank, which tells me there is no DB connection.
I'm totally stumped as to why it all works great from outside the script, but inside it times out.
I appreciate the eyes and the help!
You're overwritng a variable value. You have this at the top of the script:
PORT="5678"
but then later on you do:
read -r -p $'\n\nWhat is the PORT of the REMOTE QUEUE MANAGER? ' PORT
which overwrites your 5678 value with whatever is entered there. That port may not be listening on the DB server at all, or may be doing something else, or if you don't enter a value it'll default to port 1521 when you connect. But either way the connection is going to fail, either quickly or slowly depending on the port state (e.g. slower maybe if a firewall blocks it).
If you test the connection by adding a Connected call before the read calls (as I initially did) then it seems to be working fine; but the connections after the reads don't work because port value it tries to connect to is now wrong.
Use a different name for the two variables, e.g. RQ_PORT for the second one - both in its read command and the subsequent creation of the ~/mqsetup.mqsc file.
You may also find it useful to add the -l flag to your SQL*Plus call so that if the connection fails for some reason it won't re-prompt for credentials, which in some circumstances can make the script appear to hang until you hit enter a few times.
Not directly relevant to the problem, but when automating anything like this I usually also use the -s flag to suppress the banners (which can vary between environments); and if you're only interested in capturing query output I'd usually set headings and/or pagination off, and feedback off, and generally set SQL*Plus up to generate as little noise as possible - it makes parsing out the interesting bits easier.

Expect gets stuck sometimes during login

I have the following script. Sometimes, it runs fine and others it gets stuck. What could be wrong here?
#!/usr/bin/env expect
# set Variables
set timeout 60
set ipaddr [lindex $argv 0]
# start telnet connection
spawn telnet $ipaddr
match_max 100000
# Look for user prompt
expect "username:*"
send -- "admin\r"
expect "password:?"
# Send pass
send "thisisthepass\n"
# look for WWP prompt
expect ">"
send "sendthiscommand\r"
expect ">"
send "exit\r"
interact
The script runs fine till the end, but sometimes it gets stuck during login. This behavior is present even with the same IP: for example, it may run 1 out of 5 tries for the same IP.
I have tried adding some sleep between sending of the user and password, but it's still the same. I have also tried without expect, by sending directly the password string after the user one but still the same: sometimes the script runs fine but others it asks again for the password as if it's incorrect...
username: admin
password:
username:
Things I would do:
change send "thisisthepass\n" to send "thisisthepass\r"
include exp_internal 1 somewhere at the top of your script, and see what is going on when you have a failed attempt
exp_internal 1 will enable debugging with lots of good information on what is going on with expect's pattern matching. You can share it here and I'll be glad to take a look at it.
Are you sure the password prompt has an extra character after it (your ? in expect "password:?". Is it always there? Any chance different devices have slightly different password prompts?

PHPUnit fixture for redis

I wonder if there is an established procedure to create redis fixtures for PHPUnit.
While I can think of at some ways to achieve this, it would be nice to have something standardized.
As I dislike the idea of working with those EOF files directly, I currently use
either a logfile where I run every line as a single command,
or a LUA script if I need to generate a lot of predicteable or random lines.
How are you testing scripts incorporating redis? Due to the speed of a local instance, I work with a clean testing instance and consider a mocking of redis clearly overhead.
How to record and replay redis commands in php
While waiting for your suggestions, I share mine.
To record a logfile of every command entered into redis, you can use the shell:
redis-cli monitor > redis.txt
Running while normal usage simply generates real-life fixture.
In case you wonder how to turn
"HSET" "foobar" "111" "1" ---> $redis->hset('foobar', '111', '1');
use
preg_match_all('/"([^\s]+)"/', $line, $matches);
$command_parts = $matches[1];
$command = array_shift($command_parts);
$command = strtolower(trim($command));
call_user_func_array(array($redis, $command), $command_parts);

proc_open interaction

Here's what I'm trying to achieve: open a shell (korn or bash, doesn't matter), from that shell, I want to open a ssh connection (ssh user#host). At some point it is likely to happen I will be prompted for either a password or I might be asked whether or not I'm sure I want to connect (offending keys).
Before anyone asks: yes, I am aware there is a plugin for ssh2 exec calls, but the servers I'm working on don't support it, and are unlikely to do so.
Here's what I've tried so far:
$desc = array(array('pipe','r'),array('pipe','w'));//used in all example code
$p = proc_open('ssh user#host',$desc,$pipes);
if(!is_resource($p)){ die('#!#$%');}//will omit this line from now on
sleep(1);//omitting this,too but it's there every time I need it
Then I tried to read console output (stream_get_contents($pipes[1])) to see what I have to pass next (either password, yes or return 'connection failed: '.stream_get_contents($pipes[1]) and proc_close $p.
This gave me the following error:
Pseudo-terminal will not be allocated because stdin is not a terminal.
So, I though ssh was called in the php:// io-stream context, seems a plausible explanation of the above error.
Next: I though about my first SO question and decided it might be a good idea to open a bash/ksh shell first:
$p = proc_open('bash',$desc,$pipes);
And take it from there, but I got the exact same error message, only this time, the script stopped running but ssh did run. So I got hopeful, then felt stupid and, eventually, desperate:
$p=proc_open('bash && ssh user#host',$desc,$pipes);
After a few seconds wait, I got the following error:
PHP Fatal error: Allowed memory size of 134217728 bytes exhausted (tried to allocate 133693440 bytes)
The Call Stack keeps bringing up the stream_get_contents line, even in my last desperate attempt:
#!/path/to/bin/php -n
<?php
$p = proc_open('bash && ssh user#host',array(array('pipe','r'),array('pipe','w')),$ps);
if (!is_resource($p))
{
die('FFS');
}
usleep(10);
fwrite($ps[0],'yes'."\n");
fflush($ps[0]);
usleep(20);
fwrite($ps[0],'password'."\n");
fflush($ps[0]);
usleep(20);
fwrite($ps[0],'whoami'."\n");
fflush($ps[0]);
usleep(2);
$msg = stream_get_contents($ps[1]);
fwrite($ps[0],'exit'."\n");
fclose($ps[0]);
fclose($ps[1]);
proc_close($p);
?>
I know, its a mess, a lot of fflush and redundancy, but the point is: I know this connection will first prompt me for offending keys, and then ask a password. My guess is the stream in $pipes[1] holds the ssh connection, hence it's content is huge. what I need then, is a pipe inside a pipe... is this even possible? I must be missing something, what good is a pipe if this isn't possible...
My guess is the proc_open command is wrong to begin with, (error: Broken pipe). But I really can't see any other way around the first error... any thoughts? Or follow up questions if the above rant isn't at all clear (which it probably isn't).
Before anyone asks: yes, I am aware there is a plugin for ssh2 exec
calls, but the servers I'm working on don't support it, and are
unlikely to do so.
There are actually two. The PECL module, which is a PITA that most servers don't have installed anyway and phpseclib, a pure PHP SSH2 implementation. An example of its use:
<?php
include('Net/SSH2.php');
$ssh = new Net_SSH2('www.domain.tld');
if (!$ssh->login('username', 'password')) {
exit('Login Failed');
}
echo $ssh->exec('pwd');
echo $ssh->exec('ls -la');
?>

More efficient way of looping over SSH in KSH?

I currently have the following lines of code in a script:
set -A ARRAY OPTION1 OPTION2 OPTION3 OPTION4
set -A matches
for OPTION in ${ARRAY[#]}; do
DIFF=$(ssh $USER#$host " diff $PERSONALCONF $PRESETS$OPTION" )
if [[ $DIFF == "" ]]; then
set -A matches"${matches[#]}" $OPTION
fi
done
Basically, I have a loop that goes through each element in a pre-defined array, connects to a remote server (same server each time), and then compares a file with a file as defined by the loop using the diff command. Basically, it compares a personal.conf file with personal.conf.option1, personal.conf.option2, etc. If there is no difference, it adds it to the array. If there is a difference, nothing happens.
I was wondering if its possible to execute this or get the same result (storing the matching files in an array ON THE HOST MACHINE, not the server that's being connected to) by way of only connecting once via SSH. I cannot store anything on the remote server, nor can I execute a remote script on that server. I can only issue commands via ssh (kind of a goofy setup). Currently, it connects as many times as there are options. This seems inefficient. If anyone has a better solution I'd love to hear it.
Several options:
You can use OpenSSH multiplexing feature (see ssh(1)).
Also, most shells will gladly accept a script to run over stdin, so you could just run something like
cat script.sh | ssh $HOST /bin/sh
Most scripting languages (Perl, Python, Ruby, etc.) have some SSH module that allows connection reuse:
#!/usr/bin/perl
use Net::OpenSSH;
my ($user, $host) = (...);
my #options = (...);
my #matches;
my $ssh = Net::OpenSSH->new("$user\#$host");
for my $option (#options) {
my $diff = $ssh->capture("diff $personal_conf $presets$option");
if ($ssh->error) {
warn "command failed: " . $ssh->error;
}
else {
push #matches, $option if $diff eq '';
}
}
print "#matches\n";