From 743a531eb59a4f3704dda68557fb34481538e159 Mon Sep 17 00:00:00 2001 From: Yaroslav Halchenko Date: Sat, 21 May 2016 10:08:54 -0400 Subject: [PATCH 01/26] BF: make :port and I=[ip]:port optional for a "AUTH command used when not advertised" Closes #1430 --- config/filter.d/exim.conf | 2 +- fail2ban/tests/files/logs/exim | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/config/filter.d/exim.conf b/config/filter.d/exim.conf index 4aadf15c..54ad20d8 100644 --- a/config/filter.d/exim.conf +++ b/config/filter.d/exim.conf @@ -18,7 +18,7 @@ failregex = ^%(pid)s %(host_info)ssender verify fail for <\S+>: (?:Unknown user| ^%(pid)s %(host_info)sF=(<>|[^@]+@\S+) rejected RCPT [^@]+@\S+: (relay not permitted|Sender verify failed|Unknown user)\s*$ ^%(pid)s SMTP protocol synchronization error \([^)]*\): rejected (connection from|"\S+") %(host_info)s(next )?input=".*"\s*$ ^%(pid)s SMTP call from \S+ \[\](:\d+)? (I=\[\S+\](:\d+)? )?dropped: too many nonmail commands \(last was "\S+"\)\s*$ - ^%(pid)s SMTP protocol error in "AUTH \S*(| \S*)" H=(|\S* )(|\(\S*\) )\[\]\:\d+ I=\[\S*\]\:\d+ AUTH command used when not advertised\s*$ + ^%(pid)s SMTP protocol error in "AUTH \S*(| \S*)" H=(|\S* )(|\(\S*\) )\[\](?:\:\d+)? (?:I=\[\S*\]\:\d+ )?AUTH command used when not advertised\s*$ ^%(pid)s no MAIL in SMTP connection from (|\S* )(|\(\S*\) )\[\]\:\d+ I=\[\S*\]\:\d+ D=\d+s(| C=\S*)\s*$ ^%(pid)s \S+ SMTP connection from (|\S* )(|\(\S*\) )\[\]\:\d+ I=\[\S*\]\:\d+ closed by DROP in ACL\s*$ diff --git a/fail2ban/tests/files/logs/exim b/fail2ban/tests/files/logs/exim index a3b287d4..4b3a7ff5 100644 --- a/fail2ban/tests/files/logs/exim +++ b/fail2ban/tests/files/logs/exim @@ -48,6 +48,8 @@ 2016-03-18 00:34:06 [7513] SMTP protocol error in "AUTH LOGIN" H=(ylmf-pc) [45.32.34.167]:60723 I=[172.89.0.6]:587 AUTH command used when not advertised # failJSON: { "time": "2016-03-19T18:40:44", "match": true , "host": "92.45.204.170" } 2016-03-19 18:40:44 [26221] SMTP protocol error in "AUTH LOGIN aW5mb0BtYW5iYXQub3Jn" H=([127.0.0.1]) [92.45.204.170]:14243 I=[172.89.0.6]:587 AUTH command used when not advertised +# failJSON: { "time": "2016-05-17T06:25:27", "match": true , "host": "69.10.61.61", "desc": "from gh-1430" } +2016-05-17 06:25:27 SMTP protocol error in "AUTH LOGIN" H=(ylmf-pc) [69.10.61.61] AUTH command used when not advertised # failJSON: { "time": "2016-03-21T06:38:05", "match": true , "host": "49.212.207.15" } 2016-03-21 06:38:05 [5718] no MAIL in SMTP connection from www3005.sakura.ne.jp [49.212.207.15]:28890 I=[172.89.0.6]:25 D=21s C=EHLO,STARTTLS # failJSON: { "time": "2016-03-21T06:57:36", "match": true , "host": "122.165.71.116" } From 8b8cf2a660b5727d2a864b61917d4279f80c1b60 Mon Sep 17 00:00:00 2001 From: Yaroslav Halchenko Date: Sat, 21 May 2016 10:27:16 -0400 Subject: [PATCH 02/26] ENH: exim filters -- make more use of %(host_info)s which in turn made more flexible --- config/filter.d/exim-common.conf | 2 +- config/filter.d/exim.conf | 8 ++++---- fail2ban/tests/files/logs/exim | 2 ++ 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/config/filter.d/exim-common.conf b/config/filter.d/exim-common.conf index 1c0a0a20..8f9553fe 100644 --- a/config/filter.d/exim-common.conf +++ b/config/filter.d/exim-common.conf @@ -9,7 +9,7 @@ after = exim-common.local [Definition] -host_info = H=([\w.-]+ )?(\(\S+\) )?\[\](:\d+)? (I=\[\S+\]:\d+ )?(U=\S+ )?(P=e?smtp )? +host_info = (H=([\w.-]+ )?(\(\S+\) )?)?\[\](:\d+)? (I=\[\S+\](:\d+)? )?(U=\S+ )?(P=e?smtp )? pid = ( \[\d+\])? # DEV Notes: diff --git a/config/filter.d/exim.conf b/config/filter.d/exim.conf index 54ad20d8..517e3de7 100644 --- a/config/filter.d/exim.conf +++ b/config/filter.d/exim.conf @@ -17,10 +17,10 @@ failregex = ^%(pid)s %(host_info)ssender verify fail for <\S+>: (?:Unknown user| ^%(pid)s \w+ authenticator failed for (\S+ )?\(\S+\) \[\](:\d+)?( I=\[\S+\](:\d+)?)?: 535 Incorrect authentication data( \(set_id=.*\)|: \d+ Time\(s\))?\s*$ ^%(pid)s %(host_info)sF=(<>|[^@]+@\S+) rejected RCPT [^@]+@\S+: (relay not permitted|Sender verify failed|Unknown user)\s*$ ^%(pid)s SMTP protocol synchronization error \([^)]*\): rejected (connection from|"\S+") %(host_info)s(next )?input=".*"\s*$ - ^%(pid)s SMTP call from \S+ \[\](:\d+)? (I=\[\S+\](:\d+)? )?dropped: too many nonmail commands \(last was "\S+"\)\s*$ - ^%(pid)s SMTP protocol error in "AUTH \S*(| \S*)" H=(|\S* )(|\(\S*\) )\[\](?:\:\d+)? (?:I=\[\S*\]\:\d+ )?AUTH command used when not advertised\s*$ - ^%(pid)s no MAIL in SMTP connection from (|\S* )(|\(\S*\) )\[\]\:\d+ I=\[\S*\]\:\d+ D=\d+s(| C=\S*)\s*$ - ^%(pid)s \S+ SMTP connection from (|\S* )(|\(\S*\) )\[\]\:\d+ I=\[\S*\]\:\d+ closed by DROP in ACL\s*$ + ^%(pid)s SMTP call from \S+ %(host_info)sdropped: too many nonmail commands \(last was "\S+"\)\s*$ + ^%(pid)s SMTP protocol error in "AUTH \S*(| \S*)" %(host_info)sAUTH command used when not advertised\s*$ + ^%(pid)s no MAIL in SMTP connection from (|\S* )(|\(\S*\) )%(host_info)sD=\d+s(| C=\S*)\s*$ + ^%(pid)s \S+ SMTP connection from (|\S* )(|\(\S*\) )%(host_info)sclosed by DROP in ACL\s*$ ignoreregex = diff --git a/fail2ban/tests/files/logs/exim b/fail2ban/tests/files/logs/exim index 4b3a7ff5..9da5ff22 100644 --- a/fail2ban/tests/files/logs/exim +++ b/fail2ban/tests/files/logs/exim @@ -54,6 +54,8 @@ 2016-03-21 06:38:05 [5718] no MAIL in SMTP connection from www3005.sakura.ne.jp [49.212.207.15]:28890 I=[172.89.0.6]:25 D=21s C=EHLO,STARTTLS # failJSON: { "time": "2016-03-21T06:57:36", "match": true , "host": "122.165.71.116" } 2016-03-21 06:57:36 [5908] no MAIL in SMTP connection from [122.165.71.116]:2056 I=[172.89.0.6]:25 D=10s +# failJSON: { "time": "2016-03-21T06:57:36", "match": true , "host": "122.165.71.116" } +2016-03-21 06:57:36 [5908] no MAIL in SMTP connection from [122.165.71.116]:2056 I=[172.89.0.6]:25 D=10s # failJSON: { "time": "2016-03-21T04:07:49", "match": true , "host": "174.137.147.204" } 2016-03-21 04:07:49 [25874] 1ahr79-0006jK-G9 SMTP connection from (voyeur.webair.com) [174.137.147.204]:44884 I=[172.89.0.6]:25 closed by DROP in ACL # failJSON: { "time": "2016-03-21T04:33:13", "match": true , "host": "206.214.71.53" } From 48a8324662afdeed4016fd4630712331af66d610 Mon Sep 17 00:00:00 2001 From: Yaroslav Halchenko Date: Mon, 30 May 2016 11:02:12 -0400 Subject: [PATCH 03/26] ENH: use non-capturing regex groups in exim-common and exim filters --- config/filter.d/exim-common.conf | 4 ++-- config/filter.d/exim.conf | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/config/filter.d/exim-common.conf b/config/filter.d/exim-common.conf index 8f9553fe..0e1b74fa 100644 --- a/config/filter.d/exim-common.conf +++ b/config/filter.d/exim-common.conf @@ -9,8 +9,8 @@ after = exim-common.local [Definition] -host_info = (H=([\w.-]+ )?(\(\S+\) )?)?\[\](:\d+)? (I=\[\S+\](:\d+)? )?(U=\S+ )?(P=e?smtp )? -pid = ( \[\d+\])? +host_info = (?:H=([\w.-]+ )?(?:\(\S+\) )?)?\[\](?::\d+)? (?:I=\[\S+\](:\d+)? )?(?:U=\S+ )?(?:P=e?smtp )? +pid = (?: \[\d+\])? # DEV Notes: # From exim source code: ./src/receive.c:add_host_info_for_log diff --git a/config/filter.d/exim.conf b/config/filter.d/exim.conf index 517e3de7..1ef74b01 100644 --- a/config/filter.d/exim.conf +++ b/config/filter.d/exim.conf @@ -14,13 +14,13 @@ before = exim-common.conf [Definition] failregex = ^%(pid)s %(host_info)ssender verify fail for <\S+>: (?:Unknown user|Unrouteable address|all relevant MX records point to non-existent hosts)\s*$ - ^%(pid)s \w+ authenticator failed for (\S+ )?\(\S+\) \[\](:\d+)?( I=\[\S+\](:\d+)?)?: 535 Incorrect authentication data( \(set_id=.*\)|: \d+ Time\(s\))?\s*$ - ^%(pid)s %(host_info)sF=(<>|[^@]+@\S+) rejected RCPT [^@]+@\S+: (relay not permitted|Sender verify failed|Unknown user)\s*$ - ^%(pid)s SMTP protocol synchronization error \([^)]*\): rejected (connection from|"\S+") %(host_info)s(next )?input=".*"\s*$ + ^%(pid)s \w+ authenticator failed for (\S+ )?\(\S+\) \[\](?::\d+)?(?: I=\[\S+\](:\d+)?)?: 535 Incorrect authentication data( \(set_id=.*\)|: \d+ Time\(s\))?\s*$ + ^%(pid)s %(host_info)sF=(?:<>|[^@]+@\S+) rejected RCPT [^@]+@\S+: (?:relay not permitted|Sender verify failed|Unknown user)\s*$ + ^%(pid)s SMTP protocol synchronization error \([^)]*\): rejected (?:connection from|"\S+") %(host_info)s(?:next )?input=".*"\s*$ ^%(pid)s SMTP call from \S+ %(host_info)sdropped: too many nonmail commands \(last was "\S+"\)\s*$ - ^%(pid)s SMTP protocol error in "AUTH \S*(| \S*)" %(host_info)sAUTH command used when not advertised\s*$ - ^%(pid)s no MAIL in SMTP connection from (|\S* )(|\(\S*\) )%(host_info)sD=\d+s(| C=\S*)\s*$ - ^%(pid)s \S+ SMTP connection from (|\S* )(|\(\S*\) )%(host_info)sclosed by DROP in ACL\s*$ + ^%(pid)s SMTP protocol error in "AUTH \S*(?:| \S*)" %(host_info)sAUTH command used when not advertised\s*$ + ^%(pid)s no MAIL in SMTP connection from (?:|\S* )(?:|\(\S*\) )%(host_info)sD=\d+s(?:| C=\S*)\s*$ + ^%(pid)s \S+ SMTP connection from (?:|\S* )(?:|\(\S*\) )%(host_info)sclosed by DROP in ACL\s*$ ignoreregex = From 64346614804e91285bfb1fcbb407d0e1cdff618e Mon Sep 17 00:00:00 2001 From: Yaroslav Halchenko Date: Mon, 30 May 2016 12:12:53 -0400 Subject: [PATCH 04/26] RF: for consistency use (?:XXX)? instead of (?:|XXX) --- config/filter.d/exim.conf | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/config/filter.d/exim.conf b/config/filter.d/exim.conf index 1ef74b01..a1d699c0 100644 --- a/config/filter.d/exim.conf +++ b/config/filter.d/exim.conf @@ -18,9 +18,9 @@ failregex = ^%(pid)s %(host_info)ssender verify fail for <\S+>: (?:Unknown user| ^%(pid)s %(host_info)sF=(?:<>|[^@]+@\S+) rejected RCPT [^@]+@\S+: (?:relay not permitted|Sender verify failed|Unknown user)\s*$ ^%(pid)s SMTP protocol synchronization error \([^)]*\): rejected (?:connection from|"\S+") %(host_info)s(?:next )?input=".*"\s*$ ^%(pid)s SMTP call from \S+ %(host_info)sdropped: too many nonmail commands \(last was "\S+"\)\s*$ - ^%(pid)s SMTP protocol error in "AUTH \S*(?:| \S*)" %(host_info)sAUTH command used when not advertised\s*$ - ^%(pid)s no MAIL in SMTP connection from (?:|\S* )(?:|\(\S*\) )%(host_info)sD=\d+s(?:| C=\S*)\s*$ - ^%(pid)s \S+ SMTP connection from (?:|\S* )(?:|\(\S*\) )%(host_info)sclosed by DROP in ACL\s*$ + ^%(pid)s SMTP protocol error in "AUTH \S*(?: \S*)?" %(host_info)sAUTH command used when not advertised\s*$ + ^%(pid)s no MAIL in SMTP connection from (?:\S* )?(?:\(\S*\) )?%(host_info)sD=\d+s(?: C=\S*)?\s*$ + ^%(pid)s \S+ SMTP connection from (?:\S* )?(?:\(\S*\) )?%(host_info)sclosed by DROP in ACL\s*$ ignoreregex = From ced6c8307b66e1959a8fd4262b561229cb9fef60 Mon Sep 17 00:00:00 2001 From: Yaroslav Halchenko Date: Thu, 2 Jun 2016 20:56:28 -0400 Subject: [PATCH 05/26] BF: finalize that sample log line for exim4 was intended in 743a531eb59a4f3704dda68557fb34481538e159 to be an entry without a port after the [host] --- fail2ban/tests/files/logs/exim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fail2ban/tests/files/logs/exim b/fail2ban/tests/files/logs/exim index 9da5ff22..9053bf8d 100644 --- a/fail2ban/tests/files/logs/exim +++ b/fail2ban/tests/files/logs/exim @@ -55,7 +55,7 @@ # failJSON: { "time": "2016-03-21T06:57:36", "match": true , "host": "122.165.71.116" } 2016-03-21 06:57:36 [5908] no MAIL in SMTP connection from [122.165.71.116]:2056 I=[172.89.0.6]:25 D=10s # failJSON: { "time": "2016-03-21T06:57:36", "match": true , "host": "122.165.71.116" } -2016-03-21 06:57:36 [5908] no MAIL in SMTP connection from [122.165.71.116]:2056 I=[172.89.0.6]:25 D=10s +2016-03-21 06:57:36 [5908] no MAIL in SMTP connection from [122.165.71.116] I=[172.89.0.6]:25 D=10s # failJSON: { "time": "2016-03-21T04:07:49", "match": true , "host": "174.137.147.204" } 2016-03-21 04:07:49 [25874] 1ahr79-0006jK-G9 SMTP connection from (voyeur.webair.com) [174.137.147.204]:44884 I=[172.89.0.6]:25 closed by DROP in ACL # failJSON: { "time": "2016-03-21T04:33:13", "match": true , "host": "206.214.71.53" } From f85fb45b29768f687546ba25f805977cf00b6e43 Mon Sep 17 00:00:00 2001 From: Ludovic Gasc Date: Tue, 7 Jun 2016 11:40:35 +0200 Subject: [PATCH 06/26] Asterisk pjsip (#1456) * Improve PJSIP log support for Asterisk 13+ * Update changelog: filter.d/asterisk.conf - fix security log support for PJSIP and Asterisk 13+ * Change pjsip regexp with sebres observation, thanks to @nturcksin --- ChangeLog | 1 + config/filter.d/asterisk.conf | 1 + fail2ban/tests/files/logs/asterisk | 4 ++++ 3 files changed, 6 insertions(+) diff --git a/ChangeLog b/ChangeLog index 76719f16..21b8adfc 100644 --- a/ChangeLog +++ b/ChangeLog @@ -25,6 +25,7 @@ ver. 0.9.5 (2016/XX/XXX) - wanna-be-released added new parameter `__date_ambit` * gentoo-initd fixed --pidfile bug: `--pidfile` is option of start-stop-daemon, not argument of fail2ban (see gh-1434) + * filter.d/asterisk.conf - fix security log support for PJSIP and Asterisk 13+ - New Features: * New Actions: diff --git a/config/filter.d/asterisk.conf b/config/filter.d/asterisk.conf index 01063efa..f6ccdd4f 100644 --- a/config/filter.d/asterisk.conf +++ b/config/filter.d/asterisk.conf @@ -27,6 +27,7 @@ failregex = ^%(__prefix_line)s%(log_prefix)s Registration from '[^']*' failed fo ^%(__prefix_line)s%(log_prefix)s hacking attempt detected ''$ ^%(__prefix_line)s%(log_prefix)s SecurityEvent="(FailedACL|InvalidAccountID|ChallengeResponseFailed|InvalidPassword)",EventTV="([\d-]+|%(iso8601)s)",Severity="[\w]+",Service="[\w]+",EventVersion="\d+",AccountID="(\d*|)",SessionID=".+",LocalAddress="IPV[46]/(UDP|TCP|WS)/[\da-fA-F:.]+/\d+",RemoteAddress="IPV[46]/(UDP|TCP|WS)//\d+"(,Challenge="[\w/]+")?(,ReceivedChallenge="\w+")?(,Response="\w+",ExpectedResponse="\w*")?(,ReceivedHash="[\da-f]+")?(,ACLName="\w+")?$ ^%(__prefix_line)s%(log_prefix)s "Rejecting unknown SIP connection from "$ + ^%(__prefix_line)s%(log_prefix)s Request from '[^']*' failed for '(?::\d+)?' \(callid: \w*\) - No matching endpoint found$ ignoreregex = diff --git a/fail2ban/tests/files/logs/asterisk b/fail2ban/tests/files/logs/asterisk index aa32a290..3f49beec 100644 --- a/fail2ban/tests/files/logs/asterisk +++ b/fail2ban/tests/files/logs/asterisk @@ -67,3 +67,7 @@ Nov 4 18:30:40 localhost asterisk[32229]: NOTICE[32257]: chan_sip.c:23417 in han [2016-01-28 10:34:31] NOTICE[3477][C-000003c3] chan_sip.c: Call from '' (1.2.3.4:10836) to extension '0+441772285407' rejected because extension not found in context 'default'. # failJSON: { "time": "2016-01-28T10:34:33", "match": true , "host": "1.2.3.4" } [2016-01-28 10:34:33] NOTICE[3477][C-000003c3] chan_sip.c: Call from '' (1.2.3.4:10836) to extension '' rejected because extension not found in context 'my-context'. + +# Failed authentication with pjsip on Asterisk 13+ +# failJSON: { "time": "2016-05-23T10:18:16", "match": true , "host": "1.2.3.4" } +[2016-05-23 10:18:16] NOTICE[19388] res_pjsip/pjsip_distributor.c: Request from '"1000" ' failed for '1.2.3.4:48336' (callid: 276666022) - No matching endpoint found \ No newline at end of file From 11f7cf5ad82b26c59f5d62c35b38343264483980 Mon Sep 17 00:00:00 2001 From: Yaroslav Halchenko Date: Tue, 7 Jun 2016 21:38:39 -0400 Subject: [PATCH 07/26] DOC: changelog for recent exim filters tune up --- ChangeLog | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/ChangeLog b/ChangeLog index 21b8adfc..3f985b20 100644 --- a/ChangeLog +++ b/ChangeLog @@ -30,6 +30,7 @@ ver. 0.9.5 (2016/XX/XXX) - wanna-be-released - New Features: * New Actions: - action.d/firewallcmd-rich-rules and action.d/firewallcmd-rich-logging (gh-1367) + - Enhancements: * Extreme speedup of all sqlite database operations (gh-1436), by using of following sqlite options: @@ -38,7 +39,9 @@ ver. 0.9.5 (2016/XX/XXX) - wanna-be-released - (temp_store = MEMORY) temporary tables and indices are kept in memory * journald journalmatch for pure-ftpd (gh-1362) * Add additional regex filter for dovecot ldap authentication failures (gh-1370) - * added additional regex filters for exim (gh-1371) + * filter.d/exim*conf + - added additional regexes (gh-1371) + - made port entry optional ver. 0.9.4 (2016/03/08) - for-you-ladies From af8b650a371da68d1a35d2ad326dfddc5a6af7f6 Mon Sep 17 00:00:00 2001 From: "Serg G. Brester" Date: Mon, 13 Jun 2016 12:56:53 +0200 Subject: [PATCH 08/26] badip timeout option introduced, set to 30 seconds in our test cases (#1463) cherry-picked from 0.10 (little bit modified in test_badips.py, because no --fast option in test cases) --- config/action.d/badips.py | 11 +++++++---- fail2ban/tests/action_d/test_badips.py | 1 + 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/config/action.d/badips.py b/config/action.d/badips.py index 025289ca..4bc879a1 100644 --- a/config/action.d/badips.py +++ b/config/action.d/badips.py @@ -80,14 +80,17 @@ class BadIPsAction(ActionBase): If invalid `category`, `score`, `banaction` or `updateperiod`. """ + TIMEOUT = 10 _badips = "http://www.badips.com" def _Request(self, url, **argv): return Request(url, headers={'User-Agent': self.agent}, **argv) def __init__(self, jail, name, category, score=3, age="24h", key=None, - banaction=None, bancategory=None, bankey=None, updateperiod=900, agent="Fail2Ban"): + banaction=None, bancategory=None, bankey=None, updateperiod=900, agent="Fail2Ban", + timeout=TIMEOUT): super(BadIPsAction, self).__init__(jail, name) + self.timeout = timeout self.agent = agent self.category = category self.score = score @@ -119,7 +122,7 @@ class BadIPsAction(ActionBase): """ try: response = urlopen( - self._Request("/".join([self._badips, "get", "categories"])), None, 3) + self._Request("/".join([self._badips, "get", "categories"])), timeout=self.timeout) except HTTPError as response: messages = json.loads(response.read().decode('utf-8')) self._logSys.error( @@ -173,7 +176,7 @@ class BadIPsAction(ActionBase): urlencode({'age': age})]) if key: url = "&".join([url, urlencode({'key': key})]) - response = urlopen(self._Request(url)) + response = urlopen(self._Request(url), timeout=self.timeout) except HTTPError as response: messages = json.loads(response.read().decode('utf-8')) self._logSys.error( @@ -358,7 +361,7 @@ class BadIPsAction(ActionBase): url = "/".join([self._badips, "add", self.category, aInfo['ip']]) if self.key: url = "?".join([url, urlencode({'key': self.key})]) - response = urlopen(self._Request(url)) + response = urlopen(self._Request(url), timeout=self.timeout) except HTTPError as response: messages = json.loads(response.read().decode('utf-8')) self._logSys.error( diff --git a/fail2ban/tests/action_d/test_badips.py b/fail2ban/tests/action_d/test_badips.py index a7f148b1..3f71b7a3 100644 --- a/fail2ban/tests/action_d/test_badips.py +++ b/fail2ban/tests/action_d/test_badips.py @@ -37,6 +37,7 @@ if sys.version_info >= (2,7): self.jail.actions.add("badips", pythonModule, initOpts={ 'category': "ssh", 'banaction': "test", + 'timeout': 30, }) self.action = self.jail.actions["badips"] From 72a157b8f23240bee09c559a13cc8eb5aeb015f0 Mon Sep 17 00:00:00 2001 From: nturcksin Date: Wed, 8 Jun 2016 14:23:54 +0200 Subject: [PATCH 09/26] Improve PJSIP log support for asterisk 13+ with different callID (Squash gh-1458) Change the asterisk pjsip filter to don't take the callId part Add optional part between "Request" and "from" Listed all log message from asterisk --- config/filter.d/asterisk.conf | 9 +++++++-- fail2ban/tests/files/logs/asterisk | 13 ++++++++++++- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/config/filter.d/asterisk.conf b/config/filter.d/asterisk.conf index f6ccdd4f..535c3e0d 100644 --- a/config/filter.d/asterisk.conf +++ b/config/filter.d/asterisk.conf @@ -27,8 +27,13 @@ failregex = ^%(__prefix_line)s%(log_prefix)s Registration from '[^']*' failed fo ^%(__prefix_line)s%(log_prefix)s hacking attempt detected ''$ ^%(__prefix_line)s%(log_prefix)s SecurityEvent="(FailedACL|InvalidAccountID|ChallengeResponseFailed|InvalidPassword)",EventTV="([\d-]+|%(iso8601)s)",Severity="[\w]+",Service="[\w]+",EventVersion="\d+",AccountID="(\d*|)",SessionID=".+",LocalAddress="IPV[46]/(UDP|TCP|WS)/[\da-fA-F:.]+/\d+",RemoteAddress="IPV[46]/(UDP|TCP|WS)//\d+"(,Challenge="[\w/]+")?(,ReceivedChallenge="\w+")?(,Response="\w+",ExpectedResponse="\w*")?(,ReceivedHash="[\da-f]+")?(,ACLName="\w+")?$ ^%(__prefix_line)s%(log_prefix)s "Rejecting unknown SIP connection from "$ - ^%(__prefix_line)s%(log_prefix)s Request from '[^']*' failed for '(?::\d+)?' \(callid: \w*\) - No matching endpoint found$ - + ^%(__prefix_line)s%(log_prefix)s Request (?:'[^']*' )?from '[^']*' failed for '(?::\d+)?'\s\(callid: [^']*\) - No matching endpoint found + ^%(__prefix_line)s%(log_prefix)s Request (?:'[^']*' )?from '[^']*' failed for '(?::\d+)?'\s\(callid: [^']*\) - Not match Endpoint ACL + ^%(__prefix_line)s%(log_prefix)s Request (?:'[^']*' )?from '[^']*' failed for '(?::\d+)?'\s\(callid: [^']*\) - Not match Endpoint Contact ACL + ^%(__prefix_line)s%(log_prefix)s Request (?:'[^']*' )?from '[^']*' failed for '(?::\d+)?'\s\(callid: [^']*\) - Failed to authenticate + ^%(__prefix_line)s%(log_prefix)s Request (?:'[^']*' )?from '[^']*' failed for '(?::\d+)?'\s\(callid: [^']*\) - Error to authenticate + + ignoreregex = diff --git a/fail2ban/tests/files/logs/asterisk b/fail2ban/tests/files/logs/asterisk index 3f49beec..d17d93a1 100644 --- a/fail2ban/tests/files/logs/asterisk +++ b/fail2ban/tests/files/logs/asterisk @@ -70,4 +70,15 @@ Nov 4 18:30:40 localhost asterisk[32229]: NOTICE[32257]: chan_sip.c:23417 in han # Failed authentication with pjsip on Asterisk 13+ # failJSON: { "time": "2016-05-23T10:18:16", "match": true , "host": "1.2.3.4" } -[2016-05-23 10:18:16] NOTICE[19388] res_pjsip/pjsip_distributor.c: Request from '"1000" ' failed for '1.2.3.4:48336' (callid: 276666022) - No matching endpoint found \ No newline at end of file +[2016-05-23 10:18:16] NOTICE[19388] res_pjsip/pjsip_distributor.c: Request from '"1000" ' failed for '1.2.3.4:48336' (callid: 276666022) - No matching endpoint found +# failJSON: { "time": "2016-05-23T10:18:16", "match": true , "host": "1.2.3.4" } +[2016-05-23 10:18:16] NOTICE[19388] res_pjsip/pjsip_distributor.c: Request from '"1000" ' failed for '1.2.3.4:48336' (callid: 276666022) - Not match Endpoint ACL +# failJSON: { "time": "2016-05-23T10:18:16", "match": true , "host": "1.2.3.4" } +[2016-05-23 10:18:16] NOTICE[19388] res_pjsip/pjsip_distributor.c: Request from '"1000" ' failed for '1.2.3.4:48336' (callid: 276666022) - Not match Endpoint Contact ACL +# failJSON: { "time": "2016-05-23T10:18:16", "match": true , "host": "1.2.3.4" } +[2016-05-23 10:18:16] NOTICE[19388] res_pjsip/pjsip_distributor.c: Request from '"1000" ' failed for '1.2.3.4:48336' (callid: 276666022) - Failed to authenticate +# failJSON: { "time": "2016-05-23T10:18:16", "match": true , "host": "1.2.3.4" } +[2016-05-23 10:18:16] NOTICE[19388] res_pjsip/pjsip_distributor.c: Request from '"1000" ' failed for '1.2.3.4:48336' (callid: 276666022) - Error to authenticate +# Failed authentication with pjsip on Asterisk 13+ +# failJSON: { "time": "2016-06-08T23:40:26", "match": true , "host": "2.3.4.5" } +[2016-06-08 23:40:26] NOTICE[32497] res_pjsip/pjsip_distributor.c: Request from '"317" ' failed for '2.3.4.5:5089' (callid: 206f178f-896564cb-57573f49@1.2.3.4) - No matching endpoint found From f5f204ca7ce0b78030c5539fbb1d1d54d7fca97a Mon Sep 17 00:00:00 2001 From: sebres Date: Fri, 8 Jul 2016 11:44:16 +0200 Subject: [PATCH 10/26] Improved changes of gh-1458: `[^']*` after callid was wrong, changed to `[^\)]*`; regexp anchored at the end; almost the same regex grouped to one; Closes #1458 --- ChangeLog | 4 +++- config/filter.d/asterisk.conf | 9 ++------- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/ChangeLog b/ChangeLog index 3f985b20..b0bffb05 100644 --- a/ChangeLog +++ b/ChangeLog @@ -25,7 +25,9 @@ ver. 0.9.5 (2016/XX/XXX) - wanna-be-released added new parameter `__date_ambit` * gentoo-initd fixed --pidfile bug: `--pidfile` is option of start-stop-daemon, not argument of fail2ban (see gh-1434) - * filter.d/asterisk.conf - fix security log support for PJSIP and Asterisk 13+ + * filter.d/asterisk.conf + - fix security log support for PJSIP and Asterisk 13+ (gh-1456) + - improved log support for PJSIP and Asterisk 13+ with different callID (gh-1458) - New Features: * New Actions: diff --git a/config/filter.d/asterisk.conf b/config/filter.d/asterisk.conf index 535c3e0d..6af452e2 100644 --- a/config/filter.d/asterisk.conf +++ b/config/filter.d/asterisk.conf @@ -27,13 +27,8 @@ failregex = ^%(__prefix_line)s%(log_prefix)s Registration from '[^']*' failed fo ^%(__prefix_line)s%(log_prefix)s hacking attempt detected ''$ ^%(__prefix_line)s%(log_prefix)s SecurityEvent="(FailedACL|InvalidAccountID|ChallengeResponseFailed|InvalidPassword)",EventTV="([\d-]+|%(iso8601)s)",Severity="[\w]+",Service="[\w]+",EventVersion="\d+",AccountID="(\d*|)",SessionID=".+",LocalAddress="IPV[46]/(UDP|TCP|WS)/[\da-fA-F:.]+/\d+",RemoteAddress="IPV[46]/(UDP|TCP|WS)//\d+"(,Challenge="[\w/]+")?(,ReceivedChallenge="\w+")?(,Response="\w+",ExpectedResponse="\w*")?(,ReceivedHash="[\da-f]+")?(,ACLName="\w+")?$ ^%(__prefix_line)s%(log_prefix)s "Rejecting unknown SIP connection from "$ - ^%(__prefix_line)s%(log_prefix)s Request (?:'[^']*' )?from '[^']*' failed for '(?::\d+)?'\s\(callid: [^']*\) - No matching endpoint found - ^%(__prefix_line)s%(log_prefix)s Request (?:'[^']*' )?from '[^']*' failed for '(?::\d+)?'\s\(callid: [^']*\) - Not match Endpoint ACL - ^%(__prefix_line)s%(log_prefix)s Request (?:'[^']*' )?from '[^']*' failed for '(?::\d+)?'\s\(callid: [^']*\) - Not match Endpoint Contact ACL - ^%(__prefix_line)s%(log_prefix)s Request (?:'[^']*' )?from '[^']*' failed for '(?::\d+)?'\s\(callid: [^']*\) - Failed to authenticate - ^%(__prefix_line)s%(log_prefix)s Request (?:'[^']*' )?from '[^']*' failed for '(?::\d+)?'\s\(callid: [^']*\) - Error to authenticate - - + ^%(__prefix_line)s%(log_prefix)s Request (?:'[^']*' )?from '[^']*' failed for '(?::\d+)?'\s\(callid: [^\)]*\) - (?:No matching endpoint found|Not match Endpoint(?: Contact)? ACL|(?:Failed|Error) to authenticate)\s*$ + ignoreregex = From 4180cc362e63e49c566a8edd178ec21fa68a2875 Mon Sep 17 00:00:00 2001 From: sebres Date: Mon, 11 Jul 2016 13:22:58 +0200 Subject: [PATCH 11/26] code review --- fail2ban/client/fail2banclient.py | 12 ++++++++---- fail2ban/client/fail2bancmdline.py | 5 +---- fail2ban/protocol.py | 3 +++ fail2ban/server/server.py | 2 +- fail2ban/tests/utils.py | 7 +++---- 5 files changed, 16 insertions(+), 13 deletions(-) diff --git a/fail2ban/client/fail2banclient.py b/fail2ban/client/fail2banclient.py index a8e0a331..e3797279 100755 --- a/fail2ban/client/fail2banclient.py +++ b/fail2ban/client/fail2banclient.py @@ -411,11 +411,9 @@ class Fail2banClient(Fail2banCmdLine, Thread): signal.signal(s, sh) -## -# Wonderful visual :) -# - class _VisualWait: + """Small progress indication (as "wonderful visual") during waiting process + """ pos = 0 delta = 1 def __init__(self, maxpos=10): @@ -427,6 +425,8 @@ class _VisualWait: sys.stdout.write('\r'+(' '*(35+self.maxpos))+'\r') sys.stdout.flush() def heartbeat(self): + """Show or step for progress indicator + """ if not self.pos: sys.stdout.write("\nINFO [#" + (' '*self.maxpos) + "] Waiting on the server...\r\x1b[8C") self.pos += self.delta @@ -441,6 +441,8 @@ class _VisualWait: elif self.pos < 2: self.delta = 1 class _NotVisualWait: + """Mockup for invisible progress indication (not verbose) + """ def __enter__(self): return self def __exit__(self, *args): @@ -449,6 +451,8 @@ class _NotVisualWait: pass def VisualWait(verbose, *args, **kwargs): + """Wonderful visual progress indication (if verbose) + """ return _VisualWait(*args, **kwargs) if verbose > 1 else _NotVisualWait() diff --git a/fail2ban/client/fail2bancmdline.py b/fail2ban/client/fail2bancmdline.py index 83379a9a..9110a2b8 100644 --- a/fail2ban/client/fail2bancmdline.py +++ b/fail2ban/client/fail2bancmdline.py @@ -83,9 +83,6 @@ class Fail2banCmdLine(): output("Copyright (c) 2004-2008 Cyril Jaquier, 2008- Fail2Ban Contributors") output("Copyright of modifications held by their respective authors.") output("Licensed under the GNU General Public License v2 (GPL).") - output("") - output("Written by Cyril Jaquier .") - output("Many contributions by Yaroslav O. Halchenko .") def dispUsage(self): """ Prints Fail2Ban command line options and exits @@ -262,7 +259,7 @@ class Fail2banCmdLine(): @staticmethod def exit(code=0): # pragma: no cover - can't test logSys.debug("Exit with code %s", code) - if os._exit: + if hasattr(os, '_exit') and os._exit: os._exit(code) else: sys.exit(code) diff --git a/fail2ban/protocol.py b/fail2ban/protocol.py index 648666a1..d671f3c3 100644 --- a/fail2ban/protocol.py +++ b/fail2ban/protocol.py @@ -27,6 +27,9 @@ __license__ = "GPL" import textwrap def output(s): + """Default output handler for printing protocol. + Used to ease mocking in the test cases. + """ print(s) ## diff --git a/fail2ban/server/server.py b/fail2ban/server/server.py index 4a2a7c11..bade12cb 100644 --- a/fail2ban/server/server.py +++ b/fail2ban/server/server.py @@ -419,7 +419,7 @@ class Server: getLogger("fail2ban").setLevel(getattr(logging, value)) self.__logLevel = value except AttributeError: - raise ValueError("Invalid log level") + raise ValueError("Invalid log level %r" % value) ## # Get the logging level. diff --git a/fail2ban/tests/utils.py b/fail2ban/tests/utils.py index 2d5a38f7..a06fbef0 100644 --- a/fail2ban/tests/utils.py +++ b/fail2ban/tests/utils.py @@ -54,10 +54,9 @@ if not CONFIG_DIR: else: CONFIG_DIR = '/etc/fail2ban' -# In not installed env (setup, test-cases) use fail2ban modules from main directory: -if 1 or os.environ.get('PYTHONPATH', None) is None: - os.putenv('PYTHONPATH', os.path.dirname(os.path.dirname(os.path.dirname( - os.path.abspath(__file__))))) +# During the test cases (or setup) use fail2ban modules from main directory: +os.putenv('PYTHONPATH', os.path.dirname(os.path.dirname(os.path.dirname( + os.path.abspath(__file__))))) class F2B(optparse.Values): def __init__(self, opts={}): From 6a2b70f11db9841495c384f30e53941714ebd18a Mon Sep 17 00:00:00 2001 From: sebres Date: Mon, 11 Jul 2016 13:26:10 +0200 Subject: [PATCH 12/26] os.path.join instead of "/" - concatenation --- fail2ban/tests/fail2banclienttestcase.py | 62 ++++++++++++------------ 1 file changed, 31 insertions(+), 31 deletions(-) diff --git a/fail2ban/tests/fail2banclienttestcase.py b/fail2ban/tests/fail2banclienttestcase.py index 1a0880c7..ed961033 100644 --- a/fail2ban/tests/fail2banclienttestcase.py +++ b/fail2ban/tests/fail2banclienttestcase.py @@ -111,24 +111,24 @@ def _out_file(fn): # pragma: no cover logSys.debug('-'*30) def _start_params(tmp, use_stock=False, logtarget="/dev/null"): - cfg = tmp+"/config" + cfg = os.path.join(tmp,"config") if use_stock and STOCK: # copy config (sub-directories as alias): def ig_dirs(dir, files): return [f for f in files if os.path.isdir(os.path.join(dir, f))] shutil.copytree(STOCK_CONF_DIR, cfg, ignore=ig_dirs) - os.symlink(STOCK_CONF_DIR+"/action.d", cfg+"/action.d") - os.symlink(STOCK_CONF_DIR+"/filter.d", cfg+"/filter.d") + os.symlink(os.path.join(STOCK_CONF_DIR,"action.d"), os.path.join(cfg,"action.d")) + os.symlink(os.path.join(STOCK_CONF_DIR,"filter.d"), os.path.join(cfg,"filter.d")) # replace fail2ban params (database with memory): r = re.compile(r'^dbfile\s*=') - for line in fileinput.input(cfg+"/fail2ban.conf", inplace=True): + for line in fileinput.input(os.path.join(cfg,"fail2ban.conf"), inplace=True): line = line.rstrip('\n') if r.match(line): line = "dbfile = :memory:" print(line) # replace jail params (polling as backend to be fast in initialize): r = re.compile(r'^backend\s*=') - for line in fileinput.input(cfg+"/jail.conf", inplace=True): + for line in fileinput.input(os.path.join(cfg,"jail.conf"), inplace=True): line = line.rstrip('\n') if r.match(line): line = "backend = polling" @@ -136,21 +136,21 @@ def _start_params(tmp, use_stock=False, logtarget="/dev/null"): else: # just empty config directory without anything (only fail2ban.conf/jail.conf): os.mkdir(cfg) - f = open(cfg+"/fail2ban.conf", "w") + f = open(os.path.join(cfg,"fail2ban.conf"), "w") f.write('\n'.join(( "[Definition]", "loglevel = INFO", "logtarget = " + logtarget, "syslogsocket = auto", - "socket = "+tmp+"/f2b.sock", - "pidfile = "+tmp+"/f2b.pid", + "socket = "+os.path.join(tmp,"f2b.sock"), + "pidfile = "+os.path.join(tmp,"f2b.pid"), "backend = polling", "dbfile = :memory:", "dbpurgeage = 1d", "", ))) f.close() - f = open(cfg+"/jail.conf", "w") + f = open(os.path.join(cfg,"jail.conf"), "w") f.write('\n'.join(( "[INCLUDES]", "", "[DEFAULT]", "", @@ -158,10 +158,10 @@ def _start_params(tmp, use_stock=False, logtarget="/dev/null"): ))) f.close() if logSys.level < logging.DEBUG: # if HEAVYDEBUG - _out_file(cfg+"/fail2ban.conf") - _out_file(cfg+"/jail.conf") + _out_file(os.path.join(cfg,"fail2ban.conf")) + _out_file(os.path.join(cfg,"jail.conf")) # parameters (sock/pid and config, increase verbosity, set log, etc.): - return ("-c", cfg, "-s", tmp+"/f2b.sock", "-p", tmp+"/f2b.pid", + return ("-c", cfg, "-s", os.path.join(tmp,"f2b.sock"), "-p", os.path.join(tmp,"f2b.pid"), "-vv", "--logtarget", logtarget, "--loglevel", "DEBUG", "--syslogsocket", "auto", "--timeout", str(fail2bancmdline.MAX_WAITTIME), ) @@ -234,7 +234,7 @@ class Fail2banClientServerBase(LogCaptureTestCase): def _wait_for_srv(self, tmp, ready=True, startparams=None): try: - sock = tmp+"/f2b.sock" + sock = os.path.join(tmp,"f2b.sock") # wait for server (socket): ret = Utils.wait_for(lambda: os.path.exists(sock), MAX_WAITTIME) if not ret: @@ -245,7 +245,7 @@ class Fail2banClientServerBase(LogCaptureTestCase): if not ret: raise Exception('Unexpected: Server ready was not found.\nStart failed: %r' % (startparams,)) except: # pragma: no cover - log = tmp+"/f2b.log" + log = os.path.join(tmp,"f2b.log") if os.path.isfile(log): _out_file(log) else: @@ -319,7 +319,7 @@ class Fail2banClientTest(Fail2banClientServerBase): @with_kill_srv def testClientStartBackgroundCall(self, tmp): global INTERACT - startparams = _start_params(tmp, logtarget=tmp+"/f2b.log") + startparams = _start_params(tmp, logtarget=os.path.join(tmp,"f2b.log")) # start (in new process, using the same python version): cmd = (sys.executable, os.path.join(os.path.join(BIN), CLIENT)) logSys.debug('Start %s ...', cmd) @@ -443,29 +443,29 @@ class Fail2banClientTest(Fail2banClientServerBase): ## wrong config directory self.assertRaises(FailExitException, _exec_client, - (CLIENT, "--async", "-c", tmp+"/miss", "start",)) - self.assertLogged("Base configuration directory " + tmp+"/miss" + " does not exist") + (CLIENT, "--async", "-c", os.path.join(tmp,"miss"), "start",)) + self.assertLogged("Base configuration directory " + os.path.join(tmp,"miss") + " does not exist") self.pruneLog() ## wrong socket self.assertRaises(FailExitException, _exec_client, - (CLIENT, "--async", "-c", tmp+"/config", "-s", tmp+"/miss/f2b.sock", "start",)) - self.assertLogged("There is no directory " + tmp+"/miss" + " to contain the socket file") + (CLIENT, "--async", "-c", os.path.join(tmp,"config"), "-s", os.path.join(tmp,"miss/f2b.sock"), "start",)) + self.assertLogged("There is no directory " + os.path.join(tmp,"miss") + " to contain the socket file") self.pruneLog() ## not running self.assertRaises(FailExitException, _exec_client, - (CLIENT, "-c", tmp+"/config", "-s", tmp+"/f2b.sock", "reload",)) + (CLIENT, "-c", os.path.join(tmp,"config"), "-s", os.path.join(tmp,"f2b.sock"), "reload",)) self.assertLogged("Could not find server") self.pruneLog() ## already exists: - open(tmp+"/f2b.sock", 'a').close() + open(os.path.join(tmp,"f2b.sock"), 'a').close() self.assertRaises(FailExitException, _exec_client, - (CLIENT, "--async", "-c", tmp+"/config", "-s", tmp+"/f2b.sock", "start",)) + (CLIENT, "--async", "-c", os.path.join(tmp,"config"), "-s", os.path.join(tmp,"f2b.sock"), "start",)) self.assertLogged("Fail2ban seems to be in unexpected state (not running but the socket exists)") self.pruneLog() - os.remove(tmp+"/f2b.sock") + os.remove(os.path.join(tmp,"f2b.sock")) ## wrong option: self.assertRaises(FailExitException, _exec_client, @@ -497,7 +497,7 @@ class Fail2banServerTest(Fail2banClientServerBase): @with_kill_srv def testServerStartBackground(self, tmp): # to prevent fork of test-cases process, start server in background via command: - startparams = _start_params(tmp, logtarget=tmp+"/f2b.log") + startparams = _start_params(tmp, logtarget=os.path.join(tmp,"f2b.log")) # start (in new process, using the same python version): cmd = (sys.executable, os.path.join(os.path.join(BIN), SERVER)) logSys.debug('Start %s ...', cmd) @@ -579,20 +579,20 @@ class Fail2banServerTest(Fail2banClientServerBase): ## wrong config directory self.assertRaises(FailExitException, _exec_server, - (SERVER, "-c", tmp+"/miss",)) - self.assertLogged("Base configuration directory " + tmp+"/miss" + " does not exist") + (SERVER, "-c", os.path.join(tmp,"miss"),)) + self.assertLogged("Base configuration directory " + os.path.join(tmp,"miss") + " does not exist") self.pruneLog() ## wrong socket self.assertRaises(FailExitException, _exec_server, - (SERVER, "-c", tmp+"/config", "-x", "-s", tmp+"/miss/f2b.sock",)) - self.assertLogged("There is no directory " + tmp+"/miss" + " to contain the socket file") + (SERVER, "-c", os.path.join(tmp,"config"), "-x", "-s", os.path.join(tmp,"miss/f2b.sock"),)) + self.assertLogged("There is no directory " + os.path.join(tmp,"miss") + " to contain the socket file") self.pruneLog() ## already exists: - open(tmp+"/f2b.sock", 'a').close() + open(os.path.join(tmp,"f2b.sock"), 'a').close() self.assertRaises(FailExitException, _exec_server, - (SERVER, "-c", tmp+"/config", "-s", tmp+"/f2b.sock",)) + (SERVER, "-c", os.path.join(tmp,"config"), "-s", os.path.join(tmp,"f2b.sock"),)) self.assertLogged("Fail2ban seems to be in unexpected state (not running but the socket exists)") self.pruneLog() - os.remove(tmp+"/f2b.sock") + os.remove(os.path.join(tmp,"f2b.sock")) From 94cada0c6b7dd6ea4bc315c961380ded49ba9749 Mon Sep 17 00:00:00 2001 From: Yaroslav Halchenko Date: Mon, 11 Jul 2016 22:42:48 -0400 Subject: [PATCH 13/26] RF(PEP8 etc): Make new fail2banclienttestcase a bit more readable and PEP8 friendly --- fail2ban/tests/fail2banclienttestcase.py | 267 +++++++++++++---------- 1 file changed, 151 insertions(+), 116 deletions(-) diff --git a/fail2ban/tests/fail2banclienttestcase.py b/fail2ban/tests/fail2banclienttestcase.py index ed961033..69155c93 100644 --- a/fail2ban/tests/fail2banclienttestcase.py +++ b/fail2ban/tests/fail2banclienttestcase.py @@ -31,25 +31,29 @@ import time import signal import unittest +from os.path import join as pjoin, isdir, isfile, exists, dirname from functools import wraps from threading import Thread - from ..client import fail2banclient, fail2banserver, fail2bancmdline -from ..client.fail2banclient import Fail2banClient, exec_command_line as _exec_client, VisualWait +from ..client.fail2banclient import exec_command_line as _exec_client, VisualWait from ..client.fail2banserver import Fail2banServer, exec_command_line as _exec_server from .. import protocol from ..server import server from ..server.utils import Utils -from .utils import LogCaptureTestCase, logSys, with_tmpdir, shutil, logging +from .utils import LogCaptureTestCase, with_tmpdir, shutil, logging +from ..helpers import getLogger + +# Gets the instance of the logger. +logSys = getLogger(__name__) STOCK_CONF_DIR = "config" -STOCK = os.path.exists(os.path.join(STOCK_CONF_DIR,'fail2ban.conf')) +STOCK = exists(pjoin(STOCK_CONF_DIR, 'fail2ban.conf')) CLIENT = "fail2ban-client" SERVER = "fail2ban-server" -BIN = os.path.dirname(Fail2banServer.getServerPath()) +BIN = dirname(Fail2banServer.getServerPath()) MAX_WAITTIME = 30 if not unittest.F2B.fast else 5 @@ -57,7 +61,7 @@ MAX_WAITTIME = 30 if not unittest.F2B.fast else 5 # Several wrappers and settings for proper testing: # -fail2bancmdline.MAX_WAITTIME = MAX_WAITTIME-1 +fail2bancmdline.MAX_WAITTIME = MAX_WAITTIME - 1 fail2bancmdline.logSys = \ fail2banclient.logSys = \ @@ -72,23 +76,43 @@ fail2banclient.output = \ fail2banserver.output = \ protocol.output = _test_output + +# +# Mocking .exit so we could test its correct operation. +# Two custom exceptions will be assessed to be raised in the tests +# + +class ExitException(fail2bancmdline.ExitException): + """Exception upon a normal exit""" + pass + + +class FailExitException(fail2bancmdline.ExitException): + """Exception upon abnormal exit""" + pass + + def _test_exit(code=0): logSys.debug("Exit with code %s", code) if code == 0: raise ExitException() else: - raise FailExitException() + raise FailExitException() + fail2bancmdline.exit = \ fail2banclient.exit = \ fail2banserver.exit = _test_exit INTERACT = [] + + def _test_input_command(*args): if len(INTERACT): - #logSys.debug('--- interact command: %r', INTERACT[0]) + #logSys.debug('interact command: %r', INTERACT[0]) return INTERACT.pop(0) else: - return "exit" + return "exit" + fail2banclient.input_command = _test_input_command # prevents change logging params, log capturing, etc: @@ -97,38 +121,35 @@ fail2banclient.PRODUCTION = \ fail2banserver.PRODUCTION = False -class ExitException(fail2bancmdline.ExitException): - pass -class FailExitException(fail2bancmdline.ExitException): - pass - - -def _out_file(fn): # pragma: no cover +def _out_file(fn): + """Helper which outputs content of the file at HEAVYDEBUG loglevels""" logSys.debug('---- ' + fn + ' ----') for line in fileinput.input(fn): line = line.rstrip('\n') logSys.debug(line) logSys.debug('-'*30) + def _start_params(tmp, use_stock=False, logtarget="/dev/null"): - cfg = os.path.join(tmp,"config") + cfg = pjoin(tmp, "config") if use_stock and STOCK: # copy config (sub-directories as alias): def ig_dirs(dir, files): - return [f for f in files if os.path.isdir(os.path.join(dir, f))] + """Filters list of 'files' to contain only directories (under dir)""" + return [f for f in files if isdir(pjoin(dir, f))] shutil.copytree(STOCK_CONF_DIR, cfg, ignore=ig_dirs) - os.symlink(os.path.join(STOCK_CONF_DIR,"action.d"), os.path.join(cfg,"action.d")) - os.symlink(os.path.join(STOCK_CONF_DIR,"filter.d"), os.path.join(cfg,"filter.d")) + os.symlink(pjoin(STOCK_CONF_DIR, "action.d"), pjoin(cfg, "action.d")) + os.symlink(pjoin(STOCK_CONF_DIR, "filter.d"), pjoin(cfg, "filter.d")) # replace fail2ban params (database with memory): r = re.compile(r'^dbfile\s*=') - for line in fileinput.input(os.path.join(cfg,"fail2ban.conf"), inplace=True): + for line in fileinput.input(pjoin(cfg, "fail2ban.conf"), inplace=True): line = line.rstrip('\n') if r.match(line): line = "dbfile = :memory:" print(line) # replace jail params (polling as backend to be fast in initialize): r = re.compile(r'^backend\s*=') - for line in fileinput.input(os.path.join(cfg,"jail.conf"), inplace=True): + for line in fileinput.input(pjoin(cfg, "jail.conf"), inplace=True): line = line.rstrip('\n') if r.match(line): line = "backend = polling" @@ -136,64 +157,71 @@ def _start_params(tmp, use_stock=False, logtarget="/dev/null"): else: # just empty config directory without anything (only fail2ban.conf/jail.conf): os.mkdir(cfg) - f = open(os.path.join(cfg,"fail2ban.conf"), "w") + f = open(pjoin(cfg, "fail2ban.conf"), "w") f.write('\n'.join(( "[Definition]", "loglevel = INFO", "logtarget = " + logtarget, "syslogsocket = auto", - "socket = "+os.path.join(tmp,"f2b.sock"), - "pidfile = "+os.path.join(tmp,"f2b.pid"), + "socket = " + pjoin(tmp, "f2b.sock"), + "pidfile = " + pjoin(tmp, "f2b.pid"), "backend = polling", "dbfile = :memory:", "dbpurgeage = 1d", "", ))) f.close() - f = open(os.path.join(cfg,"jail.conf"), "w") + f = open(pjoin(cfg, "jail.conf"), "w") f.write('\n'.join(( "[INCLUDES]", "", "[DEFAULT]", "", "", ))) f.close() - if logSys.level < logging.DEBUG: # if HEAVYDEBUG - _out_file(os.path.join(cfg,"fail2ban.conf")) - _out_file(os.path.join(cfg,"jail.conf")) + if logSys.level < logging.DEBUG: # if HEAVYDEBUG + _out_file(pjoin(cfg, "fail2ban.conf")) + _out_file(pjoin(cfg, "jail.conf")) # parameters (sock/pid and config, increase verbosity, set log, etc.): - return ("-c", cfg, "-s", os.path.join(tmp,"f2b.sock"), "-p", os.path.join(tmp,"f2b.pid"), - "-vv", "--logtarget", logtarget, "--loglevel", "DEBUG", "--syslogsocket", "auto", - "--timeout", str(fail2bancmdline.MAX_WAITTIME), + return ( + "-c", cfg, "-s", pjoin(tmp, "f2b.sock"), "-p", pjoin(tmp, "f2b.pid"), + "-vv", "--logtarget", logtarget, "--loglevel", "DEBUG", "--syslogsocket", "auto", + "--timeout", str(fail2bancmdline.MAX_WAITTIME), ) -def _kill_srv(pidfile): # pragma: no cover - def _pid_exists(pid): - try: - os.kill(pid, 0) - return True - except OSError: - return False - logSys.debug("-- cleanup: %r", (pidfile, os.path.isdir(pidfile))) - if os.path.isdir(pidfile): + +def _pid_exists(pid): + """Check if PID exists by sending 0 signal to the PID process""" + try: + os.kill(pid, 0) + return True + except OSError: + return False + + +def _kill_srv(pidfile): + logSys.debug("cleanup: %r", (pidfile, isdir(pidfile))) + if isdir(pidfile): piddir = pidfile pidfile = piddir + "/f2b.pid" - if not os.path.isfile(pidfile): + if not isfile(pidfile): pidfile = piddir + "/fail2ban.pid" - if not os.path.isfile(pidfile): - logSys.debug("--- cleanup: no pidfile for %r", piddir) + + if not isfile(pidfile): + logSys.debug("cleanup: no pidfile for %r", piddir) return True + f = pid = None try: - logSys.debug("--- cleanup pidfile: %r", pidfile) + logSys.debug("cleanup pidfile: %r", pidfile) f = open(pidfile) pid = f.read().split()[1] pid = int(pid) - logSys.debug("--- cleanup pid: %r", pid) + logSys.debug("cleanup pid: %r", pid) if pid <= 0: raise ValueError('pid %s of %s is invalid' % (pid, pidfile)) if not _pid_exists(pid): return True - ## try to preper stop (have signal handler): + ## try to prepare stop (have signal handler): os.kill(pid, signal.SIGTERM) ## check still exists after small timeout: if not Utils.wait_for(lambda: not _pid_exists(pid), 1): @@ -207,6 +235,7 @@ def _kill_srv(pidfile): # pragma: no cover f.close() return True + def with_kill_srv(f): """Helper to decorate tests which receive in the last argument tmpdir to pass to kill_srv @@ -234,19 +263,25 @@ class Fail2banClientServerBase(LogCaptureTestCase): def _wait_for_srv(self, tmp, ready=True, startparams=None): try: - sock = os.path.join(tmp,"f2b.sock") + sock = pjoin(tmp, "f2b.sock") # wait for server (socket): - ret = Utils.wait_for(lambda: os.path.exists(sock), MAX_WAITTIME) + ret = Utils.wait_for(lambda: exists(sock), MAX_WAITTIME) if not ret: - raise Exception('Unexpected: Socket file does not exists.\nStart failed: %r' % (startparams,)) + raise Exception( + 'Unexpected: Socket file does not exists.\nStart failed: %r' + % (startparams,) + ) if ready: # wait for communication with worker ready: ret = Utils.wait_for(lambda: "Server ready" in self.getLog(), MAX_WAITTIME) if not ret: - raise Exception('Unexpected: Server ready was not found.\nStart failed: %r' % (startparams,)) - except: # pragma: no cover - log = os.path.join(tmp,"f2b.log") - if os.path.isfile(log): + raise Exception( + 'Unexpected: Server ready was not found.\nStart failed: %r' + % (startparams,) + ) + except: # pragma: no cover + log = pjoin(tmp, "f2b.log") + if isfile(log): _out_file(log) else: logSys.debug("No log file %s to examine details of error", log) @@ -256,16 +291,16 @@ class Fail2banClientServerBase(LogCaptureTestCase): class Fail2banClientTest(Fail2banClientServerBase): def testConsistency(self): - self.assertTrue(os.path.isfile(os.path.join(os.path.join(BIN), CLIENT))) - self.assertTrue(os.path.isfile(os.path.join(os.path.join(BIN), SERVER))) + self.assertTrue(isfile(pjoin(BIN, CLIENT))) + self.assertTrue(isfile(pjoin(BIN, SERVER))) def testClientUsage(self): - self.assertRaises(ExitException, _exec_client, + self.assertRaises(ExitException, _exec_client, (CLIENT, "-h",)) self.assertLogged("Usage: " + CLIENT) self.assertLogged("Report bugs to ") self.pruneLog() - self.assertRaises(ExitException, _exec_client, + self.assertRaises(ExitException, _exec_client, (CLIENT, "-vq", "-V",)) self.assertLogged("Fail2Ban v" + fail2bancmdline.version) @@ -273,7 +308,7 @@ class Fail2banClientTest(Fail2banClientServerBase): def testClientDump(self, tmp): # use here the stock configuration (if possible) startparams = _start_params(tmp, True) - self.assertRaises(ExitException, _exec_client, + self.assertRaises(ExitException, _exec_client, ((CLIENT,) + startparams + ("-vvd",))) self.assertLogged("Loading files") self.assertLogged("logtarget") @@ -284,33 +319,33 @@ class Fail2banClientTest(Fail2banClientServerBase): # use once the stock configuration (to test starting also) startparams = _start_params(tmp, True) # start: - self.assertRaises(ExitException, _exec_client, + self.assertRaises(ExitException, _exec_client, (CLIENT, "-b") + startparams + ("start",)) # wait for server (socket and ready): self._wait_for_srv(tmp, True, startparams=startparams) self.assertLogged("Server ready") self.assertLogged("Exit with code 0") try: - self.assertRaises(ExitException, _exec_client, + self.assertRaises(ExitException, _exec_client, (CLIENT,) + startparams + ("echo", "TEST-ECHO",)) - self.assertRaises(FailExitException, _exec_client, + self.assertRaises(FailExitException, _exec_client, (CLIENT,) + startparams + ("~~unknown~cmd~failed~~",)) self.pruneLog() # start again (should fail): - self.assertRaises(FailExitException, _exec_client, + self.assertRaises(FailExitException, _exec_client, (CLIENT, "-b") + startparams + ("start",)) self.assertLogged("Server already running") finally: self.pruneLog() # stop: - self.assertRaises(ExitException, _exec_client, + self.assertRaises(ExitException, _exec_client, (CLIENT,) + startparams + ("stop",)) self.assertLogged("Shutdown successful") self.assertLogged("Exit with code 0") self.pruneLog() # stop again (should fail): - self.assertRaises(FailExitException, _exec_client, + self.assertRaises(FailExitException, _exec_client, (CLIENT,) + startparams + ("stop",)) self.assertLogged("Failed to access socket path") self.assertLogged("Is fail2ban running?") @@ -319,9 +354,9 @@ class Fail2banClientTest(Fail2banClientServerBase): @with_kill_srv def testClientStartBackgroundCall(self, tmp): global INTERACT - startparams = _start_params(tmp, logtarget=os.path.join(tmp,"f2b.log")) + startparams = _start_params(tmp, logtarget=pjoin(tmp, "f2b.log")) # start (in new process, using the same python version): - cmd = (sys.executable, os.path.join(os.path.join(BIN), CLIENT)) + cmd = (sys.executable, pjoin(pjoin(BIN), CLIENT)) logSys.debug('Start %s ...', cmd) cmd = cmd + startparams + ("--async", "start",) ret = Utils.executeCmd(cmd, timeout=MAX_WAITTIME, shell=False, output=True) @@ -332,7 +367,7 @@ class Fail2banClientTest(Fail2banClientServerBase): self.pruneLog() try: # echo from client (inside): - self.assertRaises(ExitException, _exec_client, + self.assertRaises(ExitException, _exec_client, (CLIENT,) + startparams + ("echo", "TEST-ECHO",)) self.assertLogged("TEST-ECHO") self.assertLogged("Exit with code 0") @@ -343,7 +378,7 @@ class Fail2banClientTest(Fail2banClientServerBase): "status", "exit" ] - self.assertRaises(ExitException, _exec_client, + self.assertRaises(ExitException, _exec_client, (CLIENT,) + startparams + ("-i",)) self.assertLogged("INTERACT-ECHO") self.assertLogged("Status", "Number of jail:") @@ -355,7 +390,7 @@ class Fail2banClientTest(Fail2banClientServerBase): "restart", "exit" ] - self.assertRaises(ExitException, _exec_client, + self.assertRaises(ExitException, _exec_client, (CLIENT,) + startparams + ("-i",)) self.assertLogged("Reading config files:") self.assertLogged("Shutdown successful") @@ -367,12 +402,12 @@ class Fail2banClientTest(Fail2banClientServerBase): "reload ~~unknown~jail~fail~~", "exit" ] - self.assertRaises(ExitException, _exec_client, + self.assertRaises(ExitException, _exec_client, (CLIENT,) + startparams + ("-i",)) self.assertLogged("Failed during configuration: No section: '~~unknown~jail~fail~~'") self.pruneLog() # test reload missing jail (direct): - self.assertRaises(FailExitException, _exec_client, + self.assertRaises(FailExitException, _exec_client, (CLIENT,) + startparams + ("reload", "~~unknown~jail~fail~~")) self.assertLogged("Failed during configuration: No section: '~~unknown~jail~fail~~'") self.assertLogged("Exit with code -1") @@ -380,20 +415,20 @@ class Fail2banClientTest(Fail2banClientServerBase): finally: self.pruneLog() # stop: - self.assertRaises(ExitException, _exec_client, + self.assertRaises(ExitException, _exec_client, (CLIENT,) + startparams + ("stop",)) self.assertLogged("Shutdown successful") self.assertLogged("Exit with code 0") def _testClientStartForeground(self, tmp, startparams, phase): # start and wait to end (foreground): - logSys.debug("-- start of test worker") + logSys.debug("start of test worker") phase['start'] = True - self.assertRaises(fail2bancmdline.ExitException, _exec_client, + self.assertRaises(fail2bancmdline.ExitException, _exec_client, (CLIENT, "-f") + startparams + ("start",)) # end : phase['end'] = True - logSys.debug("-- end of test worker") + logSys.debug("end of test worker") @with_tmpdir def testClientStartForeground(self, tmp): @@ -403,7 +438,7 @@ class Fail2banClientTest(Fail2banClientServerBase): startparams = _start_params(tmp, logtarget="INHERITED") # because foreground block execution - start it in thread: phase = dict() - th = Thread(name="_TestCaseWorker", + th = Thread(name="_TestCaseWorker", target=Fail2banClientTest._testClientStartForeground, args=(self, tmp, startparams, phase)) th.daemon = True th.start() @@ -415,16 +450,16 @@ class Fail2banClientTest(Fail2banClientServerBase): self._wait_for_srv(tmp, True, startparams=startparams) self.pruneLog() # several commands to server: - self.assertRaises(ExitException, _exec_client, + self.assertRaises(ExitException, _exec_client, (CLIENT,) + startparams + ("ping",)) - self.assertRaises(FailExitException, _exec_client, + self.assertRaises(FailExitException, _exec_client, (CLIENT,) + startparams + ("~~unknown~cmd~failed~~",)) - self.assertRaises(ExitException, _exec_client, + self.assertRaises(ExitException, _exec_client, (CLIENT,) + startparams + ("echo", "TEST-ECHO",)) finally: self.pruneLog() # stop: - self.assertRaises(ExitException, _exec_client, + self.assertRaises(ExitException, _exec_client, (CLIENT,) + startparams + ("stop",)) # wait for end: Utils.wait_for(lambda: phase.get('end', None) is not None, MAX_WAITTIME) @@ -442,33 +477,33 @@ class Fail2banClientTest(Fail2banClientServerBase): startparams = _start_params(tmp, logtarget="INHERITED") ## wrong config directory - self.assertRaises(FailExitException, _exec_client, - (CLIENT, "--async", "-c", os.path.join(tmp,"miss"), "start",)) - self.assertLogged("Base configuration directory " + os.path.join(tmp,"miss") + " does not exist") + self.assertRaises(FailExitException, _exec_client, + (CLIENT, "--async", "-c", pjoin(tmp, "miss"), "start",)) + self.assertLogged("Base configuration directory " + pjoin(tmp, "miss") + " does not exist") self.pruneLog() ## wrong socket - self.assertRaises(FailExitException, _exec_client, - (CLIENT, "--async", "-c", os.path.join(tmp,"config"), "-s", os.path.join(tmp,"miss/f2b.sock"), "start",)) - self.assertLogged("There is no directory " + os.path.join(tmp,"miss") + " to contain the socket file") + self.assertRaises(FailExitException, _exec_client, + (CLIENT, "--async", "-c", pjoin(tmp, "config"), "-s", pjoin(tmp, "miss/f2b.sock"), "start",)) + self.assertLogged("There is no directory " + pjoin(tmp, "miss") + " to contain the socket file") self.pruneLog() ## not running - self.assertRaises(FailExitException, _exec_client, - (CLIENT, "-c", os.path.join(tmp,"config"), "-s", os.path.join(tmp,"f2b.sock"), "reload",)) + self.assertRaises(FailExitException, _exec_client, + (CLIENT, "-c", pjoin(tmp, "config"), "-s", pjoin(tmp, "f2b.sock"), "reload",)) self.assertLogged("Could not find server") self.pruneLog() ## already exists: - open(os.path.join(tmp,"f2b.sock"), 'a').close() - self.assertRaises(FailExitException, _exec_client, - (CLIENT, "--async", "-c", os.path.join(tmp,"config"), "-s", os.path.join(tmp,"f2b.sock"), "start",)) + open(pjoin(tmp, "f2b.sock"), 'a').close() + self.assertRaises(FailExitException, _exec_client, + (CLIENT, "--async", "-c", pjoin(tmp, "config"), "-s", pjoin(tmp, "f2b.sock"), "start",)) self.assertLogged("Fail2ban seems to be in unexpected state (not running but the socket exists)") self.pruneLog() - os.remove(os.path.join(tmp,"f2b.sock")) + os.remove(pjoin(tmp, "f2b.sock")) ## wrong option: - self.assertRaises(FailExitException, _exec_client, + self.assertRaises(FailExitException, _exec_client, (CLIENT, "-s",)) self.assertLogged("Usage: ") self.pruneLog() @@ -488,7 +523,7 @@ class Fail2banClientTest(Fail2banClientServerBase): class Fail2banServerTest(Fail2banClientServerBase): def testServerUsage(self): - self.assertRaises(ExitException, _exec_server, + self.assertRaises(ExitException, _exec_server, (SERVER, "-h",)) self.assertLogged("Usage: " + SERVER) self.assertLogged("Report bugs to ") @@ -497,9 +532,9 @@ class Fail2banServerTest(Fail2banClientServerBase): @with_kill_srv def testServerStartBackground(self, tmp): # to prevent fork of test-cases process, start server in background via command: - startparams = _start_params(tmp, logtarget=os.path.join(tmp,"f2b.log")) + startparams = _start_params(tmp, logtarget=pjoin(tmp, "f2b.log")) # start (in new process, using the same python version): - cmd = (sys.executable, os.path.join(os.path.join(BIN), SERVER)) + cmd = (sys.executable, pjoin(pjoin(BIN), SERVER)) logSys.debug('Start %s ...', cmd) cmd = cmd + startparams + ("-b",) ret = Utils.executeCmd(cmd, timeout=MAX_WAITTIME, shell=False, output=True) @@ -509,14 +544,14 @@ class Fail2banServerTest(Fail2banClientServerBase): self.assertLogged("Server ready") self.pruneLog() try: - self.assertRaises(ExitException, _exec_server, + self.assertRaises(ExitException, _exec_server, (SERVER,) + startparams + ("echo", "TEST-ECHO",)) - self.assertRaises(FailExitException, _exec_server, + self.assertRaises(FailExitException, _exec_server, (SERVER,) + startparams + ("~~unknown~cmd~failed~~",)) finally: self.pruneLog() # stop: - self.assertRaises(ExitException, _exec_server, + self.assertRaises(ExitException, _exec_server, (SERVER,) + startparams + ("stop",)) self.assertLogged("Shutdown successful") self.assertLogged("Exit with code 0") @@ -525,7 +560,7 @@ class Fail2banServerTest(Fail2banClientServerBase): # start and wait to end (foreground): logSys.debug("-- start of test worker") phase['start'] = True - self.assertRaises(fail2bancmdline.ExitException, _exec_server, + self.assertRaises(fail2bancmdline.ExitException, _exec_server, (SERVER, "-f") + startparams + ("start",)) # end : phase['end'] = True @@ -539,7 +574,7 @@ class Fail2banServerTest(Fail2banClientServerBase): startparams = _start_params(tmp, logtarget="INHERITED") # because foreground block execution - start it in thread: phase = dict() - th = Thread(name="_TestCaseWorker", + th = Thread(name="_TestCaseWorker", target=Fail2banServerTest._testServerStartForeground, args=(self, tmp, startparams, phase)) th.daemon = True th.start() @@ -551,16 +586,16 @@ class Fail2banServerTest(Fail2banClientServerBase): self._wait_for_srv(tmp, True, startparams=startparams) self.pruneLog() # several commands to server: - self.assertRaises(ExitException, _exec_server, + self.assertRaises(ExitException, _exec_server, (SERVER,) + startparams + ("ping",)) - self.assertRaises(FailExitException, _exec_server, + self.assertRaises(FailExitException, _exec_server, (SERVER,) + startparams + ("~~unknown~cmd~failed~~",)) - self.assertRaises(ExitException, _exec_server, + self.assertRaises(ExitException, _exec_server, (SERVER,) + startparams + ("echo", "TEST-ECHO",)) finally: self.pruneLog() # stop: - self.assertRaises(ExitException, _exec_server, + self.assertRaises(ExitException, _exec_server, (SERVER,) + startparams + ("stop",)) # wait for end: Utils.wait_for(lambda: phase.get('end', None) is not None, MAX_WAITTIME) @@ -578,21 +613,21 @@ class Fail2banServerTest(Fail2banClientServerBase): startparams = _start_params(tmp, logtarget="INHERITED") ## wrong config directory - self.assertRaises(FailExitException, _exec_server, - (SERVER, "-c", os.path.join(tmp,"miss"),)) - self.assertLogged("Base configuration directory " + os.path.join(tmp,"miss") + " does not exist") + self.assertRaises(FailExitException, _exec_server, + (SERVER, "-c", pjoin(tmp, "miss"),)) + self.assertLogged("Base configuration directory " + pjoin(tmp, "miss") + " does not exist") self.pruneLog() ## wrong socket - self.assertRaises(FailExitException, _exec_server, - (SERVER, "-c", os.path.join(tmp,"config"), "-x", "-s", os.path.join(tmp,"miss/f2b.sock"),)) - self.assertLogged("There is no directory " + os.path.join(tmp,"miss") + " to contain the socket file") + self.assertRaises(FailExitException, _exec_server, + (SERVER, "-c", pjoin(tmp, "config"), "-x", "-s", pjoin(tmp, "miss/f2b.sock"),)) + self.assertLogged("There is no directory " + pjoin(tmp, "miss") + " to contain the socket file") self.pruneLog() ## already exists: - open(os.path.join(tmp,"f2b.sock"), 'a').close() - self.assertRaises(FailExitException, _exec_server, - (SERVER, "-c", os.path.join(tmp,"config"), "-s", os.path.join(tmp,"f2b.sock"),)) + open(pjoin(tmp, "f2b.sock"), 'a').close() + self.assertRaises(FailExitException, _exec_server, + (SERVER, "-c", pjoin(tmp, "config"), "-s", pjoin(tmp, "f2b.sock"),)) self.assertLogged("Fail2ban seems to be in unexpected state (not running but the socket exists)") self.pruneLog() - os.remove(os.path.join(tmp,"f2b.sock")) + os.remove(pjoin(tmp, "f2b.sock")) From 4df06f535294060218f968a4733df350dfa2e239 Mon Sep 17 00:00:00 2001 From: Yaroslav Halchenko Date: Mon, 11 Jul 2016 22:44:47 -0400 Subject: [PATCH 14/26] ENH: testcases - verbosity > 2 now also includes logger name (to replace need for custom --s) --- bin/fail2ban-testcases | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/fail2ban-testcases b/bin/fail2ban-testcases index 20d444f0..748737a5 100755 --- a/bin/fail2ban-testcases +++ b/bin/fail2ban-testcases @@ -122,7 +122,7 @@ if verbosity > 1: # pragma: no cover if verbosity > 3: fmt = ' | %(module)15.15s-%(levelno)-2d: %(funcName)-20.20s |' + fmt if verbosity > 2: - fmt = ' +%(relativeCreated)5d %(thread)X %(levelname)-5.5s' + fmt + fmt = ' +%(relativeCreated)5d %(thread)X %(name)-25s %(levelname)-5.5s' + fmt else: fmt = ' %(asctime)-15s %(thread)X %(levelname)-5.5s' + fmt # From 1417cc99ef54b7dd1da83509a23531e984223993 Mon Sep 17 00:00:00 2001 From: Yaroslav Halchenko Date: Mon, 11 Jul 2016 23:26:30 -0400 Subject: [PATCH 15/26] RF: group common tests in base class, specialize assesment of good/bad runs per client/server --- fail2ban/tests/fail2banclienttestcase.py | 270 ++++++++++------------- 1 file changed, 116 insertions(+), 154 deletions(-) diff --git a/fail2ban/tests/fail2banclienttestcase.py b/fail2ban/tests/fail2banclienttestcase.py index 69155c93..e69d31e1 100644 --- a/fail2ban/tests/fail2banclienttestcase.py +++ b/fail2ban/tests/fail2banclienttestcase.py @@ -287,29 +287,91 @@ class Fail2banClientServerBase(LogCaptureTestCase): logSys.debug("No log file %s to examine details of error", log) raise + def assertExitsNormally(self, args): + raise NotImplementedError("To be defined in subclass") + + def assertExitsAbnormally(self, args): + raise NotImplementedError("To be defined in subclass") + + # + # Common tests + # + def _testStartForeground(self, tmp, startparams, phase): + # start and wait to end (foreground): + logSys.debug("start of test worker") + phase['start'] = True + self.assertExitsNormally(("-f",) + startparams + ("start",)) + # end : + phase['end'] = True + logSys.debug("end of test worker") + + @with_tmpdir + def testStartForeground(self, tmp): + # intended to be ran only in subclasses + th = None + try: + # started directly here, so prevent overwrite test cases logger with "INHERITED" + startparams = _start_params(tmp, logtarget="INHERITED") + # because foreground block execution - start it in thread: + phase = dict() + th = Thread( + name="_TestCaseWorker", + target=self._testStartForeground, + args=(tmp, startparams, phase) + ) + th.daemon = True + th.start() + try: + # wait for start thread: + Utils.wait_for(lambda: phase.get('start', None) is not None, MAX_WAITTIME) + self.assertTrue(phase.get('start', None)) + # wait for server (socket and ready): + self._wait_for_srv(tmp, True, startparams=startparams) + self.pruneLog() + # several commands to server: + self.assertExitsNormally(startparams + ("ping",)) + self.assertExitsAbnormally(startparams + ("~~unknown~cmd~failed~~",)) + self.assertExitsNormally(startparams + ("echo", "TEST-ECHO",)) + finally: + self.pruneLog() + # stop: + self.assertExitsNormally(startparams + ("stop",)) + # wait for end: + Utils.wait_for(lambda: phase.get('end', None) is not None, MAX_WAITTIME) + self.assertTrue(phase.get('end', None)) + self.assertLogged("Shutdown successful", "Exiting Fail2ban") + finally: + _kill_srv(tmp) + if th: + th.join() + class Fail2banClientTest(Fail2banClientServerBase): + def assertExitsNormally(self, args): + self.assertRaises(ExitException, _exec_client, ((CLIENT,) + args)) + + def assertExitsAbnormally(self, args): + self.assertRaises(FailExitException, _exec_client, ((CLIENT,) + args)) + def testConsistency(self): self.assertTrue(isfile(pjoin(BIN, CLIENT))) self.assertTrue(isfile(pjoin(BIN, SERVER))) def testClientUsage(self): - self.assertRaises(ExitException, _exec_client, - (CLIENT, "-h",)) + self.assertExitsNormally(("-h",)) self.assertLogged("Usage: " + CLIENT) self.assertLogged("Report bugs to ") self.pruneLog() - self.assertRaises(ExitException, _exec_client, - (CLIENT, "-vq", "-V",)) + self.assertExitsNormally(("-vq", "-V",)) self.assertLogged("Fail2Ban v" + fail2bancmdline.version) @with_tmpdir def testClientDump(self, tmp): # use here the stock configuration (if possible) startparams = _start_params(tmp, True) - self.assertRaises(ExitException, _exec_client, - ((CLIENT,) + startparams + ("-vvd",))) + self.assertExitsNormally( + (startparams + ("-vvd",))) self.assertLogged("Loading files") self.assertLogged("logtarget") @@ -319,34 +381,34 @@ class Fail2banClientTest(Fail2banClientServerBase): # use once the stock configuration (to test starting also) startparams = _start_params(tmp, True) # start: - self.assertRaises(ExitException, _exec_client, - (CLIENT, "-b") + startparams + ("start",)) + self.assertExitsNormally( + ("-b",) + startparams + ("start",)) # wait for server (socket and ready): self._wait_for_srv(tmp, True, startparams=startparams) self.assertLogged("Server ready") self.assertLogged("Exit with code 0") try: - self.assertRaises(ExitException, _exec_client, - (CLIENT,) + startparams + ("echo", "TEST-ECHO",)) - self.assertRaises(FailExitException, _exec_client, - (CLIENT,) + startparams + ("~~unknown~cmd~failed~~",)) + self.assertExitsNormally( + startparams + ("echo", "TEST-ECHO",)) + self.assertExitsAbnormally( + startparams + ("~~unknown~cmd~failed~~",)) self.pruneLog() # start again (should fail): - self.assertRaises(FailExitException, _exec_client, - (CLIENT, "-b") + startparams + ("start",)) + self.assertExitsAbnormally( + ("-b",) + startparams + ("start",)) self.assertLogged("Server already running") finally: self.pruneLog() # stop: - self.assertRaises(ExitException, _exec_client, - (CLIENT,) + startparams + ("stop",)) + self.assertExitsNormally( + startparams + ("stop",)) self.assertLogged("Shutdown successful") self.assertLogged("Exit with code 0") self.pruneLog() # stop again (should fail): - self.assertRaises(FailExitException, _exec_client, - (CLIENT,) + startparams + ("stop",)) + self.assertExitsAbnormally( + startparams + ("stop",)) self.assertLogged("Failed to access socket path") self.assertLogged("Is fail2ban running?") @@ -367,8 +429,7 @@ class Fail2banClientTest(Fail2banClientServerBase): self.pruneLog() try: # echo from client (inside): - self.assertRaises(ExitException, _exec_client, - (CLIENT,) + startparams + ("echo", "TEST-ECHO",)) + self.assertExitsNormally(startparams + ("echo", "TEST-ECHO",)) self.assertLogged("TEST-ECHO") self.assertLogged("Exit with code 0") self.pruneLog() @@ -378,8 +439,8 @@ class Fail2banClientTest(Fail2banClientServerBase): "status", "exit" ] - self.assertRaises(ExitException, _exec_client, - (CLIENT,) + startparams + ("-i",)) + self.assertExitsNormally( + startparams + ("-i",)) self.assertLogged("INTERACT-ECHO") self.assertLogged("Status", "Number of jail:") self.assertLogged("Exit with code 0") @@ -390,8 +451,8 @@ class Fail2banClientTest(Fail2banClientServerBase): "restart", "exit" ] - self.assertRaises(ExitException, _exec_client, - (CLIENT,) + startparams + ("-i",)) + self.assertExitsNormally( + startparams + ("-i",)) self.assertLogged("Reading config files:") self.assertLogged("Shutdown successful") self.assertLogged("Server ready") @@ -402,74 +463,23 @@ class Fail2banClientTest(Fail2banClientServerBase): "reload ~~unknown~jail~fail~~", "exit" ] - self.assertRaises(ExitException, _exec_client, - (CLIENT,) + startparams + ("-i",)) + self.assertExitsNormally( + startparams + ("-i",)) self.assertLogged("Failed during configuration: No section: '~~unknown~jail~fail~~'") self.pruneLog() # test reload missing jail (direct): - self.assertRaises(FailExitException, _exec_client, - (CLIENT,) + startparams + ("reload", "~~unknown~jail~fail~~")) + self.assertExitsAbnormally( + startparams + ("reload", "~~unknown~jail~fail~~")) self.assertLogged("Failed during configuration: No section: '~~unknown~jail~fail~~'") self.assertLogged("Exit with code -1") self.pruneLog() finally: self.pruneLog() # stop: - self.assertRaises(ExitException, _exec_client, - (CLIENT,) + startparams + ("stop",)) + self.assertExitsNormally(startparams + ("stop",)) self.assertLogged("Shutdown successful") self.assertLogged("Exit with code 0") - def _testClientStartForeground(self, tmp, startparams, phase): - # start and wait to end (foreground): - logSys.debug("start of test worker") - phase['start'] = True - self.assertRaises(fail2bancmdline.ExitException, _exec_client, - (CLIENT, "-f") + startparams + ("start",)) - # end : - phase['end'] = True - logSys.debug("end of test worker") - - @with_tmpdir - def testClientStartForeground(self, tmp): - th = None - try: - # started directly here, so prevent overwrite test cases logger with "INHERITED" - startparams = _start_params(tmp, logtarget="INHERITED") - # because foreground block execution - start it in thread: - phase = dict() - th = Thread(name="_TestCaseWorker", - target=Fail2banClientTest._testClientStartForeground, args=(self, tmp, startparams, phase)) - th.daemon = True - th.start() - try: - # wait for start thread: - Utils.wait_for(lambda: phase.get('start', None) is not None, MAX_WAITTIME) - self.assertTrue(phase.get('start', None)) - # wait for server (socket and ready): - self._wait_for_srv(tmp, True, startparams=startparams) - self.pruneLog() - # several commands to server: - self.assertRaises(ExitException, _exec_client, - (CLIENT,) + startparams + ("ping",)) - self.assertRaises(FailExitException, _exec_client, - (CLIENT,) + startparams + ("~~unknown~cmd~failed~~",)) - self.assertRaises(ExitException, _exec_client, - (CLIENT,) + startparams + ("echo", "TEST-ECHO",)) - finally: - self.pruneLog() - # stop: - self.assertRaises(ExitException, _exec_client, - (CLIENT,) + startparams + ("stop",)) - # wait for end: - Utils.wait_for(lambda: phase.get('end', None) is not None, MAX_WAITTIME) - self.assertTrue(phase.get('end', None)) - self.assertLogged("Shutdown successful", "Exiting Fail2ban") - finally: - _kill_srv(tmp) - if th: - th.join() - @with_tmpdir @with_kill_srv def testClientFailStart(self, tmp): @@ -477,34 +487,34 @@ class Fail2banClientTest(Fail2banClientServerBase): startparams = _start_params(tmp, logtarget="INHERITED") ## wrong config directory - self.assertRaises(FailExitException, _exec_client, - (CLIENT, "--async", "-c", pjoin(tmp, "miss"), "start",)) + self.assertExitsAbnormally( + ("--async", "-c", pjoin(tmp, "miss"), "start",)) self.assertLogged("Base configuration directory " + pjoin(tmp, "miss") + " does not exist") self.pruneLog() ## wrong socket - self.assertRaises(FailExitException, _exec_client, - (CLIENT, "--async", "-c", pjoin(tmp, "config"), "-s", pjoin(tmp, "miss/f2b.sock"), "start",)) + self.assertExitsAbnormally( + ("--async", "-c", pjoin(tmp, "config"), "-s", pjoin(tmp, "miss/f2b.sock"), "start",)) self.assertLogged("There is no directory " + pjoin(tmp, "miss") + " to contain the socket file") self.pruneLog() ## not running - self.assertRaises(FailExitException, _exec_client, - (CLIENT, "-c", pjoin(tmp, "config"), "-s", pjoin(tmp, "f2b.sock"), "reload",)) + self.assertExitsAbnormally( + ("-c", pjoin(tmp, "config"), "-s", pjoin(tmp, "f2b.sock"), "reload",)) self.assertLogged("Could not find server") self.pruneLog() ## already exists: open(pjoin(tmp, "f2b.sock"), 'a').close() - self.assertRaises(FailExitException, _exec_client, - (CLIENT, "--async", "-c", pjoin(tmp, "config"), "-s", pjoin(tmp, "f2b.sock"), "start",)) + self.assertExitsAbnormally( + ("--async", "-c", pjoin(tmp, "config"), "-s", pjoin(tmp, "f2b.sock"), "start",)) self.assertLogged("Fail2ban seems to be in unexpected state (not running but the socket exists)") self.pruneLog() os.remove(pjoin(tmp, "f2b.sock")) ## wrong option: - self.assertRaises(FailExitException, _exec_client, - (CLIENT, "-s",)) + self.assertExitsAbnormally( + ("-s",)) self.assertLogged("Usage: ") self.pruneLog() @@ -522,9 +532,14 @@ class Fail2banClientTest(Fail2banClientServerBase): class Fail2banServerTest(Fail2banClientServerBase): + def assertExitsNormally(self, args): + self.assertRaises(ExitException, _exec_server, ((SERVER,) + args)) + + def assertExitsAbnormally(self, args): + self.assertRaises(FailExitException, _exec_server, ((SERVER,) + args)) + def testServerUsage(self): - self.assertRaises(ExitException, _exec_server, - (SERVER, "-h",)) + self.assertExitsNormally(("-h",)) self.assertLogged("Usage: " + SERVER) self.assertLogged("Report bugs to ") @@ -544,68 +559,15 @@ class Fail2banServerTest(Fail2banClientServerBase): self.assertLogged("Server ready") self.pruneLog() try: - self.assertRaises(ExitException, _exec_server, - (SERVER,) + startparams + ("echo", "TEST-ECHO",)) - self.assertRaises(FailExitException, _exec_server, - (SERVER,) + startparams + ("~~unknown~cmd~failed~~",)) + self.assertExitsNormally(startparams + ("echo", "TEST-ECHO",)) + self.assertExitsAbnormally(startparams + ("~~unknown~cmd~failed~~",)) finally: self.pruneLog() # stop: - self.assertRaises(ExitException, _exec_server, - (SERVER,) + startparams + ("stop",)) + self.assertExitsNormally(startparams + ("stop",)) self.assertLogged("Shutdown successful") self.assertLogged("Exit with code 0") - def _testServerStartForeground(self, tmp, startparams, phase): - # start and wait to end (foreground): - logSys.debug("-- start of test worker") - phase['start'] = True - self.assertRaises(fail2bancmdline.ExitException, _exec_server, - (SERVER, "-f") + startparams + ("start",)) - # end : - phase['end'] = True - logSys.debug("-- end of test worker") - - @with_tmpdir - def testServerStartForeground(self, tmp): - th = None - try: - # started directly here, so prevent overwrite test cases logger with "INHERITED" - startparams = _start_params(tmp, logtarget="INHERITED") - # because foreground block execution - start it in thread: - phase = dict() - th = Thread(name="_TestCaseWorker", - target=Fail2banServerTest._testServerStartForeground, args=(self, tmp, startparams, phase)) - th.daemon = True - th.start() - try: - # wait for start thread: - Utils.wait_for(lambda: phase.get('start', None) is not None, MAX_WAITTIME) - self.assertTrue(phase.get('start', None)) - # wait for server (socket and ready): - self._wait_for_srv(tmp, True, startparams=startparams) - self.pruneLog() - # several commands to server: - self.assertRaises(ExitException, _exec_server, - (SERVER,) + startparams + ("ping",)) - self.assertRaises(FailExitException, _exec_server, - (SERVER,) + startparams + ("~~unknown~cmd~failed~~",)) - self.assertRaises(ExitException, _exec_server, - (SERVER,) + startparams + ("echo", "TEST-ECHO",)) - finally: - self.pruneLog() - # stop: - self.assertRaises(ExitException, _exec_server, - (SERVER,) + startparams + ("stop",)) - # wait for end: - Utils.wait_for(lambda: phase.get('end', None) is not None, MAX_WAITTIME) - self.assertTrue(phase.get('end', None)) - self.assertLogged("Shutdown successful", "Exiting Fail2ban") - finally: - _kill_srv(tmp) - if th: - th.join() - @with_tmpdir @with_kill_srv def testServerFailStart(self, tmp): @@ -613,21 +575,21 @@ class Fail2banServerTest(Fail2banClientServerBase): startparams = _start_params(tmp, logtarget="INHERITED") ## wrong config directory - self.assertRaises(FailExitException, _exec_server, - (SERVER, "-c", pjoin(tmp, "miss"),)) + self.assertExitsAbnormally( + ("-c", pjoin(tmp, "miss"),)) self.assertLogged("Base configuration directory " + pjoin(tmp, "miss") + " does not exist") self.pruneLog() ## wrong socket - self.assertRaises(FailExitException, _exec_server, - (SERVER, "-c", pjoin(tmp, "config"), "-x", "-s", pjoin(tmp, "miss/f2b.sock"),)) + self.assertExitsAbnormally( + ("-c", pjoin(tmp, "config"), "-x", "-s", pjoin(tmp, "miss/f2b.sock"),)) self.assertLogged("There is no directory " + pjoin(tmp, "miss") + " to contain the socket file") self.pruneLog() ## already exists: open(pjoin(tmp, "f2b.sock"), 'a').close() - self.assertRaises(FailExitException, _exec_server, - (SERVER, "-c", pjoin(tmp, "config"), "-s", pjoin(tmp, "f2b.sock"),)) + self.assertExitsAbnormally( + ("-c", pjoin(tmp, "config"), "-s", pjoin(tmp, "f2b.sock"),)) self.assertLogged("Fail2ban seems to be in unexpected state (not running but the socket exists)") self.pruneLog() os.remove(pjoin(tmp, "f2b.sock")) From fcda7c9ac7b3e8585314bbdb4d6615ed0d2a0ece Mon Sep 17 00:00:00 2001 From: Yaroslav Halchenko Date: Mon, 11 Jul 2016 23:54:52 -0400 Subject: [PATCH 16/26] RF: revertably mock out exit call while testing new client/servers --- fail2ban/client/fail2bancmdline.py | 14 ++++++++++++-- fail2ban/tests/fail2banclienttestcase.py | 23 ++++++++++++----------- 2 files changed, 24 insertions(+), 13 deletions(-) diff --git a/fail2ban/client/fail2bancmdline.py b/fail2ban/client/fail2bancmdline.py index 9110a2b8..19f596a2 100644 --- a/fail2ban/client/fail2bancmdline.py +++ b/fail2ban/client/fail2bancmdline.py @@ -42,6 +42,7 @@ PRODUCTION = True MAX_WAITTIME = 30 + class Fail2banCmdLine(): def __init__(self): @@ -256,14 +257,23 @@ class Fail2banCmdLine(): output(c) return True + # + # _exit is made to ease mocking out of the behaviour in tests, + # since method is also exposed in API via globally bound variable @staticmethod - def exit(code=0): # pragma: no cover - can't test - logSys.debug("Exit with code %s", code) + def _exit(code=0): if hasattr(os, '_exit') and os._exit: os._exit(code) else: sys.exit(code) + @staticmethod + def exit(code=0): + logSys.debug("Exit with code %s", code) + # import pdb; pdb.set_trace() + Fail2banCmdLine._exit(code) + + # global exit handler: exit = Fail2banCmdLine.exit diff --git a/fail2ban/tests/fail2banclienttestcase.py b/fail2ban/tests/fail2banclienttestcase.py index e69d31e1..fa8a210c 100644 --- a/fail2ban/tests/fail2banclienttestcase.py +++ b/fail2ban/tests/fail2banclienttestcase.py @@ -36,6 +36,7 @@ from functools import wraps from threading import Thread from ..client import fail2banclient, fail2banserver, fail2bancmdline +from ..client.fail2bancmdline import Fail2banCmdLine from ..client.fail2banclient import exec_command_line as _exec_client, VisualWait from ..client.fail2banserver import Fail2banServer, exec_command_line as _exec_server from .. import protocol @@ -92,17 +93,6 @@ class FailExitException(fail2bancmdline.ExitException): pass -def _test_exit(code=0): - logSys.debug("Exit with code %s", code) - if code == 0: - raise ExitException() - else: - raise FailExitException() - -fail2bancmdline.exit = \ -fail2banclient.exit = \ -fail2banserver.exit = _test_exit - INTERACT = [] @@ -256,11 +246,22 @@ class Fail2banClientServerBase(LogCaptureTestCase): def setUp(self): """Call before every test case.""" LogCaptureTestCase.setUp(self) + Fail2banCmdLine._exit = staticmethod(self._test_exit) def tearDown(self): """Call after every test case.""" + Fail2banCmdLine._exit = self._orig_exit LogCaptureTestCase.tearDown(self) + _orig_exit = Fail2banCmdLine._exit + + @staticmethod + def _test_exit(code=0): + if code == 0: + raise ExitException() + else: + raise FailExitException() + def _wait_for_srv(self, tmp, ready=True, startparams=None): try: sock = pjoin(tmp, "f2b.sock") From 3c42b4f56e5e20a56e22d534371ff7adbc2ce78b Mon Sep 17 00:00:00 2001 From: Yaroslav Halchenko Date: Tue, 12 Jul 2016 00:06:41 -0400 Subject: [PATCH 17/26] minor residual cleanups --- fail2ban/client/fail2bancmdline.py | 1 - fail2ban/tests/fail2banclienttestcase.py | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/fail2ban/client/fail2bancmdline.py b/fail2ban/client/fail2bancmdline.py index 19f596a2..aadf823a 100644 --- a/fail2ban/client/fail2bancmdline.py +++ b/fail2ban/client/fail2bancmdline.py @@ -270,7 +270,6 @@ class Fail2banCmdLine(): @staticmethod def exit(code=0): logSys.debug("Exit with code %s", code) - # import pdb; pdb.set_trace() Fail2banCmdLine._exit(code) diff --git a/fail2ban/tests/fail2banclienttestcase.py b/fail2ban/tests/fail2banclienttestcase.py index fa8a210c..24b396f5 100644 --- a/fail2ban/tests/fail2banclienttestcase.py +++ b/fail2ban/tests/fail2banclienttestcase.py @@ -419,7 +419,7 @@ class Fail2banClientTest(Fail2banClientServerBase): global INTERACT startparams = _start_params(tmp, logtarget=pjoin(tmp, "f2b.log")) # start (in new process, using the same python version): - cmd = (sys.executable, pjoin(pjoin(BIN), CLIENT)) + cmd = (sys.executable, pjoin(BIN, CLIENT)) logSys.debug('Start %s ...', cmd) cmd = cmd + startparams + ("--async", "start",) ret = Utils.executeCmd(cmd, timeout=MAX_WAITTIME, shell=False, output=True) @@ -550,7 +550,7 @@ class Fail2banServerTest(Fail2banClientServerBase): # to prevent fork of test-cases process, start server in background via command: startparams = _start_params(tmp, logtarget=pjoin(tmp, "f2b.log")) # start (in new process, using the same python version): - cmd = (sys.executable, pjoin(pjoin(BIN), SERVER)) + cmd = (sys.executable, pjoin(BIN, SERVER)) logSys.debug('Start %s ...', cmd) cmd = cmd + startparams + ("-b",) ret = Utils.executeCmd(cmd, timeout=MAX_WAITTIME, shell=False, output=True) From 7242c9cbdeb9dea90a5d6068d881823fda9d6139 Mon Sep 17 00:00:00 2001 From: sebres Date: Tue, 12 Jul 2016 12:02:05 +0200 Subject: [PATCH 18/26] code review after enhancements of Yaroslav --- bin/fail2ban-testcases | 4 +- fail2ban/tests/fail2banclienttestcase.py | 124 +++++++++++------------ 2 files changed, 60 insertions(+), 68 deletions(-) diff --git a/bin/fail2ban-testcases b/bin/fail2ban-testcases index 748737a5..98b9118f 100755 --- a/bin/fail2ban-testcases +++ b/bin/fail2ban-testcases @@ -30,7 +30,7 @@ import sys import time import unittest -# Check if local fail2ban module exists, and use if it exists by +# Check if local fail2ban module exists, and use if it exists by # modifying the path. This is such that tests can be used in dev # environment. if os.path.exists("fail2ban/__init__.py"): @@ -122,7 +122,7 @@ if verbosity > 1: # pragma: no cover if verbosity > 3: fmt = ' | %(module)15.15s-%(levelno)-2d: %(funcName)-20.20s |' + fmt if verbosity > 2: - fmt = ' +%(relativeCreated)5d %(thread)X %(name)-25s %(levelname)-5.5s' + fmt + fmt = ' +%(relativeCreated)5d %(thread)X %(name)-25.25s %(levelname)-5.5s' + fmt else: fmt = ' %(asctime)-15s %(thread)X %(levelname)-5.5s' + fmt # diff --git a/fail2ban/tests/fail2banclienttestcase.py b/fail2ban/tests/fail2banclienttestcase.py index 24b396f5..e0ec8dcd 100644 --- a/fail2ban/tests/fail2banclienttestcase.py +++ b/fail2ban/tests/fail2banclienttestcase.py @@ -192,9 +192,9 @@ def _kill_srv(pidfile): logSys.debug("cleanup: %r", (pidfile, isdir(pidfile))) if isdir(pidfile): piddir = pidfile - pidfile = piddir + "/f2b.pid" - if not isfile(pidfile): - pidfile = piddir + "/fail2ban.pid" + pidfile = pjoin(piddir, "f2b.pid") + if not isfile(pidfile): # pragma: no cover + pidfile = pjoin(piddir, "fail2ban.pid") if not isfile(pidfile): logSys.debug("cleanup: no pidfile for %r", piddir) @@ -243,6 +243,8 @@ def with_kill_srv(f): class Fail2banClientServerBase(LogCaptureTestCase): + _orig_exit = Fail2banCmdLine._exit + def setUp(self): """Call before every test case.""" LogCaptureTestCase.setUp(self) @@ -253,8 +255,6 @@ class Fail2banClientServerBase(LogCaptureTestCase): Fail2banCmdLine._exit = self._orig_exit LogCaptureTestCase.tearDown(self) - _orig_exit = Fail2banCmdLine._exit - @staticmethod def _test_exit(code=0): if code == 0: @@ -288,10 +288,10 @@ class Fail2banClientServerBase(LogCaptureTestCase): logSys.debug("No log file %s to examine details of error", log) raise - def assertExitsNormally(self, args): + def execSuccess(self, startparams, *args): raise NotImplementedError("To be defined in subclass") - def assertExitsAbnormally(self, args): + def execFailed(self, startparams, *args): raise NotImplementedError("To be defined in subclass") # @@ -301,7 +301,7 @@ class Fail2banClientServerBase(LogCaptureTestCase): # start and wait to end (foreground): logSys.debug("start of test worker") phase['start'] = True - self.assertExitsNormally(("-f",) + startparams + ("start",)) + self.execSuccess(("-f",) + startparams, "start") # end : phase['end'] = True logSys.debug("end of test worker") @@ -330,13 +330,13 @@ class Fail2banClientServerBase(LogCaptureTestCase): self._wait_for_srv(tmp, True, startparams=startparams) self.pruneLog() # several commands to server: - self.assertExitsNormally(startparams + ("ping",)) - self.assertExitsAbnormally(startparams + ("~~unknown~cmd~failed~~",)) - self.assertExitsNormally(startparams + ("echo", "TEST-ECHO",)) + self.execSuccess(startparams, "ping") + self.execFailed(startparams, "~~unknown~cmd~failed~~") + self.execSuccess(startparams, "echo", "TEST-ECHO") finally: self.pruneLog() # stop: - self.assertExitsNormally(startparams + ("stop",)) + self.execSuccess(startparams, "stop") # wait for end: Utils.wait_for(lambda: phase.get('end', None) is not None, MAX_WAITTIME) self.assertTrue(phase.get('end', None)) @@ -349,30 +349,31 @@ class Fail2banClientServerBase(LogCaptureTestCase): class Fail2banClientTest(Fail2banClientServerBase): - def assertExitsNormally(self, args): - self.assertRaises(ExitException, _exec_client, ((CLIENT,) + args)) + def execSuccess(self, startparams, *args): + self.assertRaises(ExitException, _exec_client, + ((CLIENT,) + startparams + args)) - def assertExitsAbnormally(self, args): - self.assertRaises(FailExitException, _exec_client, ((CLIENT,) + args)) + def execFailed(self, startparams, *args): + self.assertRaises(FailExitException, _exec_client, + ((CLIENT,) + startparams + args)) def testConsistency(self): self.assertTrue(isfile(pjoin(BIN, CLIENT))) self.assertTrue(isfile(pjoin(BIN, SERVER))) def testClientUsage(self): - self.assertExitsNormally(("-h",)) + self.execSuccess((), "-h") self.assertLogged("Usage: " + CLIENT) self.assertLogged("Report bugs to ") self.pruneLog() - self.assertExitsNormally(("-vq", "-V",)) + self.execSuccess((), "-vq", "-V") self.assertLogged("Fail2Ban v" + fail2bancmdline.version) @with_tmpdir def testClientDump(self, tmp): # use here the stock configuration (if possible) startparams = _start_params(tmp, True) - self.assertExitsNormally( - (startparams + ("-vvd",))) + self.execSuccess(startparams, "-vvd") self.assertLogged("Loading files") self.assertLogged("logtarget") @@ -382,34 +383,28 @@ class Fail2banClientTest(Fail2banClientServerBase): # use once the stock configuration (to test starting also) startparams = _start_params(tmp, True) # start: - self.assertExitsNormally( - ("-b",) + startparams + ("start",)) + self.execSuccess(("-b",) + startparams, "start") # wait for server (socket and ready): self._wait_for_srv(tmp, True, startparams=startparams) self.assertLogged("Server ready") self.assertLogged("Exit with code 0") try: - self.assertExitsNormally( - startparams + ("echo", "TEST-ECHO",)) - self.assertExitsAbnormally( - startparams + ("~~unknown~cmd~failed~~",)) + self.execSuccess(startparams, "echo", "TEST-ECHO") + self.execFailed(startparams, "~~unknown~cmd~failed~~") self.pruneLog() # start again (should fail): - self.assertExitsAbnormally( - ("-b",) + startparams + ("start",)) + self.execFailed(("-b",) + startparams, "start") self.assertLogged("Server already running") finally: self.pruneLog() # stop: - self.assertExitsNormally( - startparams + ("stop",)) + self.execSuccess(startparams, "stop") self.assertLogged("Shutdown successful") self.assertLogged("Exit with code 0") self.pruneLog() # stop again (should fail): - self.assertExitsAbnormally( - startparams + ("stop",)) + self.execFailed(startparams, "stop") self.assertLogged("Failed to access socket path") self.assertLogged("Is fail2ban running?") @@ -430,7 +425,7 @@ class Fail2banClientTest(Fail2banClientServerBase): self.pruneLog() try: # echo from client (inside): - self.assertExitsNormally(startparams + ("echo", "TEST-ECHO",)) + self.execSuccess(startparams, "echo", "TEST-ECHO") self.assertLogged("TEST-ECHO") self.assertLogged("Exit with code 0") self.pruneLog() @@ -440,8 +435,7 @@ class Fail2banClientTest(Fail2banClientServerBase): "status", "exit" ] - self.assertExitsNormally( - startparams + ("-i",)) + self.execSuccess(startparams, "-i") self.assertLogged("INTERACT-ECHO") self.assertLogged("Status", "Number of jail:") self.assertLogged("Exit with code 0") @@ -452,8 +446,7 @@ class Fail2banClientTest(Fail2banClientServerBase): "restart", "exit" ] - self.assertExitsNormally( - startparams + ("-i",)) + self.execSuccess(startparams, "-i") self.assertLogged("Reading config files:") self.assertLogged("Shutdown successful") self.assertLogged("Server ready") @@ -464,20 +457,18 @@ class Fail2banClientTest(Fail2banClientServerBase): "reload ~~unknown~jail~fail~~", "exit" ] - self.assertExitsNormally( - startparams + ("-i",)) + self.execSuccess(startparams, "-i") self.assertLogged("Failed during configuration: No section: '~~unknown~jail~fail~~'") self.pruneLog() # test reload missing jail (direct): - self.assertExitsAbnormally( - startparams + ("reload", "~~unknown~jail~fail~~")) + self.execFailed(startparams, "reload", "~~unknown~jail~fail~~") self.assertLogged("Failed during configuration: No section: '~~unknown~jail~fail~~'") self.assertLogged("Exit with code -1") self.pruneLog() finally: self.pruneLog() # stop: - self.assertExitsNormally(startparams + ("stop",)) + self.execSuccess(startparams, "stop") self.assertLogged("Shutdown successful") self.assertLogged("Exit with code 0") @@ -488,34 +479,33 @@ class Fail2banClientTest(Fail2banClientServerBase): startparams = _start_params(tmp, logtarget="INHERITED") ## wrong config directory - self.assertExitsAbnormally( - ("--async", "-c", pjoin(tmp, "miss"), "start",)) + self.execFailed((), + "--async", "-c", pjoin(tmp, "miss"), "start") self.assertLogged("Base configuration directory " + pjoin(tmp, "miss") + " does not exist") self.pruneLog() ## wrong socket - self.assertExitsAbnormally( - ("--async", "-c", pjoin(tmp, "config"), "-s", pjoin(tmp, "miss/f2b.sock"), "start",)) + self.execFailed((), + "--async", "-c", pjoin(tmp, "config"), "-s", pjoin(tmp, "miss/f2b.sock"), "start") self.assertLogged("There is no directory " + pjoin(tmp, "miss") + " to contain the socket file") self.pruneLog() ## not running - self.assertExitsAbnormally( - ("-c", pjoin(tmp, "config"), "-s", pjoin(tmp, "f2b.sock"), "reload",)) + self.execFailed((), + "-c", pjoin(tmp, "config"), "-s", pjoin(tmp, "f2b.sock"), "reload") self.assertLogged("Could not find server") self.pruneLog() ## already exists: open(pjoin(tmp, "f2b.sock"), 'a').close() - self.assertExitsAbnormally( - ("--async", "-c", pjoin(tmp, "config"), "-s", pjoin(tmp, "f2b.sock"), "start",)) + self.execFailed((), + "--async", "-c", pjoin(tmp, "config"), "-s", pjoin(tmp, "f2b.sock"), "start") self.assertLogged("Fail2ban seems to be in unexpected state (not running but the socket exists)") self.pruneLog() os.remove(pjoin(tmp, "f2b.sock")) ## wrong option: - self.assertExitsAbnormally( - ("-s",)) + self.execFailed((), "-s") self.assertLogged("Usage: ") self.pruneLog() @@ -533,14 +523,16 @@ class Fail2banClientTest(Fail2banClientServerBase): class Fail2banServerTest(Fail2banClientServerBase): - def assertExitsNormally(self, args): - self.assertRaises(ExitException, _exec_server, ((SERVER,) + args)) + def execSuccess(self, startparams, *args): + self.assertRaises(ExitException, _exec_server, + ((SERVER,) + startparams + args)) - def assertExitsAbnormally(self, args): - self.assertRaises(FailExitException, _exec_server, ((SERVER,) + args)) + def execFailed(self, startparams, *args): + self.assertRaises(FailExitException, _exec_server, + ((SERVER,) + startparams + args)) def testServerUsage(self): - self.assertExitsNormally(("-h",)) + self.execSuccess((), "-h") self.assertLogged("Usage: " + SERVER) self.assertLogged("Report bugs to ") @@ -560,12 +552,12 @@ class Fail2banServerTest(Fail2banClientServerBase): self.assertLogged("Server ready") self.pruneLog() try: - self.assertExitsNormally(startparams + ("echo", "TEST-ECHO",)) - self.assertExitsAbnormally(startparams + ("~~unknown~cmd~failed~~",)) + self.execSuccess(startparams, "echo", "TEST-ECHO") + self.execFailed(startparams, "~~unknown~cmd~failed~~") finally: self.pruneLog() # stop: - self.assertExitsNormally(startparams + ("stop",)) + self.execSuccess(startparams, "stop") self.assertLogged("Shutdown successful") self.assertLogged("Exit with code 0") @@ -576,21 +568,21 @@ class Fail2banServerTest(Fail2banClientServerBase): startparams = _start_params(tmp, logtarget="INHERITED") ## wrong config directory - self.assertExitsAbnormally( - ("-c", pjoin(tmp, "miss"),)) + self.execFailed((), + "-c", pjoin(tmp, "miss")) self.assertLogged("Base configuration directory " + pjoin(tmp, "miss") + " does not exist") self.pruneLog() ## wrong socket - self.assertExitsAbnormally( - ("-c", pjoin(tmp, "config"), "-x", "-s", pjoin(tmp, "miss/f2b.sock"),)) + self.execFailed((), + "-c", pjoin(tmp, "config"), "-x", "-s", pjoin(tmp, "miss/f2b.sock")) self.assertLogged("There is no directory " + pjoin(tmp, "miss") + " to contain the socket file") self.pruneLog() ## already exists: open(pjoin(tmp, "f2b.sock"), 'a').close() - self.assertExitsAbnormally( - ("-c", pjoin(tmp, "config"), "-s", pjoin(tmp, "f2b.sock"),)) + self.execFailed((), + "-c", pjoin(tmp, "config"), "-s", pjoin(tmp, "f2b.sock")) self.assertLogged("Fail2ban seems to be in unexpected state (not running but the socket exists)") self.pruneLog() os.remove(pjoin(tmp, "f2b.sock")) From 0dc3db1f43dc73de91c46556c7b6083cfd42e99c Mon Sep 17 00:00:00 2001 From: sebres Date: Tue, 12 Jul 2016 12:06:48 +0200 Subject: [PATCH 19/26] test of `_kill_srv` added, don't kill server if foreground; pid_exists already available in Utils; small bug and stability fixing; --- fail2ban/tests/fail2banclienttestcase.py | 81 ++++++++++++++++-------- 1 file changed, 54 insertions(+), 27 deletions(-) diff --git a/fail2ban/tests/fail2banclienttestcase.py b/fail2ban/tests/fail2banclienttestcase.py index e0ec8dcd..2a18d204 100644 --- a/fail2ban/tests/fail2banclienttestcase.py +++ b/fail2ban/tests/fail2banclienttestcase.py @@ -98,7 +98,7 @@ INTERACT = [] def _test_input_command(*args): if len(INTERACT): - #logSys.debug('interact command: %r', INTERACT[0]) + #logSys.debug('--- interact command: %r', INTERACT[0]) return INTERACT.pop(0) else: return "exit" @@ -179,15 +179,6 @@ def _start_params(tmp, use_stock=False, logtarget="/dev/null"): ) -def _pid_exists(pid): - """Check if PID exists by sending 0 signal to the PID process""" - try: - os.kill(pid, 0) - return True - except OSError: - return False - - def _kill_srv(pidfile): logSys.debug("cleanup: %r", (pidfile, isdir(pidfile))) if isdir(pidfile): @@ -204,25 +195,32 @@ def _kill_srv(pidfile): try: logSys.debug("cleanup pidfile: %r", pidfile) f = open(pidfile) - pid = f.read().split()[1] + pid = f.read() + pid = re.match(r'\S+', pid).group() pid = int(pid) - logSys.debug("cleanup pid: %r", pid) - if pid <= 0: - raise ValueError('pid %s of %s is invalid' % (pid, pidfile)) - if not _pid_exists(pid): - return True - ## try to prepare stop (have signal handler): - os.kill(pid, signal.SIGTERM) - ## check still exists after small timeout: - if not Utils.wait_for(lambda: not _pid_exists(pid), 1): - ## try to kill hereafter: - os.kill(pid, signal.SIGKILL) - return not _pid_exists(pid) - except Exception as e: + except Exception as e: # pragma: no cover logSys.debug(e) + return False finally: if f is not None: f.close() + + try: + logSys.debug("cleanup pid: %r", pid) + if pid <= 0 or pid == os.getpid(): # pragma: no cover + raise ValueError('pid %s of %s is invalid' % (pid, pidfile)) + if not Utils.pid_exists(pid): + return True + ## try to properly stop (have signal handler): + os.kill(pid, signal.SIGTERM) + ## check still exists after small timeout: + if not Utils.wait_for(lambda: not Utils.pid_exists(pid), 1): + ## try to kill hereafter: + os.kill(pid, signal.SIGKILL) + logSys.debug("cleanup: kill ready") + return not Utils.pid_exists(pid) + except Exception as e: # pragma: no cover + logSys.exception(e) return True @@ -310,11 +308,11 @@ class Fail2banClientServerBase(LogCaptureTestCase): def testStartForeground(self, tmp): # intended to be ran only in subclasses th = None + phase = dict() try: # started directly here, so prevent overwrite test cases logger with "INHERITED" startparams = _start_params(tmp, logtarget="INHERITED") # because foreground block execution - start it in thread: - phase = dict() th = Thread( name="_TestCaseWorker", target=self._testStartForeground, @@ -342,9 +340,11 @@ class Fail2banClientServerBase(LogCaptureTestCase): self.assertTrue(phase.get('end', None)) self.assertLogged("Shutdown successful", "Exiting Fail2ban") finally: - _kill_srv(tmp) if th: - th.join() + # we start client/server directly in current process (new thread), + # so don't kill (same process) - if success, just wait for end of worker: + if phase.get('end', None): + th.join() class Fail2banClientTest(Fail2banClientServerBase): @@ -586,3 +586,30 @@ class Fail2banServerTest(Fail2banClientServerBase): self.assertLogged("Fail2ban seems to be in unexpected state (not running but the socket exists)") self.pruneLog() os.remove(pjoin(tmp, "f2b.sock")) + + @with_tmpdir + def testKillAfterStart(self, tmp): + try: + # to prevent fork of test-cases process, start server in background via command: + startparams = _start_params(tmp, logtarget=pjoin(tmp, "f2b.log")) + # start (in new process, using the same python version): + cmd = (sys.executable, pjoin(BIN, SERVER)) + logSys.debug('Start %s ...', cmd) + cmd = cmd + startparams + ("-b",) + ret = Utils.executeCmd(cmd, timeout=MAX_WAITTIME, shell=False, output=True) + self.assertTrue(len(ret) and ret[0]) + # wait for server (socket and ready): + self._wait_for_srv(tmp, True, startparams=cmd) + self.assertLogged("Server ready") + self.pruneLog() + logSys.debug('Kill server ... %s', tmp) + finally: + self.assertTrue(_kill_srv(tmp)) + # wait for end (kill was successful): + Utils.wait_for(lambda: not isfile(pjoin(tmp, "f2b.pid")), MAX_WAITTIME) + self.assertFalse(isfile(pjoin(tmp, "f2b.pid"))) + self.assertLogged("cleanup: kill ready") + self.pruneLog() + # again: + self.assertTrue(_kill_srv(tmp)) + self.assertLogged("cleanup: no pidfile for") From 52cd8a9a960d24c042e6181a83e44ef49cbd54b2 Mon Sep 17 00:00:00 2001 From: Yaroslav Halchenko Date: Tue, 12 Jul 2016 22:24:56 -0400 Subject: [PATCH 20/26] RF: _rebindSignal helper to centralize assignment of new signal handlers + minor PEP8ing --- fail2ban/server/server.py | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/fail2ban/server/server.py b/fail2ban/server/server.py index bade12cb..7edf19e9 100644 --- a/fail2ban/server/server.py +++ b/fail2ban/server/server.py @@ -87,6 +87,11 @@ class Server: logSys.debug("Caught signal %d. Flushing logs" % signum) self.flushLogs() + def _rebindSignal(self, s, new): + """Bind new signal handler while storing old one in _prev_signals""" + self.__prev_signals[s] = signal.getsignal(s) + signal.signal(s, new) + def start(self, sock, pidfile, force=False, conf={}): # First set the mask to only allow access to owner os.umask(0077) @@ -120,9 +125,10 @@ class Server: # Install signal handlers if _thread_name() == '_MainThread': - for s in (signal.SIGTERM, signal.SIGINT, signal.SIGUSR1): - self.__prev_signals[s] = signal.getsignal(s) - signal.signal(s, self.__sigTERMhandler if s != signal.SIGUSR1 else self.__sigUSR1handler) + for s in (signal.SIGTERM, signal.SIGINT): + self._rebindSignal(s, self.__sigTERMhandler) + self._rebindSignal(signal.SIGUSR1, self.__sigUSR1handler) + # Ensure unhandled exceptions are logged sys.excepthook = excepthook @@ -490,18 +496,18 @@ class Server: try: handler.flush() handler.close() - except (ValueError, KeyError): # pragma: no cover + except (ValueError, KeyError): # pragma: no cover # Is known to be thrown after logging was shutdown once # with older Pythons -- seems to be safe to ignore there # At least it was still failing on 2.6.2-0ubuntu1 (jaunty) - if (2,6,3) <= sys.version_info < (3,) or \ - (3,2) <= sys.version_info: + if (2, 6, 3) <= sys.version_info < (3,) or \ + (3, 2) <= sys.version_info: raise # tell the handler to use this format hdlr.setFormatter(formatter) logger.addHandler(hdlr) # Does not display this message at startup. - if not self.__logTarget is None: + if self.__logTarget is not None: logSys.info("Start Fail2ban v%s", version.version) logSys.info( "Changed logging target to %s for Fail2ban v%s" @@ -588,9 +594,7 @@ class Server: # We need to set this in the parent process, so it gets inherited by the # child process, and this makes sure that it is effect even if the parent # terminates quickly. - for s in (signal.SIGHUP,): - self.__prev_signals[s] = signal.getsignal(s) - signal.signal(s, signal.SIG_IGN) + self._rebindSignal(signal.SIGHUP, signal.SIG_IGN) try: # Fork a child process so the parent can exit. This will return control From dfcf6f1963b87a7fbe87a78f47ba5c0ac40dd558 Mon Sep 17 00:00:00 2001 From: Yaroslav Halchenko Date: Tue, 12 Jul 2016 22:53:37 -0400 Subject: [PATCH 21/26] BF+TST: isStarted was not checking __asyncServer for exist before checking it --- fail2ban/server/server.py | 4 ++-- fail2ban/tests/servertestcase.py | 4 ++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/fail2ban/server/server.py b/fail2ban/server/server.py index 7edf19e9..60eea1f3 100644 --- a/fail2ban/server/server.py +++ b/fail2ban/server/server.py @@ -60,7 +60,7 @@ def _thread_name(): class Server: - def __init__(self, daemon = False): + def __init__(self, daemon=False): self.__loggingLock = Lock() self.__lock = RLock() self.__jails = Jails() @@ -377,7 +377,7 @@ class Server: return self.__jails[name].actions.getBanTime() def isStarted(self): - self.__asyncServer.isActive() + return self.__asyncServer is not None and self.__asyncServer.isActive() def isAlive(self, jailnum=None): if jailnum is not None and len(self.__jails) != jailnum: diff --git a/fail2ban/tests/servertestcase.py b/fail2ban/tests/servertestcase.py index 9fc62acd..d733da0a 100644 --- a/fail2ban/tests/servertestcase.py +++ b/fail2ban/tests/servertestcase.py @@ -158,6 +158,9 @@ class Transmitter(TransmitterBase): def setUp(self): self.server = TestServer() super(Transmitter, self).setUp() + # so far isStarted only tested but not used otherwise + # and here we don't really .start server + self.assertFalse(self.server.isStarted()) def testStopServer(self): self.assertEqual(self.transm.proceed(["stop"]), (0, None)) @@ -1004,6 +1007,7 @@ class LoggingTests(LogCaptureTestCase): server = TestServer() try: server.start(sock_name, pidfile_name, force=False) + self.assertFalse(server.isStarted()) self.assertLogged("Server already running") finally: server.quit() From ad393fcfbbdb0c491e86f891c6ee194fa0134f79 Mon Sep 17 00:00:00 2001 From: sebres Date: Wed, 13 Jul 2016 09:36:11 +0200 Subject: [PATCH 22/26] outsource assert..isStarted from setup into separate test case --- fail2ban/tests/servertestcase.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/fail2ban/tests/servertestcase.py b/fail2ban/tests/servertestcase.py index d733da0a..ab606dbe 100644 --- a/fail2ban/tests/servertestcase.py +++ b/fail2ban/tests/servertestcase.py @@ -158,6 +158,8 @@ class Transmitter(TransmitterBase): def setUp(self): self.server = TestServer() super(Transmitter, self).setUp() + + def testServerIsNotStarted(self): # so far isStarted only tested but not used otherwise # and here we don't really .start server self.assertFalse(self.server.isStarted()) From 53da35ea49b99a152f68bd4cf46efbcbea22cdb5 Mon Sep 17 00:00:00 2001 From: sebres Date: Wed, 13 Jul 2016 09:44:32 +0200 Subject: [PATCH 23/26] resolves Christmas-tree in logging (replaced with duplex space indentation) --- fail2ban/client/fail2banclient.py | 10 +++++----- fail2ban/client/fail2bancmdline.py | 4 ++-- fail2ban/client/fail2banserver.py | 6 +++--- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/fail2ban/client/fail2banclient.py b/fail2ban/client/fail2banclient.py index e3797279..4e42680b 100755 --- a/fail2ban/client/fail2banclient.py +++ b/fail2ban/client/fail2banclient.py @@ -227,11 +227,11 @@ class Fail2banClient(Fail2banCmdLine, Thread): # prepare: read config, check configuration is valid, etc.: if phase is not None: phase['start'] = True - logSys.debug('-- client phase %s', phase) + logSys.debug(' client phase %s', phase) stream = self.__prepareStartServer() if phase is not None: phase['ready'] = phase['start'] = (True if stream else False) - logSys.debug('-- client phase %s', phase) + logSys.debug(' client phase %s', phase) if not stream: return False # configure server with config stream: @@ -248,7 +248,7 @@ class Fail2banClient(Fail2banCmdLine, Thread): def __processCommand(self, cmd): if len(cmd) == 1 and cmd[0] == "start": - + ret = self.__startServer(self._conf["background"]) if not ret: return False @@ -322,7 +322,7 @@ class Fail2banClient(Fail2banCmdLine, Thread): # Wait for the server to start (the server has 30 seconds to answer ping) starttime = time.time() logSys.debug("__waitOnServer: %r", (alive, maxtime)) - test = lambda: os.path.exists(self._conf["socket"]) and self.__ping() + test = lambda: os.path.exists(self._conf["socket"]) and self.__ping() with VisualWait(self._conf["verbose"]) as vis: sltime = 0.0125 / 2 while self._alive: @@ -432,7 +432,7 @@ class _VisualWait: self.pos += self.delta if self.delta > 0: s = " #\x1b[1D" if self.pos > 1 else "# \x1b[2D" - else: + else: s = "\x1b[1D# \x1b[2D" sys.stdout.write(s) sys.stdout.flush() diff --git a/fail2ban/client/fail2bancmdline.py b/fail2ban/client/fail2bancmdline.py index aadf823a..4cb1927e 100644 --- a/fail2ban/client/fail2bancmdline.py +++ b/fail2ban/client/fail2bancmdline.py @@ -112,7 +112,7 @@ class Fail2banCmdLine(): output(" --timeout timeout to wait for the server (for internal usage only, don't read configuration)") output(" -h, --help display this help message") output(" -V, --version print the version") - + if not caller.endswith('server'): output("") output("Command:") @@ -185,7 +185,7 @@ class Fail2banCmdLine(): if ret is not None: return ret - logSys.debug("-- conf: %r, args: %r", self._conf, self._args) + logSys.debug(" conf: %r, args: %r", self._conf, self._args) if initial and PRODUCTION: # pragma: no cover - can't test verbose = self._conf["verbose"] diff --git a/fail2ban/client/fail2banserver.py b/fail2ban/client/fail2banserver.py index a511e017..9f825bf1 100644 --- a/fail2ban/client/fail2banserver.py +++ b/fail2ban/client/fail2banserver.py @@ -46,7 +46,7 @@ class Fail2banServer(Fail2banCmdLine): @staticmethod def startServerDirect(conf, daemon=True): - logSys.debug("-- direct starting of server in %s, deamon: %s", os.getpid(), daemon) + logSys.debug(" direct starting of server in %s, deamon: %s", os.getpid(), daemon) from ..server.server import Server server = None try: @@ -54,7 +54,7 @@ class Fail2banServer(Fail2banCmdLine): # server object will internally fork self if daemon is True server = Server(daemon) server.start(conf["socket"], - conf["pidfile"], conf["force"], + conf["pidfile"], conf["force"], conf=conf) except Exception as e: # pragma: no cover try: @@ -79,7 +79,7 @@ class Fail2banServer(Fail2banCmdLine): frk = not conf["async"] and PRODUCTION if frk: # pragma: no cover pid = os.fork() - logSys.debug("-- async starting of server in %s, fork: %s - %s", os.getpid(), frk, pid) + logSys.debug(" async starting of server in %s, fork: %s - %s", os.getpid(), frk, pid) if pid == 0: args = list() args.append(SERVER) From f0b2cd2e395a4b8e077d71f54d19d6e885da1f37 Mon Sep 17 00:00:00 2001 From: sebres Date: Wed, 13 Jul 2016 09:54:54 +0200 Subject: [PATCH 24/26] remove no more necessary PRODUCTION from fail2banclient.py --- fail2ban/client/fail2banclient.py | 15 ++++++--------- fail2ban/tests/fail2banclienttestcase.py | 1 - 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/fail2ban/client/fail2banclient.py b/fail2ban/client/fail2banclient.py index 4e42680b..9e6c4bd6 100755 --- a/fail2ban/client/fail2banclient.py +++ b/fail2ban/client/fail2banclient.py @@ -35,7 +35,7 @@ from ..version import version from .csocket import CSocket from .beautifier import Beautifier from .fail2bancmdline import Fail2banCmdLine, ServerExecutionException, ExitException, \ - logSys, PRODUCTION, exit, output + logSys, exit, output PROMPT = "fail2ban> " @@ -361,19 +361,16 @@ class Fail2banClient(Fail2banCmdLine, Thread): # Interactive mode if self._conf.get("interactive", False): - # no readline in test: - if PRODUCTION: # pragma: no cover - try: - import readline - except ImportError: - raise ServerExecutionException("Readline not available") + try: + import readline + except ImportError: + raise ServerExecutionException("Readline not available") try: ret = True if len(args) > 0: ret = self.__processCommand(args) if ret: - if PRODUCTION: # pragma: no cover - readline.parse_and_bind("tab: complete") + readline.parse_and_bind("tab: complete") self.dispInteractive() while True: cmd = input_command() diff --git a/fail2ban/tests/fail2banclienttestcase.py b/fail2ban/tests/fail2banclienttestcase.py index 2a18d204..ed2d3c46 100644 --- a/fail2ban/tests/fail2banclienttestcase.py +++ b/fail2ban/tests/fail2banclienttestcase.py @@ -107,7 +107,6 @@ fail2banclient.input_command = _test_input_command # prevents change logging params, log capturing, etc: fail2bancmdline.PRODUCTION = \ -fail2banclient.PRODUCTION = \ fail2banserver.PRODUCTION = False From 41e3f2e5ede8951e4e2a58085718c790aef6477c Mon Sep 17 00:00:00 2001 From: sebres Date: Thu, 14 Jul 2016 11:35:32 +0200 Subject: [PATCH 25/26] explicit flush stdout/stderr before exit, because of possible buffered output in python (echo or grep `fail2ban-client --version` can sometimes returns nothing) --- fail2ban/client/fail2bancmdline.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/fail2ban/client/fail2bancmdline.py b/fail2ban/client/fail2bancmdline.py index 4cb1927e..a14a1cf4 100644 --- a/fail2ban/client/fail2bancmdline.py +++ b/fail2ban/client/fail2bancmdline.py @@ -270,6 +270,10 @@ class Fail2banCmdLine(): @staticmethod def exit(code=0): logSys.debug("Exit with code %s", code) + # because of possible buffered output in python, we should flush it before exit: + sys.stdout.flush() + sys.stderr.flush() + # exit Fail2banCmdLine._exit(code) From 9b03a6d42e4085b42c9c635d94273a52ee58683b Mon Sep 17 00:00:00 2001 From: sebres Date: Thu, 14 Jul 2016 12:19:46 +0200 Subject: [PATCH 26/26] 0.10 pre-release: MANIFEST RELEASE and man pages updates --- MANIFEST | 30 ++++++++++------ RELEASE | 2 +- man/fail2ban-client.1 | 76 +++++++++++++++++++++++++++------------- man/fail2ban-regex.1 | 21 ++++++----- man/fail2ban-server.1 | 53 +++++++++++++++++++--------- man/fail2ban-testcases.1 | 22 +++++++++--- 6 files changed, 138 insertions(+), 66 deletions(-) diff --git a/MANIFEST b/MANIFEST index cd250d3d..05e665b2 100644 --- a/MANIFEST +++ b/MANIFEST @@ -13,9 +13,12 @@ config/action.d/complain.conf config/action.d/dshield.conf config/action.d/dummy.conf config/action.d/firewallcmd-allports.conf +config/action.d/firewallcmd-common.conf config/action.d/firewallcmd-ipset.conf config/action.d/firewallcmd-multiport.conf config/action.d/firewallcmd-new.conf +config/action.d/firewallcmd-rich-logging.conf +config/action.d/firewallcmd-rich-rules.conf config/action.d/hostsdeny.conf config/action.d/ipfilter.conf config/action.d/ipfw.conf @@ -31,6 +34,7 @@ config/action.d/iptables-new.conf config/action.d/iptables-xt_recent-echo.conf config/action.d/mail-buffered.conf config/action.d/mail.conf +config/action.d/mail-whois-common.conf config/action.d/mail-whois.conf config/action.d/mail-whois-lines.conf config/action.d/mynetwatchman.conf @@ -52,6 +56,7 @@ config/action.d/sendmail-whois-ipmatches.conf config/action.d/sendmail-whois-lines.conf config/action.d/sendmail-whois-matches.conf config/action.d/shorewall.conf +config/action.d/shorewall-ipset-proto6.conf config/action.d/smtp.py config/action.d/symbiosis-blacklist-allports.conf config/action.d/ufw.conf @@ -67,6 +72,7 @@ config/filter.d/apache-modsecurity.conf config/filter.d/apache-nohome.conf config/filter.d/apache-noscript.conf config/filter.d/apache-overflows.conf +config/filter.d/apache-pass.conf config/filter.d/apache-shellshock.conf config/filter.d/assp.conf config/filter.d/asterisk.conf @@ -79,17 +85,18 @@ config/filter.d/cyrus-imap.conf config/filter.d/directadmin.conf config/filter.d/dovecot.conf config/filter.d/dropbear.conf +config/filter.d/drupal-auth.conf config/filter.d/ejabberd-auth.conf config/filter.d/exim-common.conf config/filter.d/exim.conf config/filter.d/exim-spam.conf config/filter.d/freeswitch.conf +config/filter.d/froxlor-auth.conf config/filter.d/groupoffice.conf config/filter.d/gssftpd.conf config/filter.d/guacamole.conf config/filter.d/haproxy-http-auth.conf config/filter.d/horde.conf -config/filter.d/ignorecommands config/filter.d/ignorecommands/apache-fakegooglebot config/filter.d/kerio.conf config/filter.d/lighttpd-auth.conf @@ -122,7 +129,6 @@ config/filter.d/selinux-common.conf config/filter.d/selinux-ssh.conf config/filter.d/sendmail-auth.conf config/filter.d/sendmail-reject.conf -config/filter.d/sendmail-spam.conf config/filter.d/sieve.conf config/filter.d/sogo-auth.conf config/filter.d/solid-pop3d.conf @@ -148,7 +154,6 @@ config/paths-osx.conf CONTRIBUTING.md COPYING DEVELOP -doc/run-rootless.txt fail2ban-2to3 fail2ban/client/actionreader.py fail2ban/client/beautifier.py @@ -185,7 +190,6 @@ fail2ban/server/filterpyinotify.py fail2ban/server/filtersystemd.py fail2ban/server/__init__.py fail2ban/server/ipdns.py -fail2ban/server/iso8601.py fail2ban/server/jail.py fail2ban/server/jails.py fail2ban/server/jailthread.py @@ -203,21 +207,19 @@ fail2ban/tests/action_d/test_smtp.py fail2ban/tests/actionstestcase.py fail2ban/tests/actiontestcase.py fail2ban/tests/banmanagertestcase.py -fail2ban/tests/clientreadertestcase.py fail2ban/tests/clientbeautifiertestcase.py +fail2ban/tests/clientreadertestcase.py fail2ban/tests/config/action.d/brokenaction.conf fail2ban/tests/config/fail2ban.conf fail2ban/tests/config/filter.d/simple.conf fail2ban/tests/config/filter.d/test.conf fail2ban/tests/config/filter.d/test.local +fail2ban/tests/config/filter.d/zzz-generic-example.conf fail2ban/tests/config/jail.conf -fail2ban/tests/config/paths-common.conf -fail2ban/tests/config/paths-debian.conf -fail2ban/tests/config/paths-freebsd.conf -fail2ban/tests/config/paths-osx.conf fail2ban/tests/databasetestcase.py fail2ban/tests/datedetectortestcase.py fail2ban/tests/dummyjail.py +fail2ban/tests/fail2banclienttestcase.py fail2ban/tests/fail2banregextestcase.py fail2ban/tests/failmanagertestcase.py fail2ban/tests/files/action.d/action_checkainfo.py @@ -250,13 +252,13 @@ fail2ban/tests/files/ignorecommand.py fail2ban/tests/files/logs/3proxy fail2ban/tests/files/logs/apache-auth fail2ban/tests/files/logs/apache-badbots -fail2ban/tests/files/logs/apache-botscripts fail2ban/tests/files/logs/apache-botsearch fail2ban/tests/files/logs/apache-fakegooglebot fail2ban/tests/files/logs/apache-modsecurity fail2ban/tests/files/logs/apache-nohome fail2ban/tests/files/logs/apache-noscript fail2ban/tests/files/logs/apache-overflows +fail2ban/tests/files/logs/apache-pass fail2ban/tests/files/logs/apache-shellshock fail2ban/tests/files/logs/assp fail2ban/tests/files/logs/asterisk @@ -270,10 +272,12 @@ fail2ban/tests/files/logs/cyrus-imap fail2ban/tests/files/logs/directadmin fail2ban/tests/files/logs/dovecot fail2ban/tests/files/logs/dropbear +fail2ban/tests/files/logs/drupal-auth fail2ban/tests/files/logs/ejabberd-auth fail2ban/tests/files/logs/exim fail2ban/tests/files/logs/exim-spam fail2ban/tests/files/logs/freeswitch +fail2ban/tests/files/logs/froxlor-auth fail2ban/tests/files/logs/groupoffice fail2ban/tests/files/logs/gssftpd fail2ban/tests/files/logs/guacamole @@ -309,7 +313,6 @@ fail2ban/tests/files/logs/screensharingd fail2ban/tests/files/logs/selinux-ssh fail2ban/tests/files/logs/sendmail-auth fail2ban/tests/files/logs/sendmail-reject -fail2ban/tests/files/logs/sendmail-spam fail2ban/tests/files/logs/sieve fail2ban/tests/files/logs/sogo-auth fail2ban/tests/files/logs/solid-pop3d @@ -325,6 +328,7 @@ fail2ban/tests/files/logs/vsftpd fail2ban/tests/files/logs/webmin-auth fail2ban/tests/files/logs/wuftpd fail2ban/tests/files/logs/xinetd-fail +fail2ban/tests/files/logs/zzz-generic-example fail2ban/tests/files/testcase01.log fail2ban/tests/files/testcase02.log fail2ban/tests/files/testcase03.log @@ -356,6 +360,8 @@ files/gentoo-confd files/gentoo-initd files/ipmasq-ZZZzzz_fail2ban.rul files/logwatch/fail2ban +files/logwatch/fail2ban-0.8.log +files/logwatch/fail2ban-0.9.log files/macosx-initd files/monit/fail2ban files/nagios/check_fail2ban @@ -373,6 +379,8 @@ man/fail2ban-regex.1 man/fail2ban-regex.h2m man/fail2ban-server.1 man/fail2ban-server.h2m +man/fail2ban-testcases.1 +man/fail2ban-testcases.h2m man/generate-man man/jail.conf.5 README.md diff --git a/RELEASE b/RELEASE index fa89fc34..e570c9eb 100644 --- a/RELEASE +++ b/RELEASE @@ -190,7 +190,7 @@ Post Release Add the following to the top of the ChangeLog:: - ver. 0.9.6 (2016/XX/XXX) - wanna-be-released + ver. 0.10.0 (2016/XX/XXX) - wanna-be-released ----------- - Fixes: diff --git a/man/fail2ban-client.1 b/man/fail2ban-client.1 index ec5db2d2..0da5fb02 100644 --- a/man/fail2ban-client.1 +++ b/man/fail2ban-client.1 @@ -1,12 +1,12 @@ -.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.47.2. -.TH FAIL2BAN-CLIENT "1" "March 2016" "fail2ban-client v0.9.4" "User Commands" +.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.44.1. +.TH FAIL2BAN-CLIENT "1" "July 2016" "fail2ban-client v0.10.0a1" "User Commands" .SH NAME fail2ban-client \- configure and control the server .SH SYNOPSIS .B fail2ban-client -[\fI\,OPTIONS\/\fR] \fI\,\/\fR +[\fIOPTIONS\fR] \fI\fR .SH DESCRIPTION -Fail2Ban v0.9.4 reads log file that contains password failure report +Fail2Ban v0.10.0a1 reads log file that contains password failure report and bans the corresponding IP addresses using firewall rules. .SH OPTIONS .TP @@ -19,6 +19,13 @@ socket path \fB\-p\fR pidfile path .TP +\fB\-\-loglevel\fR +logging level +.HP +\fB\-\-logtarget\fR |STDOUT|STDERR|SYSLOG +.HP +\fB\-\-syslogsocket\fR auto| +.TP \fB\-d\fR dump configuration. For debugging .TP @@ -38,7 +45,13 @@ force execution of the server (remove socket file) start server in background (default) .TP \fB\-f\fR -start server in foreground (note that the client forks once itself) +start server in foreground +.TP +\fB\-\-async\fR +start server in async mode (for internal usage only, don't read configuration) +.TP +\fB\-\-timeout\fR +timeout to wait for the server (for internal usage only, don't read configuration) .TP \fB\-h\fR, \fB\-\-help\fR display this help message @@ -52,8 +65,12 @@ BASIC \fBstart\fR starts the server and the jails .TP +\fBrestart\fR +restarts the server +.TP \fBreload\fR -reloads the configuration +reloads the configuration without +restart .TP \fBreload \fR reloads the jail @@ -69,6 +86,10 @@ server \fBping\fR tests if the server is alive .TP +\fBecho\fR +for internal usage, returns back +and outputs a given string +.TP \fBhelp\fR return this output .TP @@ -227,8 +248,9 @@ for \fBset maxlines \fR sets the number of to buffer for regex search for -.TP -\fBset addaction [ ]\fR +.IP +set addaction [ ] +.IP adds a new action named for . Optionally for a Python based action, a and @@ -240,38 +262,45 @@ removes the action from .IP COMMAND ACTION CONFIGURATION -.TP -\fBset action actionstart \fR +.IP +set action actionstart +.IP sets the start command of the action for -.TP -\fBset action actionstop sets the stop command of the\fR +.IP +set action actionstop sets the stop command of the +.IP action for -.TP -\fBset action actioncheck \fR +.IP +set action actioncheck +.IP sets the check command of the action for .TP \fBset action actionban \fR sets the ban command of the action for -.TP -\fBset action actionunban \fR +.IP +set action actionunban +.IP sets the unban command of the action for -.TP -\fBset action timeout \fR +.IP +set action timeout +.IP sets as the command timeout in seconds for the action for .IP GENERAL ACTION CONFIGURATION -.TP -\fBset action \fR +.IP +set action +.IP sets the of for the action for -.TP -\fBset action [ ]\fR +.IP +set action [ ] +.IP calls the with for the action for @@ -376,9 +405,6 @@ gets the value of for the action for .SH FILES \fI/etc/fail2ban/*\fR -.SH AUTHOR -Written by Cyril Jaquier . -Many contributions by Yaroslav O. Halchenko . .SH "REPORTING BUGS" Report bugs to https://github.com/fail2ban/fail2ban/issues .SH COPYRIGHT diff --git a/man/fail2ban-regex.1 b/man/fail2ban-regex.1 index 740b461c..f954cb3a 100644 --- a/man/fail2ban-regex.1 +++ b/man/fail2ban-regex.1 @@ -1,10 +1,10 @@ -.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.47.2. -.TH FAIL2BAN-REGEX "1" "March 2016" "fail2ban-regex 0.9.4" "User Commands" +.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.44.1. +.TH FAIL2BAN-REGEX "1" "July 2016" "fail2ban-regex 0.10.0a1" "User Commands" .SH NAME fail2ban-regex \- test Fail2ban "failregex" option .SH SYNOPSIS .B fail2ban-regex -[\fI\,OPTIONS\/\fR] \fI\, \/\fR[\fI\,IGNOREREGEX\/\fR] +[\fIOPTIONS\fR] \fI \fR[\fIIGNOREREGEX\fR] .SH DESCRIPTION Fail2Ban reads log file that contains password failure report and bans the corresponding IP addresses using firewall rules. @@ -16,7 +16,7 @@ string a string representing a log line .TP filename -path to a log file (\fI\,/var/log/auth.log\/\fP) +path to a log file (\fI/var/log/auth.log\fP) .TP "systemd\-journal" search systemd journal (systemd\-python required) @@ -42,20 +42,23 @@ show program's version number and exit \fB\-h\fR, \fB\-\-help\fR show this help message and exit .TP -\fB\-d\fR DATEPATTERN, \fB\-\-datepattern\fR=\fI\,DATEPATTERN\/\fR +\fB\-d\fR DATEPATTERN, \fB\-\-datepattern\fR=\fIDATEPATTERN\fR set custom pattern used to match date/times .TP -\fB\-e\fR ENCODING, \fB\-\-encoding\fR=\fI\,ENCODING\/\fR +\fB\-e\fR ENCODING, \fB\-\-encoding\fR=\fIENCODING\fR File encoding. Default: system locale .TP -\fB\-L\fR MAXLINES, \fB\-\-maxlines\fR=\fI\,MAXLINES\/\fR +\fB\-r\fR, \fB\-\-raw\fR +Raw hosts, don't resolve dns +.TP +\fB\-L\fR MAXLINES, \fB\-\-maxlines\fR=\fIMAXLINES\fR maxlines for multi\-line regex .TP -\fB\-m\fR JOURNALMATCH, \fB\-\-journalmatch\fR=\fI\,JOURNALMATCH\/\fR +\fB\-m\fR JOURNALMATCH, \fB\-\-journalmatch\fR=\fIJOURNALMATCH\fR journalctl style matches overriding filter file. "systemd\-journal" only .TP -\fB\-l\fR LOG_LEVEL, \fB\-\-log\-level\fR=\fI\,LOG_LEVEL\/\fR +\fB\-l\fR LOG_LEVEL, \fB\-\-log\-level\fR=\fILOG_LEVEL\fR Log level for the Fail2Ban logger to use .TP \fB\-v\fR, \fB\-\-verbose\fR diff --git a/man/fail2ban-server.1 b/man/fail2ban-server.1 index 90ec1d80..3b970024 100644 --- a/man/fail2ban-server.1 +++ b/man/fail2ban-server.1 @@ -1,24 +1,17 @@ -.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.47.2. -.TH FAIL2BAN-SERVER "1" "March 2016" "fail2ban-server v0.9.4" "User Commands" +.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.44.1. +.TH FAIL2BAN-SERVER "1" "July 2016" "fail2ban-server v0.10.0a1" "User Commands" .SH NAME fail2ban-server \- start the server .SH SYNOPSIS .B fail2ban-server -[\fI\,OPTIONS\/\fR] +[\fIOPTIONS\fR] .SH DESCRIPTION -Fail2Ban v0.9.4 reads log file that contains password failure report +Fail2Ban v0.10.0a1 reads log file that contains password failure report and bans the corresponding IP addresses using firewall rules. -.PP -Only use this command for debugging purpose. Start the server with -fail2ban\-client instead. The default behaviour is to start the server -in background. .SH OPTIONS .TP -\fB\-b\fR -start in background -.TP -\fB\-f\fR -start in foreground +\fB\-c\fR +configuration directory .TP \fB\-s\fR socket path @@ -26,17 +19,45 @@ socket path \fB\-p\fR pidfile path .TP +\fB\-\-loglevel\fR +logging level +.HP +\fB\-\-logtarget\fR |STDOUT|STDERR|SYSLOG +.HP +\fB\-\-syslogsocket\fR auto| +.TP +\fB\-d\fR +dump configuration. For debugging +.TP +\fB\-i\fR +interactive mode +.TP +\fB\-v\fR +increase verbosity +.TP +\fB\-q\fR +decrease verbosity +.TP \fB\-x\fR force execution of the server (remove socket file) .TP +\fB\-b\fR +start server in background (default) +.TP +\fB\-f\fR +start server in foreground +.TP +\fB\-\-async\fR +start server in async mode (for internal usage only, don't read configuration) +.TP +\fB\-\-timeout\fR +timeout to wait for the server (for internal usage only, don't read configuration) +.TP \fB\-h\fR, \fB\-\-help\fR display this help message .TP \fB\-V\fR, \fB\-\-version\fR print the version -.SH AUTHOR -Written by Cyril Jaquier . -Many contributions by Yaroslav O. Halchenko . .SH "REPORTING BUGS" Report bugs to https://github.com/fail2ban/fail2ban/issues .SH COPYRIGHT diff --git a/man/fail2ban-testcases.1 b/man/fail2ban-testcases.1 index dc0fee32..9089d1ed 100644 --- a/man/fail2ban-testcases.1 +++ b/man/fail2ban-testcases.1 @@ -1,10 +1,10 @@ -.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.47.2. -.TH FAIL2BAN-TESTCASES "1" "March 2016" "fail2ban-testcases 0.9.4" "User Commands" +.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.44.1. +.TH FAIL2BAN-TESTCASES "1" "July 2016" "fail2ban-testcases 0.10.0a1" "User Commands" .SH NAME fail2ban-testcases \- run Fail2Ban unit-tests .SH SYNOPSIS .B fail2ban-testcases -[\fI\,OPTIONS\/\fR] [\fI\,regexps\/\fR] +[\fIOPTIONS\fR] [\fIregexps\fR] .SH DESCRIPTION Script to run Fail2Ban tests battery .SH OPTIONS @@ -15,12 +15,26 @@ show program's version number and exit \fB\-h\fR, \fB\-\-help\fR show this help message and exit .TP -\fB\-l\fR LOG_LEVEL, \fB\-\-log\-level\fR=\fI\,LOG_LEVEL\/\fR +\fB\-l\fR LOG_LEVEL, \fB\-\-log\-level\fR=\fILOG_LEVEL\fR Log level for the logger to use during running tests .TP \fB\-n\fR, \fB\-\-no\-network\fR Do not run tests that require the network .TP +\fB\-g\fR, \fB\-\-no\-gamin\fR +Do not run tests that require the gamin +.TP +\fB\-m\fR, \fB\-\-memory\-db\fR +Run database tests using memory instead of file +.TP +\fB\-f\fR, \fB\-\-fast\fR +Try to increase speed of the tests, decreasing of wait +intervals, memory database +.TP +\fB\-i\fR, \fB\-\-ignore\fR +negate [regexps] filter to ignore tests matched +specified regexps +.TP \fB\-t\fR, \fB\-\-log\-traceback\fR Enrich log\-messages with compressed tracebacks .TP