Unable to execute multiple commands + nested if in a for loop over ssh non-interactive session - bash

I am trying to run certain commands, also checking some using nested if conditions over non-interactive SSH connections to my servers in the list. But unfortunately, it hits with syntax errors.
$ for i in $(cat servers);do ssh -t -l root ${i} 'hostname && grep BACKUPENABLE /var/cpanel/backups/config && if [[ $(ss -tulnp | grep rpc) ]] ; then echo -ne "RPCbind service is running \n$(cat /etc/exports)\n"; IP=$(awk -F'(' '{print $1}' /etc/exports | awk '{print $2}'); if [[ $( ping ${IP} -c1 > /dev/null) ]] ; then echo "NFS server responds"; else echo "NFS server not responding"; fi ; else -ne "No RPC and NFS backup not found\n"; fi && ss -tulnp | grep -vE "ssh|cpsrvd|pure-ftp|exim|httpd|named|dovecot|mysqld|snmp|ntp|java|spamd|cpdavd|State|rpc" && if [ -x /usr/sbin/csf ]; then echo csf present;else echo csf not present;fi && yum -q check-update kernel' 2> /dev/null;echo;done
-bash: syntax error near unexpected token `('
when checking in debug mode
$ bash -x for i in $(cat servers);do ssh -t -l root ${i} 'hostname && grep BACKUPENABLE /var/cpanel/backups/config && if [[ $(ss -tulnp | grep rpc) ]] ; then echo -ne "RPCbind service is running \n$(cat /etc/exports)\n"; IP=$(awk -F'(' '{print $1}' /etc/exports | awk '{print $2}'); if [[ $( ping ${IP} -c1 > /dev/null) ]] ; then echo "NFS server responds"; else echo "NFS server not responding"; fi ; else -ne "No RPC and NFS backup not found\n"; fi && ss -tulnp | grep -vE "ssh|cpsrvd|pure-ftp|exim|httpd|named|dovecot|mysqld|snmp|ntp|java|spamd|cpdavd|State|rpc" && if [ -x /usr/sbin/csf ]; then echo csf present;else echo csf not present;fi && yum -q check-update kernel' 2> /dev/null;echo;done
-bash: syntax error near unexpected token `do'
But the interesting thing is when I run the set of commands on an individual server, it produces the expected result fine.
root#server [~]# hostname && grep BACKUPENABLE /var/cpanel/backups/config && if [[ $(ss -tulnp | grep rpc) ]] ; then echo -ne "RPCbind service is running \n$(cat /etc/exports)\n"; IP=$(awk -F'(' '{print $1}' /etc/exports | awk '{print $2}'); if [[ $( ping ${IP} -c1 > /dev/null) ]] ; then echo "NFS server responds"; else echo "NFS server not responding"; fi ; else -ne "No RPC and NFS backup not found\n"; fi && ss -tulnp | grep -vE "ssh|cpsrvd|pure-ftp|exim|httpd|named|dovecot|mysqld|snmp|ntp|java|spamd|cpdavd|State|rpc" && if [ -x /usr/sbin/csf ]; then echo csf present;else echo csf not present;fi && yum -q check-update kernel
server.host.com
BACKUPENABLE: 'no'
RPCbind service is running
/backups/serverpath IP.x.x.x(rw,sync,no_root_squash,no_subtree_check)
NFS server not responding
udp UNCONN 0 0 *:44892 *:*
udp UNCONN 0 0 *:2049 *:*
udp UNCONN 0 0 :::36718 :::*
udp UNCONN 0 0 :::2049 :::*
tcp LISTEN 0 64 :::2049 :::*
tcp LISTEN 0 64 *:2049 *:*
tcp LISTEN 0 64 :::45701 :::*
tcp LISTEN 0 64 *:38633 *:*
csf present
kernel.x86_64 2.6.32-754.29.1.el6 updates
Basically, I can't figure out where the problem lies. Is it SSH can't group them or break at any point of if conditions instead of running sequentially over a non-interactive session?.

Related

netcat - How do I synchronize my portscan and port listen scripts and how do i export all output to a .txt file?

My goal is to have a port listen script on Host A which listens on predefined ports using netcat.
While this script listens, the port scan script is running on Host B and checks if the ports on Host A or open or not.
The output of the scan script needs to be directed to a separate .txt file and must include all information from the nc -zvv output.
The listen script looks like this:
#!/bin/bash
# Define the four arrays of ports to listen on
ports_1st=(20 21 22 80 443)
ports_2nd=(2000 2001 2002 2003)
ports_3rd=(3000 3001 3002 3003)
ports_4th=(4000 4001 4002 4003)
# Sleep to allow the scan script to start and bind to all ports
echo "Start PortScan.sh as soon as port listen starts!"
sleep 5
echo -e "\n"
echo -e "\n"
# Countdown timer
echo "Start the port scan in:"
for i in 5 4 3 2 1; do
echo "$i..."
sleep 1
done
# Loop through each array of ports and use nc to listen on each port
echo "Listening on 1st ports..."
for ports in ports_1st; do
for port in "${!ports[#]}"; do
nc -l -k -p $port &
done
done
echo -e "\n"
echo "Listening on 1st ports completed"
echo -e "\n"
echo -e "\n"
echo "Listening on 2nd ports..."
for ports in ports_2nd; do
for port in "${!ports[#]}"; do
nc -l -k -p $port &
done
done
echo -e "\n"
echo "Listening on 2nd ports completed"
echo -e "\n"
echo -e "\n"
echo "Listening on 3rd ports..."
for ports in ports_3rd; do
for port in "${!ports[#]}"; do
nc -l -k -p $port &
done
done
echo -e "\n"
echo "Listening on 3rd ports completed"
echo -e "\n"
echo -e "\n"
echo "Listening on 4th ports..."
for ports in ports_4th; do
for port in "${!ports[#]}"; do
nc -l -k -p $port &
done
done
echo -e "\n"
echo "Listening on 4th ports completed"
echo -e "\n"
echo -e "\n"
# Wait for all background nc processes to complete
wait
echo "Portscan has been completed"
echo -e "\n"
echo -e "\n"
And the scanscript looks like this:
#!/bin/bash
# Define the range of ports to scan
ports_1st=(20 21 22 80 443)
ports_2nd=(2000 2001 2002 2003)
ports_3rd=(3000 3001 3002 3003)
ports_4th=(4000 4001 4002 4003)
# Define the IP of the target host
echo "Please enter the server IP of the target host!"
read target_host
echo -e "\n"
echo -e "\n"
# Define current date variable
current_date=$(date +"%Y-%m-%d")
# Loop through the range of ports and use nc to test for a response
echo "Scanning 1st ports..."
for ports in ports_1st; do
for port in "${ports[#]}"; do
nc -zvv -w 1 $target_host $port 2>&1 | tee -a Portscan_$current_date.txt
if [ $? -eq 0 ]; then
echo "Port $port is open" | tee -a Portscan_$current_date.txt
else
echo "Port $port is closed or connection timed out" | tee -a Portscan_$current_date.txt
fi
done
echo "1st port scan completed" | tee -a Portscan_$current_date.txt
echo -e "\n"
echo -e "\n"
echo "Scanning 2nd ports..."
for ports in ports_2nd; do
for port in "${ports[#]}"; do
nc -zvv -w 1 $target_host $port 2>&1 | tee -a Portscan_$current_date.txt
if [ $? -eq 0 ]; then
echo "Port $port is open" | tee -a Portscan_$current_date.txt
else
echo "Port $port is closed or connection timed out" | tee -a Portscan_$current_date.txt
fi
done
echo "2nd scan completed" | tee -a Portscan_$current_date.txt
echo -e "\n"
echo -e "\n"
echo "Scanning 3rd ports..."
for ports in ports_3rd; do
for port in "${ports[#]}"; do
nc -zvv -w 1 $target_host $port 2>&1 | tee -a Portscan_$current_date.txt
if [ $? -eq 0 ]; then
echo "Port $port is open" | tee -a Portscan_$current_date.txt
else
echo "Port $port is closed or connection timed out" | tee -a Portscan_$current_date.txt
fi
done
echo "3rd port scan completed" | tee -a Portscan_$current_date.txt
echo -e "\n"
echo -e "\n"
echo "Scanning 4th ports..."
for ports in ports_4th; do
for port in "${ports[#]}"; do
nc -zvv -w 1 $target_host $port 2>&1 | tee -a Portscan_$current_date.txt
if [ $? -eq 0 ]; then
echo "Port $port is open" | tee -a Portscan_$current_date.txt
else
echo "Port $port is closed or connection timed out" | tee -a Portscan_$current_date.txt
fi
done
echo "4th port scan completed" | tee -a Portscan_$current_date.txt
echo -e "\n"
echo -e "\n"
done
done
done
done
The problem is that the listen script seems to end instantly instead of waiting for all ports to be scanned and the portscan script does not put the proper output to the .txt file.
Do you have any ideas on how to fix this?
Side notes:
Netcat is mandatory to be used.
I am expecting to loop through each port mentioned in the listen script as well as in the scan script.
The scan script is supposed to prompt if the port is open or closed/timeout in a .txt file.

Linux telnet shell script

I'm trying to read multiple hosts and ports from a text file (ip.txt) and check if they are connected/failed to connect/timed out, and echo the responses to Telnet_Success.txt/Telnet_Failure.txt/Telnet_Refused.txt files
I have tried the following script, it simply shows all the connection results as failed, but when checking manually one by one, I find some of them connected. any help is appreciated.
Here is the script:
>Telnet_Success.txt
>Telnet_Refused.txt
>Telnet_Failure.txt
file=ip.txt
while read line ; do
ip=$( echo "$line" |cut -d ' ' -f1 )
port=$( echo "$line" |cut -d ' ' -f2 )
if telnet -c $ip $port </dev/null 2>&1 | grep -q Escape; then
echo "$ip $port Connected" >> Telnet_Success.txt
elif telnet -c $ip $port </dev/null 2>&1 | grep -q refused; then
echo "$ip $port Refused" >> Telnet_Refused.txt
else
echo "$ip $port Failed" >> Telnet_Failure.txt
fi
done < ${file}
I can't tell you exactly what's failing from the diagnostics you have provided, but it's definitely a problem that you attempt to call telnet multiple times - you could get different outcomes each time, producing bugs which are hard to troubleshoot. You also have some stylistic issues in your code.
Try this refactoring; see the inline comments.
>Telnet_Success.txt
>Telnet_Refused.txt
>Telnet_Failure.txt
# Why use a variable for something you only reference once anyway?
file=ip.txt
# Use the shell's field splitting facility
# Cope with missing final newline; see
# https://mywiki.wooledge.org/BashFAQ/001#My_text_files_are_broken.21__They_lack_their_final_newlines.21
while read -r ip port _ || [[ -n $port ]]; do
# Run telnet once, capture result for analysis
output=$(telnet -c "$ip" "$port" </dev/null 2>&1)
case $output in
*Escape*)
echo "$ip $port Connected" >> Telnet_Success.txt;;
*refused*)
echo "$ip $port Refused" >> Telnet_Refused.txt;;
*)
echo "$ip $port Failed" >> Telnet_Failure.txt;;
esac
# Always double quote file name variables, just in case
done < "${file}"
Hi looks like the telnet command is the culprit
should be "telnet ip port" not "telnet -c ip port"
file=ip.txt
while read line
do
ip=$( echo "$line" |cut -d ' ' -f1 )
port=$( echo "$line" |cut -d ' ' -f2 )
if telnet $ip $port </dev/null 2>&1 | grep -q Escape
then
echo "$ip $port Connected" >> Telnet_Success.txt
elif telnet $ip $port </dev/null 2>&1 | grep -q refused
then
echo "$ip $port Refused" >> Telnet_Refused.txt
else
echo "$ip $port Failed" >> Telnet_Failure.txt
fi
done < ${file}

