Merge branch '0.10' into 0.11, version bump

# Conflicts resolved:
#	fail2ban/server/database.py
pull/2116/head
sebres 7 years ago
commit 0707695146

@ -27,7 +27,7 @@ Incompatibility list (compared to v.0.9):
* v.0.10 uses more precise date template handling, that can be theoretically incompatible to some * v.0.10 uses more precise date template handling, that can be theoretically incompatible to some
user configurations resp. `datepattern`. user configurations resp. `datepattern`.
* Since v0.10 fail2ban supports the matching of the IPv6 addresses, but not all ban actions are * Since v0.10 fail2ban supports the matching of IPv6 addresses, but not all ban actions are
IPv6-capable now. IPv6-capable now.
@ -57,10 +57,22 @@ ver. 0.11.0-dev-0 (20??/??/??) - development nightly edition
(or persistent); not affected if ban-time of the jail is unchanged between stop/start. (or persistent); not affected if ban-time of the jail is unchanged between stop/start.
ver. 0.10.4-dev-1 (20??/??/??) - development edition
-----------
### Fixes
### New Features
ver. 0.10.3-dev-1 (20??/??/??) - development edition ### Enhancements
ver. 0.10.3 (2018/04/04) - the-time-is-always-right-to-do-what-is-right
----------- -----------
### ver. 0.10.3.1:
* fixed JSON serialization for the set-object within dump into database (gh-2103).
### Fixes ### Fixes
* `filter.d/asterisk.conf`: fixed failregex prefix by log over remote syslog server (gh-2060); * `filter.d/asterisk.conf`: fixed failregex prefix by log over remote syslog server (gh-2060);
* `filter.d/exim.conf`: failregex extended - SMTP call dropped: too many syntax or protocol errors (gh-2048); * `filter.d/exim.conf`: failregex extended - SMTP call dropped: too many syntax or protocol errors (gh-2048);
@ -82,6 +94,8 @@ ver. 0.10.3-dev-1 (20??/??/??) - development edition
* (Free)BSD ipfw actionban fixed to allow same rule added several times (gh-2054); * (Free)BSD ipfw actionban fixed to allow same rule added several times (gh-2054);
### New Features ### New Features
* several stability and performance optimizations, more effective filter parsing, etc;
* stable runnable within python versions 3.6 (as well as within 3.7-dev);
### Enhancements ### Enhancements
* `filter.d/apache-auth.conf`: detection of Apache SNI errors resp. misredirect attempts (gh-2017, gh-2097); * `filter.d/apache-auth.conf`: detection of Apache SNI errors resp. misredirect attempts (gh-2017, gh-2097);

@ -394,6 +394,8 @@ kill-server
man/fail2ban.1 man/fail2ban.1
man/fail2ban-client.1 man/fail2ban-client.1
man/fail2ban-client.h2m man/fail2ban-client.h2m
man/fail2ban-python.1
man/fail2ban-python.h2m
man/fail2ban-regex.1 man/fail2ban-regex.1
man/fail2ban-regex.h2m man/fail2ban-regex.h2m
man/fail2ban-server.1 man/fail2ban-server.1

