#!/usr/bin/env python3 import dns from dns import rdata, rdataclass, rdatatype from dns.zone import Zone from dns.rdtypes.ANY import CNAME, MX, NS, SOA, TXT from dns.rdtypes.IN import A, AAAA SOA_SERIAL_TIME = 31695 SOA_REFRESH_TIME = 8640 SOA_RETRY_TIME = 7200 SOA_EXPIRE_TIME = 3600000 SOA_TIME_TO_LIVE = 10800 def format_rname(mail: str) -> dns.name.Name: """ Given an email address in the standard string format `mail@example.tld` return an email address in the format required by RFC 1035 `mail.example.tld.` Be careful when using this function. It is a very simple email parsing function and does support the wide range of possible emails format. It also does not check is email is valid and does not escape caracters. Return a `dns.name.Name` object. """ local, domain = mail.split("@") rname = dns.name.Name((local, *dns.name.from_text(domain))) return rname class Record: def __init__(self, name: str, dns_rdata: rdata.Rdata): self.name = name self.dns_rdata = dns_rdata self.dataclass = dns_rdata.rdclass self.datatype = dns_rdata.rdtype class IPv4Record(Record): def __init__(self, name: str, address: str): super().__init__(name, A.A(rdataclass.IN, rdatatype.A, address)) class IPv6Record(Record): def __init__(self, name: str, address: str): super().__init__(name, AAAA.AAAA(rdataclass.IN, rdatatype.AAAA, address)) class CanonicalNameRecord(Record): def __init__(self, name: str, address: str): super().__init__( name, CNAME.CNAME(rdataclass.IN, rdatatype.CNAME, dns.name.from_text(address)), ) class MailExchangeRecord(Record): def __init__(self, name: str, priority: int, exchange: str): super().__init__( name, MX.MX(rdataclass.IN, rdatatype.MX, priority, dns.name.from_text(exchange)), ) class NameServerRecord(Record): def __init__(self, name: str, address: str): super().__init__( name, NS.NS(rdataclass.IN, rdatatype.NS, dns.name.from_text(address)) ) class TextRecord(Record): def __init__(self, name: str, data: str): super().__init__(name, TXT.TXT(rdataclass.IN, rdatatype.TXT, data)) class StartOfAuthorityRecord(Record): def __init__( self, name: str, address: str, rname: str, serial=SOA_SERIAL_TIME, refresh=SOA_REFRESH_TIME, retry=SOA_RETRY_TIME, expire=SOA_EXPIRE_TIME, ttl=SOA_TIME_TO_LIVE, ): super().__init__( name, SOA.SOA( rdataclass.IN, rdatatype.SOA, dns.name.from_text(address), format_rname(rname), serial, refresh, retry, expire, ttl, ), ) def main() -> int: # Sample from "dns.auro.re.zone" file origin = "auro.re." records = [ CanonicalNameRecord("auth", "proxy-ovh.auro.re."), CanonicalNameRecord("belenios", "proxy-ovh.auro.re."), CanonicalNameRecord("cas", "proxy-ovh.auro.re."), CanonicalNameRecord("chat", "proxy-ovh.auro.re."), CanonicalNameRecord("codimd", "proxy-ovh.auro.re."), CanonicalNameRecord("dns-main", "serge.auro.re."), CanonicalNameRecord("dns-secondary", "lama.auro.re."), CanonicalNameRecord("drone", "proxy.auro.re."), CanonicalNameRecord("element", "proxy-ovh.auro.re."), CanonicalNameRecord("etherpad", "proxy-ovh.auro.re."), CanonicalNameRecord("gitea", "proxy.auro.re."), CanonicalNameRecord("grafana", "proxy.auro.re."), CanonicalNameRecord("hedgedoc", "proxy-ovh.auro.re."), CanonicalNameRecord("imap", "mail.auro.re."), CanonicalNameRecord("intranet", "proxy.auro.re."), CanonicalNameRecord("jitsi", "jitsi-aurore.auro.re."), CanonicalNameRecord("kanboard", "proxy-ovh.auro.re."), CanonicalNameRecord("litl", "proxy.auro.re."), CanonicalNameRecord("netbox", "proxy.auro.re."), CanonicalNameRecord("nextcloud", "proxy.auro.re."), CanonicalNameRecord("pad", "proxy-ovh.auro.re."), CanonicalNameRecord("passbolt", "proxy-ovh.auro.re."), CanonicalNameRecord("paste", "proxy-ovh.auro.re."), CanonicalNameRecord("phabricator", "proxy-ovh.auro.re."), CanonicalNameRecord("portail-edc", "portail.accueil.edc.auro.re."), CanonicalNameRecord("portail-fleming", "portail.accueil.fleming.auro.re."), CanonicalNameRecord("portail-gs", "portail.accueil.sand.auro.re."), CanonicalNameRecord("portail-pacaterie", "portail.accueil.pacaterie.auro.re."), CanonicalNameRecord("portail-rives", "portail.accueil.rives.auro.re."), CanonicalNameRecord("privatebin", "proxy-ovh.auro.re."), CanonicalNameRecord("radius", "radius-aurore.auro.re."), CanonicalNameRecord("re2o", "proxy.auro.re."), CanonicalNameRecord("re2o-server", "proxy.auro.re."), CanonicalNameRecord("re2o-test", "proxy.auro.re."), CanonicalNameRecord("riot", "proxy-ovh.auro.re."), CanonicalNameRecord("rss", "proxy-ovh.auro.re."), CanonicalNameRecord("sharelatex", "proxy-ovh.auro.re."), CanonicalNameRecord("smtp", "mail.auro.re."), CanonicalNameRecord("status", "proxy-ovh.auro.re."), CanonicalNameRecord("virtu", "horus.auro.re."), CanonicalNameRecord("vote", "proxy.auro.re."), CanonicalNameRecord("wiki", "proxy.auro.re."), CanonicalNameRecord("wikijs", "proxy.auro.re."), CanonicalNameRecord("www", "proxy-ovh.auro.re."), CanonicalNameRecord("zero", "proxy-ovh.auro.re."), IPv4Record("@", "92.222.211.195"), IPv4Record("camelot", "45.66.111.59"), IPv4Record("dns-aurore", "45.66.111.253"), IPv4Record("galene", "45.66.111.65"), IPv4Record("horus", "94.23.218.136"), IPv4Record("jitsi-aurore", "45.66.111.55"), IPv4Record("lama", "185.230.78.238"), IPv4Record("mail", "45.66.111.62"), IPv4Record("passerelle", "45.66.111.254"), IPv4Record("proxy", "45.66.111.61"), IPv4Record("proxy-ovh", "92.222.211.195"), IPv4Record("radius-aurore", "45.66.111.251"), IPv4Record("router-infra", "45.66.111.10"), IPv4Record("routeur-aurore", "45.66.111.240"), IPv4Record("routeur-aurore-backup", "45.66.111.140"), IPv4Record("routeur-ovh", "92.222.211.198"), IPv4Record("serge", "92.222.211.196"), IPv4Record("vpn-ovh", "92.222.211.197"), IPv6Record("camelot", "2a09:6840:111::59"), IPv6Record("dns-aurore", "2a09:6840:111::253"), IPv6Record("galene", "2a09:6840:111:0:1ccb:e1ff:feab:5d88"), IPv6Record("jitsi-aurore", "2a09:6840:111::55"), IPv6Record("lama", "2a0c:700:12:0:42:55ff:fe46:5ac5"), IPv6Record("mail", "2a09:6840:111::62"), IPv6Record("passerelle", "2a09:6840:111::254"), IPv6Record("proxy", "2a09:6840:111::61"), IPv6Record("radius-aurore", "2a09:6840:111::251"), IPv6Record("router-infra", "2a09:6841:111:0:10::"), IPv6Record("routeur-aurore", "2a09:6840:111::240"), IPv6Record("routeur-aurore-backup", "2a09:6840:111::140"), MailExchangeRecord("@", 5, "mail.auro.re."), MailExchangeRecord("@", 10, "proxy-ovh.auro.re."), NameServerRecord("@", "lama.auro.re."), NameServerRecord("@", "serge.auro.re."), StartOfAuthorityRecord("auro.re.", "serge.auro.re", "mail@fede-aurore.net"), TextRecord("@", "v=spf1 mx ~all"), ] zone = Zone(origin) for record in records: node = zone.get_node(record.name, create=True) dataset = node.get_rdataset(record.dataclass, record.datatype, create=True) dataset.add(record.dns_rdata) print(zone.to_text(relativize=False)) return 0 if __name__ == "__main__": exit(main())