Ping Script with filter

I have a text file with host names and IP addresses like so (one IP and one host name per row). The IP addresses and host names can be separated by a spaces and/or a pipe, and the host name may be before or after the IP address
10.10.10.10 HW-DL11_C023
11.11.11.11 HW-DL11_C024
10.10.10.13 | HW-DL12_C023
11.11.11.12 | HW-DL12_C024
HW-DL13_C023 11.10.10.10
HW-DL13_C024 21.11.11.11
HW-DL14_C023 | 11.10.10.10
HW-DL14_C024 | 21.11.11.11
The script below should be able to ping hosts with a common denominator e.g. DL13 (there are two devices and it will ping only those two). What am I doing wrong, as I simply can`t make it work?
The script is in the same directory as the data; I don`t get errors, and everything is formatted. The server is Linux.
pingme () {
hostfile="/home/rex/u128789/hostfile.txt"
IFS= mapfile -t hosts < <(cat $hostfile)
for host in "${hosts[#]}"; do
match=$(echo "$host" | grep -o "\-$1_" | sed 's/-//' | sed 's/_//')
if [[ "$match" = "$1" ]]; then
hostname=$(echo "$host" | awk '{print $2}')
ping -c1 -W1 $(echo "$host" | awk '{print $1}') > /dev/null
if [[ $? = 0 ]]; then
echo "$hostname is alive"
elif [[ $? = 1 ]]; then
echo "$hostname is dead"
fi
fi
done
}
Try adding these two lines to your code:
pingme () {
hostfile="/home/rex/u128789/hostfile.txt"
IFS= mapfile -t hosts < <(cat $hostfile)
for host in "${hosts[#]}"; do
echo "Hostname: $host" # <-------- ADD THIS LINE -------
match=$(echo "$host" | grep -o "\-$1_" | sed 's/-//' | sed 's/_//')
echo "...matched with $match" # <-------- ADD THIS LINE -------
if [[ "$match" = "$1" ]]; then
hostname=$(echo "$host" | awk '{print $2}')
ping -c1 -W1 $(echo "$host" | awk '{print $1}') > /dev/null
if [[ $? = 0 ]]; then
echo "$hostname is alive"
elif [[ $? = 1 ]]; then
echo "$hostname is dead"
fi
fi
done
}
Then when you run it, you should see a list of your hosts, at least.
If you don't then you're not reading your file successfully.
If you do, there's a problem in your per-host logic.
Congratulations! You've divided your problem into two smaller problems. Once you know which half has the problem, keep dividing the problem in half until the smallest possible problem is staring you in the face. You'll probably know the solution at that point. If not, add your findings to the question and we'll help out from there.
The original code doesn't handle the pipe separator or the possibly reversed hostname and IP address in the input file. It also makes a lot of unnecessary use of external programs (grep, sed, ...).
Try this:
# Enable extended glob patterns - e.g. +(pattern-list)
shopt -s extglob
function pingme
{
local -r host_denom=$1
local -r hostfile=$HOME/u128789/hostfile.txt
local ipaddr host tmp
# (Add '|' to the usual characters in IFS)
while IFS=$'| \t\n' read -r ipaddr host ; do
# Swap host and IP address if necessary
if [[ $host == +([0-9]).+([0-9]).+([0-9]).+([0-9]) ]] ; then
tmp=$host
host=$ipaddr
ipaddr=$tmp
fi
# Ping the host if its name contains the "denominator"
if [[ $host == *-"$host_denom"_* ]] ; then
if ping -c1 -W1 -- "$ipaddr" >/dev/null ; then
printf '%s is alive\n' "$host"
else
printf '%s is dead\n' "$host"
fi
fi
done < "$hostfile"
return 0
}
pingme DL13
The final line (call the pingme function) is just an example, but it's essential to make the code do something.
REX, you need to be more specific about your what IP's you are trying to get from this example. You also don't ping enough times IMO and your script is case sensitive checking the string (not major). Anyway,
First, check that your input and output is working correctly, in this example I'm just reading and printing, if this doesn't work fix permissions etc :
file="/tmp/hostfile.txt"
while IFS= read -r line ;do
echo $line
done < "${file}"
Next, instead of a function first try to make it work as a script, in this example I manually set "match" to DL13, then I read each line (like before) and (1) match on $match, if found I remove the '|', and then read the line into an array of 2. if the first array item is an a IP (contains periods) set it as the IP the other as hostname, else set the opposite. Then run the ping test.
# BASH4+ Example:
file="/tmp/hostfile.txt"
match="dl13"
while IFS= read -r line ;do
# -- check for matching string (e.g. dl13 --
[[ "${line,,}" =~ "${match,,}" ]] || continue
# -- We found a match, split out host/ip into vars --
line=$(echo ${line//|})
IFS=' ' read -r -a items <<< "$line"
if [[ "${items[0]}" =~ '.' ]] ;then
host="${items[1]}" ; ip="${items[0]}"
else
host="${items[0]}" ; ip="${items[1]}"
fi
# -- Ping test --
ping -q -c3 "${ip}" > /dev/null
if [ $? -eq 0 ] ;then
echo "$host is alive!"
else
echo "$host is toast!"
fi
done < "${file}"

Stop script if nc connetion succeded in bash

How can I stop my script if the connection with netcat was successful?
For example if Connection to 192.168.2.4 21 port [tcp/ftp] succeeded! I'm not sure what holds that string of text.
#!/bin/bash
#Find first 3 octets of the gateway and set it to a variable.
GW=$(route -n | grep 'UG[ \t]' | awk '{print $2}' | cut -c1-10)
#loop through 1 to 255 on the 4th octect
for octet4 in {1..255}
do
sleep .2
nc -w1 $GW$octet4 21
done
You can test for nc exit status.
Eg.:
nc -w1 $GW$octet4 21
[[ "$?" -eq 0 ]] && exit
If command nc succeeded and return zero exit status which is implicitly stored in $? shell variable, exit the script. Or use just break instead of exit if you want to just jump out of the loop.
You can use the return code from nc, then break when it is equal to 0. Here is an example script that iterates until it hits googles DNS server IP 8.8.8.8 then breaks.
#!/bin/bash
for i in {1..10}; do
sleep 1;
echo Trying 8.8.8.$i
nc -w1 8.8.8.$i 53
if [ $? == 0 ]; then
break
fi
done
Your script would look like this:
#!/bin/bash
#Find first 3 octets of the gateway and set it to a variable.
GW=$(route -n | grep 'UG[ \t]' | awk '{print $2}' | cut -c1-10)
#loop through 1 to 255 on the 4th octect
for octet4 in {1..255}
do
sleep .2
nc -w1 $GW$octet4 21
if [ $? == 0 ]
then
break
fi
done

Bash ping status script

I've done the following script
HOSTS="ns1.server.com ns2.server.com"
SUBJECT="Host Down"
for myHost in $HOSTS
do
count=$(ping -c 10 $myHost | grep 'received' | awk -F',' '{ print $2 }' | awk '{
print $1 }')
if [ $count -eq 0 ]; then
echo "Host : $myHost is down (ping failed) at $(date)" | sendEmail -f email (email address removed) -u "$SUBJECT" etc etc
fi
done
Run via cron every 5 minutes however when a host is down I will receive and email every 5 minutes reflecting this. What i'd like is to add the function so that it only emails me when the status has changed. ie if it's down I don't want it to send any further updates until it's up.
I think something like this can help:
#!/bin/bash
HOSTS="ns1.server.com ns2.server.com"
HOSTS="123.123.1.1 ns1.server.com"
SUBJECT="Host Down"
ping_attempts=1
down_hosts=down_hosts.txt
for myHost in $HOSTS
do
count=$(ping -c $ping_attempts $myHost | awk -F, '/received/{print $2*1}')
echo $count
if [ $count -eq 0 ]; then
echo "$myHost is down"
if [ $(grep -c "$myHost" "$down_hosts") -eq 0 ]; then
echo "Host : $myHost is down (ping failed) at $(date)"
echo "$myHost" >> $down_hosts
fi
else
echo "$myHost is alive"
if [ $(grep -c "$myHost" "$down_hosts") -eq 1 ]; then
echo "Host : $myHost is up (ping ok) at $(date)"
sed -i "/$myHost/d" "$down_hosts"
fi
fi
done
There is a good point in the comments that you might want to use an infinite loop. But as you have asked for something different, here you go:
HOSTS="ns1.server.com ns2.server.com"
SUBJECT="Host Down"
PATH_STATUS='/yourfolder/hoststatus_' # For example can be located in /tmp.
for myHost in $HOSTS; do
count=$(ping -c 10 "$myHost" | grep 'received' | awk -F',' '{ print $2 }' | awk '{ print $1 }')
[[ -f "$PATH_STATUS$myHost"]] && prevStatus=$(cat "$PATH_STATUS$myHost") || prevStatus='unknown'
[[ $count == 0 ]] && curStatus='down' || curStatus='up'
if [[ $curStatus != $prevStatus ]]; then
echo "$curStatus" > "$PATH_STATUS$myHost"
echo "Host : $myHost is $curStatus at $(date)" | sendEmail
fi
done

Resources