98 lines
3.3 KiB
Python
98 lines
3.3 KiB
Python
# pyruse is intended as a replacement to both fail2ban and epylog
|
||
# Copyright © 2017–2018 Y. Gablin
|
||
# Full licensing information in the LICENSE file, or gnu.org/licences/gpl-3.0.txt if the file is missing.
|
||
from datetime import datetime
|
||
|
||
_mappings = []
|
||
|
||
def _cleanMappings():
|
||
global _mappings
|
||
now = int(datetime.today().timestamp())
|
||
_mappings = [m for m in _mappings if (now >> m["bits"]) <= m["time"]]
|
||
|
||
def putMapping(mapping):
|
||
global _mappings
|
||
_cleanMappings()
|
||
_mappings.append(mapping)
|
||
|
||
def getMappings():
|
||
global _mappings
|
||
_cleanMappings()
|
||
return _mappings
|
||
|
||
def periodBits(keepSeconds):
|
||
seconds, bits = keepSeconds, 0
|
||
while seconds:
|
||
bits += 1
|
||
seconds = seconds >> 1
|
||
return bits # number of significant bits in keepSeconds
|
||
|
||
def valueFor(spec, entry):
|
||
return spec[1] if spec[0] is None else entry.get(spec[0], spec[1])
|
||
|
||
class Mapper():
|
||
def __init__(self, saddr, sport, addr, port, daddr, dport, keepSeconds):
|
||
for spec in [saddr, addr]:
|
||
if spec[0] is None and spec[1] is None:
|
||
raise ValueError("Neither field nor value was specified for address")
|
||
self.saddr = saddr
|
||
self.sport = sport
|
||
self.addr = addr
|
||
self.port = port
|
||
self.daddr = daddr
|
||
self.dport = dport
|
||
self.keepBits = periodBits(keepSeconds)
|
||
|
||
def map(self, entry):
|
||
saddr = valueFor(self.saddr, entry)
|
||
addr = valueFor(self.addr, entry)
|
||
if saddr is None or addr is None:
|
||
return
|
||
sport = valueFor(self.sport, entry)
|
||
port = valueFor(self.port, entry)
|
||
daddr = valueFor(self.daddr, entry)
|
||
dport = valueFor(self.dport, entry)
|
||
putMapping(dict(
|
||
bits = self.keepBits,
|
||
time = 1 + (int(entry["__REALTIME_TIMESTAMP"].timestamp()) >> self.keepBits),
|
||
saddr = saddr, sport = sport,
|
||
addr = addr, port = port,
|
||
daddr = daddr, dport = dport
|
||
))
|
||
|
||
class Matcher():
|
||
def __init__(self, addr, port, daddr, dport, saddr, sport):
|
||
if addr is None and port is None and daddr is None and dport is None:
|
||
raise ValueError("No field was provided on which to do the matching")
|
||
if saddr is None and sport is None:
|
||
raise ValueError("No field was provided in which to store the translated values")
|
||
matchers = []
|
||
updaters = []
|
||
if addr is not None:
|
||
matchers.append((addr, "addr"))
|
||
if port is not None:
|
||
matchers.append((port, "port"))
|
||
if daddr is not None:
|
||
matchers.append((daddr, "daddr"))
|
||
if dport is not None:
|
||
matchers.append((dport, "dport"))
|
||
if saddr is not None:
|
||
updaters.append((saddr, "saddr"))
|
||
if sport is not None:
|
||
updaters.append((sport, "sport"))
|
||
self.matchers = matchers
|
||
self.updaters = updaters
|
||
|
||
def replace(self, entry):
|
||
for field, _void in self.matchers:
|
||
if field not in entry:
|
||
return
|
||
for mapping in getMappings():
|
||
for field, mapEntry in self.matchers:
|
||
if entry[field] != mapping[mapEntry]:
|
||
break
|
||
else:
|
||
for field, mapEntry in self.updaters:
|
||
entry[field] = mapping[mapEntry]
|
||
return
|