#!/bin/bash # $Id: scan,v 1.14 2004/07/04 04:14:46 jlarsen Exp $ # # Description: bash script that scans a user selected range of IP addresses. # It tries pinging each address and reports back which are active. # #------------------------------------------------------------------------------- # get_version. Function that parses script version and date info from the RCS # Id line. function get_version () { local func_name=get_version if [ ! $SCRIPT_VER ]; then cat << EOF > /tmp/$$.version \$Id: scan,v 1.14 2004/07/04 04:14:46 jlarsen Exp $ EOF SCRIPT_VER=`awk '{print $3}' < /tmp/$$.version` SCRIPT_VER="v$SCRIPT_VER" SCRIPT_DATE=`awk '{print $4}' < /tmp/$$.version` rm -f /tmp/$$.version fi } # get_version #------------------------------------------------------------------------------ # display_debug_help: Function to display debug help. function display_debug_help () { local func_name=display_debug_help if [ $(($D & 0x1)) -ne 0 ]; then echo "[$LINENO]$func_name:$$> Inside display_debug_help";fi cat << EOF | more ----- Debug Help ($SCRIPT_VER - $SCRIPT_DATE) --------------------------------------- Debug is enabled in one of three ways in decending order of precedence: "-d 0xNNNN" on command line PT_DEBUG_FLAGS environment variable "debug_flags" file in same directory as pt Debug output can be changed "on the fly" by changing the contents of "debug_flags", which is read each time pt goes through its while loop. Each debug line is qualified with a construct like the following: if [ \$((\$D & 0x1)) -ne 0 ]; then echo "[\$LINENO]\$func_name:\$\$> Debug message or action";fi The expression on the left of -ne is a bash construct. The $((expr)) gets evaluated and returns a value. The expression I'm using is "$D & 0xNN". The & performs a bitwise AND and returns the result. If it is zero (no matching bits) then the debug line is skipped. If it is non zero then the debug line is performed. Each debug line can have multiple bits that turn it on or just a single bit. The pattern "0xNN" is the collection of bits that turn the debug on. The action of each bit is defined below. 00000000 - No debug 00000001 - Enable all the "Inside function name" debug lines 00000002 - Enable process_command_line outputs 00000004 - Enable ping_ip debug output 80000000 - Don't output the "Debug flags changed" messages in "set_debug_level" EOF } # display_debug_help #------------------------------------------------------------------------------ # set_debug_level: Function that sets the $D variable based on a file, an #environment variable, or a command line variable. See display_debug_help. function set_debug_level () { local func_name=set_debug_level # Note: Command line -d overrides environment variable SCAN_DEBUG_FLAGS if it exists, which overrides debug_flags file if it exists # Check one time if SCAN_DEBUG_FLAGS env variable exists and use it if it does if [ "${ENV_VAR_TESTED:=FALSE}" = "FALSE" ]; then ENV_VAR_TESTED=TRUE D=${SCAN_DEBUG_FLAGS:-0} if [ "$D" != "0" ] && [ $(($D & 0x80000000)) -eq 0 ]; then echo "[$LINENO]$$> ${func_name:-startup}: set_debug_level: Debug flags changed to: $D"; fi fi # Check the existence of a file named "debug_flags" and use it if conditions are right if [ -e debug_flags ]; then if [ "${DEBUG_FLAGS_FILE:=FIRST_READ}" = "FIRST_READ" ]; then # Getting here means the debug_flags file has never been read. DEBUG_FLAGS_FILE=`cat -s debug_flags` if [ "$D" = "0" ]; then # Getting here means $D was zero and the debug_flags contents should be used instead since env variable doesn't exist or has value of 0 D=$DEBUG_FLAGS_FILE if [ "$D" != "0" ] && [ $(($D & 0x80000000)) -eq 0 ]; then echo "[$LINENO]$$> ${func_name:-startup}: set_debug_level: Debug flags changed to: $D"; fi fi else # Read debug_flags file and update $D if the file has changed NEW_DEBUG_FLAGS_FILE=`cat -s debug_flags` if [ $NEW_DEBUG_FLAGS_FILE != $DEBUG_FLAGS_FILE ]; then DEBUG_FLAGS_FILE=$NEW_DEBUG_FLAGS_FILE D=$DEBUG_FLAGS_FILE if [ $(($D & 0x80000000)) -eq 0 ]; then echo "[$LINENO]$$> ${func_name:-startup}: set_debug_level: Debug flags changed to: $D" fi fi fi fi } # set_debug_level #------------------------------------------------------------------------------- # setup_env. Function that determines what OS the script is running on and # setups environment variables accordingly. function setup_env () { local func_name=setup_env if [ $(($D & 0x1)) -ne 0 ]; then echo "[$LINENO]$func_name:$$> Inside setup_env";fi # Figure out which OS you're running on and setup program paths accordingly CHECK_OS=`uname | sed -n -e 's/SunOS.*/SunOS/p' ` if [ "$CHECK_OS" = "SunOS" ]; then #if [ $CHECK_OS = SunOS ]; then # Solaris paths HOST=`/usr/ucb/hostname` LS=/bin/ls MAIL=/usr/ucb/mail PING=/usr/sbin/ping PS=/bin/ps TRACE_ROUTE=/usr/sbin/traceroute ZIP_PGM=/usr/bin/zip else # Mandrake Linux paths HOST=`/bin/hostname` LS=/bin/ls MAIL=/bin/mail PING=/bin/ping PS=/bin/ps TRACE_ROUTE=/usr/sbin/traceroute ZIP_PGM=/usr/bin/zip fi # Set the default values of all environment variables here FILE_NAME=/dev/null START_IP=not_entered END_IP=not_entered LIVE_IPS=0 DEAD_IPS=0 IP_A=0 IP_B=0 IP_C=0 IP_D=0 YES=1 NO=0 TRUE=1 FALSE=0 ACTIVE=0 DEAD=1 TIMED_OUT=255 TIME_OUT_CNT=2 GOOD_IP=0 BAD_IP=1 } # setup_env #------------------------------------------------------------------------------ # Function to display help function display_help () { local func_name=display_help if [ $(($D & 0x1)) -ne 0 ]; then echo "[$LINENO]$func_name:$$> Inside display_help";fi cat << EOF ----- scan ($SCRIPT_VER - $SCRIPT_DATE) -------------------------------------------- usage: scan start_IP end_IP [ options ] Each IP address is tested between start_IP and end_IP. Live IP addresses are reported. Options: -f name Filename for output (Default is standard out) -h This help screen -hd Debug help -to n Ping timeout count in seconds (Default 2) EOF } # display_help #------------------------------------------------------------------------------ # clock - Function that prints a changing pattern to a spot on the screen # $1 - "first" means the first time function is called function clock () { clock[0]="/" clock[1]="-" clock[2]="\\" clock[3]="|" if [ ${1:-not_first} = "first" ]; then clock_cnt=0 printf "|" fi if [ $clock_cnt -eq 4 ]; then clock_cnt=0 fi printf "\b%s" ${clock[$clock_cnt]} let clock_cnt=clock_cnt+1 } # clock #------------------------------------------------------------------------------ # process_command_line function process_command_line () { local func_name=process_command_line if [ $(($D & 0x1)) -ne 0 ]; then echo "[$LINENO]$func_name:$$> Inside process_command_line";fi while [ $# -ne 0 ] do case $1 in -d) #debug mode # Command line -d overrides environment variable SCAN_DEBUG_FLAGS if it exists, which overrides debug_flags file if it exists D=$2 if [ "$D" != "0" ] && [ $(($D & 0x80000000)) -eq 0 ]; then echo "[$LINENO]$func_name:$$> Debug flags changed to: $D"; fi if [ $(($D & 0x2)) -ne 0 ]; then echo "[$LINENO]$func_name:$$> In case -d"; fi shift ;; -f) if [ $(($D & 0x2)) -ne 0 ]; then echo "[$LINENO]$func_name:$$> In case -f"; fi FILE_NAME=$2 shift ;; -h) #show help if [ $(($D & 0x2)) -ne 0 ]; then echo "[$LINENO]$func_name:$$> In case -h"; fi display_help exit ;; -hd) #show debug help if [ $(($D & 0x2)) -ne 0 ]; then echo "[$LINENO]$func_name:$$> In case -hd"; fi display_debug_help exit ;; -to) if [ $(($D & 0x2)) -ne 0 ]; then echo "[$LINENO]$func_name:$$> In case -to"; fi TIME_OUT_CNT=$2 shift ;; *.*.*.*) if [ $(($D & 0x2)) -ne 0 ]; then echo "[$LINENO]$func_name:$$> In case *.*.*.*"; fi if [ $START_IP = "not_entered" ]; then START_IP=$1 else END_IP=$1 fi ;; *.*.*) if [ $(($D & 0x2)) -ne 0 ]; then echo "[$LINENO]$func_name:$$> In case *.*.*"; fi display_help echo "ERROR: Incomplete IP address: $1" exit ;; *.*) if [ $(($D & 0x2)) -ne 0 ]; then echo "[$LINENO]$func_name:$$> In case *.*"; fi display_help echo "ERROR: Incomplete IP address: $1" exit ;; *) #IP address if [ $(($D & 0x2)) -ne 0 ]; then echo "[$LINENO]$func_name:$$> In case *"; fi display_help echo "ERROR: Incomplete IP address: $1" exit ;; -*) # Unknown command line argument if [ $(($D & 0x2)) -ne 0 ]; then echo "[$LINENO]$func_name:$$> In case -*"; fi display_help echo "ERROR: Unrecognized command line argument: $1" exit ;; esac # Don't shift command line if processing at last argument if [ $# -ne 0 ]; then shift fi done } # process_command_line #------------------------------------------------------------------ # ping_ip: Function that pings an IP # $1: IP to ping # $2: Timeout value (default: 2 seconds) # $RC_PING_IP has the value $GOOD_IP or $BAD_IP function ping_ip () { local func_name=ping_ip if [ $(($D & 0x1)) -ne 0 ]; then echo "[$LINENO]$func_name:$$> Inside ping_ip (IP: $1 Timeout: ${2:-2})";fi # This local function is what gets timed function _ping_ip() { if [ "$CHECK_OS" = "SunOS" ]; then # Solaris line PING_RESULT=`$PING -s -n $1 56 1` else # Linux line PING_RESULT=`$PING -q -c 1 $1` fi # Check for success. Don't check for failure because it fails in different # ways hiding a dropped connection. PING_STATUS=`echo $PING_RESULT | sed -n -e 's/.*, 0% packet loss.*/ping_good/p' ` if [ $(($D & 0x100)) -ne 0 ]; then print "\n" echo "[$LINENO]$func_name:$$> PING_RESULT: $PING_RESULT" echo "[$LINENO]$func_name:$$> PING_STATUS: $PING_STATUS" print "\n" fi # Process the results of the ping if [ "$PING_STATUS" = "ping_good" ]; then # Increment the good ping counters echo "$GOOD_IP" >| scan.ret_val else echo "$BAD_IP" >| scan.ret_val fi } # _ping_ip # Initialize the file that gets tested to detect timeout condition # The timed_function overwrites this with numerical return value when it succeeds echo "TIMED_OUT" >| scan.ret_val # Start the timed_function as a subshell in the background (_ping_ip $1 ) & # Allow up to $2 seconds for timed_function to complete but default to 2 seconds local count=0 local max_count=${2:-2} while [ $count -lt $max_count ]; do ret_val=`cat scan.ret_val` if [ "$ret_val" != "TIMED_OUT" ]; then rm -f scan.ret_val RC_PING_IP=$ret_val return $ret_val fi let count=count+1 if [ $(($D & 0x100)) -ne 0 ]; then printf "."; fi sleep 1 clock done # Getting here means timed_function timed out. Kill the subshell $! kill -n 9 $! if [ $(($D & 0x100)) -ne 0 ]; then printf "\n" echo "[$LINENO]$func_name:$$> ping_ip -I $1 timed out" fi rm -f scan.ret_val RC_PING_IP=$BAD_IP return $BAD_IP } # ping_ip #------------------------------------------------------------------ # scan_ips: This function does the IP scanning function scan_ips () { local func_name=scan_ips if [ $(($D & 0x1)) -ne 0 ]; then echo "[$LINENO]$func_name:$$> Inside scan_ips";fi # Load the loop counters based on the starting IP address PARSE_IP=`echo $START_IP | sed -n -e 's/\./ /gp'` IP_A=`echo $PARSE_IP | awk '{print $1}'` IP_B=`echo $PARSE_IP | awk '{print $2}'` IP_C=`echo $PARSE_IP | awk '{print $3}'` IP_D=`echo $PARSE_IP | awk '{print $4}'` TEST_IP="$IP_A.$IP_B.$IP_C.$IP_D" start_time=`date +%Y\-%b\-%d\ %H\:%M\:%S\ %a` echo "----- scan ($SCRIPT_VER - $SCRIPT_DATE) ---------------------------------------" | tee $FILE_NAME echo "Test range: $START_IP to $END_IP" | tee -a $FILE_NAME echo "Ping timeout value: $TIME_OUT_CNT seconds" | tee -a $FILE_NAME echo "The following IP addresses respond to a ping:" | tee -a $FILE_NAME printf "%15s " $TEST_IP clock first local col_cnt=0 while [ $TEST_IP != $END_IP ]; do ping_ip $TEST_IP $TIME_OUT_CNT 2>/dev/null if [ $RC_PING_IP -eq $GOOD_IP ]; then let LIVE_IPS=LIVE_IPS+1 if [ $col_cnt -eq 3 ]; then # Cleanup the screen by backing up and outputing a space printf "\b \n" # Output the IP to the file since it is alive printf "%15s\n" $TEST_IP >> $FILE_NAME col_cnt=0 else # Cleanup the screen by backing up and outputing a space printf "\b " # Output the IP to the file since it is alive printf "%15s " $TEST_IP >> $FILE_NAME let col_cnt=col_cnt+1 fi else let DEAD_IPS=DEAD_IPS+1 # Since the IP is dead backup on the screen so it can be overwritten printf "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b" fi let IP_D=IP_D+1 if [ $IP_D -eq 255 ]; then IP_D=1 let IP_C=IP_C+1 if [ $IP_C -eq 256 ]; then IP_C=0 let IP_B=IP_B+1 if [ $IP_B -eq 256 ]; then IP_B=0 let IP_A=IP_A+1 if [ $IP_A -eq 256 ]; then echo "ERROR: Illegal IP address reached!" exit fi fi fi fi TEST_IP="$IP_A.$IP_B.$IP_C.$IP_D" # Output only to STDOUT the IP under test printf "%15s " $TEST_IP clock first done # Process the last IP address on its own ping_ip $TEST_IP $TIME_OUT_CNT 2>/dev/null if [ $RC_PING_IP -eq $GOOD_IP ]; then let LIVE_IPS=LIVE_IPS+1 if [ $col_cnt -eq 4 ]; then printf "\b \n" printf "%15s\n" $TEST_IP >> $FILE_NAME col_cnt=0 else printf "\b " printf "%15s\n" $TEST_IP >> $FILE_NAME let col_cnt=col_cnt+1 fi else let DEAD_IPS=DEAD_IPS+1 printf "\n" >> $FILE_NAME printf "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b " fi printf "\n" # Output to the file echo "Scan started: $start_time" | tee -a $FILE_NAME echo "Scan ended: `date +%Y\-%b\-%d\ %H\:%M\:%S\ %a`" | tee -a $FILE_NAME echo "Live IPs: $LIVE_IPS Dead IPs: $DEAD_IPS Total IPs scanned: `expr $LIVE_IPS + $DEAD_IPS`" | tee -a $FILE_NAME echo "--------------------------------------------------------------------------------" | tee -a $FILE_NAME } # scan_ips #------------------------------------------------------------------------------ # main function main () { local func_name=main if [ $(($D & 0x1)) -ne 0 ]; then echo "[$LINENO]$func_name:$$> Inside main";fi # If no arguments are passed then output the help screen and exit if [ $# -eq 0 ]; then display_help exit fi setup_env process_command_line $* # Check for START_IP on command line if [ $START_IP = not_entered ]; then display_help echo "ERROR: You must provide a starting IP address" exit fi # Check for END_IP on command line if [ $END_IP = not_entered ]; then display_help echo "ERROR: You must provide an ending IP address" exit fi # Make sure ending IP is greater than starting IP if [[ "$END_IP" < "$START_IP" ]]; then echo "WARNING: Starting IP $START_IP greater than ending IP $END_IP. Reversing IPs" temp_ip=$START_IP START_IP=$END_IP END_IP=$temp_ip fi # Perform the IP scan scan_ips # Remove the scan.ret_val file rm -f scan.ret_val } # main set_debug_level get_version main $*