#!/usr/bin/bash # check for nmap and jq # check for root if [ -f arpscan.json ] then tempjson=$(jq '.ips += {} | .ips=(.ips|to_entries|map_values(.value.online=false)|from_entries)' arpscan.json) if [ $? -ne 0 ] then echo "Failed to load network JSON" 1>&2 exit 1 fi else tempjson={} fi jsonescape() { jq -Rs . < <(printf "%s" "${1}") } starttime="$(jsonescape "$(date '+%Y-%m-%dT%H:%M:%SZ')")" foundhost() { local currentip local mac currentip="$(jsonescape "${1}")" mac="$(jsonescape "${2}")" # remove all offline entries for this MAC tempjson=$(jq ".ips |= with_entries(if .value.mac==${mac} and .value.online==false then empty else . end)" <<< "${tempjson}") # add entry tempjson=$(jq "${currentip} as \$ip|.ips[\$ip].mac=${mac}|.ips[\$ip].online=true|.ips[\$ip].lastseen=${starttime}" <<< "${tempjson}") } doscan() { local nmaplog nmaplog=$(sudo nmap -sn -n "${1}") if [ $? -ne 0 ] then echo "nmap failed" 1>&2 return 1 fi cat <<< "${nmaplog}" > "nmaplog.$(sed 's/\//\./' <<< "${1}").txt" local parsestep local ipaddr parsestep=waitheader ipaddr= while read -r line do case $parsestep in waitheader) if [[ "${line}" != "Starting Nmap "* ]] then echo "Nmap start header expected, got ${line}" 1>&2 return 1 fi parsestep=hostdata ;; hostdata) if [[ "${line}" == "Nmap done: "* ]] then parsestep=scandone elif [[ "${line}" =~ ^Nmap\ scan\ report\ for\ (.*)$ ]] then ipaddr="${BASH_REMATCH[1]}" elif [[ "${line}" =~ ^Host\ is\ up(\ \(.*\))?.$ ]] then continue elif [[ "${line}" =~ ^MAC\ Address:\ ([A-F0-9:]{17}).*$ ]] then local macaddr macaddr="${BASH_REMATCH[1]}" if [[ "${ipaddr}" == "" ]] then echo "Found MAC without IP" 1>&2 return 1 fi foundhost "${ipaddr}" "${macaddr}" if [ $? -ne 0 ] then echo "Registering host failed" 1>&2 return 1 fi ipaddr= else echo "Failed to parse: ${line}" 1>&2 return 1 fi ;; scandone) echo "Unexpected data after scan: ${line}" 1>&2 return 1 ;; *) echo "Invalid parse step: ${parsestep}" 1>&2 return 1 ;; esac done <<< "$nmaplog" #done < nmaplog.txt } localifaces="$(ip -j addr | jq '[.[]|{ifname:.ifname,address:.address,addr_info:.addr_info[]}|select(.ifname!="lo")|select(.addr_info.prefixlen>=20)|select(.addr_info.family=="inet")|{ifname:.ifname,ip:.addr_info.local,prefix:.addr_info.prefixlen,mac:(.address|ascii_upcase)}]|map_values(.ip32=(.ip|split(".")|to_entries|map((.value|tonumber)*pow(256;3-.key))|add))|map_values(.subnet32=(.ip32/pow(2;32-.prefix)|floor)*pow(2;32-.prefix))|map_values(.subnet=([(.subnet32/pow(256;3)|floor%256),(.subnet32/pow(256;2)|floor%256),(.subnet32/256|floor%256),.subnet32%256]|join(".")))|map_values(del(.ip32)|del(.subnet32))')" for i in $(seq "$(jq 'length' <<< "${localifaces}")") do currentiface="$(jq ".[${i}-1]" <<< "${localifaces}")" foundhost "$(jq -r '.ip' <<< "${currentiface}")" "$(jq -r '.mac' <<< "${currentiface}")" subnet="$(jq -r '.subnet' <<< "${currentiface}")/$(jq '.prefix' <<< "${currentiface}")" echo "Scanning ${subnet}" doscan "${subnet}" if [ $? -ne 0 ] then echo "Scan failed" 1>&2 exit 1 fi done # remove old entries tempjson=$(jq ".ips |= with_entries(if (${starttime}|fromdate)-(.value.lastseen|fromdate)>(14*24*60*60) then empty else . end)" <<< "${tempjson}") ## make sure the MAC vendor and device/port fields exist #tempjson="$(jq '.macvendor+={}|.devices+=[]' <<< "${tempjson}")" if [ ! -f macvendor.json ] then echo '{}' > macvendor.json fi if [ ! -d devices ] then mkdir -p devices fi if [ ! -f devices/_example.json ] then cat < devices/_example.json { "desc": "Description of the device", "model": "Example Device Model", "type": "example", "user": "username", "ports": [ { "name": "port1", "mac": "00:00:00:00:00:00" } ] } EOF fi tempjson="$(jq '.ips=(.ips|to_entries|sort_by(.key|split(".")|to_entries|map((.value|tonumber)*pow(256;3-.key))|add)|from_entries)' <<< "${tempjson}")" fancyjson="$(jq --slurpfile macvendor macvendor.json --slurpfile devices <(cat devices/*.json) '. as $root|$macvendor[0]|to_entries|map({key:.value[],value:.key})|from_entries as $vendors|($devices|map(.*(.ports[]|with_entries(.key|="port_"+.))))|map_values(del(.ports))|map({key:.port_mac,value:.})|from_entries as $ports|$root.ips|map_values(.port=$ports[.mac]|.vendor=$vendors[.mac[0:8]])' <<< "${tempjson}")" jq -r '(["[?]","IP","MAC","Vendor","Model","User","Device","Port","Last seen"],(to_entries|sort_by(.key|split(".")|to_entries|map((.value|tonumber)*pow(256;3-.key))|add)|.[]|[if .value.online then "[#]" else "[ ]" end,.key,.value.mac,.value.vendor,.value.port.model,.value.port.user,.value.port.desc,.value.port.port_name,.value.lastseen]))|@tsv' <<< "${fancyjson}" | column -ts $'\t' cat <<< "${tempjson}" > arpscan.json