"Fossies" - the Fresh Open Source Software Archive  

Source code changes of the file "fail2ban/server/database.py" between
fail2ban-0.10.5.tar.gz and fail2ban-0.11.1.tar.gz

About:

database.py  (fail2ban-0.10.5):database.py  (fail2ban-0.11.1)
skipping to change at line 138 skipping to change at line 138
sqlite3.OperationalError sqlite3.OperationalError
Error connecting/creating a SQLite3 database. Error connecting/creating a SQLite3 database.
RuntimeError RuntimeError
If exisiting database fails to update to new schema. If exisiting database fails to update to new schema.
Attributes Attributes
---------- ----------
filename filename
purgeage purgeage
""" """
__version__ = 2 __version__ = 4
# Note all SCRIPTS strings must end in ';' for py26 compatibility # Note all SCRIPTS strings must end in ';' for py26 compatibility
_CREATE_SCRIPTS = ( _CREATE_SCRIPTS = (
('fail2banDb', "CREATE TABLE IF NOT EXISTS fail2banDb(version IN TEGER);") ('fail2banDb', "CREATE TABLE IF NOT EXISTS fail2banDb(version IN TEGER);")
,('jails', "CREATE TABLE IF NOT EXISTS jails(" \ ,('jails', "CREATE TABLE IF NOT EXISTS jails(" \
"name TEXT NOT NULL UNIQUE, " \ "name TEXT NOT NULL UNIQUE, " \
"enabled INTEGER NOT NULL DEFAULT 1" \ "enabled INTEGER NOT NULL DEFAULT 1" \
");" \ ");" \
"CREATE INDEX IF NOT EXISTS jails_name ON jails(name);") "CREATE INDEX IF NOT EXISTS jails_name ON jails(name);")
,('logs', "CREATE TABLE IF NOT EXISTS logs(" \ ,('logs', "CREATE TABLE IF NOT EXISTS logs(" \
"jail TEXT NOT NULL, " \ "jail TEXT NOT NULL, " \
skipping to change at line 166 skipping to change at line 166
"CREATE INDEX IF NOT EXISTS logs_path ON logs(path);" \ "CREATE INDEX IF NOT EXISTS logs_path ON logs(path);" \
"CREATE INDEX IF NOT EXISTS logs_jail_path ON logs(jail, path);") "CREATE INDEX IF NOT EXISTS logs_jail_path ON logs(jail, path);")
#TODO: systemd journal features \ #TODO: systemd journal features \
#"journalmatch TEXT, " \ #"journalmatch TEXT, " \
#"journlcursor TEXT, " \ #"journlcursor TEXT, " \
#"lastfiletime INTEGER DEFAULT 0, " # is this easily avai lable #"lastfiletime INTEGER DEFAULT 0, " # is this easily avai lable
,('bans', "CREATE TABLE IF NOT EXISTS bans(" \ ,('bans', "CREATE TABLE IF NOT EXISTS bans(" \
"jail TEXT NOT NULL, " \ "jail TEXT NOT NULL, " \
"ip TEXT, " \ "ip TEXT, " \
"timeofban INTEGER NOT NULL, " \ "timeofban INTEGER NOT NULL, " \
"bantime INTEGER NOT NULL, " \
"bancount INTEGER NOT NULL default 1, " \
"data JSON, " \ "data JSON, " \
"FOREIGN KEY(jail) REFERENCES jails(name) " \ "FOREIGN KEY(jail) REFERENCES jails(name) " \
");" \ ");" \
"CREATE INDEX IF NOT EXISTS bans_jail_timeofban_ip ON ban s(jail, timeofban);" \ "CREATE INDEX IF NOT EXISTS bans_jail_timeofban_ip ON ban s(jail, timeofban);" \
"CREATE INDEX IF NOT EXISTS bans_jail_ip ON bans(jail, ip );" \ "CREATE INDEX IF NOT EXISTS bans_jail_ip ON bans(jail, ip );" \
"CREATE INDEX IF NOT EXISTS bans_ip ON bans(ip);") "CREATE INDEX IF NOT EXISTS bans_ip ON bans(ip);")
,('bips', "CREATE TABLE IF NOT EXISTS bips(" \
"ip TEXT NOT NULL, " \
"jail TEXT NOT NULL, " \
"timeofban INTEGER NOT NULL, " \
"bantime INTEGER NOT NULL, " \
"bancount INTEGER NOT NULL default 1, " \
"data JSON, " \
"PRIMARY KEY(ip, jail), " \
"FOREIGN KEY(jail) REFERENCES jails(name) " \
");" \
"CREATE INDEX IF NOT EXISTS bips_timeofban ON bips(timeof
ban);" \
"CREATE INDEX IF NOT EXISTS bips_ip ON bips(ip);")
) )
_CREATE_TABS = dict(_CREATE_SCRIPTS) _CREATE_TABS = dict(_CREATE_SCRIPTS)
def __init__(self, filename, purgeAge=24*60*60): def __init__(self, filename, purgeAge=24*60*60, outDatedFactor=3):
self.maxMatches = 10 self.maxMatches = 10
self._lock = RLock() self._lock = RLock()
self._dbFilename = filename self._dbFilename = filename
self._purgeAge = purgeAge self._purgeAge = purgeAge
self._outDatedFactor = outDatedFactor;
self._connectDB() self._connectDB()
def _connectDB(self, checkIntegrity=False): def _connectDB(self, checkIntegrity=False):
filename = self._dbFilename filename = self._dbFilename
try: try:
self._db = sqlite3.connect( self._db = sqlite3.connect(
filename, check_same_thread=False, filename, check_same_thread=False,
detect_types=sqlite3.PARSE_DECLTYPES) detect_types=sqlite3.PARSE_DECLTYPES)
# # to allow use multi-byte utf-8 # # to allow use multi-byte utf-8
# self._db.text_factory = str # self._db.text_factory = str
skipping to change at line 365 skipping to change at line 380
raise NotImplementedError( raise NotImplementedError(
"Attempt to travel to future vers ion of database ...how did you get here??") "Attempt to travel to future vers ion of database ...how did you get here??")
try: try:
logSys.info("Upgrade database: %s from version '%r'", sel f._dbBackupFilename, version) logSys.info("Upgrade database: %s from version '%r'", sel f._dbBackupFilename, version)
if not os.path.isfile(self._dbBackupFilename): if not os.path.isfile(self._dbBackupFilename):
shutil.copyfile(self.filename, self._dbBackupFile name) shutil.copyfile(self.filename, self._dbBackupFile name)
logSys.info(" Database backup created: %s", self ._dbBackupFilename) logSys.info(" Database backup created: %s", self ._dbBackupFilename)
if version < 2 and self._tableExists(cur, "logs"): if version < 2 and self._tableExists(cur, "logs"):
cur.executescript("BEGIN TRANSACTION;" cur.executescript("BEGIN TRANSACTION;"
"CREATE TEMPORARY TABLE logs_temp "CREATE TEMPORARY TABLE l
AS SELECT * FROM logs;" ogs_temp AS SELECT * FROM logs;"
"DROP TABLE logs;" "DROP TABLE logs;"
"%s;" "%s;"
"INSERT INTO logs SELECT * from l "INSERT INTO logs SELECT
ogs_temp;" * from logs_temp;"
"DROP TABLE logs_temp;" "DROP TABLE logs_temp;"
"UPDATE fail2banDb SET version = "UPDATE fail2banDb SET ve
2;" rsion = 2;"
"COMMIT;" % Fail2BanDb._CREATE_TA "COMMIT;" % Fail2BanDb._C
BS['logs']) REATE_TABS['logs'])
if version < 3 and self._tableExists(cur, "bans"):
# set ban-time to -2 (note it means rather unknow
n, as persistent, will be fixed by restore):
cur.executescript("BEGIN TRANSACTION;"
"CREATE TEMPORARY TABLE b
ans_temp AS SELECT jail, ip, timeofban, -2 as bantime, 1 as bancount, data FROM
bans;"
"DROP TABLE bans;"
"%s;\n"
"INSERT INTO bans SELECT
* from bans_temp;"
"DROP TABLE bans_temp;"
"COMMIT;" % Fail2BanDb._C
REATE_TABS['bans'])
if version < 4 and not self._tableExists(cur, "bips"):
cur.executescript("BEGIN TRANSACTION;"
"%s;\n"
"UPDATE fail2banDb SET ve
rsion = 4;"
"COMMIT;" % Fail2BanDb._C
REATE_TABS['bips'])
if self._tableExists(cur, "bans"):
cur.execute(
"INSERT OR REPLACE INTO b
ips(ip, jail, timeofban, bantime, bancount, data)"
" SELECT ip, jail, timeo
fban, bantime, bancount, data FROM bans order by timeofban")
cur.execute("SELECT version FROM fail2banDb LIMIT 1") cur.execute("SELECT version FROM fail2banDb LIMIT 1")
return cur.fetchone()[0] return cur.fetchone()[0]
except Exception as e: except Exception as e:
# if still failed, just recreate database as fallback: # if still failed, just recreate database as fallback:
logSys.error("Failed to upgrade database '%s': %s", logSys.error("Failed to upgrade database '%s': %s",
self._dbFilename, e.args[0], self._dbFilename, e.args[0],
exc_info=logSys.getEffectiveLevel() <= 10) exc_info=logSys.getEffectiveLevel() <= 10)
raise raise
skipping to change at line 548 skipping to change at line 582
data = ticket.getData() data = ticket.getData()
matches = data.get('matches') matches = data.get('matches')
if self.maxMatches: if self.maxMatches:
if matches and len(matches) > self.maxMatches: if matches and len(matches) > self.maxMatches:
data = data.copy() data = data.copy()
data['matches'] = matches[-self.maxMatches:] data['matches'] = matches[-self.maxMatches:]
elif matches: elif matches:
data = data.copy() data = data.copy()
del data['matches'] del data['matches']
cur.execute( cur.execute(
"INSERT INTO bans(jail, ip, timeofban, data) VALUES(?, ?, "INSERT INTO bans(jail, ip, timeofban, bantime, bancount,
?, ?)", data) VALUES(?, ?, ?, ?, ?, ?)",
(jail.name, ip, int(round(ticket.getTime())), data)) (jail.name, ip, int(round(ticket.getTime())), ticket.getB
anTime(jail.actions.getBanTime()), ticket.getBanCount(),
data))
cur.execute(
"INSERT OR REPLACE INTO bips(ip, jail, timeofban, bantime
, bancount, data) VALUES(?, ?, ?, ?, ?, ?)",
(ip, jail.name, int(round(ticket.getTime())), ticket.getB
anTime(jail.actions.getBanTime()), ticket.getBanCount(),
data))
@commitandrollback @commitandrollback
def delBan(self, cur, jail, *args): def delBan(self, cur, jail, *args):
"""Delete a single or multiple tickets from the database. """Delete a single or multiple tickets from the database.
Parameters Parameters
---------- ----------
jail : Jail jail : Jail
Jail in which the ticket(s) should be removed. Jail in which the ticket(s) should be removed.
args : list of IP args : list of IP
IPs to be removed, if not given all tickets of jail will be removed. IPs to be removed, if not given all tickets of jail will be removed.
""" """
query = "DELETE FROM bans WHERE jail = ?" query1 = "DELETE FROM bips WHERE jail = ?"
query2 = "DELETE FROM bans WHERE jail = ?"
queryArgs = [jail.name]; queryArgs = [jail.name];
if not len(args): if not len(args):
cur.execute(query, queryArgs); cur.execute(query1, queryArgs);
cur.execute(query2, queryArgs);
return return
query += " AND ip = ?" query1 += " AND ip = ?"
query2 += " AND ip = ?"
queryArgs.append(''); queryArgs.append('');
for ip in args: for ip in args:
queryArgs[1] = str(ip); queryArgs[1] = str(ip);
cur.execute(query, queryArgs); cur.execute(query1, queryArgs);
cur.execute(query2, queryArgs);
@commitandrollback @commitandrollback
def _getBans(self, cur, jail=None, bantime=None, ip=None): def _getBans(self, cur, jail=None, bantime=None, ip=None):
query = "SELECT ip, timeofban, data FROM bans WHERE 1" query = "SELECT ip, timeofban, data FROM bans WHERE 1"
queryArgs = [] queryArgs = []
if jail is not None: if jail is not None:
query += " AND jail=?" query += " AND jail=?"
queryArgs.append(jail.name) queryArgs.append(jail.name)
if bantime is not None and bantime >= 0: if bantime is not None and bantime >= 0:
skipping to change at line 689 skipping to change at line 732
data['matches'] = matches data['matches'] = matches
tickdata.update(data) tickdata.update(data)
prev_timeofban = timeofban prev_timeofban = timeofban
ticket = FailTicket(banip, prev_timeofban, data=t ickdata) ticket = FailTicket(banip, prev_timeofban, data=t ickdata)
tickets.append(ticket) tickets.append(ticket)
if cacheKey: if cacheKey:
self._bansMergedCache[cacheKey] = tickets if ip i s None else ticket self._bansMergedCache[cacheKey] = tickets if ip i s None else ticket
return tickets if ip is None else ticket return tickets if ip is None else ticket
@commitandrollback
def getBan(self, cur, ip, jail=None, forbantime=None, overalljails=None,
fromtime=None):
ip = str(ip)
if not overalljails:
query = "SELECT bancount, timeofban, bantime FROM bips"
else:
query = "SELECT sum(bancount), max(timeofban), sum(bantim
e) FROM bips"
query += " WHERE ip = ?"
queryArgs = [ip]
if not overalljails and jail is not None:
query += " AND jail=?"
queryArgs.append(jail.name)
if forbantime is not None:
query += " AND timeofban > ?"
queryArgs.append(MyTime.time() - forbantime)
if fromtime is not None:
query += " AND timeofban > ?"
queryArgs.append(fromtime)
if overalljails or jail is None:
query += " GROUP BY ip ORDER BY timeofban DESC LIMIT 1"
cur = self._db.cursor()
return cur.execute(query, queryArgs)
def _getCurrentBans(self, cur, jail = None, ip = None, forbantime=None, f romtime=None): def _getCurrentBans(self, cur, jail = None, ip = None, forbantime=None, f romtime=None):
if fromtime is None:
fromtime = MyTime.time()
queryArgs = [] queryArgs = []
if jail is not None: if jail is not None:
query = "SELECT ip, timeofban, data FROM bans WHERE jail= ?" query = "SELECT ip, timeofban, bantime, bancount, data FR OM bips WHERE jail=?"
queryArgs.append(jail.name) queryArgs.append(jail.name)
else: else:
query = "SELECT ip, max(timeofban), data FROM bans WHERE 1" query = "SELECT ip, max(timeofban), bantime, bancount, da ta FROM bips WHERE 1"
if ip is not None: if ip is not None:
query += " AND ip=?" query += " AND ip=?"
queryArgs.append(ip) queryArgs.append(ip)
query += " AND (timeofban + bantime > ? OR bantime <= -1)"
queryArgs.append(fromtime)
if forbantime not in (None, -1): # not specified or persistent (a ll) if forbantime not in (None, -1): # not specified or persistent (a ll)
query += " AND timeofban > ?" query += " AND timeofban > ?"
queryArgs.append(fromtime - forbantime) queryArgs.append(fromtime - forbantime)
if ip is None: if ip is None:
query += " GROUP BY ip ORDER BY ip, timeofban DESC" query += " GROUP BY ip ORDER BY ip, timeofban DESC"
else: else:
query += " ORDER BY timeofban DESC LIMIT 1" query += " ORDER BY timeofban DESC LIMIT 1"
cur = self._db.cursor() cur = self._db.cursor()
return cur.execute(query, queryArgs) return cur.execute(query, queryArgs)
def getCurrentBans(self, jail = None, ip = None, forbantime=None, fromtim @commitandrollback
e=None, maxmatches=None): def getCurrentBans(self, cur, jail=None, ip=None, forbantime=None, fromti
me=None,
correctBanTime=True, maxmatches=None
):
"""Reads tickets (with merged info) currently affected from ban f
rom the database.
There are all the tickets corresponding parameters jail/ip, forba
ntime,
fromtime (normally now).
If correctBanTime specified (default True) it will fix the restor
ed ban-time
(and therefore endOfBan) of the ticket (normally it is ban-time o
f jail as maximum)
for all tickets with ban-time greater (or persistent).
"""
if fromtime is None:
fromtime = MyTime.time()
tickets = [] tickets = []
ticket = None ticket = None
if correctBanTime is True:
correctBanTime = jail.getMaxBanTime() if jail is not None
else None
# don't change if persistent allowed:
if correctBanTime == -1: correctBanTime = None
for ticket in self._getCurrentBans(cur, jail=jail, ip=ip,
forbantime=forbantime, fromtime=fromtime
):
# can produce unpack error (database may return sporadica
l wrong-empty row):
try:
banip, timeofban, bantime, bancount, data = ticke
t
# additionally check for empty values:
if banip is None or banip == "": # pragma: no cov
er
raise ValueError('unexpected value %r' %
(banip,))
# if bantime unknown (after upgrade-db from earli
er version), just use min known ban-time:
if bantime == -2: # todo: remove it in future ver
sion
bantime = jail.actions.getBanTime() if ja
il is not None else (
correctBanTime if correctBanTime
else 600)
elif correctBanTime and correctBanTime >= 0:
# if persistent ban (or greater as max),
use current max-bantime of the jail:
if bantime == -1 or bantime > correctBanT
ime:
bantime = correctBanTime
# after correction check the end of ban again:
if bantime != -1 and timeofban + bantime <= fromt
ime:
# not persistent and too old - ignore it:
logSys.debug("ignore ticket (with new max
ban-time %r): too old %r <= %r, ticket: %r",
bantime, timeofban + bantime, fro
mtime, ticket)
continue
except ValueError as e: # pragma: no cover
logSys.debug("get current bans: ignore row %r - %
s", ticket, e)
continue
# logSys.debug('restore ticket %r, %r, %r', banip, time
ofban, data)
ticket = FailTicket(banip, timeofban, data=data)
# filter matches if expected (current count > as maxmatch
es specified):
if maxmatches is None:
maxmatches = self.maxMatches
if maxmatches:
matches = ticket.getMatches()
if matches and len(matches) > maxmatches:
ticket.setMatches(matches[-maxmatches:])
else:
ticket.setMatches(None)
# logSys.debug('restored ticket: %r', ticket)
ticket.setBanTime(bantime)
ticket.setBanCount(bancount)
if ip is not None: return ticket
tickets.append(ticket)
with self._lock: return tickets
results = list(self._getCurrentBans(self._db.cursor(),
jail=jail, ip=ip, forbantime=forbantime, fromtime
=fromtime))
if results: def _cleanjails(self, cur):
for banip, timeofban, data in results: """Remove empty jails jails and log files from database.
# logSys.debug('restore ticket %r, %r, %r', ban """
ip, timeofban, data) cur.execute(
ticket = FailTicket(banip, timeofban, data=data) "DELETE FROM jails WHERE enabled = 0 "
# filter matches if expected (current count > as "AND NOT EXISTS(SELECT * FROM bans WHERE jail = j
maxmatches specified): ails.name) "
if maxmatches is None: "AND NOT EXISTS(SELECT * FROM bips WHERE jail = j
maxmatches = self.maxMatches ails.name)")
if maxmatches:
matches = ticket.getMatches()
if matches and len(matches) > maxmatches:
ticket.setMatches(matches[-maxmat
ches:])
else:
ticket.setMatches(None)
# logSys.debug('restored ticket: %r', ticket)
if ip is not None: return ticket
tickets.append(ticket)
return tickets def _purge_bips(self, cur):
"""Purge old bad ips (jails and log files from database).
Currently it is timed out IP, whose time since last ban is severa
l times out-dated (outDatedFactor is default 3).
Permanent banned ips will be never removed.
"""
cur.execute(
"DELETE FROM bips WHERE timeofban < ? and bantime != -1 a
nd (timeofban + (bantime * ?)) < ?",
(int(MyTime.time()) - self._purgeAge, self._outDatedFacto
r, int(MyTime.time()) - self._purgeAge))
@commitandrollback @commitandrollback
def purge(self, cur): def purge(self, cur):
"""Purge old bans, jails and log files from database. """Purge old bans, jails and log files from database.
""" """
self._bansMergedCache = {} self._bansMergedCache = {}
cur.execute( cur.execute(
"DELETE FROM bans WHERE timeofban < ?", "DELETE FROM bans WHERE timeofban < ?",
(MyTime.time() - self._purgeAge, )) (MyTime.time() - self._purgeAge, ))
cur.execute( self._purge_bips(cur)
"DELETE FROM jails WHERE enabled = 0 " self._cleanjails(cur)
"AND NOT EXISTS(SELECT * FROM bans WHERE jail = j
ails.name)")
 End of changes. 22 change blocks. 
50 lines changed or deleted 210 lines changed or added

Home  |  About  |  Features  |  All  |  Newest  |  Dox  |  Diffs  |  RSS Feeds  |  Screenshots  |  Comments  |  Imprint  |  Privacy  |  HTTP(S)