Current File : //proc/thread-self/root/lib64/nagios/plugins/nccustom/check_openport.sh
#!/bin/bash
################################
# check iptables for open port #
#                              #
# Created by Bogdan Kukharskiy #
#          Namecheap           #
################################
# This script is for testing IPTABLES if the specified port is open (with the possibility to exclude users)
#
# Usage: "check_openport.sh [-x eXclude users(can be as a list separated by ',')] -p Port to check"
#
# Returns the Nagios native status codes:
# Nagios Status
#  0 = OK (Specified port is NOT open or opened only for users from excluded list)
#  1 = WARNING NO Warning states 
#  2 = CRITICAL (Specified port is OPENED, additionally shows users list OR specified port is not closed globally)
#  3 = UNKNOWN (Wrong usage)
#
# Speed optimized version


# Declaring arrays
DEFAULT_EXCLUDE_ARRAY=('root' 'cpanel' 'mailman' 'mailnull')
EXCLUDE_ARRAY=()
PORTS_ARRAY=()
OPEN_PORTS=()
UNPRIV_PORTS=()

## USAGE MESSAGE
usage() {
cat << EOF
usage: $0 options

This script is for testing IPTABLES if the specified port(s) is(are) open (with the possibility to exclude users).
Default excluded users:  ${DEFAULT_EXCLUDE_ARRAY[@]}
OPTIONS:
   -h Show this message
   -x eXclude users (optional, string (can be as a list separated by ',')
   -p Ports to check, integer number (can be as a list separated by ','), mandatory parameter

EOF
}


# Generate a mapping of UID to username
declare -A UID_TO_USERNAME
while IFS=: read -r uid username; do
    UID_TO_USERNAME["$uid"]="$username"
done < <(getent passwd)


function check_open_ports() {
    # let's check if the requested port is opened for someone (in ACCEPT dest)
    IS_ACCEPT=0
    for ARRSTR in "${ACCSTR_ARRAY[@]}"; do
        #PORTSTR=$(echo "$ARRSTR" | awk '{split($0,array,"--dports|--dport")} END{print array[2]}' | awk '{print$1}')
        IFS=' ' read -ra array <<< "${ARRSTR}" unset IFS
        array_index=0
        PORTSTR=""
        OWNER=""
        for a in "${array[@]}"; do
            if [[ ${a} =~ "--dport" ]]; then
                PORTSTR=${array[array_index+1]}
            fi
            if [[ ${a} =~ "id-owner" ]]; then
                OWNER=${array[array_index+1]}
            fi
            array_index=$((array_index+1))
        done
        if [[ ${#OWNER} -ne 0 ]]; then        #owner of the rule is not empty
            if [[ $OWNER =~ ^[0-9]+$ ]]; then #if iptables returned number uid
#               OWNER=$(id -u -n $OWNER 2>/dev/null)
                OWNER="${UID_TO_USERNAME[$OWNER]}"
            fi
            if [[ ${PORTSTR} == *":"* ]]; then    #result contains ":" what means we have to check the range of ports
                RANGESTART=$(echo "${PORTSTR}" | cut -d":" -f1)
                RANGEEND=$(echo "${PORTSTR}" | cut -d":" -f2)
                if ((PORT >= RANGESTART && PORT <= RANGEEND)); then
                    if ! [[ "${EXCLUDE_ARRAY[@]}" =~ ${OWNER} ]]; then #checking that user is NOT in excluded array
                        ALLOWED_ARRAY+=("${OWNER}")  #adding to array
                        IS_ACCEPT=1
                    fi
                fi;
            elif [[ ${PORTSTR} =~ ${RX} ]]; then
                if ! [[ "${EXCLUDE_ARRAY[@]}" =~ ${OWNER} ]]; then #checking that user is NOT in excluded array
                    ALLOWED_ARRAY+=("${OWNER}")  #adding to array
                    IS_ACCEPT=1
                fi
            fi
        fi
    done

    if [[ ${IS_ACCEPT} -eq 1 ]]; then
        return 5
    fi

    # let's check if the requested port is closed totally (in REJECTED dest)
    for REJECTSTR in "${REJECTSTR_ARRAY[@]}"; do
        #PORTSTR=$(echo "$REJECTSTR" | awk '{split($0,array,"--dports|--dport")} END{print array[2]}' | awk '{print$1}')
        IFS=' ' read -ra rarray <<< "${REJECTSTR}" unset IFS
        array_index=0
        PORTSTR=""
        for a in "${rarray[@]}"; do
            if [[ ${a} =~ "--dport" ]]; then
                PORTSTR=${rarray[array_index+1]}
                break
            else
                array_index=$((array_index+1))
            fi
        done
        if [[ ${PORTSTR} == *":"* ]]; then   #result contains ":" what means we have to check the range of ports
            RANGESTART=$(echo "${REJECTSTR}" | cut -d":" -f1)
            RANGEEND=$(echo "${REJECTSTR}" | cut -d":" -f2)
            if ((PORT >= RANGESTART && PORT <= RANGEEND)); then
                #OK, the port is blocked globally (at least in the REJECTed range ${REJECTSTR})
                return 0
            fi;
        elif [[ ${PORTSTR}  =~ ${RX} ]]; then
                #OK, the port is blocked globally (at least in the REJECTed array ${RESULTSTR})
            return 0
        fi
    done

    return 1
}


## FETCH ARGUMENTS
while getopts ":hx:p:" OPTION; do
        case "${OPTION}" in
                h)
                        usage
                        exit 3
                        ;;
                x)
                        IFS=, read -r -a EXCLUDE_ARRAY <<< "$OPTARG" unset IFS
                        ;;
                p)
                        IFS=, read -r -a PORTS_ARRAY <<< "$OPTARG" unset IFS
                        ;;
                ?)
                        usage
                        exit 3
                        ;;
                *)      echo "No reasonable options found!"
                        exit 3
                        ;;
        esac
