2021-01-14 19:49:54 +01:00
|
|
|
import csv
|
2021-02-09 20:44:19 +01:00
|
|
|
import logging
|
|
|
|
import os
|
2021-02-10 23:07:37 +01:00
|
|
|
import re
|
2021-02-09 20:44:19 +01:00
|
|
|
import sqlite3
|
|
|
|
from dataclasses import dataclass
|
2021-02-10 23:21:05 +01:00
|
|
|
from datetime import datetime, timedelta
|
2021-02-09 20:44:19 +01:00
|
|
|
from time import sleep
|
|
|
|
from typing import List, Optional
|
|
|
|
|
|
|
|
import click
|
|
|
|
import routeros_api
|
2021-02-10 23:07:37 +01:00
|
|
|
from jinja2 import Environment, select_autoescape, FileSystemLoader
|
|
|
|
|
|
|
|
from config import Config
|
2021-02-09 20:44:19 +01:00
|
|
|
|
|
|
|
|
|
|
|
def _get_db(filepath: str):
|
|
|
|
logging.debug(f"Opening database: {filepath}")
|
|
|
|
connection = sqlite3.connect(filepath)
|
|
|
|
|
|
|
|
c = connection.cursor()
|
|
|
|
c.execute(
|
|
|
|
'''CREATE TABLE IF NOT EXISTS spottings (
|
|
|
|
id INTEGER PRIMARY KEY,
|
|
|
|
ts INTEGER,
|
|
|
|
mac TEXT,
|
|
|
|
hostname TEXT,
|
|
|
|
ip TEXT
|
|
|
|
)'''
|
|
|
|
)
|
|
|
|
connection.commit()
|
|
|
|
|
|
|
|
return connection
|
|
|
|
|
|
|
|
|
2021-02-10 23:21:05 +01:00
|
|
|
def _fetch_leases(db, from_ts: datetime):
|
|
|
|
c = db.cursor()
|
|
|
|
query = c.execute(
|
|
|
|
'SELECT ts, mac, hostname, ip FROM spottings WHERE ts > ? ORDER BY ts DESC',
|
|
|
|
(int(from_ts.timestamp()),)
|
|
|
|
)
|
|
|
|
for row in query:
|
|
|
|
yield Lease(datetime.fromtimestamp(row[0]), row[1], row[2], row[3])
|
|
|
|
|
|
|
|
|
2021-02-09 20:44:19 +01:00
|
|
|
@dataclass
|
|
|
|
class Lease:
|
2021-02-10 23:21:05 +01:00
|
|
|
ts: datetime
|
2021-02-09 20:44:19 +01:00
|
|
|
mac: str
|
|
|
|
hostname: Optional[str]
|
|
|
|
ip: str
|
|
|
|
|
|
|
|
|
2021-02-10 23:07:37 +01:00
|
|
|
@dataclass
|
|
|
|
class Status:
|
|
|
|
status: str
|
|
|
|
text: str
|
|
|
|
|
|
|
|
|
2021-02-09 20:44:19 +01:00
|
|
|
@click.command()
|
|
|
|
@click.option('--address', default=os.getenv("ROUTER_IP", "192.168.42.1"), help='Address of the router.')
|
|
|
|
@click.option('--period', default=60, help='How often to check for clients (in seconds).')
|
|
|
|
@click.option('--ssid', default=os.getenv("SSID", "anabasis"), help='Limit clients to SSID containing this string.')
|
2021-02-10 23:07:37 +01:00
|
|
|
@click.option('-o', '--output', multiple=True, help="Output file.")
|
2021-02-09 20:44:19 +01:00
|
|
|
def run_forever(address: str, period: int, ssid: str, output: str):
|
|
|
|
logging.basicConfig(level=logging.DEBUG, format='%(asctime)s - [%(levelname)s] %(message)s')
|
2021-02-10 23:07:37 +01:00
|
|
|
config = Config
|
2021-02-09 20:44:19 +01:00
|
|
|
db = _get_db("clients.sqlite3")
|
|
|
|
|
|
|
|
connection = routeros_api.RouterOsApiPool(
|
|
|
|
address,
|
|
|
|
username='admin',
|
|
|
|
password=os.environ["ROUTER_PASSWORD"],
|
|
|
|
plaintext_login=True
|
|
|
|
)
|
|
|
|
|
2021-02-10 23:07:37 +01:00
|
|
|
jinja_env = Environment(
|
|
|
|
loader=FileSystemLoader('templates'),
|
|
|
|
autoescape=select_autoescape(['html', 'xml'])
|
|
|
|
)
|
|
|
|
|
2021-02-09 20:44:19 +01:00
|
|
|
while True:
|
|
|
|
logging.info(f"Querying router at {address}...")
|
|
|
|
api = connection.get_api()
|
|
|
|
|
|
|
|
currently_registered = api.get_resource('/caps-man/registration-table').call('print')
|
|
|
|
logging.debug(f"Got {len(currently_registered)} registered clients.")
|
|
|
|
dhcp_leases = api.get_resource('/ip/dhcp-server/lease').call('print')
|
|
|
|
logging.debug(f"Got {len(dhcp_leases)} DHCP leases.")
|
|
|
|
|
2021-02-10 23:21:05 +01:00
|
|
|
now = datetime.now()
|
|
|
|
timestamp = int(now.timestamp())
|
|
|
|
|
2021-02-09 20:44:19 +01:00
|
|
|
registered_leases: List[Lease] = []
|
|
|
|
for client in filter(lambda c: ssid in c['ssid'], currently_registered):
|
|
|
|
try:
|
|
|
|
lease = next(
|
|
|
|
lease for lease in dhcp_leases if lease.get('active-mac-address') == client['mac-address']
|
|
|
|
)
|
|
|
|
except StopIteration:
|
|
|
|
continue
|
|
|
|
registered_leases.append(
|
2021-02-10 23:21:05 +01:00
|
|
|
Lease(ts=now, ip=lease['active-address'], mac=lease['active-mac-address'],
|
|
|
|
hostname=lease.get('host-name'))
|
2021-02-09 20:44:19 +01:00
|
|
|
)
|
|
|
|
logging.info(f"Found {len(registered_leases)} registered leases.")
|
|
|
|
logging.debug(", ".join([str(lease) for lease in registered_leases]))
|
|
|
|
|
2021-02-10 23:07:37 +01:00
|
|
|
if len(registered_leases) > 0:
|
|
|
|
if len([lease for lease in registered_leases
|
2021-02-10 23:46:56 +01:00
|
|
|
if not any(re.match(ch, lease.hostname or "") for ch in config.computer_hostnames)]) > 0:
|
2021-02-10 23:07:37 +01:00
|
|
|
status = Status(status="populated", text="There seem to be people!")
|
|
|
|
else:
|
|
|
|
status = Status(status="empty", text="There are only computers.")
|
|
|
|
else:
|
|
|
|
status = Status(status="empty", text="There are no devices connected?")
|
|
|
|
|
2021-02-09 20:44:19 +01:00
|
|
|
logging.debug("Logging into the database...")
|
|
|
|
cur = db.cursor()
|
|
|
|
cur.executemany("INSERT INTO spottings (ts, mac, hostname, ip) VALUES (?,?,?,?)", [
|
|
|
|
(timestamp, lease.mac, lease.hostname, lease.ip) for lease in registered_leases
|
|
|
|
])
|
|
|
|
db.commit()
|
|
|
|
|
2021-02-10 23:07:37 +01:00
|
|
|
for output_file in output:
|
|
|
|
if output_file.endswith(".csv"):
|
|
|
|
logging.debug(f"Outputting CSV file into {output_file}...")
|
|
|
|
with open(output_file, 'w') as file:
|
|
|
|
writer = csv.writer(file)
|
|
|
|
for lease in registered_leases:
|
|
|
|
writer.writerow((lease.ip, lease.mac, lease.hostname or "???"))
|
|
|
|
elif output_file.endswith(".lst"):
|
|
|
|
logging.debug(f"Outputting LST file into {output_file}...")
|
|
|
|
with open(output_file, 'w') as file:
|
|
|
|
file.write(f"{now}\n")
|
|
|
|
writer = csv.writer(file)
|
|
|
|
for lease in registered_leases:
|
|
|
|
writer.writerow((lease.ip, lease.mac, lease.hostname or "???"))
|
|
|
|
elif output_file.endswith(".html"):
|
2021-02-10 23:21:05 +01:00
|
|
|
last_human = next((lease for lease in _fetch_leases(db, now - timedelta(hours=24))
|
2021-02-10 23:46:56 +01:00
|
|
|
if not any(re.match(ch, lease.hostname or "") for ch in config.computer_hostnames)),
|
|
|
|
None)
|
2021-02-10 23:21:05 +01:00
|
|
|
|
2021-02-10 23:07:37 +01:00
|
|
|
logging.debug(f"Outputting HTML file into {output_file}...")
|
|
|
|
with open(output_file, 'w') as file:
|
|
|
|
out_str = jinja_env.get_template("index.html").render(
|
|
|
|
now=now,
|
|
|
|
leases=registered_leases,
|
2021-02-10 23:21:05 +01:00
|
|
|
status=status,
|
|
|
|
last_human=last_human
|
2021-02-10 23:07:37 +01:00
|
|
|
)
|
|
|
|
file.write(out_str)
|
2021-02-09 20:44:19 +01:00
|
|
|
|
|
|
|
logging.info(f"Sleeping for {period} seconds.")
|
|
|
|
sleep(period)
|
|
|
|
|
2021-01-14 19:49:54 +01:00
|
|
|
|
2021-02-09 20:44:19 +01:00
|
|
|
if __name__ == '__main__':
|
|
|
|
run_forever()
|