Initial version

This commit is contained in:
Kevin Alberts 2020-04-25 02:30:29 +02:00
commit c894805619
Signed by: Kurocon
GPG key ID: BCD496FEBA0C6BC1
4 changed files with 278 additions and 0 deletions

6
.gitignore vendored Normal file
View file

@ -0,0 +1,6 @@
.idea/
venv/
*.pyc
settings.py
smbspy*.txt
apachespy*.txt

263
lsof_spy.py Normal file
View file

@ -0,0 +1,263 @@
#!/usr/bin/env python3
import os
import subprocess
import sys
import requests
try:
from settings import *
except ImportError:
print("Loading of settings failed. Please check your settings (copy settings.py.default to settings.py)")
sys.exit(2)
# lsof output columns statics
FILE = HOST = 8
DEVICE = 3
TYPE = 4
PID = 1
def telegram_send(s):
context = {
'chat_id': TELEGRAM_CHAT_ID,
'text': s,
'parse_mode': 'HTML',
'disable_notification': True
}
r = requests.post("https://api.telegram.org/bot{}/sendMessage".format(TELEGRAM_BOT_KEY), data=context)
with open("/tmp/spitfire_smbspy_result.txt", 'w', encoding='utf=8') as f:
f.write("Result: {}, Text: {}".format(r.status_code, r.text))
###
# SMBD monitoring
###
smb_data = {}
smbd_output = ""
smb_result = ""
smb_users = 0
smb_active = 0
smb_files = 0
try:
smbd_output = subprocess.check_output(["lsof", "-c", "/^"+SMBD+"$/", "-Pcnt"], stderr=subprocess.DEVNULL).decode()
except subprocess.CalledProcessError as e:
# Error running process.
pass
conns = list(y.split() for y in filter(
lambda x: ("IPv4" in x or "IPv6" in x) and HOSTNAME in x and "smbd" in x, smbd_output
))
for conn in conns:
try:
pid = conn[PID]
except IndexError:
pid = "-1"
try:
hoststr = conn[HOST]
try:
host = hoststr.split("->")[1].split(":")[0]
except IndexError:
host = hoststr
except IndexError:
host = "Unknown"
smb_data[pid] = {'host': host, 'pid': pid, 'dirs': set(), 'files': set()}
# All lines which are in the base dir and which is a directory or a regular file
files = list(y.split() for y in filter(
lambda x: BASE_DIR in x and (("DIR" in x and "cwd" not in x) or "REG" in x), smbd_output
))
for file in files:
pid = file[PID]
entry = " ".join(file[FILE:])
if file[TYPE] == "DIR":
smb_data[pid]['dirs'].add(entry)
else:
smb_data[pid]['files'].add(entry)
# Reformat data to index on host instead of PID
host_data = {}
for data in smb_data.values():
host = data['host']
if host not in host_data.keys():
host_data[host] = {'pids': [data['pid']], 'dirs': set(data['dirs']), 'files': set(data['files'])}
else:
host_data[host]['pids'].append(data['pid'])
host_data[host]['dirs'].union(data['dirs'])
host_data[host]['files'].union(data['files'])
# Filter dirs to remove any dirs that are parents of another entry in the list (not useful)
for host, data in host_data.items():
olddirs = sorted(list(data['dirs']))
newdirs = []
for d in olddirs:
if not any(d in x for x in olddirs if d != x):
newdirs.append(d)
data['dirs'] = newdirs
for host in sorted(host_data.keys()):
data = host_data[host]
flen = len(data['files'])
dlen = len(data['dirs'])
plen = len(data['pids'])
smb_users += 1
amounts = ["{} connection(s)".format(plen)]
if dlen > 0:
amounts.append("{} dir(s)".format(dlen))
if flen > 0:
smb_active += 1
smb_files += flen
amounts.append("{} file(s)".format(flen))
if dlen == 0 and flen == 0:
amounts.append("Nothing")
smb_result += "- {}: <i>{} open</i>\n".format(host, ", ".join(amounts))
if flen > 0:
for f in sorted(data['files']):
smb_result += " - {}\n".format(f)
elif dlen > 0:
for d in sorted(data['dirs']):
smb_result += " <i>- {}</i>\n".format(d)
###
# HTTP Monitoring
###
http_data = {}
http_output = ""
http_result = ""
http_users = 0
http_files = 0
try:
http_output = subprocess.check_output(["lsof", "-c", "/^"+APACHE+"$/", "-Pcnt"], stderr=subprocess.DEVNULL).decode()
except subprocess.CalledProcessError as e:
# Error running process.
pass
conns = list(y.split() for y in filter(
lambda x: ("IPv4" in x or "IPv6" in x) and HOSTNAME in x and "apache2" in x, http_output
))
for conn in conns:
try:
pid = conn[PID]
except IndexError:
pid = "-1"
try:
hoststr = conn[HOST]
try:
host = hoststr.split("->")[1].split(":")[0]
except IndexError:
host = hoststr
except IndexError:
host = "Unknown"
http_data[pid] = {'host': host, 'pid': pid, 'files': set()}
# All lines which are in the base dir and which is a directory or a regular file
files = list(y.split() for y in filter(lambda x: BASE_DIR in x and "REG" in x, http_output))
for file in files:
pid = file[PID]
entry = " ".join(file[FILE:])
http_data[pid]['files'].add(entry)
# Reformat data to index on host instead of PID
http_host_data = {}
for data in http_data.values():
host = data['host']
if host not in http_host_data.keys():
http_host_data[host] = {'pids': [data['pid']], 'files': set(data['files'])}
else:
http_host_data[host]['pids'].append(data['pid'])
http_host_data[host]['files'].union(data['files'])
for host in sorted(http_host_data.keys()):
data = http_host_data[host]
flen = len(data['files'])
plen = len(data['pids'])
http_users += 1
amounts = ["{} connection(s)".format(plen)]
if flen > 0:
http_files += flen
amounts.append("{} file(s)".format(flen))
if flen == 0:
amounts.append("Nothing")
http_result += "- {}: <i>{} open</i>\n".format(host, ", ".join(amounts))
if flen > 0:
for f in sorted(data['files']):
http_result += " - {}\n".format(f)
# Construct final telegram string
final_result = ""
if smb_result != "":
final_result += "<b>{} SMB</b> has <b>{}</b> file(s) opened by " \
"<b>{}</b> user(s) (<b>{}</b> active users).\n".format(
SERVER_NAME, smb_files, smb_users, smb_active
)
final_result += smb_result
else:
final_result += "<b>{} SMB</b> has no open files or command failure.\n".format(SERVER_NAME)
if http_result != "":
final_result += "<b>{} HTTP</b> has <b>{}</b> active transfer(s) by <b>{}</b> user(s).\n".format(
SERVER_NAME, http_files, http_users
)
final_result += http_result
else:
final_result += "<b>{} HTTP</b> has no open files or command failure.".format(SERVER_NAME)
# Report to Telegram if changed
if os.path.isfile("/tmp/spitfire_smbspy_status.txt"):
with open('/tmp/spitfire_smbspy_status.txt', 'r', encoding='utf-8') as f:
old_str = "".join(f.readlines())
else:
old_str = ""
if old_str != final_result:
with open('/tmp/spitfire_smbspy_status.txt', 'w', encoding='utf=8') as f:
f.write(final_result)
telegram_send(final_result)
# Copy results to SpitfireSeries
import shutil, stat
try:
shutil.copyfile("/tmp/spitfire_smbspy_status.txt", "/var/www/spitfireseries/spitfire_smbspy_status.txt")
os.chmod("/var/www/spitfireseries/spitfire_smbspy_status.txt", 0o666)
except Exception as e:
import syslog
syslog.syslog("Could not copy SMB status to SpitfireSeries: {}".format(e))
# Report to Nagios
limits = {
'users': [10, 20],
'files': [20, 50]
}
if smb_active + http_users >= limits['users'][1] or smb_files + http_files >= limits['files'][1]:
status = "CRITICAL"
return_code = 2
elif smb_active + http_users >= limits['users'][0] or smb_files + http_files >= limits['files'][0]:
status = "WARNING"
return_code = 1
else:
status = "OK"
return_code = 0
# Construct final monitoring string
monitoring_result = "{}: ".format(status)
if smb_result != "":
monitoring_result += "{} files opened on SMB ({}/{} users), ".format(smb_files, smb_active, smb_users)
else:
monitoring_result += "SMB no open files or failure, ".format(SERVER_NAME)
if http_result != "":
monitoring_result += "{} active HTTP transfers ({} users).".format(http_files, http_users)
else:
monitoring_result += "HTTP no open files or failure.".format(SERVER_NAME)
# Print monitoring string and exit with proper exit code
print(monitoring_result)
sys.exit(return_code)

1
requirements.txt Normal file
View file

@ -0,0 +1 @@
requests

8
settings.py.default Normal file
View file

@ -0,0 +1,8 @@
SERVER_NAME = "SomeServer"
SMBD = "smbd"
APACHE = "apache2"
BASE_DIR = "/media/share_root"
HOSTNAME = "some.hostname.nl"
TELEGRAM_BOT_KEY = ""
TELEGRAM_CHAT_ID = ""