fb-pdnstools 1.0.0__py3-none-any.whl → 1.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.
- fb_pdnstools/__init__.py +76 -34
- fb_pdnstools/base_handler.py +119 -102
- fb_pdnstools/bulk_rm_app.py +242 -152
- fb_pdnstools/bulk_rm_cfg.py +33 -19
- fb_pdnstools/common.py +13 -13
- fb_pdnstools/errors.py +15 -15
- fb_pdnstools/record.py +407 -241
- fb_pdnstools/server.py +81 -44
- fb_pdnstools/xlate.py +57 -36
- fb_pdnstools/zone.py +600 -369
- fb_pdnstools-1.1.0.data/data/share/locale/de/LC_MESSAGES/fb_pdnstools.mo +0 -0
- fb_pdnstools-1.1.0.data/data/share/locale/en/LC_MESSAGES/fb_pdnstools.mo +0 -0
- fb_pdnstools-1.1.0.dist-info/METADATA +53 -0
- fb_pdnstools-1.1.0.dist-info/RECORD +17 -0
- {fb_pdnstools-1.0.0.dist-info → fb_pdnstools-1.1.0.dist-info}/WHEEL +1 -2
- fb_pdnstools-1.1.0.dist-info/entry_points.txt +3 -0
- fb_pdnstools/local_version.py +0 -17
- fb_pdnstools-1.0.0.data/scripts/pdns-bulk-remove +0 -68
- fb_pdnstools-1.0.0.dist-info/METADATA +0 -40
- fb_pdnstools-1.0.0.dist-info/RECORD +0 -17
- fb_pdnstools-1.0.0.dist-info/top_level.txt +0 -1
- {fb_pdnstools-1.0.0.dist-info → fb_pdnstools-1.1.0.dist-info/licenses}/LICENSE +0 -0
fb_pdnstools/bulk_rm_app.py
CHANGED
|
@@ -12,6 +12,7 @@ from __future__ import absolute_import
|
|
|
12
12
|
# Standard modules
|
|
13
13
|
import copy
|
|
14
14
|
import ipaddress
|
|
15
|
+
import locale
|
|
15
16
|
import logging
|
|
16
17
|
import os
|
|
17
18
|
import pathlib
|
|
@@ -21,12 +22,12 @@ from functools import cmp_to_key
|
|
|
21
22
|
|
|
22
23
|
# Third party modules
|
|
23
24
|
from fb_tools.app import BaseApplication
|
|
25
|
+
from fb_tools.argparse_actions import CfgFileOptionAction
|
|
24
26
|
from fb_tools.common import compare_fqdn
|
|
25
27
|
from fb_tools.common import pp
|
|
26
28
|
from fb_tools.common import reverse_pointer
|
|
27
29
|
from fb_tools.common import to_bool
|
|
28
30
|
from fb_tools.common import to_str
|
|
29
|
-
from fb_tools.config import CfgFileOptionAction
|
|
30
31
|
from fb_tools.errors import FbAppError
|
|
31
32
|
|
|
32
33
|
# Own modules
|
|
@@ -35,9 +36,15 @@ from . import DEFAULT_PORT
|
|
|
35
36
|
from . import __version__ as GLOBAL_VERSION
|
|
36
37
|
from .bulk_rm_cfg import PdnsBulkRmCfg
|
|
37
38
|
from .server import PowerDNSServer
|
|
39
|
+
from .xlate import DOMAIN
|
|
40
|
+
from .xlate import LOCALE_DIR
|
|
38
41
|
from .xlate import XLATOR
|
|
42
|
+
from .xlate import __base_dir__ as __xlate_base_dir__
|
|
43
|
+
from .xlate import __lib_dir__ as __xlate_lib_dir__
|
|
44
|
+
from .xlate import __mo_file__ as __xlate_mo_file__
|
|
45
|
+
from .xlate import __module_dir__ as __xlate_module_dir__
|
|
39
46
|
|
|
40
|
-
__version__ =
|
|
47
|
+
__version__ = "1.0.0"
|
|
41
48
|
LOG = logging.getLogger(__name__)
|
|
42
49
|
|
|
43
50
|
_ = XLATOR.gettext
|
|
@@ -61,10 +68,11 @@ class PdnsBulkRmApp(BaseApplication):
|
|
|
61
68
|
def __init__(self, appname=None, verbose=0, version=GLOBAL_VERSION, *args, **kwargs):
|
|
62
69
|
"""Initialize the PdnsBulkRmApp object."""
|
|
63
70
|
desc = _(
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
71
|
+
"Removes the given addresses (A-, AAAA- or CNAME-Records) completety from "
|
|
72
|
+
"PowerDNS. If there are multiple entries to a DNS-Name, all appropriate "
|
|
73
|
+
"records are removed. Additionally all appropriate reverse entries (PTR-records) "
|
|
74
|
+
"were also removed, if they are pointing back to the given A- or AAAA-record."
|
|
75
|
+
)
|
|
68
76
|
|
|
69
77
|
self._cfg_file = None
|
|
70
78
|
self.config = None
|
|
@@ -78,10 +86,7 @@ class PdnsBulkRmApp(BaseApplication):
|
|
|
78
86
|
self.expected_ptr = None
|
|
79
87
|
|
|
80
88
|
super(PdnsBulkRmApp, self).__init__(
|
|
81
|
-
description=desc,
|
|
82
|
-
verbose=verbose,
|
|
83
|
-
version=version,
|
|
84
|
-
*args, **kwargs
|
|
89
|
+
*args, **kwargs, description=desc, verbose=verbose, version=version,
|
|
85
90
|
)
|
|
86
91
|
|
|
87
92
|
self.initialized = True
|
|
@@ -114,8 +119,20 @@ class PdnsBulkRmApp(BaseApplication):
|
|
|
114
119
|
@rtype: dict
|
|
115
120
|
"""
|
|
116
121
|
res = super(PdnsBulkRmApp, self).as_dict(short=short)
|
|
117
|
-
res[
|
|
118
|
-
res[
|
|
122
|
+
res["cfg_file"] = self.cfg_file
|
|
123
|
+
res["rm_reverse"] = self.rm_reverse
|
|
124
|
+
|
|
125
|
+
if "xlate" not in res:
|
|
126
|
+
res["xlate"] = {}
|
|
127
|
+
|
|
128
|
+
res["xlate"]["fb_vmware"] = {
|
|
129
|
+
"__module_dir__": __xlate_module_dir__,
|
|
130
|
+
"__lib_dir__": __xlate_lib_dir__,
|
|
131
|
+
"__base_dir__": __xlate_base_dir__,
|
|
132
|
+
"LOCALE_DIR": LOCALE_DIR,
|
|
133
|
+
"DOMAIN": DOMAIN,
|
|
134
|
+
"__mo_file__": __xlate_mo_file__,
|
|
135
|
+
}
|
|
119
136
|
|
|
120
137
|
return res
|
|
121
138
|
|
|
@@ -138,8 +155,11 @@ class PdnsBulkRmApp(BaseApplication):
|
|
|
138
155
|
self.perform_arg_parser()
|
|
139
156
|
|
|
140
157
|
self.config = PdnsBulkRmCfg(
|
|
141
|
-
appname=self.appname,
|
|
142
|
-
|
|
158
|
+
appname=self.appname,
|
|
159
|
+
verbose=self.verbose,
|
|
160
|
+
base_dir=self.base_dir,
|
|
161
|
+
config_file=self.cfg_file,
|
|
162
|
+
)
|
|
143
163
|
|
|
144
164
|
self.config.read()
|
|
145
165
|
if self.config.verbose > self.verbose:
|
|
@@ -147,7 +167,7 @@ class PdnsBulkRmApp(BaseApplication):
|
|
|
147
167
|
self.config.initialized = True
|
|
148
168
|
|
|
149
169
|
if self.verbose > 3:
|
|
150
|
-
LOG.debug(
|
|
170
|
+
LOG.debug("Read configuration:\n{}".format(pp(self.config.as_dict())))
|
|
151
171
|
|
|
152
172
|
self.perform_arg_parser_pdns()
|
|
153
173
|
|
|
@@ -155,15 +175,21 @@ class PdnsBulkRmApp(BaseApplication):
|
|
|
155
175
|
self.read_address_file()
|
|
156
176
|
|
|
157
177
|
if not self.addresses:
|
|
158
|
-
LOG.error(_(
|
|
178
|
+
LOG.error(_("No addresses to remove given."))
|
|
159
179
|
self.exit(1)
|
|
160
180
|
|
|
161
181
|
self.pdns = PowerDNSServer(
|
|
162
|
-
appname=self.appname,
|
|
163
|
-
|
|
164
|
-
|
|
182
|
+
appname=self.appname,
|
|
183
|
+
verbose=self.verbose,
|
|
184
|
+
base_dir=self.base_dir,
|
|
185
|
+
master_server=self.config.pdns_master,
|
|
186
|
+
port=self.config.pdns_api_port,
|
|
187
|
+
key=self.config.pdns_api_key,
|
|
188
|
+
use_https=self.config.pdns_api_https,
|
|
165
189
|
path_prefix=self.config.pdns_api_prefix,
|
|
166
|
-
simulate=self.simulate,
|
|
190
|
+
simulate=self.simulate,
|
|
191
|
+
force=self.force,
|
|
192
|
+
initialized=True,
|
|
167
193
|
)
|
|
168
194
|
|
|
169
195
|
self.pdns.initialized = True
|
|
@@ -174,69 +200,102 @@ class PdnsBulkRmApp(BaseApplication):
|
|
|
174
200
|
"""Public available method to initiate the argument parser."""
|
|
175
201
|
super(PdnsBulkRmApp, self).init_arg_parser()
|
|
176
202
|
|
|
177
|
-
default_cfg_file = self.base_dir.joinpath(
|
|
203
|
+
default_cfg_file = self.base_dir.joinpath("etc").joinpath(self.appname + ".ini")
|
|
178
204
|
|
|
179
205
|
self.arg_parser.add_argument(
|
|
180
|
-
|
|
206
|
+
"-c",
|
|
207
|
+
"--config",
|
|
208
|
+
"--config-file",
|
|
209
|
+
dest="cfg_file",
|
|
210
|
+
metavar=_("FILE"),
|
|
181
211
|
action=CfgFileOptionAction,
|
|
182
|
-
help=_(
|
|
212
|
+
help=_("Configuration file (default: {!r})").format(default_cfg_file),
|
|
183
213
|
)
|
|
184
214
|
|
|
185
|
-
pdns_group = self.arg_parser.add_argument_group(_(
|
|
215
|
+
pdns_group = self.arg_parser.add_argument_group(_("PowerDNS options"))
|
|
186
216
|
|
|
187
217
|
pdns_group.add_argument(
|
|
188
|
-
|
|
218
|
+
"-H",
|
|
219
|
+
"--host",
|
|
220
|
+
dest="host",
|
|
221
|
+
metavar=_("HOST"),
|
|
189
222
|
help=_(
|
|
190
|
-
|
|
191
|
-
|
|
223
|
+
"Address or hostname of the PowerDNS server providing " "the API (Default: {!r})."
|
|
224
|
+
).format(PdnsBulkRmCfg.default_pdns_master),
|
|
192
225
|
)
|
|
193
226
|
|
|
194
227
|
pdns_group.add_argument(
|
|
195
|
-
|
|
196
|
-
|
|
228
|
+
"-P",
|
|
229
|
+
"--port",
|
|
230
|
+
dest="port",
|
|
231
|
+
type=int,
|
|
232
|
+
metavar=_("PORT"),
|
|
233
|
+
help=_("Port on PowerDNS server for API on (Default: {}).").format(DEFAULT_PORT),
|
|
234
|
+
)
|
|
197
235
|
|
|
198
236
|
pdns_group.add_argument(
|
|
199
|
-
|
|
200
|
-
|
|
237
|
+
"-K",
|
|
238
|
+
"--key",
|
|
239
|
+
"--api-key",
|
|
240
|
+
metavar="KEY",
|
|
241
|
+
dest="api_key",
|
|
242
|
+
help=_("The API key for accessing the PowerDNS API."),
|
|
201
243
|
)
|
|
202
244
|
|
|
203
245
|
pdns_group.add_argument(
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
246
|
+
"--https",
|
|
247
|
+
action="store_true",
|
|
248
|
+
dest="https",
|
|
249
|
+
help=_("Use HTTPS to access the PowerDNS API (Default: {}).").format(
|
|
250
|
+
PdnsBulkRmCfg.default_pdns_api_https
|
|
251
|
+
),
|
|
207
252
|
)
|
|
208
253
|
|
|
209
254
|
pdns_group.add_argument(
|
|
210
|
-
|
|
255
|
+
"--prefix",
|
|
256
|
+
dest="api_path_prefix",
|
|
211
257
|
help=_(
|
|
212
|
-
|
|
213
|
-
|
|
258
|
+
"The global prefix for all paths for accessing the PowerDNS API "
|
|
259
|
+
"(Default: {!r})."
|
|
260
|
+
).format(DEFAULT_API_PREFIX),
|
|
214
261
|
)
|
|
215
262
|
|
|
216
263
|
# Source of the addresses - file or cmdline arguments
|
|
217
264
|
# source_group = self.arg_parser.add_mutually_exclusive_group()
|
|
218
265
|
|
|
219
266
|
self.arg_parser.add_argument(
|
|
220
|
-
|
|
267
|
+
"-N",
|
|
268
|
+
"--no-reverse",
|
|
269
|
+
action="store_true",
|
|
270
|
+
dest="no_reverse",
|
|
221
271
|
help=_(
|
|
222
272
|
"Don't remove reverse DNS entries (PTR records) to the given addresses. "
|
|
223
|
-
|
|
273
|
+
"(Default: False - reverse entries will be removed)."
|
|
274
|
+
),
|
|
224
275
|
)
|
|
225
276
|
|
|
226
277
|
self.arg_parser.add_argument(
|
|
227
|
-
|
|
278
|
+
"-F",
|
|
279
|
+
"--file",
|
|
280
|
+
metavar=_("FILE"),
|
|
281
|
+
dest="addr_file",
|
|
282
|
+
type=pathlib.Path,
|
|
228
283
|
help=_(
|
|
229
|
-
|
|
230
|
-
|
|
284
|
+
"File containing the addresses to remove. The addresses must be "
|
|
285
|
+
"whitespace separeted, lines may be commented out by prepending them "
|
|
231
286
|
"with a hash sign '#'. This option is mutually exclusive with "
|
|
232
|
-
|
|
287
|
+
"giving the addresses as command line arguments."
|
|
288
|
+
),
|
|
233
289
|
)
|
|
234
290
|
|
|
235
291
|
self.arg_parser.add_argument(
|
|
236
|
-
|
|
292
|
+
"addresses",
|
|
293
|
+
metavar=_("ADDRESS"),
|
|
294
|
+
type=str,
|
|
295
|
+
nargs="*",
|
|
237
296
|
help=_(
|
|
238
|
-
|
|
239
|
-
|
|
297
|
+
"Addresses to remove. This option is mutually exclusive with " "the {!r} option."
|
|
298
|
+
).format("-F/--file"),
|
|
240
299
|
)
|
|
241
300
|
|
|
242
301
|
# -------------------------------------------------------------------------
|
|
@@ -250,8 +309,9 @@ class PdnsBulkRmApp(BaseApplication):
|
|
|
250
309
|
"""Execute some actions after parsing the command line parameters."""
|
|
251
310
|
if self.args.addr_file and self.args.addresses:
|
|
252
311
|
msg = _(
|
|
253
|
-
|
|
254
|
-
|
|
312
|
+
"The option {!r} is mutually exclusive with giving the addresses "
|
|
313
|
+
"as command line arguments."
|
|
314
|
+
).format("-F/--file")
|
|
255
315
|
LOG.error(msg)
|
|
256
316
|
self.arg_parser.print_usage(sys.stderr)
|
|
257
317
|
self.exit(1)
|
|
@@ -259,15 +319,15 @@ class PdnsBulkRmApp(BaseApplication):
|
|
|
259
319
|
if self.args.addr_file:
|
|
260
320
|
afile = self.args.addr_file
|
|
261
321
|
if not afile.exists():
|
|
262
|
-
msg = _(
|
|
322
|
+
msg = _("File {!r} does not exists.").format(str(afile))
|
|
263
323
|
LOG.error(msg)
|
|
264
324
|
self.exit(1)
|
|
265
325
|
if not afile.is_file():
|
|
266
|
-
msg = _(
|
|
326
|
+
msg = _("File {!r} is not a regular file.").format(str(afile))
|
|
267
327
|
LOG.error(msg)
|
|
268
328
|
self.exit(1)
|
|
269
329
|
if not os.access(str(afile), os.R_OK):
|
|
270
|
-
msg = _(
|
|
330
|
+
msg = _("No read access to file {!r}.").format(str(afile))
|
|
271
331
|
LOG.error(msg)
|
|
272
332
|
self.exit(1)
|
|
273
333
|
self.address_file = afile
|
|
@@ -289,7 +349,7 @@ class PdnsBulkRmApp(BaseApplication):
|
|
|
289
349
|
if self.args.addresses:
|
|
290
350
|
for address in self.args.addresses:
|
|
291
351
|
addr = address.strip().lower()
|
|
292
|
-
if addr !=
|
|
352
|
+
if addr != "" and addr not in self.addresses:
|
|
293
353
|
self.addresses.append(addr)
|
|
294
354
|
|
|
295
355
|
# -------------------------------------------------------------------------
|
|
@@ -297,26 +357,26 @@ class PdnsBulkRmApp(BaseApplication):
|
|
|
297
357
|
"""Read the file containing all addresses to remove."""
|
|
298
358
|
content = self.read_file(self.address_file)
|
|
299
359
|
if self.verbose > 2:
|
|
300
|
-
LOG.debug(_(
|
|
360
|
+
LOG.debug(_("Content of {!r}:").format(str(self.address_file)) + "\n" + content)
|
|
301
361
|
|
|
302
|
-
re_comment = re.compile(r
|
|
303
|
-
re_whitespace = re.compile(r
|
|
362
|
+
re_comment = re.compile(r"\s*#.*")
|
|
363
|
+
re_whitespace = re.compile(r"\s+")
|
|
304
364
|
|
|
305
365
|
addresses = []
|
|
306
366
|
for line in content.splitlines():
|
|
307
|
-
line_stripped = re_comment.sub(
|
|
308
|
-
if line_stripped ==
|
|
367
|
+
line_stripped = re_comment.sub("", line).strip()
|
|
368
|
+
if line_stripped == "":
|
|
309
369
|
continue
|
|
310
370
|
for token in re_whitespace.split(line_stripped):
|
|
311
371
|
addr = token.strip().lower()
|
|
312
|
-
if addr !=
|
|
372
|
+
if addr != "" and addr not in addresses:
|
|
313
373
|
addresses.append(addr)
|
|
314
374
|
|
|
315
375
|
if addresses:
|
|
316
376
|
self.addresses = addresses
|
|
317
377
|
|
|
318
378
|
if not self.addresses:
|
|
319
|
-
LOG.error(_(
|
|
379
|
+
LOG.error(_("No addresses to remove found in {!r}.").format(str(self.address_file)))
|
|
320
380
|
self.exit(1)
|
|
321
381
|
|
|
322
382
|
# -------------------------------------------------------------------------
|
|
@@ -329,8 +389,7 @@ class PdnsBulkRmApp(BaseApplication):
|
|
|
329
389
|
def _run(self):
|
|
330
390
|
|
|
331
391
|
print()
|
|
332
|
-
LOG.debug(
|
|
333
|
-
a=self.appname, v=self.version))
|
|
392
|
+
LOG.debug("Starting {a!r}, version {v!r} ...".format(a=self.appname, v=self.version))
|
|
334
393
|
|
|
335
394
|
ret = 0
|
|
336
395
|
try:
|
|
@@ -354,19 +413,20 @@ class PdnsBulkRmApp(BaseApplication):
|
|
|
354
413
|
"""Display the informotion to t screen about simulation mode."""
|
|
355
414
|
if not self.simulate:
|
|
356
415
|
return
|
|
357
|
-
print(self.colored(_(
|
|
416
|
+
print(self.colored(_("Simulation mode - nothing will be removed in real."), "YELLOW"))
|
|
358
417
|
print()
|
|
359
418
|
|
|
360
419
|
# -------------------------------------------------------------------------
|
|
361
420
|
def do_remove(self):
|
|
362
421
|
"""Remove finally all addresses in DNS."""
|
|
363
422
|
for zone_name in sorted(
|
|
364
|
-
|
|
423
|
+
self.records2remove.keys(), key=lambda x: cmp_to_key(compare_fqdn)(x)
|
|
424
|
+
):
|
|
365
425
|
print()
|
|
366
426
|
zone = self.pdns.zones[zone_name]
|
|
367
427
|
rrsets_rm = []
|
|
368
428
|
for rrset in self.records2remove[zone_name]:
|
|
369
|
-
zone.add_rrset_for_remove(rrset[
|
|
429
|
+
zone.add_rrset_for_remove(rrset["fqdn"], rrset["type"], rrsets_rm)
|
|
370
430
|
zone.del_rrsets(rrsets_rm)
|
|
371
431
|
|
|
372
432
|
print()
|
|
@@ -375,34 +435,34 @@ class PdnsBulkRmApp(BaseApplication):
|
|
|
375
435
|
def _canon_addresses(self, addresses):
|
|
376
436
|
|
|
377
437
|
if self.verbose > 1:
|
|
378
|
-
LOG.debug(_(
|
|
438
|
+
LOG.debug(_("Canonizing all given addresses."))
|
|
379
439
|
all_fqdns = []
|
|
380
440
|
|
|
381
441
|
for addr in addresses:
|
|
382
442
|
|
|
383
443
|
fqdn = self.pdns.name2fqdn(addr)
|
|
384
444
|
if not fqdn:
|
|
385
|
-
LOG.warning(_(
|
|
445
|
+
LOG.warning(_("Address {!r} could not interpreted as a FQDN.").format(addr))
|
|
386
446
|
continue
|
|
387
447
|
if fqdn not in all_fqdns:
|
|
388
448
|
all_fqdns.append(fqdn)
|
|
389
449
|
|
|
390
450
|
if self.verbose > 2:
|
|
391
|
-
LOG.debug(_(
|
|
451
|
+
LOG.debug(_("Canonized addresses:") + "\n" + pp(all_fqdns))
|
|
392
452
|
return all_fqdns
|
|
393
453
|
|
|
394
454
|
# -------------------------------------------------------------------------
|
|
395
455
|
def _get_zones_of_addresses(self, fqdns):
|
|
396
456
|
|
|
397
457
|
if self.verbose > 1:
|
|
398
|
-
LOG.debug(_(
|
|
458
|
+
LOG.debug(_("Retrieve zones for canonized addresses."))
|
|
399
459
|
zones_of_records = {}
|
|
400
460
|
|
|
401
461
|
for fqdn in fqdns:
|
|
402
462
|
|
|
403
463
|
zones = self.pdns.get_all_zones_for_item(fqdn)
|
|
404
464
|
if not zones:
|
|
405
|
-
LOG.warning(_(
|
|
465
|
+
LOG.warning(_("Did not found an appropriate zone for address {!r}.").format(fqdn))
|
|
406
466
|
continue
|
|
407
467
|
|
|
408
468
|
for zone_name in zones:
|
|
@@ -411,14 +471,14 @@ class PdnsBulkRmApp(BaseApplication):
|
|
|
411
471
|
zones_of_records[zone_name][fqdn] = {}
|
|
412
472
|
|
|
413
473
|
if self.verbose > 2:
|
|
414
|
-
LOG.debug(_(
|
|
474
|
+
LOG.debug(_("Zones of addresses:") + "\n" + pp(zones_of_records))
|
|
415
475
|
return zones_of_records
|
|
416
476
|
|
|
417
477
|
# -------------------------------------------------------------------------
|
|
418
478
|
def _verify_fqdns_in_pdns_zones(self, zone_name, zones_of_records, fqdns_found=None):
|
|
419
479
|
|
|
420
480
|
if self.verbose > 1:
|
|
421
|
-
LOG.debug(_(
|
|
481
|
+
LOG.debug(_("Verifying FQDNs for zone {!r}.").format(zone_name))
|
|
422
482
|
|
|
423
483
|
if fqdns_found is None:
|
|
424
484
|
fqdns_found = []
|
|
@@ -426,8 +486,11 @@ class PdnsBulkRmApp(BaseApplication):
|
|
|
426
486
|
zone = self.pdns.zones[zone_name]
|
|
427
487
|
zone.update()
|
|
428
488
|
if self.verbose > 1:
|
|
429
|
-
LOG.debug(
|
|
430
|
-
c
|
|
489
|
+
LOG.debug(
|
|
490
|
+
_("Found {c} resource record sets (RRSET) for zone {z!r}.").format(
|
|
491
|
+
c=len(zone.rrsets), z=zone_name
|
|
492
|
+
)
|
|
493
|
+
)
|
|
431
494
|
|
|
432
495
|
for fqdn in zones_of_records[zone_name]:
|
|
433
496
|
fqdns_in_zone_found = self._verify_fqdn_in_pdns_zone(zone_name, fqdn)
|
|
@@ -442,13 +505,16 @@ class PdnsBulkRmApp(BaseApplication):
|
|
|
442
505
|
zone = self.pdns.zones[zone_name]
|
|
443
506
|
fqdns_in_zone_found = []
|
|
444
507
|
|
|
445
|
-
fqdn_puny = to_str(fqdn.encode(
|
|
508
|
+
fqdn_puny = to_str(fqdn.encode("idna"))
|
|
446
509
|
if self.verbose > 1:
|
|
447
510
|
if fqdn != fqdn_puny:
|
|
448
|
-
LOG.debug(
|
|
449
|
-
f
|
|
511
|
+
LOG.debug(
|
|
512
|
+
_("Searching {f!r} ({p!r}) in zone {z!r} ...").format(
|
|
513
|
+
f=fqdn, p=fqdn_puny, z=zone_name
|
|
514
|
+
)
|
|
515
|
+
)
|
|
450
516
|
else:
|
|
451
|
-
LOG.debug(_(
|
|
517
|
+
LOG.debug(_("Searching {f!r} in zone {z!r} ...").format(f=fqdn, z=zone_name))
|
|
452
518
|
|
|
453
519
|
for rrset in zone.rrsets:
|
|
454
520
|
|
|
@@ -458,33 +524,35 @@ class PdnsBulkRmApp(BaseApplication):
|
|
|
458
524
|
if rrset.name != fqdn_puny:
|
|
459
525
|
continue
|
|
460
526
|
|
|
461
|
-
rrset2remove = {
|
|
527
|
+
rrset2remove = {"fqdn": fqdn_puny, "type": rrset.type.upper(), "records": []}
|
|
462
528
|
found = False
|
|
463
529
|
if zone.reverse_zone:
|
|
464
|
-
if rrset.type.upper() ==
|
|
530
|
+
if rrset.type.upper() == "PTR":
|
|
465
531
|
found = True
|
|
466
532
|
else:
|
|
467
|
-
if rrset.type.upper() in (
|
|
533
|
+
if rrset.type.upper() in ("A", "AAAA", "CNAME"):
|
|
468
534
|
found = True
|
|
469
535
|
if not found:
|
|
470
536
|
continue
|
|
471
537
|
|
|
472
538
|
for record in rrset.records:
|
|
473
|
-
if zone.reverse_zone and rrset.type.upper() ==
|
|
539
|
+
if zone.reverse_zone and rrset.type.upper() == "PTR":
|
|
474
540
|
if self.expected_ptr is not None and fqdn_puny in self.expected_ptr:
|
|
475
541
|
ptr = self.pdns.decanon_name(fqdn_puny)
|
|
476
542
|
exp = self.pdns.decanon_name(self.expected_ptr[fqdn_puny])
|
|
477
543
|
addr = self.pdns.decanon_name(record.content)
|
|
478
544
|
if self.verbose > 1:
|
|
479
|
-
LOG.debug(_(
|
|
545
|
+
LOG.debug(_("Expexted PTR: {p!r} => {a!r}.").format(p=ptr, a=exp))
|
|
480
546
|
if record.content != self.expected_ptr[fqdn_puny]:
|
|
481
|
-
LOG.warning(
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
547
|
+
LOG.warning(
|
|
548
|
+
_(
|
|
549
|
+
"PTR {p!r} does not pointing to expected {e!r}, "
|
|
550
|
+
"but to {c!r} instead, ignoring for deletion."
|
|
551
|
+
).format(p=ptr, e=exp, c=addr)
|
|
552
|
+
)
|
|
485
553
|
continue
|
|
486
|
-
record2remove = {
|
|
487
|
-
rrset2remove[
|
|
554
|
+
record2remove = {"content": record.content, "disabled": record.disabled}
|
|
555
|
+
rrset2remove["records"].append(record2remove)
|
|
488
556
|
if zone_name not in self.records2remove:
|
|
489
557
|
self.records2remove[zone_name] = []
|
|
490
558
|
self.records2remove[zone_name].append(rrset2remove)
|
|
@@ -496,7 +564,7 @@ class PdnsBulkRmApp(BaseApplication):
|
|
|
496
564
|
# -------------------------------------------------------------------------
|
|
497
565
|
def verify_addresses(self, addresses):
|
|
498
566
|
"""Verify all given DNS addresses."""
|
|
499
|
-
LOG.debug(_(
|
|
567
|
+
LOG.debug(_("Verifying all given DNS addresses."))
|
|
500
568
|
|
|
501
569
|
fqdns_found = []
|
|
502
570
|
|
|
@@ -504,35 +572,36 @@ class PdnsBulkRmApp(BaseApplication):
|
|
|
504
572
|
zones_of_records = self._get_zones_of_addresses(all_fqdns)
|
|
505
573
|
|
|
506
574
|
if not zones_of_records:
|
|
507
|
-
msg = _(
|
|
575
|
+
msg = _("Did not found any addresses with an appropriate zone in PowerDNS.")
|
|
508
576
|
LOG.error(msg)
|
|
509
577
|
return 1
|
|
510
578
|
|
|
511
579
|
if self.verbose > 1:
|
|
512
|
-
LOG.debug(_(
|
|
580
|
+
LOG.debug(_("Found zones for addresses:") + "\n" + pp(zones_of_records))
|
|
513
581
|
|
|
514
582
|
for zone_name in zones_of_records:
|
|
515
583
|
fqdns_found = self._verify_fqdns_in_pdns_zones(
|
|
516
|
-
zone_name, zones_of_records, fqdns_found
|
|
584
|
+
zone_name, zones_of_records, fqdns_found
|
|
585
|
+
)
|
|
517
586
|
if self.verbose > 2:
|
|
518
|
-
LOG.debug(_(
|
|
587
|
+
LOG.debug(_("The following FQDNs were found:") + "\n" + pp(fqdns_found))
|
|
519
588
|
|
|
520
589
|
fqdns_not_found = []
|
|
521
590
|
for fqdn in all_fqdns:
|
|
522
591
|
if fqdn not in fqdns_found:
|
|
523
592
|
fqdns_not_found.append(fqdn)
|
|
524
593
|
if fqdns_not_found:
|
|
525
|
-
msg = _(
|
|
594
|
+
msg = _("The following addresses (FQDNs) are not found:")
|
|
526
595
|
for fqdn in fqdns_not_found:
|
|
527
|
-
msg +=
|
|
596
|
+
msg += "\n * {!r}".format(fqdn)
|
|
528
597
|
LOG.warning(msg)
|
|
529
598
|
|
|
530
599
|
if not self.records2remove:
|
|
531
600
|
return 1
|
|
532
601
|
|
|
533
602
|
if self.verbose > 2:
|
|
534
|
-
msg = _(
|
|
535
|
-
msg +=
|
|
603
|
+
msg = _("Found resource record sets to remove:")
|
|
604
|
+
msg += "\n" + pp(self.records2remove)
|
|
536
605
|
LOG.debug(msg)
|
|
537
606
|
|
|
538
607
|
return 0
|
|
@@ -540,7 +609,7 @@ class PdnsBulkRmApp(BaseApplication):
|
|
|
540
609
|
# -------------------------------------------------------------------------
|
|
541
610
|
def get_reverse_records(self):
|
|
542
611
|
"""Evaluate reverse PTR records of A and AAAA records."""
|
|
543
|
-
LOG.debug(_(
|
|
612
|
+
LOG.debug(_("Evaluating reverse PTR records of A and AAAA records."))
|
|
544
613
|
|
|
545
614
|
addresses = []
|
|
546
615
|
self.expected_ptr = {}
|
|
@@ -549,12 +618,12 @@ class PdnsBulkRmApp(BaseApplication):
|
|
|
549
618
|
|
|
550
619
|
for rrset in self.records2remove[zone_name]:
|
|
551
620
|
|
|
552
|
-
if rrset[
|
|
621
|
+
if rrset["type"] not in ("A", "AAAA"):
|
|
553
622
|
continue
|
|
554
623
|
|
|
555
|
-
for record in rrset[
|
|
556
|
-
addr_str = record[
|
|
557
|
-
LOG.debug(_(
|
|
624
|
+
for record in rrset["records"]:
|
|
625
|
+
addr_str = record["content"]
|
|
626
|
+
LOG.debug(_("Try to get reverse address of {!r} ...").format(addr_str))
|
|
558
627
|
addr = None
|
|
559
628
|
fqdn = None
|
|
560
629
|
|
|
@@ -562,100 +631,121 @@ class PdnsBulkRmApp(BaseApplication):
|
|
|
562
631
|
addr = ipaddress.ip_address(addr_str)
|
|
563
632
|
fqdn = self.pdns.canon_name(reverse_pointer(addr))
|
|
564
633
|
except ValueError:
|
|
565
|
-
msg = _(
|
|
566
|
-
addr_str
|
|
634
|
+
msg = _("IP address {!r} seems not to be a valid IP address.").format(
|
|
635
|
+
addr_str
|
|
636
|
+
)
|
|
567
637
|
LOG.error(msg)
|
|
568
638
|
continue
|
|
569
|
-
LOG.debug(_(
|
|
639
|
+
LOG.debug(_("Found reverse address {!r}.").format(fqdn))
|
|
570
640
|
if fqdn not in addresses:
|
|
571
641
|
addresses.append(fqdn)
|
|
572
|
-
self.expected_ptr[fqdn] = rrset[
|
|
642
|
+
self.expected_ptr[fqdn] = rrset["fqdn"]
|
|
573
643
|
|
|
574
644
|
if not addresses:
|
|
575
645
|
return 0
|
|
576
646
|
|
|
577
647
|
if self.verbose > 1:
|
|
578
|
-
LOG.debug(_(
|
|
648
|
+
LOG.debug(_("Expected PTR records:") + "\n" + pp(self.expected_ptr))
|
|
579
649
|
|
|
580
650
|
return self.verify_addresses(addresses)
|
|
581
651
|
|
|
582
652
|
# -------------------------------------------------------------------------
|
|
583
653
|
def show_records(self):
|
|
584
654
|
"""Display all DNS records to remove on screen."""
|
|
585
|
-
title = _(
|
|
655
|
+
title = _("All DNS records to remove")
|
|
586
656
|
print()
|
|
587
657
|
print(title)
|
|
588
|
-
print(
|
|
658
|
+
print("=" * len(title))
|
|
589
659
|
print()
|
|
590
660
|
|
|
591
|
-
disabled = _(
|
|
661
|
+
disabled = _("Disabled.")
|
|
592
662
|
headers = {
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
663
|
+
"fqdn": _("Name"),
|
|
664
|
+
"z": _("Zone"),
|
|
665
|
+
"type": _("Type"),
|
|
666
|
+
"rec": _("Record"),
|
|
667
|
+
"dis": "",
|
|
598
668
|
}
|
|
599
669
|
lengths = {
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
670
|
+
"fqdn": len(headers["fqdn"]),
|
|
671
|
+
"z": len(headers["z"]),
|
|
672
|
+
"type": len(headers["type"]),
|
|
673
|
+
"rec": len(headers["rec"]),
|
|
674
|
+
"dis": len(disabled),
|
|
605
675
|
}
|
|
606
676
|
count = 0
|
|
607
|
-
if lengths[
|
|
608
|
-
lengths[
|
|
677
|
+
if lengths["type"] < 8:
|
|
678
|
+
lengths["type"] = 8
|
|
609
679
|
|
|
610
680
|
for zone_name in self.records2remove.keys():
|
|
611
|
-
if len(zone_name) > lengths[
|
|
612
|
-
lengths[
|
|
681
|
+
if len(zone_name) > lengths["z"]:
|
|
682
|
+
lengths["z"] = len(zone_name)
|
|
613
683
|
for rrset in self.records2remove[zone_name]:
|
|
614
|
-
fqdn = self.pdns.decanon_name(rrset[
|
|
615
|
-
rr_type = rrset[
|
|
616
|
-
if len(fqdn) > lengths[
|
|
617
|
-
lengths[
|
|
618
|
-
if len(rr_type) > lengths[
|
|
619
|
-
lengths[
|
|
620
|
-
for record in rrset[
|
|
684
|
+
fqdn = self.pdns.decanon_name(rrset["fqdn"])
|
|
685
|
+
rr_type = rrset["type"]
|
|
686
|
+
if len(fqdn) > lengths["fqdn"]:
|
|
687
|
+
lengths["fqdn"] = len(fqdn)
|
|
688
|
+
if len(rr_type) > lengths["type"]:
|
|
689
|
+
lengths["type"] = len(rr_type)
|
|
690
|
+
for record in rrset["records"]:
|
|
621
691
|
count += 1
|
|
622
|
-
content = self.pdns.decanon_name(record[
|
|
623
|
-
if len(content) > lengths[
|
|
624
|
-
lengths[
|
|
692
|
+
content = self.pdns.decanon_name(record["content"])
|
|
693
|
+
if len(content) > lengths["rec"]:
|
|
694
|
+
lengths["rec"] = len(content)
|
|
625
695
|
|
|
626
|
-
tpl =
|
|
627
|
-
tpl +=
|
|
628
|
-
tpl +=
|
|
629
|
-
tpl +=
|
|
630
|
-
tpl +=
|
|
696
|
+
tpl = "{{fqdn:<{}}} ".format(lengths["fqdn"])
|
|
697
|
+
tpl += "{{z:<{}}} ".format(lengths["z"])
|
|
698
|
+
tpl += "{{type:<{}}} ".format(lengths["type"])
|
|
699
|
+
tpl += "{{rec:<{}}} ".format(lengths["rec"])
|
|
700
|
+
tpl += "{{dis:<{}}}".format(lengths["dis"])
|
|
631
701
|
|
|
632
702
|
header = tpl.format(**headers)
|
|
633
703
|
print(header)
|
|
634
|
-
print(
|
|
704
|
+
print("-" * len(header))
|
|
635
705
|
|
|
636
706
|
for zone_name in sorted(
|
|
637
|
-
|
|
707
|
+
self.records2remove.keys(), key=lambda x: cmp_to_key(compare_fqdn)(x)
|
|
708
|
+
):
|
|
638
709
|
for rrset in self.records2remove[zone_name]:
|
|
639
|
-
for record in rrset[
|
|
640
|
-
content = self.pdns.decanon_name(record[
|
|
710
|
+
for record in rrset["records"]:
|
|
711
|
+
content = self.pdns.decanon_name(record["content"])
|
|
641
712
|
out = {}
|
|
642
|
-
out[
|
|
643
|
-
out[
|
|
644
|
-
out[
|
|
645
|
-
out[
|
|
646
|
-
if record[
|
|
647
|
-
out[
|
|
713
|
+
out["z"] = self.pdns.decanon_name(zone_name)
|
|
714
|
+
out["fqdn"] = self.pdns.decanon_name(rrset["fqdn"])
|
|
715
|
+
out["type"] = rrset["type"]
|
|
716
|
+
out["rec"] = content
|
|
717
|
+
if record["disabled"]:
|
|
718
|
+
out["dis"] = disabled
|
|
648
719
|
else:
|
|
649
|
-
out[
|
|
720
|
+
out["dis"] = ""
|
|
650
721
|
print(tpl.format(**out))
|
|
651
722
|
print()
|
|
652
|
-
msg = ngettext(
|
|
723
|
+
msg = ngettext("Total one DNS record to remove.", "Total {} DNS records to remove.", count)
|
|
653
724
|
print(msg.format(count))
|
|
654
725
|
print()
|
|
655
726
|
|
|
656
727
|
|
|
657
728
|
# =============================================================================
|
|
658
|
-
|
|
729
|
+
def main():
|
|
730
|
+
"""Entrypoint for pdns-bulk-remove."""
|
|
731
|
+
my_path = pathlib.Path(__file__)
|
|
732
|
+
appname = my_path.name
|
|
733
|
+
|
|
734
|
+
locale.setlocale(locale.LC_ALL, "")
|
|
735
|
+
|
|
736
|
+
app = PdnsBulkRmApp(appname=appname)
|
|
737
|
+
app.initialized = True
|
|
738
|
+
|
|
739
|
+
if app.verbose > 2:
|
|
740
|
+
print(_("{c}-Object:\n{a}").format(c=app.__class__.__name__, a=app), file=sys.stderr)
|
|
741
|
+
|
|
742
|
+
app()
|
|
743
|
+
|
|
744
|
+
sys.exit(0)
|
|
745
|
+
|
|
746
|
+
|
|
747
|
+
# =============================================================================
|
|
748
|
+
if __name__ == "__main__":
|
|
659
749
|
|
|
660
750
|
pass
|
|
661
751
|
|