Notify only when IP changes - bash

I use this script to log ext IP changes
host myip.opendns.com resolver1.opendns.com | sed -n -e 's/^\(.*\)\(myip.opendns.com has address \)/\1/p' | sed "s/^/`date` /" » /DataVolume/shares/Public/MyIP.txt
How can I create an alert (maybe creating a separate log file or sending a mail) only when IP changes?

Something along these lines:
#!/bin/bash
previp=
while :; do
ip=$(host myip.opendns.com resolver1.opendns.com |
sed -n '/.* has address \(.*\)/ { s//\1/; p; q; }' )
if [[ $previp != "$ip" ]]; then
msg="$(date): IP change from '$previp' to '$ip'"
echo "$msg" >> logfile
mail -s "IP change" somebody#somewhere <<< "$msg"
previp=$ip
fi
sleep 60
done
If you want it to run one time only (from crontab, for example):
#!/bin/bash
ipfile='/tmp/previous_ip'
ip=$(host myip.opendns.com resolver1.opendns.com |
sed -n '/.* has address \(.*\)/ { s//\1/; p; q; }' )
if ! [[ -f $ipfile ]]; then
echo "$ip" > "$ipfile"
fi
read -r previp < "$ipfile"
if [[ $previp != "$ip" ]]; then
msg="$(date): IP change from '$previp' to '$ip'"
echo "$msg" >> logfile
mail -s "IP change" somebody#somewhere <<< "$msg"
echo "$ip" > "$ipfile"
fi

Related

Why SNMP or SSH prevents the while to loop from iterating?

