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