done

## CHECK ARGUMENTS
if ! [[ ${#PORTS_ARRAY[@]} -eq 0 ]]; then
    for PORT in "${PORTS_ARRAY[@]}"; do
        if [ -z "${PORT}" ] || ! [[ ${PORT} =~ ^[0-9]+$ ]] ; then
                usage
                exit 3
        fi
    done
else
    usage
    exit 3
fi

## MAIN ROUTINE

EXCLUDE_ARRAY=("${DEFAULT_EXCLUDE_ARRAY[@]}" "${EXCLUDE_ARRAY[@]}") #combine default exclude array with readed
IPTABLES_OUTPUT=$(iptables -S)

RESULTSTR=$(echo "${IPTABLES_OUTPUT}" |grep -i "\-j REJECT")
if [ -z "${RESULTSTR}" ]; then
        echo "No REJECTs were found in IPTABLES"
        exit 2
fi

RESULTSTR=$(echo "${IPTABLES_OUTPUT}" |grep -i "\-j ACCEPT")
if [ -z "${RESULTSTR}" ]; then
        echo "No ACCEPTs were found in IPTABLES"
        exit 2
fi

REJECTSTR_ARRAY=()
ACCSTR_ARRAY=()

# Loop through the iptables output and filter relevant rules
while read -r line; do
    if [[ $line == *"-j REJECT"* && $line == *"dport"* ]]; then
        REJECTSTR_ARRAY+=("$line")
    elif [[ $line == *"-j ACCEPT"* && $line == *"dport"* ]]; then
        ACCSTR_ARRAY+=("$line")
    fi
done <<< "${IPTABLES_OUTPUT}"

if [[ ${#REJECTSTR_ARRAY[@]} -eq 0 ]]; then
        echo "No destination ports in REJECTs were found in IPTABLES"
        exit 2
elif [[ ${#ACCSTR_ARRAY[@]} -eq 0 ]]; then
    echo "No destination ports in ACCEPTs were found in IPTABLES"
    exit 2
fi

UNPRIV_DATA="CRITICAL! Unprivileged users with opened: "
for PORT in "${PORTS_ARRAY[@]}"; do
    ALLOWED_ARRAY=()
    RX="^${PORT}$|^${PORT},|,${PORT}$|,${PORT},"
    check_open_ports "${PORT}"
    RESULT=$?
    if [[ "${RESULT}" -eq 1 ]]; then
        OPEN_PORTS+=("${PORT}")
    elif [[ "${RESULT}" -eq 5 ]]; then
        UNPRIV_PORTS+=("${PORT}")
        USERS=$(echo "${ALLOWED_ARRAY[@]}" | tr ' ' ,)
        UNPRIV_DATA+="port: ${PORT} - ${USERS}. "
        unset ALLOWED_ARRAY
    fi
done

if ! [[ "${#UNPRIV_PORTS[@]}" -eq 0 ]]; then
        echo "${UNPRIV_DATA}"
        exit 2
elif ! [[ ${#OPEN_PORTS[@]} -eq 0 ]]; then   #some ports are not blocked globally (not in any of REJECTs)
        echo "CRITICAL! Ports ${OPEN_PORTS[*]} are NOT blocked globally (not in any of REJECTs)"
        exit 2
else            #there are no common users who have specified port opened and port is blocked globally
        echo "OK! Ports ${PORTS_ARRAY[*]} are closed globally and opened only for privileged users:"
        echo "${EXCLUDE_ARRAY[@]}"
        exit 0
fi