|
|
@ -129,14 +129,15 @@ class Fail2BanDb(object):
|
|
|
|
purgeage
|
|
|
|
purgeage
|
|
|
|
"""
|
|
|
|
"""
|
|
|
|
__version__ = 2
|
|
|
|
__version__ = 2
|
|
|
|
# Note all _TABLE_* strings must end in ';' for py26 compatibility
|
|
|
|
# Note all SCRIPTS strings must end in ';' for py26 compatibility
|
|
|
|
_TABLE_fail2banDb = "CREATE TABLE fail2banDb(version INTEGER);"
|
|
|
|
_CREATE_SCRIPTS = (
|
|
|
|
_TABLE_jails = "CREATE TABLE jails(" \
|
|
|
|
('fail2banDb', "CREATE TABLE IF NOT EXISTS fail2banDb(version INTEGER);")
|
|
|
|
|
|
|
|
,('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 jails_name ON jails(name);"
|
|
|
|
"CREATE INDEX IF NOT EXISTS jails_name ON jails(name);")
|
|
|
|
_TABLE_logs = "CREATE TABLE logs(" \
|
|
|
|
,('logs', "CREATE TABLE IF NOT EXISTS logs(" \
|
|
|
|
"jail TEXT NOT NULL, " \
|
|
|
|
"jail TEXT NOT NULL, " \
|
|
|
|
"path TEXT, " \
|
|
|
|
"path TEXT, " \
|
|
|
|
"firstlinemd5 TEXT, " \
|
|
|
|
"firstlinemd5 TEXT, " \
|
|
|
@ -145,22 +146,24 @@ class Fail2BanDb(object):
|
|
|
|
"UNIQUE(jail, path)," \
|
|
|
|
"UNIQUE(jail, path)," \
|
|
|
|
"UNIQUE(jail, path, firstlinemd5)" \
|
|
|
|
"UNIQUE(jail, path, firstlinemd5)" \
|
|
|
|
");" \
|
|
|
|
");" \
|
|
|
|
"CREATE INDEX logs_path ON logs(path);" \
|
|
|
|
"CREATE INDEX IF NOT EXISTS logs_path ON logs(path);" \
|
|
|
|
"CREATE INDEX 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 available \
|
|
|
|
#"lastfiletime INTEGER DEFAULT 0, " # is this easily available
|
|
|
|
_TABLE_bans = "CREATE TABLE 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, " \
|
|
|
|
"data JSON, " \
|
|
|
|
"data JSON, " \
|
|
|
|
"FOREIGN KEY(jail) REFERENCES jails(name) " \
|
|
|
|
"FOREIGN KEY(jail) REFERENCES jails(name) " \
|
|
|
|
");" \
|
|
|
|
");" \
|
|
|
|
"CREATE INDEX bans_jail_timeofban_ip ON bans(jail, timeofban);" \
|
|
|
|
"CREATE INDEX IF NOT EXISTS bans_jail_timeofban_ip ON bans(jail, timeofban);" \
|
|
|
|
"CREATE INDEX bans_jail_ip ON bans(jail, ip);" \
|
|
|
|
"CREATE INDEX IF NOT EXISTS bans_jail_ip ON bans(jail, ip);" \
|
|
|
|
"CREATE INDEX bans_ip ON bans(ip);" \
|
|
|
|
"CREATE INDEX IF NOT EXISTS bans_ip ON bans(ip);")
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
_CREATE_TABS = dict(_CREATE_SCRIPTS)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def __init__(self, filename, purgeAge=24*60*60):
|
|
|
|
def __init__(self, filename, purgeAge=24*60*60):
|
|
|
@ -206,16 +209,9 @@ class Fail2BanDb(object):
|
|
|
|
# speedup: temporary tables and indices are kept in memory:
|
|
|
|
# speedup: temporary tables and indices are kept in memory:
|
|
|
|
cur.execute("PRAGMA temp_store = MEMORY")
|
|
|
|
cur.execute("PRAGMA temp_store = MEMORY")
|
|
|
|
|
|
|
|
|
|
|
|
if checkIntegrity:
|
|
|
|
|
|
|
|
logSys.debug(" Check integrity ...")
|
|
|
|
|
|
|
|
cur.execute("PRAGMA integrity_check")
|
|
|
|
|
|
|
|
for s in cur.fetchall():
|
|
|
|
|
|
|
|
logSys.debug(" %s", s)
|
|
|
|
|
|
|
|
self._db.commit()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
cur.execute("SELECT version FROM fail2banDb LIMIT 1")
|
|
|
|
cur.execute("SELECT version FROM fail2banDb LIMIT 1")
|
|
|
|
except sqlite3.OperationalError:
|
|
|
|
except sqlite3.OperationalError:
|
|
|
|
logSys.warning("New database created. Version '%i'",
|
|
|
|
logSys.warning("New database created. Version '%r'",
|
|
|
|
self.createDb())
|
|
|
|
self.createDb())
|
|
|
|
except sqlite3.Error as e:
|
|
|
|
except sqlite3.Error as e:
|
|
|
|
logSys.error(
|
|
|
|
logSys.error(
|
|
|
@ -233,14 +229,23 @@ class Fail2BanDb(object):
|
|
|
|
if version < Fail2BanDb.__version__:
|
|
|
|
if version < Fail2BanDb.__version__:
|
|
|
|
newversion = self.updateDb(version)
|
|
|
|
newversion = self.updateDb(version)
|
|
|
|
if newversion == Fail2BanDb.__version__:
|
|
|
|
if newversion == Fail2BanDb.__version__:
|
|
|
|
logSys.warning( "Database updated from '%i' to '%i'",
|
|
|
|
logSys.warning( "Database updated from '%r' to '%r'",
|
|
|
|
version, newversion)
|
|
|
|
version, newversion)
|
|
|
|
else: # pragma: no cover
|
|
|
|
else: # pragma: no cover
|
|
|
|
logSys.error( "Database update failed to achieve version '%i'"
|
|
|
|
logSys.error( "Database update failed to achieve version '%r'"
|
|
|
|
": updated from '%i' to '%i'",
|
|
|
|
": updated from '%r' to '%r'",
|
|
|
|
Fail2BanDb.__version__, version, newversion)
|
|
|
|
Fail2BanDb.__version__, version, newversion)
|
|
|
|
raise RuntimeError('Failed to fully update')
|
|
|
|
raise RuntimeError('Failed to fully update')
|
|
|
|
finally:
|
|
|
|
finally:
|
|
|
|
|
|
|
|
if checkIntegrity:
|
|
|
|
|
|
|
|
logSys.debug(" Create missing tables/indices ...")
|
|
|
|
|
|
|
|
self._createDb(cur, incremental=True)
|
|
|
|
|
|
|
|
logSys.debug(" -> ok")
|
|
|
|
|
|
|
|
logSys.debug(" Check integrity ...")
|
|
|
|
|
|
|
|
cur.execute("PRAGMA integrity_check")
|
|
|
|
|
|
|
|
for s in cur.fetchall():
|
|
|
|
|
|
|
|
logSys.debug(" -> %s", ' '.join(s))
|
|
|
|
|
|
|
|
self._db.commit()
|
|
|
|
if cur:
|
|
|
|
if cur:
|
|
|
|
# pypy: set journal mode after possible upgrade db:
|
|
|
|
# pypy: set journal mode after possible upgrade db:
|
|
|
|
if pypy:
|
|
|
|
if pypy:
|
|
|
@ -261,6 +266,8 @@ class Fail2BanDb(object):
|
|
|
|
return self.__dbBackupFilename
|
|
|
|
return self.__dbBackupFilename
|
|
|
|
|
|
|
|
|
|
|
|
def repairDB(self):
|
|
|
|
def repairDB(self):
|
|
|
|
|
|
|
|
class RepairException(Exception):
|
|
|
|
|
|
|
|
pass
|
|
|
|
# avoid endless recursion if reconnect failed again for some reasons:
|
|
|
|
# avoid endless recursion if reconnect failed again for some reasons:
|
|
|
|
_repairDB = self.repairDB
|
|
|
|
_repairDB = self.repairDB
|
|
|
|
self.repairDB = None
|
|
|
|
self.repairDB = None
|
|
|
@ -280,13 +287,14 @@ class Fail2BanDb(object):
|
|
|
|
self._connectDB(checkIntegrity=True)
|
|
|
|
self._connectDB(checkIntegrity=True)
|
|
|
|
else:
|
|
|
|
else:
|
|
|
|
logSys.info(" Repair seems to be failed, restored %d byte(s).", dbFileSize)
|
|
|
|
logSys.info(" Repair seems to be failed, restored %d byte(s).", dbFileSize)
|
|
|
|
raise Exception('Recreate ...')
|
|
|
|
raise RepairException('Recreate ...')
|
|
|
|
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(" Error repairing of fail2ban database '%s': %s",
|
|
|
|
logSys.error(" Error repairing of fail2ban database '%s': %s",
|
|
|
|
self._dbFilename, e.args[0])
|
|
|
|
self._dbFilename, e.args[0],
|
|
|
|
|
|
|
|
exc_info=(not isinstance(e, RepairException) and logSys.getEffectiveLevel() <= 10))
|
|
|
|
os.remove(self._dbFilename)
|
|
|
|
os.remove(self._dbFilename)
|
|
|
|
self._connectDB()
|
|
|
|
self._connectDB(checkIntegrity=True)
|
|
|
|
finally:
|
|
|
|
finally:
|
|
|
|
self.repairDB = _repairDB
|
|
|
|
self.repairDB = _repairDB
|
|
|
|
|
|
|
|
|
|
|
@ -306,24 +314,23 @@ class Fail2BanDb(object):
|
|
|
|
def purgeage(self, value):
|
|
|
|
def purgeage(self, value):
|
|
|
|
self._purgeAge = MyTime.str2seconds(value)
|
|
|
|
self._purgeAge = MyTime.str2seconds(value)
|
|
|
|
|
|
|
|
|
|
|
|
@commitandrollback
|
|
|
|
def _createDb(self, cur, incremental=False):
|
|
|
|
def createDb(self, cur):
|
|
|
|
|
|
|
|
"""Creates a new database, called during initialisation.
|
|
|
|
"""Creates a new database, called during initialisation.
|
|
|
|
"""
|
|
|
|
"""
|
|
|
|
# Version info
|
|
|
|
# create all (if not exists):
|
|
|
|
cur.executescript(Fail2BanDb._TABLE_fail2banDb)
|
|
|
|
for (n, s) in Fail2BanDb._CREATE_SCRIPTS:
|
|
|
|
cur.execute("INSERT INTO fail2banDb(version) VALUES(?)",
|
|
|
|
cur.executescript(s)
|
|
|
|
|
|
|
|
# save current database version (if not already set):
|
|
|
|
|
|
|
|
cur.execute("INSERT INTO fail2banDb(version)"
|
|
|
|
|
|
|
|
" SELECT ? WHERE NOT EXISTS (SELECT 1 FROM fail2banDb LIMIT 1)",
|
|
|
|
(Fail2BanDb.__version__, ))
|
|
|
|
(Fail2BanDb.__version__, ))
|
|
|
|
# Jails
|
|
|
|
|
|
|
|
cur.executescript(Fail2BanDb._TABLE_jails)
|
|
|
|
|
|
|
|
# Logs
|
|
|
|
|
|
|
|
cur.executescript(Fail2BanDb._TABLE_logs)
|
|
|
|
|
|
|
|
# Bans
|
|
|
|
|
|
|
|
cur.executescript(Fail2BanDb._TABLE_bans)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
cur.execute("SELECT version FROM fail2banDb LIMIT 1")
|
|
|
|
cur.execute("SELECT version FROM fail2banDb LIMIT 1")
|
|
|
|
return cur.fetchone()[0]
|
|
|
|
return cur.fetchone()[0]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@commitandrollback
|
|
|
|
|
|
|
|
def createDb(self, cur, incremental=False):
|
|
|
|
|
|
|
|
return self._createDb(cur, incremental);
|
|
|
|
|
|
|
|
|
|
|
|
@commitandrollback
|
|
|
|
@commitandrollback
|
|
|
|
def updateDb(self, cur, version):
|
|
|
|
def updateDb(self, cur, version):
|
|
|
|
"""Update an existing database, called during initialisation.
|
|
|
|
"""Update an existing database, called during initialisation.
|
|
|
@ -347,7 +354,7 @@ class Fail2BanDb(object):
|
|
|
|
"INSERT INTO logs SELECT * from logs_temp;"
|
|
|
|
"INSERT INTO logs SELECT * from logs_temp;"
|
|
|
|
"DROP TABLE logs_temp;"
|
|
|
|
"DROP TABLE logs_temp;"
|
|
|
|
"UPDATE fail2banDb SET version = 2;"
|
|
|
|
"UPDATE fail2banDb SET version = 2;"
|
|
|
|
"COMMIT;" % Fail2BanDb._TABLE_logs)
|
|
|
|
"COMMIT;" % Fail2BanDb._CREATE_TABS['logs'])
|
|
|
|
|
|
|
|
|
|
|
|
cur.execute("SELECT version FROM fail2banDb LIMIT 1")
|
|
|
|
cur.execute("SELECT version FROM fail2banDb LIMIT 1")
|
|
|
|
return cur.fetchone()[0]
|
|
|
|
return cur.fetchone()[0]
|
|
|
|