@ -114,6 +114,15 @@ class BadIPsAction(ActionBase): # pragma: no cover - may be unavailable
except Exception as e: # pragma: no cover except Exception as e: # pragma: no cover
return False, e 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): def getCategories(self, incParents=False):
"""Get badips.com categories. """Get badips.com categories.
@ -133,11 +142,8 @@ class BadIPsAction(ActionBase): # pragma: no cover - may be unavailable
try: try:
response = urlopen( response = urlopen(
self._Request("/".join([self._badips, "get", "categories"])), timeout=self.timeout) self._Request("/".join([self._badips, "get", "categories"])), timeout=self.timeout)
except HTTPError as response: except HTTPError as response: # pragma: no cover
messages = json.loads(response.read().decode('utf-8')) self.logError(response, "Failed to fetch categories")
self._logSys.error(
"Failed to fetch categories. badips.com response: '%s'",
messages['err'])
raise raise
else: else:
response_json = json.loads(response.read().decode('utf-8')) response_json = json.loads(response.read().decode('utf-8'))
@ -188,11 +194,8 @@ class BadIPsAction(ActionBase): # pragma: no cover - may be unavailable
url = "&".join([url, urlencode({'key': key})]) url = "&".join([url, urlencode({'key': key})])
self._logSys.debug('badips.com: get list, url: %r', url) self._logSys.debug('badips.com: get list, url: %r', url)
response = urlopen(self._Request(url), timeout=self.timeout) response = urlopen(self._Request(url), timeout=self.timeout)
except HTTPError as response: except HTTPError as response: # pragma: no cover
messages = json.loads(response.read().decode('utf-8')) self.logError(response, "Failed to fetch bad IP list")
self._logSys.error(
"Failed to fetch bad IP list. badips.com response: '%s'",
messages['err'])
raise raise
else: else:
return set(response.read().decode('utf-8').split()) return set(response.read().decode('utf-8').split())
@ -286,7 +289,7 @@ class BadIPsAction(ActionBase): # pragma: no cover - may be unavailable
exc_info=self._logSys.getEffectiveLevel()<=logging.DEBUG) exc_info=self._logSys.getEffectiveLevel()<=logging.DEBUG)
else: else:
self._bannedips.add(ip) self._bannedips.add(ip)
self._logSys.info( self._logSys.debug(
"Banned IP %s for jail '%s' with action '%s'", "Banned IP %s for jail '%s' with action '%s'",
ip, self._jail.name, self.banaction) ip, self._jail.name, self.banaction)
@ -306,7 +309,7 @@ class BadIPsAction(ActionBase): # pragma: no cover - may be unavailable
ip, self._jail.name, self.banaction, e, ip, self._jail.name, self.banaction, e,
exc_info=self._logSys.getEffectiveLevel()<=logging.DEBUG) exc_info=self._logSys.getEffectiveLevel()<=logging.DEBUG)
else: else:
self._logSys.info( self._logSys.debug(
"Unbanned IP %s for jail '%s' with action '%s'", "Unbanned IP %s for jail '%s' with action '%s'",
ip, self._jail.name, self.banaction) ip, self._jail.name, self.banaction)
finally: finally:
@ -338,7 +341,7 @@ class BadIPsAction(ActionBase): # pragma: no cover - may be unavailable
# Add new IPs which are now listed # Add new IPs which are now listed
self._banIPs(ips - self._bannedips) self._banIPs(ips - self._bannedips)
self._logSys.info( self._logSys.debug(
"Updated IPs for jail '%s'. Update again in %i seconds", "Updated IPs for jail '%s'. Update again in %i seconds",
self._jail.name, self.updateperiod) self._jail.name, self.updateperiod)
finally: finally:
@ -374,15 +377,12 @@ class BadIPsAction(ActionBase): # pragma: no cover - may be unavailable
url = "?".join([url, urlencode({'key': self.key})]) url = "?".join([url, urlencode({'key': self.key})])
self._logSys.debug('badips.com: ban, url: %r', url) self._logSys.debug('badips.com: ban, url: %r', url)
response = urlopen(self._Request(url), timeout=self.timeout) response = urlopen(self._Request(url), timeout=self.timeout)
except HTTPError as response: except HTTPError as response: # pragma: no cover
messages = json.loads(response.read().decode('utf-8')) self.logError(response, "Failed to ban")
self._logSys.error(
"Response from badips.com report: '%s'",
messages['err'])
raise raise
else: else:
messages = json.loads(response.read().decode('utf-8')) messages = json.loads(response.read().decode('utf-8'))
self._logSys.info( self._logSys.debug(
"Response from badips.com report: '%s'", "Response from badips.com report: '%s'",
messages['suc']) messages['suc'])

