Pitching a script to find improvements and suggestions: Firewall and Network Auditor
ProgrammingThis forum is for all programming questions.
The question does not have to be directly related to Linux and any language is fair game.
Notices
Welcome to LinuxQuestions.org, a friendly and active Linux Community.
You are currently viewing LQ as a guest. By joining our community you will have the ability to post topics, receive our newsletter, use the advanced search, subscribe to threads and access many other special features. Registration is quick, simple and absolutely free. Join our community today!
Note that registered members see fewer ads, and ContentLink is completely disabled once you log in.
Pitching a script to find improvements and suggestions: Firewall and Network Auditor
Hello all,
Recently I had to write a script to gather and audit some network info from Linux servers. I would appreciate suggestions and improvement tweaks and further inclusions to make the script more robust.
- Bellamkonda Sudhakar
The script itself:
Code:
#!/bin/bash
##################################################################
#
# Script to check / audit systems process using network resources
# and report a detailed info from various system file, process and
# outputs
#
##################################################################
#
# Author: Bellamkonda Sudhakar
# License: GNU GPL v3
##################################################################
VERSION=0.5
##################################################################
AUDITFL=`hostname -s`
CHKPID="/var/run/netaudchk-ppid"
FLWPID="/var/run/netaudchk-fpid"
TMPFD="/tmp/netadtchk"
TMPFL1="tmpfl1"
TMPFL2="tmpfl2"
TMPFL3="tmpfl3"
OPSYS=`uname -o`
OPKER=`uname -s`
OPDST=`uname -r`
HSTNM=$AUDITFL
FLTC="tcpflen"
FWTSTIN="fwtest.in"
FETSTOUT="fwtest.out"
HNIC=`lspci | grep -ic ethernet`
VNIC=`ip -d link | grep ^[0-9] | awk '{ print $2 }' | sed 's/://' | grep -vi "lo" | grep -vc "eth"`
LNIC=`ip -d link | grep ^[0-9] | awk '{ print $2 }' | sed 's/://' | grep -ic "lo"`
TNIC=`expr $HNIC + $VNIC + $LNIC`
IP4E=`ip -d addr | grep -ic inet`
IP6E=`ip -d addr | grep -ic inet6`
TTCP=`ss -t | wc -l`
TTCPL=`ss -t -l | wc -l`
TTCPC=`ss -t -a | wc -l`
IPFW=`cat /proc/sys/net/ipv4/ip_forward`
IPMQ=`cat /proc/sys/net/ipv4/ip_dynaddr`
IPPROC=""
MJR=${OPDST:0:1}
MIR=${OPDST:2:1}
FWILBIN="None"
FWON="Off"
FWIN="False"
FWOT="False"
FWFW="False"
FWMQ="False"
NMSR=`grep nameserver /etc/resolv.conf | grep -v ^# | head -1 | awk '{ print $2 } '`
DFGW=`ip route | grep default | sed 's/default\|proto\|static//g'`
###################################################################
# Functions
function lpnt {
for r in {1..80..1}
do
printf "%c" $1
done
printf "\n"
}
#
###################################################################
function cleanup {
rm -f $CHKPID
rm -f $FLWPID
}
###################################################################
function create_workspace {
mkdir $TMPFD
}
###################################################################
function remove_workspace {
rm -Rf $TMPFD
}
###################################################################
function pause {
echo -n "Press enter to continue..."
read
}
###################################################################
function line {
echo >> $TMPFD/$AUDITFL
}
###################################################################
#Main Program
if [ $UID -ne 0 ]
then
echo
echo "No root previleges"
exit 0
fi
if [ "$1" == "-c" ]
then
cleanup
echo "PID files removed"
exit 0
fi
if [ "$1" == "-h" ]
then
echo
echo "Usage: "
echo "netadtchk [option]"
echo " -a All"
echo " -h Help"
echo " -c Clear PID files"
echo " no option prints the summery information to the stdout and into the /tmp/netadtchk/$AUDITFL file."
echo
exit 0
fi
if [ -f $CHKPID ]
then
echo " Net Auditor running with PID `cat $CHKPID`"
echo " To clear the PID files use the -c option"
exit 0
else
echo $$ > $CHKPID
fi
#
#
if [ -d $TMPFD ]
then
remove_workspace
create_workspace
else
create_workspace
fi
#
case "$MJR" in
"2" )
case "$MIR" in
"0" | "1" )
FWILBIN="ipfwadm";
IPPROC="ip_[mfp][ao][rs][wqt][qha]*";;
"2" | "3" )
FWILBIN="ipchains";
IPPROC="ip_[fm][wa][cns]*";;
* )
FWILBIN="iptables";
IPPROC="ip_tables*";;
esac;;
"3" )
FWILBIN="iptables";
IPPROC="ip_table*";;
* )
FWILBIN="Unknown";;
esac
if [ $IP4E ]; then IPV="IPV4"; fi
if [ $IP6E ]; then IPV=$IPV"/IPV6"; fi
FWILPROC1=`ls /proc/net/$IPPROC 2>/dev/null | grep -c ip_tables`
FWILPROC2=`which $FWILBIN 2>/dev/null | grep -c $FWILBIN`
if [ $FWILPROC1 -ne 0 -a $FWILPROC2 -ne 0 ] ; then FWILSTAT="IP_Tables"; else FWILSTAT="None"; fi
#
if [ $FWILBIN == "iptables" ]
then
$FWILBIN -L | grep -i chain > $TMPFD/$TMPFL1
for C in `awk ' { print $2 } ' $TMPFD/$TMPFL1`
do
if [ $C == "INPUT" ]
then
if [ `$FWILBIN -L $C | grep -wc 'all\|tcp\|udp'` -gt 0 ] ; then FWIN="True"; fi
fi
if [ $C == "OUTPUT" ]
then
if [ `$FWILBIN -L $C | grep -wc 'all\|tcp\|udp'` -gt 0 ] ; then FWOT="True"; fi
fi
if [ $C == "FORWARD" ]
then
if [ `$FWILBIN -L $C | grep -wc 'all\|tcp\|udp'` -gt 0 ] ; then FWFW="True"; fi
fi
done
fi
#
FWR=`$FWILBIN -L | sed '/^$/d' | grep -iv chain | grep -ivc "target\|source\|destination"`
if [ $FWR -gt 0 -a $FWIN == "True" ] ; then FWON="On"; fi
#
printf "%12s %12s %20s %20s %9s\n" "Hostname" "OS" "Kernel" "Release" "IPV" >> $TMPFD/$AUDITFL
printf "%12s %12s %20s %20s %9s\n\n" "$AUDITFL" "$OPSYS" "$OPKER" "$OPDST" "$IPV" >> $TMPFD/$AUDITFL
#
printf "%15s%12s%20s%20s%25s\n" "FW Binary" "Forwarding" "Masquerading" "1st Name Srv" "Default Gateway" >> $TMPFD/$AUDITFL
printf "%15s%12s%20s%20s%25s\n\n" "$FWILBIN" "$IPFW" "$IPMQ" "$NMSR" "$DFGW" >> $TMPFD/$AUDITFL
#
printf "%14s%14s%14s%14s%14s\n" "FW Status" "FW In" "FW Out" "FW Forward" "FW Masq" >> $TMPFD/$AUDITFL
printf "%14s%14s%14s%14s%14s\n" "$FWON" "$FWIN" "$FWOT" "$FWFW" "$FWMQ" >> $TMPFD/$AUDITFL
#
printf "\n%15s %15s %20s\n" "Type" "Name" "IP Details" >> $TMPFD/$AUDITFL
for N in `ip -d link | grep ^[0-9] | awk '{ print $2 }' | sed 's/://'`
do
case "$N" in
"lo" )
TYPE="Loopback";;
"eth0" | "eth1" | "eth2" | "eth3" | "eth4" | "eth5" )
TYPE="Hardware";;
* )
TYPE="Virtual/Other"
esac
IPDET=`ifconfig $N | grep -w "inet"`
printf "%15s %15s %s\n" "$TYPE" "$N" "$IPDET" >> $TMPFD/$AUDITFL
done
line
echo "Routes:" >> $TMPFD/$AUDITFL
netstat -rn >> $TMPFD/$AUDITFL
line
ss -nl4 | awk ' { print $3 } ' | grep -iv local | sed 's/*/0.0.0.0/g' > $TMPFD/$TMPFL1
ss -nl6 | awk ' { print $3 } ' | grep -iv local | sed 's/\(.*\)\:/\1 /' > $TMPFD/$TMPFL2
sed -i 's/\:/ /g' $TMPFD/$TMPFL1
sort $TMPFD/$TMPFL1 > $TMPFD/$TMPFL3
sort $TMPFD/$TMPFL2 >> $TMPFD/$TMPFL3
echo "This system has `expr $TTCPL - 1` Listening Sockets (IPv4/IPv6) at TCP/Ports: " >> $TMPFD/$AUDITFL
cp $TMPFD/$TMPFL3 $TMPFD/$FLTC
#
#
for P in `awk ' { print $2 } ' $TMPFD/$FLTC`
do
PP=$PP$P,
lsof -i tcp:$P | grep LISTEN | awk -v P=$P -v TMPFL=$TMPFD/$TMPFL3 ' { printf " %12s %12s %20s\n", P, $1, $2 >> TMPFL } '
done
echo "$PP details" >> $TMPFD/$AUDITFL
sort -g -k3 $TMPFD/$TMPFL3 > $TMPFD/$TMPFL2
awk -v TMPFL3=$TMPFD/$TMPFL3 ' BEGIN { }
{ if(cf != $2)
printf "%12s %12s %20s\n", $1, $2, $3 > TMPFL3
cf = $2 }
END { } ' $TMPFD/$TMPFL2
line
lsof -i tcp:$PP | grep LISTEN | sort -d > $TMPFD/$TMPFL2
awk -v TMPFL1=$TMPFD/$TMPFL1 ' BEGIN { }
{ if(cf != $1)
print $1, $2, $9 > TMPFL1
cf = $1 }
END { } ' $TMPFD/$TMPFL2
printf "%5s %10s %8s %30s %20s %s\n" "PID" "Process" "Port" "As per /proc" "As per Which" "Working Dir" >> $TMPFD/$AUDITFL
for ID in `awk ' { print $2 } ' $TMPFD/$TMPFL1`
do
PROCEXE=`ls -l --full-time /proc/$ID/exe | awk ' { print $11 } '`
PROCPWS=`ls -l --full-time /proc/$ID/cwd | awk ' { print $11 } '`
PROC=`awk -v P=$ID ' { if (P == $2) print $1 } ' $TMPFD/$TMPFL1`
WPROC=`which 2>/dev/null $PROC`
P=`awk -v ID=$ID ' BEGIN { } { if (ID == $3) print $1 } END { } ' $TMPFD/$TMPFL3`
printf "%5s %10s %8s %30s %20s %s\n" "$ID" "$PROC" "$P" "-$PROCEXE" "-$WPROC" "-$PROCPWS" >> $TMPFD/$AUDITFL
done
line
netstat -tulnp | grep LISTEN | awk ' { print $4, $7 } ' | sed 's/\(.*\)\:/\1 /' | sed 's/\// /g' > $TMPFD/$TMPFL1
printf "%20s %8s %8s %20s\n" "IP" "PORT" "PID" "PROCESS" >> $TMPFD/$AUDITFL
awk ' { printf "%20s %8s %8s %20s\n", $1, $2, $3, $4 } ' $TMPFD/$TMPFL1 >> $TMPFD/$AUDITFL
line
lpnt "#" >> $TMPFD/$AUDITFL
##################################################################
## Print details from Audit file
clear
cat $TMPFD/$AUDITFL
echo "Complete Information is stored at $TMPFD/$AUDITFL"
if [ "$1" == "-a" ]
then
echo "All TCP Listening Process Details" >> $TMPFD/$AUDITFL
netstat -tunlp | grep -v udp >> $TMPFD/$AUDITFL
line
echo "ALL UDP Listening Process Details" >> $TMPFD/$AUDITFL
netstat -tunlp | grep -v tcp >> $TMPFD/$AUDITFL
line
echo "Current established connections - NETSTAT" >> $TMPFD/$AUDITFL
netstat -tunap >> $TMPFD/$AUDITFL
line
echo "Current established connections - SS" >> $TMPFD/$AUDITFL
ss -o state established >> $TMPFD/$AUDITFL
line
lpnt "#" >> $TMPFD/$AUDITFL
fi
#
cleanup
exit 0
#
###################################################################
Click here to see the post LQ members have rated as the most helpful post in this thread.
That's a lot of code to comment on and it looks pretty tidy; two reasons why nobody has commented?
As said, it's tidy code but some things you might like to consider:
$( ... ) is recommended over ` ... ` for reasons explained here.
[[ ... ]] is recommended over [ ... ] for reasons explained here.
A common but not universal convention is to use upper case variable names for environment variables and lower case names for the rest.
Some of the command output string manipulation looks as if it could be simplified. For example
Code:
ip -d link | grep ^[0-9] | awk '{ print $2 }' | sed 's/://' | grep -vi "lo" | grep -vc "eth"
... can be simplified to
Code:
ip -d link | grep -c '^[0-9]*: eth'
Errors conventionally result in a message to stderr and a non-zero return code. If you regard it as an error, this code
Code:
if [ $UID -ne 0 ]
then
echo
echo "No root previleges"
exit 0
fi
could be changed to
Code:
if [ $UID -ne 0 ]
then
echo "No root privileges" >&2
exit 1
fi
The single command line option could more conveniently be handled using a case statement, here including an error trap for an invalid option and assuming usage is moved to a function which gives verbose output if called with -v (and otherwise could suggest using the -h option):
More complex command line parsing, facilitating trapping invalid extra arguments is best handled by getopts.
Purely a matter of style, no right and no wrong way; it is more compact to use
Code:
if [[ <test expression> ]];then
<commands>
fi
Again purely a matter of style, no right and no wrong way; you may prefer, instead of
Code:
if [ $IP4E ]; then IPV="IPV4"; fi
to use
Code:
[[ $IP4E ]] && IPV="IPV4"
... but beware; [[ <test expression> ]] && <commands 1> || <commands 2> can behave unexpectedly (if <commands 1> set a non zero return code, <commands 2> is executed).
If strings do not contain anything to be substituted by bash, they may be in single quotes, not double (for example "IPV4" above).
Aliases can make what are intended to be external commands behave unexpectedly. ls is often aliased and so troublesome. Safest for scripts to unset all aliases with unalias -a
In case the script's name is changed, the usage message could use ${0##*/} for its name.
The usage message could be done with a single echo (but is not then so easy to indent prettily):
Code:
echo '
Usage: netadtchk [-a | -h | -c]
-a All
-h Help. Prints this usage message and exits
-c Clear PID files"
If no option is given, prints the summary information to the stdout and into the /tmp/netadtchk/$AUDITFL file
'
Note: the -a option is not serviced in the script.
I apologize - I need to learn quite q few things. Bear with me .
Still reading script - and catkin's insight was good. I am unsure about command
Code:
uname -o
I mean script if script is already meant for *nix why would you need that - that too if you intend to use it just once. I think a static value would have been good enough.
I would appreciate suggestions and improvement tweaks and further inclusions to make the script more robust.
Moved: This thread is more suitable in the Programming forum since you're only interested in robustness (I would first look at existing functionality tools like GNU Tiger, LSAT or OpenVAS provide) and has been moved accordingly to help your thread/question get the exposure it deserves.
You have a lot of unquoted variables in your script. While this is ok as long as their contents don't contain spaces or other reserved characters, if for some reason this should change, then you're going to have trouble.
You should get into the habit of always quoting variable expansions by default, then you only have to worry about those few times when they need to be unquoted.
You make four individual calls to "ip". It might be more efficient if you call "id -d addr" only once and store its entire output into a variable. Then you could simply parse that for all the values you want.
As catkin pointed out, you have lots of useless uses of grep and related tools. If your command uses sed or awk, then you already have grep-like ability built in and generally shouldn't need it. Also, don't forget that grep and sed have -e options allowing you to apply multiple expressions at once.
Finally, always quote your expressions to avoid conflicts with shell grammar.
The < redirector works stand-alone inside a command substitution, so bash can import a file into a variable without using cat. And please use $(), as catkin mentioned. It's so much more readable and flexible.
I believe the above is plenty enough for you to digest at the moment, so my only addition is a personal view.
The use of a function is generally tied to reducing the need to repeat the same lines of code several times.
You have functions like create_workspace which ultimately are only required at one point in the entire script,
so I probably would not have worried about creating a function for this example.
The use of a function is generally tied to reducing the need to repeat the same lines of code several times.
Another reason for a function is to make the code more comprehensible; it has been suggested that no section of code should be more than X lines where X is what is visible on a screen (a lot bigger than it used to be!). Like all such "rules" it is a judgement call.
that's a big WOW, Thanks a lot, in fact you can say that my BASH scripting knowledge is a bit of an ancient relic and I have used some of the suggestions in other scripts that i have been writing. I guess that I will rewrite the script and revise it with all there in mind.
I appreciate all you guys and especially Catkin and David the H.
@asimba : so that i can use this script in different *nix other than linux, i.e. freebsd etc. hopefully.
LinuxQuestions.org is looking for people interested in writing
Editorials, Articles, Reviews, and more. If you'd like to contribute
content, let us know.