From e3b061e94b54067525c5e7f2ac716d1c838c9f20 Mon Sep 17 00:00:00 2001 From: sebres Date: Wed, 23 Aug 2017 12:55:36 +0200 Subject: [PATCH 1/4] - `files/fail2ban.service` renamed as template to `files/fail2ban.service.in`; - setup process generates `build/fail2ban.service` from `files/fail2ban.service.in` using distribution related bin-path; - bug-fixing by running setup with option `--dry-run` (note: specify option `--dry-run` before `install`, like `python setup.py --dry-run install`); - test cases extended to cover dry-run. --- fail2ban/tests/misctestcase.py | 20 +++++++- .../{fail2ban.service => fail2ban.service.in} | 8 ++-- setup.py | 47 ++++++++++++++++--- 3 files changed, 63 insertions(+), 12 deletions(-) rename files/{fail2ban.service => fail2ban.service.in} (70%) diff --git a/fail2ban/tests/misctestcase.py b/fail2ban/tests/misctestcase.py index 77e65d7c..a5e8fc17 100644 --- a/fail2ban/tests/misctestcase.py +++ b/fail2ban/tests/misctestcase.py @@ -101,6 +101,22 @@ class SetupTest(unittest.TestCase): "Seems to be running with python distribution %s" " -- install can be tested only with system distribution %s" % (str(tuple(sys.version_info)), sysVer)) + def testSetupInstallDryRun(self): + if not self.setup: + return # if verbose skip didn't work out + tmp = tempfile.mkdtemp() + # suppress stdout (and stderr) if not heavydebug + supdbgout = ' >/dev/null 2>&1' if unittest.F2B.log_level >= logging.DEBUG else '' # HEAVYDEBUG + try: + # try dry-run: + self.assertEqual(os.system("%s %s --dry-run install --disable-2to3 --root=%s%s" + % (sys.executable, self.setup , tmp, supdbgout)), 0) + # check nothing was created: + self.assertTrue(not os.listdir(tmp)) + finally: + # clean up + shutil.rmtree(tmp) + def testSetupInstallRoot(self): if not self.setup: return # if verbose skip didn't work out @@ -108,8 +124,8 @@ class SetupTest(unittest.TestCase): # suppress stdout (and stderr) if not heavydebug supdbgout = ' >/dev/null' if unittest.F2B.log_level >= logging.DEBUG else '' # HEAVYDEBUG try: - os.system("%s %s install --disable-2to3 --dry-run --root=%s%s" - % (sys.executable, self.setup, tmp, supdbgout)) + self.assertEqual(os.system("%s %s install --disable-2to3 --root=%s%s" + % (sys.executable, self.setup, tmp, supdbgout)), 0) def strippath(l): return [x[len(tmp)+1:] for x in l] diff --git a/files/fail2ban.service b/files/fail2ban.service.in similarity index 70% rename from files/fail2ban.service rename to files/fail2ban.service.in index 6eeba957..7114a938 100644 --- a/files/fail2ban.service +++ b/files/fail2ban.service.in @@ -7,11 +7,11 @@ PartOf=iptables.service firewalld.service [Service] Type=simple ExecStartPre=/bin/mkdir -p /var/run/fail2ban -ExecStart=/usr/bin/fail2ban-server -xf start +ExecStart=@BINDIR@/fail2ban-server -xf start # if should be logged in systemd journal, use following line or set logtarget to stdout in fail2ban.local -# ExecStart=/usr/bin/fail2ban-server -xf --logtarget=stdout start -ExecStop=/usr/bin/fail2ban-client stop -ExecReload=/usr/bin/fail2ban-client reload +# ExecStart=@BINDIR@/fail2ban-server -xf --logtarget=stdout start +ExecStop=@BINDIR@/fail2ban-client stop +ExecReload=@BINDIR@/fail2ban-client reload PIDFile=/var/run/fail2ban/fail2ban.pid Restart=on-failure RestartPreventExitStatus=0 255 diff --git a/setup.py b/setup.py index 5754db49..11748778 100755 --- a/setup.py +++ b/setup.py @@ -50,6 +50,7 @@ except ImportError: import os from os.path import isfile, join, isdir, realpath +import re import sys import warnings from glob import glob @@ -57,11 +58,25 @@ from glob import glob from fail2ban.setup import updatePyExec +source_dir = os.path.realpath(os.path.dirname( + # __file__ seems to be overwritten sometimes on some python versions (e.g. bug of 2.6 by running under cProfile, etc.): + sys.argv[0] if os.path.basename(sys.argv[0]) == 'setup.py' else __file__ +)) + # Wrapper to install python binding (to current python version): class install_scripts_f2b(install_scripts): def get_outputs(self): outputs = install_scripts.get_outputs(self) + # setup.py --dry-run install: + dry_run = not outputs + self.update_scripts(dry_run) + if dry_run: + #bindir = self.install_dir + bindir = self.build_dir + print('creating fail2ban-python binding -> %s (dry-run, real path can be different)' % (bindir,)) + print('Copying content of %s to %s' % (self.build_dir, self.install_dir)); + return outputs fn = None for fn in outputs: if os.path.basename(fn) == 'fail2ban-server': @@ -71,6 +86,27 @@ class install_scripts_f2b(install_scripts): updatePyExec(bindir) return outputs + def update_scripts(self, dry_run=False): + buildroot = os.path.dirname(self.build_dir) + print('Creating %s/fail2ban.service (from fail2ban.service.in): @BINDIR@ -> %s' % (buildroot, self.install_dir)) + with open(os.path.join(source_dir, 'files/fail2ban.service.in'), 'r') as fn: + lines = fn.readlines() + fn = None + if not dry_run: + fn = open(os.path.join(buildroot, 'fail2ban.service'), 'w') + try: + for ln in lines: + ln = re.sub(r'@BINDIR@', lambda v: self.install_dir, ln) + if dry_run: + sys.stdout.write(' | ' + ln) + continue + fn.write(ln) + finally: + if fn: fn.close() + if dry_run: + print(' `') + + # Wrapper to specify fail2ban own options: class install_command_f2b(install): user_options = install.user_options + [ @@ -94,11 +130,7 @@ class install_command_f2b(install): # Update fail2ban-python env to current python version (where f2b-modules located/installed) -rootdir = os.path.realpath(os.path.dirname( - # __file__ seems to be overwritten sometimes on some python versions (e.g. bug of 2.6 by running under cProfile, etc.): - sys.argv[0] if os.path.basename(sys.argv[0]) == 'setup.py' else __file__ -)) -updatePyExec(os.path.join(rootdir, 'bin')) +updatePyExec(os.path.join(source_dir, 'bin')) if setuptools and "test" in sys.argv: import logging @@ -270,5 +302,8 @@ if isdir("/usr/lib/fail2ban"): if sys.argv[1] == "install": print("") print("Please do not forget to update your configuration files.") - print("They are in /etc/fail2ban/.") + print("They are in \"/etc/fail2ban/\".") + print("") + print("You can also install systemd service-unit file from \"build/fail2ban.service\"") + print("resp. corresponding init script from \"files/*-initd\".") print("") From f451cf34b350c1280747c22fb869905acbd892b6 Mon Sep 17 00:00:00 2001 From: sebres Date: Wed, 23 Aug 2017 13:20:51 +0200 Subject: [PATCH 2/4] don't check return code by dry-run: returns 256 on some python/setuptool versions. --- fail2ban/tests/misctestcase.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fail2ban/tests/misctestcase.py b/fail2ban/tests/misctestcase.py index a5e8fc17..373a7178 100644 --- a/fail2ban/tests/misctestcase.py +++ b/fail2ban/tests/misctestcase.py @@ -109,8 +109,8 @@ class SetupTest(unittest.TestCase): supdbgout = ' >/dev/null 2>&1' if unittest.F2B.log_level >= logging.DEBUG else '' # HEAVYDEBUG try: # try dry-run: - self.assertEqual(os.system("%s %s --dry-run install --disable-2to3 --root=%s%s" - % (sys.executable, self.setup , tmp, supdbgout)), 0) + os.system("%s %s --dry-run install --disable-2to3 --root=%s%s" + % (sys.executable, self.setup , tmp, supdbgout)) # check nothing was created: self.assertTrue(not os.listdir(tmp)) finally: From 9b8563f35e88a764e60b61cfdf162dfaf726b0d8 Mon Sep 17 00:00:00 2001 From: sebres Date: Fri, 1 Sep 2017 09:56:21 +0200 Subject: [PATCH 3/4] - fixes regex for message `imap-login: Disconnected (auth failed, X attempts) ...` has to many variations on additional info after ``, leave it end-anchored because variable part `user=<[^>]*>` (before ``) to avoid injecting, but can be safe rewritten using `[^>]*` in opposite to "greedy" `user=<[^>]*>`. - introduces mode `aggressive` and extends regex for this mode to match: * no auth attempts (previously removed in gh-601, because of lots of false positives on misconfigured MTAs) * disconnected before auth was ready * client didn't finish SASL auth --- config/filter.d/dovecot.conf | 17 ++++++++++++++--- fail2ban/tests/files/logs/dovecot | 16 ++++++++++++++++ 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/config/filter.d/dovecot.conf b/config/filter.d/dovecot.conf index 42ab2c88..fc649d3a 100644 --- a/config/filter.d/dovecot.conf +++ b/config/filter.d/dovecot.conf @@ -13,9 +13,22 @@ _daemon = (dovecot(-auth)?|auth) prefregex = ^%(__prefix_line)s(%(_auth_worker)s(?:\([^\)]+\))?: )?(?:%(__pam_auth)s(?:\(dovecot:auth\))?: |(?:pop3|imap)-login: )?(?:Info: )?.+$ failregex = ^authentication failure; logname=\S* uid=\S* euid=\S* tty=dovecot ruser=\S* rhost=(?:\s+user=\S*)?\s*$ - ^(?:Aborted login|Disconnected)(?::(?: [^ \(]+)+)? \((?:auth failed, \d+ attempts( in \d+ secs)?|tried to use (disabled|disallowed) \S+ auth)\):( user=<[^>]*>,)?( method=\S+,)? rip=(?:, lip=\S+)?(?:, TLS(?: handshaking(?:: SSL_accept\(\) failed: error:[\dA-F]+:SSL routines:[TLS\d]+_GET_CLIENT_HELLO:unknown protocol)?)?(: Disconnected)?)?(, session=<\S+>)?\s*$ + ^(?:Aborted login|Disconnected)(?::(?: [^ \(]+)+)? \((?:auth failed, \d+ attempts(?: in \d+ secs)?|tried to use (disabled|disallowed) \S+ auth)\):( user=<[^>]*>,)?( method=\S+,)? rip=(?:[^>]*(?:, session=<\S+>)?)\s*$ ^pam\(\S+,\): pam_authenticate\(\) failed: (User not known to the underlying authentication module: \d+ Time\(s\)|Authentication failure \(password mismatch\?\))\s*$ ^[a-z\-]{3,15}\(\S*,(?:,\S*)?\): (?:unknown user|invalid credentials)\s*$ + > + +mdre-aggressive = ^(?:Aborted login|Disconnected)(?::(?: [^ \(]+)+)? \((?:no auth attempts|disconnected before auth was ready,|client didn't finish \S+ auth,)(?: (?:in|waited) \d+ secs)?\):( user=<[^>]*>,)?( method=\S+,)? rip=(?:[^>]*(?:, session=<\S+>)?)\s*$ + +mdre-normal = + +# Parameter `mode` - `normal` or `aggressive`. +# Aggressive mode can be used to match log-entries like: +# 'no auth attempts', 'disconnected before auth was ready', 'client didn't finish SASL auth'. +# Note it may produce lots of false positives on misconfigured MTAs. +# Ex.: +# filter = dovecot[mode=aggressive] +mode = normal ignoreregex = @@ -27,8 +40,6 @@ datepattern = {^LN-BEG}TAI64N # DEV Notes: # * the first regex is essentially a copy of pam-generic.conf # * Probably doesn't do dovecot sql/ldap backends properly (resolved in edit 21/03/2016) -# * Removed the 'no auth attempts' log lines from the matches because produces -# lots of false positives on misconfigured MTAs making regexp unusable # # Author: Martin Waschbuesch # Daniel Black (rewrote with begin and end anchors) diff --git a/fail2ban/tests/files/logs/dovecot b/fail2ban/tests/files/logs/dovecot index 1614ff8c..34c91d42 100644 --- a/fail2ban/tests/files/logs/dovecot +++ b/fail2ban/tests/files/logs/dovecot @@ -81,3 +81,19 @@ Mar 23 06:10:52 auth: Info: ldap(dog,52.37.139.121,): invalid credentials Jul 26 11:11:21 hostname dovecot: imap-login: Disconnected: Too many invalid commands (tried to use disallowed plaintext auth): user=, rip=192.0.2.1, lip=192.168.1.1, session= # failJSON: { "time": "2005-07-26T11:12:19", "match": true , "host": "192.0.2.2" } Jul 26 11:12:19 hostname dovecot: imap-login: Disconnected: Too many invalid commands (auth failed, 1 attempts in 17 secs): user=, method=PLAIN, rip=192.0.2.2, lip=192.168.1.1, TLS, session= + +# failJSON: { "time": "2004-08-28T06:38:51", "match": true , "host": "192.0.2.3" } +Aug 28 06:38:51 s166-62-100-187 dovecot: imap-login: Disconnected (auth failed, 1 attempts in 9 secs): user=, method=PLAIN, rip=192.0.2.3, lip=192.168.1.2, TLS: Disconnected, TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits) + +# --------------------------------------- +# Test-cases of aggressive mode: +# --------------------------------------- + +# filterOptions: [{"mode": "aggressive"}] + +# failJSON: { "time": "2004-08-29T16:06:58", "match": true , "host": "192.0.2.5" } +Aug 29 16:06:58 s166-62-100-187 dovecot: imap-login: Disconnected (disconnected before auth was ready, waited 0 secs): user=<>, rip=192.0.2.5, lip=192.168.1.2, TLS handshaking: SSL_accept() syscall failed: Connection reset by peer +# failJSON: { "time": "2004-08-31T16:15:10", "match": true , "host": "192.0.2.6" } +Aug 31 16:15:10 s166-62-100-187 dovecot: imap-login: Disconnected (client didn't finish SASL auth, waited 2 secs): user=<>, method=PLAIN, rip=192.0.2.6, lip=192.168.1.2, TLS: SSL_read() syscall failed: Connection reset by peer, TLSv1.2 with cipher DHE-RSA-AES256-GCM-SHA384 (256/256 bits) +# failJSON: { "time": "2004-08-31T16:21:53", "match": true , "host": "192.0.2.7" } +Aug 31 16:21:53 s166-62-100-187 dovecot: imap-login: Disconnected (no auth attempts in 4 secs): user=<>, rip=192.0.2.7, lip=192.168.1.2, TLS handshaking: SSL_accept() syscall failed: Connection reset by peer From 2cfc53c08efcbc9b1d1f5c5da6d719b3867618ce Mon Sep 17 00:00:00 2001 From: sebres Date: Fri, 1 Sep 2017 10:25:09 +0200 Subject: [PATCH 4/4] remove capturing groups --- config/filter.d/dovecot.conf | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/config/filter.d/dovecot.conf b/config/filter.d/dovecot.conf index fc649d3a..4df7c752 100644 --- a/config/filter.d/dovecot.conf +++ b/config/filter.d/dovecot.conf @@ -8,17 +8,17 @@ before = common.conf [Definition] _auth_worker = (?:dovecot: )?auth(?:-worker)? -_daemon = (dovecot(-auth)?|auth) +_daemon = (?:dovecot(?:-auth)?|auth) -prefregex = ^%(__prefix_line)s(%(_auth_worker)s(?:\([^\)]+\))?: )?(?:%(__pam_auth)s(?:\(dovecot:auth\))?: |(?:pop3|imap)-login: )?(?:Info: )?.+$ +prefregex = ^%(__prefix_line)s(?:%(_auth_worker)s(?:\([^\)]+\))?: )?(?:%(__pam_auth)s(?:\(dovecot:auth\))?: |(?:pop3|imap)-login: )?(?:Info: )?.+$ failregex = ^authentication failure; logname=\S* uid=\S* euid=\S* tty=dovecot ruser=\S* rhost=(?:\s+user=\S*)?\s*$ - ^(?:Aborted login|Disconnected)(?::(?: [^ \(]+)+)? \((?:auth failed, \d+ attempts(?: in \d+ secs)?|tried to use (disabled|disallowed) \S+ auth)\):( user=<[^>]*>,)?( method=\S+,)? rip=(?:[^>]*(?:, session=<\S+>)?)\s*$ - ^pam\(\S+,\): pam_authenticate\(\) failed: (User not known to the underlying authentication module: \d+ Time\(s\)|Authentication failure \(password mismatch\?\))\s*$ + ^(?:Aborted login|Disconnected)(?::(?: [^ \(]+)+)? \((?:auth failed, \d+ attempts(?: in \d+ secs)?|tried to use (?:disabled|disallowed) \S+ auth)\):(?: user=<[^>]*>,)?(?: method=\S+,)? rip=(?:[^>]*(?:, session=<\S+>)?)\s*$ + ^pam\(\S+,\): pam_authenticate\(\) failed: (?:User not known to the underlying authentication module: \d+ Time\(s\)|Authentication failure \(password mismatch\?\))\s*$ ^[a-z\-]{3,15}\(\S*,(?:,\S*)?\): (?:unknown user|invalid credentials)\s*$ > -mdre-aggressive = ^(?:Aborted login|Disconnected)(?::(?: [^ \(]+)+)? \((?:no auth attempts|disconnected before auth was ready,|client didn't finish \S+ auth,)(?: (?:in|waited) \d+ secs)?\):( user=<[^>]*>,)?( method=\S+,)? rip=(?:[^>]*(?:, session=<\S+>)?)\s*$ +mdre-aggressive = ^(?:Aborted login|Disconnected)(?::(?: [^ \(]+)+)? \((?:no auth attempts|disconnected before auth was ready,|client didn't finish \S+ auth,)(?: (?:in|waited) \d+ secs)?\):(?: user=<[^>]*>,)?(?: method=\S+,)? rip=(?:[^>]*(?:, session=<\S+>)?)\s*$ mdre-normal =