@ -39,9 +39,14 @@ from ..helpers import getLogger, PREFER_ENC
logSys = getLogger(__name__) logSys = getLogger(__name__)
if sys.version_info >= (3,): if sys.version_info >= (3,):
def _json_default(x):
if isinstance(x, set):
x = list(x)
return x
def _json_dumps_safe(x): def _json_dumps_safe(x):
try: try:
x = json.dumps(x, ensure_ascii=False).encode( x = json.dumps(x, ensure_ascii=False, default=_json_default).encode(
PREFER_ENC, 'replace') PREFER_ENC, 'replace')
except Exception as e: # pragma: no cover except Exception as e: # pragma: no cover
logSys.error('json dumps failed: %s', e) logSys.error('json dumps failed: %s', e)
@ -60,7 +65,7 @@ else:
def _normalize(x): def _normalize(x):
if isinstance(x, dict): if isinstance(x, dict):
return dict((_normalize(k), _normalize(v)) for k, v in x.iteritems()) return dict((_normalize(k), _normalize(v)) for k, v in x.iteritems())
elif isinstance(x, list): elif isinstance(x, (list, set)):
return [_normalize(element) for element in x] return [_normalize(element) for element in x]
elif isinstance(x, unicode): elif isinstance(x, unicode):
return x.encode(PREFER_ENC) return x.encode(PREFER_ENC)
@ -561,14 +566,18 @@ class Fail2BanDb(object):
except KeyError: except KeyError:
pass pass
#TODO: Implement data parts once arbitrary match keys completed #TODO: Implement data parts once arbitrary match keys completed
data = ticket.getData()
matches = data.get('matches')
if matches and len(matches) > self.maxEntries:
data['matches'] = matches[-self.maxEntries:]
cur.execute( cur.execute(
"INSERT INTO bans(jail, ip, timeofban, bantime, bancount, data) VALUES(?, ?, ?, ?, ?, ?)", "INSERT INTO bans(jail, ip, timeofban, bantime, bancount, data) VALUES(?, ?, ?, ?, ?, ?)",
(jail.name, ip, int(round(ticket.getTime())), ticket.getBanTime(jail.actions.getBanTime()), ticket.getBanCount(), (jail.name, ip, int(round(ticket.getTime())), ticket.getBanTime(jail.actions.getBanTime()), ticket.getBanCount(),
ticket.getData())) data))
cur.execute( cur.execute(
"INSERT OR REPLACE INTO bips(ip, jail, timeofban, bantime, bancount, data) VALUES(?, ?, ?, ?, ?, ?)", "INSERT OR REPLACE INTO bips(ip, jail, timeofban, bantime, bancount, data) VALUES(?, ?, ?, ?, ?, ?)",
(ip, jail.name, int(round(ticket.getTime())), ticket.getBanTime(jail.actions.getBanTime()), ticket.getBanCount(), (ip, jail.name, int(round(ticket.getTime())), ticket.getBanTime(jail.actions.getBanTime()), ticket.getBanCount(),
ticket.getData())) data))
@commitandrollback @commitandrollback
def delBan(self, cur, jail, *args): def delBan(self, cur, jail, *args):
@ -701,11 +710,11 @@ class Fail2BanDb(object):
else: else:
matches = m[-maxadd:] + matches matches = m[-maxadd:] + matches
failures += data.get('failures', 1) failures += data.get('failures', 1)
tickdata.update(data.get('data', {})) data['failures'] = failures
data['matches'] = matches
tickdata.update(data)
prev_timeofban = timeofban prev_timeofban = timeofban
ticket = FailTicket(banip, prev_timeofban, matches) ticket = FailTicket(banip, prev_timeofban, data=tickdata)
ticket.setAttempt(failures)
ticket.setData(**tickdata)
tickets.append(ticket) tickets.append(ticket)
if cacheKey: if cacheKey:

