mirror of https://github.com/fail2ban/fail2ban
Merge branch '0.11'
commit
fb08534ed7
3
MANIFEST
3
MANIFEST
|
@ -5,8 +5,6 @@ bin/fail2ban-testcases
|
||||||
ChangeLog
|
ChangeLog
|
||||||
config/action.d/abuseipdb.conf
|
config/action.d/abuseipdb.conf
|
||||||
config/action.d/apf.conf
|
config/action.d/apf.conf
|
||||||
config/action.d/badips.conf
|
|
||||||
config/action.d/badips.py
|
|
||||||
config/action.d/blocklist_de.conf
|
config/action.d/blocklist_de.conf
|
||||||
config/action.d/bsd-ipfw.conf
|
config/action.d/bsd-ipfw.conf
|
||||||
config/action.d/cloudflare.conf
|
config/action.d/cloudflare.conf
|
||||||
|
@ -220,7 +218,6 @@ fail2ban/setup.py
|
||||||
fail2ban-testcases-all
|
fail2ban-testcases-all
|
||||||
fail2ban-testcases-all-python3
|
fail2ban-testcases-all-python3
|
||||||
fail2ban/tests/action_d/__init__.py
|
fail2ban/tests/action_d/__init__.py
|
||||||
fail2ban/tests/action_d/test_badips.py
|
|
||||||
fail2ban/tests/action_d/test_smtp.py
|
fail2ban/tests/action_d/test_smtp.py
|
||||||
fail2ban/tests/actionstestcase.py
|
fail2ban/tests/actionstestcase.py
|
||||||
fail2ban/tests/actiontestcase.py
|
fail2ban/tests/actiontestcase.py
|
||||||
|
|
|
@ -1,19 +0,0 @@
|
||||||
# Fail2ban reporting to badips.com
|
|
||||||
#
|
|
||||||
# Note: This reports an IP only and does not actually ban traffic. Use
|
|
||||||
# another action in the same jail if you want bans to occur.
|
|
||||||
#
|
|
||||||
# Set the category to the appropriate value before use.
|
|
||||||
#
|
|
||||||
# To get see register and optional key to get personalised graphs see:
|
|
||||||
# http://www.badips.com/blog/personalized-statistics-track-the-attackers-of-all-your-servers-with-one-key
|
|
||||||
|
|
||||||
[Definition]
|
|
||||||
|
|
||||||
actionban = curl --fail --user-agent "<agent>" http://www.badips.com/add/<category>/<ip>
|
|
||||||
|
|
||||||
[Init]
|
|
||||||
|
|
||||||
# Option: category
|
|
||||||
# Notes.: Values are from the list here: http://www.badips.com/get/categories
|
|
||||||
category =
|
|
|
@ -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
|
|
|
@ -84,8 +84,15 @@ srv_cfg_path = /etc/nginx/
|
||||||
#srv_cmd = nginx -c %(srv_cfg_path)s/nginx.conf
|
#srv_cmd = nginx -c %(srv_cfg_path)s/nginx.conf
|
||||||
srv_cmd = nginx
|
srv_cmd = nginx
|
||||||
|
|
||||||
# first test configuration is correct, hereafter send reload signal:
|
# pid file (used to check nginx is running):
|
||||||
blck_lst_reload = %(srv_cmd)s -qt; if [ $? -eq 0 ]; then
|
srv_pid = /run/nginx.pid
|
||||||
|
|
||||||
|
# command used to check whether nginx is running and configuration is valid:
|
||||||
|
srv_is_running = [ -f "%(srv_pid)s" ]
|
||||||
|
srv_check_cmd = %(srv_is_running)s && %(srv_cmd)s -qt
|
||||||
|
|
||||||
|
# first test nginx is running and configuration is correct, hereafter send reload signal:
|
||||||
|
blck_lst_reload = %(srv_check_cmd)s; if [ $? -eq 0 ]; then
|
||||||
%(srv_cmd)s -s reload; if [ $? -ne 0 ]; then echo 'reload failed.'; fi;
|
%(srv_cmd)s -s reload; if [ $? -ne 0 ]; then echo 'reload failed.'; fi;
|
||||||
fi;
|
fi;
|
||||||
|
|
||||||
|
|
|
@ -6,24 +6,35 @@
|
||||||
#
|
#
|
||||||
import sys
|
import sys
|
||||||
from fail2ban.server.ipdns import DNSUtils, IPAddr
|
from fail2ban.server.ipdns import DNSUtils, IPAddr
|
||||||
|
from threading import Thread
|
||||||
|
|
||||||
def process_args(argv):
|
def process_args(argv):
|
||||||
if len(argv) != 2:
|
if len(argv) - 1 not in (1, 2):
|
||||||
raise ValueError("Please provide a single IP as an argument. Got: %s\n"
|
raise ValueError("Usage %s ip ?timeout?. Got: %s\n"
|
||||||
% (argv[1:]))
|
% (argv[0], argv[1:]))
|
||||||
ip = argv[1]
|
ip = argv[1]
|
||||||
|
|
||||||
if not IPAddr(ip).isValid:
|
if not IPAddr(ip).isValid:
|
||||||
raise ValueError("Argument must be a single valid IP. Got: %s\n"
|
raise ValueError("Argument must be a single valid IP. Got: %s\n"
|
||||||
% ip)
|
% ip)
|
||||||
return ip
|
return argv[1:]
|
||||||
|
|
||||||
google_ips = None
|
google_ips = None
|
||||||
|
|
||||||
def is_googlebot(ip):
|
def is_googlebot(ip, timeout=55):
|
||||||
import re
|
import re
|
||||||
|
|
||||||
|
timeout = float(timeout or 0)
|
||||||
|
if timeout:
|
||||||
|
def ipToNameTO(host, ip, timeout):
|
||||||
|
host[0] = DNSUtils.ipToName(ip)
|
||||||
|
host = [None]
|
||||||
|
th = Thread(target=ipToNameTO, args=(host, ip, timeout)); th.daemon=True; th.start()
|
||||||
|
th.join(timeout)
|
||||||
|
host = host[0]
|
||||||
|
else:
|
||||||
host = DNSUtils.ipToName(ip)
|
host = DNSUtils.ipToName(ip)
|
||||||
|
|
||||||
if not host or not re.match(r'.*\.google(bot)?\.com$', host):
|
if not host or not re.match(r'.*\.google(bot)?\.com$', host):
|
||||||
return False
|
return False
|
||||||
host_ips = DNSUtils.dnsToIp(host)
|
host_ips = DNSUtils.dnsToIp(host)
|
||||||
|
@ -31,7 +42,7 @@ def is_googlebot(ip):
|
||||||
|
|
||||||
if __name__ == '__main__': # pragma: no cover
|
if __name__ == '__main__': # pragma: no cover
|
||||||
try:
|
try:
|
||||||
ret = is_googlebot(process_args(sys.argv))
|
ret = is_googlebot(*process_args(sys.argv))
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
sys.stderr.write(str(e))
|
sys.stderr.write(str(e))
|
||||||
sys.exit(2)
|
sys.exit(2)
|
||||||
|
|
|
@ -242,20 +242,6 @@ action_cf_mwl = cloudflare[cfuser="%(cfemail)s", cftoken="%(cfapikey)s"]
|
||||||
#
|
#
|
||||||
action_blocklist_de = blocklist_de[email="%(sender)s", service="%(__name__)s", apikey="%(blocklist_de_apikey)s", agent="%(fail2ban_agent)s"]
|
action_blocklist_de = blocklist_de[email="%(sender)s", service="%(__name__)s", apikey="%(blocklist_de_apikey)s", agent="%(fail2ban_agent)s"]
|
||||||
|
|
||||||
# Report ban via badips.com, and use as blacklist
|
|
||||||
#
|
|
||||||
# See BadIPsAction docstring in config/action.d/badips.py for
|
|
||||||
# documentation for this action.
|
|
||||||
#
|
|
||||||
# NOTE: This action relies on banaction being present on start and therefore
|
|
||||||
# should be last action defined for a jail.
|
|
||||||
#
|
|
||||||
action_badips = badips.py[category="%(__name__)s", banaction="%(banaction)s", agent="%(fail2ban_agent)s"]
|
|
||||||
#
|
|
||||||
# Report ban via badips.com (uses action.d/badips.conf for reporting only)
|
|
||||||
#
|
|
||||||
action_badips_report = badips[category="%(__name__)s", agent="%(fail2ban_agent)s"]
|
|
||||||
|
|
||||||
# Report ban via abuseipdb.com.
|
# Report ban via abuseipdb.com.
|
||||||
#
|
#
|
||||||
# See action.d/abuseipdb.conf for usage example and details.
|
# See action.d/abuseipdb.conf for usage example and details.
|
||||||
|
|
|
@ -192,7 +192,7 @@ class Fail2banCmdLine():
|
||||||
cmdOpts = 'hc:s:p:xfbdtviqV'
|
cmdOpts = 'hc:s:p:xfbdtviqV'
|
||||||
cmdLongOpts = ['loglevel=', 'logtarget=', 'syslogsocket=', 'test', 'async',
|
cmdLongOpts = ['loglevel=', 'logtarget=', 'syslogsocket=', 'test', 'async',
|
||||||
'conf=', 'pidfile=', 'pname=', 'socket=',
|
'conf=', 'pidfile=', 'pname=', 'socket=',
|
||||||
'timeout=', 'str2sec=', 'help', 'version', 'dp', '--dump-pretty']
|
'timeout=', 'str2sec=', 'help', 'version', 'dp', 'dump-pretty']
|
||||||
optList, self._args = getopt.getopt(self._argv[1:], cmdOpts, cmdLongOpts)
|
optList, self._args = getopt.getopt(self._argv[1:], cmdOpts, cmdLongOpts)
|
||||||
except getopt.GetoptError:
|
except getopt.GetoptError:
|
||||||
self.dispUsage()
|
self.dispUsage()
|
||||||
|
|
|
@ -127,9 +127,10 @@ class FailManager:
|
||||||
return len(self.__failList)
|
return len(self.__failList)
|
||||||
|
|
||||||
def cleanup(self, time):
|
def cleanup(self, time):
|
||||||
|
time -= self.__maxTime
|
||||||
with self.__lock:
|
with self.__lock:
|
||||||
todelete = [fid for fid,item in self.__failList.iteritems() \
|
todelete = [fid for fid,item in self.__failList.iteritems() \
|
||||||
if item.getTime() + self.__maxTime <= time]
|
if item.getTime() <= time]
|
||||||
if len(todelete) == len(self.__failList):
|
if len(todelete) == len(self.__failList):
|
||||||
# remove all:
|
# remove all:
|
||||||
self.__failList = dict()
|
self.__failList = dict()
|
||||||
|
@ -143,7 +144,7 @@ class FailManager:
|
||||||
else:
|
else:
|
||||||
# create new dictionary without items to be deleted:
|
# create new dictionary without items to be deleted:
|
||||||
self.__failList = dict((fid,item) for fid,item in self.__failList.iteritems() \
|
self.__failList = dict((fid,item) for fid,item in self.__failList.iteritems() \
|
||||||
if item.getTime() + self.__maxTime > time)
|
if item.getTime() > time)
|
||||||
self.__bgSvc.service()
|
self.__bgSvc.service()
|
||||||
|
|
||||||
def delFailure(self, fid):
|
def delFailure(self, fid):
|
||||||
|
|
|
@ -94,6 +94,8 @@ class Filter(JailThread):
|
||||||
## Store last time stamp, applicable for multi-line
|
## Store last time stamp, applicable for multi-line
|
||||||
self.__lastTimeText = ""
|
self.__lastTimeText = ""
|
||||||
self.__lastDate = None
|
self.__lastDate = None
|
||||||
|
## Next service (cleanup) time
|
||||||
|
self.__nextSvcTime = -(1<<63)
|
||||||
## if set, treat log lines without explicit time zone to be in this time zone
|
## if set, treat log lines without explicit time zone to be in this time zone
|
||||||
self.__logtimezone = None
|
self.__logtimezone = None
|
||||||
## Default or preferred encoding (to decode bytes from file or journal):
|
## Default or preferred encoding (to decode bytes from file or journal):
|
||||||
|
@ -115,10 +117,10 @@ class Filter(JailThread):
|
||||||
self.checkFindTime = True
|
self.checkFindTime = True
|
||||||
## shows that filter is in operation mode (processing new messages):
|
## shows that filter is in operation mode (processing new messages):
|
||||||
self.inOperation = True
|
self.inOperation = True
|
||||||
## if true prevents against retarded banning in case of RC by too many failures (disabled only for test purposes):
|
|
||||||
self.banASAP = True
|
|
||||||
## Ticks counter
|
## Ticks counter
|
||||||
self.ticks = 0
|
self.ticks = 0
|
||||||
|
## Processed lines counter
|
||||||
|
self.procLines = 0
|
||||||
## Thread name:
|
## Thread name:
|
||||||
self.name="f2b/f."+self.jailName
|
self.name="f2b/f."+self.jailName
|
||||||
|
|
||||||
|
@ -442,12 +444,23 @@ class Filter(JailThread):
|
||||||
|
|
||||||
def performBan(self, ip=None):
|
def performBan(self, ip=None):
|
||||||
"""Performs a ban for IPs (or given ip) that are reached maxretry of the jail."""
|
"""Performs a ban for IPs (or given ip) that are reached maxretry of the jail."""
|
||||||
try: # pragma: no branch - exception is the only way out
|
|
||||||
while True:
|
while True:
|
||||||
|
try:
|
||||||
ticket = self.failManager.toBan(ip)
|
ticket = self.failManager.toBan(ip)
|
||||||
self.jail.putFailTicket(ticket)
|
|
||||||
except FailManagerEmpty:
|
except FailManagerEmpty:
|
||||||
self.failManager.cleanup(MyTime.time())
|
break
|
||||||
|
self.jail.putFailTicket(ticket)
|
||||||
|
if ip: break
|
||||||
|
self.performSvc()
|
||||||
|
|
||||||
|
def performSvc(self, force=False):
|
||||||
|
"""Performs a service tasks (clean failure list)."""
|
||||||
|
tm = MyTime.time()
|
||||||
|
# avoid too early clean up:
|
||||||
|
if force or tm >= self.__nextSvcTime:
|
||||||
|
self.__nextSvcTime = tm + 5
|
||||||
|
# clean up failure list:
|
||||||
|
self.failManager.cleanup(tm)
|
||||||
|
|
||||||
def addAttempt(self, ip, *matches):
|
def addAttempt(self, ip, *matches):
|
||||||
"""Generate a failed attempt for ip"""
|
"""Generate a failed attempt for ip"""
|
||||||
|
@ -695,11 +708,15 @@ class Filter(JailThread):
|
||||||
attempts = self.failManager.addFailure(tick)
|
attempts = self.failManager.addFailure(tick)
|
||||||
# avoid RC on busy filter (too many failures) - if attempts for IP/ID reached maxretry,
|
# avoid RC on busy filter (too many failures) - if attempts for IP/ID reached maxretry,
|
||||||
# we can speedup ban, so do it as soon as possible:
|
# we can speedup ban, so do it as soon as possible:
|
||||||
if self.banASAP and attempts >= self.failManager.getMaxRetry():
|
if attempts >= self.failManager.getMaxRetry():
|
||||||
self.performBan(ip)
|
self.performBan(ip)
|
||||||
# report to observer - failure was found, for possibly increasing of it retry counter (asynchronous)
|
# report to observer - failure was found, for possibly increasing of it retry counter (asynchronous)
|
||||||
if Observers.Main is not None:
|
if Observers.Main is not None:
|
||||||
Observers.Main.add('failureFound', self.failManager, self.jail, tick)
|
Observers.Main.add('failureFound', self.failManager, self.jail, tick)
|
||||||
|
self.procLines += 1
|
||||||
|
# every 100 lines check need to perform service tasks:
|
||||||
|
if self.procLines % 100 == 0:
|
||||||
|
self.performSvc()
|
||||||
# reset (halve) error counter (successfully processed line):
|
# reset (halve) error counter (successfully processed line):
|
||||||
if self._errors:
|
if self._errors:
|
||||||
self._errors //= 2
|
self._errors //= 2
|
||||||
|
@ -1068,6 +1085,7 @@ class FileFilter(Filter):
|
||||||
# is created and is added to the FailManager.
|
# is created and is added to the FailManager.
|
||||||
|
|
||||||
def getFailures(self, filename, inOperation=None):
|
def getFailures(self, filename, inOperation=None):
|
||||||
|
if self.idle: return False
|
||||||
log = self.getLog(filename)
|
log = self.getLog(filename)
|
||||||
if log is None:
|
if log is None:
|
||||||
logSys.error("Unable to get failures in %s", filename)
|
logSys.error("Unable to get failures in %s", filename)
|
||||||
|
|
|
@ -55,7 +55,6 @@ class FilterGamin(FileFilter):
|
||||||
|
|
||||||
def __init__(self, jail):
|
def __init__(self, jail):
|
||||||
FileFilter.__init__(self, jail)
|
FileFilter.__init__(self, jail)
|
||||||
self.__modified = False
|
|
||||||
# Gamin monitor
|
# Gamin monitor
|
||||||
self.monitor = gamin.WatchMonitor()
|
self.monitor = gamin.WatchMonitor()
|
||||||
fd = self.monitor.get_fd()
|
fd = self.monitor.get_fd()
|
||||||
|
@ -67,21 +66,9 @@ class FilterGamin(FileFilter):
|
||||||
logSys.log(4, "Got event: " + repr(event) + " for " + path)
|
logSys.log(4, "Got event: " + repr(event) + " for " + path)
|
||||||
if event in (gamin.GAMCreated, gamin.GAMChanged, gamin.GAMExists):
|
if event in (gamin.GAMCreated, gamin.GAMChanged, gamin.GAMExists):
|
||||||
logSys.debug("File changed: " + path)
|
logSys.debug("File changed: " + path)
|
||||||
self.__modified = True
|
|
||||||
|
|
||||||
self.ticks += 1
|
self.ticks += 1
|
||||||
self._process_file(path)
|
|
||||||
|
|
||||||
def _process_file(self, path):
|
|
||||||
"""Process a given file
|
|
||||||
|
|
||||||
TODO -- RF:
|
|
||||||
this is a common logic and must be shared/provided by FileFilter
|
|
||||||
"""
|
|
||||||
self.getFailures(path)
|
self.getFailures(path)
|
||||||
if not self.banASAP: # pragma: no cover
|
|
||||||
self.performBan()
|
|
||||||
self.__modified = False
|
|
||||||
|
|
||||||
##
|
##
|
||||||
# Add a log file path
|
# Add a log file path
|
||||||
|
@ -128,6 +115,9 @@ class FilterGamin(FileFilter):
|
||||||
Utils.wait_for(lambda: not self.active or self._handleEvents(),
|
Utils.wait_for(lambda: not self.active or self._handleEvents(),
|
||||||
self.sleeptime)
|
self.sleeptime)
|
||||||
self.ticks += 1
|
self.ticks += 1
|
||||||
|
if self.ticks % 10 == 0:
|
||||||
|
self.performSvc()
|
||||||
|
|
||||||
logSys.debug("[%s] filter terminated", self.jailName)
|
logSys.debug("[%s] filter terminated", self.jailName)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
|
@ -27,9 +27,7 @@ __license__ = "GPL"
|
||||||
import os
|
import os
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from .failmanager import FailManagerEmpty
|
|
||||||
from .filter import FileFilter
|
from .filter import FileFilter
|
||||||
from .mytime import MyTime
|
|
||||||
from .utils import Utils
|
from .utils import Utils
|
||||||
from ..helpers import getLogger, logging
|
from ..helpers import getLogger, logging
|
||||||
|
|
||||||
|
@ -55,7 +53,6 @@ class FilterPoll(FileFilter):
|
||||||
|
|
||||||
def __init__(self, jail):
|
def __init__(self, jail):
|
||||||
FileFilter.__init__(self, jail)
|
FileFilter.__init__(self, jail)
|
||||||
self.__modified = False
|
|
||||||
## The time of the last modification of the file.
|
## The time of the last modification of the file.
|
||||||
self.__prevStats = dict()
|
self.__prevStats = dict()
|
||||||
self.__file404Cnt = dict()
|
self.__file404Cnt = dict()
|
||||||
|
@ -115,13 +112,10 @@ class FilterPoll(FileFilter):
|
||||||
break
|
break
|
||||||
for filename in modlst:
|
for filename in modlst:
|
||||||
self.getFailures(filename)
|
self.getFailures(filename)
|
||||||
self.__modified = True
|
|
||||||
|
|
||||||
self.ticks += 1
|
self.ticks += 1
|
||||||
if self.__modified:
|
if self.ticks % 10 == 0:
|
||||||
if not self.banASAP: # pragma: no cover
|
self.performSvc()
|
||||||
self.performBan()
|
|
||||||
self.__modified = False
|
|
||||||
except Exception as e: # pragma: no cover
|
except Exception as e: # pragma: no cover
|
||||||
if not self.active: # if not active - error by stop...
|
if not self.active: # if not active - error by stop...
|
||||||
break
|
break
|
||||||
|
|
|
@ -75,7 +75,6 @@ class FilterPyinotify(FileFilter):
|
||||||
|
|
||||||
def __init__(self, jail):
|
def __init__(self, jail):
|
||||||
FileFilter.__init__(self, jail)
|
FileFilter.__init__(self, jail)
|
||||||
self.__modified = False
|
|
||||||
# Pyinotify watch manager
|
# Pyinotify watch manager
|
||||||
self.__monitor = pyinotify.WatchManager()
|
self.__monitor = pyinotify.WatchManager()
|
||||||
self.__notifier = None
|
self.__notifier = None
|
||||||
|
@ -140,9 +139,6 @@ class FilterPyinotify(FileFilter):
|
||||||
"""
|
"""
|
||||||
if not self.idle:
|
if not self.idle:
|
||||||
self.getFailures(path)
|
self.getFailures(path)
|
||||||
if not self.banASAP: # pragma: no cover
|
|
||||||
self.performBan()
|
|
||||||
self.__modified = False
|
|
||||||
|
|
||||||
def _addPending(self, path, reason, isDir=False):
|
def _addPending(self, path, reason, isDir=False):
|
||||||
if path not in self.__pending:
|
if path not in self.__pending:
|
||||||
|
@ -352,9 +348,14 @@ class FilterPyinotify(FileFilter):
|
||||||
if not self.active: break
|
if not self.active: break
|
||||||
self.__notifier.read_events()
|
self.__notifier.read_events()
|
||||||
|
|
||||||
|
self.ticks += 1
|
||||||
|
|
||||||
# check pending files/dirs (logrotate ready):
|
# check pending files/dirs (logrotate ready):
|
||||||
if not self.idle:
|
if self.idle:
|
||||||
|
continue
|
||||||
self._checkPending()
|
self._checkPending()
|
||||||
|
if self.ticks % 10 == 0:
|
||||||
|
self.performSvc()
|
||||||
|
|
||||||
except Exception as e: # pragma: no cover
|
except Exception as e: # pragma: no cover
|
||||||
if not self.active: # if not active - error by stop...
|
if not self.active: # if not active - error by stop...
|
||||||
|
@ -364,8 +365,6 @@ class FilterPyinotify(FileFilter):
|
||||||
# incr common error counter:
|
# incr common error counter:
|
||||||
self.commonError()
|
self.commonError()
|
||||||
|
|
||||||
self.ticks += 1
|
|
||||||
|
|
||||||
logSys.debug("[%s] filter exited (pyinotifier)", self.jailName)
|
logSys.debug("[%s] filter exited (pyinotifier)", self.jailName)
|
||||||
self.__notifier = None
|
self.__notifier = None
|
||||||
|
|
||||||
|
|
|
@ -322,10 +322,9 @@ class FilterSystemd(JournalFilter): # pragma: systemd no cover
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
break
|
break
|
||||||
if self.__modified:
|
|
||||||
if not self.banASAP: # pragma: no cover
|
|
||||||
self.performBan()
|
|
||||||
self.__modified = 0
|
self.__modified = 0
|
||||||
|
if self.ticks % 10 == 0:
|
||||||
|
self.performSvc()
|
||||||
# update position in log (time and iso string):
|
# update position in log (time and iso string):
|
||||||
if self.jail.database is not None:
|
if self.jail.database is not None:
|
||||||
self.jail.database.updateJournal(self.jail, 'systemd-journal', line[1], line[0][1])
|
self.jail.database.updateJournal(self.jail, 'systemd-journal', line[1], line[0][1])
|
||||||
|
|
|
@ -1,157 +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 os
|
|
||||||
import unittest
|
|
||||||
import sys
|
|
||||||
from functools import wraps
|
|
||||||
from socket import timeout
|
|
||||||
from ssl import SSLError
|
|
||||||
|
|
||||||
from ..actiontestcase import CallingMap
|
|
||||||
from ..dummyjail import DummyJail
|
|
||||||
from ..servertestcase import IPAddr
|
|
||||||
from ..utils import LogCaptureTestCase, CONFIG_DIR
|
|
||||||
|
|
||||||
if sys.version_info >= (3, ): # pragma: 2.x no cover
|
|
||||||
from urllib.error import HTTPError, URLError
|
|
||||||
else: # pragma: 3.x no cover
|
|
||||||
from urllib2 import HTTPError, URLError
|
|
||||||
|
|
||||||
def skip_if_not_available(f):
|
|
||||||
"""Helper to decorate tests to skip in case of timeout/http-errors like "502 bad gateway".
|
|
||||||
"""
|
|
||||||
@wraps(f)
|
|
||||||
def wrapper(self, *args):
|
|
||||||
try:
|
|
||||||
return f(self, *args)
|
|
||||||
except (SSLError, HTTPError, URLError, timeout) as e: # pragma: no cover - timeout/availability issues
|
|
||||||
if not isinstance(e, timeout) and 'timed out' not in str(e):
|
|
||||||
if not hasattr(e, 'code') or e.code > 200 and e.code <= 404:
|
|
||||||
raise
|
|
||||||
raise unittest.SkipTest('Skip test because of %s' % e)
|
|
||||||
return wrapper
|
|
||||||
|
|
||||||
if sys.version_info >= (2,7): # pragma: no cover - may be unavailable
|
|
||||||
class BadIPsActionTest(LogCaptureTestCase):
|
|
||||||
|
|
||||||
available = True, None
|
|
||||||
pythonModule = None
|
|
||||||
modAction = None
|
|
||||||
|
|
||||||
@skip_if_not_available
|
|
||||||
def setUp(self):
|
|
||||||
"""Call before every test case."""
|
|
||||||
super(BadIPsActionTest, self).setUp()
|
|
||||||
unittest.F2B.SkipIfNoNetwork()
|
|
||||||
|
|
||||||
self.jail = DummyJail()
|
|
||||||
|
|
||||||
self.jail.actions.add("test")
|
|
||||||
|
|
||||||
pythonModuleName = os.path.join(CONFIG_DIR, "action.d", "badips.py")
|
|
||||||
|
|
||||||
# check availability (once if not alive, used shorter timeout as in test cases):
|
|
||||||
if BadIPsActionTest.available[0]:
|
|
||||||
if not BadIPsActionTest.modAction:
|
|
||||||
if not BadIPsActionTest.pythonModule:
|
|
||||||
BadIPsActionTest.pythonModule = self.jail.actions._load_python_module(pythonModuleName)
|
|
||||||
BadIPsActionTest.modAction = BadIPsActionTest.pythonModule.Action
|
|
||||||
self.jail.actions._load_python_module(pythonModuleName)
|
|
||||||
BadIPsActionTest.available = BadIPsActionTest.modAction.isAvailable(timeout=2 if unittest.F2B.fast else 30)
|
|
||||||
if not BadIPsActionTest.available[0]:
|
|
||||||
raise unittest.SkipTest('Skip test because service is not available: %s' % BadIPsActionTest.available[1])
|
|
||||||
|
|
||||||
self.jail.actions.add("badips", pythonModuleName, initOpts={
|
|
||||||
'category': "ssh",
|
|
||||||
'banaction': "test",
|
|
||||||
'age': "2w",
|
|
||||||
'score': 5,
|
|
||||||
#'key': "fail2ban-test-suite",
|
|
||||||
#'bankey': "fail2ban-test-suite",
|
|
||||||
'timeout': (3 if unittest.F2B.fast else 60),
|
|
||||||
})
|
|
||||||
self.action = self.jail.actions["badips"]
|
|
||||||
|
|
||||||
def tearDown(self):
|
|
||||||
"""Call after every test case."""
|
|
||||||
# Must cancel timer!
|
|
||||||
if self.action._timer:
|
|
||||||
self.action._timer.cancel()
|
|
||||||
super(BadIPsActionTest, self).tearDown()
|
|
||||||
|
|
||||||
@skip_if_not_available
|
|
||||||
def testCategory(self):
|
|
||||||
categories = self.action.getCategories()
|
|
||||||
self.assertIn("ssh", categories)
|
|
||||||
self.assertTrue(len(categories) >= 10)
|
|
||||||
|
|
||||||
self.assertRaises(
|
|
||||||
ValueError, setattr, self.action, "category",
|
|
||||||
"invalid-category")
|
|
||||||
|
|
||||||
# Not valid for reporting category...
|
|
||||||
self.assertRaises(
|
|
||||||
ValueError, setattr, self.action, "category", "mail")
|
|
||||||
# but valid for blacklisting.
|
|
||||||
self.action.bancategory = "mail"
|
|
||||||
|
|
||||||
@skip_if_not_available
|
|
||||||
def testScore(self):
|
|
||||||
self.assertRaises(ValueError, setattr, self.action, "score", -5)
|
|
||||||
self.action.score = 3
|
|
||||||
self.action.score = "3"
|
|
||||||
|
|
||||||
@skip_if_not_available
|
|
||||||
def testBanaction(self):
|
|
||||||
self.assertRaises(
|
|
||||||
ValueError, setattr, self.action, "banaction",
|
|
||||||
"invalid-action")
|
|
||||||
self.action.banaction = "test"
|
|
||||||
|
|
||||||
@skip_if_not_available
|
|
||||||
def testUpdateperiod(self):
|
|
||||||
self.assertRaises(
|
|
||||||
ValueError, setattr, self.action, "updateperiod", -50)
|
|
||||||
self.assertRaises(
|
|
||||||
ValueError, setattr, self.action, "updateperiod", 0)
|
|
||||||
self.action.updateperiod = 900
|
|
||||||
self.action.updateperiod = "900"
|
|
||||||
|
|
||||||
@skip_if_not_available
|
|
||||||
def testStartStop(self):
|
|
||||||
self.action.start()
|
|
||||||
self.assertTrue(len(self.action._bannedips) > 10,
|
|
||||||
"%s is fewer as 10: %r" % (len(self.action._bannedips), self.action._bannedips))
|
|
||||||
self.action.stop()
|
|
||||||
self.assertTrue(len(self.action._bannedips) == 0)
|
|
||||||
|
|
||||||
@skip_if_not_available
|
|
||||||
def testBanIP(self):
|
|
||||||
aInfo = CallingMap({
|
|
||||||
'ip': IPAddr('192.0.2.1')
|
|
||||||
})
|
|
||||||
self.action.ban(aInfo)
|
|
||||||
self.assertLogged('badips.com: ban', wait=True)
|
|
||||||
self.pruneLog()
|
|
||||||
# produce an error using wrong category/IP:
|
|
||||||
self.action._category = 'f2b-this-category-dont-available-test-suite-only'
|
|
||||||
aInfo['ip'] = ''
|
|
||||||
self.assertRaises(BadIPsActionTest.pythonModule.HTTPError, self.action.ban, aInfo)
|
|
||||||
self.assertLogged('IP is invalid', 'invalid category', wait=True, all=False)
|
|
|
@ -458,8 +458,6 @@ class JailReaderTest(LogCaptureTestCase):
|
||||||
('sender', 'f2b-test@example.com'), ('blocklist_de_apikey', 'test-key'),
|
('sender', 'f2b-test@example.com'), ('blocklist_de_apikey', 'test-key'),
|
||||||
('action',
|
('action',
|
||||||
'%(action_blocklist_de)s\n'
|
'%(action_blocklist_de)s\n'
|
||||||
'%(action_badips_report)s\n'
|
|
||||||
'%(action_badips)s\n'
|
|
||||||
'mynetwatchman[port=1234,protocol=udp,agent="%(fail2ban_agent)s"]'
|
'mynetwatchman[port=1234,protocol=udp,agent="%(fail2ban_agent)s"]'
|
||||||
),
|
),
|
||||||
))
|
))
|
||||||
|
@ -473,16 +471,14 @@ class JailReaderTest(LogCaptureTestCase):
|
||||||
if len(cmd) <= 4:
|
if len(cmd) <= 4:
|
||||||
continue
|
continue
|
||||||
# differentiate between set and multi-set (wrop it here to single set):
|
# differentiate between set and multi-set (wrop it here to single set):
|
||||||
if cmd[0] == 'set' and (cmd[4] == 'agent' or cmd[4].endswith('badips.py')):
|
if cmd[0] == 'set' and cmd[4] == 'agent':
|
||||||
act.append(cmd)
|
act.append(cmd)
|
||||||
elif cmd[0] == 'multi-set':
|
elif cmd[0] == 'multi-set':
|
||||||
act.extend([['set'] + cmd[1:4] + o for o in cmd[4] if o[0] == 'agent'])
|
act.extend([['set'] + cmd[1:4] + o for o in cmd[4] if o[0] == 'agent'])
|
||||||
useragent = 'Fail2Ban/%s' % version
|
useragent = 'Fail2Ban/%s' % version
|
||||||
self.assertEqual(len(act), 4)
|
self.assertEqual(len(act), 2)
|
||||||
self.assertEqual(act[0], ['set', 'blocklisttest', 'action', 'blocklist_de', 'agent', useragent])
|
self.assertEqual(act[0], ['set', 'blocklisttest', 'action', 'blocklist_de', 'agent', useragent])
|
||||||
self.assertEqual(act[1], ['set', 'blocklisttest', 'action', 'badips', 'agent', useragent])
|
self.assertEqual(act[1], ['set', 'blocklisttest', 'action', 'mynetwatchman', 'agent', useragent])
|
||||||
self.assertEqual(eval(act[2][5]).get('agent', '<wrong>'), useragent)
|
|
||||||
self.assertEqual(act[3], ['set', 'blocklisttest', 'action', 'mynetwatchman', 'agent', useragent])
|
|
||||||
|
|
||||||
@with_tmpdir
|
@with_tmpdir
|
||||||
def testGlob(self, d):
|
def testGlob(self, d):
|
||||||
|
|
|
@ -1326,7 +1326,7 @@ class Fail2banServerTest(Fail2banClientServerBase):
|
||||||
'backend = polling',
|
'backend = polling',
|
||||||
'usedns = no',
|
'usedns = no',
|
||||||
'logpath = %(tmp)s/blck-failures.log',
|
'logpath = %(tmp)s/blck-failures.log',
|
||||||
'action = nginx-block-map[blck_lst_reload="", blck_lst_file="%(tmp)s/blck-lst.map"]',
|
'action = nginx-block-map[srv_cmd="echo nginx", srv_pid="%(tmp)s/f2b.pid", blck_lst_file="%(tmp)s/blck-lst.map"]',
|
||||||
' blocklist_de[actionban=\'curl() { echo "*** curl" "$*";}; <Definition/actionban>\', email="Fail2Ban <fail2ban@localhost>", '
|
' blocklist_de[actionban=\'curl() { echo "*** curl" "$*";}; <Definition/actionban>\', email="Fail2Ban <fail2ban@localhost>", '
|
||||||
'apikey="TEST-API-KEY", agent="fail2ban-test-agent", service=<name>]',
|
'apikey="TEST-API-KEY", agent="fail2ban-test-agent", service=<name>]',
|
||||||
'filter =',
|
'filter =',
|
||||||
|
@ -1366,6 +1366,8 @@ class Fail2banServerTest(Fail2banClientServerBase):
|
||||||
self.assertIn('\\125-000-004 1;\n', mp)
|
self.assertIn('\\125-000-004 1;\n', mp)
|
||||||
self.assertIn('\\125-000-005 1;\n', mp)
|
self.assertIn('\\125-000-005 1;\n', mp)
|
||||||
|
|
||||||
|
# check nginx reload is logged (pid of fail2ban is used to simulate success check nginx is running):
|
||||||
|
self.assertLogged("stdout: 'nginx -qt'", "stdout: 'nginx -s reload'", all=True)
|
||||||
# check blocklist_de substitution (e. g. new-line after <matches>):
|
# check blocklist_de substitution (e. g. new-line after <matches>):
|
||||||
self.assertLogged(
|
self.assertLogged(
|
||||||
"stdout: '*** curl --fail --data-urlencode server=Fail2Ban <fail2ban@localhost>"
|
"stdout: '*** curl --fail --data-urlencode server=Fail2Ban <fail2ban@localhost>"
|
||||||
|
|
|
@ -164,8 +164,15 @@ def _assert_correct_last_attempt(utest, filter_, output, count=None):
|
||||||
# get fail ticket from jail
|
# get fail ticket from jail
|
||||||
found.append(_ticket_tuple(filter_.getFailTicket()))
|
found.append(_ticket_tuple(filter_.getFailTicket()))
|
||||||
else:
|
else:
|
||||||
# when we are testing without jails
|
# when we are testing without jails wait for failures (up to max time)
|
||||||
# wait for failures (up to max time)
|
if filter_.jail:
|
||||||
|
while True:
|
||||||
|
t = filter_.jail.getFailTicket()
|
||||||
|
if not t: break
|
||||||
|
found.append(_ticket_tuple(t))
|
||||||
|
if found:
|
||||||
|
tickcount -= len(found)
|
||||||
|
if tickcount > 0:
|
||||||
Utils.wait_for(
|
Utils.wait_for(
|
||||||
lambda: filter_.failManager.getFailCount() >= (tickcount, failcount),
|
lambda: filter_.failManager.getFailCount() >= (tickcount, failcount),
|
||||||
_maxWaitTime(10))
|
_maxWaitTime(10))
|
||||||
|
@ -599,13 +606,14 @@ class IgnoreIPDNS(LogCaptureTestCase):
|
||||||
cmd = os.path.join(STOCK_CONF_DIR, "filter.d/ignorecommands/apache-fakegooglebot")
|
cmd = os.path.join(STOCK_CONF_DIR, "filter.d/ignorecommands/apache-fakegooglebot")
|
||||||
## below test direct as python module:
|
## below test direct as python module:
|
||||||
mod = Utils.load_python_module(cmd)
|
mod = Utils.load_python_module(cmd)
|
||||||
self.assertFalse(mod.is_googlebot(mod.process_args([cmd, "128.178.222.69"])))
|
self.assertFalse(mod.is_googlebot(*mod.process_args([cmd, "128.178.222.69"])))
|
||||||
self.assertFalse(mod.is_googlebot(mod.process_args([cmd, "192.0.2.1"])))
|
self.assertFalse(mod.is_googlebot(*mod.process_args([cmd, "192.0.2.1"])))
|
||||||
|
self.assertFalse(mod.is_googlebot(*mod.process_args([cmd, "192.0.2.1", 0.1])))
|
||||||
bot_ips = ['66.249.66.1']
|
bot_ips = ['66.249.66.1']
|
||||||
for ip in bot_ips:
|
for ip in bot_ips:
|
||||||
self.assertTrue(mod.is_googlebot(mod.process_args([cmd, str(ip)])), "test of googlebot ip %s failed" % ip)
|
self.assertTrue(mod.is_googlebot(*mod.process_args([cmd, str(ip)])), "test of googlebot ip %s failed" % ip)
|
||||||
self.assertRaises(ValueError, lambda: mod.is_googlebot(mod.process_args([cmd])))
|
self.assertRaises(ValueError, lambda: mod.is_googlebot(*mod.process_args([cmd])))
|
||||||
self.assertRaises(ValueError, lambda: mod.is_googlebot(mod.process_args([cmd, "192.0"])))
|
self.assertRaises(ValueError, lambda: mod.is_googlebot(*mod.process_args([cmd, "192.0"])))
|
||||||
## via command:
|
## via command:
|
||||||
self.filter.ignoreCommand = cmd + " <ip>"
|
self.filter.ignoreCommand = cmd + " <ip>"
|
||||||
for ip in bot_ips:
|
for ip in bot_ips:
|
||||||
|
@ -617,7 +625,7 @@ class IgnoreIPDNS(LogCaptureTestCase):
|
||||||
self.pruneLog()
|
self.pruneLog()
|
||||||
self.filter.ignoreCommand = cmd + " bad arguments <ip>"
|
self.filter.ignoreCommand = cmd + " bad arguments <ip>"
|
||||||
self.assertFalse(self.filter.inIgnoreIPList("192.0"))
|
self.assertFalse(self.filter.inIgnoreIPList("192.0"))
|
||||||
self.assertLogged('Please provide a single IP as an argument.')
|
self.assertLogged('Usage')
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -800,7 +808,6 @@ class LogFileMonitor(LogCaptureTestCase):
|
||||||
_, self.name = tempfile.mkstemp('fail2ban', 'monitorfailures')
|
_, self.name = tempfile.mkstemp('fail2ban', 'monitorfailures')
|
||||||
self.file = open(self.name, 'a')
|
self.file = open(self.name, 'a')
|
||||||
self.filter = FilterPoll(DummyJail())
|
self.filter = FilterPoll(DummyJail())
|
||||||
self.filter.banASAP = False # avoid immediate ban in this tests
|
|
||||||
self.filter.addLogPath(self.name, autoSeek=False)
|
self.filter.addLogPath(self.name, autoSeek=False)
|
||||||
self.filter.active = True
|
self.filter.active = True
|
||||||
self.filter.addFailRegex(r"(?:(?:Authentication failure|Failed [-/\w+]+) for(?: [iI](?:llegal|nvalid) user)?|[Ii](?:llegal|nvalid) user|ROOT LOGIN REFUSED) .*(?: from|FROM) <HOST>")
|
self.filter.addFailRegex(r"(?:(?:Authentication failure|Failed [-/\w+]+) for(?: [iI](?:llegal|nvalid) user)?|[Ii](?:llegal|nvalid) user|ROOT LOGIN REFUSED) .*(?: from|FROM) <HOST>")
|
||||||
|
@ -960,7 +967,7 @@ class LogFileMonitor(LogCaptureTestCase):
|
||||||
os.rename(self.name, self.name + '.bak')
|
os.rename(self.name, self.name + '.bak')
|
||||||
_copy_lines_between_files(GetFailures.FILENAME_01, self.name, skip=14, n=1).close()
|
_copy_lines_between_files(GetFailures.FILENAME_01, self.name, skip=14, n=1).close()
|
||||||
self.filter.getFailures(self.name)
|
self.filter.getFailures(self.name)
|
||||||
_assert_correct_last_attempt(self, self.filter, GetFailures.FAILURES_01)
|
#_assert_correct_last_attempt(self, self.filter, GetFailures.FAILURES_01)
|
||||||
self.assertEqual(self.filter.failManager.getFailTotal(), 3)
|
self.assertEqual(self.filter.failManager.getFailTotal(), 3)
|
||||||
|
|
||||||
|
|
||||||
|
@ -1018,7 +1025,6 @@ def get_monitor_failures_testcase(Filter_):
|
||||||
self.file = open(self.name, 'a')
|
self.file = open(self.name, 'a')
|
||||||
self.jail = DummyJail()
|
self.jail = DummyJail()
|
||||||
self.filter = Filter_(self.jail)
|
self.filter = Filter_(self.jail)
|
||||||
self.filter.banASAP = False # avoid immediate ban in this tests
|
|
||||||
self.filter.addLogPath(self.name, autoSeek=False)
|
self.filter.addLogPath(self.name, autoSeek=False)
|
||||||
# speedup search using exact date pattern:
|
# speedup search using exact date pattern:
|
||||||
self.filter.setDatePattern(r'^(?:%a )?%b %d %H:%M:%S(?:\.%f)?(?: %ExY)?')
|
self.filter.setDatePattern(r'^(?:%a )?%b %d %H:%M:%S(?:\.%f)?(?: %ExY)?')
|
||||||
|
@ -1111,8 +1117,9 @@ def get_monitor_failures_testcase(Filter_):
|
||||||
skip=12, n=3, mode='w')
|
skip=12, n=3, mode='w')
|
||||||
self.assert_correct_last_attempt(GetFailures.FAILURES_01)
|
self.assert_correct_last_attempt(GetFailures.FAILURES_01)
|
||||||
|
|
||||||
def _wait4failures(self, count=2):
|
def _wait4failures(self, count=2, waitEmpty=True):
|
||||||
# Poll might need more time
|
# Poll might need more time
|
||||||
|
if waitEmpty:
|
||||||
self.assertTrue(self.isEmpty(_maxWaitTime(5)),
|
self.assertTrue(self.isEmpty(_maxWaitTime(5)),
|
||||||
"Queue must be empty but it is not: %s."
|
"Queue must be empty but it is not: %s."
|
||||||
% (', '.join([str(x) for x in self.jail.queue])))
|
% (', '.join([str(x) for x in self.jail.queue])))
|
||||||
|
@ -1277,14 +1284,14 @@ def get_monitor_failures_testcase(Filter_):
|
||||||
# tail written before, so let's not copy anything yet
|
# tail written before, so let's not copy anything yet
|
||||||
#_copy_lines_between_files(GetFailures.FILENAME_01, self.name, n=100)
|
#_copy_lines_between_files(GetFailures.FILENAME_01, self.name, n=100)
|
||||||
# we should detect the failures
|
# we should detect the failures
|
||||||
self.assert_correct_last_attempt(GetFailures.FAILURES_01, count=6) # was needed if we write twice above
|
self.assert_correct_last_attempt(GetFailures.FAILURES_01, count=3) # was needed if we write twice above
|
||||||
|
|
||||||
# now copy and get even more
|
# now copy and get even more
|
||||||
_copy_lines_between_files(GetFailures.FILENAME_01, self.file, skip=12, n=3)
|
_copy_lines_between_files(GetFailures.FILENAME_01, self.file, skip=12, n=3)
|
||||||
# check for 3 failures (not 9), because 6 already get above...
|
# check for 3 failures (not 9), because 6 already get above...
|
||||||
self.assert_correct_last_attempt(GetFailures.FAILURES_01)
|
self.assert_correct_last_attempt(GetFailures.FAILURES_01, count=3)
|
||||||
# total count in this test:
|
# total count in this test:
|
||||||
self.assertEqual(self.filter.failManager.getFailTotal(), 12)
|
self._wait4failures(12, False)
|
||||||
|
|
||||||
cls = MonitorFailures
|
cls = MonitorFailures
|
||||||
cls.__qualname__ = cls.__name__ = "MonitorFailures<%s>(%s)" \
|
cls.__qualname__ = cls.__name__ = "MonitorFailures<%s>(%s)" \
|
||||||
|
@ -1316,7 +1323,6 @@ def get_monitor_failures_journal_testcase(Filter_): # pragma: systemd no cover
|
||||||
def _initFilter(self, **kwargs):
|
def _initFilter(self, **kwargs):
|
||||||
self._getRuntimeJournal() # check journal available
|
self._getRuntimeJournal() # check journal available
|
||||||
self.filter = Filter_(self.jail, **kwargs)
|
self.filter = Filter_(self.jail, **kwargs)
|
||||||
self.filter.banASAP = False # avoid immediate ban in this tests
|
|
||||||
self.filter.addJournalMatch([
|
self.filter.addJournalMatch([
|
||||||
"SYSLOG_IDENTIFIER=fail2ban-testcases",
|
"SYSLOG_IDENTIFIER=fail2ban-testcases",
|
||||||
"TEST_FIELD=1",
|
"TEST_FIELD=1",
|
||||||
|
@ -1512,7 +1518,7 @@ def get_monitor_failures_journal_testcase(Filter_): # pragma: systemd no cover
|
||||||
"SYSLOG_IDENTIFIER=fail2ban-testcases",
|
"SYSLOG_IDENTIFIER=fail2ban-testcases",
|
||||||
"TEST_FIELD=1",
|
"TEST_FIELD=1",
|
||||||
"TEST_UUID=%s" % self.test_uuid])
|
"TEST_UUID=%s" % self.test_uuid])
|
||||||
self.assert_correct_ban("193.168.0.128", 4)
|
self.assert_correct_ban("193.168.0.128", 3)
|
||||||
_copy_lines_to_journal(
|
_copy_lines_to_journal(
|
||||||
self.test_file, self.journal_fields, n=6, skip=10)
|
self.test_file, self.journal_fields, n=6, skip=10)
|
||||||
# we should detect the failures
|
# we should detect the failures
|
||||||
|
@ -1526,7 +1532,7 @@ def get_monitor_failures_journal_testcase(Filter_): # pragma: systemd no cover
|
||||||
self.test_file, self.journal_fields, skip=15, n=4)
|
self.test_file, self.journal_fields, skip=15, n=4)
|
||||||
self.waitForTicks(1)
|
self.waitForTicks(1)
|
||||||
self.assertTrue(self.isFilled(10))
|
self.assertTrue(self.isFilled(10))
|
||||||
self.assert_correct_ban("87.142.124.10", 4)
|
self.assert_correct_ban("87.142.124.10", 3)
|
||||||
# Add direct utf, unicode, blob:
|
# Add direct utf, unicode, blob:
|
||||||
for l in (
|
for l in (
|
||||||
"error: PAM: Authentication failure for \xe4\xf6\xfc\xdf from 192.0.2.1",
|
"error: PAM: Authentication failure for \xe4\xf6\xfc\xdf from 192.0.2.1",
|
||||||
|
@ -1570,7 +1576,6 @@ class GetFailures(LogCaptureTestCase):
|
||||||
setUpMyTime()
|
setUpMyTime()
|
||||||
self.jail = DummyJail()
|
self.jail = DummyJail()
|
||||||
self.filter = FileFilter(self.jail)
|
self.filter = FileFilter(self.jail)
|
||||||
self.filter.banASAP = False # avoid immediate ban in this tests
|
|
||||||
self.filter.active = True
|
self.filter.active = True
|
||||||
# speedup search using exact date pattern:
|
# speedup search using exact date pattern:
|
||||||
self.filter.setDatePattern(r'^(?:%a )?%b %d %H:%M:%S(?:\.%f)?(?: %ExY)?')
|
self.filter.setDatePattern(r'^(?:%a )?%b %d %H:%M:%S(?:\.%f)?(?: %ExY)?')
|
||||||
|
@ -1641,6 +1646,7 @@ class GetFailures(LogCaptureTestCase):
|
||||||
[u'Aug 14 11:%d:59 i60p295 sshd[12365]: Failed publickey for roehl from ::ffff:141.3.81.106 port 51332 ssh2'
|
[u'Aug 14 11:%d:59 i60p295 sshd[12365]: Failed publickey for roehl from ::ffff:141.3.81.106 port 51332 ssh2'
|
||||||
% m for m in 53, 54, 57, 58])
|
% m for m in 53, 54, 57, 58])
|
||||||
|
|
||||||
|
self.filter.setMaxRetry(4)
|
||||||
self.filter.addLogPath(GetFailures.FILENAME_02, autoSeek=0)
|
self.filter.addLogPath(GetFailures.FILENAME_02, autoSeek=0)
|
||||||
self.filter.addFailRegex(r"Failed .* from <HOST>")
|
self.filter.addFailRegex(r"Failed .* from <HOST>")
|
||||||
self.filter.getFailures(GetFailures.FILENAME_02)
|
self.filter.getFailures(GetFailures.FILENAME_02)
|
||||||
|
@ -1649,6 +1655,7 @@ class GetFailures(LogCaptureTestCase):
|
||||||
def testGetFailures03(self):
|
def testGetFailures03(self):
|
||||||
output = ('203.162.223.135', 6, 1124013600.0)
|
output = ('203.162.223.135', 6, 1124013600.0)
|
||||||
|
|
||||||
|
self.filter.setMaxRetry(6)
|
||||||
self.filter.addLogPath(GetFailures.FILENAME_03, autoSeek=0)
|
self.filter.addLogPath(GetFailures.FILENAME_03, autoSeek=0)
|
||||||
self.filter.addFailRegex(r"error,relay=<HOST>,.*550 User unknown")
|
self.filter.addFailRegex(r"error,relay=<HOST>,.*550 User unknown")
|
||||||
self.filter.getFailures(GetFailures.FILENAME_03)
|
self.filter.getFailures(GetFailures.FILENAME_03)
|
||||||
|
@ -1657,6 +1664,7 @@ class GetFailures(LogCaptureTestCase):
|
||||||
def testGetFailures03_InOperation(self):
|
def testGetFailures03_InOperation(self):
|
||||||
output = ('203.162.223.135', 9, 1124013600.0)
|
output = ('203.162.223.135', 9, 1124013600.0)
|
||||||
|
|
||||||
|
self.filter.setMaxRetry(9)
|
||||||
self.filter.addLogPath(GetFailures.FILENAME_03, autoSeek=0)
|
self.filter.addLogPath(GetFailures.FILENAME_03, autoSeek=0)
|
||||||
self.filter.addFailRegex(r"error,relay=<HOST>,.*550 User unknown")
|
self.filter.addFailRegex(r"error,relay=<HOST>,.*550 User unknown")
|
||||||
self.filter.getFailures(GetFailures.FILENAME_03, inOperation=True)
|
self.filter.getFailures(GetFailures.FILENAME_03, inOperation=True)
|
||||||
|
@ -1674,7 +1682,7 @@ class GetFailures(LogCaptureTestCase):
|
||||||
def testGetFailures03_Seek2(self):
|
def testGetFailures03_Seek2(self):
|
||||||
# same test as above but with seek to 'Aug 14 11:59:04' - so other output ...
|
# same test as above but with seek to 'Aug 14 11:59:04' - so other output ...
|
||||||
output = ('203.162.223.135', 2, 1124013600.0)
|
output = ('203.162.223.135', 2, 1124013600.0)
|
||||||
self.filter.setMaxRetry(1)
|
self.filter.setMaxRetry(2)
|
||||||
|
|
||||||
self.filter.addLogPath(GetFailures.FILENAME_03, autoSeek=output[2])
|
self.filter.addLogPath(GetFailures.FILENAME_03, autoSeek=output[2])
|
||||||
self.filter.addFailRegex(r"error,relay=<HOST>,.*550 User unknown")
|
self.filter.addFailRegex(r"error,relay=<HOST>,.*550 User unknown")
|
||||||
|
@ -1684,10 +1692,12 @@ class GetFailures(LogCaptureTestCase):
|
||||||
def testGetFailures04(self):
|
def testGetFailures04(self):
|
||||||
# because of not exact time in testcase04.log (no year), we should always use our test time:
|
# because of not exact time in testcase04.log (no year), we should always use our test time:
|
||||||
self.assertEqual(MyTime.time(), 1124013600)
|
self.assertEqual(MyTime.time(), 1124013600)
|
||||||
# should find exact 4 failures for *.186 and 2 failures for *.185
|
# should find exact 4 failures for *.186 and 2 failures for *.185, but maxretry is 2, so 3 tickets:
|
||||||
output = (('212.41.96.186', 4, 1124013600.0),
|
output = (
|
||||||
('212.41.96.185', 2, 1124013598.0))
|
('212.41.96.186', 2, 1124013480.0),
|
||||||
|
('212.41.96.186', 2, 1124013600.0),
|
||||||
|
('212.41.96.185', 2, 1124013598.0)
|
||||||
|
)
|
||||||
# speedup search using exact date pattern:
|
# speedup search using exact date pattern:
|
||||||
self.filter.setDatePattern((r'^%ExY(?P<_sep>[-/.])%m(?P=_sep)%d[T ]%H:%M:%S(?:[.,]%f)?(?:\s*%z)?',
|
self.filter.setDatePattern((r'^%ExY(?P<_sep>[-/.])%m(?P=_sep)%d[T ]%H:%M:%S(?:[.,]%f)?(?:\s*%z)?',
|
||||||
r'^(?:%a )?%b %d %H:%M:%S(?:\.%f)?(?: %ExY)?',
|
r'^(?:%a )?%b %d %H:%M:%S(?:\.%f)?(?: %ExY)?',
|
||||||
|
@ -1744,9 +1754,11 @@ class GetFailures(LogCaptureTestCase):
|
||||||
unittest.F2B.SkipIfNoNetwork()
|
unittest.F2B.SkipIfNoNetwork()
|
||||||
# We should still catch failures with usedns = no ;-)
|
# We should still catch failures with usedns = no ;-)
|
||||||
output_yes = (
|
output_yes = (
|
||||||
('93.184.216.34', 2, 1124013539.0,
|
('93.184.216.34', 1, 1124013299.0,
|
||||||
[u'Aug 14 11:54:59 i60p295 sshd[12365]: Failed publickey for roehl from example.com port 51332 ssh2',
|
[u'Aug 14 11:54:59 i60p295 sshd[12365]: Failed publickey for roehl from example.com port 51332 ssh2']
|
||||||
u'Aug 14 11:58:59 i60p295 sshd[12365]: Failed publickey for roehl from ::ffff:93.184.216.34 port 51332 ssh2']
|
),
|
||||||
|
('93.184.216.34', 1, 1124013539.0,
|
||||||
|
[u'Aug 14 11:58:59 i60p295 sshd[12365]: Failed publickey for roehl from ::ffff:93.184.216.34 port 51332 ssh2']
|
||||||
),
|
),
|
||||||
('2606:2800:220:1:248:1893:25c8:1946', 1, 1124013299.0,
|
('2606:2800:220:1:248:1893:25c8:1946', 1, 1124013299.0,
|
||||||
[u'Aug 14 11:54:59 i60p295 sshd[12365]: Failed publickey for roehl from example.com port 51332 ssh2']
|
[u'Aug 14 11:54:59 i60p295 sshd[12365]: Failed publickey for roehl from example.com port 51332 ssh2']
|
||||||
|
@ -1771,7 +1783,6 @@ class GetFailures(LogCaptureTestCase):
|
||||||
self.pruneLog("[test-phase useDns=%s]" % useDns)
|
self.pruneLog("[test-phase useDns=%s]" % useDns)
|
||||||
jail = DummyJail()
|
jail = DummyJail()
|
||||||
filter_ = FileFilter(jail, useDns=useDns)
|
filter_ = FileFilter(jail, useDns=useDns)
|
||||||
filter_.banASAP = False # avoid immediate ban in this tests
|
|
||||||
filter_.active = True
|
filter_.active = True
|
||||||
filter_.failManager.setMaxRetry(1) # we might have just few failures
|
filter_.failManager.setMaxRetry(1) # we might have just few failures
|
||||||
|
|
||||||
|
@ -1781,8 +1792,11 @@ class GetFailures(LogCaptureTestCase):
|
||||||
_assert_correct_last_attempt(self, filter_, output)
|
_assert_correct_last_attempt(self, filter_, output)
|
||||||
|
|
||||||
def testGetFailuresMultiRegex(self):
|
def testGetFailuresMultiRegex(self):
|
||||||
output = ('141.3.81.106', 8, 1124013541.0)
|
output = [
|
||||||
|
('141.3.81.106', 8, 1124013541.0)
|
||||||
|
]
|
||||||
|
|
||||||
|
self.filter.setMaxRetry(8)
|
||||||
self.filter.addLogPath(GetFailures.FILENAME_02, autoSeek=False)
|
self.filter.addLogPath(GetFailures.FILENAME_02, autoSeek=False)
|
||||||
self.filter.addFailRegex(r"Failed .* from <HOST>")
|
self.filter.addFailRegex(r"Failed .* from <HOST>")
|
||||||
self.filter.addFailRegex(r"Accepted .* from <HOST>")
|
self.filter.addFailRegex(r"Accepted .* from <HOST>")
|
||||||
|
@ -1800,8 +1814,11 @@ class GetFailures(LogCaptureTestCase):
|
||||||
self.assertRaises(FailManagerEmpty, self.filter.failManager.toBan)
|
self.assertRaises(FailManagerEmpty, self.filter.failManager.toBan)
|
||||||
|
|
||||||
def testGetFailuresMultiLine(self):
|
def testGetFailuresMultiLine(self):
|
||||||
output = [("192.0.43.10", 2, 1124013599.0),
|
output = [
|
||||||
("192.0.43.11", 1, 1124013598.0)]
|
("192.0.43.10", 1, 1124013598.0),
|
||||||
|
("192.0.43.10", 1, 1124013599.0),
|
||||||
|
("192.0.43.11", 1, 1124013598.0)
|
||||||
|
]
|
||||||
self.filter.addLogPath(GetFailures.FILENAME_MULTILINE, autoSeek=False)
|
self.filter.addLogPath(GetFailures.FILENAME_MULTILINE, autoSeek=False)
|
||||||
self.filter.setMaxLines(100)
|
self.filter.setMaxLines(100)
|
||||||
self.filter.addFailRegex(r"^.*rsyncd\[(?P<pid>\d+)\]: connect from .+ \(<HOST>\)$<SKIPLINES>^.+ rsyncd\[(?P=pid)\]: rsync error: .*$")
|
self.filter.addFailRegex(r"^.*rsyncd\[(?P<pid>\d+)\]: connect from .+ \(<HOST>\)$<SKIPLINES>^.+ rsyncd\[(?P=pid)\]: rsync error: .*$")
|
||||||
|
@ -1809,17 +1826,13 @@ class GetFailures(LogCaptureTestCase):
|
||||||
|
|
||||||
self.filter.getFailures(GetFailures.FILENAME_MULTILINE)
|
self.filter.getFailures(GetFailures.FILENAME_MULTILINE)
|
||||||
|
|
||||||
foundList = []
|
_assert_correct_last_attempt(self, self.filter, output)
|
||||||
while True:
|
|
||||||
try:
|
|
||||||
foundList.append(
|
|
||||||
_ticket_tuple(self.filter.failManager.toBan())[0:3])
|
|
||||||
except FailManagerEmpty:
|
|
||||||
break
|
|
||||||
self.assertSortedEqual(foundList, output)
|
|
||||||
|
|
||||||
def testGetFailuresMultiLineIgnoreRegex(self):
|
def testGetFailuresMultiLineIgnoreRegex(self):
|
||||||
output = [("192.0.43.10", 2, 1124013599.0)]
|
output = [
|
||||||
|
("192.0.43.10", 1, 1124013598.0),
|
||||||
|
("192.0.43.10", 1, 1124013599.0)
|
||||||
|
]
|
||||||
self.filter.addLogPath(GetFailures.FILENAME_MULTILINE, autoSeek=False)
|
self.filter.addLogPath(GetFailures.FILENAME_MULTILINE, autoSeek=False)
|
||||||
self.filter.setMaxLines(100)
|
self.filter.setMaxLines(100)
|
||||||
self.filter.addFailRegex(r"^.*rsyncd\[(?P<pid>\d+)\]: connect from .+ \(<HOST>\)$<SKIPLINES>^.+ rsyncd\[(?P=pid)\]: rsync error: .*$")
|
self.filter.addFailRegex(r"^.*rsyncd\[(?P<pid>\d+)\]: connect from .+ \(<HOST>\)$<SKIPLINES>^.+ rsyncd\[(?P=pid)\]: rsync error: .*$")
|
||||||
|
@ -1828,14 +1841,17 @@ class GetFailures(LogCaptureTestCase):
|
||||||
|
|
||||||
self.filter.getFailures(GetFailures.FILENAME_MULTILINE)
|
self.filter.getFailures(GetFailures.FILENAME_MULTILINE)
|
||||||
|
|
||||||
_assert_correct_last_attempt(self, self.filter, output.pop())
|
_assert_correct_last_attempt(self, self.filter, output)
|
||||||
|
|
||||||
self.assertRaises(FailManagerEmpty, self.filter.failManager.toBan)
|
self.assertRaises(FailManagerEmpty, self.filter.failManager.toBan)
|
||||||
|
|
||||||
def testGetFailuresMultiLineMultiRegex(self):
|
def testGetFailuresMultiLineMultiRegex(self):
|
||||||
output = [("192.0.43.10", 2, 1124013599.0),
|
output = [
|
||||||
|
("192.0.43.10", 1, 1124013598.0),
|
||||||
|
("192.0.43.10", 1, 1124013599.0),
|
||||||
("192.0.43.11", 1, 1124013598.0),
|
("192.0.43.11", 1, 1124013598.0),
|
||||||
("192.0.43.15", 1, 1124013598.0)]
|
("192.0.43.15", 1, 1124013598.0)
|
||||||
|
]
|
||||||
self.filter.addLogPath(GetFailures.FILENAME_MULTILINE, autoSeek=False)
|
self.filter.addLogPath(GetFailures.FILENAME_MULTILINE, autoSeek=False)
|
||||||
self.filter.setMaxLines(100)
|
self.filter.setMaxLines(100)
|
||||||
self.filter.addFailRegex(r"^.*rsyncd\[(?P<pid>\d+)\]: connect from .+ \(<HOST>\)$<SKIPLINES>^.+ rsyncd\[(?P=pid)\]: rsync error: .*$")
|
self.filter.addFailRegex(r"^.*rsyncd\[(?P<pid>\d+)\]: connect from .+ \(<HOST>\)$<SKIPLINES>^.+ rsyncd\[(?P=pid)\]: rsync error: .*$")
|
||||||
|
@ -1844,14 +1860,9 @@ class GetFailures(LogCaptureTestCase):
|
||||||
|
|
||||||
self.filter.getFailures(GetFailures.FILENAME_MULTILINE)
|
self.filter.getFailures(GetFailures.FILENAME_MULTILINE)
|
||||||
|
|
||||||
foundList = []
|
_assert_correct_last_attempt(self, self.filter, output)
|
||||||
while True:
|
|
||||||
try:
|
self.assertRaises(FailManagerEmpty, self.filter.failManager.toBan)
|
||||||
foundList.append(
|
|
||||||
_ticket_tuple(self.filter.failManager.toBan())[0:3])
|
|
||||||
except FailManagerEmpty:
|
|
||||||
break
|
|
||||||
self.assertSortedEqual(foundList, output)
|
|
||||||
|
|
||||||
|
|
||||||
class DNSUtilsTests(unittest.TestCase):
|
class DNSUtilsTests(unittest.TestCase):
|
||||||
|
|
Loading…
Reference in New Issue