I’ve been looking for a way to scan my home network and alert me via email when a new device, by MAC address, is seen on my network. At one point I tried completing this with a shell script on my server using a flatfile database of known MAC addresses but either 1.) my shell-script-fu wasn’t strong enough to get the job done or 2.) I needed something more powerful, like python. Enter python.
Working at NASA I am surrounded by some incredibly intelligent people. So I decided to ask one of my co-workers to assist me with writing a python script. This is what we came up with. You can find his original script here: https://github.com/AdamFSU/Scripts
Here is what I worked out.
I have a CentOS 7 server running a postfix smtp relay. Here is the configuration for postfix I took from a RHCE 7 class I took on Linux Academy:
- Install necessary packages
yum install postfix cyrus-sasl cyrus-sasl-plain –y
- Edit the config: vim /etc/postfix/main.cf
relayhost = [smtp.mailserver.com]
example: relayhost = [smtp.gmail.com]:587
smtp_use_tls = yes
smtp_sasl_auth_enable = yes
smtp_sasl_password_maps = hash:/etc/postfix/sasl_passwd
smtp_tls_CAfile = /etc/ssl/certs/ca-bundle.crt
smtp_sasl_security_options = noanonymous
smtp_sasl_tls_security_options = noanonymous
inet_protocols = ipv4
inet_interfaces = loopback-only
mynetworks = 127.0.0.0/8
myorigin = $myhostname
mydestination =
local_transport = error: local delivery disabled
create a sasl password file specifically for Gmail:
cd /etc/postfix
vim sasl_passwd
[smtp.gmail.com]:587 username@gmail.com:password
Finish configuration of sasl password:
postmap sasl_passwd
chown root:postfix sasl_passwd
chmod 640 sasl_passwd
postmap sasl_passwd
- Enable and start necessary services
systemctl enable postfix && systemctl start postfix
Now, here are the additional requirements for the script:
Prerequisites:
- Required Python packages: python-nmap – To install run: python3 -m pip install python-nmap
- The $PYTHONPATH environment variable must be set: example: export PYTHONPATH=/usr/lib/python3/dist-packages/ For CentOS added to root’s .bashrc: export PYTHONPATH=/usr/local/lib/python3.6/site-packages/
- This script also uses the mail utility for sending email alerts – For CentOS: install mailx
yum install mailx -y - Flatfile called master_mac.txt with listing of known MAC addresses on the LAN
- The nmap utility
yum install nmap -y
Here is the python script:
#!/usr/bin/python3
# *** NOTE: THIS SCRIPT MUST BE RUN AS ROOT ***
# python module imports
import os
import shutil
import nmap
import sys
import subprocess
from datetime import datetime
# variable containing the filepath of the nmap scan results file
mac_list = os.environ['HOME'] + "/maclist.txt"
# variable containing the filepath of the approved mac addresses on the LAN file
masterfile = os.environ['HOME'] + "/master_mac.txt"
# If a file from previous nmap scans exists, create a backup of the file
if os.path.exists(mac_list):
shutil.copyfile(mac_list, os.environ['HOME'] + '/maclist_' + datetime.now().strftime("%Y_%m_%H:%M") + '.log.bk')
# Open a new file for the new nmap scan results
f = open(mac_list, "w+")
# Print to console this warning
print("Don't forget to update your network in the nmap scan of this script")
# This block of code will scan the network and extract the IP and MAC address
# Create port scanner object, nm
nm = nmap.PortScanner()
# Perform: nmap -oX - -n -sn 192.168.0.1/24
# *** NOTE: -sP has changed to -sn for newer versions of nmap! ***
# Change this to your network IP range
nm.scan(hosts='192.168.0.1/24', arguments='-n -sn')
# Retrieve results for all hosts found
nm.all_hosts()
# For every host result, write their mac address and ip to the $mac_list file
for host in nm.all_hosts():
# If 'mac' expression is found in results write results to file
if 'mac' in nm[host]['addresses']:
f.write(str(nm[host]['addresses']) + "\n")
# If 'mac' expression isn't found in results print warning
elif nm[host]['status']['reason'] != "localhost-response":
print("MAC addresses not found in results, make sure you run this script as root!")
# Close file for editing
f.close()
# Open file for reading
f = open(mac_list, "r")
# Read each line of file and store it in a list
mac_addresses = f.read().splitlines()
# Close file for reading
f.close()
# Open masterfile for reading
if os.path.isfile(masterfile):
f2 = open(masterfile, "r")
else:
print("Could not find master_mac file! Verify file path is correct!")
sys.exit()
# Read each line of file and store it in a list
master_mac_addresses = f2.read().splitlines()
# Close file for reading
f2.close()
# Create empty list of new devices found on network
new_devices = []
# For every list entry in the mac_addresses list
for i in mac_addresses:
# Convert the list index from a string to a dictionary so we can parse out mac address for comparison
dic = eval(i)
# Compare mac address portion of dictionary to mac addresses in the master_mac_addresses file
# If the scanned mac address is not in the master_mac_addresses file
if dic['mac'] not in master_mac_addresses:
# Add scanned mac address to new devices list
new_devices.append(dic)
# If the new_devices list isn't empty
if len(new_devices) != 0:
# output a warning to the console
warning = "\nWARNING!! NEW DEVICE(S) ON THE LAN!! - UNKNOWN MAC ADDRESS(ES): " + str(new_devices) + "\n"
print(warning)
# Create email notification of the warning
try:
# subject of email
subject = "WARNING, new device on LAN!"
# content of email
content = "New unknown device(s) on the LAN: " + str(new_devices)
# shell process of sending email with mutt
m1 = subprocess.Popen('echo "{content}" | mail -s "{subject}" youremail@gmail.com'.format(
content=content, subject=subject), shell=True)
# output whether email was successful in sending or not
print(m1.communicate())
# if sending of the email fails, this will output why
except OSError as e:
print("Error sending email: {0}".format(e))
except subprocess.SubprocessError as se:
print("Error sending email: {0}".format(se))
Next is to make sure your Gmail is setup correctly by making sure to enable less secure app access in Google account security. Then setup a crontab under root that looks something like this:
#scan the network every 10 minutes
*/10 * * * * /root/bin/nmapscan_wo_log.py > /dev/null 2>&1