#!/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([SUDO, "lsof", "-c", "/^"+SMBD+"$/", "-Pcnt"], stderr=subprocess.DEVNULL).decode().split("\n") 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([SUDO, "lsof", "-c", "/^"+APACHE+"$/", "-Pcnt"], stderr=subprocess.DEVNULL).decode().split("\n") 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)