mirror of https://github.com/fail2ban/fail2ban
remove support of python 2.x
parent
03d7c92ae8
commit
99ff701678
|
@ -22,7 +22,7 @@ jobs:
|
|||
runs-on: ubuntu-20.04
|
||||
strategy:
|
||||
matrix:
|
||||
python-version: [2.7, 3.6, 3.7, 3.8, 3.9, '3.10', '3.11', pypy2, pypy3]
|
||||
python-version: [3.6, 3.7, 3.8, 3.9, '3.10', '3.11', pypy3]
|
||||
fail-fast: false
|
||||
# Steps represent a sequence of tasks that will be executed as part of the job
|
||||
steps:
|
||||
|
@ -51,11 +51,7 @@ jobs:
|
|||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
if [[ "$F2B_PY" = 3 ]]; then python -m pip install --upgrade pip || echo "can't upgrade pip"; fi
|
||||
if [[ "$F2B_PY" = 3 ]] && ! command -v 2to3x -v 2to3 > /dev/null; then
|
||||
#pip install 2to3
|
||||
sudo apt-get -y install 2to3
|
||||
fi
|
||||
#if [[ "$F2B_PY" = 3 ]]; then python -m pip install --upgrade pip || echo "can't upgrade pip"; fi
|
||||
#sudo apt-get -y install python${F2B_PY/2/}-pyinotify || echo 'inotify not available'
|
||||
python -m pip install pyinotify || echo 'inotify not available'
|
||||
#sudo apt-get -y install python${F2B_PY/2/}-systemd || echo 'systemd not available'
|
||||
|
@ -67,8 +63,6 @@ jobs:
|
|||
- name: Before scripts
|
||||
run: |
|
||||
cd "$GITHUB_WORKSPACE"
|
||||
# Manually execute 2to3 for now
|
||||
if [[ "$F2B_PY" = 3 ]]; then echo "2to3 ..." && ./fail2ban-2to3; fi
|
||||
_debug() { echo -n "$1 "; err=$("${@:2}" 2>&1) && echo 'OK' || echo -e "FAIL\n$err"; }
|
||||
# (debug) output current preferred encoding:
|
||||
_debug 'Encodings:' python -c 'import locale, sys; from fail2ban.helpers import PREFER_ENC; print(PREFER_ENC, locale.getpreferredencoding(), (sys.stdout and sys.stdout.encoding))'
|
||||
|
@ -80,9 +74,8 @@ jobs:
|
|||
|
||||
- name: Test suite
|
||||
run: |
|
||||
if [[ "$F2B_PY" = 2 ]]; then
|
||||
python setup.py test
|
||||
elif dpkg --compare-versions "$F2B_PYV" lt 3.10; then
|
||||
#python setup.py test
|
||||
if dpkg --compare-versions "$F2B_PYV" lt 3.10; then
|
||||
python bin/fail2ban-testcases --verbosity=2
|
||||
else
|
||||
echo "Skip systemd backend since systemd-python module must be fixed for python >= v.3.10 in GHA ..."
|
||||
|
|
10
.travis.yml
10
.travis.yml
|
@ -10,10 +10,6 @@ dist: xenial
|
|||
matrix:
|
||||
fast_finish: true
|
||||
include:
|
||||
- python: 2.7
|
||||
#- python: pypy
|
||||
- python: 3.4
|
||||
- python: 3.5
|
||||
- python: 3.6
|
||||
- python: 3.7
|
||||
- python: 3.8
|
||||
|
@ -39,20 +35,14 @@ install:
|
|||
# codecov:
|
||||
- travis_retry pip install codecov
|
||||
# dnspython or dnspython3
|
||||
- if [[ "$F2B_PY" = 2 ]]; then travis_retry pip install dnspython || echo 'not installed'; fi
|
||||
- if [[ "$F2B_PY" = 3 ]]; then travis_retry pip install dnspython3 || echo 'not installed'; fi
|
||||
# python systemd bindings:
|
||||
- if [[ "$F2B_PY" = 2 ]]; then travis_retry sudo apt-get install -qq python-systemd || echo 'not installed'; fi
|
||||
- if [[ "$F2B_PY" = 3 ]]; then travis_retry sudo apt-get install -qq python3-systemd || echo 'not installed'; fi
|
||||
# gamin - install manually (not in PyPI) - travis-ci system Python is 2.7
|
||||
- if [[ $TRAVIS_PYTHON_VERSION == 2.7 ]]; then (travis_retry sudo apt-get install -qq python-gamin && cp /usr/share/pyshared/gamin.py /usr/lib/pyshared/python2.7/_gamin.so $VIRTUAL_ENV/lib/python2.7/site-packages/) || echo 'not installed'; fi
|
||||
# pyinotify
|
||||
- travis_retry pip install pyinotify || echo 'not installed'
|
||||
# Install helper tools
|
||||
- sudo apt-get install shellcheck
|
||||
before_script:
|
||||
# Manually execute 2to3 for now
|
||||
- if [[ "$F2B_PY" = 3 ]]; then ./fail2ban-2to3; fi
|
||||
# (debug) output current preferred encoding:
|
||||
- python -c 'import locale, sys; from fail2ban.helpers import PREFER_ENC; print(PREFER_ENC, locale.getpreferredencoding(), (sys.stdout and sys.stdout.encoding))'
|
||||
script:
|
||||
|
|
2
MANIFEST
2
MANIFEST
|
@ -175,7 +175,6 @@ CONTRIBUTING.md
|
|||
COPYING
|
||||
.coveragerc
|
||||
DEVELOP
|
||||
fail2ban-2to3
|
||||
fail2ban/client/actionreader.py
|
||||
fail2ban/client/beautifier.py
|
||||
fail2ban/client/configparserinc.py
|
||||
|
@ -204,7 +203,6 @@ fail2ban/server/datedetector.py
|
|||
fail2ban/server/datetemplate.py
|
||||
fail2ban/server/failmanager.py
|
||||
fail2ban/server/failregex.py
|
||||
fail2ban/server/filtergamin.py
|
||||
fail2ban/server/filterpoll.py
|
||||
fail2ban/server/filter.py
|
||||
fail2ban/server/filterpyinotify.py
|
||||
|
|
|
@ -34,13 +34,12 @@ Fail2Ban is likely already packaged for your Linux distribution and [can install
|
|||
If your distribution is not listed, you can install from GitHub:
|
||||
|
||||
Required:
|
||||
- [Python2 >= 2.7 or Python >= 3.2](https://www.python.org) or [PyPy](https://pypy.org)
|
||||
- python-setuptools, python-distutils or python3-setuptools for installation from source
|
||||
- [Python >= 3.5](https://www.python.org) or [PyPy3](https://pypy.org)
|
||||
- python-setuptools, python-distutils (or python3-setuptools) for installation from source
|
||||
|
||||
Optional:
|
||||
- [pyinotify >= 0.8.3](https://github.com/seb-m/pyinotify), may require:
|
||||
* Linux >= 2.6.13
|
||||
- [gamin >= 0.0.21](http://www.gnome.org/~veillard/gamin)
|
||||
- [systemd >= 204](http://www.freedesktop.org/wiki/Software/systemd) and python bindings:
|
||||
* [python-systemd package](https://www.freedesktop.org/software/systemd/python-systemd/index.html)
|
||||
- [dnspython](http://www.dnspython.org/)
|
||||
|
|
|
@ -113,19 +113,17 @@ maxretry = 5
|
|||
maxmatches = %(maxretry)s
|
||||
|
||||
# "backend" specifies the backend used to get files modification.
|
||||
# Available options are "pyinotify", "gamin", "polling", "systemd" and "auto".
|
||||
# Available options are "pyinotify", "polling", "systemd" and "auto".
|
||||
# This option can be overridden in each jail as well.
|
||||
#
|
||||
# pyinotify: requires pyinotify (a file alteration monitor) to be installed.
|
||||
# If pyinotify is not installed, Fail2ban will use auto.
|
||||
# gamin: requires Gamin (a file alteration monitor) to be installed.
|
||||
# If Gamin is not installed, Fail2ban will use auto.
|
||||
# polling: uses a polling algorithm which does not require external libraries.
|
||||
# systemd: uses systemd python library to access the systemd journal.
|
||||
# Specifying "logpath" is not valid for this backend.
|
||||
# See "journalmatch" in the jails associated filter config
|
||||
# auto: will try to use the following backends, in order:
|
||||
# pyinotify, gamin, polling.
|
||||
# pyinotify, polling.
|
||||
#
|
||||
# Note: if systemd backend is chosen as the default but you enable a jail
|
||||
# for which logs are present only in its own log files, specify some other
|
||||
|
|
|
@ -1,7 +0,0 @@
|
|||
fail2ban.server.filtergamin module
|
||||
==================================
|
||||
|
||||
.. automodule:: fail2ban.server.filtergamin
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
|
@ -13,7 +13,6 @@ fail2ban.server package
|
|||
fail2ban.server.failmanager
|
||||
fail2ban.server.failregex
|
||||
fail2ban.server.filter
|
||||
fail2ban.server.filtergamin
|
||||
fail2ban.server.filterpoll
|
||||
fail2ban.server.filterpyinotify
|
||||
fail2ban.server.filtersystemd
|
||||
|
|
|
@ -1,14 +0,0 @@
|
|||
#!/bin/bash
|
||||
# This script carries out conversion of fail2ban to python3
|
||||
# A backup of any converted files are created with ".bak"
|
||||
# extension
|
||||
|
||||
set -eu
|
||||
|
||||
if 2to3 -w --no-diffs bin/* fail2ban;then
|
||||
echo "Success!" >&2
|
||||
exit 0
|
||||
else
|
||||
echo "Fail!" >&2
|
||||
exit 1
|
||||
fi
|
|
@ -29,49 +29,36 @@ import re
|
|||
import sys
|
||||
from ..helpers import getLogger
|
||||
|
||||
if sys.version_info >= (3,): # pragma: 2.x no cover
|
||||
# SafeConfigParser deprecated from Python 3.2 (renamed to ConfigParser)
|
||||
from configparser import ConfigParser as SafeConfigParser, BasicInterpolation, \
|
||||
InterpolationMissingOptionError, NoOptionError, NoSectionError
|
||||
|
||||
# SafeConfigParser deprecated from Python 3.2 (renamed to ConfigParser)
|
||||
from configparser import ConfigParser as SafeConfigParser, BasicInterpolation, \
|
||||
InterpolationMissingOptionError, NoOptionError, NoSectionError
|
||||
# And interpolation of __name__ was simply removed, thus we need to
|
||||
# decorate default interpolator to handle it
|
||||
class BasicInterpolationWithName(BasicInterpolation):
|
||||
"""Decorator to bring __name__ interpolation back.
|
||||
|
||||
# And interpolation of __name__ was simply removed, thus we need to
|
||||
# decorate default interpolator to handle it
|
||||
class BasicInterpolationWithName(BasicInterpolation):
|
||||
"""Decorator to bring __name__ interpolation back.
|
||||
Original handling of __name__ was removed because of
|
||||
functional deficiencies: http://bugs.python.org/issue10489
|
||||
|
||||
Original handling of __name__ was removed because of
|
||||
functional deficiencies: http://bugs.python.org/issue10489
|
||||
commit v3.2a4-105-g61f2761
|
||||
Author: Lukasz Langa <lukasz@langa.pl>
|
||||
Date: Sun Nov 21 13:41:35 2010 +0000
|
||||
|
||||
commit v3.2a4-105-g61f2761
|
||||
Author: Lukasz Langa <lukasz@langa.pl>
|
||||
Date: Sun Nov 21 13:41:35 2010 +0000
|
||||
Issue #10489: removed broken `__name__` support from configparser
|
||||
|
||||
Issue #10489: removed broken `__name__` support from configparser
|
||||
But should be fine to reincarnate for our use case
|
||||
"""
|
||||
def _interpolate_some(self, parser, option, accum, rest, section, map,
|
||||
*args, **kwargs):
|
||||
if section and not (__name__ in map):
|
||||
map = map.copy() # just to be safe
|
||||
map['__name__'] = section
|
||||
# try to wrap section options like %(section/option)s:
|
||||
parser._map_section_options(section, option, rest, map)
|
||||
return super(BasicInterpolationWithName, self)._interpolate_some(
|
||||
parser, option, accum, rest, section, map, *args, **kwargs)
|
||||
|
||||
But should be fine to reincarnate for our use case
|
||||
"""
|
||||
def _interpolate_some(self, parser, option, accum, rest, section, map,
|
||||
*args, **kwargs):
|
||||
if section and not (__name__ in map):
|
||||
map = map.copy() # just to be safe
|
||||
map['__name__'] = section
|
||||
# try to wrap section options like %(section/option)s:
|
||||
parser._map_section_options(section, option, rest, map)
|
||||
return super(BasicInterpolationWithName, self)._interpolate_some(
|
||||
parser, option, accum, rest, section, map, *args, **kwargs)
|
||||
|
||||
else: # pragma: 3.x no cover
|
||||
from configparser import SafeConfigParser, \
|
||||
InterpolationMissingOptionError, NoOptionError, NoSectionError
|
||||
|
||||
# Interpolate missing known/option as option from default section
|
||||
SafeConfigParser._cp_interpolate_some = SafeConfigParser._interpolate_some
|
||||
def _interpolate_some(self, option, accum, rest, section, map, *args, **kwargs):
|
||||
# try to wrap section options like %(section/option)s:
|
||||
self._map_section_options(section, option, rest, map)
|
||||
return self._cp_interpolate_some(option, accum, rest, section, map, *args, **kwargs)
|
||||
SafeConfigParser._interpolate_some = _interpolate_some
|
||||
|
||||
def _expandConfFilesWithLocal(filenames):
|
||||
"""Expands config files with local extension.
|
||||
|
|
|
@ -48,30 +48,6 @@ if PREFER_ENC.startswith('ANSI_'): # pragma: no cover
|
|||
elif all((os.getenv(v) in (None, "") for v in ('LANGUAGE', 'LC_ALL', 'LC_CTYPE', 'LANG'))):
|
||||
PREFER_ENC = 'UTF-8';
|
||||
|
||||
# py-2.x: try to minimize influence of sporadic conversion errors on python 2.x,
|
||||
# caused by implicit converting of string/unicode (e. g. `str(u"\uFFFD")` produces an error
|
||||
# if default encoding is 'ascii');
|
||||
if sys.version_info < (3,): # pragma: 3.x no cover
|
||||
# correct default (global system) encoding (mostly UTF-8):
|
||||
def __resetDefaultEncoding(encoding):
|
||||
global PREFER_ENC
|
||||
ode = sys.getdefaultencoding().upper()
|
||||
if ode == 'ASCII' and ode != PREFER_ENC.upper():
|
||||
# setdefaultencoding is normally deleted after site initialized, so hack-in using load of sys-module:
|
||||
_sys = sys
|
||||
if not hasattr(_sys, "setdefaultencoding"):
|
||||
try:
|
||||
from imp import load_dynamic as __ldm
|
||||
_sys = __ldm('_sys', 'sys')
|
||||
except ImportError: # pragma: no cover - only if load_dynamic fails
|
||||
importlib.reload(sys)
|
||||
_sys = sys
|
||||
if hasattr(_sys, "setdefaultencoding"):
|
||||
_sys.setdefaultencoding(encoding)
|
||||
# override to PREFER_ENC:
|
||||
__resetDefaultEncoding(PREFER_ENC)
|
||||
del __resetDefaultEncoding
|
||||
|
||||
# todo: rewrite explicit (and implicit) str-conversions via encode/decode with IO-encoding (sys.stdout.encoding),
|
||||
# e. g. inside tags-replacement by command-actions, etc.
|
||||
|
||||
|
@ -85,41 +61,21 @@ if sys.version_info < (3,): # pragma: 3.x no cover
|
|||
# [True, True, False]; # -- python2
|
||||
# [True, False, True]; # -- python3
|
||||
#
|
||||
if sys.version_info >= (3,): # pragma: 2.x no cover
|
||||
def uni_decode(x, enc=PREFER_ENC, errors='strict'):
|
||||
try:
|
||||
if isinstance(x, bytes):
|
||||
return x.decode(enc, errors)
|
||||
return x
|
||||
except (UnicodeDecodeError, UnicodeEncodeError): # pragma: no cover - unsure if reachable
|
||||
if errors != 'strict':
|
||||
raise
|
||||
return x.decode(enc, 'replace')
|
||||
def uni_string(x):
|
||||
if not isinstance(x, bytes):
|
||||
return str(x)
|
||||
return x.decode(PREFER_ENC, 'replace')
|
||||
def uni_bytes(x):
|
||||
return bytes(x, 'UTF-8')
|
||||
else: # pragma: 3.x no cover
|
||||
def uni_decode(x, enc=PREFER_ENC, errors='strict'):
|
||||
try:
|
||||
if isinstance(x, str):
|
||||
return x.encode(enc, errors)
|
||||
return x
|
||||
except (UnicodeDecodeError, UnicodeEncodeError): # pragma: no cover - unsure if reachable
|
||||
if errors != 'strict':
|
||||
raise
|
||||
return x.encode(enc, 'replace')
|
||||
if sys.getdefaultencoding().upper() != 'UTF-8': # pragma: no cover - utf-8 is default encoding now
|
||||
def uni_string(x):
|
||||
if not isinstance(x, str):
|
||||
return str(x)
|
||||
return x.encode(PREFER_ENC, 'replace')
|
||||
else:
|
||||
uni_string = str
|
||||
uni_bytes = bytes
|
||||
|
||||
def uni_decode(x, enc=PREFER_ENC, errors='strict'):
|
||||
try:
|
||||
if isinstance(x, bytes):
|
||||
return x.decode(enc, errors)
|
||||
return x
|
||||
except (UnicodeDecodeError, UnicodeEncodeError): # pragma: no cover - unsure if reachable
|
||||
if errors != 'strict':
|
||||
raise
|
||||
return x.decode(enc, 'replace')
|
||||
def uni_string(x):
|
||||
if not isinstance(x, bytes):
|
||||
return str(x)
|
||||
return x.decode(PREFER_ENC, 'replace')
|
||||
def uni_bytes(x):
|
||||
return bytes(x, 'UTF-8')
|
||||
|
||||
def _as_bool(val):
|
||||
return bool(val) if not isinstance(val, str) \
|
||||
|
@ -227,10 +183,7 @@ def __stopOnIOError(logSys=None, logHndlr=None): # pragma: no cover
|
|||
pass
|
||||
sys.exit(0)
|
||||
|
||||
try:
|
||||
BrokenPipeError = BrokenPipeError
|
||||
except NameError: # pragma: 3.x no cover
|
||||
BrokenPipeError = IOError
|
||||
BrokenPipeError = BrokenPipeError
|
||||
|
||||
__origLog = logging.Logger._log
|
||||
def __safeLog(self, level, msg, args, **kwargs):
|
||||
|
@ -333,36 +286,17 @@ def splitwords(s):
|
|||
return []
|
||||
return list(filter(bool, [v.strip() for v in re.split('[ ,\n]+', s)]))
|
||||
|
||||
if sys.version_info >= (3,5):
|
||||
eval(compile(r'''if 1:
|
||||
def _merge_dicts(x, y):
|
||||
"""Helper to merge dicts.
|
||||
"""
|
||||
if y:
|
||||
return {**x, **y}
|
||||
return x
|
||||
|
||||
def _merge_copy_dicts(x, y):
|
||||
"""Helper to merge dicts to guarantee a copy result (r is never x).
|
||||
"""
|
||||
def _merge_dicts(x, y):
|
||||
"""Helper to merge dicts.
|
||||
"""
|
||||
if y:
|
||||
return {**x, **y}
|
||||
''', __file__, 'exec'))
|
||||
else:
|
||||
def _merge_dicts(x, y):
|
||||
"""Helper to merge dicts.
|
||||
"""
|
||||
r = x
|
||||
if y:
|
||||
r = x.copy()
|
||||
r.update(y)
|
||||
return r
|
||||
def _merge_copy_dicts(x, y):
|
||||
"""Helper to merge dicts to guarantee a copy result (r is never x).
|
||||
"""
|
||||
r = x.copy()
|
||||
if y:
|
||||
r.update(y)
|
||||
return r
|
||||
return x
|
||||
|
||||
def _merge_copy_dicts(x, y):
|
||||
"""Helper to merge dicts to guarantee a copy result (r is never x).
|
||||
"""
|
||||
return {**x, **y}
|
||||
|
||||
#
|
||||
# Following function used for parse options from parameter (e.g. `name[p1=0, p2="..."][p3='...']`).
|
||||
|
@ -521,10 +455,7 @@ if _libcap:
|
|||
Side effect: name can be silently truncated to 15 bytes (16 bytes with NTS zero)
|
||||
"""
|
||||
try:
|
||||
if sys.version_info >= (3,): # pragma: 2.x no cover
|
||||
name = name.encode()
|
||||
else: # pragma: 3.x no cover
|
||||
name = bytes(name)
|
||||
name = name.encode()
|
||||
_libcap.prctl(15, name) # PR_SET_NAME = 15
|
||||
except: # pragma: no cover
|
||||
pass
|
||||
|
|
|
@ -45,55 +45,24 @@ def _json_default(x):
|
|||
x = list(x)
|
||||
return uni_string(x)
|
||||
|
||||
if sys.version_info >= (3,): # pragma: 2.x no cover
|
||||
def _json_dumps_safe(x):
|
||||
try:
|
||||
x = json.dumps(x, ensure_ascii=False, default=_json_default).encode(
|
||||
PREFER_ENC, 'replace')
|
||||
except Exception as e:
|
||||
# adapter handler should be exception-safe
|
||||
logSys.error('json dumps failed: %r', e, exc_info=logSys.getEffectiveLevel() <= 4)
|
||||
x = '{}'
|
||||
return x
|
||||
def _json_dumps_safe(x):
|
||||
try:
|
||||
x = json.dumps(x, ensure_ascii=False, default=_json_default).encode(
|
||||
PREFER_ENC, 'replace')
|
||||
except Exception as e:
|
||||
# adapter handler should be exception-safe
|
||||
logSys.error('json dumps failed: %r', e, exc_info=logSys.getEffectiveLevel() <= 4)
|
||||
x = '{}'
|
||||
return x
|
||||
|
||||
def _json_loads_safe(x):
|
||||
try:
|
||||
x = json.loads(x.decode(PREFER_ENC, 'replace'))
|
||||
except Exception as e:
|
||||
# converter handler should be exception-safe
|
||||
logSys.error('json loads failed: %r', e, exc_info=logSys.getEffectiveLevel() <= 4)
|
||||
x = {}
|
||||
return x
|
||||
else: # pragma: 3.x no cover
|
||||
def _normalize(x):
|
||||
if isinstance(x, dict):
|
||||
return dict((_normalize(k), _normalize(v)) for k, v in x.items())
|
||||
elif isinstance(x, (list, set)):
|
||||
return [_normalize(element) for element in x]
|
||||
elif isinstance(x, str):
|
||||
# in 2.x default text_factory is unicode - so return proper unicode here:
|
||||
return x.encode(PREFER_ENC, 'replace').decode(PREFER_ENC)
|
||||
elif isinstance(x, str):
|
||||
return x.decode(PREFER_ENC, 'replace')
|
||||
return x
|
||||
|
||||
def _json_dumps_safe(x):
|
||||
try:
|
||||
x = json.dumps(_normalize(x), ensure_ascii=False, default=_json_default)
|
||||
except Exception as e:
|
||||
# adapter handler should be exception-safe
|
||||
logSys.error('json dumps failed: %r', e, exc_info=logSys.getEffectiveLevel() <= 4)
|
||||
x = '{}'
|
||||
return x
|
||||
|
||||
def _json_loads_safe(x):
|
||||
try:
|
||||
x = json.loads(x.decode(PREFER_ENC, 'replace'))
|
||||
except Exception as e:
|
||||
# converter handler should be exception-safe
|
||||
logSys.error('json loads failed: %r', e, exc_info=logSys.getEffectiveLevel() <= 4)
|
||||
x = {}
|
||||
return x
|
||||
def _json_loads_safe(x):
|
||||
try:
|
||||
x = json.loads(x.decode(PREFER_ENC, 'replace'))
|
||||
except Exception as e:
|
||||
# converter handler should be exception-safe
|
||||
logSys.error('json loads failed: %r', e, exc_info=logSys.getEffectiveLevel() <= 4)
|
||||
x = {}
|
||||
return x
|
||||
|
||||
sqlite3.register_adapter(dict, _json_dumps_safe)
|
||||
sqlite3.register_converter("JSON", _json_loads_safe)
|
||||
|
|
|
@ -1,136 +0,0 @@
|
|||
# emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: t -*-
|
||||
# vi: set ft=python sts=4 ts=4 sw=4 noet :
|
||||
|
||||
# This file is part of Fail2Ban.
|
||||
#
|
||||
# Fail2Ban is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Fail2Ban is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Fail2Ban; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
|
||||
# Author: Cyril Jaquier, Yaroslav Halchenko
|
||||
|
||||
__author__ = "Cyril Jaquier, Yaroslav Halchenko"
|
||||
__copyright__ = "Copyright (c) 2004 Cyril Jaquier, 2012 Yaroslav Halchenko"
|
||||
__license__ = "GPL"
|
||||
|
||||
import fcntl
|
||||
import time
|
||||
|
||||
import gamin
|
||||
|
||||
from .failmanager import FailManagerEmpty
|
||||
from .filter import FileFilter
|
||||
from .mytime import MyTime
|
||||
from .utils import Utils
|
||||
from ..helpers import getLogger
|
||||
|
||||
# Gets the instance of the logger.
|
||||
logSys = getLogger(__name__)
|
||||
|
||||
|
||||
##
|
||||
# Log reader class.
|
||||
#
|
||||
# This class reads a log file and detects login failures or anything else
|
||||
# that matches a given regular expression. This class is instanciated by
|
||||
# a Jail object.
|
||||
|
||||
class FilterGamin(FileFilter):
|
||||
|
||||
##
|
||||
# Constructor.
|
||||
#
|
||||
# Initialize the filter object with default values.
|
||||
# @param jail the jail object
|
||||
|
||||
def __init__(self, jail):
|
||||
FileFilter.__init__(self, jail)
|
||||
# Gamin monitor
|
||||
self.monitor = gamin.WatchMonitor()
|
||||
fd = self.monitor.get_fd()
|
||||
flags = fcntl.fcntl(fd, fcntl.F_GETFD)
|
||||
fcntl.fcntl(fd, fcntl.F_SETFD, flags|fcntl.FD_CLOEXEC)
|
||||
logSys.debug("Created FilterGamin")
|
||||
|
||||
def callback(self, path, event):
|
||||
logSys.log(4, "Got event: " + repr(event) + " for " + path)
|
||||
if event in (gamin.GAMCreated, gamin.GAMChanged, gamin.GAMExists):
|
||||
logSys.debug("File changed: " + path)
|
||||
|
||||
self.ticks += 1
|
||||
self.getFailures(path)
|
||||
|
||||
##
|
||||
# Add a log file path
|
||||
#
|
||||
# @param path log file path
|
||||
|
||||
def _addLogPath(self, path):
|
||||
self.monitor.watch_file(path, self.callback)
|
||||
|
||||
##
|
||||
# Delete a log path
|
||||
#
|
||||
# @param path the log file to delete
|
||||
|
||||
def _delLogPath(self, path):
|
||||
self.monitor.stop_watch(path)
|
||||
|
||||
def _handleEvents(self):
|
||||
ret = False
|
||||
mon = self.monitor
|
||||
while mon and mon.event_pending() > 0:
|
||||
mon.handle_events()
|
||||
mon = self.monitor
|
||||
ret = True
|
||||
return ret
|
||||
|
||||
##
|
||||
# Main loop.
|
||||
#
|
||||
# This function is the main loop of the thread. It checks if the
|
||||
# file has been modified and looks for failures.
|
||||
# @return True when the thread exits nicely
|
||||
|
||||
def run(self):
|
||||
# Gamin needs a loop to collect and dispatch events
|
||||
while self.active:
|
||||
if self.idle:
|
||||
# wait a little bit here for not idle, to prevent hi-load:
|
||||
if not Utils.wait_for(lambda: not self.active or not self.idle,
|
||||
self.sleeptime * 10, self.sleeptime
|
||||
):
|
||||
self.ticks += 1
|
||||
continue
|
||||
Utils.wait_for(lambda: not self.active or self._handleEvents(),
|
||||
self.sleeptime)
|
||||
self.ticks += 1
|
||||
if self.ticks % 10 == 0:
|
||||
self.performSvc()
|
||||
|
||||
logSys.debug("[%s] filter terminated", self.jailName)
|
||||
return True
|
||||
|
||||
def stop(self):
|
||||
super(FilterGamin, self).stop()
|
||||
self.__cleanup()
|
||||
|
||||
##
|
||||
# Desallocates the resources used by Gamin.
|
||||
|
||||
def __cleanup(self):
|
||||
if not self.monitor:
|
||||
return
|
||||
for filename in self.getLogPaths():
|
||||
self.monitor.stop_watch(filename)
|
||||
self.monitor = None
|
|
@ -66,7 +66,7 @@ class Jail(object):
|
|||
#Known backends. Each backend should have corresponding __initBackend method
|
||||
# yoh: stored in a list instead of a tuple since only
|
||||
# list had .index until 2.6
|
||||
_BACKENDS = ['pyinotify', 'gamin', 'polling', 'systemd']
|
||||
_BACKENDS = ['pyinotify', 'polling', 'systemd']
|
||||
|
||||
def __init__(self, name, backend = "auto", db=None):
|
||||
self.__db = db
|
||||
|
@ -131,12 +131,6 @@ class Jail(object):
|
|||
logSys.info("Jail '%s' uses poller %r" % (self.name, kwargs))
|
||||
self.__filter = FilterPoll(self, **kwargs)
|
||||
|
||||
def _initGamin(self, **kwargs):
|
||||
# Try to import gamin
|
||||
from .filtergamin import FilterGamin
|
||||
logSys.info("Jail '%s' uses Gamin %r" % (self.name, kwargs))
|
||||
self.__filter = FilterGamin(self, **kwargs)
|
||||
|
||||
def _initPyinotify(self, **kwargs):
|
||||
# Try to import pyinotify
|
||||
from .filterpyinotify import FilterPyinotify
|
||||
|
|
|
@ -78,14 +78,9 @@ class JailThread(Thread):
|
|||
print(e)
|
||||
self.run = run_with_except_hook
|
||||
|
||||
if sys.version_info >= (3,): # pragma: 2.x no cover
|
||||
def _bootstrap(self):
|
||||
prctl_set_th_name(self.name)
|
||||
return super(JailThread, self)._bootstrap();
|
||||
else: # pragma: 3.x no cover
|
||||
def __bootstrap(self):
|
||||
prctl_set_th_name(self.name)
|
||||
return Thread._Thread__bootstrap(self)
|
||||
def _bootstrap(self):
|
||||
prctl_set_th_name(self.name)
|
||||
return super(JailThread, self)._bootstrap();
|
||||
|
||||
@abstractmethod
|
||||
def status(self, flavor="basic"): # pragma: no cover - abstract
|
||||
|
@ -125,9 +120,6 @@ class JailThread(Thread):
|
|||
if self.active is not None:
|
||||
super(JailThread, self).join()
|
||||
|
||||
## python 2.x replace binding of private __bootstrap method:
|
||||
if sys.version_info < (3,): # pragma: 3.x no cover
|
||||
JailThread._Thread__bootstrap = JailThread._JailThread__bootstrap
|
||||
## python 3.9, restore isAlive method:
|
||||
elif not hasattr(JailThread, 'isAlive'): # pragma: 2.x no cover
|
||||
if not hasattr(JailThread, 'isAlive'):
|
||||
JailThread.isAlive = JailThread.is_alive
|
||||
|
|
|
@ -58,11 +58,6 @@ except ImportError: # pragma: no cover
|
|||
def _thread_name():
|
||||
return threading.current_thread().__class__.__name__
|
||||
|
||||
try:
|
||||
FileExistsError
|
||||
except NameError: # pragma: 3.x no cover
|
||||
FileExistsError = OSError
|
||||
|
||||
def _make_file_path(name):
|
||||
"""Creates path of file (last level only) on demand"""
|
||||
name = os.path.dirname(name)
|
||||
|
|
|
@ -73,7 +73,7 @@ class DefaultTestOptions(optparse.Values):
|
|||
self.__dict__ = {
|
||||
'log_level': None, 'verbosity': None, 'log_lazy': True,
|
||||
'log_traceback': None, 'full_traceback': None,
|
||||
'fast': False, 'memory_db': False, 'no_gamin': False,
|
||||
'fast': False, 'memory_db': False,
|
||||
'no_network': False, 'negate_re': False
|
||||
}
|
||||
|
||||
|
@ -105,9 +105,6 @@ def getOptParser(doc=""):
|
|||
Option('-n', "--no-network", action="store_true",
|
||||
dest="no_network",
|
||||
help="Do not run tests that require the network"),
|
||||
Option('-g', "--no-gamin", action="store_true",
|
||||
dest="no_gamin",
|
||||
help="Do not run tests that require the gamin"),
|
||||
Option('-m', "--memory-db", action="store_true",
|
||||
dest="memory_db",
|
||||
help="Run database tests using memory instead of file"),
|
||||
|
@ -186,7 +183,6 @@ class F2B(DefaultTestOptions):
|
|||
self.__dict__ = opts.__dict__
|
||||
if self.fast: # pragma: no cover - normal mode in travis
|
||||
self.memory_db = True
|
||||
self.no_gamin = True
|
||||
self.__dict__['share_config'] = {}
|
||||
def SkipIfFast(self):
|
||||
pass
|
||||
|
@ -492,16 +488,6 @@ def gatherTests(regexps=None, opts=None):
|
|||
# Additional filters available only if external modules are available
|
||||
# yoh: Since I do not know better way for parametric tests
|
||||
# with good old unittest
|
||||
try:
|
||||
# because gamin can be very slow on some platforms (and can produce many failures
|
||||
# with fast sleep interval) - skip it by fast run:
|
||||
if unittest.F2B.fast or unittest.F2B.no_gamin: # pragma: no cover
|
||||
raise ImportError('Skip, fast: %s, no_gamin: %s' % (unittest.F2B.fast, unittest.F2B.no_gamin))
|
||||
from ..server.filtergamin import FilterGamin
|
||||
filters.append(FilterGamin)
|
||||
except ImportError as e: # pragma: no cover
|
||||
logSys.warning("Skipping gamin backend testing. Got exception '%s'" % e)
|
||||
|
||||
try:
|
||||
from ..server.filterpyinotify import FilterPyinotify
|
||||
filters.append(FilterPyinotify)
|
||||
|
@ -596,17 +582,6 @@ def assertSortedEqual(self, a, b, level=1, nestedOnly=False, key=repr, msg=None)
|
|||
self.fail(msg)
|
||||
unittest.TestCase.assertSortedEqual = assertSortedEqual
|
||||
|
||||
if not hasattr(unittest.TestCase, 'assertRaisesRegexp'):
|
||||
def assertRaisesRegexp(self, exccls, regexp, fun, *args, **kwargs):
|
||||
try:
|
||||
fun(*args, **kwargs)
|
||||
except exccls as e:
|
||||
if re.search(regexp, str(e)) is None:
|
||||
self.fail('\"%s\" does not match \"%s\"' % (regexp, e))
|
||||
else:
|
||||
self.fail('%s not raised' % getattr(exccls, '__name__'))
|
||||
unittest.TestCase.assertRaisesRegex = assertRaisesRegexp
|
||||
|
||||
# always custom following methods, because we use atm better version of both (support generators)
|
||||
if True: ## if not hasattr(unittest.TestCase, 'assertIn'):
|
||||
def assertIn(self, a, b, msg=None):
|
||||
|
|
|
@ -80,7 +80,7 @@ _fail2ban () {
|
|||
;;
|
||||
*)
|
||||
if [[ "${words[$cword-2]}" == "add" ]];then
|
||||
COMPREPLY=( $( compgen -W "auto polling gamin pyinotify systemd" -- "$cur" ) )
|
||||
COMPREPLY=( $( compgen -W "auto polling pyinotify systemd" -- "$cur" ) )
|
||||
return 0
|
||||
elif [[ "${words[$cword-2]}" == "set" || "${words[$cword-2]}" == "get" ]];then
|
||||
cmd="${words[cword-2]}"
|
||||
|
|
|
@ -30,9 +30,6 @@ Prevent lazy logging inside tests
|
|||
\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
|
||||
|
|
|
@ -123,7 +123,7 @@ filter = test[test.method=POST, baduseragents="badagent|<known/baduseragents>"]
|
|||
.fi
|
||||
.RE
|
||||
|
||||
Comments: use '#' for comment lines and '; ' (space is important) for inline comments. When using Python2.X, '; ' can only be used on the first line due to an Python library bug.
|
||||
Comments: use '#' for comment lines and '; ' (space is important) for inline comments.
|
||||
|
||||
.SH "FAIL2BAN CONFIGURATION FILE(S) (\fIfail2ban.conf\fB)"
|
||||
|
||||
|
@ -276,7 +276,7 @@ number of failures that have to occur in the last \fBfindtime\fR seconds to ban
|
|||
.B backend
|
||||
backend to be used to detect changes in the logpath.
|
||||
.br
|
||||
It defaults to "auto" which will try "pyinotify", "gamin", "systemd" before "polling". Any of these can be specified. "pyinotify" is only valid on Linux systems with the "pyinotify" Python libraries. "gamin" requires the "gamin" libraries.
|
||||
It defaults to "auto" which will try "pyinotify", "systemd" before "polling". Any of these can be specified. "pyinotify" is only valid on Linux systems with the "pyinotify" Python libraries.
|
||||
.TP
|
||||
.B usedns
|
||||
use DNS to resolve HOST names that appear in the logs. By default it is "warn" which will resolve hostnames to IPs however it will also log a warning. If you are using DNS here you could be blocking the wrong IPs due to the asymmetric nature of reverse DNS (that the application used to write the domain name to log) compared to forward DNS that fail2ban uses to resolve this back to an IP (but not necessarily the same one). Ideally you should configure your applications to log a real IP. This can be set to "yes" to prevent warnings in the log or "no" to disable DNS resolution altogether (thus ignoring entries where hostname, not an IP is logged)..
|
||||
|
@ -299,9 +299,6 @@ Available options are listed below.
|
|||
.B pyinotify
|
||||
requires pyinotify (a file alteration monitor) to be installed. If pyinotify is not installed, Fail2ban will use auto.
|
||||
.TP
|
||||
.B gamin
|
||||
requires Gamin (a file alteration monitor) to be installed. If Gamin is not installed, Fail2ban will use auto.
|
||||
.TP
|
||||
.B polling
|
||||
uses a polling algorithm which does not require external libraries.
|
||||
.TP
|
||||
|
|
13
setup.py
13
setup.py
|
@ -29,14 +29,16 @@ try:
|
|||
from setuptools import setup
|
||||
from setuptools.command.install import install
|
||||
from setuptools.command.install_scripts import install_scripts
|
||||
from setuptools.command.build_py import build_py
|
||||
build_scripts = None
|
||||
except ImportError:
|
||||
setuptools = None
|
||||
from distutils.core import setup
|
||||
|
||||
# all versions
|
||||
from distutils.command.build_py import build_py
|
||||
from distutils.command.build_scripts import build_scripts
|
||||
# older versions
|
||||
if setuptools is None:
|
||||
from distutils.command.build_py import build_py
|
||||
from distutils.command.build_scripts import build_scripts
|
||||
from distutils.command.install import install
|
||||
from distutils.command.install_scripts import install_scripts
|
||||
|
||||
|
@ -205,10 +207,9 @@ setup(
|
|||
url = "http://www.fail2ban.org",
|
||||
license = "GPL",
|
||||
platforms = "Posix",
|
||||
cmdclass = {
|
||||
'build_py': build_py, 'build_scripts': build_scripts,
|
||||
cmdclass = dict({'build_py': build_py, 'build_scripts': build_scripts} if build_scripts else {}, **{
|
||||
'install_scripts': install_scripts_f2b, 'install': install_command_f2b
|
||||
},
|
||||
}),
|
||||
scripts = [
|
||||
'bin/fail2ban-client',
|
||||
'bin/fail2ban-server',
|
||||
|
|
Loading…
Reference in New Issue