From 2bf4df61facd82a95faed4b5a60abfd64d96760b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anton=20Luka=20=C5=A0ijanec?= Date: Wed, 22 May 2024 01:37:00 +0200 Subject: rfc2136 simple editor --- skripti/zone/axfr.py | 85 ++++++++++++++++++++++++ skripti/zone/update.py | 174 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 259 insertions(+) create mode 100755 skripti/zone/axfr.py create mode 100755 skripti/zone/update.py diff --git a/skripti/zone/axfr.py b/skripti/zone/axfr.py new file mode 100755 index 0000000..fe508f1 --- /dev/null +++ b/skripti/zone/axfr.py @@ -0,0 +1,85 @@ +#!/usr/bin/python3 +import dns.zone +import dns.resolver +import json +import sys +domena = sys.argv[1] +strežniki = [dns.resolver.resolve(domena, "SOA")[0].mname] +for i in dns.resolver.resolve(domena, "NS"): + strežniki.append(i.target) +naslovi = [] +for strežnik in strežniki: + for i in dns.resolver.resolve(strežnik, "AAAA"): + naslovi.append(i.address) + for i in dns.resolver.resolve(strežnik, "A"): + naslovi.append(i.address) +for naslov in naslovi: # opcijsko dodaj tule kakšen try catch + zone = None + zone = dns.zone.from_xfr(dns.query.xfr(naslov, domena)) + if zone != None: + break +config = None +try: + config = json.loads(b''.join(dns.resolver.resolve("_urejevalnik." + domena, "TXT")[0].strings).decode()) +except dns.resolver.NXDOMAIN: + pass +except json.decoder.JSONDecodeError: + pass +if config == None: + berime = """; Dobrodošli v preprost urejevalnik DNS zapisov. +; Komentarji se shranijo v DNS strežnik in so javni. Morajo biti na samostojnih vrsticah. +; Te komentarje z navodili lahko izbrišete -- ne bodo se ponovno pojavili. +; Nove zapise naložite na strežnik z ukazom zone/update.py zonefile.db +; Zapise prenesete iz strežnika z ukazom zone/axfr.py domena > zonefile.db +; Prva vrstica je konfiguracijski zapis v JSON obliki. Naslednje podatke lahko spremenite: +; "t": privzeti TTL, ki se uporabi, če zapis v datoteki nima TTLja +; "+": koliko naj prištejem serijski številki pred nalaganjem na strežnik + +""" + config = {"v": 0, "d": domena, "c": {"@ SOA": berime}, "t": 1, "+": 100, "i": {}} +configout = config.copy() +del configout["c"] +del configout["i"] +print(f"{json.dumps(configout)}") +for r in zone.iterate_rdatas(): + if r[0].to_unicode() == "_urejevalnik" or r[2].rdtype in [dns.rdatatype.RRSIG, dns.rdatatype.NSEC, dns.rdatatype.NSEC3, dns.rdatatype.DNSKEY]: + continue + commentkey = r[0].to_unicode() + " " + r[2].rdtype.name + if commentkey in config["c"].keys(): + print(config["c"][commentkey], end="") + del config["c"][commentkey] + konec = "\t" + if r[0].to_unicode() in config["i"].keys(): + konec = config["i"][r[0].to_unicode()] + print(r[0].to_unicode(), end=konec) + if r[1] != config["t"]: + print(r[1], end="\t") + if r[2].rdclass != dns.rdataclass.IN: + print(r[2].rdataclass.name, end="\t") + print(r[2].rdtype.name, end="\t") + if r[2].rdtype == dns.rdatatype.TXT: + prvič = True + for string in r[2].strings: + if prvič: + prvič = False + else: + print(" ", end="") + bajti = b'' + for char in string: + if char < ord(b' '): + bajti += b'\\' + ("%03d" % ord(char)).encode() + else: + bajti += bytes([char]) + niz = "" + for znak in bajti.replace(b'\\', b'\\\\').replace(b'"', b'\\"').decode('utf-8', errors='surrogateescape'): + if '\udc80' <= znak <= '\udcff': + niz += '\\'+("%03d" % (ord(znak)-0xdc00)) + else: + niz += znak + + print('"' + niz + '"', end="") + else: + print(r[2].to_text(), end="") + print() +for i in config["c"].items(): + print(i[1], end="") diff --git a/skripti/zone/update.py b/skripti/zone/update.py new file mode 100755 index 0000000..34a4e39 --- /dev/null +++ b/skripti/zone/update.py @@ -0,0 +1,174 @@ +#!/usr/bin/python3 +import dns.zone +import dns.resolver +import dns.update +import dns.tsigkeyring +import json +import sys +import math +with open(sys.argv[1], "r") as db: + lines = db.readlines() +newconfig = json.loads(lines.pop(0)[:-1]) +domena = newconfig["d"] +strežniki = [dns.resolver.resolve(domena, "SOA")[0].mname] +naslovi = [] +for strežnik in strežniki: + for i in dns.resolver.resolve(strežnik, "AAAA"): + naslovi.append(i.address) + for i in dns.resolver.resolve(strežnik, "A"): + naslovi.append(i.address) +for naslov in naslovi: # opcijsko dodaj tule kakšen try catch + zone = None + zone = dns.zone.from_xfr(dns.query.xfr(naslov, domena)) + if zone != None: + break +config = None +try: + config = json.loads(b''.join(dns.resolver.resolve("_urejevalnik." + domena, "TXT")[0].strings).decode()) +except dns.resolver.NXDOMAIN: + pass +except json.decoder.JSONDecodeError: + pass +if config == None: + config = {"v": 0, "d": domena, "c": {}, "t": 1, "+": 100, "i": {}} +rrs = [] +for r in zone.iterate_rdatas(): + if r[0].to_unicode() == "_urejevalnik" or r[2].rdtype in [dns.rdatatype.RRSIG, dns.rdatatype.NSEC, dns.rdatatype.NSEC3, dns.rdatatype.DNSKEY]: + continue + commentkey = r[0].to_unicode() + " " + r[2].rdtype.name + komentar = "" + if commentkey in config["c"].keys(): + komentar = config["c"][commentkey] + del config["c"][commentkey] + konec = "\t" + if r[0].to_unicode() in config["i"].keys(): + konec = config["i"][r[0].to_unicode()] + vrednost = "" + if r[2].rdtype == dns.rdatatype.TXT: + for string in r[2].strings: + bajti = b'' + for char in string: + if char < ord(b' '): + bajti += b'\\' + ("%03d" % ord(char)).encode() + else: + bajti += bytes([char]) + niz = "" + for znak in bajti.replace(b'\\', b'\\\\').replace(b'"', b'\\"').decode('utf-8', errors='surrogateescape'): + if '\udc80' <= znak <= '\udcff': + niz += '\\'+("%03d" % (ord(znak)-0xdc00)) + else: + niz += znak + vrednost = '"' + niz + '"' + else: + vrednost = r[2].to_text() + rrs.append((r[1], r[0].to_unicode(), komentar, konec, r[2].rdclass, r[2].rdtype, vrednost)) +komentar = "" +lineno = 1 +novikomentarji = {} +novikonci = {} +keyring = None +plus = 0 +minus = 0 +if len(sys.argv) == 3: + with open(sys.argv[2]) as file: + ključ = file.read() + keyring = dns.tsigkeyring.from_text({ključ.split()[ključ.split().index("key")+1].replace('"', "").replace(";", ""): ključ.split()[ključ.split().index("secret")+1].replace('"', "").replace(";", "")}) +update = dns.update.Update(domena, keyring=keyring) +while True: + try: + lineno += 1 + line = lines.pop(0)[:-1] # odstranimo zadnji \n, ki ga zraven da .readlines + if len(line.split()) == 0 or line[0] == ';' or line[0] == '#' or line[0] == '/': + komentar += line + "\n" + continue + ime = line.split()[0] + konec = "" + index = len(ime) + while line[index] in ["\t", " "]: + konec += line[index] + index += 1 + nizi = line.split()[1:] + tip = None + razred = None + ttl = None + while tip == None: + try: + ttl = int(nizi[0]) + nizi.pop(0) + except ValueError: + pass + try: + razred = dns.rdataclass.from_text(nizi[0]) + nizi.pop(0) + except dns.rdataclass.UnknownRdataclass: + pass + try: + tip = dns.rdatatype.from_text(nizi[0]) + for i in [" ", "\t"]: + for j in [" ", "\t"]: + try: + datastart = line.index(i+nizi[0]+j)+len(i+nizi[0]+i) + except ValueError: + continue + break + else: + continue + break + nizi.pop(0) + except dns.rdatatype.UnknownRdatatype: + pass + if tip == None: + print(f"NAPAKA: na vrstici {lineno} ne najdem tipa zapisa. Vrstica je lahko bodisi komentar, ki se začne z ';', bodisi je v obliki IME [TTL={newconfig["t"]}] [CLASS=IN] TIP PODATKI.") + print(f"Vsebina neveljavne vrstice: " + line) + sys.exit(1) + while line[datastart] in [" ", "\t"]: + datastart += 1 + data = line[datastart:] + if razred == None: + razred = dns.rdataclass.IN + if ttl == None: + ttl = newconfig["t"] + ime = dns.name.from_unicode(ime, dns.name.from_unicode(domena)).choose_relativity(dns.name.from_unicode(domena), True).to_unicode() + if tip == dns.rdatatype.SOA: + data = data.split() + data[2] = str(int(data[2])+newconfig["+"]) + data = " ".join(data) + tapl = (ttl, ime, komentar, konec, razred, tip, data) + if komentar != "": + novikomentarji[ime + " " + tip.to_text(tip)] = komentar + if konec != "\t": + novikonci[ime] = konec + if not tapl in rrs: + print("+ " + komentar.replace("\n", "\n+ ") + ime + konec + str(ttl) + "\t" + razred.to_text(razred) + "\t" + tip.to_text(tip) + "\t" + data) + plus += 1 + update.add(ime, ttl, tip, data) + else: + rrs.remove(tapl) + komentar = "" + except IndexError: + break +obstoječ = "" # zadnji komentar +for komentar in config["c"].values(): + obstoječ += komentar +for rr in rrs: + print("- " + komentar.replace("\n", "\n- ") + rr[1] + konec + str(rr[0]) + "\t" + rr[4].to_text(rr[4]) + "\t" + rr[5].to_text(rr[5]) + "\t" + rr[6]) + minus += 1 + update.delete(rr[1], rr[5].to_text(rr[5]), rr[6]) +if obstoječ != komentar: + print("- " + "\n+ ".join(obstoječ.split("\n"))) + print("+ " + "\n+ ".join(komentar.split("\n"))) + plus += 1 + minus += 1 +novikomentarji["z"] = komentar +newconfig["c"] = novikomentarji +newconfig["i"] = novikonci +odziv = input(f"(-{minus}/+{plus}) Ali želite te spremembe poslati na strežnik? [Y/D/J/n] ") +if len(odziv) != 0 and odziv[0] in ["n", "N", "0", "f", "F"]: + print("Prekinjam. Nasvidenje!") + sys.exit(0) +jason = json.dumps(newconfig) +jasonsplit = " ".join(['"' + jason[i*255:i*255+255].replace("\\", "\\\\").replace('"', '\\"') + '"' for i in range(math.ceil(len(jason)/255))]) +update.replace("_urejevalnik", 1, dns.rdatatype.TXT, jasonsplit) +response = dns.query.tcp(update, naslov) +print("Poslal zahtevo. Odziv strežnika:") +print(response) -- cgit v1.2.3