From c89480561913c8fdf474e9fb24bbd080a38368fe Mon Sep 17 00:00:00 2001 From: Kevin Alberts Date: Sat, 25 Apr 2020 02:30:29 +0200 Subject: [PATCH] Initial version --- .gitignore | 6 + lsof_spy.py | 263 ++++++++++++++++++++++++++++++++++++++++++++ requirements.txt | 1 + settings.py.default | 8 ++ 4 files changed, 278 insertions(+) create mode 100644 .gitignore create mode 100644 lsof_spy.py create mode 100644 requirements.txt create mode 100644 settings.py.default diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..fd023a7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +.idea/ +venv/ +*.pyc +settings.py +smbspy*.txt +apachespy*.txt diff --git a/lsof_spy.py b/lsof_spy.py new file mode 100644 index 0000000..b0d601b --- /dev/null +++ b/lsof_spy.py @@ -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 += "- {}: {} open\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 += " - {}\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 += "- {}: {} open\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 += "{} SMB has {} file(s) opened by " \ + "{} user(s) ({} active users).\n".format( + SERVER_NAME, smb_files, smb_users, smb_active + ) + final_result += smb_result +else: + final_result += "{} SMB has no open files or command failure.\n".format(SERVER_NAME) + +if http_result != "": + final_result += "{} HTTP has {} active transfer(s) by {} user(s).\n".format( + SERVER_NAME, http_files, http_users + ) + final_result += http_result +else: + final_result += "{} HTTP 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) diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..663bd1f --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +requests \ No newline at end of file diff --git a/settings.py.default b/settings.py.default new file mode 100644 index 0000000..0fe8568 --- /dev/null +++ b/settings.py.default @@ -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 = ""