Merge branch '0.10' into 0.11

# Conflicts:
#	config/action.d/firewallcmd-ipset.conf
#	fail2ban/server/jail.py
#	fail2ban/tests/servertestcase.py
pull/2019/merge
sebres 2017-12-06 00:14:23 +01:00
commit 7e5d8f37fd
14 changed files with 130 additions and 77 deletions

View File

@ -49,6 +49,7 @@ ver. 0.11.0-dev-0 (2017/??/??) - development nightly edition
* `action.d/pf.conf`: * `action.d/pf.conf`:
- fixed syntax error in achnor definition (documentation, see gh-1919); - fixed syntax error in achnor definition (documentation, see gh-1919);
- enclose ports in braces for multiport jails (see gh-1925); - enclose ports in braces for multiport jails (see gh-1925);
* `action.d/firewallcmd-ipset.conf`: fixed create of set for ipv6 (missing `family inet6`, gh-1990)
* `filter.d/sshd.conf`: extended failregex for modes "extra"/"aggressive": now finds all possible (also future) * `filter.d/sshd.conf`: extended failregex for modes "extra"/"aggressive": now finds all possible (also future)
forms of "no matching (cipher|mac|MAC|compression method|key exchange method|host key type) found", forms of "no matching (cipher|mac|MAC|compression method|key exchange method|host key type) found",
see "ssherr.c" for all possible SSH_ERR_..._ALG_MATCH errors (gh-1943, gh-1944); see "ssherr.c" for all possible SSH_ERR_..._ALG_MATCH errors (gh-1943, gh-1944);
@ -60,12 +61,21 @@ ver. 0.11.0-dev-0 (2017/??/??) - development nightly edition
(corresponds %H, but allows space if not zero-padded). (corresponds %H, but allows space if not zero-padded).
- %l - one- or two-digit number giving the hour of the day (12-11) on a 12-hour clock, - %l - one- or two-digit number giving the hour of the day (12-11) on a 12-hour clock,
(corresponds %I, but allows space if not zero-padded). (corresponds %I, but allows space if not zero-padded).
* `filter.d/exim.conf`: added mode `aggressive` to ban flood resp. DDOS-similar failures (gh-1983);
* New Actions: * New Actions:
- `action.d/nginx-block-map.conf` - in order to ban not IP-related tickets via nginx (session blacklisting in - `action.d/nginx-block-map.conf` - in order to ban not IP-related tickets via nginx (session blacklisting in
nginx-location with map-file); nginx-location with map-file);
### Enhancements ### Enhancements
* jail.conf: extended with new parameter `mode` for the filters supporting it (gh-1988);
* action.d/pf.conf: extended with bulk-unban, command `actionflush` in order to flush all bans at once. * action.d/pf.conf: extended with bulk-unban, command `actionflush` in order to flush all bans at once.
* Introduced new parameters for logging within fail2ban-server (gh-1980).
Usage `logtarget = target[facility=..., datetime=on|off, format="..."]`:
- `facility` - specify syslog facility (default `daemon`, see https://docs.python.org/2/library/logging.handlers.html#sysloghandler
for the list of facilities);
- `datetime` - add date-time to the message (default on, ignored if `format` specified);
- `format` - specify own format how it will be logged, for example for short-log into STDOUT:
`fail2ban-server -f --logtarget 'stdout[format="%(relativeCreated)5d | %(message)s"]' start`;
ver. 0.10.1 (2017/10/12) - succeeded-before-friday-the-13th ver. 0.10.1 (2017/10/12) - succeeded-before-friday-the-13th

View File

@ -18,7 +18,7 @@ before = firewallcmd-common.conf
[Definition] [Definition]
actionstart = ipset create <ipmset> hash:ip actionstart = ipset create <ipmset> hash:ip<familyopt>
firewall-cmd --direct --add-rule <family> filter <chain> 0 -p <protocol> -m multiport --dports <port> -m set --match-set <ipmset> src -j <blocktype> firewall-cmd --direct --add-rule <family> filter <chain> 0 -p <protocol> -m multiport --dports <port> -m set --match-set <ipmset> src -j <blocktype>
actionstop = firewall-cmd --direct --remove-rule <family> filter <chain> 0 -p <protocol> -m multiport --dports <port> -m set --match-set <ipmset> src -j <blocktype> actionstop = firewall-cmd --direct --remove-rule <family> filter <chain> 0 -p <protocol> -m multiport --dports <port> -m set --match-set <ipmset> src -j <blocktype>
@ -41,10 +41,12 @@ actionunban = ipset del <ipmset> <ip> -exist
chain = INPUT_direct chain = INPUT_direct
ipmset = f2b-<name> ipmset = f2b-<name>
familyopt =
[Init?family=inet6] [Init?family=inet6]
ipmset = f2b-<name>6 ipmset = f2b-<name>6
familyopt = <sp>family inet6
# DEV NOTES: # DEV NOTES:

View File

@ -24,6 +24,21 @@ failregex = ^%(pid)s %(host_info)ssender verify fail for <\S+>: (?:Unknown user|
^%(pid)s SMTP protocol error in "[^"]+(?:"+[^"]*(?="))*?" %(host_info)sAUTH command used when not advertised\s*$ ^%(pid)s SMTP protocol error in "[^"]+(?:"+[^"]*(?="))*?" %(host_info)sAUTH command used when not advertised\s*$
^%(pid)s no MAIL in SMTP connection from (?:[^\[\( ]* )?(?:\(\S*\) )?%(host_info)sD=\d\S*s(?: C=\S*)?\s*$ ^%(pid)s no MAIL in SMTP connection from (?:[^\[\( ]* )?(?:\(\S*\) )?%(host_info)sD=\d\S*s(?: C=\S*)?\s*$
^%(pid)s (?:[\w\-]+ )?SMTP connection from (?:[^\[\( ]* )?(?:\(\S*\) )?%(host_info)sclosed by DROP in ACL\s*$ ^%(pid)s (?:[\w\-]+ )?SMTP connection from (?:[^\[\( ]* )?(?:\(\S*\) )?%(host_info)sclosed by DROP in ACL\s*$
<mdre-<mode>>
mdre-aggressive = ^%(pid)s no host name found for IP address <HOST>$
^%(pid)s no IP address found for host \S+ \(during SMTP connection from \[<HOST>\]\)$
mdre-normal =
# Parameter `mode` - `normal` or `aggressive`.
# Aggressive mode can be used to match flood and ddos-similar log-entries like:
# 'no host found for IP', 'no IP found for host'.
# Note this is not an authentication failures, so it may produce lots of false
# positives on misconfigured MTAs.
# Ex.:
# filter = exim[mode=aggressive]
mode = normal
ignoreregex = ignoreregex =

View File

@ -36,10 +36,10 @@ ngx_limit_req_zones = [^"]+
# Use following full expression if you should range limit request to specified # Use following full expression if you should range limit request to specified
# servers, requests, referrers etc. only : # servers, requests, referrers etc. only :
# #
# failregex = ^\s*\[error\] \d+#\d+: \*\d+ limiting requests, excess: [\d\.]+ by zone "(?:%(ngx_limit_req_zones)s)", client: <HOST>, server: \S*, request: "\S+ \S+ HTTP/\d+\.\d+", host: "\S+"(, referrer: "\S+")?\s*$ # failregex = ^\s*\[[a-z]+\] \d+#\d+: \*\d+ limiting requests, excess: [\d\.]+ by zone "(?:%(ngx_limit_req_zones)s)", client: <HOST>, server: \S*, request: "\S+ \S+ HTTP/\d+\.\d+", host: "\S+"(, referrer: "\S+")?\s*$
# Shortly, much faster and stable version of regexp: # Shortly, much faster and stable version of regexp:
failregex = ^\s*\[error\] \d+#\d+: \*\d+ limiting requests, excess: [\d\.]+ by zone "(?:%(ngx_limit_req_zones)s)", client: <HOST>, failregex = ^\s*\[[a-z]+\] \d+#\d+: \*\d+ limiting requests, excess: [\d\.]+ by zone "(?:%(ngx_limit_req_zones)s)", client: <HOST>,
ignoreregex = ignoreregex =

View File

@ -154,10 +154,13 @@ logencoding = auto
enabled = false enabled = false
# "mode" defines the mode of the filter (see corresponding filter implementation for more info).
mode = normal
# "filter" defines the filter to use by the jail. # "filter" defines the filter to use by the jail.
# By default jails have names matching their filter name # By default jails have names matching their filter name
# #
filter = %(__name__)s filter = %(__name__)s[mode=%(mode)s]
# #
@ -274,8 +277,7 @@ action = %(action_)s
# To use more aggressive sshd modes set filter parameter "mode" in jail.local: # To use more aggressive sshd modes set filter parameter "mode" in jail.local:
# normal (default), ddos, extra or aggressive (combines all). # normal (default), ddos, extra or aggressive (combines all).
# See "tests/files/logs/sshd" or "filter.d/sshd.conf" for usage example and details. # See "tests/files/logs/sshd" or "filter.d/sshd.conf" for usage example and details.
mode = normal #mode = normal
filter = sshd[mode=%(mode)s]
port = ssh port = ssh
logpath = %(sshd_log)s logpath = %(sshd_log)s
backend = %(sshd_backend)s backend = %(sshd_backend)s
@ -573,7 +575,6 @@ backend = %(syslog_backend)s
[postfix] [postfix]
# To use another modes set filter parameter "mode" in jail.local: # To use another modes set filter parameter "mode" in jail.local:
mode = more mode = more
filter = postfix[mode=%(mode)s]
port = smtp,465,submission port = smtp,465,submission
logpath = %(postfix_log)s logpath = %(postfix_log)s
backend = %(postfix_backend)s backend = %(postfix_backend)s
@ -599,8 +600,7 @@ backend = %(syslog_backend)s
# To use more aggressive modes set filter parameter "mode" in jail.local: # To use more aggressive modes set filter parameter "mode" in jail.local:
# normal (default), extra or aggressive # normal (default), extra or aggressive
# See "tests/files/logs/sendmail-reject" or "filter.d/sendmail-reject.conf" for usage example and details. # See "tests/files/logs/sendmail-reject" or "filter.d/sendmail-reject.conf" for usage example and details.
mode = normal #mode = normal
filter = sendmail-reject[mode=%(mode)s]
port = smtp,465,submission port = smtp,465,submission
logpath = %(syslog_mail)s logpath = %(syslog_mail)s
backend = %(syslog_backend)s backend = %(syslog_backend)s
@ -636,7 +636,8 @@ logpath = %(solidpop3d_log)s
[exim] [exim]
# see filter.d/exim.conf for further modes supported from filter:
#mode = normal
port = smtp,465,submission port = smtp,465,submission
logpath = %(exim_main_log)s logpath = %(exim_main_log)s
@ -906,17 +907,14 @@ logpath = /var/log/haproxy.log
[slapd] [slapd]
port = ldap,ldaps port = ldap,ldaps
filter = slapd
logpath = /var/log/slapd.log logpath = /var/log/slapd.log
[domino-smtp] [domino-smtp]
port = smtp,ssmtp port = smtp,ssmtp
filter = domino-smtp
logpath = /home/domino01/data/IBM_TECHNICAL_SUPPORT/console.log logpath = /home/domino01/data/IBM_TECHNICAL_SUPPORT/console.log
[phpmyadmin-syslog] [phpmyadmin-syslog]
port = http,https port = http,https
filter = phpmyadmin-syslog
logpath = %(syslog_authpriv)s logpath = %(syslog_authpriv)s
backend = %(syslog_backend)s backend = %(syslog_backend)s

View File

@ -46,12 +46,12 @@ except ImportError:
FilterSystemd = None FilterSystemd = None
from ..version import version from ..version import version
from .jailreader import JailReader
from .filterreader import FilterReader from .filterreader import FilterReader
from ..server.filter import Filter, FileContainer from ..server.filter import Filter, FileContainer
from ..server.failregex import Regex, RegexException from ..server.failregex import Regex, RegexException
from ..helpers import str2LogLevel, getVerbosityFormat, FormatterWithTraceBack, getLogger, PREFER_ENC from ..helpers import str2LogLevel, getVerbosityFormat, FormatterWithTraceBack, getLogger, \
extractOptions, PREFER_ENC
# Gets the instance of the logger. # Gets the instance of the logger.
logSys = getLogger("fail2ban") logSys = getLogger("fail2ban")
@ -287,7 +287,7 @@ class Fail2banRegex(object):
fltFile = None fltFile = None
fltOpt = {} fltOpt = {}
if regextype == 'fail': if regextype == 'fail':
fltName, fltOpt = JailReader.extractOptions(value) fltName, fltOpt = extractOptions(value)
if fltName is not None: if fltName is not None:
if "." in fltName[~5:]: if "." in fltName[~5:]:
tryNames = (fltName,) tryNames = (fltName,)
@ -606,7 +606,7 @@ class Fail2banRegex(object):
return False return False
output( "Use systemd journal" ) output( "Use systemd journal" )
output( "Use encoding : %s" % self._encoding ) output( "Use encoding : %s" % self._encoding )
backend, beArgs = JailReader.extractOptions(cmd_log) backend, beArgs = extractOptions(cmd_log)
flt = FilterSystemd(None, **beArgs) flt = FilterSystemd(None, **beArgs)
flt.setLogEncoding(self._encoding) flt.setLogEncoding(self._encoding)
myjournal = flt.getJournalReader() myjournal = flt.getJournalReader()

View File

@ -33,8 +33,7 @@ from .configreader import ConfigReaderUnshared, ConfigReader
from .filterreader import FilterReader from .filterreader import FilterReader
from .actionreader import ActionReader from .actionreader import ActionReader
from ..version import version from ..version import version
from ..helpers import getLogger from ..helpers import getLogger, extractOptions, splitwords
from ..helpers import splitwords
# Gets the instance of the logger. # Gets the instance of the logger.
logSys = getLogger(__name__) logSys = getLogger(__name__)
@ -42,15 +41,6 @@ logSys = getLogger(__name__)
class JailReader(ConfigReader): class JailReader(ConfigReader):
# regex, to extract list of options:
optionCRE = re.compile(r"^([^\[]+)(?:\[(.*)\])?\s*$", re.DOTALL)
# regex, to iterate over single option in option list, syntax:
# `action = act[p1="...", p2='...', p3=...]`, where the p3=... not contains `,` or ']'
# since v0.10 separator extended with `]\s*[` for support of multiple option groups, syntax
# `action = act[p1=...][p2=...]`
optionExtractRE = re.compile(
r'([\w\-_\.]+)=(?:"([^"]*)"|\'([^\']*)\'|([^,\]]*))(?:,|\]\s*\[|$)', re.DOTALL)
def __init__(self, name, force_enable=False, **kwargs): def __init__(self, name, force_enable=False, **kwargs):
ConfigReader.__init__(self, **kwargs) ConfigReader.__init__(self, **kwargs)
self.__name = name self.__name = name
@ -141,7 +131,7 @@ class JailReader(ConfigReader):
# Read filter # Read filter
flt = self.__opts["filter"] flt = self.__opts["filter"]
if flt: if flt:
filterName, filterOpt = JailReader.extractOptions(flt) filterName, filterOpt = extractOptions(flt)
if not filterName: if not filterName:
raise JailDefError("Invalid filter definition %r" % flt) raise JailDefError("Invalid filter definition %r" % flt)
self.__filter = FilterReader( self.__filter = FilterReader(
@ -171,7 +161,7 @@ class JailReader(ConfigReader):
try: try:
if not act: # skip empty actions if not act: # skip empty actions
continue continue
actName, actOpt = JailReader.extractOptions(act) actName, actOpt = extractOptions(act)
if not actName: if not actName:
raise JailDefError("Invalid action definition %r" % act) raise JailDefError("Invalid action definition %r" % act)
if actName.endswith(".py"): if actName.endswith(".py"):
@ -275,22 +265,5 @@ class JailReader(ConfigReader):
stream.insert(0, ["add", self.__name, backend]) stream.insert(0, ["add", self.__name, backend])
return stream return stream
@staticmethod
def extractOptions(option):
match = JailReader.optionCRE.match(option)
if not match:
# TODO proper error handling
return None, None
option_name, optstr = match.groups()
option_opts = dict()
if optstr:
for optmatch in JailReader.optionExtractRE.finditer(optstr):
opt = optmatch.group(1)
value = [
val for val in optmatch.group(2,3,4) if val is not None][0]
option_opts[opt.strip()] = value.strip()
return option_name, option_opts
class JailDefError(Exception): class JailDefError(Exception):
pass pass

View File

@ -237,6 +237,34 @@ else:
return uni_decode(x, enc, 'replace') return uni_decode(x, enc, 'replace')
#
# Following function used for parse options from parameter (e.g. `name[p1=0, p2="..."][p3='...']`).
#
# regex, to extract list of options:
OPTION_CRE = re.compile(r"^([^\[]+)(?:\[(.*)\])?\s*$", re.DOTALL)
# regex, to iterate over single option in option list, syntax:
# `action = act[p1="...", p2='...', p3=...]`, where the p3=... not contains `,` or ']'
# since v0.10 separator extended with `]\s*[` for support of multiple option groups, syntax
# `action = act[p1=...][p2=...]`
OPTION_EXTRACT_CRE = re.compile(
r'([\w\-_\.]+)=(?:"([^"]*)"|\'([^\']*)\'|([^,\]]*))(?:,|\]\s*\[|$)', re.DOTALL)
def extractOptions(option):
match = OPTION_CRE.match(option)
if not match:
# TODO proper error handling
return None, None
option_name, optstr = match.groups()
option_opts = dict()
if optstr:
for optmatch in OPTION_EXTRACT_CRE.finditer(optstr):
opt = optmatch.group(1)
value = [
val for val in optmatch.group(2,3,4) if val is not None][0]
option_opts[opt.strip()] = value.strip()
return option_name, option_opts
# #
# Following facilities used for safe recursive interpolation of # Following facilities used for safe recursive interpolation of
# tags (<tag>) in tagged options. # tags (<tag>) in tagged options.

View File

@ -29,8 +29,7 @@ import random
import Queue import Queue
from .actions import Actions from .actions import Actions
from ..client.jailreader import JailReader from ..helpers import getLogger, extractOptions, MyTime
from ..helpers import getLogger, MyTime
from .mytime import MyTime from .mytime import MyTime
# Gets the instance of the logger. # Gets the instance of the logger.
@ -90,7 +89,7 @@ class Jail(object):
return "%s(%r)" % (self.__class__.__name__, self.name) return "%s(%r)" % (self.__class__.__name__, self.name)
def _setBackend(self, backend): def _setBackend(self, backend):
backend, beArgs = JailReader.extractOptions(backend) backend, beArgs = extractOptions(backend)
backend = backend.lower() # to assure consistent matching backend = backend.lower() # to assure consistent matching
backends = self._BACKENDS backends = self._BACKENDS

View File

@ -38,7 +38,7 @@ from .filter import FileFilter, JournalFilter
from .transmitter import Transmitter from .transmitter import Transmitter
from .asyncserver import AsyncServer, AsyncServerException from .asyncserver import AsyncServer, AsyncServerException
from .. import version from .. import version
from ..helpers import getLogger, str2LogLevel, getVerbosityFormat, excepthook from ..helpers import getLogger, extractOptions, str2LogLevel, getVerbosityFormat, excepthook
# Gets the instance of the logger. # Gets the instance of the logger.
logSys = getLogger(__name__) logSys = getLogger(__name__)
@ -577,6 +577,7 @@ class Server:
def setLogTarget(self, target): def setLogTarget(self, target):
# check reserved targets in uppercase, don't change target, because it can be file: # check reserved targets in uppercase, don't change target, because it can be file:
target, logOptions = extractOptions(target)
systarget = target.upper() systarget = target.upper()
with self.__loggingLock: with self.__loggingLock:
# don't set new handlers if already the same # don't set new handlers if already the same
@ -589,7 +590,12 @@ class Server:
# set a format which is simpler for console use # set a format which is simpler for console use
fmt = "%(name)-24s[%(process)d]: %(levelname)-7s %(message)s" fmt = "%(name)-24s[%(process)d]: %(levelname)-7s %(message)s"
if systarget == "SYSLOG": if systarget == "SYSLOG":
facility = logging.handlers.SysLogHandler.LOG_DAEMON facility = logOptions.get('facility', 'DAEMON').upper()
try:
facility = getattr(logging.handlers.SysLogHandler, 'LOG_' + facility)
except AttributeError: # pragma: no cover
logSys.error("Unable to set facility %r, using 'DAEMON'", logOptions.get('facility'))
facility = logging.handlers.SysLogHandler.LOG_DAEMON
if self.__syslogSocket == "auto": if self.__syslogSocket == "auto":
import platform import platform
self.__syslogSocket = self.__autoSyslogSocketPaths.get( self.__syslogSocket = self.__autoSyslogSocketPaths.get(
@ -640,9 +646,16 @@ class Server:
if self.__verbose is None: if self.__verbose is None:
self.__verbose = logging.DEBUG - logger.getEffectiveLevel() + 1 self.__verbose = logging.DEBUG - logger.getEffectiveLevel() + 1
# If handler don't already add date to the message: # If handler don't already add date to the message:
addtime = systarget not in ("SYSLOG", "SYSOUT") addtime = logOptions.get('datetime')
if addtime is not None:
addtime = addtime in ('1', 'on', 'true', 'yes')
else:
addtime = systarget not in ("SYSLOG", "SYSOUT")
# If log-format is redefined in options:
if logOptions.get('format', '') != '':
fmt = logOptions.get('format')
# verbose log-format: # verbose log-format:
if self.__verbose is not None and self.__verbose > 2: # pragma: no cover elif self.__verbose is not None and self.__verbose > 2: # pragma: no cover
fmt = getVerbosityFormat(self.__verbose-1, fmt = getVerbosityFormat(self.__verbose-1,
addtime=addtime) addtime=addtime)
elif addtime: elif addtime:

View File

@ -30,7 +30,7 @@ import tempfile
import unittest import unittest
from ..client.configreader import ConfigReader, ConfigReaderUnshared, NoSectionError from ..client.configreader import ConfigReader, ConfigReaderUnshared, NoSectionError
from ..client import configparserinc from ..client import configparserinc
from ..client.jailreader import JailReader from ..client.jailreader import JailReader, extractOptions
from ..client.filterreader import FilterReader from ..client.filterreader import FilterReader
from ..client.jailsreader import JailsReader from ..client.jailsreader import JailsReader
from ..client.actionreader import ActionReader, CommandAction from ..client.actionreader import ActionReader, CommandAction
@ -260,25 +260,25 @@ class JailReaderTest(LogCaptureTestCase):
# Simple example # Simple example
option = "mail-whois[name=SSH]" option = "mail-whois[name=SSH]"
expected = ('mail-whois', {'name': 'SSH'}) expected = ('mail-whois', {'name': 'SSH'})
result = JailReader.extractOptions(option) result = extractOptions(option)
self.assertEqual(expected, result) self.assertEqual(expected, result)
self.assertEqual(('mail.who_is', {}), JailReader.extractOptions("mail.who_is")) self.assertEqual(('mail.who_is', {}), extractOptions("mail.who_is"))
self.assertEqual(('mail.who_is', {'a':'cat', 'b':'dog'}), JailReader.extractOptions("mail.who_is[a=cat,b=dog]")) self.assertEqual(('mail.who_is', {'a':'cat', 'b':'dog'}), extractOptions("mail.who_is[a=cat,b=dog]"))
self.assertEqual(('mail--ho_is', {}), JailReader.extractOptions("mail--ho_is")) self.assertEqual(('mail--ho_is', {}), extractOptions("mail--ho_is"))
self.assertEqual(('mail--ho_is', {}), JailReader.extractOptions("mail--ho_is['s']")) self.assertEqual(('mail--ho_is', {}), extractOptions("mail--ho_is['s']"))
#self.printLog() #self.printLog()
#self.assertLogged("Invalid argument ['s'] in ''s''") #self.assertLogged("Invalid argument ['s'] in ''s''")
self.assertEqual(('mail', {'a': ','}), JailReader.extractOptions("mail[a=',']")) self.assertEqual(('mail', {'a': ','}), extractOptions("mail[a=',']"))
#self.assertRaises(ValueError, JailReader.extractOptions ,'mail-how[') #self.assertRaises(ValueError, extractOptions ,'mail-how[')
# Empty option # Empty option
option = "abc[]" option = "abc[]"
expected = ('abc', {}) expected = ('abc', {})
result = JailReader.extractOptions(option) result = extractOptions(option)
self.assertEqual(expected, result) self.assertEqual(expected, result)
# More complex examples # More complex examples
@ -296,11 +296,11 @@ class JailReaderTest(LogCaptureTestCase):
'opt10': "", 'opt10': "",
'opt11': "", 'opt11': "",
}) })
result = JailReader.extractOptions(option) result = extractOptions(option)
self.assertEqual(expected, result) self.assertEqual(expected, result)
# And multiple groups (`][` instead of `,`) # And multiple groups (`][` instead of `,`)
result = JailReader.extractOptions(option.replace(',', '][')) result = extractOptions(option.replace(',', ']['))
expected2 = (expected[0], expected2 = (expected[0],
dict((k, v.replace(',', '][')) for k, v in expected[1].iteritems()) dict((k, v.replace(',', '][')) for k, v in expected[1].iteritems())
) )
@ -439,7 +439,7 @@ class FilterReaderTest(unittest.TestCase):
def testFilterReaderSubstitionKnown(self): def testFilterReaderSubstitionKnown(self):
output = [['set', 'jailname', 'addfailregex', 'to=test,sweet@example.com,test2,sweet@example.com fromip=<IP>']] output = [['set', 'jailname', 'addfailregex', 'to=test,sweet@example.com,test2,sweet@example.com fromip=<IP>']]
filterName, filterOpt = JailReader.extractOptions( filterName, filterOpt = extractOptions(
'substition[honeypot="<sweet>,<known/honeypot>", sweet="test,<known/honeypot>,test2"]') 'substition[honeypot="<sweet>,<known/honeypot>", sweet="test,<known/honeypot>,test2"]')
filterReader = FilterReader('substition', "jailname", filterOpt, filterReader = FilterReader('substition', "jailname", filterOpt,
share_config=TEST_FILES_DIR_SHARE_CFG, basedir=TEST_FILES_DIR) share_config=TEST_FILES_DIR_SHARE_CFG, basedir=TEST_FILES_DIR)
@ -650,7 +650,7 @@ class JailsReaderTest(LogCaptureTestCase):
if jail == 'INCLUDES': if jail == 'INCLUDES':
continue continue
filterName = jails.get(jail, 'filter') filterName = jails.get(jail, 'filter')
filterName, filterOpt = JailReader.extractOptions(filterName) filterName, filterOpt = extractOptions(filterName)
allFilters.add(filterName) allFilters.add(filterName)
self.assertTrue(len(filterName)) self.assertTrue(len(filterName))
# moreover we must have a file for it # moreover we must have a file for it
@ -669,7 +669,7 @@ class JailsReaderTest(LogCaptureTestCase):
# somewhat duplicating here what is done in JailsReader if # somewhat duplicating here what is done in JailsReader if
# the jail is enabled # the jail is enabled
for act in actions.split('\n'): for act in actions.split('\n'):
actName, actOpt = JailReader.extractOptions(act) actName, actOpt = extractOptions(act)
self.assertTrue(len(actName)) self.assertTrue(len(actName))
self.assertTrue(isinstance(actOpt, dict)) self.assertTrue(isinstance(actOpt, dict))
if actName == 'iptables-multiport': if actName == 'iptables-multiport':
@ -696,7 +696,7 @@ class JailsReaderTest(LogCaptureTestCase):
if not (a.endswith('common.conf') or a.endswith('-aggressive.conf'))) if not (a.endswith('common.conf') or a.endswith('-aggressive.conf')))
# get filters of all jails (filter names without options inside filter[...]) # get filters of all jails (filter names without options inside filter[...])
filters_jail = set( filters_jail = set(
JailReader.extractOptions(jail.options['filter'])[0] for jail in jails.jails extractOptions(jail.options['filter'])[0] for jail in jails.jails
) )
self.maxDiff = None self.maxDiff = None
self.assertTrue(filters.issubset(filters_jail), self.assertTrue(filters.issubset(filters_jail),

View File

@ -201,7 +201,7 @@ def _start_params(tmp, use_stock=False, use_stock_cfg=None,
_write_file(pjoin(cfg, "fail2ban.conf"), "w", _write_file(pjoin(cfg, "fail2ban.conf"), "w",
"[Definition]", "[Definition]",
"loglevel = INFO", "loglevel = INFO",
"logtarget = " + logtarget, "logtarget = " + logtarget.replace('%', '%%'),
"syslogsocket = auto", "syslogsocket = auto",
"socket = " + pjoin(tmp, "f2b.sock"), "socket = " + pjoin(tmp, "f2b.sock"),
"pidfile = " + pjoin(tmp, "f2b.pid"), "pidfile = " + pjoin(tmp, "f2b.pid"),
@ -767,7 +767,8 @@ class Fail2banServerTest(Fail2banClientServerBase):
def testKillAfterStart(self, tmp): def testKillAfterStart(self, tmp):
try: try:
# to prevent fork of test-cases process, start server in background via command: # to prevent fork of test-cases process, start server in background via command:
startparams = _start_params(tmp, logtarget=pjoin(tmp, "f2b.log")) startparams = _start_params(tmp, logtarget=pjoin(tmp,
'f2b.log[format="SRV: %(relativeCreated)3d | %(message)s", datetime=off]'))
# start (in new process, using the same python version): # start (in new process, using the same python version):
cmd = (sys.executable, pjoin(BIN, SERVER)) cmd = (sys.executable, pjoin(BIN, SERVER))
logSys.debug('Start %s ...', cmd) logSys.debug('Start %s ...', cmd)

View File

@ -83,3 +83,17 @@
2017-11-28 14:14:31 SMTP protocol error in "aUtH lOgIn" H=(roxzgj) [192.0.2.5] AUTH command used when not advertised 2017-11-28 14:14:31 SMTP protocol error in "aUtH lOgIn" H=(roxzgj) [192.0.2.5] AUTH command used when not advertised
# failJSON: { "time": "2017-11-28T14:14:32", "match": true , "host": "192.0.2.6", "desc": "quoted injecting on AUTH command" } # failJSON: { "time": "2017-11-28T14:14:32", "match": true , "host": "192.0.2.6", "desc": "quoted injecting on AUTH command" }
2017-11-28 14:14:32 SMTP protocol error in "aUtH lOgIn" H=(test) [8.8.8.8]" H=(roxzgj) [192.0.2.6] AUTH command used when not advertised 2017-11-28 14:14:32 SMTP protocol error in "aUtH lOgIn" H=(test) [8.8.8.8]" H=(roxzgj) [192.0.2.6] AUTH command used when not advertised
## no matches with `mode = normal`:
# failJSON: { "match": false , "desc": "aggressive mode only" }
2017-12-03 08:32:00 no host name found for IP address 192.0.2.8
# failJSON: { "match": false , "desc": "aggressive mode only" }
2017-12-03 08:51:35 no IP address found for host test.example.com (during SMTP connection from [192.0.2.9])
# filterOptions: [{"mode": "aggressive"}]
# failJSON: { "time": "2017-12-03T08:32:00", "match": true , "host": "192.0.2.8", "desc": "no host found for IP" }
2017-12-03 08:32:00 no host name found for IP address 192.0.2.8
# failJSON: { "time": "2017-12-03T08:51:35", "match": true , "host": "192.0.2.9", "desc": "no IP found for host" }
2017-12-03 08:51:35 no IP address found for host test.example.com (during SMTP connection from [192.0.2.9])

View File

@ -42,7 +42,7 @@ from ..server.ticket import BanTicket
from ..server.utils import Utils from ..server.utils import Utils
from .dummyjail import DummyJail from .dummyjail import DummyJail
from .utils import LogCaptureTestCase from .utils import LogCaptureTestCase
from ..helpers import getLogger, PREFER_ENC from ..helpers import getLogger, extractOptions, PREFER_ENC
from .. import version from .. import version
try: try:
@ -831,8 +831,8 @@ class TransmitterLogging(TransmitterBase):
for logTarget in logTargets: for logTarget in logTargets:
os.remove(logTarget) os.remove(logTarget)
self.setGetTest("logtarget", "STDOUT") self.setGetTest("logtarget", 'STDOUT[format="%(message)s"]', 'STDOUT')
self.setGetTest("logtarget", "STDERR") self.setGetTest("logtarget", 'STDERR[datetime=off]', 'STDERR')
def testLogTargetSYSLOG(self): def testLogTargetSYSLOG(self):
if not os.path.exists("/dev/log"): if not os.path.exists("/dev/log"):
@ -1043,7 +1043,7 @@ class LoggingTests(LogCaptureTestCase):
os.remove(f) os.remove(f)
from clientreadertestcase import ActionReader, JailReader, JailsReader, CONFIG_DIR, STOCK from clientreadertestcase import ActionReader, JailsReader, CONFIG_DIR, STOCK
class ServerConfigReaderTests(LogCaptureTestCase): class ServerConfigReaderTests(LogCaptureTestCase):
@ -1166,7 +1166,7 @@ class ServerConfigReaderTests(LogCaptureTestCase):
def getDefaultJailStream(self, jail, act): def getDefaultJailStream(self, jail, act):
act = act.replace('%(__name__)s', jail) act = act.replace('%(__name__)s', jail)
actName, actOpt = JailReader.extractOptions(act) actName, actOpt = extractOptions(act)
stream = [ stream = [
['add', jail, 'polling'], ['add', jail, 'polling'],
# ['set', jail, 'addfailregex', 'DUMMY-REGEX <HOST>'], # ['set', jail, 'addfailregex', 'DUMMY-REGEX <HOST>'],
@ -1674,7 +1674,7 @@ class ServerConfigReaderTests(LogCaptureTestCase):
"`firewall-cmd --direct --add-rule ipv4 filter INPUT_direct 0 -p tcp -m multiport --dports http -m set --match-set f2b-j-w-fwcmd-ipset src -j REJECT --reject-with icmp-port-unreachable`", "`firewall-cmd --direct --add-rule ipv4 filter INPUT_direct 0 -p tcp -m multiport --dports http -m set --match-set f2b-j-w-fwcmd-ipset src -j REJECT --reject-with icmp-port-unreachable`",
), ),
'ip6-start': ( 'ip6-start': (
"`ipset create f2b-j-w-fwcmd-ipset6 hash:ip`", "`ipset create f2b-j-w-fwcmd-ipset6 hash:ip family inet6`",
"`firewall-cmd --direct --add-rule ipv6 filter INPUT_direct 0 -p tcp -m multiport --dports http -m set --match-set f2b-j-w-fwcmd-ipset6 src -j REJECT --reject-with icmp6-port-unreachable`", "`firewall-cmd --direct --add-rule ipv6 filter INPUT_direct 0 -p tcp -m multiport --dports http -m set --match-set f2b-j-w-fwcmd-ipset6 src -j REJECT --reject-with icmp6-port-unreachable`",
), ),
'stop': ( 'stop': (