daily autocommit
This commit is contained in:
@@ -1,391 +0,0 @@
|
||||
# emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: t -*-
|
||||
# vi: set ft=python sts=4 ts=4 sw=4 noet :
|
||||
|
||||
# This file is part of Fail2Ban.
|
||||
#
|
||||
# Fail2Ban is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Fail2Ban is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Fail2Ban; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
|
||||
import sys
|
||||
if sys.version_info < (2, 7): # pragma: no cover
|
||||
raise ImportError("badips.py action requires Python >= 2.7")
|
||||
import json
|
||||
import threading
|
||||
import logging
|
||||
if sys.version_info >= (3, ): # pragma: 2.x no cover
|
||||
from urllib.request import Request, urlopen
|
||||
from urllib.parse import urlencode
|
||||
from urllib.error import HTTPError
|
||||
else: # pragma: 3.x no cover
|
||||
from urllib2 import Request, urlopen, HTTPError
|
||||
from urllib import urlencode
|
||||
|
||||
from fail2ban.server.actions import Actions, ActionBase, BanTicket
|
||||
from fail2ban.helpers import splitwords, str2LogLevel
|
||||
|
||||
|
||||
|
||||
class BadIPsAction(ActionBase): # pragma: no cover - may be unavailable
|
||||
"""Fail2Ban action which reports bans to badips.com, and also
|
||||
blacklist bad IPs listed on badips.com by using another action's
|
||||
ban method.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
jail : Jail
|
||||
The jail which the action belongs to.
|
||||
name : str
|
||||
Name assigned to the action.
|
||||
category : str
|
||||
Valid badips.com category for reporting failures.
|
||||
score : int, optional
|
||||
Minimum score for bad IPs. Default 3.
|
||||
age : str, optional
|
||||
Age of last report for bad IPs, per badips.com syntax.
|
||||
Default "24h" (24 hours)
|
||||
banaction : str, optional
|
||||
Name of banaction to use for blacklisting bad IPs. If `None`,
|
||||
no blacklist of IPs will take place.
|
||||
Default `None`.
|
||||
bancategory : str, optional
|
||||
Name of category to use for blacklisting, which can differ
|
||||
from category used for reporting. e.g. may want to report
|
||||
"postfix", but want to use whole "mail" category for blacklist.
|
||||
Default `category`.
|
||||
bankey : str, optional
|
||||
Key issued by badips.com to retrieve personal list
|
||||
of blacklist IPs.
|
||||
updateperiod : int, optional
|
||||
Time in seconds between updating bad IPs blacklist.
|
||||
Default 900 (15 minutes)
|
||||
loglevel : int/str, optional
|
||||
Log level of the message when an IP is (un)banned.
|
||||
Default `DEBUG`.
|
||||
Can be also supplied as two-value list (comma- or space separated) to
|
||||
provide level of the summary message when a group of IPs is (un)banned.
|
||||
Example `DEBUG,INFO`.
|
||||
agent : str, optional
|
||||
User agent transmitted to server.
|
||||
Default `Fail2Ban/ver.`
|
||||
|
||||
Raises
|
||||
------
|
||||
ValueError
|
||||
If invalid `category`, `score`, `banaction` or `updateperiod`.
|
||||
"""
|
||||
|
||||
TIMEOUT = 10
|
||||
_badips = "https://www.badips.com"
|
||||
def _Request(self, url, **argv):
|
||||
return Request(url, headers={'User-Agent': self.agent}, **argv)
|
||||
|
||||
def __init__(self, jail, name, category, score=3, age="24h",
|
||||
banaction=None, bancategory=None, bankey=None, updateperiod=900,
|
||||
loglevel='DEBUG', agent="Fail2Ban", timeout=TIMEOUT):
|
||||
super(BadIPsAction, self).__init__(jail, name)
|
||||
|
||||
self.timeout = timeout
|
||||
self.agent = agent
|
||||
self.category = category
|
||||
self.score = score
|
||||
self.age = age
|
||||
self.banaction = banaction
|
||||
self.bancategory = bancategory or category
|
||||
self.bankey = bankey
|
||||
loglevel = splitwords(loglevel)
|
||||
self.sumloglevel = str2LogLevel(loglevel[-1])
|
||||
self.loglevel = str2LogLevel(loglevel[0])
|
||||
self.updateperiod = updateperiod
|
||||
|
||||
self._bannedips = set()
|
||||
# Used later for threading.Timer for updating badips
|
||||
self._timer = None
|
||||
|
||||
@staticmethod
|
||||
def isAvailable(timeout=1):
|
||||
try:
|
||||
response = urlopen(Request("/".join([BadIPsAction._badips]),
|
||||
headers={'User-Agent': "Fail2Ban"}), timeout=timeout)
|
||||
return True, ''
|
||||
except Exception as e: # pragma: no cover
|
||||
return False, e
|
||||
|
||||
def logError(self, response, what=''): # pragma: no cover - sporadical (502: Bad Gateway, etc)
|
||||
messages = {}
|
||||
try:
|
||||
messages = json.loads(response.read().decode('utf-8'))
|
||||
except:
|
||||
pass
|
||||
self._logSys.error(
|
||||
"%s. badips.com response: '%s'", what,
|
||||
messages.get('err', 'Unknown'))
|
||||
|
||||
def getCategories(self, incParents=False):
|
||||
"""Get badips.com categories.
|
||||
|
||||
Returns
|
||||
-------
|
||||
set
|
||||
Set of categories.
|
||||
|
||||
Raises
|
||||
------
|
||||
HTTPError
|
||||
Any issues with badips.com request.
|
||||
ValueError
|
||||
If badips.com response didn't contain necessary information
|
||||
"""
|
||||
try:
|
||||
response = urlopen(
|
||||
self._Request("/".join([self._badips, "get", "categories"])), timeout=self.timeout)
|
||||
except HTTPError as response: # pragma: no cover
|
||||
self.logError(response, "Failed to fetch categories")
|
||||
raise
|
||||
else:
|
||||
response_json = json.loads(response.read().decode('utf-8'))
|
||||
if not 'categories' in response_json:
|
||||
err = "badips.com response lacked categories specification. Response was: %s" \
|
||||
% (response_json,)
|
||||
self._logSys.error(err)
|
||||
raise ValueError(err)
|
||||
categories = response_json['categories']
|
||||
categories_names = set(
|
||||
value['Name'] for value in categories)
|
||||
if incParents:
|
||||
categories_names.update(set(
|
||||
value['Parent'] for value in categories
|
||||
if "Parent" in value))
|
||||
return categories_names
|
||||
|
||||
def getList(self, category, score, age, key=None):
|
||||
"""Get badips.com list of bad IPs.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
category : str
|
||||
Valid badips.com category.
|
||||
score : int
|
||||
Minimum score for bad IPs.
|
||||
age : str
|
||||
Age of last report for bad IPs, per badips.com syntax.
|
||||
key : str, optional
|
||||
Key issued by badips.com to fetch IPs reported with the
|
||||
associated key.
|
||||
|
||||
Returns
|
||||
-------
|
||||
set
|
||||
Set of bad IPs.
|
||||
|
||||
Raises
|
||||
------
|
||||
HTTPError
|
||||
Any issues with badips.com request.
|
||||
"""
|
||||
try:
|
||||
url = "?".join([
|
||||
"/".join([self._badips, "get", "list", category, str(score)]),
|
||||
urlencode({'age': age})])
|
||||
if key:
|
||||
url = "&".join([url, urlencode({'key': key})])
|
||||
self._logSys.debug('badips.com: get list, url: %r', url)
|
||||
response = urlopen(self._Request(url), timeout=self.timeout)
|
||||
except HTTPError as response: # pragma: no cover
|
||||
self.logError(response, "Failed to fetch bad IP list")
|
||||
raise
|
||||
else:
|
||||
return set(response.read().decode('utf-8').split())
|
||||
|
||||
@property
|
||||
def category(self):
|
||||
"""badips.com category for reporting IPs.
|
||||
"""
|
||||
return self._category
|
||||
|
||||
@category.setter
|
||||
def category(self, category):
|
||||
if category not in self.getCategories():
|
||||
self._logSys.error("Category name '%s' not valid. "
|
||||
"see badips.com for list of valid categories",
|
||||
category)
|
||||
raise ValueError("Invalid category: %s" % category)
|
||||
self._category = category
|
||||
|
||||
@property
|
||||
def bancategory(self):
|
||||
"""badips.com bancategory for fetching IPs.
|
||||
"""
|
||||
return self._bancategory
|
||||
|
||||
@bancategory.setter
|
||||
def bancategory(self, bancategory):
|
||||
if bancategory != "any" and bancategory not in self.getCategories(incParents=True):
|
||||
self._logSys.error("Category name '%s' not valid. "
|
||||
"see badips.com for list of valid categories",
|
||||
bancategory)
|
||||
raise ValueError("Invalid bancategory: %s" % bancategory)
|
||||
self._bancategory = bancategory
|
||||
|
||||
@property
|
||||
def score(self):
|
||||
"""badips.com minimum score for fetching IPs.
|
||||
"""
|
||||
return self._score
|
||||
|
||||
@score.setter
|
||||
def score(self, score):
|
||||
score = int(score)
|
||||
if 0 <= score <= 5:
|
||||
self._score = score
|
||||
else:
|
||||
raise ValueError("Score must be 0-5")
|
||||
|
||||
@property
|
||||
def banaction(self):
|
||||
"""Jail action to use for banning/unbanning.
|
||||
"""
|
||||
return self._banaction
|
||||
|
||||
@banaction.setter
|
||||
def banaction(self, banaction):
|
||||
if banaction is not None and banaction not in self._jail.actions:
|
||||
self._logSys.error("Action name '%s' not in jail '%s'",
|
||||
banaction, self._jail.name)
|
||||
raise ValueError("Invalid banaction")
|
||||
self._banaction = banaction
|
||||
|
||||
@property
|
||||
def updateperiod(self):
|
||||
"""Period in seconds between banned bad IPs will be updated.
|
||||
"""
|
||||
return self._updateperiod
|
||||
|
||||
@updateperiod.setter
|
||||
def updateperiod(self, updateperiod):
|
||||
updateperiod = int(updateperiod)
|
||||
if updateperiod > 0:
|
||||
self._updateperiod = updateperiod
|
||||
else:
|
||||
raise ValueError("Update period must be integer greater than 0")
|
||||
|
||||
def _banIPs(self, ips):
|
||||
for ip in ips:
|
||||
try:
|
||||
ai = Actions.ActionInfo(BanTicket(ip), self._jail)
|
||||
self._jail.actions[self.banaction].ban(ai)
|
||||
except Exception as e:
|
||||
self._logSys.error(
|
||||
"Error banning IP %s for jail '%s' with action '%s': %s",
|
||||
ip, self._jail.name, self.banaction, e,
|
||||
exc_info=self._logSys.getEffectiveLevel()<=logging.DEBUG)
|
||||
else:
|
||||
self._bannedips.add(ip)
|
||||
self._logSys.log(self.loglevel,
|
||||
"Banned IP %s for jail '%s' with action '%s'",
|
||||
ip, self._jail.name, self.banaction)
|
||||
|
||||
def _unbanIPs(self, ips):
|
||||
for ip in ips:
|
||||
try:
|
||||
ai = Actions.ActionInfo(BanTicket(ip), self._jail)
|
||||
self._jail.actions[self.banaction].unban(ai)
|
||||
except Exception as e:
|
||||
self._logSys.error(
|
||||
"Error unbanning IP %s for jail '%s' with action '%s': %s",
|
||||
ip, self._jail.name, self.banaction, e,
|
||||
exc_info=self._logSys.getEffectiveLevel()<=logging.DEBUG)
|
||||
else:
|
||||
self._logSys.log(self.loglevel,
|
||||
"Unbanned IP %s for jail '%s' with action '%s'",
|
||||
ip, self._jail.name, self.banaction)
|
||||
finally:
|
||||
self._bannedips.remove(ip)
|
||||
|
||||
def start(self):
|
||||
"""If `banaction` set, blacklists bad IPs.
|
||||
"""
|
||||
if self.banaction is not None:
|
||||
self.update()
|
||||
|
||||
def update(self):
|
||||
"""If `banaction` set, updates blacklisted IPs.
|
||||
|
||||
Queries badips.com for list of bad IPs, removing IPs from the
|
||||
blacklist if no longer present, and adds new bad IPs to the
|
||||
blacklist.
|
||||
"""
|
||||
if self.banaction is not None:
|
||||
if self._timer:
|
||||
self._timer.cancel()
|
||||
self._timer = None
|
||||
|
||||
try:
|
||||
ips = self.getList(
|
||||
self.bancategory, self.score, self.age, self.bankey)
|
||||
# Remove old IPs no longer listed
|
||||
s = self._bannedips - ips
|
||||
m = len(s)
|
||||
self._unbanIPs(s)
|
||||
# Add new IPs which are now listed
|
||||
s = ips - self._bannedips
|
||||
p = len(s)
|
||||
self._banIPs(s)
|
||||
if m != 0 or p != 0:
|
||||
self._logSys.log(self.sumloglevel,
|
||||
"Updated IPs for jail '%s' (-%d/+%d)",
|
||||
self._jail.name, m, p)
|
||||
self._logSys.debug(
|
||||
"Next update for jail '%' in %i seconds",
|
||||
self._jail.name, self.updateperiod)
|
||||
finally:
|
||||
self._timer = threading.Timer(self.updateperiod, self.update)
|
||||
self._timer.start()
|
||||
|
||||
def stop(self):
|
||||
"""If `banaction` set, clears blacklisted IPs.
|
||||
"""
|
||||
if self.banaction is not None:
|
||||
if self._timer:
|
||||
self._timer.cancel()
|
||||
self._timer = None
|
||||
self._unbanIPs(self._bannedips.copy())
|
||||
|
||||
def ban(self, aInfo):
|
||||
"""Reports banned IP to badips.com.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
aInfo : dict
|
||||
Dictionary which includes information in relation to
|
||||
the ban.
|
||||
|
||||
Raises
|
||||
------
|
||||
HTTPError
|
||||
Any issues with badips.com request.
|
||||
"""
|
||||
try:
|
||||
url = "/".join([self._badips, "add", self.category, str(aInfo['ip'])])
|
||||
self._logSys.debug('badips.com: ban, url: %r', url)
|
||||
response = urlopen(self._Request(url), timeout=self.timeout)
|
||||
except HTTPError as response: # pragma: no cover
|
||||
self.logError(response, "Failed to ban")
|
||||
raise
|
||||
else:
|
||||
messages = json.loads(response.read().decode('utf-8'))
|
||||
self._logSys.debug(
|
||||
"Response from badips.com report: '%s'",
|
||||
messages['suc'])
|
||||
|
||||
Action = BadIPsAction
|
||||
@@ -1,377 +0,0 @@
|
||||
# emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: t -*-
|
||||
# vi: set ft=python sts=4 ts=4 sw=4 noet :
|
||||
|
||||
# This file is part of Fail2Ban.
|
||||
#
|
||||
# Fail2Ban is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Fail2Ban is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Fail2Ban; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
|
||||
import sys
|
||||
if sys.version_info < (2, 7):
|
||||
raise ImportError("badips.py action requires Python >= 2.7")
|
||||
import json
|
||||
import threading
|
||||
import logging
|
||||
if sys.version_info >= (3, ):
|
||||
from urllib.request import Request, urlopen
|
||||
from urllib.parse import urlencode
|
||||
from urllib.error import HTTPError
|
||||
else:
|
||||
from urllib2 import Request, urlopen, HTTPError
|
||||
from urllib import urlencode
|
||||
|
||||
from fail2ban.server.actions import ActionBase
|
||||
|
||||
|
||||
class BadIPsAction(ActionBase):
|
||||
"""Fail2Ban action which reports bans to badips.com, and also
|
||||
blacklist bad IPs listed on badips.com by using another action's
|
||||
ban method.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
jail : Jail
|
||||
The jail which the action belongs to.
|
||||
name : str
|
||||
Name assigned to the action.
|
||||
category : str
|
||||
Valid badips.com category for reporting failures.
|
||||
score : int, optional
|
||||
Minimum score for bad IPs. Default 3.
|
||||
age : str, optional
|
||||
Age of last report for bad IPs, per badips.com syntax.
|
||||
Default "24h" (24 hours)
|
||||
key : str, optional
|
||||
Key issued by badips.com to report bans, for later retrieval
|
||||
of personalised content.
|
||||
banaction : str, optional
|
||||
Name of banaction to use for blacklisting bad IPs. If `None`,
|
||||
no blacklist of IPs will take place.
|
||||
Default `None`.
|
||||
bancategory : str, optional
|
||||
Name of category to use for blacklisting, which can differ
|
||||
from category used for reporting. e.g. may want to report
|
||||
"postfix", but want to use whole "mail" category for blacklist.
|
||||
Default `category`.
|
||||
bankey : str, optional
|
||||
Key issued by badips.com to blacklist IPs reported with the
|
||||
associated key.
|
||||
updateperiod : int, optional
|
||||
Time in seconds between updating bad IPs blacklist.
|
||||
Default 900 (15 minutes)
|
||||
agent : str, optional
|
||||
User agent transmitted to server.
|
||||
Default `Fail2Ban/ver.`
|
||||
|
||||
Raises
|
||||
------
|
||||
ValueError
|
||||
If invalid `category`, `score`, `banaction` or `updateperiod`.
|
||||
"""
|
||||
|
||||
TIMEOUT = 10
|
||||
_badips = "http://www.badips.com"
|
||||
def _Request(self, url, **argv):
|
||||
return Request(url, headers={'User-Agent': self.agent}, **argv)
|
||||
|
||||
def __init__(self, jail, name, category, score=3, age="24h", key=None,
|
||||
banaction=None, bancategory=None, bankey=None, updateperiod=900, agent="Fail2Ban",
|
||||
timeout=TIMEOUT):
|
||||
super(BadIPsAction, self).__init__(jail, name)
|
||||
|
||||
self.timeout = timeout
|
||||
self.agent = agent
|
||||
self.category = category
|
||||
self.score = score
|
||||
self.age = age
|
||||
self.key = key
|
||||
self.banaction = banaction
|
||||
self.bancategory = bancategory or category
|
||||
self.bankey = bankey
|
||||
self.updateperiod = updateperiod
|
||||
|
||||
self._bannedips = set()
|
||||
# Used later for threading.Timer for updating badips
|
||||
self._timer = None
|
||||
|
||||
def getCategories(self, incParents=False):
|
||||
"""Get badips.com categories.
|
||||
|
||||
Returns
|
||||
-------
|
||||
set
|
||||
Set of categories.
|
||||
|
||||
Raises
|
||||
------
|
||||
HTTPError
|
||||
Any issues with badips.com request.
|
||||
ValueError
|
||||
If badips.com response didn't contain necessary information
|
||||
"""
|
||||
try:
|
||||
response = urlopen(
|
||||
self._Request("/".join([self._badips, "get", "categories"])), timeout=self.timeout)
|
||||
except HTTPError as response:
|
||||
messages = json.loads(response.read().decode('utf-8'))
|
||||
self._logSys.error(
|
||||
"Failed to fetch categories. badips.com response: '%s'",
|
||||
messages['err'])
|
||||
raise
|
||||
else:
|
||||
response_json = json.loads(response.read().decode('utf-8'))
|
||||
if not 'categories' in response_json:
|
||||
err = "badips.com response lacked categories specification. Response was: %s" \
|
||||
% (response_json,)
|
||||
self._logSys.error(err)
|
||||
raise ValueError(err)
|
||||
categories = response_json['categories']
|
||||
categories_names = set(
|
||||
value['Name'] for value in categories)
|
||||
if incParents:
|
||||
categories_names.update(set(
|
||||
value['Parent'] for value in categories
|
||||
if "Parent" in value))
|
||||
return categories_names
|
||||
|
||||
def getList(self, category, score, age, key=None):
|
||||
"""Get badips.com list of bad IPs.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
category : str
|
||||
Valid badips.com category.
|
||||
score : int
|
||||
Minimum score for bad IPs.
|
||||
age : str
|
||||
Age of last report for bad IPs, per badips.com syntax.
|
||||
key : str, optional
|
||||
Key issued by badips.com to fetch IPs reported with the
|
||||
associated key.
|
||||
|
||||
Returns
|
||||
-------
|
||||
set
|
||||
Set of bad IPs.
|
||||
|
||||
Raises
|
||||
------
|
||||
HTTPError
|
||||
Any issues with badips.com request.
|
||||
"""
|
||||
try:
|
||||
url = "?".join([
|
||||
"/".join([self._badips, "get", "list", category, str(score)]),
|
||||
urlencode({'age': age})])
|
||||
if key:
|
||||
url = "&".join([url, urlencode({'key': key})])
|
||||
response = urlopen(self._Request(url), timeout=self.timeout)
|
||||
except HTTPError as response:
|
||||
messages = json.loads(response.read().decode('utf-8'))
|
||||
self._logSys.error(
|
||||
"Failed to fetch bad IP list. badips.com response: '%s'",
|
||||
messages['err'])
|
||||
raise
|
||||
else:
|
||||
return set(response.read().decode('utf-8').split())
|
||||
|
||||
@property
|
||||
def category(self):
|
||||
"""badips.com category for reporting IPs.
|
||||
"""
|
||||
return self._category
|
||||
|
||||
@category.setter
|
||||
def category(self, category):
|
||||
if category not in self.getCategories():
|
||||
self._logSys.error("Category name '%s' not valid. "
|
||||
"see badips.com for list of valid categories",
|
||||
category)
|
||||
raise ValueError("Invalid category: %s" % category)
|
||||
self._category = category
|
||||
|
||||
@property
|
||||
def bancategory(self):
|
||||
"""badips.com bancategory for fetching IPs.
|
||||
"""
|
||||
return self._bancategory
|
||||
|
||||
@bancategory.setter
|
||||
def bancategory(self, bancategory):
|
||||
if bancategory not in self.getCategories(incParents=True):
|
||||
self._logSys.error("Category name '%s' not valid. "
|
||||
"see badips.com for list of valid categories",
|
||||
bancategory)
|
||||
raise ValueError("Invalid bancategory: %s" % bancategory)
|
||||
self._bancategory = bancategory
|
||||
|
||||
@property
|
||||
def score(self):
|
||||
"""badips.com minimum score for fetching IPs.
|
||||
"""
|
||||
return self._score
|
||||
|
||||
@score.setter
|
||||
def score(self, score):
|
||||
score = int(score)
|
||||
if 0 <= score <= 5:
|
||||
self._score = score
|
||||
else:
|
||||
raise ValueError("Score must be 0-5")
|
||||
|
||||
@property
|
||||
def banaction(self):
|
||||
"""Jail action to use for banning/unbanning.
|
||||
"""
|
||||
return self._banaction
|
||||
|
||||
@banaction.setter
|
||||
def banaction(self, banaction):
|
||||
if banaction is not None and banaction not in self._jail.actions:
|
||||
self._logSys.error("Action name '%s' not in jail '%s'",
|
||||
banaction, self._jail.name)
|
||||
raise ValueError("Invalid banaction")
|
||||
self._banaction = banaction
|
||||
|
||||
@property
|
||||
def updateperiod(self):
|
||||
"""Period in seconds between banned bad IPs will be updated.
|
||||
"""
|
||||
return self._updateperiod
|
||||
|
||||
@updateperiod.setter
|
||||
def updateperiod(self, updateperiod):
|
||||
updateperiod = int(updateperiod)
|
||||
if updateperiod > 0:
|
||||
self._updateperiod = updateperiod
|
||||
else:
|
||||
raise ValueError("Update period must be integer greater than 0")
|
||||
|
||||
def _banIPs(self, ips):
|
||||
for ip in ips:
|
||||
try:
|
||||
self._jail.actions[self.banaction].ban({
|
||||
'ip': ip,
|
||||
'failures': 0,
|
||||
'matches': "",
|
||||
'ipmatches': "",
|
||||
'ipjailmatches': "",
|
||||
})
|
||||
except Exception as e:
|
||||
self._logSys.error(
|
||||
"Error banning IP %s for jail '%s' with action '%s': %s",
|
||||
ip, self._jail.name, self.banaction, e,
|
||||
exc_info=self._logSys.getEffectiveLevel()<=logging.DEBUG)
|
||||
else:
|
||||
self._bannedips.add(ip)
|
||||
self._logSys.info(
|
||||
"Banned IP %s for jail '%s' with action '%s'",
|
||||
ip, self._jail.name, self.banaction)
|
||||
|
||||
def _unbanIPs(self, ips):
|
||||
for ip in ips:
|
||||
try:
|
||||
self._jail.actions[self.banaction].unban({
|
||||
'ip': ip,
|
||||
'failures': 0,
|
||||
'matches': "",
|
||||
'ipmatches': "",
|
||||
'ipjailmatches': "",
|
||||
})
|
||||
except Exception as e:
|
||||
self._logSys.info(
|
||||
"Error unbanning IP %s for jail '%s' with action '%s': %s",
|
||||
ip, self._jail.name, self.banaction, e,
|
||||
exc_info=self._logSys.getEffectiveLevel()<=logging.DEBUG)
|
||||
else:
|
||||
self._logSys.info(
|
||||
"Unbanned IP %s for jail '%s' with action '%s'",
|
||||
ip, self._jail.name, self.banaction)
|
||||
finally:
|
||||
self._bannedips.remove(ip)
|
||||
|
||||
def start(self):
|
||||
"""If `banaction` set, blacklists bad IPs.
|
||||
"""
|
||||
if self.banaction is not None:
|
||||
self.update()
|
||||
|
||||
def update(self):
|
||||
"""If `banaction` set, updates blacklisted IPs.
|
||||
|
||||
Queries badips.com for list of bad IPs, removing IPs from the
|
||||
blacklist if no longer present, and adds new bad IPs to the
|
||||
blacklist.
|
||||
"""
|
||||
if self.banaction is not None:
|
||||
if self._timer:
|
||||
self._timer.cancel()
|
||||
self._timer = None
|
||||
|
||||
try:
|
||||
ips = self.getList(
|
||||
self.bancategory, self.score, self.age, self.bankey)
|
||||
# Remove old IPs no longer listed
|
||||
self._unbanIPs(self._bannedips - ips)
|
||||
# Add new IPs which are now listed
|
||||
self._banIPs(ips - self._bannedips)
|
||||
|
||||
self._logSys.info(
|
||||
"Updated IPs for jail '%s'. Update again in %i seconds",
|
||||
self._jail.name, self.updateperiod)
|
||||
finally:
|
||||
self._timer = threading.Timer(self.updateperiod, self.update)
|
||||
self._timer.start()
|
||||
|
||||
def stop(self):
|
||||
"""If `banaction` set, clears blacklisted IPs.
|
||||
"""
|
||||
if self.banaction is not None:
|
||||
if self._timer:
|
||||
self._timer.cancel()
|
||||
self._timer = None
|
||||
self._unbanIPs(self._bannedips.copy())
|
||||
|
||||
def ban(self, aInfo):
|
||||
"""Reports banned IP to badips.com.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
aInfo : dict
|
||||
Dictionary which includes information in relation to
|
||||
the ban.
|
||||
|
||||
Raises
|
||||
------
|
||||
HTTPError
|
||||
Any issues with badips.com request.
|
||||
"""
|
||||
try:
|
||||
url = "/".join([self._badips, "add", self.category, aInfo['ip']])
|
||||
if self.key:
|
||||
url = "?".join([url, urlencode({'key': self.key})])
|
||||
response = urlopen(self._Request(url), timeout=self.timeout)
|
||||
except HTTPError as response:
|
||||
messages = json.loads(response.read().decode('utf-8'))
|
||||
self._logSys.error(
|
||||
"Response from badips.com report: '%s'",
|
||||
messages['err'])
|
||||
raise
|
||||
else:
|
||||
messages = json.loads(response.read().decode('utf-8'))
|
||||
self._logSys.info(
|
||||
"Response from badips.com report: '%s'",
|
||||
messages['suc'])
|
||||
|
||||
Action = BadIPsAction
|
||||
@@ -1,93 +0,0 @@
|
||||
# Fail2Ban configuration file
|
||||
#
|
||||
# Author: Daniel Black
|
||||
#
|
||||
# This is a included configuration file and includes the definitions for the iptables
|
||||
# used in all iptables based actions by default.
|
||||
#
|
||||
# The user can override the defaults in iptables-common.local
|
||||
#
|
||||
# Modified: Alexander Koeppe <format_c@online.de>, Serg G. Brester <serg.brester@sebres.de>
|
||||
# made config file IPv6 capable (see new section Init?family=inet6)
|
||||
|
||||
[INCLUDES]
|
||||
|
||||
after = iptables-blocktype.local
|
||||
iptables-common.local
|
||||
# iptables-blocktype.local is obsolete
|
||||
|
||||
[Definition]
|
||||
|
||||
# Option: actionflush
|
||||
# Notes.: command executed once to flush IPS, by shutdown (resp. by stop of the jail or this action)
|
||||
# Values: CMD
|
||||
#
|
||||
actionflush = <iptables> -F f2b-<name>
|
||||
|
||||
|
||||
[Init]
|
||||
|
||||
# Option: chain
|
||||
# Notes specifies the iptables chain to which the Fail2Ban rules should be
|
||||
# added
|
||||
# Values: STRING Default: INPUT
|
||||
chain = INPUT
|
||||
|
||||
# Default name of the chain
|
||||
#
|
||||
name = default
|
||||
|
||||
# Option: port
|
||||
# Notes.: specifies port to monitor
|
||||
# Values: [ NUM | STRING ] Default:
|
||||
#
|
||||
port = ssh
|
||||
|
||||
# Option: protocol
|
||||
# Notes.: internally used by config reader for interpolations.
|
||||
# Values: [ tcp | udp | icmp | all ] Default: tcp
|
||||
#
|
||||
protocol = tcp
|
||||
|
||||
# Option: blocktype
|
||||
# Note: This is what the action does with rules. This can be any jump target
|
||||
# as per the iptables man page (section 8). Common values are DROP
|
||||
# REJECT, REJECT --reject-with icmp-port-unreachable
|
||||
# Values: STRING
|
||||
#blocktype = REJECT --reject-with icmp-port-unreachable
|
||||
blocktype = DROP
|
||||
|
||||
# Option: returntype
|
||||
# Note: This is the default rule on "actionstart". This should be RETURN
|
||||
# in all (blocking) actions, except REJECT in allowing actions.
|
||||
# Values: STRING
|
||||
returntype = RETURN
|
||||
|
||||
# Option: lockingopt
|
||||
# Notes.: Option was introduced to iptables to prevent multiple instances from
|
||||
# running concurrently and causing irratic behavior. -w was introduced
|
||||
# in iptables 1.4.20, so might be absent on older systems
|
||||
# See https://github.com/fail2ban/fail2ban/issues/1122
|
||||
# Values: STRING
|
||||
lockingopt = -w
|
||||
|
||||
# Option: iptables
|
||||
# Notes.: Actual command to be executed, including common to all calls options
|
||||
# Values: STRING
|
||||
iptables = iptables <lockingopt>
|
||||
|
||||
|
||||
[Init?family=inet6]
|
||||
|
||||
# Option: blocktype (ipv6)
|
||||
# Note: This is what the action does with rules. This can be any jump target
|
||||
# as per the iptables man page (section 8). Common values are DROP
|
||||
# REJECT, REJECT --reject-with icmp6-port-unreachable
|
||||
# Values: STRING
|
||||
blocktype = REJECT --reject-with icmp6-port-unreachable
|
||||
|
||||
# Option: iptables (ipv6)
|
||||
# Notes.: Actual command to be executed, including common to all calls options
|
||||
# Values: STRING
|
||||
iptables = ip6tables <lockingopt>
|
||||
|
||||
@@ -1,90 +0,0 @@
|
||||
# Fail2Ban configuration file
|
||||
#
|
||||
# Authors: Sergey G Brester (sebres), Daniel Black, Alexander Koeppe
|
||||
#
|
||||
# This is for ipset protocol 6 (and hopefully later) (ipset v6.14).
|
||||
# Use ipset -V to see the protocol and version. Version 4 should use
|
||||
# iptables-ipset-proto4.conf.
|
||||
#
|
||||
# This requires the program ipset which is normally in package called ipset.
|
||||
#
|
||||
# IPset was a feature introduced in the linux kernel 2.6.39 and 3.0.0 kernels.
|
||||
#
|
||||
# If you are running on an older kernel you make need to patch in external
|
||||
# modules.
|
||||
#
|
||||
|
||||
[INCLUDES]
|
||||
|
||||
before = iptables.conf
|
||||
|
||||
[Definition]
|
||||
|
||||
# Option: actionstart
|
||||
# Notes.: command executed on demand at the first ban (or at the start of Fail2Ban if actionstart_on_demand is set to false).
|
||||
# Values: CMD
|
||||
#
|
||||
actionstart = ipset -exist create <ipmset> hash:ip timeout <default-ipsettime> <familyopt>
|
||||
<_ipt_add_rules>
|
||||
|
||||
# Option: actionflush
|
||||
# Notes.: command executed once to flush IPS, by shutdown (resp. by stop of the jail or this action)
|
||||
# Values: CMD
|
||||
#
|
||||
actionflush = ipset flush <ipmset>
|
||||
|
||||
# Option: actionstop
|
||||
# Notes.: command executed at the stop of jail (or at the end of Fail2Ban)
|
||||
# Values: CMD
|
||||
#
|
||||
actionstop = <_ipt_del_rules>
|
||||
<actionflush>
|
||||
ipset destroy <ipmset>
|
||||
|
||||
# Option: actionban
|
||||
# Notes.: command executed when banning an IP. Take care that the
|
||||
# command is executed with Fail2Ban user rights.
|
||||
# Tags: See jail.conf(5) man page
|
||||
# Values: CMD
|
||||
#
|
||||
actionban = ipset -exist add <ipmset> <ip> timeout <ipsettime>
|
||||
|
||||
# actionprolong = %(actionban)s
|
||||
|
||||
# Option: actionunban
|
||||
# Notes.: command executed when unbanning an IP. Take care that the
|
||||
# command is executed with Fail2Ban user rights.
|
||||
# Tags: See jail.conf(5) man page
|
||||
# Values: CMD
|
||||
#
|
||||
actionunban = ipset -exist del <ipmset> <ip>
|
||||
|
||||
# Several capabilities used internaly:
|
||||
|
||||
rule-jump = -m set --match-set <ipmset> src -j <blocktype>
|
||||
|
||||
|
||||
[Init]
|
||||
|
||||
# Option: default-ipsettime
|
||||
# Notes: specifies default timeout in seconds (handled default ipset timeout only)
|
||||
# Values: [ NUM ] Default: 0 (no timeout, managed by fail2ban by unban)
|
||||
default-ipsettime = 0
|
||||
|
||||
# Option: ipsettime
|
||||
# Notes: specifies ticket timeout (handled ipset timeout only)
|
||||
# Values: [ NUM ] Default: 0 (managed by fail2ban by unban)
|
||||
ipsettime = 0
|
||||
|
||||
# expresion to caclulate timeout from bantime, example:
|
||||
# banaction = %(known/banaction)s[ipsettime='<timeout-bantime>']
|
||||
timeout-bantime = $([ "<bantime>" -le 2147483 ] && echo "<bantime>" || echo 0)
|
||||
|
||||
ipmset = f2b-<name>
|
||||
familyopt =
|
||||
|
||||
|
||||
[Init?family=inet6]
|
||||
|
||||
ipmset = f2b-<name>6
|
||||
familyopt = family inet6
|
||||
@@ -1,62 +0,0 @@
|
||||
# Fail2Ban configuration file
|
||||
#
|
||||
# Author: Cyril Jaquier
|
||||
#
|
||||
#
|
||||
|
||||
[Definition]
|
||||
|
||||
# Option: actionstart
|
||||
# Notes.: command executed once at the start of Fail2Ban.
|
||||
# Values: CMD
|
||||
#
|
||||
actionstart = printf %%b "Hi,\n
|
||||
The jail <name> has been started successfully.\n
|
||||
Regards,\n
|
||||
Fail2Ban"|mail -s "[Fail2Ban] <name>: started on `uname -n`" <dest>
|
||||
|
||||
# Option: actionstop
|
||||
# Notes.: command executed once at the end of Fail2Ban
|
||||
# Values: CMD
|
||||
#
|
||||
actionstop = printf %%b "Hi,\n
|
||||
The jail <name> has been stopped.\n
|
||||
Regards,\n
|
||||
Fail2Ban"|mail -s "[Fail2Ban] <name>: stopped on `uname -n`" <dest>
|
||||
|
||||
# Option: actioncheck
|
||||
# Notes.: command executed once before each actionban command
|
||||
# Values: CMD
|
||||
#
|
||||
actioncheck =
|
||||
|
||||
# Option: actionban
|
||||
# Notes.: command executed when banning an IP. Take care that the
|
||||
# command is executed with Fail2Ban user rights.
|
||||
# Tags: See jail.conf(5) man page
|
||||
# Values: CMD
|
||||
#
|
||||
actionban = printf %%b "Hi,\n
|
||||
The IP <ip> has just been banned by Fail2Ban after
|
||||
<failures> attempts against <name>.\n
|
||||
Regards,\n
|
||||
Fail2Ban"|mail -s "[Fail2Ban] <name>: banned <ip> from `uname -n`" <dest>
|
||||
|
||||
# Option: actionunban
|
||||
# Notes.: command executed when unbanning an IP. Take care that the
|
||||
# command is executed with Fail2Ban user rights.
|
||||
# Tags: See jail.conf(5) man page
|
||||
# Values: CMD
|
||||
#
|
||||
actionunban =
|
||||
|
||||
[Init]
|
||||
|
||||
# Default name of the chain
|
||||
#
|
||||
name = default
|
||||
|
||||
# Destination/Addressee of the mail
|
||||
#
|
||||
dest = bogdan@898.ro
|
||||
|
||||
@@ -1,230 +0,0 @@
|
||||
# emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: t -*-
|
||||
# vi: set ft=python sts=4 ts=4 sw=4 noet :
|
||||
|
||||
# This file is part of Fail2Ban.
|
||||
#
|
||||
# Fail2Ban is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Fail2Ban is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Fail2Ban; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
|
||||
import socket
|
||||
import smtplib
|
||||
from email.mime.text import MIMEText
|
||||
from email.utils import formatdate, formataddr
|
||||
|
||||
from fail2ban.server.actions import ActionBase, CallingMap
|
||||
|
||||
messages = {}
|
||||
messages['start'] = \
|
||||
"""Hi,
|
||||
|
||||
The jail %(jailname)s has been started successfully.
|
||||
|
||||
Regards,
|
||||
Fail2Ban"""
|
||||
|
||||
messages['stop'] = \
|
||||
"""Hi,
|
||||
|
||||
The jail %(jailname)s has been stopped.
|
||||
|
||||
Regards,
|
||||
Fail2Ban"""
|
||||
|
||||
messages['ban'] = {}
|
||||
messages['ban']['head'] = \
|
||||
"""Hi,
|
||||
|
||||
The IP %(ip)s has just been banned for %(bantime)i seconds
|
||||
by Fail2Ban after %(failures)i attempts against %(jailname)s.
|
||||
"""
|
||||
messages['ban']['tail'] = \
|
||||
"""
|
||||
Regards,
|
||||
Fail2Ban"""
|
||||
messages['ban']['matches'] = \
|
||||
"""
|
||||
Matches for this ban:
|
||||
%(matches)s
|
||||
"""
|
||||
messages['ban']['ipmatches'] = \
|
||||
"""
|
||||
Matches for %(ip)s:
|
||||
%(ipmatches)s
|
||||
"""
|
||||
messages['ban']['ipjailmatches'] = \
|
||||
"""
|
||||
Matches for %(ip)s for jail %(jailname)s:
|
||||
%(ipjailmatches)s
|
||||
"""
|
||||
|
||||
|
||||
class SMTPAction(ActionBase):
|
||||
"""Fail2Ban action which sends emails to inform on jail starting,
|
||||
stopping and bans.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self, jail, name, host="localhost", user=None, password=None,
|
||||
sendername="Fail2Ban", sender="fail2ban", dest="root", matches=None):
|
||||
"""Initialise action.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
jail : Jail
|
||||
The jail which the action belongs to.
|
||||
name : str
|
||||
Named assigned to the action.
|
||||
host : str, optional
|
||||
SMTP host, of host:port format. Default host "localhost" and
|
||||
port "25"
|
||||
user : str, optional
|
||||
Username used for authentication with SMTP server.
|
||||
password : str, optional
|
||||
Password used for authentication with SMTP server.
|
||||
sendername : str, optional
|
||||
Name to use for from address in email. Default "Fail2Ban".
|
||||
sender : str, optional
|
||||
Email address to use for from address in email.
|
||||
Default "fail2ban".
|
||||
dest : str, optional
|
||||
Email addresses of intended recipient(s) in comma space ", "
|
||||
delimited format. Default "root".
|
||||
matches : str, optional
|
||||
Type of matches to be included from ban in email. Can be one
|
||||
of "matches", "ipmatches" or "ipjailmatches". Default None
|
||||
(see man jail.conf.5).
|
||||
"""
|
||||
|
||||
super(SMTPAction, self).__init__(jail, name)
|
||||
|
||||
self.host = host
|
||||
#TODO: self.ssl = ssl
|
||||
|
||||
self.user = user
|
||||
self.password =password
|
||||
|
||||
self.fromname = sendername
|
||||
self.fromaddr = sender
|
||||
self.toaddr = dest
|
||||
|
||||
self.matches = matches
|
||||
|
||||
self.message_values = CallingMap(
|
||||
jailname = self._jail.name,
|
||||
hostname = socket.gethostname,
|
||||
bantime = lambda: self._jail.actions.getBanTime(),
|
||||
)
|
||||
|
||||
# bypass ban/unban for restored tickets
|
||||
self.norestored = 1
|
||||
|
||||
def _sendMessage(self, subject, text):
|
||||
"""Sends message based on arguments and instance's properties.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
subject : str
|
||||
Subject of the email.
|
||||
text : str
|
||||
Body of the email.
|
||||
|
||||
Raises
|
||||
------
|
||||
SMTPConnectionError
|
||||
Error on connecting to host.
|
||||
SMTPAuthenticationError
|
||||
Error authenticating with SMTP server.
|
||||
SMTPException
|
||||
See Python `smtplib` for full list of other possible
|
||||
exceptions.
|
||||
"""
|
||||
msg = MIMEText(text)
|
||||
msg['Subject'] = subject
|
||||
msg['From'] = formataddr((self.fromname, self.fromaddr))
|
||||
msg['To'] = self.toaddr
|
||||
msg['Date'] = formatdate()
|
||||
|
||||
smtp = smtplib.SMTP()
|
||||
try:
|
||||
self._logSys.debug("Connected to SMTP '%s', response: %i: %s",
|
||||
self.host, *smtp.connect(self.host))
|
||||
if self.user and self.password: # pragma: no cover (ATM no tests covering that)
|
||||
smtp.login(self.user, self.password)
|
||||
failed_recipients = smtp.sendmail(
|
||||
self.fromaddr, self.toaddr.split(", "), msg.as_string())
|
||||
except smtplib.SMTPConnectError: # pragma: no cover
|
||||
self._logSys.error("Error connecting to host '%s'", self.host)
|
||||
raise
|
||||
except smtplib.SMTPAuthenticationError: # pragma: no cover
|
||||
self._logSys.error(
|
||||
"Failed to authenticate with host '%s' user '%s'",
|
||||
self.host, self.user)
|
||||
raise
|
||||
except smtplib.SMTPException: # pragma: no cover
|
||||
self._logSys.error(
|
||||
"Error sending mail to host '%s' from '%s' to '%s'",
|
||||
self.host, self.fromaddr, self.toaddr)
|
||||
raise
|
||||
else:
|
||||
if failed_recipients: # pragma: no cover
|
||||
self._logSys.warning(
|
||||
"Email to '%s' failed to following recipients: %r",
|
||||
self.toaddr, failed_recipients)
|
||||
self._logSys.debug("Email '%s' successfully sent", subject)
|
||||
finally:
|
||||
try:
|
||||
self._logSys.debug("Disconnected from '%s', response %i: %s",
|
||||
self.host, *smtp.quit())
|
||||
except smtplib.SMTPServerDisconnected: # pragma: no cover
|
||||
pass # Not connected
|
||||
|
||||
def start(self):
|
||||
"""Sends email to recipients informing that the jail has started.
|
||||
"""
|
||||
self._sendMessage(
|
||||
"[Fail2Ban] %(jailname)s: started on %(hostname)s" %
|
||||
self.message_values,
|
||||
messages['start'] % self.message_values)
|
||||
|
||||
def stop(self):
|
||||
"""Sends email to recipients informing that the jail has stopped.
|
||||
"""
|
||||
self._sendMessage(
|
||||
"[Fail2Ban] %(jailname)s: stopped on %(hostname)s" %
|
||||
self.message_values,
|
||||
messages['stop'] % self.message_values)
|
||||
|
||||
def ban(self, aInfo):
|
||||
"""Sends email to recipients informing that ban has occurred.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
aInfo : dict
|
||||
Dictionary which includes information in relation to
|
||||
the ban.
|
||||
"""
|
||||
if aInfo.get('restored'):
|
||||
return
|
||||
aInfo.update(self.message_values)
|
||||
message = "".join([
|
||||
messages['ban']['head'],
|
||||
messages['ban'].get(self.matches, ""),
|
||||
messages['ban']['tail']
|
||||
])
|
||||
self._sendMessage(
|
||||
"[Fail2Ban] %(jailname)s: banned %(ip)s from %(hostname)s" %
|
||||
aInfo,
|
||||
message % aInfo)
|
||||
|
||||
Action = SMTPAction
|
||||
@@ -1,225 +0,0 @@
|
||||
# emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: t -*-
|
||||
# vi: set ft=python sts=4 ts=4 sw=4 noet :
|
||||
|
||||
# This file is part of Fail2Ban.
|
||||
#
|
||||
# Fail2Ban is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Fail2Ban is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Fail2Ban; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
|
||||
import socket
|
||||
import smtplib
|
||||
from email.mime.text import MIMEText
|
||||
from email.utils import formatdate, formataddr
|
||||
|
||||
from fail2ban.server.actions import ActionBase, CallingMap
|
||||
|
||||
messages = {}
|
||||
messages['start'] = \
|
||||
"""Hi,
|
||||
|
||||
The jail %(jailname)s has been started successfully.
|
||||
|
||||
Regards,
|
||||
Fail2Ban"""
|
||||
|
||||
messages['stop'] = \
|
||||
"""Hi,
|
||||
|
||||
The jail %(jailname)s has been stopped.
|
||||
|
||||
Regards,
|
||||
Fail2Ban"""
|
||||
|
||||
messages['ban'] = {}
|
||||
messages['ban']['head'] = \
|
||||
"""Hi,
|
||||
|
||||
The IP %(ip)s has just been banned for %(bantime)i seconds
|
||||
by Fail2Ban after %(failures)i attempts against %(jailname)s.
|
||||
"""
|
||||
messages['ban']['tail'] = \
|
||||
"""
|
||||
Regards,
|
||||
Fail2Ban"""
|
||||
messages['ban']['matches'] = \
|
||||
"""
|
||||
Matches for this ban:
|
||||
%(matches)s
|
||||
"""
|
||||
messages['ban']['ipmatches'] = \
|
||||
"""
|
||||
Matches for %(ip)s:
|
||||
%(ipmatches)s
|
||||
"""
|
||||
messages['ban']['ipjailmatches'] = \
|
||||
"""
|
||||
Matches for %(ip)s for jail %(jailname)s:
|
||||
%(ipjailmatches)s
|
||||
"""
|
||||
|
||||
|
||||
class SMTPAction(ActionBase):
|
||||
"""Fail2Ban action which sends emails to inform on jail starting,
|
||||
stopping and bans.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self, jail, name, host="localhost", user=None, password=None,
|
||||
sendername="Fail2Ban", sender="fail2ban", dest="root", matches=None):
|
||||
"""Initialise action.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
jail : Jail
|
||||
The jail which the action belongs to.
|
||||
name : str
|
||||
Named assigned to the action.
|
||||
host : str, optional
|
||||
SMTP host, of host:port format. Default host "localhost" and
|
||||
port "25"
|
||||
user : str, optional
|
||||
Username used for authentication with SMTP server.
|
||||
password : str, optional
|
||||
Password used for authentication with SMTP server.
|
||||
sendername : str, optional
|
||||
Name to use for from address in email. Default "Fail2Ban".
|
||||
sender : str, optional
|
||||
Email address to use for from address in email.
|
||||
Default "fail2ban".
|
||||
dest : str, optional
|
||||
Email addresses of intended recipient(s) in comma space ", "
|
||||
delimited format. Default "root".
|
||||
matches : str, optional
|
||||
Type of matches to be included from ban in email. Can be one
|
||||
of "matches", "ipmatches" or "ipjailmatches". Default None
|
||||
(see man jail.conf.5).
|
||||
"""
|
||||
|
||||
super(SMTPAction, self).__init__(jail, name)
|
||||
|
||||
self.host = host
|
||||
#TODO: self.ssl = ssl
|
||||
|
||||
self.user = user
|
||||
self.password =password
|
||||
|
||||
self.fromname = sendername
|
||||
self.fromaddr = sender
|
||||
self.toaddr = dest
|
||||
|
||||
self.matches = matches
|
||||
|
||||
self.message_values = CallingMap(
|
||||
jailname = self._jail.name,
|
||||
hostname = socket.gethostname,
|
||||
bantime = self._jail.actions.getBanTime,
|
||||
)
|
||||
|
||||
def _sendMessage(self, subject, text):
|
||||
"""Sends message based on arguments and instance's properties.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
subject : str
|
||||
Subject of the email.
|
||||
text : str
|
||||
Body of the email.
|
||||
|
||||
Raises
|
||||
------
|
||||
SMTPConnectionError
|
||||
Error on connecting to host.
|
||||
SMTPAuthenticationError
|
||||
Error authenticating with SMTP server.
|
||||
SMTPException
|
||||
See Python `smtplib` for full list of other possible
|
||||
exceptions.
|
||||
"""
|
||||
msg = MIMEText(text)
|
||||
msg['Subject'] = subject
|
||||
msg['From'] = formataddr((self.fromname, self.fromaddr))
|
||||
msg['To'] = self.toaddr
|
||||
msg['Date'] = formatdate()
|
||||
|
||||
smtp = smtplib.SMTP()
|
||||
try:
|
||||
self._logSys.debug("Connected to SMTP '%s', response: %i: %s",
|
||||
self.host, *smtp.connect(self.host))
|
||||
if self.user and self.password:
|
||||
smtp.login(self.user, self.password)
|
||||
failed_recipients = smtp.sendmail(
|
||||
self.fromaddr, self.toaddr.split(", "), msg.as_string())
|
||||
except smtplib.SMTPConnectError:
|
||||
self._logSys.error("Error connecting to host '%s'", self.host)
|
||||
raise
|
||||
except smtplib.SMTPAuthenticationError:
|
||||
self._logSys.error(
|
||||
"Failed to authenticate with host '%s' user '%s'",
|
||||
self.host, self.user)
|
||||
raise
|
||||
except smtplib.SMTPException:
|
||||
self._logSys.error(
|
||||
"Error sending mail to host '%s' from '%s' to '%s'",
|
||||
self.host, self.fromaddr, self.toaddr)
|
||||
raise
|
||||
else:
|
||||
if failed_recipients:
|
||||
self._logSys.warning(
|
||||
"Email to '%s' failed to following recipients: %r",
|
||||
self.toaddr, failed_recipients)
|
||||
self._logSys.debug("Email '%s' successfully sent", subject)
|
||||
finally:
|
||||
try:
|
||||
self._logSys.debug("Disconnected from '%s', response %i: %s",
|
||||
self.host, *smtp.quit())
|
||||
except smtplib.SMTPServerDisconnected:
|
||||
pass # Not connected
|
||||
|
||||
def start(self):
|
||||
"""Sends email to recipients informing that the jail has started.
|
||||
"""
|
||||
self._sendMessage(
|
||||
"[Fail2Ban] %(jailname)s: started on %(hostname)s" %
|
||||
self.message_values,
|
||||
messages['start'] % self.message_values)
|
||||
|
||||
def stop(self):
|
||||
"""Sends email to recipients informing that the jail has stopped.
|
||||
"""
|
||||
self._sendMessage(
|
||||
"[Fail2Ban] %(jailname)s: stopped on %(hostname)s" %
|
||||
self.message_values,
|
||||
messages['stop'] % self.message_values)
|
||||
|
||||
def ban(self, aInfo):
|
||||
"""Sends email to recipients informing that ban has occurred.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
aInfo : dict
|
||||
Dictionary which includes information in relation to
|
||||
the ban.
|
||||
"""
|
||||
aInfo.update(self.message_values)
|
||||
message = "".join([
|
||||
messages['ban']['head'],
|
||||
messages['ban'].get(self.matches, ""),
|
||||
messages['ban']['tail']
|
||||
])
|
||||
self._sendMessage(
|
||||
"[Fail2Ban] %(jailname)s: banned %(ip)s from %(hostname)s" %
|
||||
aInfo,
|
||||
message % aInfo)
|
||||
|
||||
Action = SMTPAction
|
||||
@@ -1,69 +0,0 @@
|
||||
# Fail2Ban main configuration file
|
||||
#
|
||||
# Comments: use '#' for comment lines and ';' (following a space) for inline comments
|
||||
#
|
||||
# Changes: in most of the cases you should not modify this
|
||||
# file, but provide customizations in fail2ban.local file, e.g.:
|
||||
#
|
||||
# [Definition]
|
||||
# loglevel = DEBUG
|
||||
#
|
||||
|
||||
[Definition]
|
||||
|
||||
# Option: loglevel
|
||||
# Notes.: Set the log level output.
|
||||
# CRITICAL
|
||||
# ERROR
|
||||
# WARNING
|
||||
# NOTICE
|
||||
# INFO
|
||||
# DEBUG
|
||||
# Values: [ LEVEL ] Default: ERROR
|
||||
#
|
||||
loglevel = INFO
|
||||
|
||||
# Option: logtarget
|
||||
# Notes.: Set the log target. This could be a file, SYSLOG, STDERR or STDOUT.
|
||||
# Only one log target can be specified.
|
||||
# If you change logtarget from the default value and you are
|
||||
# using logrotate -- also adjust or disable rotation in the
|
||||
# corresponding configuration file
|
||||
# (e.g. /etc/logrotate.d/fail2ban on Debian systems)
|
||||
# Values: [ STDOUT | STDERR | SYSLOG | FILE ] Default: STDERR
|
||||
#
|
||||
logtarget = /var/log/fail2ban.log
|
||||
|
||||
# Option: syslogsocket
|
||||
# Notes: Set the syslog socket file. Only used when logtarget is SYSLOG
|
||||
# auto uses platform.system() to determine predefined paths
|
||||
# Values: [ auto | FILE ] Default: auto
|
||||
syslogsocket = auto
|
||||
|
||||
# Option: socket
|
||||
# Notes.: Set the socket file. This is used to communicate with the daemon. Do
|
||||
# not remove this file when Fail2ban runs. It will not be possible to
|
||||
# communicate with the server afterwards.
|
||||
# Values: [ FILE ] Default: /var/run/fail2ban/fail2ban.sock
|
||||
#
|
||||
socket = /var/run/fail2ban/fail2ban.sock
|
||||
|
||||
# Option: pidfile
|
||||
# Notes.: Set the PID file. This is used to store the process ID of the
|
||||
# fail2ban server.
|
||||
# Values: [ FILE ] Default: /var/run/fail2ban/fail2ban.pid
|
||||
#
|
||||
pidfile = /var/run/fail2ban/fail2ban.pid
|
||||
|
||||
# Options: dbfile
|
||||
# Notes.: Set the file for the fail2ban persistent data to be stored.
|
||||
# A value of ":memory:" means database is only stored in memory
|
||||
# and data is lost when fail2ban is stopped.
|
||||
# A value of "None" disables the database.
|
||||
# Values: [ None :memory: FILE ] Default: /var/lib/fail2ban/fail2ban.sqlite3
|
||||
dbfile = /var/lib/fail2ban/fail2ban.sqlite3
|
||||
|
||||
# Options: dbpurgeage
|
||||
# Notes.: Sets age at which bans should be purged from the database
|
||||
# Values: [ SECONDS ] Default: 86400 (24hours)
|
||||
dbpurgeage = 86400
|
||||
@@ -1,47 +0,0 @@
|
||||
# Fail2Ban filter Dovecot authentication and pop3/imap server
|
||||
#
|
||||
|
||||
[INCLUDES]
|
||||
|
||||
before = common.conf
|
||||
|
||||
[Definition]
|
||||
|
||||
_auth_worker = (?:dovecot: )?auth(?:-worker)?
|
||||
_daemon = (?:dovecot(?:-auth)?|auth)
|
||||
|
||||
prefregex = ^%(__prefix_line)s(?:%(_auth_worker)s(?:\([^\)]+\))?: )?(?:%(__pam_auth)s(?:\(dovecot:auth\))?: |(?:pop3|imap|managesieve|submission)-login: )?(?:Info: )?<F-CONTENT>.+</F-CONTENT>$
|
||||
|
||||
failregex = ^authentication failure; logname=<F-ALT_USER1>\S*</F-ALT_USER1> uid=\S* euid=\S* tty=dovecot ruser=<F-USER>\S*</F-USER> rhost=<HOST>(?:\s+user=<F-ALT_USER>\S*</F-ALT_USER>)?\s*$
|
||||
^(?:Aborted login|Disconnected|Remote closed connection|Client has quit the connection)(?::(?: [^ \(]+)+)? \((?:auth failed, \d+ attempts(?: in \d+ secs)?|tried to use (?:disabled|disallowed) \S+ auth|proxy dest auth failed)\):(?: user=<<F-USER>[^>]*</F-USER>>,)?(?: method=\S+,)? rip=<HOST>(?:[^>]*(?:, session=<\S+>)?)\s*$
|
||||
^pam\(\S+,<HOST>(?:,\S*)?\): pam_authenticate\(\) failed: (?:User not known to the underlying authentication module: \d+ Time\(s\)|Authentication failure \(password mismatch\?\)|Permission denied)\s*$
|
||||
^[a-z\-]{3,15}\(\S*,<HOST>(?:,\S*)?\): (?:unknown user|invalid credentials|Password mismatch)
|
||||
<mdre-<mode>>
|
||||
|
||||
mdre-aggressive = ^(?:Aborted login|Disconnected|Remote closed connection|Client has quit the connection)(?::(?: [^ \(]+)+)? \((?:no auth attempts|disconnected before auth was ready,|client didn't finish \S+ auth,)(?: (?:in|waited) \d+ secs)?\):(?: user=<[^>]*>,)?(?: method=\S+,)? rip=<HOST>(?:[^>]*(?:, session=<\S+>)?)\s*$
|
||||
|
||||
mdre-normal =
|
||||
|
||||
# Parameter `mode` - `normal` or `aggressive`.
|
||||
# Aggressive mode can be used to match log-entries like:
|
||||
# 'no auth attempts', 'disconnected before auth was ready', 'client didn't finish SASL auth'.
|
||||
# Note it may produce lots of false positives on misconfigured MTAs.
|
||||
# Ex.:
|
||||
# filter = dovecot[mode=aggressive]
|
||||
mode = normal
|
||||
|
||||
ignoreregex =
|
||||
|
||||
journalmatch = _SYSTEMD_UNIT=dovecot.service
|
||||
|
||||
datepattern = {^LN-BEG}TAI64N
|
||||
{^LN-BEG}
|
||||
|
||||
# DEV Notes:
|
||||
# * the first regex is essentially a copy of pam-generic.conf
|
||||
# * Probably doesn't do dovecot sql/ldap backends properly (resolved in edit 21/03/2016)
|
||||
#
|
||||
# Author: Martin Waschbuesch
|
||||
# Daniel Black (rewrote with begin and end anchors)
|
||||
# Martin O'Neal (added LDAP authentication failure regex)
|
||||
# Sergey G. Brester aka sebres (reviewed, optimized, IPv6-compatibility)
|
||||
@@ -1,26 +0,0 @@
|
||||
# Fail2Ban filter Dovecot authentication and pop3/imap server
|
||||
#
|
||||
# based on filter dovecot.conf from fail2ban-0.8.13
|
||||
|
||||
[INCLUDES]
|
||||
|
||||
before = common.conf
|
||||
|
||||
[Definition]
|
||||
|
||||
_daemon = (auth|dovecot(-auth)?|auth-worker)
|
||||
|
||||
failregex = ^%(__prefix_line)s(pam_unix(\(dovecot:auth\))?:)?\s+authentication failure; logname=\S* uid=\S* euid=\S* tty=dovecot ruser=\S* rhost=<HOST>(\s+user=\S*)?\s*$
|
||||
^%(__prefix_line)s(pop3|imap|managesieve)-login: (Info: )?(Aborted login|Disconnected)(: Inactivity|: Too many invalid commands\.)? \(((auth failed, \d+ attempts)( in \d+ secs)?|tried to use (disabled|disallowed) \S+ auth)\):( user=<\S*>,)?( method=\S+,)? rip=<HOST>(, lip=(\d{1,3}\.){3}\d{1,3})?(, TLS( handshaking(: SSL_accept\(\) failed: error:[\dA-F]+:SSL routines:[TLS\d]+_GET_CLIENT_HELLO:unknown protocol)?)?(: Disconnected)?)?(, session=<\S+>)?\s*$
|
||||
^%(__prefix_line)s(Info|dovecot: auth\(default\)): pam\(\S+,<HOST>\): pam_authenticate\(\) failed: (User not known to the underlying authentication module: \d+ Time\(s\)|Authentication failure \(password mismatch\?\))\s*$
|
||||
|
||||
ignoreregex =
|
||||
|
||||
# DEV Notes:
|
||||
# * the first regex is essentially a copy of pam-generic.conf
|
||||
# * Probably doesn't do dovecot sql/ldap backends properly
|
||||
# * Removed the 'no auth attempts' log lines from the matches because produces
|
||||
# lots of false positives on misconfigured MTAs making regexp unuseable
|
||||
#
|
||||
# Author: Martin Waschbuesch
|
||||
# Daniel Black (rewrote with begin and end anchors)
|
||||
@@ -1,38 +0,0 @@
|
||||
#!/usr/bin/env fail2ban-python
|
||||
# Inspired by https://isc.sans.edu/forums/diary/When+Google+isnt+Google/15968/
|
||||
#
|
||||
# Written in Python to reuse built-in Python batteries and not depend on
|
||||
# presence of host and cut commands
|
||||
#
|
||||
import sys
|
||||
from fail2ban.server.ipdns import DNSUtils, IPAddr
|
||||
|
||||
def process_args(argv):
|
||||
if len(argv) != 2:
|
||||
raise ValueError("Please provide a single IP as an argument. Got: %s\n"
|
||||
% (argv[1:]))
|
||||
ip = argv[1]
|
||||
|
||||
if not IPAddr(ip).isValid:
|
||||
raise ValueError("Argument must be a single valid IP. Got: %s\n"
|
||||
% ip)
|
||||
return ip
|
||||
|
||||
google_ips = None
|
||||
|
||||
def is_googlebot(ip):
|
||||
import re
|
||||
|
||||
host = DNSUtils.ipToName(ip)
|
||||
if not host or not re.match(r'.*\.google(bot)?\.com$', host):
|
||||
return False
|
||||
host_ips = DNSUtils.dnsToIp(host)
|
||||
return (ip in host_ips)
|
||||
|
||||
if __name__ == '__main__': # pragma: no cover
|
||||
try:
|
||||
ret = is_googlebot(process_args(sys.argv))
|
||||
except ValueError as e:
|
||||
sys.stderr.write(str(e))
|
||||
sys.exit(2)
|
||||
sys.exit(0 if ret else 1)
|
||||
@@ -1,50 +0,0 @@
|
||||
# Fail2Ban filter file for named (bind9).
|
||||
#
|
||||
|
||||
# This filter blocks attacks against named (bind9) however it requires special
|
||||
# configuration on bind.
|
||||
#
|
||||
# By default, logging is off with bind9 installation.
|
||||
#
|
||||
# You will need something like this in your named.conf to provide proper logging.
|
||||
#
|
||||
# logging {
|
||||
# channel security_file {
|
||||
# file "/var/log/named/security.log" versions 3 size 30m;
|
||||
# severity dynamic;
|
||||
# print-time yes;
|
||||
# };
|
||||
# category security {
|
||||
# security_file;
|
||||
# };
|
||||
# };
|
||||
|
||||
[Definition]
|
||||
|
||||
# Daemon name
|
||||
_daemon=named
|
||||
|
||||
# Shortcuts for easier comprehension of the failregex
|
||||
|
||||
__pid_re=(?:\[\d+\])
|
||||
__daemon_re=\(?%(_daemon)s(?:\(\S+\))?\)?:?
|
||||
__daemon_combs_re=(?:%(__pid_re)s?:\s+%(__daemon_re)s|%(__daemon_re)s%(__pid_re)s?:)
|
||||
|
||||
# hostname daemon_id spaces
|
||||
# this can be optional (for instance if we match named native log files)
|
||||
__line_prefix=(?:\s\S+ %(__daemon_combs_re)s\s+)?
|
||||
|
||||
prefregex = ^%(__line_prefix)s(?: error:)?\s*client(?: @\S*)? <HOST>#\S+(?: \([\S.]+\))?: <F-CONTENT>.+</F-CONTENT>\s(?:denied|\(NOTAUTH\))\s*$
|
||||
|
||||
failregex = ^(?:view (?:internal|external): )?query(?: \(cache\))?
|
||||
^zone transfer
|
||||
^bad zone transfer request: '\S+/IN': non-authoritative zone
|
||||
|
||||
ignoreregex =
|
||||
|
||||
# DEV Notes:
|
||||
# Trying to generalize the
|
||||
# structure which is general to capture general patterns in log
|
||||
# lines to cover different configurations/distributions
|
||||
#
|
||||
# Author: Yaroslav Halchenko
|
||||
@@ -1,52 +0,0 @@
|
||||
# Fail2Ban filter file for named (bind9).
|
||||
#
|
||||
|
||||
# This filter blocks attacks against named (bind9) however it requires special
|
||||
# configuration on bind.
|
||||
#
|
||||
# By default, logging is off with bind9 installation.
|
||||
#
|
||||
# You will need something like this in your named.conf to provide proper logging.
|
||||
#
|
||||
# logging {
|
||||
# channel security_file {
|
||||
# file "/var/log/named/security.log" versions 3 size 30m;
|
||||
# severity dynamic;
|
||||
# print-time yes;
|
||||
# };
|
||||
# category security {
|
||||
# security_file;
|
||||
# };
|
||||
# };
|
||||
|
||||
[Definition]
|
||||
|
||||
# Daemon name
|
||||
_daemon=named
|
||||
|
||||
# Shortcuts for easier comprehension of the failregex
|
||||
|
||||
__pid_re=(?:\[\d+\])
|
||||
__daemon_re=\(?%(_daemon)s(?:\(\S+\))?\)?:?
|
||||
__daemon_combs_re=(?:%(__pid_re)s?:\s+%(__daemon_re)s|%(__daemon_re)s%(__pid_re)s?:)
|
||||
|
||||
# hostname daemon_id spaces
|
||||
# this can be optional (for instance if we match named native log files)
|
||||
__line_prefix=(?:\s\S+ %(__daemon_combs_re)s\s+)?
|
||||
|
||||
prefregex = ^%(__line_prefix)s( error:)?\s*client \@\S+ <HOST>#\S+( \([\S.]+\))?: <F-CONTENT>.+</F-CONTENT>$
|
||||
|
||||
failregex = ^(view (internal|external): )?query(?: \(cache\))? '.*' denied\s*$
|
||||
^zone transfer '\S+/AXFR/\w+' denied\s*$
|
||||
^bad zone transfer request: '\S+/IN': non-authoritative zone \(NOTAUTH\)\s*$
|
||||
|
||||
ignoreregex =
|
||||
|
||||
# DEV Notes:
|
||||
# Trying to generalize the
|
||||
# structure which is general to capture general patterns in log
|
||||
# lines to cover different configurations/distributions
|
||||
#
|
||||
# (\.\d+)? is a really ugly catch of the microseconds not captured in the date detector
|
||||
#
|
||||
# Author: Yaroslav Halchenko
|
||||
@@ -1,23 +0,0 @@
|
||||
#
|
||||
# Fail2Ban filter to match web requests for selected URLs that don't exist
|
||||
#
|
||||
|
||||
[INCLUDES]
|
||||
|
||||
# Load regexes for filtering
|
||||
before = botsearch-common.conf
|
||||
|
||||
[Definition]
|
||||
|
||||
failregex = ^ \[error\] \d+#\d+: \*\d+ (\S+ )?\"\S+\" (failed|is not found) \(2\: No such file or directory\), client\: <HOST>, server\: \S*\, request: \"(GET|POST|HEAD) \/.*\"
|
||||
|
||||
ignoreregex =
|
||||
|
||||
datepattern = {^LN-BEG}%%ExY(?P<_sep>[-/.])%%m(?P=_sep)%%d[T ]%%H:%%M:%%S(?:[.,]%%f)?(?:\s*%%z)?
|
||||
^[^\[]*\[({DATE})
|
||||
{^LN-BEG}
|
||||
|
||||
# DEV Notes:
|
||||
# Based on apache-botsearch filter
|
||||
#
|
||||
# Author: Frantisek Sumsal
|
||||
@@ -1,39 +0,0 @@
|
||||
# Fail2Ban filter for repeat bans
|
||||
#
|
||||
# This filter monitors the fail2ban log file, and enables you to add long
|
||||
# time bans for ip addresses that get banned by fail2ban multiple times.
|
||||
#
|
||||
# Reasons to use this: block very persistent attackers for a longer time,
|
||||
# stop receiving email notifications about the same attacker over and
|
||||
# over again.
|
||||
#
|
||||
# This jail is only useful if you set the 'findtime' and 'bantime' parameters
|
||||
# in jail.conf to a higher value than the other jails. Also, this jail has its
|
||||
# drawbacks, namely in that it works only with iptables, or if you use a
|
||||
# different blocking mechanism for this jail versus others (e.g. hostsdeny
|
||||
# for most jails, and shorewall for this one).
|
||||
|
||||
[INCLUDES]
|
||||
|
||||
# Read common prefixes. If any customizations available -- read them from
|
||||
# common.local
|
||||
before = common.conf
|
||||
|
||||
[Definition]
|
||||
|
||||
_daemon = fail2ban\.actions\s*
|
||||
|
||||
# The name of the jail that this filter is used for. In jail.conf, name the
|
||||
# jail using this filter 'recidive', or change this line!
|
||||
_jailname = recidive
|
||||
|
||||
failregex = ^(%(__prefix_line)s| %(_daemon)s%(__pid_re)s?:\s+)NOTICE\s+\[(?!%(_jailname)s\])(?:.*)\]\s+Ban\s+<HOST>\s*$
|
||||
|
||||
#ignoreregex =
|
||||
ignoreregex = \[recidive.*\]\s+Ban\s+<HOST>
|
||||
|
||||
[Init]
|
||||
|
||||
journalmatch = _SYSTEMD_UNIT=fail2ban.service PRIORITY=5
|
||||
|
||||
# Author: Tom Hendrikx, modifications by Amir Caspi
|
||||
@@ -1,132 +0,0 @@
|
||||
# Fail2Ban jail base specification file
|
||||
#
|
||||
# HOW TO ACTIVATE JAILS:
|
||||
#
|
||||
# YOU SHOULD NOT MODIFY THIS FILE.
|
||||
#
|
||||
# It will probably be overwitten or improved in a distribution update.
|
||||
#
|
||||
# Provide customizations in a jail.local file or a jail.d/customisation.local.
|
||||
# For example to change the default bantime for all jails and to enable the
|
||||
# ssh-iptables jail the following (uncommented) would appear in the .local file.
|
||||
# See man 5 jail.conf for details.
|
||||
#
|
||||
# [DEFAULT]
|
||||
# bantime = 3600
|
||||
#
|
||||
# [ssh-iptables]
|
||||
# enabled = true
|
||||
|
||||
|
||||
|
||||
# Comments: use '#' for comment lines and ';' (following a space) for inline comments
|
||||
|
||||
# The DEFAULT allows a global definition of the options. They can be overridden
|
||||
# in each jail afterwards.
|
||||
|
||||
[DEFAULT]
|
||||
|
||||
# "ignoreip" can be an IP address, a CIDR mask or a DNS host. Fail2ban will not
|
||||
# ban a host which matches an address in this list. Several addresses can be
|
||||
# defined using space separator.
|
||||
ignoreip = 127.0.0.1/8
|
||||
|
||||
# "bantime" is the number of seconds that a host is banned.
|
||||
bantime = 600
|
||||
|
||||
# A host is banned if it has generated "maxretry" during the last "findtime"
|
||||
# seconds.
|
||||
findtime = 600
|
||||
|
||||
# "maxretry" is the number of failures before a host get banned.
|
||||
maxretry = 3
|
||||
|
||||
# "backend" specifies the backend used to get files modification.
|
||||
# Available options are "pyinotify", "gamin", "polling" and "auto".
|
||||
# This option can be overridden in each jail as well.
|
||||
#
|
||||
# pyinotify: requires pyinotify (a file alteration monitor) to be installed.
|
||||
# If pyinotify is not installed, Fail2ban will use auto.
|
||||
# gamin: requires Gamin (a file alteration monitor) to be installed.
|
||||
# If Gamin is not installed, Fail2ban will use auto.
|
||||
# polling: uses a polling algorithm which does not require external libraries.
|
||||
# auto: will try to use the following backends, in order:
|
||||
# pyinotify, gamin, polling.
|
||||
backend = auto
|
||||
|
||||
# "usedns" specifies if jails should trust hostnames in logs,
|
||||
# warn when DNS lookups are performed, or ignore all hostnames in logs
|
||||
#
|
||||
# yes: if a hostname is encountered, a DNS lookup will be performed.
|
||||
# warn: if a hostname is encountered, a DNS lookup will be performed,
|
||||
# but it will be logged as a warning.
|
||||
# no: if a hostname is encountered, will not be used for banning,
|
||||
# but it will be logged as info.
|
||||
usedns = no
|
||||
|
||||
|
||||
# This jail corresponds to the standard configuration in Fail2ban.
|
||||
# The mail-whois action send a notification e-mail with a whois request
|
||||
# in the body.
|
||||
|
||||
[ssh]
|
||||
|
||||
enabled = false
|
||||
filter = sshd
|
||||
action = iptables[name=SSH, port=ssh, protocol=tcp]
|
||||
logpath = /var/log/secure
|
||||
maxretry = 5
|
||||
|
||||
|
||||
# Jail for more extended banning of persistent abusers
|
||||
# !!! WARNING !!!
|
||||
# Make sure that your loglevel specified in fail2ban.conf/.local
|
||||
# is not at DEBUG level -- which might then cause fail2ban to fall into
|
||||
# an infinite loop constantly feeding itself with non-informative lines
|
||||
[recidive]
|
||||
enabled = false
|
||||
filter = recidive
|
||||
logpath = /var/log/fail2ban.log
|
||||
action = iptables-allports[name=recidive]
|
||||
bantime = 604800 ; 1 week
|
||||
findtime = 86400 ; 1 day
|
||||
maxretry = 5
|
||||
|
||||
[named-tcp]
|
||||
enabled = false
|
||||
port = domain,953
|
||||
protocol = tcp
|
||||
action = iptables-multiport[name=named, port="53,953" protocol=tcp]
|
||||
filter = named-refused
|
||||
logpath = /var/named/data/security.log
|
||||
|
||||
[named-udp]
|
||||
enabled = false
|
||||
port = domain,953
|
||||
protocol = udp
|
||||
action = iptables-multiport[name=named, port="53,953" protocol=udp]
|
||||
filter = named-refused
|
||||
logpath = /var/named/data/security.log
|
||||
|
||||
[vsftpd]
|
||||
enabled = false
|
||||
filter = vsftpd
|
||||
action = iptables[name=VSFTPD, port=ftp, protocol=tcp]
|
||||
logpath = /var/log/vsftpd.log
|
||||
maxretry = 3
|
||||
|
||||
[ngx-botsearch]
|
||||
enabled = false
|
||||
filter = nginx-botsearch
|
||||
action = iptables-multiport[name=ngxbotsearch, port="http,https", protocol=tcp]
|
||||
logpath = /var/log/nginx/*.error.log
|
||||
maxretry = 10
|
||||
|
||||
[php-url-fopen]
|
||||
enabled = false
|
||||
protocol = tcp
|
||||
filter = php-url-fopen
|
||||
action = iptables-multiport[name=php-url-fopen, port"http,https", protocol=tcp]
|
||||
logpath = /var/log/nginx/*.access.log
|
||||
maxretry = 3
|
||||
|
||||
Reference in New Issue
Block a user