I'm writing a code in order to capture information about routers thanks to SSH and SNMP. I work on network with 4 different router configurations. In my script bash, a while loop read line by line the IP address of routers then try to know the configuration and finally capture information about my router.
To do so, I use a while loop but it only run for one IP address...
I assume that the problem comes from SNMP or SSH.
I tried the while loop just with
echo $line
The while loop :
while IFS= read -r line
do
echo
echo "line : $line"
echo
typeRouter=""
version=""
# type multitech V4.1
type=$(snmpget -v3 -u Ai -l authpriv -a SHA -A "pass" -x AES -X "pass" "$line" 1.3.6.1.4.1.995.14.1.1.8.3.0 | cut -d\" -f2 | cut -d. -f1 )
if [[ "$type" == "4" ]]; then
typeRouter=2
version=4
fi
# type sierra
type=$(snmpget -v3 -u Ai -l authpriv -a SHA -A "pass" -x AES -X "pass" "$line" iso.3.6.1.4.1.20542.9.1.1.9.7.0 | cut -d\" -f2 )
if [[ "$type" == "LX40" ]]; then
typeRouter=3
fi
case $typeRouter in
2)
echo "Mutlitech"
# version 4.1 et ultérieur
MultiV4 "$line" "$txtfile" "pass" "pass"
;;
3) # TODO PASSWORD
echo "Sierra"
Sierra "$line" "$txtfile" "pass" "pass"
;;
*)
echo
echo "Unknown router model, IP : $line"
echo
;;
esac
done < "file"
The function called
MultiV4 () {
# "$1" : #IP
# $2 : txtfile
# "$3" : mdp snmp
# $4 : mdp ssh
# Files
txtfile=$2
model="Multitech"
MACtxt="ephemere/mac.txt"
MacAddressRouter=$(snmpget -v3 -u Ai -l authpriv -a SHA -A "$3" -x AES -X "$3" "$1" iso.3.6.1.4.1.995.14.1.1.5.1.0 | cut -d\" -f2)
echo "Router MAC address : $MacAddressRouter"
sshpass -p "$4" ssh -o StrictHostKeyChecking=no admin#"$1" "cat /proc/net/arp" > "$MACtxt"
rssiPacket=$(snmpget -v3 -u Ai -l authpriv -a SHA -A "$3" -x AES -X "$3" "$1" iso.3.6.1.4.1.995.14.1.1.3.5.0 | cut -d: -f2 | cut -d\" -f2 | tr -d dBm)
echo "RSSI packet : $rssiPacket"
firmware=$(snmpget -v3 -u Ai -l authpriv -a SHA -A "$3" -x AES -X "$3" "$1" iso.3.6.1.4.1.995.14.1.1.3.4.0 | cut -d: -f2 | cut -d\" -f2)
echo "Firmware version : $firmware"
echo "$MacAddressRouter", "$MacAddressPlayer", "$rssiPacket", "$model", "$firmware", "$imei", "$SIMStatus", "$operator", "$connectionType", "$IPaddress", "$SINR", "$RSRP", "$RSRQ" >> "$txtfile"
}
My file
10.252.144.138
10.252.144.140
10.252.144.23
10.252.144.15
10.252.144.134
Can you help me why the loop running only one time ?
I solved the problem using
ssh -n
Go see this answer for more explanation : Shell script while read line loop stops after the first line

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}"

if statement function to many argumnets

I've overlooked my program for any mistakes and can't find any. Usually when I run into a mistake with BASH the interpreter is off on where the mistake is. I'm trying to customize this script from SANS InfoSec Using Linux Scripts to Monitor Security. Everything is fine until the part where the check function looks at the different protocols. When I uncomment them I get the error: ./report: line 41: [: too many arguments. Here is the program...
#!/bin/bash
if [ "$(id -u)" != "0" ]; then
echo "Must be root to run this script!"
exit 1
fi
##### CONSTANTS -
report=/home/chron/Desktop/report.log
#router=/home/chron/Desktop/router.log
red=`tput bold;tput setaf 1`
yellow=`tput bold;tput setaf 3`
green=`tput bold;tput setaf 2`
blue=`tput bold;tput setaf 4`
magenta=`tput bold;tput setaf 5`
cyan=`tput bold;tput setaf 6`
white=`tput sgr0`
##### FUNCTIONS -
pingtest() {
ping=`ping -c 3 localhost | tail -2`
loss=`echo $ping | cut -d"," -f3 | cut -d" " -f2`
delay=`echo $ping | cut -d"=" -f2 | cut -d"." -f1`
if [ "$loss" = "100%" ]; then
echo -n $red$1$white is not responding at all | mail -s'REPORT' localhost
echo 'You have mail in /var/mail!'
echo `date` $1 is not responding at all >> $report
elif [ "$loss" != "0%" ]; then
echo $yellow$1$white is responding with some packet loss
else
if [ "$delay" -lt 100 ]; then
echo $green$1$white is responding normally
else
echo $yellow$1$white is responding slow
fi
fi
}
check() {
if [ "$2" != "" -a "$2" $3 ] ; then
echo -n $green$1$white' '
else
echo -n $red$1$white' '
echo `date` $1 was not $3 >> $report
fi
}
##### __MAIN__ -
pingtest localhost # hostname or ip
echo "Server Configuration:"
check hostname `hostname -s` '= localhost'
check domain `hostname -d` '= domain.com'
check ipaddress `hostname -I | cut -d" " -f1` '= 10.10.0.6'
check gateway `netstat -nr | grep ^0.0.0.0 | cut -c17-27` '= 10.10.0.1'
echo
echo "Integrity of Files:"
check hostsfile `md5sum /etc/hosts | grep 7c5c6678160fc706533dc46b95f06675 | wc -l` '= 1'
check passwd `md5sum /etc/passwd | grep adf5a9f5a9a70759aef4332cf2382944 | wc -l` '= 1'
#/etc/inetd.conf is missing...
echo
#echo "Integrity of Website:"
#check www/index.html `lynx -reload -dump http://<LOCALIP> 2>&1 | md5sum | cut -d" " -f1 '=<MD5SUM>'
#echo
echo "Incoming attempts:"
#lynx -auth user:password -dump http://10.10.0.1 >> $router 2>&1
check telnet `grep \ 23$ $PWD/router.log | wc -l` '= 0'
check ftp `grep \ 21$ $PWD/router.log | wc -l` '= 0'
check ssh `grep \ 22$ $PWD/router.log | wc -l` '=0'
check smtp `grep \ 25$ $PWD/router.log | wc -l` '=0'
check dns `grep \ 53$ $PWD/router.log | wc -l` '=0'
echo
Some of the lines are commented out for later tweaking. Right now my problem is with the protocols. Not sure what's wrong because it looks like to me there are 3 arguments for the function.
In your last three calls to check, you are missing the required space between the operator and the operand.
check ssh `grep \ 22$ $PWD/router.log | wc -l` '=0'
check smtp `grep \ 25$ $PWD/router.log | wc -l` '=0'
check dns `grep \ 53$ $PWD/router.log | wc -l` '=0'
The final argument to all of these should be '= 0'.
However, this is not a good way to structure your code. If you really need to parameterize the comparison fully (all your calls use = as the operation), pass the operator as a separate argument. Further, written correctly, there is no need to pre-check that $2 is a non-empty string.
check() {
if [ "$2" "$3" "$4" ] ; then
printf '%s%s%s ' "$green" "$1" "$white"
else
printf '%s%s%s ' "$red" "$1" "$white"
printf '%s %s was not %s\n' "$(date)" "$1" "$3" >> "$report"
fi
}
Then your calls to check should look like
check hostname "$(hostname -s)" = localhost
check domain "$(hostname -d)" = domain.com
check ipaddress "$(hostname -I | cut -d" " -f1)" = 10.10.0.6
check gateway "$(netstat -nr | grep ^0.0.0.0 | cut -c17-27)" = 10.10.0.1
etc
Run your code through http://shellcheck.net; there are a lot of things you can correct.
Here is my other problem. I changed it up a bit just to see what's going on.
router=/home/chron/Desktop/router.log
check() {
if [ "$2" "$3" "$4" ]; then
printf "%s%s%s" "$green" "$1" "$white"
else
printf "%s%s%s" "$red" "$1" "$white"
printf "%s %s was not %s\n" "$(date)" "$1" $3" >> report.log
fi
check gateway "$(route | grep 10.10.0.1 | cut -c17-27)" = 10.10.0.1
check telnet "$(grep -c \ 23$ $router)" = 0
check ftp "$(grep -c \ 21$ $router)" = 0
check ssh "$(grep -c \ 22$ $router)" = 0
check smtp "$(grep -c \ 25$ $router)" = 0
check dns "$(grep -c \ 53$ $router)" = 0

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