@ -20,12 +20,34 @@
import os import os
import unittest import unittest
import sys import sys
from functools import wraps
from socket import timeout
from ssl import SSLError
from ..actiontestcase import CallingMap from ..actiontestcase import CallingMap
from ..dummyjail import DummyJail from ..dummyjail import DummyJail
from ..servertestcase import IPAddr from ..servertestcase import IPAddr
from ..utils import LogCaptureTestCase, CONFIG_DIR from ..utils import LogCaptureTestCase, CONFIG_DIR
if sys.version_info >= (3, ):
from urllib.error import HTTPError, URLError
else:
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 if sys.version_info >= (2,7): # pragma: no cover - may be unavailable
class BadIPsActionTest(LogCaptureTestCase): class BadIPsActionTest(LogCaptureTestCase):
@ -51,7 +73,7 @@ if sys.version_info >= (2,7): # pragma: no cover - may be unavailable
BadIPsActionTest.pythonModule = self.jail.actions._load_python_module(pythonModuleName) BadIPsActionTest.pythonModule = self.jail.actions._load_python_module(pythonModuleName)
BadIPsActionTest.modAction = BadIPsActionTest.pythonModule.Action BadIPsActionTest.modAction = BadIPsActionTest.pythonModule.Action
self.jail.actions._load_python_module(pythonModuleName) self.jail.actions._load_python_module(pythonModuleName)
BadIPsActionTest.available = BadIPsActionTest.modAction.isAvailable(timeout=2 if unittest.F2B.fast else 60) BadIPsActionTest.available = BadIPsActionTest.modAction.isAvailable(timeout=2 if unittest.F2B.fast else 30)
if not BadIPsActionTest.available[0]: if not BadIPsActionTest.available[0]:
raise unittest.SkipTest('Skip test because service is not available: %s' % BadIPsActionTest.available[1]) raise unittest.SkipTest('Skip test because service is not available: %s' % BadIPsActionTest.available[1])
@ -62,7 +84,7 @@ if sys.version_info >= (2,7): # pragma: no cover - may be unavailable
'score': 5, 'score': 5,
'key': "fail2ban-test-suite", 'key': "fail2ban-test-suite",
#'bankey': "fail2ban-test-suite", #'bankey': "fail2ban-test-suite",
'timeout': (3 if unittest.F2B.fast else 30), 'timeout': (3 if unittest.F2B.fast else 60),
}) })
self.action = self.jail.actions["badips"] self.action = self.jail.actions["badips"]
@ -73,6 +95,7 @@ if sys.version_info >= (2,7): # pragma: no cover - may be unavailable
self.action._timer.cancel() self.action._timer.cancel()
super(BadIPsActionTest, self).tearDown() super(BadIPsActionTest, self).tearDown()
@skip_if_not_available
def testCategory(self): def testCategory(self):
categories = self.action.getCategories() categories = self.action.getCategories()
self.assertIn("ssh", categories) self.assertIn("ssh", categories)
@ -88,17 +111,20 @@ if sys.version_info >= (2,7): # pragma: no cover - may be unavailable
# but valid for blacklisting. # but valid for blacklisting.
self.action.bancategory = "mail" self.action.bancategory = "mail"
@skip_if_not_available
def testScore(self): def testScore(self):
self.assertRaises(ValueError, setattr, self.action, "score", -5) self.assertRaises(ValueError, setattr, self.action, "score", -5)
self.action.score = 3 self.action.score = 3
self.action.score = "3" self.action.score = "3"
@skip_if_not_available
def testBanaction(self): def testBanaction(self):
self.assertRaises( self.assertRaises(
ValueError, setattr, self.action, "banaction", ValueError, setattr, self.action, "banaction",
"invalid-action") "invalid-action")
self.action.banaction = "test" self.action.banaction = "test"
@skip_if_not_available
def testUpdateperiod(self): def testUpdateperiod(self):
self.assertRaises( self.assertRaises(
ValueError, setattr, self.action, "updateperiod", -50) ValueError, setattr, self.action, "updateperiod", -50)
@ -107,6 +133,7 @@ if sys.version_info >= (2,7): # pragma: no cover - may be unavailable
self.action.updateperiod = 900 self.action.updateperiod = 900
self.action.updateperiod = "900" self.action.updateperiod = "900"
@skip_if_not_available
def testStartStop(self): def testStartStop(self):
self.action.start() self.action.start()
self.assertTrue(len(self.action._bannedips) > 10, self.assertTrue(len(self.action._bannedips) > 10,
@ -114,6 +141,7 @@ if sys.version_info >= (2,7): # pragma: no cover - may be unavailable
self.action.stop() self.action.stop()
self.assertTrue(len(self.action._bannedips) == 0) self.assertTrue(len(self.action._bannedips) == 0)
@skip_if_not_available
def testBanIP(self): def testBanIP(self):
aInfo = CallingMap({ aInfo = CallingMap({
'ip': IPAddr('192.0.2.1') 'ip': IPAddr('192.0.2.1')

@ -339,12 +339,18 @@ class DatabaseTest(LogCaptureTestCase):
def testGetBansMerged_MaxEntries(self): def testGetBansMerged_MaxEntries(self):
self.testAddJail() self.testAddJail()
maxEntries = 2 maxEntries = 2
failures = ["abc\n", "123\n", "ABC\n", "1234\n"] failures = [
{"matches": ["abc\n"], "user": set(['test'])},
{"matches": ["123\n"], "user": set(['test'])},
{"matches": ["ABC\n"], "user": set(['test', 'root'])},
{"matches": ["1234\n"], "user": set(['test', 'root'])},
]
matches2find = [f["matches"][0] for f in failures]
# add failures sequential: # add failures sequential:
i = 80 i = 80
for f in failures: for f in failures:
i -= 10 i -= 10
ticket = FailTicket("127.0.0.1", MyTime.time() - i, [f]) ticket = FailTicket("127.0.0.1", MyTime.time() - i, data=f)
ticket.setAttempt(1) ticket.setAttempt(1)
self.db.addBan(self.jail, ticket) self.db.addBan(self.jail, ticket)
# should retrieve 2 matches only, but count of all attempts: # should retrieve 2 matches only, but count of all attempts:
@ -353,9 +359,10 @@ class DatabaseTest(LogCaptureTestCase):
self.assertEqual(ticket.getIP(), "127.0.0.1") self.assertEqual(ticket.getIP(), "127.0.0.1")
self.assertEqual(ticket.getAttempt(), len(failures)) self.assertEqual(ticket.getAttempt(), len(failures))
self.assertEqual(len(ticket.getMatches()), maxEntries) self.assertEqual(len(ticket.getMatches()), maxEntries)
self.assertEqual(ticket.getMatches(), failures[len(failures) - maxEntries:]) self.assertEqual(ticket.getMatches(), matches2find[-maxEntries:])
# add more failures at once: # add more failures at once:
ticket = FailTicket("127.0.0.1", MyTime.time() - 10, failures) ticket = FailTicket("127.0.0.1", MyTime.time() - 10, matches2find,
data={"user": set(['test', 'root'])})
ticket.setAttempt(len(failures)) ticket.setAttempt(len(failures))
self.db.addBan(self.jail, ticket) self.db.addBan(self.jail, ticket)
# should retrieve 2 matches only, but count of all attempts: # should retrieve 2 matches only, but count of all attempts:
@ -363,7 +370,13 @@ class DatabaseTest(LogCaptureTestCase):
ticket = self.db.getBansMerged("127.0.0.1") ticket = self.db.getBansMerged("127.0.0.1")
self.assertEqual(ticket.getAttempt(), 2 * len(failures)) self.assertEqual(ticket.getAttempt(), 2 * len(failures))
self.assertEqual(len(ticket.getMatches()), maxEntries) self.assertEqual(len(ticket.getMatches()), maxEntries)
self.assertEqual(ticket.getMatches(), failures[len(failures) - maxEntries:]) self.assertEqual(ticket.getMatches(), matches2find[-maxEntries:])
# also using getCurrentBans:
ticket = self.db.getCurrentBans(self.jail, "127.0.0.1", fromtime=MyTime.time()-100)
self.assertTrue(ticket is not None)
self.assertEqual(ticket.getAttempt(), len(failures))
self.assertEqual(len(ticket.getMatches()), maxEntries)
self.assertEqual(ticket.getMatches(), matches2find[-maxEntries:])
def testGetBansMerged(self): def testGetBansMerged(self):
self.testAddJail() self.testAddJail()

@ -24,4 +24,4 @@ __author__ = "Cyril Jaquier, Yaroslav Halchenko, Steven Hiscocks, Daniel Black"
__copyright__ = "Copyright (c) 2004 Cyril Jaquier, 2005-2016 Yaroslav Halchenko, 2013-2014 Steven Hiscocks, Daniel Black" __copyright__ = "Copyright (c) 2004 Cyril Jaquier, 2005-2016 Yaroslav Halchenko, 2013-2014 Steven Hiscocks, Daniel Black"
__license__ = "GPL-v2+" __license__ = "GPL-v2+"
version = "0.11.0.dev1" version = "0.11.0.dev2"

@ -1,12 +1,12 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.47.4. .\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.47.4.
.TH FAIL2BAN-CLIENT "1" "January 2018" "fail2ban-client v0.11.0.dev1" "User Commands" .TH FAIL2BAN-CLIENT "1" "April 2018" "fail2ban-client v0.11.0.dev2" "User Commands"
.SH NAME .SH NAME
fail2ban-client \- configure and control the server fail2ban-client \- configure and control the server
.SH SYNOPSIS .SH SYNOPSIS
.B fail2ban-client .B fail2ban-client
[\fI\,OPTIONS\/\fR] \fI\,<COMMAND>\/\fR [\fI\,OPTIONS\/\fR] \fI\,<COMMAND>\/\fR
.SH DESCRIPTION .SH DESCRIPTION
Fail2Ban v0.11.0.dev1 reads log file that contains password failure report Fail2Ban v0.11.0.dev2 reads log file that contains password failure report
and bans the corresponding IP addresses using firewall rules. and bans the corresponding IP addresses using firewall rules.
.SH OPTIONS .SH OPTIONS
.TP .TP

@ -1,14 +1,10 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.47.5. .\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.47.4.
.TH FAIL2BAN-PYTHON "1" "January 2018" "fail2ban-python f2bversion" "User Commands" .TH FAIL2BAN-PYTHON "1" "April 2018" "fail2ban-python f2bversion" "User Commands"
.SH NAME .SH NAME
fail2ban-python \- a helper for Fail2Ban to assure that the same Python is used fail2ban-python \- a helper for Fail2Ban to assure that the same Python is used
.SH DESCRIPTION .SH DESCRIPTION
usage: ../bin/fail2ban\-python [option] ... [\-c cmd | \fB\-m\fR mod | file | \fB\-]\fR [arg] ... usage: ../bin/fail2ban\-python [option] ... [\-c cmd | \fB\-m\fR mod | file | \fB\-]\fR [arg] ...
Options and arguments (and corresponding environment variables): Options and arguments (and corresponding environment variables):
\fB\-b\fR : issue warnings about comparing bytearray with unicode
.IP
(\fB\-bb\fR: issue errors)
.PP
\fB\-B\fR : don't write .py[co] files on import; also PYTHONDONTWRITEBYTECODE=x \fB\-B\fR : don't write .py[co] files on import; also PYTHONDONTWRITEBYTECODE=x
\fB\-c\fR cmd : program passed in as string (terminates option list) \fB\-c\fR cmd : program passed in as string (terminates option list)
\fB\-d\fR : debug output from parser; also PYTHONDEBUG=x \fB\-d\fR : debug output from parser; also PYTHONDEBUG=x

@ -1,5 +1,5 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.47.4. .\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.47.4.
.TH FAIL2BAN-REGEX "1" "January 2018" "fail2ban-regex 0.11.0.dev1" "User Commands" .TH FAIL2BAN-REGEX "1" "April 2018" "fail2ban-regex 0.11.0.dev2" "User Commands"
.SH NAME .SH NAME
fail2ban-regex \- test Fail2ban "failregex" option fail2ban-regex \- test Fail2ban "failregex" option
.SH SYNOPSIS .SH SYNOPSIS

@ -1,12 +1,12 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.47.4. .\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.47.4.
.TH FAIL2BAN-SERVER "1" "January 2018" "fail2ban-server v0.11.0.dev1" "User Commands" .TH FAIL2BAN-SERVER "1" "April 2018" "fail2ban-server v0.11.0.dev2" "User Commands"
.SH NAME .SH NAME
fail2ban-server \- start the server fail2ban-server \- start the server
.SH SYNOPSIS .SH SYNOPSIS
.B fail2ban-server .B fail2ban-server
[\fI\,OPTIONS\/\fR] [\fI\,OPTIONS\/\fR]
.SH DESCRIPTION .SH DESCRIPTION
Fail2Ban v0.11.0.dev1 reads log file that contains password failure report Fail2Ban v0.11.0.dev2 reads log file that contains password failure report
and bans the corresponding IP addresses using firewall rules. and bans the corresponding IP addresses using firewall rules.
.SH OPTIONS .SH OPTIONS
.TP .TP

@ -1,5 +1,5 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.47.4. .\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.47.4.
.TH FAIL2BAN-TESTCASES "1" "January 2018" "fail2ban-testcases 0.11.0.dev1" "User Commands" .TH FAIL2BAN-TESTCASES "1" "April 2018" "fail2ban-testcases 0.11.0.dev2" "User Commands"
.SH NAME .SH NAME
fail2ban-testcases \- run Fail2Ban unit-tests fail2ban-testcases \- run Fail2Ban unit-tests
.SH SYNOPSIS .SH SYNOPSIS

Loading…
Cancel
Save