86 lines
2.8 KiB
Python
86 lines
2.8 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.
|
|||
|
import abc
|
|||
|
import datetime
|
|||
|
import json
|
|||
|
|
|||
|
class NetfilterBan(abc.ABC):
|
|||
|
def __init__(self, storage):
|
|||
|
self.storage = storage
|
|||
|
|
|||
|
def initSelf(self, ipv4Set, ipv6Set, ipField, banSeconds):
|
|||
|
self.ipv4Set = ipv4Set
|
|||
|
self.ipv6Set = ipv6Set
|
|||
|
self.field = ipField
|
|||
|
self.banSeconds = banSeconds
|
|||
|
|
|||
|
@abc.abstractmethod
|
|||
|
def setBan(self, nfSet, ip, seconds):
|
|||
|
pass
|
|||
|
|
|||
|
@abc.abstractmethod
|
|||
|
def cancelBan(self, nfSet, ip):
|
|||
|
pass
|
|||
|
|
|||
|
def act(self, entry):
|
|||
|
ip = entry[self.field]
|
|||
|
nfSet = self.ipv6Set if ":" in ip else self.ipv4Set
|
|||
|
newBan = {"IP": ip, "nfSet": nfSet}
|
|||
|
|
|||
|
now = datetime.datetime.utcnow()
|
|||
|
bans = []
|
|||
|
previousTS = None
|
|||
|
try:
|
|||
|
with open(self.storage) as dataFile:
|
|||
|
for ban in json.load(dataFile):
|
|||
|
if ban["timestamp"] > 0 and ban["timestamp"] <= now.timestamp():
|
|||
|
continue
|
|||
|
elif {k: ban[k] for k in newBan.keys()} == newBan:
|
|||
|
# should not happen, since the IP is banned…
|
|||
|
previousTS = ban["timestamp"]
|
|||
|
else:
|
|||
|
bans.append(ban)
|
|||
|
except IOError:
|
|||
|
pass # new file
|
|||
|
|
|||
|
if previousTS is not None:
|
|||
|
try:
|
|||
|
self.cancelBan(nfSet, ip)
|
|||
|
except Exception:
|
|||
|
pass # too late: not a problem
|
|||
|
|
|||
|
if self.banSeconds:
|
|||
|
until = now + datetime.timedelta(seconds = self.banSeconds)
|
|||
|
newBan["timestamp"] = until.timestamp()
|
|||
|
timeout = self.banSeconds
|
|||
|
else:
|
|||
|
newBan["timestamp"] = 0
|
|||
|
timeout = 0
|
|||
|
|
|||
|
self.setBan(nfSet, ip, timeout)
|
|||
|
bans.append(newBan)
|
|||
|
with open(self.storage, "w") as dataFile:
|
|||
|
json.dump(bans, dataFile)
|
|||
|
|
|||
|
def boot(self):
|
|||
|
now = int(datetime.datetime.utcnow().timestamp())
|
|||
|
bans = []
|
|||
|
try:
|
|||
|
with open(self.storage) as dataFile:
|
|||
|
for ban in json.load(dataFile):
|
|||
|
if ban["timestamp"] == 0:
|
|||
|
self.setBan(ban["nfSet"], ban["IP"], 0)
|
|||
|
bans.append(ban)
|
|||
|
elif int(ban["timestamp"]) <= now:
|
|||
|
continue
|
|||
|
else:
|
|||
|
timeout = int(ban["timestamp"]) - now
|
|||
|
self.setBan(ban["nfSet"], ban["IP"], timeout)
|
|||
|
bans.append(ban)
|
|||
|
except IOError:
|
|||
|
pass # no file
|
|||
|
|
|||
|
with open(self.storage, "w") as dataFile:
|
|||
|
json.dump(bans, dataFile)
|