banip 2.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- banip/__init__.py +1 -0
- banip/__main__.py +6 -0
- banip/app.py +217 -0
- banip/argument_types.py +68 -0
- banip/bots.py +461 -0
- banip/build.py +407 -0
- banip/check.py +121 -0
- banip/config.py +591 -0
- banip/constants.py +38 -0
- banip/database.py +307 -0
- banip/null.py +13 -0
- banip/parsers/__init__.py +1 -0
- banip/parsers/bots_args.py +41 -0
- banip/parsers/build_args.py +58 -0
- banip/parsers/check_args.py +19 -0
- banip/parsers/database_args.py +48 -0
- banip/parsers/patch_args.py +49 -0
- banip/parsers/stats_args.py +31 -0
- banip/patch.py +88 -0
- banip/py.typed +0 -0
- banip/stats.py +79 -0
- banip/utilities/__init__.py +43 -0
- banip/utilities/data.py +130 -0
- banip/utilities/display.py +157 -0
- banip/utilities/external.py +35 -0
- banip/utilities/ip.py +79 -0
- banip/utilities/lookup.py +171 -0
- banip-2.1.0.dist-info/METADATA +80 -0
- banip-2.1.0.dist-info/RECORD +33 -0
- banip-2.1.0.dist-info/WHEEL +4 -0
- banip-2.1.0.dist-info/entry_points.txt +3 -0
- banip-2.1.0.dist-info/licenses/LICENSE +22 -0
- banip-2.1.0.dist-info/licenses/LICENSE-rich +20 -0
banip/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""banip package."""
|
banip/__main__.py
ADDED
banip/app.py
ADDED
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
"""Entry point for banip."""
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
import importlib
|
|
7
|
+
import importlib.util
|
|
8
|
+
import sys
|
|
9
|
+
from importlib.metadata import version
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from types import ModuleType
|
|
12
|
+
|
|
13
|
+
from banip.constants import APP_NAME
|
|
14
|
+
from banip.constants import ARG_PARSERS_BASE
|
|
15
|
+
from banip.constants import CUSTOM_CODE
|
|
16
|
+
from banip.constants import CUSTOM_PARSERS
|
|
17
|
+
from banip.constants import DATA
|
|
18
|
+
from banip.utilities import print_docstring
|
|
19
|
+
|
|
20
|
+
__version__ = version("banip")
|
|
21
|
+
|
|
22
|
+
LEGACY_PLUGIN_WARNING = (
|
|
23
|
+
"Warning: banip plugins are deprecated and will be removed in banip 3.0. "
|
|
24
|
+
"Legacy plugins remain supported throughout banip 2.x."
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
# ======================================================================
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def check_setup() -> bool:
|
|
31
|
+
"""Check whether the local environment is configured.
|
|
32
|
+
|
|
33
|
+
Returns
|
|
34
|
+
-------
|
|
35
|
+
bool
|
|
36
|
+
True if the required directories exist; otherwise False.
|
|
37
|
+
"""
|
|
38
|
+
proper_setup = (DATA / "geolite").exists()
|
|
39
|
+
if not proper_setup:
|
|
40
|
+
msg = """
|
|
41
|
+
The local environment is not configured correctly. Make sure
|
|
42
|
+
the following structure exists in your home directory:
|
|
43
|
+
|
|
44
|
+
.banip
|
|
45
|
+
└── geolite
|
|
46
|
+
"""
|
|
47
|
+
print_docstring(msg=msg)
|
|
48
|
+
return False
|
|
49
|
+
return True
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
# ======================================================================
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def requires_setup(args: argparse.Namespace) -> bool:
|
|
56
|
+
"""Return whether a command requires initialized local data paths.
|
|
57
|
+
|
|
58
|
+
Parameters
|
|
59
|
+
----------
|
|
60
|
+
args : argparse.Namespace
|
|
61
|
+
Parsed command-line arguments.
|
|
62
|
+
|
|
63
|
+
Returns
|
|
64
|
+
-------
|
|
65
|
+
bool
|
|
66
|
+
True when setup should be checked before dispatch.
|
|
67
|
+
"""
|
|
68
|
+
return not (args.cmd == "database" and args.action == "init")
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
# ======================================================================
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def load_custom_module(mod_name: str, location: Path) -> ModuleType:
|
|
75
|
+
"""Load a custom module.
|
|
76
|
+
|
|
77
|
+
Parameters
|
|
78
|
+
----------
|
|
79
|
+
mod_name : str
|
|
80
|
+
The name of the module to load.
|
|
81
|
+
location : Path
|
|
82
|
+
The absolute path to the Python code for the module.
|
|
83
|
+
|
|
84
|
+
Returns
|
|
85
|
+
-------
|
|
86
|
+
ModuleType
|
|
87
|
+
The loaded module.
|
|
88
|
+
"""
|
|
89
|
+
mod_path = f"{location}/{mod_name}.py"
|
|
90
|
+
if spec := importlib.util.spec_from_file_location(mod_name, mod_path):
|
|
91
|
+
if (module := importlib.util.module_from_spec(spec)) and spec.loader:
|
|
92
|
+
spec.loader.exec_module(module)
|
|
93
|
+
return module
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
# ======================================================================
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def collect_parsers(start: Path) -> list[str]:
|
|
100
|
+
"""Collect the module names of all argument parsers to import.
|
|
101
|
+
|
|
102
|
+
Parameters
|
|
103
|
+
----------
|
|
104
|
+
start : Path
|
|
105
|
+
The directory where parser collection starts.
|
|
106
|
+
|
|
107
|
+
Returns
|
|
108
|
+
-------
|
|
109
|
+
list[str]
|
|
110
|
+
Argument parser module names.
|
|
111
|
+
"""
|
|
112
|
+
parser_names: list[str] = []
|
|
113
|
+
if not start.exists():
|
|
114
|
+
return parser_names
|
|
115
|
+
for p in start.iterdir():
|
|
116
|
+
if p.is_file() and p.name.endswith(".py") and p.name != "__init__.py":
|
|
117
|
+
if "plugins" in str(p):
|
|
118
|
+
prefix = "plugins.parsers"
|
|
119
|
+
else:
|
|
120
|
+
prefix = "parsers"
|
|
121
|
+
parser_names.append(f"{prefix}.{p.stem}")
|
|
122
|
+
return parser_names
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
# ======================================================================
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def legacy_plugins_present() -> bool:
|
|
129
|
+
"""Return whether either legacy plugin directory contains Python code.
|
|
130
|
+
|
|
131
|
+
Returns
|
|
132
|
+
-------
|
|
133
|
+
bool
|
|
134
|
+
True when a legacy parser or command implementation is present.
|
|
135
|
+
"""
|
|
136
|
+
return any(
|
|
137
|
+
path.is_file() and path.suffix == ".py" and path.name != "__init__.py"
|
|
138
|
+
for directory in (CUSTOM_PARSERS, CUSTOM_CODE)
|
|
139
|
+
if directory.exists()
|
|
140
|
+
for path in directory.iterdir()
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
# ======================================================================
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def main() -> int:
|
|
148
|
+
"""Parse user input and run the requested command."""
|
|
149
|
+
msg = """
|
|
150
|
+
Generate and query IP blocklists for use with proxy servers such as
|
|
151
|
+
HAProxy. See https://geozeke.github.io/banip/ for
|
|
152
|
+
setup instructions.
|
|
153
|
+
"""
|
|
154
|
+
epi = f"Version: {__version__}"
|
|
155
|
+
parser = argparse.ArgumentParser(prog=APP_NAME, description=msg, epilog=epi)
|
|
156
|
+
parser.add_argument(
|
|
157
|
+
"-v", "--version", action="version", version=f"{APP_NAME} {__version__}"
|
|
158
|
+
)
|
|
159
|
+
msg = "For help on any command below, run: banip {command} -h."
|
|
160
|
+
subparsers = parser.add_subparsers(title="commands", dest="cmd", description=msg)
|
|
161
|
+
|
|
162
|
+
# Dynamically load argument subparsers and process command line
|
|
163
|
+
# arguments.
|
|
164
|
+
|
|
165
|
+
parser_names: list[str] = []
|
|
166
|
+
mod: ModuleType | None = None
|
|
167
|
+
parser_names = collect_parsers(ARG_PARSERS_BASE)
|
|
168
|
+
parser_names += collect_parsers(CUSTOM_PARSERS)
|
|
169
|
+
if legacy_plugins_present():
|
|
170
|
+
print(LEGACY_PLUGIN_WARNING, file=sys.stderr)
|
|
171
|
+
parser_names = sorted(parser_names, key=lambda x: x.split(".")[-1])
|
|
172
|
+
for p_name in parser_names:
|
|
173
|
+
if "plugins" not in p_name:
|
|
174
|
+
parser_code = importlib.import_module(f"banip.{p_name}")
|
|
175
|
+
else:
|
|
176
|
+
parser_code = load_custom_module(
|
|
177
|
+
p_name.split(".")[-1], location=CUSTOM_PARSERS
|
|
178
|
+
)
|
|
179
|
+
parser_code.load_command_args(subparsers)
|
|
180
|
+
args = parser.parse_args()
|
|
181
|
+
|
|
182
|
+
# Make sure the local setup is complete after parsing so help and
|
|
183
|
+
# version output work in a fresh environment.
|
|
184
|
+
if args.cmd and requires_setup(args) and not check_setup():
|
|
185
|
+
return 1
|
|
186
|
+
|
|
187
|
+
# Run the selected command. Python's argparse module guarantees that
|
|
188
|
+
# we'll get either: (1) a valid command (base or custom) or (2) no
|
|
189
|
+
# command at all. Given that, we can determine whether the entered
|
|
190
|
+
# command is built in or custom based on its companion in the list of
|
|
191
|
+
# argument parser names. We then adjust the prefix based on that.
|
|
192
|
+
|
|
193
|
+
if args.cmd:
|
|
194
|
+
try:
|
|
195
|
+
if f"parsers.{args.cmd}_args" in parser_names:
|
|
196
|
+
mod_name = f"{APP_NAME}.{args.cmd}"
|
|
197
|
+
mod = importlib.import_module(mod_name)
|
|
198
|
+
else:
|
|
199
|
+
mod = load_custom_module(args.cmd, location=CUSTOM_CODE)
|
|
200
|
+
except (ModuleNotFoundError, FileNotFoundError):
|
|
201
|
+
msg = f"""
|
|
202
|
+
Code for a custom command must have the same filename as the
|
|
203
|
+
command itself. Make sure you have a Python file called
|
|
204
|
+
\"{args.cmd}.py\" in: {CUSTOM_CODE}
|
|
205
|
+
"""
|
|
206
|
+
print("\n".join([line.strip() for line in msg.split("\n")]))
|
|
207
|
+
sys.exit(1)
|
|
208
|
+
else:
|
|
209
|
+
mod = importlib.import_module(f"{APP_NAME}.null")
|
|
210
|
+
|
|
211
|
+
mod.task_runner(args)
|
|
212
|
+
|
|
213
|
+
return 0
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
if __name__ == "__main__":
|
|
217
|
+
raise SystemExit(main())
|
banip/argument_types.py
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
from argparse import ArgumentTypeError
|
|
2
|
+
|
|
3
|
+
# ======================================================================
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def threshold_type(x: str) -> int:
|
|
7
|
+
"""Validate the threshold input.
|
|
8
|
+
|
|
9
|
+
Parameters
|
|
10
|
+
----------
|
|
11
|
+
x : str
|
|
12
|
+
User input for the threshold option.
|
|
13
|
+
|
|
14
|
+
Returns
|
|
15
|
+
-------
|
|
16
|
+
int
|
|
17
|
+
The validated user input.
|
|
18
|
+
|
|
19
|
+
Raises
|
|
20
|
+
------
|
|
21
|
+
argparse.ArgumentTypeError
|
|
22
|
+
If the user input is not an integer.
|
|
23
|
+
argparse.ArgumentTypeError
|
|
24
|
+
If the user input is not within the acceptable range [1, 10].
|
|
25
|
+
"""
|
|
26
|
+
try:
|
|
27
|
+
x_int = int(x)
|
|
28
|
+
except ValueError:
|
|
29
|
+
raise ArgumentTypeError("Value must be an integer")
|
|
30
|
+
|
|
31
|
+
if x_int not in range(1, 11):
|
|
32
|
+
raise ArgumentTypeError("Value must be between 1 and 10")
|
|
33
|
+
|
|
34
|
+
return x_int
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
# ======================================================================
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def compact_type(x: str) -> int:
|
|
41
|
+
"""Validate the compact input.
|
|
42
|
+
|
|
43
|
+
Parameters
|
|
44
|
+
----------
|
|
45
|
+
x : str
|
|
46
|
+
User input for the compact option.
|
|
47
|
+
|
|
48
|
+
Returns
|
|
49
|
+
-------
|
|
50
|
+
int
|
|
51
|
+
The validated user input.
|
|
52
|
+
|
|
53
|
+
Raises
|
|
54
|
+
------
|
|
55
|
+
argparse.ArgumentTypeError
|
|
56
|
+
If the user input is not an integer.
|
|
57
|
+
argparse.ArgumentTypeError
|
|
58
|
+
If the user input is not within the acceptable range [1, 255].
|
|
59
|
+
"""
|
|
60
|
+
try:
|
|
61
|
+
x_int = int(x)
|
|
62
|
+
except ValueError:
|
|
63
|
+
raise ArgumentTypeError("Value must be an integer")
|
|
64
|
+
|
|
65
|
+
if x_int not in range(1, 256):
|
|
66
|
+
raise ArgumentTypeError("Value must be between 1 and 255")
|
|
67
|
+
|
|
68
|
+
return x_int
|